Moodle code style fixes part 1

This commit is contained in:
PMKuipers 2023-08-24 23:02:41 +02:00
parent ab8a3c6f84
commit 1a3df05195
63 changed files with 4467 additions and 3885 deletions

View File

@ -33,4 +33,4 @@ Instructions for downloading and integrating bootstrap-vue and associoated files
/* eslint-disable */ /* eslint-disable */
/* /*
<content of LICENSE.md from the vue-functional-data-merge git repository> <content of LICENSE.md from the vue-functional-data-merge git repository>
*/ */

View File

@ -11,4 +11,4 @@ Instructions for downloading and integrating portal-vue
import Vue from '../vue/vue'; import Vue from '../vue/vue';
/* End modification */ /* End modification */
4. add /* eslint-disable */ to top of file 4. add /* eslint-disable */ to top of file

View File

@ -1,2 +1,2 @@
A number of simple utility scripts that could be easily re-used are collected here. A number of simple utility scripts that could be easily re-used are collected here.
Most are more like boilerplate tools than real separate scripts Most are more like boilerplate tools than real separate scripts

View File

@ -1,13 +1,35 @@
<?php <?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/>.
/**
*
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define('CLI_SCRIPT', true);
require_once("../../config.php");
$plugin = new stdClass; $plugin = new stdClass;
include('version.php'); require_once('version.php');
$a = explode("_",$plugin->component,2); $a = explode("_", $plugin->component, 2);
$plugin->type = $a[0]; $plugin->type = $a[0];
$plugin->name = $a[1]; $plugin->name = $a[1];
$exclude_paths = [ $excludepaths = [
"build", // dir for build zip files "build", // Dir for build zip files.
"build/*", "build/*",
"build.*", "build.*",
"vuemode.sh", "vuemode.sh",
@ -19,28 +41,28 @@ $exclude_paths = [
"*.zip", "*.zip",
]; ];
// Determine some paths // Determine some paths.
$wd = realpath(dirname(__FILE__)); $wd = realpath(dirname(__FILE__));
$parent = dirname($wd); $parent = dirname($wd);
$plugindirname = basename($wd); $plugindirname = basename($wd);
$builddir = $wd."/"."build"; $builddir = $wd."/"."build";
$zipname = $builddir."/"."{$plugin->name}-{$plugin->version}.zip"; $zipname = $builddir."/"."{$plugin->name}-{$plugin->version}.zip";
// create the exclude line // Create the exclude line.
$exclude = "-x "; $exclude = "-x ";
foreach($exclude_paths as $x){ foreach ($excludepaths as $x) {
$exclude .= "'{$plugindirname}/{$x}' "; $exclude .= "'{$plugindirname}/{$x}' ";
} }
if(!is_dir($builddir)){ if (!is_dir($builddir)) {
mkdir($builddir); mkdir($builddir);
if(!is_dir($builddir)){ if (!is_dir($builddir)) {
print("Cannot access dir '{$builddir}' to store zip files\n"); print("Cannot access dir '{$builddir}' to store zip files\n");
exit(1); exit(1);
} }
} }
if(file_exists($zipfile)){ if (file_exists($zipfile)) {
print("Zip file '{$zipfile}' already exists. Exiting...\n"); print("Zip file '{$zipfile}' already exists. Exiting...\n");
exit(1); exit(1);
} }

View File

@ -1,12 +1,32 @@
<?php <?php
require_once("../../config.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/>.
/**
*
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once("../../config.php");
require_once($CFG->libdir.'/adminlib.php'); require_once($CFG->libdir.'/adminlib.php');
admin_externalpage_setup("local_treestudyplan_gradeconfig"); admin_externalpage_setup("local_treestudyplan_gradeconfig");
$systemcontext = context_system::instance(); $systemcontext = context_system::instance();
// Check if user has capability to manage this // Check if user has capability to manage this.
require_capability('local/treestudyplan:configure', $systemcontext); require_capability('local/treestudyplan:configure', $systemcontext);
@ -18,186 +38,184 @@ $scales = \grade_scale::fetch_all_global();
$mappings = $DB->get_records(GRADECFG_TABLE); $mappings = $DB->get_records(GRADECFG_TABLE);
$scale_cfgs = []; $scale_cfgs = [];
$grade_cfgs = []; $grade_cfgs = [];
foreach($mappings as $cfg){ foreach ($mappings as $cfg) {
if(!empty($cfg->scale_id)){ if (!empty($cfg->scale_id)) {
$scale_cfgs[$cfg->scale_id] = $cfg; $scale_cfgs[$cfg->scale_id] = $cfg;
} }
elseif(!empty($cfg->grade_points)){ else if (!empty($cfg->grade_points)) {
$grade_cfgs[$cfg->grade_points] = $cfg; $grade_cfgs[$cfg->grade_points] = $cfg;
} }
} }
print $OUTPUT->header(); print $OUTPUT->header();
if($_POST["action"] == "update"){ if ($_POST["action"] == "update") {
// First loop through the scales to see which need to be updated // First loop through the scales to see which need to be updated.
foreach($scales as $scale) foreach ($scales as $scale) {
{ if (array_key_exists($scale->id, $scale_cfgs)) {
if(array_key_exists($scale->id,$scale_cfgs)){
$scalecfg = $scale_cfgs[$scale->id]; $scalecfg = $scale_cfgs[$scale->id];
$needupdate = false; $needupdate = false;
foreach(["min_progress", "min_completed"] as $handle) { foreach (["min_progress", "min_completed"] as $handle) {
$key = "s_{$scale->id}_{$handle}"; $key = "s_{$scale->id}_{$handle}";
if(array_key_exists($key,$_POST) && is_numeric($_POST[$key])){ if (array_key_exists($key, $_POST) && is_numeric($_POST[$key])) {
$value = intval($_POST[$key]); $value = intval($_POST[$key]);
if($value != $scalecfg->$handle){ if ($value != $scalecfg->$handle) {
$scalecfg->$handle = $value; $scalecfg->$handle = $value;
$needupdate = true; $needupdate = true;
} }
} }
} }
if($needupdate){ if ($needupdate) {
$DB->update_record(GRADECFG_TABLE,$scalecfg); $DB->update_record(GRADECFG_TABLE, $scalecfg);
} }
} }
else { else {
$scalecfg = (object)[ "scale_id" => $scale->id,]; $scalecfg = (object)[ "scale_id" => $scale->id, ];
$requireinsert = false; $requireinsert = false;
foreach(["min_progress", "min_completed"] as $handle) { foreach (["min_progress", "min_completed"] as $handle) {
$key = "s_{$scale->id}_{$handle}"; $key = "s_{$scale->id}_{$handle}";
if(array_key_exists($key,$_POST) && is_numeric($_POST[$key])){ if (array_key_exists($key, $_POST) && is_numeric($_POST[$key])) {
$scalecfg->$handle = intval($_POST[$key]); $scalecfg->$handle = intval($_POST[$key]);
$requireinsert = true; $requireinsert = true;
} }
} }
if($requireinsert){ if ($requireinsert) {
// Insert into database and add to the list of scale configs // Insert into database and add to the list of scale configs.
$id = $DB->insert_record(GRADECFG_TABLE,$scalecfg); $id = $DB->insert_record(GRADECFG_TABLE, $scalecfg);
$scalecfg = $DB->get_record(GRADECFG_TABLE,['id' => $id]); $scalecfg = $DB->get_record(GRADECFG_TABLE, ['id' => $id]);
$scale_cfgs[$id] = $scalecfg; $scale_cfgs[$id] = $scalecfg;
} }
} }
} }
// Now, loop through the gradepoints to parse // Now, loop through the gradepoints to parse .
$deletelist = []; $deletelist = [];
foreach($grade_cfgs as $gradecfg){ foreach ($grade_cfgs as $gradecfg) {
$deletekey = "g_{$gradecfg->grade_points}_delete"; $deletekey = "g_{$gradecfg->grade_points}_delete";
if(array_key_exists($deletekey,$_POST) && boolval($_POST[$deletekey]) === true){ if (array_key_exists($deletekey, $_POST) && boolval($_POST[$deletekey]) === true) {
$DB->delete_records(GRADECFG_TABLE,["id" => $gradecfg->id]); $DB->delete_records(GRADECFG_TABLE, ["id" => $gradecfg->id]);
$deletelist[] = $gradecfg; $deletelist[] = $gradecfg;
} }
else else
{ {
foreach(["min_progress", "min_completed"] as $handle) { foreach (["min_progress", "min_completed"] as $handle) {
$key = "g_{$gradecfg->grade_points}_{$handle}"; $key = "g_{$gradecfg->grade_points}_{$handle}";
if(array_key_exists($key,$_POST) && is_numeric($_POST[$key])){ if (array_key_exists($key, $_POST) && is_numeric($_POST[$key])) {
$gradecfg->$handle = floatval($_POST[$key]); $gradecfg->$handle = floatval($_POST[$key]);
} }
} }
$DB->update_record(GRADECFG_TABLE,$gradecfg); $DB->update_record(GRADECFG_TABLE, $gradecfg);
// reload to ensure proper rounding is done // reload to ensure proper rounding is done.
$grade_cfgs[$gradecfg->grade_points] = $DB->get_record(GRADECFG_TABLE,['id' => $gradecfg->id]); $grade_cfgs[$gradecfg->grade_points] = $DB->get_record(GRADECFG_TABLE, ['id' => $gradecfg->id]);
} }
} }
foreach($deletelist as $gradeconfig){ foreach ($deletelist as $gradeconfig) {
unset($grade_cfgs[$gradecfg->grade_points]); unset($grade_cfgs[$gradecfg->grade_points]);
} }
unset($deletelist); unset($deletelist);
// And add an optionally existing new gradepoint setting // And add an optionally existing new gradepoint setting.
if(array_key_exists("g_new_gradepoints",$_POST) && !empty($_POST["g_new_gradepoints"]) && is_numeric($_POST["g_new_gradepoints"]) ){ if (array_key_exists("g_new_gradepoints", $_POST) && !empty($_POST["g_new_gradepoints"]) && is_numeric($_POST["g_new_gradepoints"]) ) {
$gp = intval($_POST["g_new_gradepoints"]); $gp = intval($_POST["g_new_gradepoints"]);
if(!array_key_exists($gp,$grade_cfgs)){ if (!array_key_exists($gp, $grade_cfgs)) {
$gradecfg = (object)[ "grade_points" => $gp]; $gradecfg = (object)[ "grade_points" => $gp];
$requireinsert = false; $requireinsert = false;
foreach(["min_progress", "min_completed"] as $handle) { foreach (["min_progress", "min_completed"] as $handle) {
$key = "g_new_{$handle}"; $key = "g_new_{$handle}";
if(array_key_exists($key,$_POST) && is_numeric($_POST[$key])){ if (array_key_exists($key, $_POST) && is_numeric($_POST[$key])) {
$gradecfg->$handle = floatval($_POST[$key]); $gradecfg->$handle = floatval($_POST[$key]);
$requireinsert = true; $requireinsert = true;
} }
} }
if($requireinsert){ if ($requireinsert) {
// Insert into database and add to the list of grade configs // Insert into database and add to the list of grade configs.
$id = $DB->insert_record(GRADECFG_TABLE,$gradecfg); $id = $DB->insert_record(GRADECFG_TABLE, $gradecfg);
// reload to ensure proper rounding is done // reload to ensure proper rounding is done.
$gradecfg = $DB->get_record(GRADECFG_TABLE,['id' => $id]); $gradecfg = $DB->get_record(GRADECFG_TABLE, ['id' => $id]);
$grade_cfgs[$id] = $gradecfg; $grade_cfgs[$id] = $gradecfg;
} }
} }
} }
} }
//process all available scales and load the current configuration for it. //process all available scales and load the current configuration for it.
$data = []; $data = [];
foreach($scales as $scale) foreach ($scales as $scale) {
{
$scale->load_items(); $scale->load_items();
$scalecfg = null; $scalecfg = null;
if(array_key_exists($scale->id,$scale_cfgs)){ if (array_key_exists($scale->id, $scale_cfgs)) {
$scalecfg = $scale_cfgs[$scale->id]; $scalecfg = $scale_cfgs[$scale->id];
} }
$attrs_c = ['value' => '','disabled' => 'disabled', ]; $attrs_c = ['value' => '', 'disabled' => 'disabled', ];
$attrs_p = ['value' => '','disabled' => 'disabled', ]; $attrs_p = ['value' => '', 'disabled' => 'disabled', ];
if(!isset($scalecfg) || $scalecfg->min_completed == ""){ if (!isset($scalecfg) || $scalecfg->min_completed == "") {
$attrs_c["selected"] = "selected"; $attrs_c["selected"] = "selected";
} }
if(!isset($scalecfg) || $scalecfg->min_progress == ""){ if (!isset($scalecfg) || $scalecfg->min_progress == "") {
$attrs_p["selected"] = "selected"; $attrs_p["selected"] = "selected";
} }
$options_completed = html_writer::tag("option",get_string('select_scaleitem','local_treestudyplan'),$attrs_c); $options_completed = html_writer::tag("option", get_string('select_scaleitem', 'local_treestudyplan'), $attrs_c);
$options_progress = html_writer::tag("option",get_string('select_scaleitem','local_treestudyplan'),$attrs_p); $options_progress = html_writer::tag("option", get_string('select_scaleitem', 'local_treestudyplan'), $attrs_p);
$key = 1; // Start counting by one, as used in sum aggregations $key = 1; // Start counting by one, as used in sum aggregations.
foreach($scale->scale_items as $value){ foreach ($scale->scale_items as $value) {
$attrs_c = ["value" => $key]; $attrs_c = ["value" => $key];
$attrs_p = ["value" => $key]; $attrs_p = ["value" => $key];
if(isset($scalecfg)){ if (isset($scalecfg)) {
if(intval($scalecfg->min_completed) == $key){ if (intval($scalecfg->min_completed) == $key) {
$attrs_c["selected"] = "selected"; $attrs_c["selected"] = "selected";
} }
if(intval($scalecfg->min_progress) == $key){ if (intval($scalecfg->min_progress) == $key) {
$attrs_p["selected"] = "selected"; $attrs_p["selected"] = "selected";
} }
} }
$options_progress .= html_writer::tag("option",$value,$attrs_p); $options_progress .= html_writer::tag("option", $value, $attrs_p);
$options_completed .= html_writer::tag("option",$value,$attrs_c); $options_completed .= html_writer::tag("option", $value, $attrs_c);
$key++; $key++;
} }
$row = []; $row = [];
$row[] = $scale->name; $row[] = $scale->name;
//$row[] = html_writer::tag("select", $options_progress, ['name' => "s_{$scale->id}_min_progress",'autocomplete' => 'off']) ; //$row[] = html_writer::tag("select", $options_progress, ['name' => "s_{$scale->id}_min_progress", 'autocomplete' => 'off']) ;.
$row[] = html_writer::tag("select", $options_completed, ['name' => "s_{$scale->id}_min_completed",'autocomplete' => 'off']) ; $row[] = html_writer::tag("select", $options_completed, ['name' => "s_{$scale->id}_min_completed", 'autocomplete' => 'off']) ;
$data[] = $row; $data[] = $row;
} }
print html_writer::start_tag("form",["method" => "post",]); print html_writer::start_tag("form", ["method" => "post", ]);
print html_writer::tag("input", null, ['name' => "action", 'value' => 'update', 'type' => 'hidden']); print html_writer::tag("input", null, ['name' => "action", 'value' => 'update', 'type' => 'hidden']);
$table = new html_table(); $table = new html_table();
$table->id = ""; $table->id = "";
$table->attributes['class'] = 'generaltable m-roomtable'; $table->attributes['class'] = 'generaltable m-roomtable';
$table->tablealign = 'center'; $table->tablealign = 'center';
$table->summary = '';//get_string('uploadtimetable_preview', 'local_chronotable'); $table->summary = '';//get_string('uploadtimetable_preview', 'local_chronotable');.
$table->head = []; $table->head = [];
$table->data = $data; $table->data = $data;
$table->head[] = get_string('scale'); $table->head[] = get_string('scale');
//$table->head[] = get_string('min_progress', 'local_treestudyplan'); //$table->head[] = get_string('min_progress', 'local_treestudyplan');.
$table->head[] = get_string('min_completed', 'local_treestudyplan'); $table->head[] = get_string('min_completed', 'local_treestudyplan');
print $OUTPUT->heading(get_string('cfg_grades_desc_head', 'local_treestudyplan')); print $OUTPUT->heading(get_string('cfg_grades_desc_head', 'local_treestudyplan'));
print html_writer::tag('p', get_string('cfg_grades_desc', 'local_treestudyplan')); print html_writer::tag('p', get_string('cfg_grades_desc', 'local_treestudyplan'));
print $OUTPUT->heading(get_string('cfg_grades_scales', 'local_treestudyplan')); print $OUTPUT->heading(get_string('cfg_grades_scales', 'local_treestudyplan'));
print html_writer::tag('div', html_writer::table($table), ['class'=>'flexible-wrap']); print html_writer::tag('div', html_writer::table($table), ['class'=>'flexible-wrap']);
$data = []; $data = [];
foreach($grade_cfgs as $g){ foreach ($grade_cfgs as $g) {
$row = []; $row = [];
$row[] = $g->grade_points; $row[] = $g->grade_points;
// $row[] = html_writer::tag("input", null, ['name' => "g_{$g->grade_points}_min_progress", 'value' => "{$g->min_progress}", 'type' => 'text', "class" => "float", 'autocomplete' => 'off']) ; // $row[] = html_writer::tag("input", null, ['name' => "g_{$g->grade_points}_min_progress", 'value' => "{$g->min_progress}", 'type' => 'text', "class" => "float", 'autocomplete' => 'off']) ;.
$row[] = html_writer::tag("input", null, ['name' => "g_{$g->grade_points}_min_completed", 'value' => "{$g->min_completed}", 'type' => 'text', "class" => "float", 'autocomplete' => 'off']) ; $row[] = html_writer::tag("input", null, ['name' => "g_{$g->grade_points}_min_completed", 'value' => "{$g->min_completed}", 'type' => 'text', "class" => "float", 'autocomplete' => 'off']) ;
$row[] = html_writer::tag("input", null, ['name' => "g_{$g->grade_points}_delete", 'type' => 'checkbox', ]) ; $row[] = html_writer::tag("input", null, ['name' => "g_{$g->grade_points}_delete", 'type' => 'checkbox', ]) ;
$data[] = $row; $data[] = $row;
@ -205,8 +223,8 @@ foreach($grade_cfgs as $g){
$row = []; $row = [];
$row[] = html_writer::tag("input", null, ['name' => "g_new_gradepoints", 'value' => '', 'type' => 'number', 'min' => '0', 'pattern' => '/d+', 'step' => '1', 'autocomplete' => 'off']); $row[] = html_writer::tag("input", null, ['name' => "g_new_gradepoints", 'value' => '', 'type' => 'number', 'min' => '0', 'pattern' => '/d+', 'step' => '1', 'autocomplete' => 'off']);
//$row[] = html_writer::tag("input", null, ['name' => "g_new_min_progress", 'value' => '', 'type' => 'text', "class" => "float", 'autocomplete' => 'off']) ; //$row[] = html_writer::tag("input", null, ['name' => "g_new_min_progress", 'value' => '', 'type' => 'text', "class" => "float", 'autocomplete' => 'off']) ;.
$row[] = html_writer::tag("input", null, ['name' => "g_new_min_completed", 'value' => '', 'type' => 'text',"class" => "float", 'autocomplete' => 'off']) ; $row[] = html_writer::tag("input", null, ['name' => "g_new_min_completed", 'value' => '', 'type' => 'text', "class" => "float", 'autocomplete' => 'off']) ;
$data[] = $row; $data[] = $row;
@ -215,13 +233,13 @@ $table = new html_table();
$table->id = ""; $table->id = "";
$table->attributes['class'] = 'generaltable m-roomtable'; $table->attributes['class'] = 'generaltable m-roomtable';
$table->tablealign = 'center'; $table->tablealign = 'center';
$table->summary = '';//get_string('uploadtimetable_preview', 'local_chronotable'); $table->summary = '';//get_string('uploadtimetable_preview', 'local_chronotable');.
$table->head = []; $table->head = [];
$table->data = $data; $table->data = $data;
$table->head[] = get_string('grade_points', 'local_treestudyplan'); $table->head[] = get_string('grade_points', 'local_treestudyplan');
//$table->head[] = get_string('min_progress', 'local_treestudyplan'); //$table->head[] = get_string('min_progress', 'local_treestudyplan');.
$table->head[] = get_string('min_completed', 'local_treestudyplan'); $table->head[] = get_string('min_completed', 'local_treestudyplan');
$table->head[] = get_string('delete',); $table->head[] = get_string('delete', );
print $OUTPUT->heading(get_string('cfg_grades_grades', 'local_treestudyplan')); print $OUTPUT->heading(get_string('cfg_grades_grades', 'local_treestudyplan'));

View File

@ -1,4 +1,25 @@
<?php <?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/>.
/**
*
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace local_treestudyplan; namespace local_treestudyplan;
require_once($CFG->libdir.'/externallib.php'); require_once($CFG->libdir.'/externallib.php');
@ -7,29 +28,29 @@ abstract class aggregator {
private const FALLBACK = "bistate"; private const FALLBACK = "bistate";
private static $mod_supported = []; private static $mod_supported = [];
public static function supported($mod){ public static function supported($mod) {
if(!array_key_exists($mod,self::$mod_supported)){ if (!array_key_exists($mod, self::$mod_supported)) {
self::$mod_supported[$mod] = class_exists(self::aggregator_name($mod)); self::$mod_supported[$mod] = class_exists(self::aggregator_name($mod));
} }
return self::$mod_supported[$mod]; return self::$mod_supported[$mod];
} }
private static function aggregator_name($mod){ private static function aggregator_name($mod) {
return "\local_treestudyplan\\local\\aggregators\\{$mod}_aggregator"; return "\local_treestudyplan\\local\\aggregators\\{$mod}_aggregator";
} }
public static function list(){ public static function list() {
// static list, since we'd need to implement a lot of static data for new aggregation methods anyway // static list, since we'd need to implement a lot of static data for new aggregation methods anyway.
// and this is faster than any dynamic method. // and this is faster than any dynamic method.
return [ return [
"core", # use moodle core completion "core", # use moodle core completion
"bistate", "bistate",
"tristate", # deprecated "tristate", # deprecated
]; ];
} }
public static function create($mod,$configstr){ public static function create($mod, $configstr) {
if(self::supported($mod)){ if (self::supported($mod)) {
$ag_class = self::aggregator_name($mod); $ag_class = self::aggregator_name($mod);
return new $ag_class($configstr); return new $ag_class($configstr);
} else { } else {
@ -37,15 +58,15 @@ abstract class aggregator {
} }
} }
public static function createOrDefault($mod,$configstr){ public static function createOrDefault($mod, $configstr) {
try { try {
return self::create($mod,$configstr); return self::create($mod, $configstr);
} }
catch(\ValueError $x){ catch(\ValueError $x) {
return self::create(self::FALLBACK,""); return self::create(self::FALLBACK, "");
} }
} }
private function __construct($configstr) { private function __construct($configstr) {
$this->initialize($configstr); $this->initialize($configstr);
} }
@ -59,60 +80,60 @@ abstract class aggregator {
public abstract function grade_completion(gradeinfo $gradeinfo, $userid); public abstract function grade_completion(gradeinfo $gradeinfo, $userid);
// Aggregation method makes use of "required grades" in a course/module // Aggregation method makes use of "required grades" in a course/module.
public abstract function useRequiredGrades(); public abstract function useRequiredGrades();
// Aggregation method makes use of // Aggregation method makes use of .
public abstract function useItemConditions(); public abstract function useItemConditions();
// Whether the aggregation method uses core_completion, or treestudyplan custom completion // Whether the aggregation method uses core_completion, or treestudyplan custom completion.
public function usecorecompletioninfo(){ public function usecorecompletioninfo() {
return false; return false;
} }
// Parameter editing functions - override in child class to implement parameter config for aggregation // Parameter editing functions - override in child class to implement parameter config for aggregation.
// Return the current configuration string // Return the current configuration string.
public function config_string() { public function config_string() {
return ""; return "";
} }
public static function basic_structure($value=VALUE_REQUIRED){ public static function basic_structure($value=VALUE_REQUIRED) {
return new \external_single_structure([ return new \external_single_structure([
"useRequiredGrades" => new \external_value(PARAM_BOOL, 'id of studyplan'), "useRequiredGrades" => new \external_value(PARAM_BOOL, 'id of studyplan'),
"useItemConditions" => new \external_value(PARAM_BOOL, 'name of studyplan'), "useItemConditions" => new \external_value(PARAM_BOOL, 'name of studyplan'),
],"Aggregator requirements",$value); ], "Aggregator requirements", $value);
} }
public function basic_model(){ public function basic_model() {
return [ return [
"useRequiredGrades" => $this->useRequiredGrades(), "useRequiredGrades" => $this->useRequiredGrades(),
"useItemConditions" => $this->useItemConditions(), "useItemConditions" => $this->useItemConditions(),
]; ];
} }
public static function list_structure($value=VALUE_REQUIRED){ public static function list_structure($value=VALUE_REQUIRED) {
return new \external_multiple_structure(new \external_single_structure([ return new \external_multiple_structure(new \external_single_structure([
"id" => new \external_value(PARAM_TEXT, 'id of aggregator'), "id" => new \external_value(PARAM_TEXT, 'id of aggregator'),
"name" => new \external_value(PARAM_TEXT, 'name of agregator'), "name" => new \external_value(PARAM_TEXT, 'name of agregator'),
"deprecated" => new \external_value(PARAM_BOOL, 'if method is deprecated'), "deprecated" => new \external_value(PARAM_BOOL, 'if method is deprecated'),
"defaultconfig" => new \external_value(PARAM_TEXT, 'default config of agregator'), "defaultconfig" => new \external_value(PARAM_TEXT, 'default config of agregator'),
],"Available aggregators",$value)); ], "Available aggregators", $value));
} }
public static function list_model(){ public static function list_model() {
$list = []; $list = [];
foreach(self::list() as $agid){ foreach (self::list() as $agid) {
$a = self::create($agid,""); // create new one with empty config string $a = self::create($agid, ""); // create new one with empty config string.
$list[] = [ $list[] = [
'id' => $agid, 'id' => $agid,
'name' => get_string("{$agid}_aggregator_title","local_treestudyplan"), 'name' => get_string("{$agid}_aggregator_title", "local_treestudyplan"),
'deprecated' => $a->isDeprecated(), 'deprecated' => $a->isDeprecated(),
'defaultconfig' => $a->config_string(), 'defaultconfig' => $a->config_string(),
]; ];
} }
return $list; return $list;
} }

View File

@ -1,16 +1,37 @@
<?php <?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/>.
/**
*
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace local_treestudyplan; namespace local_treestudyplan;
use local_treestudyplan\local\helpers\webservicehelper; use local_treestudyplan\local\helpers\webservicehelper;
require_once($CFG->libdir.'/externallib.php'); require_once($CFG->libdir.'/externallib.php');
class associationservice extends \external_api class associationservice extends \external_api
{ {
const CAP_EDIT = "local/treestudyplan:editstudyplan"; const CAP_EDIT = "local/treestudyplan:editstudyplan";
const CAP_VIEW = "local/treestudyplan:viewuserreports"; const CAP_VIEW = "local/treestudyplan:viewuserreports";
public static function user_structure(){ public static function user_structure() {
return new \external_single_structure([ return new \external_single_structure([
"id" => new \external_value(PARAM_INT, 'user id'), "id" => new \external_value(PARAM_INT, 'user id'),
"username" => new \external_value(PARAM_TEXT, 'username'), "username" => new \external_value(PARAM_TEXT, 'username'),
@ -21,7 +42,7 @@ class associationservice extends \external_api
]); ]);
} }
public static function make_user_model($r){ public static function make_user_model($r) {
return [ return [
"id" => $r->id, "id" => $r->id,
"username" => $r->username, "username" => $r->username,
@ -32,7 +53,7 @@ class associationservice extends \external_api
]; ];
} }
public static function cohort_structure(){ public static function cohort_structure() {
return new \external_single_structure([ return new \external_single_structure([
"id" => new \external_value(PARAM_INT, 'cohort id'), "id" => new \external_value(PARAM_INT, 'cohort id'),
"name" => new \external_value(PARAM_TEXT, 'name'), "name" => new \external_value(PARAM_TEXT, 'name'),
@ -48,15 +69,15 @@ class associationservice extends \external_api
]); ]);
} }
public static function make_cohort_model($r){ public static function make_cohort_model($r) {
global $DB; global $DB;
$ctx = \context::instance_by_id($r->contextid); $ctx = \context::instance_by_id($r->contextid);
$ctxPath = array_reverse($ctx->get_parent_context_ids(true)); $ctxPath = array_reverse($ctx->get_parent_context_ids(true));
if(count($ctxPath) > 1 && $ctxPath[0] == 1) { if (count($ctxPath) > 1 && $ctxPath[0] == 1) {
array_shift($ctxPath); array_shift($ctxPath);
} }
$result = [ $result = [
"id" => $r->id, "id" => $r->id,
"name" => $r->name, "name" => $r->name,
@ -64,59 +85,59 @@ class associationservice extends \external_api
"description" => $r->description, "description" => $r->description,
"visible" => $r->visible, "visible" => $r->visible,
"context" => [ "context" => [
"name" => $ctx->get_context_name(false,false), "name" => $ctx->get_context_name(false, false),
"shortname" => $ctx->get_context_name(false,true), "shortname" => $ctx->get_context_name(false, true),
"path" => array_map(function($c){ return \context::instance_by_id($c)->get_context_name(false,false);},$ctxPath), "path" => array_map(function($c) { return \context::instance_by_id($c)->get_context_name(false, false);}, $ctxPath),
"shortpath" => array_map(function($c){ return \context::instance_by_id($c)->get_context_name(false,true);},$ctxPath), "shortpath" => array_map(function($c) { return \context::instance_by_id($c)->get_context_name(false, true);}, $ctxPath),
] ]
]; ];
return $result; return $result;
} }
public static function list_cohort_parameters() public static function list_cohort_parameters()
{ {
return new \external_function_parameters( [ return new \external_function_parameters( [
'like' => new \external_value(PARAM_TEXT, 'search text', VALUE_OPTIONAL), 'like' => new \external_value(PARAM_TEXT, 'search text', VALUE_OPTIONAL),
'exclude_id' => new \external_value(PARAM_INT, 'exclude members of this studyplan', VALUE_OPTIONAL), 'exclude_id' => new \external_value(PARAM_INT, 'exclude members of this studyplan', VALUE_OPTIONAL),
'context_ic' => new \external_value(PARAM_INT, 'context for this request', VALUE_OPTIONAL), 'context_ic' => new \external_value(PARAM_INT, 'context for this request', VALUE_OPTIONAL),
] ); ] );
} }
public static function list_cohort_returns() public static function list_cohort_returns()
{ {
return new \external_multiple_structure(self::cohort_structure()); return new \external_multiple_structure(self::cohort_structure());
} }
// Actual functions // Actual functions.
public static function list_cohort($like='',$exclude_id=null,$context_id=1) public static function list_cohort($like='', $exclude_id=null, $context_id=1)
{ {
global $CFG, $DB; global $CFG, $DB;
// Only allow this if the user has the right to edit in this context // Only allow this if the user has the right to edit in this context.
$context = webservicehelper::find_context($context_id); $context = webservicehelper::find_context($context_id);
webservicehelper::require_capabilities(self::CAP_EDIT, $context); webservicehelper::require_capabilities(self::CAP_EDIT, $context);
$pattern = "%{$like}%"; $pattern = "%{$like}%";
$params = ["pattern_nm" => $pattern,"pattern_id" => $pattern,]; $params = ["pattern_nm" => $pattern, "pattern_id" => $pattern, ];
$sql = "SELECT c.* from {cohort} c LEFT JOIN {local_treestudyplan_cohort} j ON c.id = j.cohort_id"; $sql = "SELECT c.* from {cohort} c LEFT JOIN {local_treestudyplan_cohort} j ON c.id = j.cohort_id";
$sql .= " WHERE c.visible = 1 AND(name LIKE :pattern_nm OR idnumber LIKE :pattern_id)"; $sql .= " WHERE c.visible = 1 AND(name LIKE :pattern_nm OR idnumber LIKE :pattern_id)";
if(isset($exclude_id) && is_numeric($exclude_id)){ if (isset($exclude_id) && is_numeric($exclude_id)) {
$sql .= " AND (j.studyplan_id IS NULL OR j.studyplan_id != :exclude_id)"; $sql .= " AND (j.studyplan_id IS NULL OR j.studyplan_id != :exclude_id)";
$params['exclude_id'] = $exclude_id; $params['exclude_id'] = $exclude_id;
} }
if($context_id > 1){ // system context returns all cohorts, including system cohorts if ($context_id > 1) { // system context returns all cohorts, including system cohorts.
// otherwise, // otherwise, .
$sql .= " AND contextid = :context_id"; $sql .= " AND contextid = :context_id";
$params['context_id'] = $context_id; $params['context_id'] = $context_id;
} }
$cohorts = []; $cohorts = [];
$rs = $DB->get_recordset_sql($sql,$params); $rs = $DB->get_recordset_sql($sql, $params);
foreach ($rs as $r) { foreach ($rs as $r) {
$cohorts[] = static::make_cohort_model($r); $cohorts[] = static::make_cohort_model($r);
} }
@ -124,28 +145,28 @@ class associationservice extends \external_api
return $cohorts; return $cohorts;
} }
public static function find_user_parameters() public static function find_user_parameters()
{ {
return new \external_function_parameters( [ return new \external_function_parameters( [
'like' => new \external_value(PARAM_TEXT, 'search text'), 'like' => new \external_value(PARAM_TEXT, 'search text'),
'exclude_id' => new \external_value(PARAM_INT, 'exclude members of this studyplan', VALUE_OPTIONAL), 'exclude_id' => new \external_value(PARAM_INT, 'exclude members of this studyplan', VALUE_OPTIONAL),
'context_id' => new \external_value(PARAM_INT, 'context for this request', VALUE_OPTIONAL), 'context_id' => new \external_value(PARAM_INT, 'context for this request', VALUE_OPTIONAL),
] ); ] );
} }
public static function find_user_returns() public static function find_user_returns()
{ {
return new \external_multiple_structure(self::user_structure()); return new \external_multiple_structure(self::user_structure());
} }
// Actual functions // Actual functions.
public static function find_user($like,$exclude_id=null,$context_id=1) public static function find_user($like, $exclude_id=null, $context_id=1)
{ {
global $CFG, $DB; global $CFG, $DB;
// Only allow this if the user has the right to edit in this context (using system rights would make things more confusing) // Only allow this if the user has the right to edit in this context (using system rights would make things more confusing).
$context = webservicehelper::find_context($context_id); $context = webservicehelper::find_context($context_id);
webservicehelper::require_capabilities(self::CAP_EDIT,$context); webservicehelper::require_capabilities(self::CAP_EDIT, $context);
$pattern = "%{$like}%"; $pattern = "%{$like}%";
$params = ["pattern_fn" => $pattern, $params = ["pattern_fn" => $pattern,
@ -154,10 +175,10 @@ class associationservice extends \external_api
]; ];
$sql = "SELECT u.* from {user} u LEFT JOIN {local_treestudyplan_user} j ON u.id = j.user_id"; $sql = "SELECT u.* from {user} u LEFT JOIN {local_treestudyplan_user} j ON u.id = j.user_id";
$sql .= " WHERE u.deleted != 1 AND (firstname LIKE :pattern_fn OR lastname LIKE :pattern_ln OR username LIKE :pattern_un)"; $sql .= " WHERE u.deleted != 1 AND (firstname LIKE :pattern_fn OR lastname LIKE :pattern_ln OR username LIKE :pattern_un)";
if(isset($exclude_id) && is_numeric($exclude_id)){ if (isset($exclude_id) && is_numeric($exclude_id)) {
$sql .= " AND (j.studyplan_id IS NULL OR j.studyplan_id != :exclude_id)"; $sql .= " AND (j.studyplan_id IS NULL OR j.studyplan_id != :exclude_id)";
$params['exclude_id'] = $exclude_id; $params['exclude_id'] = $exclude_id;
} }
$users = []; $users = [];
$rs = $DB->get_recordset_sql($sql, $params); $rs = $DB->get_recordset_sql($sql, $params);
@ -170,7 +191,7 @@ class associationservice extends \external_api
return $users; return $users;
} }
public static function connect_cohort_parameters() public static function connect_cohort_parameters()
{ {
return new \external_function_parameters( [ return new \external_function_parameters( [
"studyplan_id" => new \external_value(PARAM_INT, 'id of studyplan', VALUE_OPTIONAL), "studyplan_id" => new \external_value(PARAM_INT, 'id of studyplan', VALUE_OPTIONAL),
@ -178,7 +199,7 @@ class associationservice extends \external_api
] ); ] );
} }
public static function connect_cohort_returns() public static function connect_cohort_returns()
{ {
return new \external_single_structure([ return new \external_single_structure([
"success" => new \external_value(PARAM_BOOL, 'operation completed succesfully'), "success" => new \external_value(PARAM_BOOL, 'operation completed succesfully'),
@ -186,18 +207,17 @@ class associationservice extends \external_api
]); ]);
} }
// Actual functions // Actual functions.
public static function connect_cohort($studyplan_id,$cohort_id) public static function connect_cohort($studyplan_id, $cohort_id)
{ {
global $CFG, $DB; global $CFG, $DB;
$studyplan = studyplan::findById($studyplan_id); $studyplan = studyplan::findById($studyplan_id);
webservicehelper::require_capabilities(self::CAP_EDIT,$studyplan->context()); webservicehelper::require_capabilities(self::CAP_EDIT, $studyplan->context());
if(!$DB->record_exists('local_treestudyplan_cohort', ['studyplan_id' => $studyplan_id, 'cohort_id' => $cohort_id])) if (!$DB->record_exists('local_treestudyplan_cohort', ['studyplan_id' => $studyplan_id, 'cohort_id' => $cohort_id])) {
{
$id = $DB->insert_record('local_treestudyplan_cohort', [ $id = $DB->insert_record('local_treestudyplan_cohort', [
'studyplan_id' => $studyplan_id, 'studyplan_id' => $studyplan_id,
'cohort_id' => $cohort_id, 'cohort_id' => $cohort_id,
]); ]);
@ -210,7 +230,7 @@ class associationservice extends \external_api
} }
public static function disconnect_cohort_parameters() public static function disconnect_cohort_parameters()
{ {
return new \external_function_parameters( [ return new \external_function_parameters( [
"studyplan_id" => new \external_value(PARAM_INT, 'id of studyplan', VALUE_OPTIONAL), "studyplan_id" => new \external_value(PARAM_INT, 'id of studyplan', VALUE_OPTIONAL),
@ -218,7 +238,7 @@ class associationservice extends \external_api
] ); ] );
} }
public static function disconnect_cohort_returns() public static function disconnect_cohort_returns()
{ {
return new \external_single_structure([ return new \external_single_structure([
"success" => new \external_value(PARAM_BOOL, 'operation completed succesfully'), "success" => new \external_value(PARAM_BOOL, 'operation completed succesfully'),
@ -226,21 +246,20 @@ class associationservice extends \external_api
]); ]);
} }
// Actual functions // Actual functions.
public static function disconnect_cohort($studyplan_id,$cohort_id) public static function disconnect_cohort($studyplan_id, $cohort_id)
{ {
global $CFG, $DB; global $CFG, $DB;
$studyplan = studyplan::findById($studyplan_id); $studyplan = studyplan::findById($studyplan_id);
webservicehelper::require_capabilities(self::CAP_EDIT,$studyplan->context()); webservicehelper::require_capabilities(self::CAP_EDIT, $studyplan->context());
if($DB->record_exists('local_treestudyplan_cohort', ['studyplan_id' => $studyplan_id, 'cohort_id' => $cohort_id])) if ($DB->record_exists('local_treestudyplan_cohort', ['studyplan_id' => $studyplan_id, 'cohort_id' => $cohort_id])) {
{
$DB->delete_records('local_treestudyplan_cohort', [ $DB->delete_records('local_treestudyplan_cohort', [
'studyplan_id' => $studyplan_id, 'studyplan_id' => $studyplan_id,
'cohort_id' => $cohort_id, 'cohort_id' => $cohort_id,
]); ]);
$studyplan->mark_csync_changed(); $studyplan->mark_csync_changed();
return ['success' => true, 'msg'=>'Cohort Disconnected']; return ['success' => true, 'msg'=>'Cohort Disconnected'];
@ -248,9 +267,9 @@ class associationservice extends \external_api
return ['success' => true, 'msg'=>'Connection does not exist']; return ['success' => true, 'msg'=>'Connection does not exist'];
} }
} }
public static function connect_user_parameters() public static function connect_user_parameters()
{ {
return new \external_function_parameters( [ return new \external_function_parameters( [
"studyplan_id" => new \external_value(PARAM_INT, 'id of studyplan', VALUE_OPTIONAL), "studyplan_id" => new \external_value(PARAM_INT, 'id of studyplan', VALUE_OPTIONAL),
@ -258,7 +277,7 @@ class associationservice extends \external_api
] ); ] );
} }
public static function connect_user_returns() public static function connect_user_returns()
{ {
return new \external_single_structure([ return new \external_single_structure([
"success" => new \external_value(PARAM_BOOL, 'operation completed succesfully'), "success" => new \external_value(PARAM_BOOL, 'operation completed succesfully'),
@ -266,18 +285,17 @@ class associationservice extends \external_api
]); ]);
} }
// Actual functions // Actual functions.
public static function connect_user($studyplan_id,$user_id) public static function connect_user($studyplan_id, $user_id)
{ {
global $CFG, $DB; global $CFG, $DB;
$studyplan = studyplan::findById($studyplan_id); $studyplan = studyplan::findById($studyplan_id);
webservicehelper::require_capabilities(self::CAP_EDIT,$studyplan->context()); webservicehelper::require_capabilities(self::CAP_EDIT, $studyplan->context());
if(!$DB->record_exists('local_treestudyplan_user', ['studyplan_id' => $studyplan_id, 'user_id' => $user_id])) if (!$DB->record_exists('local_treestudyplan_user', ['studyplan_id' => $studyplan_id, 'user_id' => $user_id])) {
{
$id = $DB->insert_record('local_treestudyplan_user', [ $id = $DB->insert_record('local_treestudyplan_user', [
'studyplan_id' => $studyplan_id, 'studyplan_id' => $studyplan_id,
'user_id' => $user_id, 'user_id' => $user_id,
]); ]);
$studyplan->mark_csync_changed(); $studyplan->mark_csync_changed();
@ -289,7 +307,7 @@ class associationservice extends \external_api
} }
} }
public static function disconnect_user_parameters() public static function disconnect_user_parameters()
{ {
return new \external_function_parameters( [ return new \external_function_parameters( [
"studyplan_id" => new \external_value(PARAM_INT, 'id of studyplan', VALUE_OPTIONAL), "studyplan_id" => new \external_value(PARAM_INT, 'id of studyplan', VALUE_OPTIONAL),
@ -297,7 +315,7 @@ class associationservice extends \external_api
] ); ] );
} }
public static function disconnect_user_returns() public static function disconnect_user_returns()
{ {
return new \external_single_structure([ return new \external_single_structure([
"success" => new \external_value(PARAM_BOOL, 'operation completed succesfully'), "success" => new \external_value(PARAM_BOOL, 'operation completed succesfully'),
@ -305,17 +323,16 @@ class associationservice extends \external_api
]); ]);
} }
// Actual functions // Actual functions.
public static function disconnect_user($studyplan_id,$user_id) public static function disconnect_user($studyplan_id, $user_id)
{ {
global $CFG, $DB; global $CFG, $DB;
$studyplan = studyplan::findById($studyplan_id); $studyplan = studyplan::findById($studyplan_id);
webservicehelper::require_capabilities(self::CAP_EDIT,$studyplan->context()); webservicehelper::require_capabilities(self::CAP_EDIT, $studyplan->context());
if($DB->record_exists('local_treestudyplan_user', ['studyplan_id' => $studyplan_id, 'user_id' => $user_id])) if ($DB->record_exists('local_treestudyplan_user', ['studyplan_id' => $studyplan_id, 'user_id' => $user_id])) {
{
$DB->delete_records('local_treestudyplan_user', [ $DB->delete_records('local_treestudyplan_user', [
'studyplan_id' => $studyplan_id, 'studyplan_id' => $studyplan_id,
'user_id' => $user_id, 'user_id' => $user_id,
]); ]);
@ -327,32 +344,31 @@ class associationservice extends \external_api
} }
} }
public static function associated_users_parameters() public static function associated_users_parameters()
{ {
return new \external_function_parameters( [ return new \external_function_parameters( [
"studyplan_id" => new \external_value(PARAM_INT, 'id of studyplan', VALUE_OPTIONAL), "studyplan_id" => new \external_value(PARAM_INT, 'id of studyplan', VALUE_OPTIONAL),
] ); ] );
} }
public static function associated_users_returns() public static function associated_users_returns()
{ {
return new \external_multiple_structure(self::user_structure()); return new \external_multiple_structure(self::user_structure());
} }
// Actual functions // Actual functions.
public static function associated_users($studyplan_id) public static function associated_users($studyplan_id)
{ {
global $CFG, $DB; global $CFG, $DB;
$studyplan = studyplan::findById($studyplan_id); $studyplan = studyplan::findById($studyplan_id);
webservicehelper::require_capabilities(self::CAP_VIEW,$studyplan->context()); webservicehelper::require_capabilities(self::CAP_VIEW, $studyplan->context());
$sql = "SELECT DISTINCT u.* FROM {user} u INNER JOIN {local_treestudyplan_user} j ON j.user_id = u.id"; $sql = "SELECT DISTINCT u.* FROM {user} u INNER JOIN {local_treestudyplan_user} j ON j.user_id = u.id";
$sql .= " WHERE j.studyplan_id = :studyplan_id"; $sql .= " WHERE j.studyplan_id = :studyplan_id";
$rs = $DB->get_recordset_sql($sql, ['studyplan_id' => $studyplan_id]); $rs = $DB->get_recordset_sql($sql, ['studyplan_id' => $studyplan_id]);
$users = []; $users = [];
foreach($rs as $u) foreach ($rs as $u) {
{
$users[] = self::make_user_model($u); $users[] = self::make_user_model($u);
} }
$rs->close(); $rs->close();
@ -360,31 +376,30 @@ class associationservice extends \external_api
return $users; return $users;
} }
public static function associated_cohorts_parameters() public static function associated_cohorts_parameters()
{ {
return new \external_function_parameters( [ return new \external_function_parameters( [
"studyplan_id" => new \external_value(PARAM_INT, 'id of studyplan', VALUE_OPTIONAL), "studyplan_id" => new \external_value(PARAM_INT, 'id of studyplan', VALUE_OPTIONAL),
] ); ] );
} }
public static function associated_cohorts_returns() public static function associated_cohorts_returns()
{ {
return new \external_multiple_structure(self::cohort_structure()); return new \external_multiple_structure(self::cohort_structure());
} }
// Actual functions // Actual functions.
public static function associated_cohorts($studyplan_id) public static function associated_cohorts($studyplan_id)
{ {
global $CFG, $DB; global $CFG, $DB;
$studyplan = studyplan::findById($studyplan_id); $studyplan = studyplan::findById($studyplan_id);
webservicehelper::require_capabilities(self::CAP_VIEW,$studyplan->context()); webservicehelper::require_capabilities(self::CAP_VIEW, $studyplan->context());
$sql = "SELECT DISTINCT c.* FROM {cohort} c INNER JOIN {local_treestudyplan_cohort} j ON j.cohort_id = c.id"; $sql = "SELECT DISTINCT c.* FROM {cohort} c INNER JOIN {local_treestudyplan_cohort} j ON j.cohort_id = c.id";
$sql .= " WHERE j.studyplan_id = :studyplan_id"; $sql .= " WHERE j.studyplan_id = :studyplan_id";
$rs = $DB->get_recordset_sql($sql, ['studyplan_id' => $studyplan_id]); $rs = $DB->get_recordset_sql($sql, ['studyplan_id' => $studyplan_id]);
$cohorts = []; $cohorts = [];
foreach($rs as $c) foreach ($rs as $c) {
{
$cohorts[] = self::make_cohort_model($c); $cohorts[] = self::make_cohort_model($c);
} }
$rs->close(); $rs->close();
@ -392,42 +407,41 @@ class associationservice extends \external_api
} }
public static function all_associated_parameters() public static function all_associated_parameters()
{ {
return new \external_function_parameters( [ return new \external_function_parameters( [
"studyplan_id" => new \external_value(PARAM_INT, 'id of studyplan', VALUE_OPTIONAL), "studyplan_id" => new \external_value(PARAM_INT, 'id of studyplan', VALUE_OPTIONAL),
] ); ] );
} }
public static function all_associated_returns() public static function all_associated_returns()
{ {
return new \external_multiple_structure(self::user_structure()); return new \external_multiple_structure(self::user_structure());
} }
// Actual functions // Actual functions.
public static function all_associated($studyplan_id) public static function all_associated($studyplan_id)
{ {
global $CFG, $DB; global $CFG, $DB;
$studyplan = studyplan::findById($studyplan_id); $studyplan = studyplan::findById($studyplan_id);
webservicehelper::require_capabilities(self::CAP_VIEW,$studyplan->context()); webservicehelper::require_capabilities(self::CAP_VIEW, $studyplan->context());
$users = []; $users = [];
// SQL JOIN script selecting all users that have a cohort linked to this studyplan // SQL JOIN script selecting all users that have a cohort linked to this studyplan .
// or are directly linked // or are directly linked.
$sql = "SELECT DISTINCT u.id, u.username, u.firstname, u.lastname, u.idnumber, u.email $sql = "SELECT DISTINCT u.id, u.username, u.firstname, u.lastname, u.idnumber, u.email
FROM {user} u FROM {user} u
LEFT JOIN {cohort_members} cm ON u.id = cm.userid LEFT JOIN {cohort_members} cm ON u.id = cm.userid
LEFT JOIN {local_treestudyplan_cohort} tc ON cm.cohortid = tc.cohort_id LEFT JOIN {local_treestudyplan_cohort} tc ON cm.cohortid = tc.cohort_id
LEFT JOIN {local_treestudyplan_user} tu ON u.id = tu.user_id LEFT JOIN {local_treestudyplan_user} tu ON u.id = tu.user_id
WHERE tc.studyplan_id = {$studyplan_id} WHERE tc.studyplan_id = {$studyplan_id}
OR tu.studyplan_id = {$studyplan_id} OR tu.studyplan_id = {$studyplan_id}
ORDER BY u.lastname, u.firstname"; ORDER BY u.lastname, u.firstname";
$rs = $DB->get_recordset_sql($sql); $rs = $DB->get_recordset_sql($sql);
foreach($rs as $u) foreach ($rs as $u) {
{
$users[] = self::make_user_model($u); $users[] = self::make_user_model($u);
} }
$rs->close(); $rs->close();
@ -436,15 +450,15 @@ class associationservice extends \external_api
return $users; return $users;
} }
public static function sortusermodels(&$list){ public static function sortusermodels(&$list) {
return usort($list,function($a,$b){ return usort($list, function($a, $b) {
$m= []; $m= [];
if(preg_match("/.*?([A-Z].*)/",$a['lastname'],$m)){ if (preg_match("/.*?([A-Z].*)/", $a['lastname'], $m)) {
$sort_ln_a = $m[1]; $sort_ln_a = $m[1];
} else { } else {
$sort_ln_a = $a['lastname']; $sort_ln_a = $a['lastname'];
} }
if(preg_match("/.*?([A-Z].*)/",$b['lastname'],$m)){ if (preg_match("/.*?([A-Z].*)/", $b['lastname'], $m)) {
$sort_ln_b = $m[1]; $sort_ln_b = $m[1];
} else { } else {
$sort_ln_b = $b['lastname']; $sort_ln_b = $b['lastname'];
@ -454,32 +468,32 @@ class associationservice extends \external_api
}); });
} }
public static function cascade_cohortsync_parameters() public static function cascade_cohortsync_parameters()
{ {
return new \external_function_parameters( [ return new \external_function_parameters( [
"studyplan_id" => new \external_value(PARAM_INT, 'id of studyplan', VALUE_OPTIONAL), "studyplan_id" => new \external_value(PARAM_INT, 'id of studyplan', VALUE_OPTIONAL),
] ); ] );
} }
public static function cascade_cohortsync_returns() public static function cascade_cohortsync_returns()
{ {
return success::structure(); return success::structure();
} }
// Actual functions // Actual functions.
public static function cascade_cohortsync($studyplan_id) public static function cascade_cohortsync($studyplan_id)
{ {
$studyplan = studyplan::findById($studyplan_id); $studyplan = studyplan::findById($studyplan_id);
webservicehelper::require_capabilities(self::CAP_EDIT,$studyplan->context()); webservicehelper::require_capabilities(self::CAP_EDIT, $studyplan->context());
$enroller = new cascadecohortsync($studyplan); $enroller = new cascadecohortsync($studyplan);
$enroller->sync(); $enroller->sync();
if(get_config("local_treestudyplan","csync_users")){ if (get_config("local_treestudyplan", "csync_users")) {
$userenroller = new cascadeusersync($studyplan); $userenroller = new cascadeusersync($studyplan);
$userenroller->sync(); $userenroller->sync();
} }
$studyplan->clear_csync_changed(); // Clear the csync required flag $studyplan->clear_csync_changed(); // Clear the csync required flag.
return success::success()->model(); return success::success()->model();

View File

@ -1,9 +1,30 @@
<?php <?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/>.
/**
*
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace local_treestudyplan; namespace local_treestudyplan;
require_once($CFG->libdir.'/externallib.php'); require_once($CFG->libdir.'/externallib.php');
class badgeinfo { class badgeinfo {
private $badge; // Holds database record private $badge; // Holds database record.
private const STATUSINFO = [ private const STATUSINFO = [
BADGE_STATUS_INACTIVE => 'inactive', BADGE_STATUS_INACTIVE => 'inactive',
@ -17,7 +38,7 @@ class badgeinfo {
BADGE_STATUS_ACTIVE => 0, BADGE_STATUS_ACTIVE => 0,
BADGE_STATUS_INACTIVE_LOCKED => 1, BADGE_STATUS_INACTIVE_LOCKED => 1,
BADGE_STATUS_ACTIVE_LOCKED => 1, BADGE_STATUS_ACTIVE_LOCKED => 1,
BADGE_STATUS_ARCHIVED => 1, // We don't want to edit archived badges anyway.... BADGE_STATUS_ARCHIVED => 1, // We don't want to edit archived badges anyway.... .
]; ];
public function __construct(\core_badges\badge $badge) { public function __construct(\core_badges\badge $badge) {
@ -25,43 +46,42 @@ class badgeinfo {
$this->badge = $badge; $this->badge = $badge;
} }
public function name(){ public function name() {
return $this->badge->name; return $this->badge->name;
} }
public static function id_from_name($name){ public static function id_from_name($name) {
global $DB; global $DB;
return $DB->get_field("badge", "id", ['name' => $name]); return $DB->get_field("badge", "id", ['name' => $name]);
} }
public static function exists($id){ public static function exists($id) {
global $DB; global $DB;
return is_numeric($id) && $DB->record_exists('badge', array('id' => $id)); return is_numeric($id) && $DB->record_exists('badge', array('id' => $id));
} }
public static function editor_structure($value=VALUE_REQUIRED){ public static function editor_structure($value=VALUE_REQUIRED) {
return new \external_single_structure([ return new \external_single_structure([
"id" => new \external_value(PARAM_INT, 'id of badge'), "id" => new \external_value(PARAM_INT, 'id of badge'),
"infolink" => new \external_value(PARAM_TEXT, 'badge issue information link', VALUE_OPTIONAL), "infolink" => new \external_value(PARAM_TEXT, 'badge issue information link', VALUE_OPTIONAL),
"name" => new \external_value(PARAM_TEXT, 'badge name'), "name" => new \external_value(PARAM_TEXT, 'badge name'),
"status" => new \external_value(PARAM_TEXT, 'badge status'), "status" => new \external_value(PARAM_TEXT, 'badge status'),
"locked" => new \external_value(PARAM_TEXT, 'badge lock status'), "locked" => new \external_value(PARAM_TEXT, 'badge lock status'),
"criteria" => new \external_multiple_structure(new \external_value(PARAM_RAW, 'criteria text'),'badge criteria',VALUE_OPTIONAL), "criteria" => new \external_multiple_structure(new \external_value(PARAM_RAW, 'criteria text'), 'badge criteria', VALUE_OPTIONAL),
"description"=> new \external_value(PARAM_TEXT, 'badge description'), "description"=> new \external_value(PARAM_TEXT, 'badge description'),
"imageurl" => new \external_value(PARAM_TEXT, 'url of badge image'), "imageurl" => new \external_value(PARAM_TEXT, 'url of badge image'),
"studentcount" => new \external_value(PARAM_INT, 'number of studyplan students that can get this badge',VALUE_OPTIONAL), "studentcount" => new \external_value(PARAM_INT, 'number of studyplan students that can get this badge', VALUE_OPTIONAL),
"issuedcount" => new \external_value(PARAM_INT, 'number of studyplan students that have got this badge',VALUE_OPTIONAL), "issuedcount" => new \external_value(PARAM_INT, 'number of studyplan students that have got this badge', VALUE_OPTIONAL),
],"Badge info",$value); ], "Badge info", $value);
} }
public function editor_model(array $studentlist=null) public function editor_model(array $studentlist=null) {
{
$context = ($this->badge->type == BADGE_TYPE_SITE) ? \context_system::instance() : \context_course::instance($this->badge->courseid); $context = ($this->badge->type == BADGE_TYPE_SITE) ? \context_system::instance() : \context_course::instance($this->badge->courseid);
// If the user is viewing another user's badge and doesn't have the right capability return only part of the data. // If the user is viewing another user's badge and doesn't have the right capability return only part of the data.
$criteria = []; $criteria = [];
foreach($this->badge->get_criteria() as $bc){ foreach ($this->badge->get_criteria() as $bc) {
$criteria[] = $bc->get_title()." ".$bc->get_details(); $criteria[] = $bc->get_title()." ".$bc->get_details();
} }
$model = [ $model = [
@ -72,11 +92,11 @@ class badgeinfo {
'locked' => self::LOCKEDINFO[$this->badge->status], 'locked' => self::LOCKEDINFO[$this->badge->status],
'criteria' => $criteria, 'criteria' => $criteria,
'description' => $this->badge->description, 'description' => $this->badge->description,
'imageurl' => \moodle_url::make_pluginfile_url($context->id, 'badges', 'badgeimage', $this->badge->id, '/','f1')->out(false), 'imageurl' => \moodle_url::make_pluginfile_url($context->id, 'badges', 'badgeimage', $this->badge->id, '/', 'f1')->out(false),
]; ];
// Add badge issue stats if a studentlist is attached to the request // Add badge issue stats if a studentlist is attached to the request.
if(!empty($studentlist) && is_array($studentlist)){ if (!empty($studentlist) && is_array($studentlist)) {
$model['studentcount'] = count($studentlist); $model['studentcount'] = count($studentlist);
$model['issuedcount'] = $this->count_issued($studentlist); $model['issuedcount'] = $this->count_issued($studentlist);
} }
@ -84,25 +104,23 @@ class badgeinfo {
return $model; return $model;
} }
public static function user_structure($value=VALUE_REQUIRED) public static function user_structure($value=VALUE_REQUIRED) {
{
return new \external_single_structure([ return new \external_single_structure([
"id" => new \external_value(PARAM_INT, 'id of badge'), "id" => new \external_value(PARAM_INT, 'id of badge'),
"infolink" => new \external_value(PARAM_TEXT, 'badge issue information link', VALUE_OPTIONAL), "infolink" => new \external_value(PARAM_TEXT, 'badge issue information link', VALUE_OPTIONAL),
"name" => new \external_value(PARAM_TEXT, 'badge name'), "name" => new \external_value(PARAM_TEXT, 'badge name'),
"criteria" => new \external_multiple_structure(new \external_value(PARAM_RAW, 'criteria text'),'badge criteria',VALUE_OPTIONAL), "criteria" => new \external_multiple_structure(new \external_value(PARAM_RAW, 'criteria text'), 'badge criteria', VALUE_OPTIONAL),
"description"=> new \external_value(PARAM_TEXT, 'badge description'), "description"=> new \external_value(PARAM_TEXT, 'badge description'),
"imageurl" => new \external_value(PARAM_TEXT, 'url of badge image'), "imageurl" => new \external_value(PARAM_TEXT, 'url of badge image'),
"issued" => new \external_value(PARAM_BOOL, 'badge is issued'), "issued" => new \external_value(PARAM_BOOL, 'badge is issued'),
"dateissued" => new \external_value(PARAM_TEXT, 'date the badge was issued',VALUE_OPTIONAL), "dateissued" => new \external_value(PARAM_TEXT, 'date the badge was issued', VALUE_OPTIONAL),
"dateexpire" => new \external_value(PARAM_TEXT, 'date the badge will expire',VALUE_OPTIONAL), "dateexpire" => new \external_value(PARAM_TEXT, 'date the badge will expire', VALUE_OPTIONAL),
"uniquehash" => new \external_value(PARAM_TEXT, 'badge issue hash', VALUE_OPTIONAL), "uniquehash" => new \external_value(PARAM_TEXT, 'badge issue hash', VALUE_OPTIONAL),
"issuedlink" => new \external_value(PARAM_TEXT, 'badge issue information link', VALUE_OPTIONAL), "issuedlink" => new \external_value(PARAM_TEXT, 'badge issue information link', VALUE_OPTIONAL),
],"Badge info",$value); ], "Badge info", $value);
} }
public function user_model($userid) public function user_model($userid) {
{
global $DB; global $DB;
$context = ($this->badge->type == BADGE_TYPE_SITE) ? \context_system::instance() : \context_course::instance($this->badge->courseid); $context = ($this->badge->type == BADGE_TYPE_SITE) ? \context_system::instance() : \context_course::instance($this->badge->courseid);
@ -110,24 +128,24 @@ class badgeinfo {
// If the user is viewing another user's badge and doesn't have the right capability return only part of the data. // If the user is viewing another user's badge and doesn't have the right capability return only part of the data.
$criteria = []; $criteria = [];
foreach($this->badge->get_criteria() as $bc){ foreach ($this->badge->get_criteria() as $bc) {
$criteria[] = $bc->get_title()."".$bc->get_details(); $criteria[] = $bc->get_title()."".$bc->get_details();
} }
$badge = [ $badge = [
'id' => $this->badge->id, 'id' => $this->badge->id,
'name' => $this->badge->name, 'name' => $this->badge->name,
'description' => $this->badge->description, 'description' => $this->badge->description,
'imageurl' => \moodle_url::make_pluginfile_url($context->id, 'badges', 'badgeimage', $this->badge->id, '/','f1')->out(false), 'imageurl' => \moodle_url::make_pluginfile_url($context->id, 'badges', 'badgeimage', $this->badge->id, '/', 'f1')->out(false),
'criteria' => $criteria, 'criteria' => $criteria,
'issued' => $issued, 'issued' => $issued,
'infolink' => (new \moodle_url('/badges/overview.php', ['id' => $this->badge->id]))->out(false), 'infolink' => (new \moodle_url('/badges/overview.php', ['id' => $this->badge->id]))->out(false),
]; ];
if($issued) { if ($issued) {
$issueinfo = $DB->get_record('badge_issued', array('badgeid' => $this->badge->id, 'userid' => $userid)); $issueinfo = $DB->get_record('badge_issued', array('badgeid' => $this->badge->id, 'userid' => $userid));
$badge['dateissued'] = date("Y-m-d",$issueinfo->dateissued); $badge['dateissued'] = date("Y-m-d", $issueinfo->dateissued);
if($issueinfo->expiredate){ if ($issueinfo->expiredate) {
$badge['dateexpire'] = date("Y-m-d",$issueinfo->dateexpire); $badge['dateexpire'] = date("Y-m-d", $issueinfo->dateexpire);
} }
$badge['uniquehash'] = $issueinfo->uniquehash; $badge['uniquehash'] = $issueinfo->uniquehash;
$badge['issuedlink'] = (new \moodle_url('/badges/badge.php', ['hash' => $issueinfo->uniquehash]))->out(false); $badge['issuedlink'] = (new \moodle_url('/badges/badge.php', ['hash' => $issueinfo->uniquehash]))->out(false);
@ -136,11 +154,11 @@ class badgeinfo {
return $badge; return $badge;
} }
function count_issued(array $student_ids){ function count_issued(array $student_ids) {
$issuecount = 0; $issuecount = 0;
foreach($student_ids as $userid){ foreach ($student_ids as $userid) {
if($this->badge->is_issued($userid)){ if ($this->badge->is_issued($userid)) {
$issuecount++; $issuecount++;
} }
} }

View File

@ -1,4 +1,25 @@
<?php <?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/>.
/**
*
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace local_treestudyplan; namespace local_treestudyplan;
require_once($CFG->libdir.'/externallib.php'); require_once($CFG->libdir.'/externallib.php');
@ -9,27 +30,27 @@ class cascadecohortsync {
private $studyplan; private $studyplan;
private $studyplanid; private $studyplanid;
function __construct(studyplan $studyplan){ function __construct(studyplan $studyplan) {
$this->studyplan = $studyplan; $this->studyplan = $studyplan;
$this->studyplanid = $studyplan->id(); $this->studyplanid = $studyplan->id();
} }
static private function array_remove_value($array,$value){ static private function array_remove_value($array, $value) {
$a = []; $a = [];
foreach($array as $v){ foreach ($array as $v) {
if($v != $value){ if ($v != $value) {
$a[] = $v; $a[] = $v;
} }
} }
return $a; return $a;
} }
static function uploadenrolmentmethods_get_group($courseid, $groupname) { static function uploadenrolmentmethods_get_group($courseid, $groupname) {
// Function shamelessly copied from tool/uploadenrolmentmethods/locallib.php // Function shamelessly copied from tool/uploadenrolmentmethods/locallib.php.
global $DB, $CFG; global $DB, $CFG;
require_once($CFG->dirroot.'/group/lib.php'); require_once($CFG->dirroot.'/group/lib.php');
// Check to see if the group name already exists in this course. // Check to see if the group name already exists in this course.
if ($DB->record_exists('groups', array('name' => $groupname, 'courseid' => $courseid))) { if ($DB->record_exists('groups', array('name' => $groupname, 'courseid' => $courseid))) {
$group = $DB->get_record('groups', array('name' => $groupname, 'courseid' => $courseid)); $group = $DB->get_record('groups', array('name' => $groupname, 'courseid' => $courseid));
@ -40,156 +61,156 @@ class cascadecohortsync {
$groupdata->courseid = $courseid; $groupdata->courseid = $courseid;
$groupdata->name = $groupname; $groupdata->name = $groupname;
$groupid = groups_create_group($groupdata); $groupid = groups_create_group($groupdata);
return $groupid; return $groupid;
} }
function sync(){ function sync() {
global $DB; global $DB;
/* Explainer: /* Explainer:
This script uses {enrol}.customtext4 to store a json array of all studyplans that need this cohort sync to exist. This script uses {enrol}.customtext4 to store a json array of all studyplans that need this cohort sync to exist.
Since the cohort-sync enrolment method uses only customint1 and customint2, this is a safe place to store the data. Since the cohort-sync enrolment method uses only customint1 and customint2, this is a safe place to store the data.
(Should the cohortsync script at any future time be modified to use customtext fields, it is still extremely unlikely that (Should the cohortsync script at any future time be modified to use customtext fields, it is still extremely unlikely that
customtext4 will be used.) customtext4 will be used.)
Because of the overhead involved in keeping an extra table up to date and clean it up if cohort syncs are removed outside of this script, Because of the overhead involved in keeping an extra table up to date and clean it up if cohort syncs are removed outside of this script,
it was determined to be the simplest and cleanest solution. it was determined to be the simplest and cleanest solution.
*/ */
$enrol = \enrol_get_plugin(self::METHOD); $enrol = \enrol_get_plugin(self::METHOD);
// Find the courses that need to be synced to the associated cohorts // Find the courses that need to be synced to the associated cohorts.
$courseids = $this->studyplan->get_linked_course_ids(); $courseids = $this->studyplan->get_linked_course_ids();
// And find the cohorts that are linked to this studyplan. // And find the cohorts that are linked to this studyplan.
$cohortids = $this->studyplan->get_linked_cohort_ids(); $cohortids = $this->studyplan->get_linked_cohort_ids();
// Next, for each course that is linked: // Next, for each course that is linked:.
foreach($courseids as $courseid){ foreach ($courseids as $courseid) {
$course = \get_course($courseid); $course = \get_course($courseid);
//\mtrace("Processing Course {$courseid} {$course->shortname}"); //\mtrace("Processing Course {$courseid} {$course->shortname}");.
// first create any nonexistent links // first create any nonexistent links.
foreach($cohortids as $cohortid){ foreach ($cohortids as $cohortid) {
$cohort = $DB->get_record('cohort',['id'=>$cohortid]); $cohort = $DB->get_record('cohort', ['id'=>$cohortid]);
//\mtrace("Processing cohort {$cohortid} {$cohort->shortname}"); //\mtrace("Processing cohort {$cohortid} {$cohort->shortname}");.
$instanceparams = [ $instanceparams = [
'courseid' => $courseid, 'courseid' => $courseid,
'customint1' => $cohortid, 'customint1' => $cohortid,
'enrol' => self::METHOD, 'enrol' => self::METHOD,
'roleid' => get_config("local_treestudyplan","csync_roleid"), 'roleid' => get_config("local_treestudyplan", "csync_roleid"),
]; ];
$instancenewparams = [ $instancenewparams = [
'customint1' => $cohortid, 'customint1' => $cohortid,
'enrol' => self::METHOD, 'enrol' => self::METHOD,
'roleid' => get_config("local_treestudyplan","csync_roleid"), 'roleid' => get_config("local_treestudyplan", "csync_roleid"),
]; ];
// Create group: // Create group: .
// 1: check if a link exists // 1: check if a link exists.
// If not, make it (maybe use some of the custom text to list the studyplans involved) // If not, make it (maybe use some of the custom text to list the studyplans involved).
if ($instance = $DB->get_record('enrol', $instanceparams)) { if ($instance = $DB->get_record('enrol', $instanceparams)) {
//\mtrace("Instance exists"); //\mtrace("Instance exists");.
// it already exists // it already exists.
// check if this studyplan is already referenced in customtext4 in json format // check if this studyplan is already referenced in customtext4 in json format.
//TODO: Check this code - Maybe add option to not remember manually added stuff //TODO: Check this code - Maybe add option to not remember manually added stuff .
$plans = json_decode($instance->customtext4); $plans = json_decode($instance->customtext4);
if($plans == false || !is_array(($plans))){ if ($plans == false || !is_array(($plans))) {
// If the data was not an array (null or garbled), count it as manually added // If the data was not an array (null or garbled), count it as manually added.
// This will prevent it's deletion upon // This will prevent it's deletion upon.
if(get_config("local_treestudyplan","csync_remember_manual_csync")){ if (get_config("local_treestudyplan", "csync_remember_manual_csync")) {
$plans = ["manual"]; $plans = ["manual"];
} else { } else {
$plans = []; $plans = [];
} }
} }
if(!in_array($this->studyplanid ,$plans)){ if (!in_array($this->studyplanid , $plans)) {
// if not, add it to the reference // if not, add it to the reference.
//\mtrace("Adding this plan to the list"); //\mtrace("Adding this plan to the list");.
$plans[] = (int)($this->studyplanid); $plans[] = (int)($this->studyplanid);
$enrol->update_instance($instance,(object)["customtext4"=>json_encode($plans)]); $enrol->update_instance($instance, (object)["customtext4"=>json_encode($plans)]);
} }
} else { } else {
//\mtrace("New instance should be made"); //\mtrace("New instance should be made");.
// If method members should be added to a group, create it or get its ID. // If method members should be added to a group, create it or get its ID.
if (get_config("local_treestudyplan","csync_creategroup")) { if (get_config("local_treestudyplan", "csync_creategroup")) {
// Make or get new new cohort group - but only on creating of instances // Make or get new new cohort group - but only on creating of instances.
$groupname = $cohort->name." ".strtolower(\get_string('defaultgroupname', 'core_group')); $groupname = $cohort->name." ".strtolower(\get_string('defaultgroupname', 'core_group'));
//\mtrace("Adding group {$groupname} for this method"); //\mtrace("Adding group {$groupname} for this method");.
// and make sure the // and make sure the .
$instancenewparams['customint2'] = self::uploadenrolmentmethods_get_group($courseid, $groupname); $instancenewparams['customint2'] = self::uploadenrolmentmethods_get_group($courseid, $groupname);
} }
if ($instanceid = $enrol->add_instance($course, $instancenewparams)) { if ($instanceid = $enrol->add_instance($course, $instancenewparams)) {
// also record the (as of yet only) studyplans id requiring this association // also record the (as of yet only) studyplans id requiring this association.
// in the customtext4 field in json format // in the customtext4 field in json format.
//\mtrace("Instance ({$instanceid} created. Updateing instance with studyplan id"); //\mtrace("Instance ({$instanceid} created. Updateing instance with studyplan id");.
$instance = $DB->get_record('enrol', array('id' => $instanceid)); $instance = $DB->get_record('enrol', array('id' => $instanceid));
$enrol->update_instance($instance,(object)["customtext4"=>json_encode([(int)($this->studyplanid)])]); $enrol->update_instance($instance, (object)["customtext4"=>json_encode([(int)($this->studyplanid)])]);
//\mtrace("Synchronize the enrolment"); //\mtrace("Synchronize the enrolment");.
// Successfully added a valid new instance, so now instantiate it. // Successfully added a valid new instance, so now instantiate it.
// First synchronise the enrolment. // First synchronise the enrolment.
$cohorttrace = new \null_progress_trace(); $cohorttrace = new \null_progress_trace();
$result = enrol_cohort_sync($cohorttrace, $cohortid); $result = enrol_cohort_sync($cohorttrace, $cohortid);
if($result > 0){ if ($result > 0) {
//\mtrace("Error during 'enrol_cohort_sync': code {$result}"); //\mtrace("Error during 'enrol_cohort_sync': code {$result}");.
} }
$cohorttrace->finished(); $cohorttrace->finished();
} else { } else {
// Instance not added for some reason, so report an error somewhere // Instance not added for some reason, so report an error somewhere.
// (or not) // (or not).
//\mtrace("Error - instance not added for some reason"); //\mtrace("Error - instance not added for some reason");.
} }
} }
} }
// 2: Check if there are cohort links for this studyplan in this course that should be removed // 2: Check if there are cohort links for this studyplan in this course that should be removed.
// A: Check if there are cohort links that are no longer related to this studyplan // A: Check if there are cohort links that are no longer related to this studyplan.
// B: Check if these links are valid through another studyplan... // B: Check if these links are valid through another studyplan...
// If no one uses the link anymore, deactivate it... // If no one uses the link anymore, deactivate it...
// INFO: This does not remove the sync from courses that are unlinked from a studplan. But maybe we do not want that anyway // INFO: This does not remove the sync from courses that are unlinked from a studplan. But maybe we do not want that anyway.
// since it is generally a good idea to keep student access to courses available // since it is generally a good idea to keep student access to courses available .
if(get_config("local_treestudyplan","csync_autoremove")){ if (get_config("local_treestudyplan", "csync_autoremove")) {
// Only try the autoremove if the option is enabled // Only try the autoremove if the option is enabled.
//\mtrace("Autoremove scan for course {$courseid} {$course->shortname}"); //\mtrace("Autoremove scan for course {$courseid} {$course->shortname}");.
// find all cohort syncs for this course // find all cohort syncs for this course.
$searchparams = [ $searchparams = [
'courseid' => $courseid, 'courseid' => $courseid,
'enrol' => self::METHOD, 'enrol' => self::METHOD,
'roleid' => get_config("local_treestudyplan","csync_roleid"), 'roleid' => get_config("local_treestudyplan", "csync_roleid"),
]; ];
$records = $DB->get_records("enrol",$searchparams); $records = $DB->get_records("enrol", $searchparams);
foreach($records as $instance){ foreach ($records as $instance) {
if(!empty($instance->customtext4)){ // only check the records that have studyplan information in the customtext4 field if (!empty($instance->customtext4)) { // only check the records that have studyplan information in the customtext4 field.
// first check if the cohort is not one of the cohort id's we have associated // first check if the cohort is not one of the cohort id's we have associated.
if(!in_array($instance->customint1,$cohortids)){ if (!in_array($instance->customint1, $cohortids)) {
//\mtrace("Found cohort sync instance that is not currently liked to the studyplan: {$instance->id}"); //\mtrace("Found cohort sync instance that is not currently liked to the studyplan: {$instance->id}");.
// So it may or may not need to be removed // So it may or may not need to be removed.
$plans = json_decode($instance->customtext4); $plans = json_decode($instance->customtext4);
if($plans !== null && is_array($plans)){ if ($plans !== null && is_array($plans)) {
//if a valid array is not returned, better leave it be, we don't want to mess with it //if a valid array is not returned, better leave it be, we don't want to mess with it.
// otherwise, check if we should remove it // otherwise, check if we should remove it.
if(in_array($this->studyplanid,$plans)){ if (in_array($this->studyplanid, $plans)) {
//\mtrace("Found this studyplan in the id list - removing from list"); //\mtrace("Found this studyplan in the id list - removing from list");.
//if this plan was referenced before //if this plan was referenced before.
// first remove the link // first remove the link.
$fplans = self::array_remove_value($plans,$this->studyplanid); $fplans = self::array_remove_value($plans, $this->studyplanid);
if(count($fplans) == 0){ if (count($fplans) == 0) {
// delete the sync if there are no studyplan references left // delete the sync if there are no studyplan references left.
//\mtrace("No references are left, removing instance"); //\mtrace("No references are left, removing instance");.
$enrol->delete_instance($instance); $enrol->delete_instance($instance);
} else { } else {
// otherwise just update the references so this studyplan is no longer linked // otherwise just update the references so this studyplan is no longer linked.
//\mtrace("Still references left in the list, updating list..."); //\mtrace("Still references left in the list, updating list...");.
$enrol->update_instance($instance,(object)["customtext4"=>json_encode($fplans)]); $enrol->update_instance($instance, (object)["customtext4"=>json_encode($fplans)]);
} }
} }
} }
@ -198,6 +219,6 @@ class cascadecohortsync {
} }
} }
} }
//\mtrace("Cascading complete"); //\mtrace("Cascading complete");.
} }
} }

View File

@ -1,4 +1,25 @@
<?php <?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/>.
/**
*
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace local_treestudyplan; namespace local_treestudyplan;
require_once($CFG->libdir.'/externallib.php'); require_once($CFG->libdir.'/externallib.php');
@ -8,57 +29,57 @@ class cascadeusersync {
private const METHOD = "manual"; private const METHOD = "manual";
private $studyplan; private $studyplan;
function __construct(studyplan $studyplan){ function __construct(studyplan $studyplan) {
$this->studyplan = $studyplan; $this->studyplan = $studyplan;
} }
function sync(){ function sync() {
global $DB; global $DB;
/* Explainer: /* Explainer:
This script uses {enrol}.customtext4 to store a json array of all studyplans that need this cohort sync to exist. This script uses {enrol}.customtext4 to store a json array of all studyplans that need this cohort sync to exist.
Since the cohort-sync enrolment method uses only customint1 and customint2, this is a safe place to store the data. Since the cohort-sync enrolment method uses only customint1 and customint2, this is a safe place to store the data.
(Should the cohortsync script at any future time be modified to use customtext fields, it is still extremely unlikely that (Should the cohortsync script at any future time be modified to use customtext fields, it is still extremely unlikely that
customtext4 will be used.) customtext4 will be used.)
Because of the overhead involved in keeping an extra table up to date and clean it up if cohort syncs are removed outside of this script, Because of the overhead involved in keeping an extra table up to date and clean it up if cohort syncs are removed outside of this script,
it was determined to be the simplest and cleanest solution. it was determined to be the simplest and cleanest solution.
*/ */
$enrol = \enrol_get_plugin(self::METHOD); $enrol = \enrol_get_plugin(self::METHOD);
// Find the courses that need to be synced to the associated cohorts // Find the courses that need to be synced to the associated cohorts.
$courseids = $this->studyplan->get_linked_course_ids(); $courseids = $this->studyplan->get_linked_course_ids();
// And find the users that are linked to this studyplan. // And find the users that are linked to this studyplan.
$userids = $this->studyplan->get_linked_user_ids(); $userids = $this->studyplan->get_linked_user_ids();
// Get the roleid to use for synchronizations // Get the roleid to use for synchronizations.
$roleid = get_config("local_treestudyplan","csync_roleid"); $roleid = get_config("local_treestudyplan", "csync_roleid");
// Next, for each course that is linked: // Next, for each course that is linked:.
foreach($courseids as $courseid){ foreach ($courseids as $courseid) {
$course = \get_course($courseid); $course = \get_course($courseid);
if(count($userids) > 0){ if (count($userids) > 0) {
// Get the manual enrol instance for this course // Get the manual enrol instance for this course.
$instanceparams = ['courseid' => $courseid, 'enrol' => 'manual']; $instanceparams = ['courseid' => $courseid, 'enrol' => 'manual'];
if(!($instance = $DB->get_record('enrol', $instanceparams))){ if (!($instance = $DB->get_record('enrol', $instanceparams))) {
if ($instanceid = $enrol->add_default_instance($course)) { if ($instanceid = $enrol->add_default_instance($course)) {
$instance = $DB->get_record('enrol', array('id' => $instanceid)); $instance = $DB->get_record('enrol', array('id' => $instanceid));
} }
else { else {
// Instance not added for some reason, so report an error somewhere // Instance not added for some reason, so report an error somewhere.
// (or not) // (or not).
$instance = null; $instance = null;
} }
} }
if($instance !== null){ if ($instance !== null) {
foreach($userids as $uid){ foreach ($userids as $uid) {
// Try a manual registration - it will just be updated if it is already there.... // Try a manual registration - it will just be updated if it is already there....
$enrol->enrol_user($instance,$uid,$roleid); $enrol->enrol_user($instance, $uid, $roleid);
} }
} }
} }
// We do not do any autoremoval for user syncs, to avoid students losing access to the course data // We do not do any autoremoval for user syncs, to avoid students losing access to the course data.
} }
} }
} }

View File

@ -1,4 +1,24 @@
<?php <?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/>.
/**
*
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace local_treestudyplan; namespace local_treestudyplan;
require_once($CFG->libdir.'/externallib.php'); require_once($CFG->libdir.'/externallib.php');
@ -23,29 +43,29 @@ class completion {
]; ];
public static function label($completion) { public static function label($completion) {
if(array_key_exists($completion,self::LABELS)){ if (array_key_exists($completion, self::LABELS)) {
return self::LABELS[$completion]; return self::LABELS[$completion];
} }
else else
{ {
return self::LABELS[self::INCOMPLETE]; return self::LABELS[self::INCOMPLETE];
} }
} }
public static function structure($value=VALUE_REQUIRED){ public static function structure($value=VALUE_REQUIRED) {
return new \external_value(PARAM_TEXT, 'completion state (failed|incomplete|pending|progress|completed|good|excellent)',$value); return new \external_value(PARAM_TEXT, 'completion state (failed|incomplete|pending|progress|completed|good|excellent)', $value);
} }
public static function count_states(array $states){ public static function count_states(array $states) {
// initialize result array // initialize result array.
$statecount = []; $statecount = [];
foreach(array_keys(self::LABELS) as $key) { foreach (array_keys(self::LABELS) as $key) {
$statecount[$key] = 0; $statecount[$key] = 0;
} }
// process all states in array and increment relevant counter for each one // process all states in array and increment relevant counter for each one.
foreach($states as $c){ foreach ($states as $c) {
if(array_key_exists($c,$statecount)){ if (array_key_exists($c, $statecount)) {
$statecount[$c] += 1; $statecount[$c] += 1;
} }
} }

View File

@ -1,11 +1,31 @@
<?php <?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/>.
/**
*
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace local_treestudyplan; namespace local_treestudyplan;
require_once($CFG->libdir.'/externallib.php'); require_once($CFG->libdir.'/externallib.php');
use \grade_item; use \grade_item;
class completionscanner class completionscanner
{ {
private static $mod_supported = []; private static $mod_supported = [];
private static $course_students = []; private static $course_students = [];
@ -15,19 +35,19 @@ class completionscanner
private $gi = null; private $gi = null;
private $pending_cache = []; private $pending_cache = [];
public static function supported($mod){ public static function supported($mod) {
if(!array_key_exists($mod,self::$mod_supported)){ if (!array_key_exists($mod, self::$mod_supported)) {
self::$mod_supported[$mod] = class_exists("\local_treestudyplan\\local\\ungradedscanners\\{$mod}_scanner"); self::$mod_supported[$mod] = class_exists("\local_treestudyplan\\local\\ungradedscanners\\{$mod}_scanner");
} }
return self::$mod_supported[$mod]; return self::$mod_supported[$mod];
} }
public static function get_course_students($courseid){ public static function get_course_students($courseid) {
global $CFG; global $CFG;
if(!array_key_exists($courseid,self::$course_students)){ if (!array_key_exists($courseid, self::$course_students)) {
$students = []; $students = [];
$context = \context_course::instance($courseid); $context = \context_course::instance($courseid);
foreach (explode(',', $CFG->gradebookroles) as $roleid) { foreach (explode(', ', $CFG->gradebookroles) as $roleid) {
$roleid = trim($roleid); $roleid = trim($roleid);
$students = array_keys(get_role_users($roleid, $context, false, 'u.id', 'u.id ASC')); $students = array_keys(get_role_users($roleid, $context, false, 'u.id', 'u.id ASC'));
} }
@ -36,28 +56,26 @@ class completionscanner
return self::$course_students[$courseid]; return self::$course_students[$courseid];
} }
public function __construct(\completion_criteria $crit,$course){ public function __construct(\completion_criteria $crit, $course) {
$this->courseid = $course->id; $this->courseid = $course->id;
$this->course = $course; $this->course = $course;
$this->modinfo = get_fast_modinfo($course); $this->modinfo = get_fast_modinfo($course);
$this->crit = $crit; $this->crit = $crit;
$this->completioninfo = new \completion_info($course); $this->completioninfo = new \completion_info($course);
// Find a related scanner if the type is an activity type // Find a related scanner if the type is an activity type.
if($crit->criteriatype == COMPLETION_CRITERIA_TYPE_ACTIVITY){ if ($crit->criteriatype == COMPLETION_CRITERIA_TYPE_ACTIVITY) {
// First find the course module // First find the course module.
$this->cm = $this->modinfo->get_cm($crit->moduleinstance); $this->cm = $this->modinfo->get_cm($crit->moduleinstance);
$gi = grade_item::fetch(['itemtype' => 'mod', 'itemmodule' => $this->cm->modname, 'iteminstance' => $this->cm->instance, 'courseid' => $this->courseid]); $gi = grade_item::fetch(['itemtype' => 'mod', 'itemmodule' => $this->cm->modname, 'iteminstance' => $this->cm->instance, 'courseid' => $this->courseid]);
if($gi !== false) if ($gi !== false) {
{ // Grade none items should not be relevant.
// Grade none items should not be relevant // Note that the grade status is probably only relevant if the item has not yet received a completion, but has been submitted.
// Note that the grade status is probably only relevant if the item has not yet received a completion, but has been submitted if (($gi->gradetype == GRADE_TYPE_VALUE || $gi->gradetype == GRADE_TYPE_SCALE)) {
if(($gi->gradetype == GRADE_TYPE_VALUE || $gi->gradetype == GRADE_TYPE_SCALE)) // If it's a relevant grade type, initialize a scanner if possible.
{ $this->gi = $gi;
// If it's a relevant grade type, initialize a scanner if possible if (self::supported($gi->itemmodule)) {
$this->gi = $gi;
if(self::supported($gi->itemmodule)) {
$scannerclass = "\local_treestudyplan\\local\ungradedscanners\\{$gi->itemmodule}_scanner"; $scannerclass = "\local_treestudyplan\\local\ungradedscanners\\{$gi->itemmodule}_scanner";
$this->scanner = new $scannerclass($gi); $this->scanner = new $scannerclass($gi);
} }
@ -67,57 +85,57 @@ class completionscanner
} }
public function pending($userid){ public function pending($userid) {
if(!array_key_exists($userid, $this->pending_cache)){ if (!array_key_exists($userid, $this->pending_cache)) {
if($this->scanner === null) { if ($this->scanner === null) {
$this->pending_cache[$userid] = false; $this->pending_cache[$userid] = false;
} }
else { else {
$this->pending_cache[$userid] = $this->scanner->has_ungraded_submission($userid);; $this->pending_cache[$userid] = $this->scanner->has_ungraded_submission($userid);;
} }
} }
return $this->pending_cache[$userid]; return $this->pending_cache[$userid];
} }
public static function structure($value=VALUE_OPTIONAL){ public static function structure($value=VALUE_OPTIONAL) {
return new \external_single_structure([ return new \external_single_structure([
"ungraded" => new \external_value(PARAM_INT, 'number of ungraded submissions'), "ungraded" => new \external_value(PARAM_INT, 'number of ungraded submissions'),
"completed" => new \external_value(PARAM_INT, 'number of completed students'), "completed" => new \external_value(PARAM_INT, 'number of completed students'),
"completed_pass" => new \external_value(PARAM_INT, 'number of completed-pass students'), "completed_pass" => new \external_value(PARAM_INT, 'number of completed-pass students'),
"completed_fail" => new \external_value(PARAM_INT, 'number of completed-fail students'), "completed_fail" => new \external_value(PARAM_INT, 'number of completed-fail students'),
"students" => new \external_value(PARAM_INT, 'number of students that should submit'), "students" => new \external_value(PARAM_INT, 'number of students that should submit'),
],"details about gradable submissions",$value); ], "details about gradable submissions", $value);
} }
public function model(){ public function model() {
// get completion info // get completion info.
$students = self::get_course_students($this->courseid); $students = self::get_course_students($this->courseid);
$completed = 0; $completed = 0;
$ungraded = 0; $ungraded = 0;
$completed_pass = 0; $completed_pass = 0;
$completed_fail = 0; $completed_fail = 0;
foreach($students as $userid){ foreach ($students as $userid) {
if($this->pending($userid)){ if ($this->pending($userid)) {
// First check if the completion needs grading // First check if the completion needs grading.
$ungraded++; $ungraded++;
} else { } else {
$completion = $this->completioninfo->get_user_completion($userid,$this->crit); $completion = $this->completioninfo->get_user_completion($userid, $this->crit);
if($this->crit->criteriatype == COMPLETION_CRITERIA_TYPE_ACTIVITY){ if ($this->crit->criteriatype == COMPLETION_CRITERIA_TYPE_ACTIVITY) {
// If it's an activity completion, add all the relevant activities as sub-items // If it's an activity completion, add all the relevant activities as sub-items.
$completion_status = $this->completioninfo->get_grade_completion($this->cm,$userid); $completion_status = $this->completioninfo->get_grade_completion($this->cm, $userid);
if($completion_status == COMPLETION_COMPLETE_PASS){ if ($completion_status == COMPLETION_COMPLETE_PASS) {
$completed_pass++; $completed_pass++;
} else if ($completion_status == COMPLETION_COMPLETE_FAIL){ } else if ($completion_status == COMPLETION_COMPLETE_FAIL) {
$completed_fail++; $completed_fail++;
} else if ($completion_status == COMPLETION_COMPLETE){ } else if ($completion_status == COMPLETION_COMPLETE) {
$completed++; $completed++;
} }
} }
else{ else{
if($completion->is_complete()){ if ($completion->is_complete()) {
$completed++; $completed++;
} }
} }

View File

@ -1,15 +1,35 @@
<?php <?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/>.
/**
*
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace local_treestudyplan; namespace local_treestudyplan;
class contextinfo { class contextinfo {
public $context; public $context;
public function __construct($context){ public function __construct($context) {
$this->context = $context; $this->context = $context;
} }
public static function structure($value=VALUE_REQUIRED){ public static function structure($value=VALUE_REQUIRED) {
return new \external_single_structure([ return new \external_single_structure([
"name" => new \external_value(PARAM_TEXT, 'context name'), "name" => new \external_value(PARAM_TEXT, 'context name'),
"shortname" => new \external_value(PARAM_TEXT, 'context short name'), "shortname" => new \external_value(PARAM_TEXT, 'context short name'),
@ -19,16 +39,16 @@ class contextinfo {
} }
public function model() { public function model() {
$ctxPath = array_reverse($this->context->get_parent_context_ids(true)); $ctxPath = array_reverse($this->context->get_parent_context_ids(true));
if(count($ctxPath) > 1 && $ctxPath[0] == 1) { if (count($ctxPath) > 1 && $ctxPath[0] == 1) {
array_shift($ctxPath); array_shift($ctxPath);
} }
return [ return [
"name" => $this->context->get_context_name(false,false), "name" => $this->context->get_context_name(false, false),
"shortname" => $this->context->get_context_name(false,true), "shortname" => $this->context->get_context_name(false, true),
"path" => array_map(function($c){ return \context::instance_by_id($c)->get_context_name(false,false);},$ctxPath), "path" => array_map(function($c) { return \context::instance_by_id($c)->get_context_name(false, false);}, $ctxPath),
"shortpath" => array_map(function($c){ return \context::instance_by_id($c)->get_context_name(false,true);},$ctxPath), "shortpath" => array_map(function($c) { return \context::instance_by_id($c)->get_context_name(false, true);}, $ctxPath),
]; ];
} }
@ -37,7 +57,7 @@ class contextinfo {
} }
public static function context_by_id($contextid): \context { public static function context_by_id($contextid): \context {
if($contextid <= 1){ if ($contextid <= 1) {
$contextid = 1; $contextid = 1;
} }
return \context::instance_by_id($contextid); return \context::instance_by_id($contextid);

View File

@ -1,4 +1,24 @@
<?php <?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/>.
/**
*
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace local_treestudyplan; namespace local_treestudyplan;
require_once($CFG->libdir.'/externallib.php'); require_once($CFG->libdir.'/externallib.php');
@ -17,142 +37,141 @@ class corecompletioninfo {
private $modinfo; private $modinfo;
private static $COMPLETION_HANDLES = null; private static $COMPLETION_HANDLES = null;
public function id(){ public function id() {
return $this->course->id; return $this->course->id;
} }
public function __construct($course){ public function __construct($course) {
global $DB; global $DB;
$this->course = $course; $this->course = $course;
$this->completion = new \completion_info($this->course); $this->completion = new \completion_info($this->course);
$this->modinfo = get_fast_modinfo($this->course); $this->modinfo = get_fast_modinfo($this->course);
} }
static public function completiontypes(){ static public function completiontypes() {
global $COMPLETION_CRITERIA_TYPES; global $COMPLETION_CRITERIA_TYPES;
// Just return the keys of the global array COMPLETION_CRITERIA_TYPES, so we don't have to manually // Just return the keys of the global array COMPLETION_CRITERIA_TYPES, so we don't have to manually.
// add any completion types.... // add any completion types....
return \array_keys($COMPLETION_CRITERIA_TYPES); return \array_keys($COMPLETION_CRITERIA_TYPES);
} }
/** /**
* Translate a numeric completion constant to a text string * Translate a numeric completion constant to a text string
* @param $completion The completion code as defined in completionlib.php to translate to a text handle * @param $completion The completion code as defined in completionlib.php to translate to a text handle
*/ */
static public function completion_handle($completion){ static public function completion_handle($completion) {
if(empty(self::$COMPLETION_HANDLES)){ if (empty(self::$COMPLETION_HANDLES)) {
// Cache the translation table, to avoid overhead // Cache the translation table, to avoid overhead.
self::$COMPLETION_HANDLES = [ self::$COMPLETION_HANDLES = [
COMPLETION_INCOMPLETE => "incomplete", COMPLETION_INCOMPLETE => "incomplete",
COMPLETION_COMPLETE => "complete", COMPLETION_COMPLETE => "complete",
COMPLETION_COMPLETE_PASS => "complete-pass", COMPLETION_COMPLETE_PASS => "complete-pass",
COMPLETION_COMPLETE_FAIL => "complete-fail", COMPLETION_COMPLETE_FAIL => "complete-fail",
COMPLETION_COMPLETE_FAIL_HIDDEN => "complete-fail"]; // the front end won't differentiate between hidden or not COMPLETION_COMPLETE_FAIL_HIDDEN => "complete-fail"]; // the front end won't differentiate between hidden or not.
} }
return self::$COMPLETION_HANDLES[$completion] ?? "undefined"; return self::$COMPLETION_HANDLES[$completion] ?? "undefined";
} }
public static function completion_item_editor_structure($value=VALUE_REQUIRED){ public static function completion_item_editor_structure($value=VALUE_REQUIRED) {
return new \external_single_structure([ return new \external_single_structure([
"id" => new \external_value(PARAM_INT,'criteria id',VALUE_OPTIONAL), "id" => new \external_value(PARAM_INT, 'criteria id', VALUE_OPTIONAL),
"title" => new \external_value(PARAM_TEXT,'name of subitem',VALUE_OPTIONAL), "title" => new \external_value(PARAM_TEXT, 'name of subitem', VALUE_OPTIONAL),
"link" => new \external_value(PARAM_TEXT, 'optional link to more details',VALUE_OPTIONAL), "link" => new \external_value(PARAM_TEXT, 'optional link to more details', VALUE_OPTIONAL),
"details" => new \external_single_structure([ "details" => new \external_single_structure([
"type" => new \external_value(PARAM_RAW, 'type',VALUE_OPTIONAL), "type" => new \external_value(PARAM_RAW, 'type', VALUE_OPTIONAL),
"criteria" => new \external_value(PARAM_RAW, 'criteria',VALUE_OPTIONAL), "criteria" => new \external_value(PARAM_RAW, 'criteria', VALUE_OPTIONAL),
"requirement" => new \external_value(PARAM_RAW, 'requirement',VALUE_OPTIONAL), "requirement" => new \external_value(PARAM_RAW, 'requirement', VALUE_OPTIONAL),
"status" => new \external_value(PARAM_RAW, 'status',VALUE_OPTIONAL), "status" => new \external_value(PARAM_RAW, 'status', VALUE_OPTIONAL),
]), ]),
"progress" => completionscanner::structure(), "progress" => completionscanner::structure(),
], 'completion type',$value); ], 'completion type', $value);
} }
public static function completion_type_editor_structure($value=VALUE_REQUIRED){ public static function completion_type_editor_structure($value=VALUE_REQUIRED) {
return new \external_single_structure([ return new \external_single_structure([
"items" => new \external_multiple_structure(self::completion_item_editor_structure(),'subitems',VALUE_OPTIONAL), "items" => new \external_multiple_structure(self::completion_item_editor_structure(), 'subitems', VALUE_OPTIONAL),
"title" => new \external_value(PARAM_TEXT,'optional title',VALUE_OPTIONAL), "title" => new \external_value(PARAM_TEXT, 'optional title', VALUE_OPTIONAL),
"desc" => new \external_value(PARAM_TEXT, 'optional description',VALUE_OPTIONAL), "desc" => new \external_value(PARAM_TEXT, 'optional description', VALUE_OPTIONAL),
"type" => new \external_value(PARAM_TEXT, 'completion type name'), "type" => new \external_value(PARAM_TEXT, 'completion type name'),
"aggregation" => new \external_value(PARAM_TEXT, 'completion aggregation for this type ["all","any"]'), "aggregation" => new \external_value(PARAM_TEXT, 'completion aggregation for this type ["all", "any"]'),
], 'completion type',$value); ], 'completion type', $value);
} }
public static function editor_structure($value=VALUE_REQUIRED){ public static function editor_structure($value=VALUE_REQUIRED) {
return new \external_single_structure([ return new \external_single_structure([
"conditions" => new \external_multiple_structure(self::completion_type_editor_structure(),'completion conditions'), "conditions" => new \external_multiple_structure(self::completion_type_editor_structure(), 'completion conditions'),
"aggregation" => new \external_value(PARAM_TEXT, 'completion aggregation ["all","any"]'), "aggregation" => new \external_value(PARAM_TEXT, 'completion aggregation ["all", "any"]'),
"enabled" => new \external_value(PARAM_BOOL,"whether completion is enabled here"), "enabled" => new \external_value(PARAM_BOOL, "whether completion is enabled here"),
], 'course completion info',$value); ], 'course completion info', $value);
} }
public static function completion_item_user_structure($value=VALUE_REQUIRED){ public static function completion_item_user_structure($value=VALUE_REQUIRED) {
return new \external_single_structure([ return new \external_single_structure([
"id" => new \external_value(PARAM_INT,'id of completion',VALUE_OPTIONAL), "id" => new \external_value(PARAM_INT, 'id of completion', VALUE_OPTIONAL),
"title" => new \external_value(PARAM_TEXT,'name of subitem',VALUE_OPTIONAL), "title" => new \external_value(PARAM_TEXT, 'name of subitem', VALUE_OPTIONAL),
"details" => new \external_single_structure([ "details" => new \external_single_structure([
"type" => new \external_value(PARAM_RAW, 'type',VALUE_OPTIONAL), "type" => new \external_value(PARAM_RAW, 'type', VALUE_OPTIONAL),
"criteria" => new \external_value(PARAM_RAW, 'criteria',VALUE_OPTIONAL), "criteria" => new \external_value(PARAM_RAW, 'criteria', VALUE_OPTIONAL),
"requirement" => new \external_value(PARAM_RAW, 'requirement',VALUE_OPTIONAL), "requirement" => new \external_value(PARAM_RAW, 'requirement', VALUE_OPTIONAL),
"status" => new \external_value(PARAM_RAW, 'status',VALUE_OPTIONAL), "status" => new \external_value(PARAM_RAW, 'status', VALUE_OPTIONAL),
]), ]),
"link" => new \external_value(PARAM_TEXT, 'optional link to more details',VALUE_OPTIONAL), "link" => new \external_value(PARAM_TEXT, 'optional link to more details', VALUE_OPTIONAL),
"completed" => new \external_value(PARAM_BOOL, 'simple completed or not'), "completed" => new \external_value(PARAM_BOOL, 'simple completed or not'),
"status" => new \external_value(PARAM_TEXT, 'extended completion status ["incomplete","progress","complete", "complete-pass","complete-fail"]'), "status" => new \external_value(PARAM_TEXT, 'extended completion status ["incomplete", "progress", "complete", "complete-pass", "complete-fail"]'),
"pending" => new \external_value(PARAM_BOOL, 'optional pending state, for submitted but not yet reviewed activities',VALUE_OPTIONAL), "pending" => new \external_value(PARAM_BOOL, 'optional pending state, for submitted but not yet reviewed activities', VALUE_OPTIONAL),
"grade" => new \external_value(PARAM_TEXT, 'optional grade result for this subitem',VALUE_OPTIONAL), "grade" => new \external_value(PARAM_TEXT, 'optional grade result for this subitem', VALUE_OPTIONAL),
"feedback" => new \external_value(PARAM_RAW, 'optional feedback for this subitem ',VALUE_OPTIONAL), "feedback" => new \external_value(PARAM_RAW, 'optional feedback for this subitem ', VALUE_OPTIONAL),
], 'completion type',$value); ], 'completion type', $value);
} }
public static function completion_type_user_structure($value=VALUE_REQUIRED){ public static function completion_type_user_structure($value=VALUE_REQUIRED) {
return new \external_single_structure([ return new \external_single_structure([
"items" => new \external_multiple_structure(self::completion_item_user_structure(),'subitems',VALUE_OPTIONAL), "items" => new \external_multiple_structure(self::completion_item_user_structure(), 'subitems', VALUE_OPTIONAL),
"title" => new \external_value(PARAM_TEXT,'optional title',VALUE_OPTIONAL), "title" => new \external_value(PARAM_TEXT, 'optional title', VALUE_OPTIONAL),
"desc" => new \external_value(PARAM_TEXT, 'optional description',VALUE_OPTIONAL), "desc" => new \external_value(PARAM_TEXT, 'optional description', VALUE_OPTIONAL),
"type" => new \external_value(PARAM_TEXT, 'completion type name'), "type" => new \external_value(PARAM_TEXT, 'completion type name'),
"aggregation" => new \external_value(PARAM_TEXT, 'completion aggregation for this type ["all","any"]'), "aggregation" => new \external_value(PARAM_TEXT, 'completion aggregation for this type ["all", "any"]'),
"completed" => new \external_value(PARAM_BOOL, 'current completion value for this type'), "completed" => new \external_value(PARAM_BOOL, 'current completion value for this type'),
"status" => new \external_value(PARAM_TEXT, 'extended completion status ["incomplete","progress","complete", "complete-pass","complete-fail"]'), "status" => new \external_value(PARAM_TEXT, 'extended completion status ["incomplete", "progress", "complete", "complete-pass", "complete-fail"]'),
"progress" => new \external_value(PARAM_INT, 'completed sub-conditions'), "progress" => new \external_value(PARAM_INT, 'completed sub-conditions'),
"count" => new \external_value(PARAM_INT, 'total number of sub-conditions'), "count" => new \external_value(PARAM_INT, 'total number of sub-conditions'),
], 'completion type',$value); ], 'completion type', $value);
} }
public static function user_structure($value=VALUE_REQUIRED){ public static function user_structure($value=VALUE_REQUIRED) {
return new \external_single_structure([ return new \external_single_structure([
"progress" => new \external_value(PARAM_INT, 'completed sub-conditions'), "progress" => new \external_value(PARAM_INT, 'completed sub-conditions'),
"enabled" => new \external_value(PARAM_BOOL,"whether completion is enabled here"), "enabled" => new \external_value(PARAM_BOOL, "whether completion is enabled here"),
"tracked" => new \external_value(PARAM_BOOL,"whether completion is tracked for the user",VALUE_OPTIONAL), "tracked" => new \external_value(PARAM_BOOL, "whether completion is tracked for the user", VALUE_OPTIONAL),
"count" => new \external_value(PARAM_INT, 'total number of sub-conditions'), "count" => new \external_value(PARAM_INT, 'total number of sub-conditions'),
"conditions" => new \external_multiple_structure(self::completion_type_user_structure(),'completion conditions'), "conditions" => new \external_multiple_structure(self::completion_type_user_structure(), 'completion conditions'),
"completed" => new \external_value(PARAM_BOOL, 'current completion value'), "completed" => new \external_value(PARAM_BOOL, 'current completion value'),
"aggregation" => new \external_value(PARAM_TEXT, 'completion aggregation ["all","any"]'), "aggregation" => new \external_value(PARAM_TEXT, 'completion aggregation ["all", "any"]'),
"pending" => new \external_value(PARAM_BOOL,"true if the user has any assignments pending grading",VALUE_OPTIONAL), "pending" => new \external_value(PARAM_BOOL, "true if the user has any assignments pending grading", VALUE_OPTIONAL),
], 'course completion info',$value); ], 'course completion info', $value);
} }
private static function aggregation_handle($method){ private static function aggregation_handle($method) {
return ($method==COMPLETION_AGGREGATION_ALL)?"all":"any"; return ($method==COMPLETION_AGGREGATION_ALL)?"all":"any";
} }
public function editor_model() { public function editor_model() {
global $DB, $CFG, $COMPLETION_CRITERIA_TYPES; global $DB, $CFG, $COMPLETION_CRITERIA_TYPES;
$conditions = []; $conditions = [];
$aggregation = "all"; // default $aggregation = "all"; // default.
$info = [ $info = [
"conditions" => $conditions, "conditions" => $conditions,
"aggregation" => self::aggregation_handle($this->completion->get_aggregation_method()), "aggregation" => self::aggregation_handle($this->completion->get_aggregation_method()),
"enabled" => $this->completion->is_enabled() "enabled" => $this->completion->is_enabled()
]; ];
// Check if completion tracking is enabled for this course - otherwise, revert to defaults // Check if completion tracking is enabled for this course - otherwise, revert to defaults .
if($this->completion->is_enabled()) if ($this->completion->is_enabled()) {
{
$aggregation = $this->completion->get_aggregation_method(); $aggregation = $this->completion->get_aggregation_method();
// Loop through all condition types to see if they are applicable // Loop through all condition types to see if they are applicable.
foreach(self::completiontypes() as $type){ foreach (self::completiontypes() as $type) {
$criterias = $this->completion->get_criteria($type); // Returns array of relevant criteria items $criterias = $this->completion->get_criteria($type); // Returns array of relevant criteria items.
if(count($criterias) > 0 ) // Only take it into account if the criteria count is > 0 if (count($criterias) > 0 ) // Only take it into account if the criteria count is > 0.
{ {
$cinfo = [ $cinfo = [
"type" => $COMPLETION_CRITERIA_TYPES[$type], "type" => $COMPLETION_CRITERIA_TYPES[$type],
@ -161,13 +180,13 @@ class corecompletioninfo {
"items" => [], "items" => [],
]; ];
foreach($criterias as $criteria){ foreach ($criterias as $criteria) {
// Unfortunately, we cannot easily get the criteria details with get_details() without having a // Unfortunately, we cannot easily get the criteria details with get_details() without having a .
// user completion object involved, so'we'll have to retrieve the details per completion type // user completion object involved, so'we'll have to retrieve the details per completion type.
// See moodle/completion/criteria/completion_criteria_*.php::get_details() for the code that is // See moodle/completion/criteria/completion_criteria_*.php::get_details() for the code that is.
// in the code below is based on // in the code below is based on.
if($type == COMPLETION_CRITERIA_TYPE_SELF){ if ($type == COMPLETION_CRITERIA_TYPE_SELF) {
$details = [ $details = [
"type" => $criteria->get_title(), "type" => $criteria->get_title(),
"criteria" => $criteria->get_title(), "criteria" => $criteria->get_title(),
@ -175,15 +194,15 @@ class corecompletioninfo {
"status" => "", "status" => "",
]; ];
} }
else if ($type == COMPLETION_CRITERIA_TYPE_DATE){ else if ($type == COMPLETION_CRITERIA_TYPE_DATE) {
$details = [ $details = [
"type" => get_string('datepassed', 'completion'), "type" => get_string('datepassed', 'completion'),
"criteria" => get_string('remainingenroleduntildate', 'completion'), "criteria" => get_string('remainingenroleduntildate', 'completion'),
"requirement" => date("Y-m-d",$criteria->timeend), "requirement" => date("Y-m-d", $criteria->timeend),
"status" => "", "status" => "",
]; ];
} }
else if ($type == COMPLETION_CRITERIA_TYPE_UNENROL){ else if ($type == COMPLETION_CRITERIA_TYPE_UNENROL) {
$details = [ $details = [
"type" => get_string('unenrolment', 'completion'), "type" => get_string('unenrolment', 'completion'),
"criteria" => get_string('unenrolment', 'completion'), "criteria" => get_string('unenrolment', 'completion'),
@ -191,12 +210,12 @@ class corecompletioninfo {
"status" => "", "status" => "",
]; ];
} }
else if ($type == COMPLETION_CRITERIA_TYPE_ACTIVITY){ else if ($type == COMPLETION_CRITERIA_TYPE_ACTIVITY) {
$cm = $this->modinfo->get_cm($criteria->moduleinstance); $cm = $this->modinfo->get_cm($criteria->moduleinstance);
$details = [ $details = [
"type" => $criteria->get_title(), "type" => $criteria->get_title(),
"criteria" => "", // Will be built in a moment by code copied from completion_criteria_activity.php "criteria" => "", // Will be built in a moment by code copied from completion_criteria_activity.php.
"requirement" => "", // Will be built momentarily by code copied from completion_criteria_activity.php "requirement" => "", // Will be built momentarily by code copied from completion_criteria_activity.php.
"status" => "", "status" => "",
]; ];
if ($cm->has_view()) { if ($cm->has_view()) {
@ -204,12 +223,12 @@ class corecompletioninfo {
} else { } else {
$details['criteria'] = $cm->get_formatted_name(); $details['criteria'] = $cm->get_formatted_name();
} }
// Build requirements // Build requirements.
$details['requirement'] = array(); $details['requirement'] = array();
if ($cm->completion == COMPLETION_TRACKING_MANUAL) { if ($cm->completion == COMPLETION_TRACKING_MANUAL) {
$details['requirement'][] = get_string('markingyourselfcomplete', 'completion'); $details['requirement'][] = get_string('markingyourselfcomplete', 'completion');
} elseif ($cm->completion == COMPLETION_TRACKING_AUTOMATIC) { } else if ($cm->completion == COMPLETION_TRACKING_AUTOMATIC) {
if ($cm->completionview) { if ($cm->completionview) {
$modulename = \core_text::strtolower(get_string('modulename', $criteria->module)); $modulename = \core_text::strtolower(get_string('modulename', $criteria->module));
$details['requirement'][] = get_string('viewingactivity', 'completion', $modulename); $details['requirement'][] = get_string('viewingactivity', 'completion', $modulename);
@ -227,7 +246,7 @@ class corecompletioninfo {
$details['requirement'] = implode(', ', $details['requirement']); $details['requirement'] = implode(', ', $details['requirement']);
} }
else if ($type == COMPLETION_CRITERIA_TYPE_DURATION){ else if ($type == COMPLETION_CRITERIA_TYPE_DURATION) {
$details = [ $details = [
"type" => get_string('periodpostenrolment', 'completion'), "type" => get_string('periodpostenrolment', 'completion'),
"criteria" => get_string('remainingenroledfortime', 'completion'), "criteria" => get_string('remainingenroledfortime', 'completion'),
@ -235,18 +254,18 @@ class corecompletioninfo {
"status" => "", "status" => "",
]; ];
} }
else if ($type == COMPLETION_CRITERIA_TYPE_GRADE){ else if ($type == COMPLETION_CRITERIA_TYPE_GRADE) {
$details = [ $details = [
"type" => get_string('coursegrade', 'completion'), "type" => get_string('coursegrade', 'completion'),
"criteria" => get_string('graderequired', 'completion'), "criteria" => get_string('graderequired', 'completion'),
// TODO: convert to selected representation (letter, percentage, etc) // TODO: convert to selected representation (letter, percentage, etc).
"requirement" => get_string('graderequired', 'completion').": ".format_float($criteria->gradepass, 1), "requirement" => get_string('graderequired', 'completion').": ".format_float($criteria->gradepass, 1),
"status" => "", "status" => "",
]; ];
} }
else if ($type == COMPLETION_CRITERIA_TYPE_ROLE){ else if ($type == COMPLETION_CRITERIA_TYPE_ROLE) {
$criteria = $criteria->get_title(); $criteria = $criteria->get_title();
$details = [ $details = [
"type" => get_string('manualcompletionby', 'completion'), "type" => get_string('manualcompletionby', 'completion'),
"criteria" => $criteria, "criteria" => $criteria,
@ -254,7 +273,7 @@ class corecompletioninfo {
"status" => "", "status" => "",
]; ];
} }
else if ($type == COMPLETION_CRITERIA_TYPE_COURSE){ else if ($type == COMPLETION_CRITERIA_TYPE_COURSE) {
$prereq = get_course($criteria->courseinstance); $prereq = get_course($criteria->courseinstance);
$coursecontext = \context_course::instance($prereq->id, MUST_EXIST); $coursecontext = \context_course::instance($prereq->id, MUST_EXIST);
$fullname = format_string($prereq->fullname, true, array('context' => $coursecontext)); $fullname = format_string($prereq->fullname, true, array('context' => $coursecontext));
@ -265,7 +284,7 @@ class corecompletioninfo {
"status" => "", "status" => "",
]; ];
} else { } else {
// Moodle added a criteria type // Moodle added a criteria type.
$details = [ $details = [
"type" => "", "type" => "",
"criteria" => "", "criteria" => "",
@ -273,9 +292,9 @@ class corecompletioninfo {
"status" => "", "status" => "",
]; ];
} }
$scanner = new completionscanner($criteria,$this->course); $scanner = new completionscanner($criteria, $this->course);
// only add the items list if we actually have items... // only add the items list if we actually have items...
$cinfo["items"][] = [ $cinfo["items"][] = [
"id" => $criteria->id, "id" => $criteria->id,
@ -296,18 +315,18 @@ class corecompletioninfo {
return $info; return $info;
} }
private function aggregate_completions($typeaggregation,$completions){ private function aggregate_completions($typeaggregation, $completions) {
$completed = 0; $completed = 0;
$count = count($completions); $count = count($completions);
foreach($completions as $c){ foreach ($completions as $c) {
if($c->is_complete()){ if ($c->is_complete()) {
$completed++; $completed++;
} }
} }
if($typeaggregation == COMPLETION_AGGREGATION_ALL){ if ($typeaggregation == COMPLETION_AGGREGATION_ALL) {
return $completed >= $count; return $completed >= $count;
} }
else { // COMPLETION_AGGREGATION_ANY else { // COMPLETION_AGGREGATION_ANY.
return $completed > 1; return $completed > 1;
} }
@ -323,21 +342,20 @@ class corecompletioninfo {
"conditions" => [], "conditions" => [],
"completed" => $this->completion->is_course_complete($userid), "completed" => $this->completion->is_course_complete($userid),
"aggregation" => self::aggregation_handle($this->completion->get_aggregation_method()), "aggregation" => self::aggregation_handle($this->completion->get_aggregation_method()),
"enabled" => $this->completion->is_enabled(), "enabled" => $this->completion->is_enabled(),
"tracked" => $this->completion->is_tracked_user($userid), "tracked" => $this->completion->is_tracked_user($userid),
]; ];
// Check if completion tracking is enabled for this course - otherwise, revert to defaults // Check if completion tracking is enabled for this course - otherwise, revert to defaults .
if($this->completion->is_enabled() && $this->completion->is_tracked_user($userid)) if ($this->completion->is_enabled() && $this->completion->is_tracked_user($userid)) {
{
$anypending = false; $anypending = false;
// Loop through all conditions to see if they are applicable // Loop through all conditions to see if they are applicable.
foreach(self::completiontypes() as $type){ foreach (self::completiontypes() as $type) {
// Get the main completion for this type // Get the main completion for this type.
$completions = $this->completion->get_completions($userid,$type); $completions = $this->completion->get_completions($userid, $type);
if(count($completions) > 0){ if (count($completions) > 0) {
$typeaggregation = $this->completion->get_aggregation_method($type); $typeaggregation = $this->completion->get_aggregation_method($type);
$completed = $this->aggregate_completions($typeaggregation,$completions); $completed = $this->aggregate_completions($typeaggregation, $completions);
$cinfo = [ $cinfo = [
"type" => $COMPLETION_CRITERIA_TYPES[$type], "type" => $COMPLETION_CRITERIA_TYPES[$type],
"aggregation" => self::aggregation_handle($typeaggregation), "aggregation" => self::aggregation_handle($typeaggregation),
@ -348,67 +366,67 @@ class corecompletioninfo {
]; ];
$progress = 0; $progress = 0;
foreach($completions as $completion){ foreach ($completions as $completion) {
$criteria = $completion->get_criteria(); $criteria = $completion->get_criteria();
if($completion->is_complete()) { if ($completion->is_complete()) {
$progress += 1; // Add a point to the progress counter $progress += 1; // Add a point to the progress counter.
} }
$iinfo = [ $iinfo = [
"id" => $criteria->id, "id" => $criteria->id,
"title" => $criteria->get_title_detailed(), "title" => $criteria->get_title_detailed(),
"details" => $criteria->get_details($completion), "details" => $criteria->get_details($completion),
"completed" => $completion->is_complete(), // Make sure to override for activi "completed" => $completion->is_complete(), // Make sure to override for activi.
"status" => self::completion_handle($completion->is_complete()?COMPLETION_COMPLETE:COMPLETION_INCOMPLETE), "status" => self::completion_handle($completion->is_complete()?COMPLETION_COMPLETE:COMPLETION_INCOMPLETE),
]; ];
if($type == COMPLETION_CRITERIA_TYPE_ACTIVITY){ if ($type == COMPLETION_CRITERIA_TYPE_ACTIVITY) {
$cm = $this->modinfo->get_cm($criteria->moduleinstance); $cm = $this->modinfo->get_cm($criteria->moduleinstance);
// If it's an activity completion, add all the relevant activities as sub-items // If it's an activity completion, add all the relevant activities as sub-items.
$completion_status = $this->completion->get_grade_completion($cm,$userid); $completion_status = $this->completion->get_grade_completion($cm, $userid);
$iinfo['status'] = self::completion_handle($completion_status); $iinfo['status'] = self::completion_handle($completion_status);
// Re-evaluate the completed value, to make sure COMPLETE_FAIL doesn't creep in as completed // Re-evaluate the completed value, to make sure COMPLETE_FAIL doesn't creep in as completed.
$iinfo['completed'] = in_array($completion_status,[COMPLETION_COMPLETE, COMPLETION_COMPLETE_PASS]); $iinfo['completed'] = in_array($completion_status, [COMPLETION_COMPLETE, COMPLETION_COMPLETE_PASS]);
// Determine the grade (retrieve from grade item, not from completion) // Determine the grade (retrieve from grade item, not from completion).
$grade = $this->get_grade($cm,$userid); $grade = $this->get_grade($cm, $userid);
$iinfo['grade'] = $grade->grade; $iinfo['grade'] = $grade->grade;
$iinfo['feedback'] = $grade->feedback; $iinfo['feedback'] = $grade->feedback;
$iinfo['pending'] = $grade->pending; $iinfo['pending'] = $grade->pending;
$anypending = $anypending || $grade->pending; $anypending = $anypending || $grade->pending;
// Overwrite the status with progress if something has been graded, or is pending // Overwrite the status with progress if something has been graded, or is pending.
if($completion_status != COMPLETION_INCOMPLETE || $anypending){ if ($completion_status != COMPLETION_INCOMPLETE || $anypending) {
if($cinfo["status"] == "incomplete"){ if ($cinfo["status"] == "incomplete") {
$cinfo["status"] = "progress"; $cinfo["status"] = "progress";
} }
} }
} }
else if ($type == COMPLETION_CRITERIA_TYPE_GRADE){ else if ($type == COMPLETION_CRITERIA_TYPE_GRADE) {
// Make sure we provide the current course grade // Make sure we provide the current course grade.
$iinfo['grade'] = floatval($iinfo['details']['status']); $iinfo['grade'] = floatval($iinfo['details']['status']);
if($iinfo["grade"] > 0){ if ($iinfo["grade"] > 0) {
$iinfo["grade"] = format_float($iinfo["grade"], 1). "/".format_float(floatval($iinfo['details']['requirement'])); $iinfo["grade"] = format_float($iinfo["grade"], 1). "/".format_float(floatval($iinfo['details']['requirement']));
$iinfo["status"] = $completion->is_complete()?"complete-pass":"complete-fail"; $iinfo["status"] = $completion->is_complete()?"complete-pass":"complete-fail";
if ($cinfo["status"] == "incomplete"){ if ($cinfo["status"] == "incomplete") {
$cinfo["status"] = "progress"; $cinfo["status"] = "progress";
} }
} }
} }
// finally add the item to the items list // finally add the item to the items list.
$cinfo["items"][] = $iinfo; $cinfo["items"][] = $iinfo;
} }
// Set the count and progress stats based on the Type's aggregation style // Set the count and progress stats based on the Type's aggregation style.
if($typeaggregation == COMPLETION_AGGREGATION_ALL){ if ($typeaggregation == COMPLETION_AGGREGATION_ALL) {
// Count and Progress amount to the sum of items // Count and Progress amount to the sum of items.
$cinfo["count"] = count($cinfo["items"]); $cinfo["count"] = count($cinfo["items"]);
$cinfo["progress"] = $progress; $cinfo["progress"] = $progress;
} }
else { //$typeaggregation == COMPLETION_AGGREGATION_ANY else { //$typeaggregation == COMPLETION_AGGREGATION_ANY.
// Count and progress are either 1 or 0, since any of the items // Count and progress are either 1 or 0, since any of the items.
// complete's the type // complete's the type.
$cinfo["count"] = (count($cinfo["items"]) > 0)?1:0; $cinfo["count"] = (count($cinfo["items"]) > 0)?1:0;
$cinfo["progress"] = ($progress>0)?1:0; $cinfo["progress"] = ($progress>0)?1:0;
} }
@ -426,41 +444,39 @@ class corecompletioninfo {
* Get the grade for a certain course module * Get the grade for a certain course module
* @return stdClass|null object containing 'grade' and optional 'feedback' attribute * @return stdClass|null object containing 'grade' and optional 'feedback' attribute
*/ */
private function get_grade($cm,$userid){ private function get_grade($cm, $userid) {
// TODO: Display grade in the way described in the course setup (with letters if needed) // TODO: Display grade in the way described in the course setup (with letters if needed).
$gi= grade_item::fetch(['itemtype' => 'mod', $gi= grade_item::fetch(['itemtype' => 'mod',
'itemmodule' => $cm->modname, 'itemmodule' => $cm->modname,
'iteminstance' => $cm->instance, 'iteminstance' => $cm->instance,
'courseid' => $this->course->id]); // Make sure we only get results relevant to this course 'courseid' => $this->course->id]); // Make sure we only get results relevant to this course.
if($gi) if ($gi) {
{ // Only the following types of grade yield a result.
// Only the following types of grade yield a result if (($gi->gradetype == GRADE_TYPE_VALUE || $gi->gradetype == GRADE_TYPE_SCALE)) {
if(($gi->gradetype == GRADE_TYPE_VALUE || $gi->gradetype == GRADE_TYPE_SCALE))
{
$scale = $gi->load_scale(); $scale = $gi->load_scale();
$grade = (object)$gi->get_final($userid); // Get the grade for the specified user $grade = (object)$gi->get_final($userid); // Get the grade for the specified user.
$result = new \stdClass; $result = new \stdClass;
// Check if the final grade is available and numeric (safety check) // Check if the final grade is available and numeric (safety check).
if(!empty($grade) && !empty($grade->finalgrade) && is_numeric($grade->finalgrade)){ if (!empty($grade) && !empty($grade->finalgrade) && is_numeric($grade->finalgrade)) {
// convert scale grades to corresponding scale name // convert scale grades to corresponding scale name.
if(isset($scale)){ if (isset($scale)) {
// get scale value // get scale value.
$result->grade = $scale->get_nearest_item($grade->finalgrade); $result->grade = $scale->get_nearest_item($grade->finalgrade);
}
else
{
// round final grade to 1 decimal point
$result->grade = round($grade->finalgrade,1);
} }
else
{
// round final grade to 1 decimal point.
$result->grade = round($grade->finalgrade, 1);
}
$result->feedback = trim($grade->feedback); $result->feedback = trim($grade->feedback);
$result->pending = (new gradingscanner($gi))->pending($userid); $result->pending = (new gradingscanner($gi))->pending($userid);
} }
else { else {
$result->grade = "-"; // Activity is gradable, but user did not receive a grade yet $result->grade = "-"; // Activity is gradable, but user did not receive a grade yet.
$result->feedback = null; $result->feedback = null;
$result->pending = false; $result->pending = false;
} }
@ -468,46 +484,44 @@ class corecompletioninfo {
} }
} }
return null; // Activity cannot be graded (Shouldn't be happening, but still....) return null; // Activity cannot be graded (Shouldn't be happening, but still....).
} }
/** /**
* Get the grade for a certain course module * Get the grade for a certain course module
* @return stdClass|null object containing 'grade' and optional 'feedback' attribute * @return stdClass|null object containing 'grade' and optional 'feedback' attribute
*/ */
private function get_course_grade($userid){ private function get_course_grade($userid) {
// TODO: Display grade in the way described in the course setup (with letters if needed) // TODO: Display grade in the way described in the course setup (with letters if needed).
$gi= grade_item::fetch(['itemtype' => 'course', $gi= grade_item::fetch(['itemtype' => 'course',
'iteminstance' => $this->course->id, 'iteminstance' => $this->course->id,
'courseid' => $this->course->id]); 'courseid' => $this->course->id]);
if($gi) if ($gi) {
{ // Only the following types of grade yield a result.
// Only the following types of grade yield a result if (($gi->gradetype == GRADE_TYPE_VALUE || $gi->gradetype == GRADE_TYPE_SCALE)) {
if(($gi->gradetype == GRADE_TYPE_VALUE || $gi->gradetype == GRADE_TYPE_SCALE))
{
$scale = $gi->load_scale(); $scale = $gi->load_scale();
$grade = $gi->get_final($userid); // Get the grade for the specified user $grade = $gi->get_final($userid); // Get the grade for the specified user.
// Check if the final grade is available and numeric (safety check) // Check if the final grade is available and numeric (safety check).
if(!empty($grade) && !empty($grade->finalgrade) && is_numeric($grade->finalgrade)){ if (!empty($grade) && !empty($grade->finalgrade) && is_numeric($grade->finalgrade)) {
// convert scale grades to corresponding scale name // convert scale grades to corresponding scale name.
if(isset($scale)){ if (isset($scale)) {
// get scale value // get scale value.
return $scale->get_nearest_item($grade->finalgrade); return $scale->get_nearest_item($grade->finalgrade);
} }
else else
{ {
// round final grade to 1 decimal point // round final grade to 1 decimal point.
return round($grade->finalgrade,1); return round($grade->finalgrade, 1);
} }
} }
else { else {
return "-"; // User did not receive a grade yet for this course return "-"; // User did not receive a grade yet for this course.
} }
} }
} }
return null; // Course cannot be graded (Shouldn't be happening, but still....) return null; // Course cannot be graded (Shouldn't be happening, but still....).
} }
@ -518,8 +532,8 @@ class corecompletioninfo {
* @param int $userid The id of the user, 0 for the current user * @param int $userid The id of the user, 0 for the current user
* @return null|float The percentage, or null if completion is not supported in the course, * @return null|float The percentage, or null if completion is not supported in the course,
* or there are no activities that support completion. * or there are no activities that support completion.
*/ */
function get_progress_percentage($userid){ function get_progress_percentage($userid) {
// First, let's make sure completion is enabled. // First, let's make sure completion is enabled.
if (!$this->completion->is_enabled()) { if (!$this->completion->is_enabled()) {
@ -534,38 +548,38 @@ class corecompletioninfo {
$count = count($completions); $count = count($completions);
$completed = 0; $completed = 0;
// Before we check how many modules have been completed see if the course has completed. // Before we check how many modules have been completed see if the course has completed. .
if ($this->completion->is_course_complete($userid)) { if ($this->completion->is_course_complete($userid)) {
$completed = $count; $completed = $count;
} }
else { else {
// count all completions, but treat // count all completions, but treat .
foreach($completions as $completion){ foreach ($completions as $completion) {
$crit = $completion->get_criteria(); $crit = $completion->get_criteria();
if($crit->criteriatype == COMPLETION_CRITERIA_TYPE_ACTIVITY) { if ($crit->criteriatype == COMPLETION_CRITERIA_TYPE_ACTIVITY) {
// get the cm data object // get the cm data object.
$cm = $this->modinfo->get_cm($crit->moduleinstance); $cm = $this->modinfo->get_cm($crit->moduleinstance);
// retrieve data for this object // retrieve data for this object.
$data = $this->completion->get_data($cm, false, $userid); $data = $this->completion->get_data($cm, false, $userid);
// Count complete, but failed as incomplete too... // Count complete, but failed as incomplete too...
if (($data->completionstate == COMPLETION_INCOMPLETE) || ($data->completionstate == COMPLETION_COMPLETE_FAIL)) { if (($data->completionstate == COMPLETION_INCOMPLETE) || ($data->completionstate == COMPLETION_COMPLETE_FAIL)) {
$completed += 0; $completed += 0;
} else { } else {
$completed += 1; $completed += 1;
} }
} }
else { else {
if($completion->is_complete()){ if ($completion->is_complete()) {
$completed += 1; $completed += 1;
} }
} }
} }
} }
$result = new \stdClass; $result = new \stdClass;
$result->count = $count; $result->count = $count;
$result->completed = $completed; $result->completed = $completed;
$result->percentage = ($completed / $count) * 100; $result->percentage = ($completed / $count) * 100;
return $result; return $result;
} }
/** /**
@ -574,8 +588,8 @@ class corecompletioninfo {
* @param int $userid The id of the user, 0 for the current user * @param int $userid The id of the user, 0 for the current user
* @return null|\stdClass The percentage, or null if completion is not supported in the course, * @return null|\stdClass The percentage, or null if completion is not supported in the course,
* or there are no activities that support completion. * or there are no activities that support completion.
*/ */
function get_advanced_progress_percentage($userid):\stdClass { function get_advanced_progress_percentage($userid):\stdClass {
// First, let's make sure completion is enabled. // First, let's make sure completion is enabled.
if (!$this->completion->is_enabled()) { if (!$this->completion->is_enabled()) {
@ -591,74 +605,74 @@ class corecompletioninfo {
$aggregation = $this->completion->get_aggregation_method(); $aggregation = $this->completion->get_aggregation_method();
$critcount = []; $critcount = [];
// Before we check how many modules have been completed see if the course has completed. // Before we check how many modules have been completed see if the course has completed. .
// count all completions, but treat // count all completions, but treat .
foreach($completions as $completion){ foreach ($completions as $completion) {
$crit = $completion->get_criteria(); $crit = $completion->get_criteria();
// Make a new object for the type if it's not already there // Make a new object for the type if it's not already there.
$type = $crit->criteriatype; $type = $crit->criteriatype;
if(!array_key_exists($type,$critcount)){ if (!array_key_exists($type, $critcount)) {
$critcount[$type] = new \stdClass; $critcount[$type] = new \stdClass;
$critcount[$type]->count = 0; $critcount[$type]->count = 0;
$critcount[$type]->completed = 0; $critcount[$type]->completed = 0;
$critcount[$type]->aggregation = $this->completion->get_aggregation_method($type); $critcount[$type]->aggregation = $this->completion->get_aggregation_method($type);
} }
// Get a reference to the counter object for this type // Get a reference to the counter object for this type.
$typecount =& $critcount[$type]; $typecount =& $critcount[$type];
$typecount->count += 1; $typecount->count += 1;
if($crit->criteriatype == COMPLETION_CRITERIA_TYPE_ACTIVITY) { if ($crit->criteriatype == COMPLETION_CRITERIA_TYPE_ACTIVITY) {
// get the cm data object // get the cm data object.
$cm = $this->modinfo->get_cm($crit->moduleinstance); $cm = $this->modinfo->get_cm($crit->moduleinstance);
// retrieve data for this object // retrieve data for this object.
$data = $this->completion->get_data($cm, false, $userid); $data = $this->completion->get_data($cm, false, $userid);
// Count complete, but failed as incomplete too... // Count complete, but failed as incomplete too...
if (($data->completionstate == COMPLETION_INCOMPLETE) || ($data->completionstate == COMPLETION_COMPLETE_FAIL)) { if (($data->completionstate == COMPLETION_INCOMPLETE) || ($data->completionstate == COMPLETION_COMPLETE_FAIL)) {
$typecount->completed += 0; $typecount->completed += 0;
} else { } else {
$typecount->completed += 1; $typecount->completed += 1;
} }
} }
else { else {
if($completion->is_complete()){ if ($completion->is_complete()) {
$typecount->completed += 1; $typecount->completed += 1;
} }
} }
} }
// Now that we have all completions sorted by type, we can be smart about how to do the count // Now that we have all completions sorted by type, we can be smart about how to do the count.
$count = 0; $count = 0;
$completed = 0; $completed = 0;
$completion_percentage = 0; $completion_percentage = 0;
foreach($critcount as $c){ foreach ($critcount as $c) {
// Take only types that are actually present into account // Take only types that are actually present into account.
if($c->count > 0){ if ($c->count > 0) {
// If the aggregation for the type is ANY, reduce the count to 1 for this type // If the aggregation for the type is ANY, reduce the count to 1 for this type.
// And adjust the progress accordingly (check if any have been completed or not) // And adjust the progress accordingly (check if any have been completed or not).
if($c->aggregation == COMPLETION_AGGREGATION_ALL){ if ($c->aggregation == COMPLETION_AGGREGATION_ALL) {
$ct = $c->count; $ct = $c->count;
$cmpl = $c->completed; $cmpl = $c->completed;
} }
else { else {
$ct = 1; $ct = 1;
$cmpl = ($c->completed > 0)?1:0; $cmpl = ($c->completed > 0)?1:0;
} }
// if ANY completion for the types, count only the criteria type with the highest completion percentage - // if ANY completion for the types, count only the criteria type with the highest completion percentage -.
// Overwrite data if current type is more complete // Overwrite data if current type is more complete.
if($aggregation == COMPLETION_AGGREGATION_ANY) { if ($aggregation == COMPLETION_AGGREGATION_ANY) {
$pct = $cmpl/$ct; $pct = $cmpl/$ct;
if($pct > $completion_percentage){ if ($pct > $completion_percentage) {
$count = $ct; $count = $ct;
$completed = $cmpl; $completed = $cmpl;
$completion_percentage = $pct; $completion_percentage = $pct;
} }
} }
// if ALL completion for the types, add the count for this type to that of the others // if ALL completion for the types, add the count for this type to that of the others.
else { else {
$count += $ct; $count += $ct;
$completed += $cmpl; $completed += $cmpl;
// Don't really care about recalculating completion percentage every round in this case // Don't really care about recalculating completion percentage every round in this case.
} }
} }
} }
@ -667,8 +681,8 @@ class corecompletioninfo {
$result->count = $count; $result->count = $count;
$result->completed = $completed; $result->completed = $completed;
$result->percentage = ($count > 0)?(($completed / $count) * 100):0; $result->percentage = ($count > 0)?(($completed / $count) * 100):0;
return $result; return $result;
} }

View File

@ -1,4 +1,24 @@
<?php <?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/>.
/**
*
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace local_treestudyplan; namespace local_treestudyplan;
require_once($CFG->libdir.'/externallib.php'); require_once($CFG->libdir.'/externallib.php');
@ -13,70 +33,70 @@ use \grade_outcome;
class courseinfo { class courseinfo {
const TABLE = 'course'; const TABLE = 'course';
private $course; private $course;
private $context; private $context;
private $coursecontext; private $coursecontext;
private $studyitem; private $studyitem;
private static $contentitems = null; private static $contentitems = null;
public function id(){ public function id() {
return $this->course->id; return $this->course->id;
} }
public function shortname(){ public function shortname() {
return $this->course->shortname; return $this->course->shortname;
} }
public function course(){ public function course() {
return $this->course; // php arrays are assigned by copy return $this->course; // php arrays are assigned by copy.
} }
public function course_context(){ public function course_context() {
return $this->coursecontext; return $this->coursecontext;
} }
public function category_context(){ public function category_context() {
return $this->context; return $this->context;
} }
protected function get_contentitems() { protected function get_contentitems() {
global $PAGE; global $PAGE;
if(empty(static::$contentitems)){ if (empty(static::$contentitems)) {
$PAGE->set_context(\context_system::instance()); $PAGE->set_context(\context_system::instance());
static::$contentitems = (new content_item_readonly_repository())->find_all(); static::$contentitems = (new content_item_readonly_repository())->find_all();
} }
return static::$contentitems; return static::$contentitems;
} }
protected function amTeacher(){ protected function amTeacher() {
global $USER; global $USER;
return is_enrolled($this->coursecontext, $USER, 'mod/assign:grade'); return is_enrolled($this->coursecontext, $USER, 'mod/assign:grade');
} }
protected function iCanSelectGradables($userid=-1){ protected function iCanSelectGradables($userid=-1) {
global $USER, $DB; global $USER, $DB;
if($userid <= 0){ if ($userid <= 0) {
$usr = $USER; $usr = $USER;
} }
else else
{ {
$usr = $DB->get_record('user', ['id' => $userid, 'deleted' => 0]); $usr = $DB->get_record('user', ['id' => $userid, 'deleted' => 0]);
} }
return($usr && is_enrolled($this->coursecontext, $usr, 'local/treestudyplan:selectowngradables')); return($usr && is_enrolled($this->coursecontext, $usr, 'local/treestudyplan:selectowngradables'));
} }
public static function get_contentitem($name) { public static function get_contentitem($name) {
$contentitems = static::get_contentitems(); $contentitems = static::get_contentitems();
for($i = 0; $i < count($contentitems); $i++){ for($i = 0; $i < count($contentitems); $i++) {
if($contentitems[$i]->get_name() == $name){ if ($contentitems[$i]->get_name() == $name) {
return $contentitems[$i]; return $contentitems[$i];
} }
} }
return null; return null;
} }
public function __construct($id,studyitem $studyitem = null){ public function __construct($id, studyitem $studyitem = null) {
global $DB; global $DB;
$this->studyitem = $studyitem; $this->studyitem = $studyitem;
$this->course = \get_course($id); $this->course = \get_course($id);
@ -84,71 +104,69 @@ class courseinfo {
$this->coursecontext = \context_course::instance($this->course->id); $this->coursecontext = \context_course::instance($this->course->id);
} }
public static function exists($id){ public static function exists($id) {
global $DB; global $DB;
return is_numeric($id) && $DB->record_exists(self::TABLE, ['id' => $id]); return is_numeric($id) && $DB->record_exists(self::TABLE, ['id' => $id]);
} }
public static function id_from_shortname($shortname){ public static function id_from_shortname($shortname) {
global $DB; global $DB;
return $DB->get_field(self::TABLE, "id", ['shortname' => $shortname]); return $DB->get_field(self::TABLE, "id", ['shortname' => $shortname]);
} }
public static function coursetiming($course){ public static function coursetiming($course) {
$now = time(); $now = time();
if($now > $course->startdate) if ($now > $course->startdate) {
{ if ($course->enddate > 0 && $now > $course->enddate) {
if($course->enddate > 0 && $now > $course->enddate)
{
return "past"; return "past";
} }
else { else {
return "present"; return "present";
} }
} }
else{ else{
return "future"; return "future";
} }
} }
public function timing(){ public function timing() {
return self::coursetiming($this->course); return self::coursetiming($this->course);
} }
public function displayname(){ public function displayname() {
$displayfield = get_config("local_treestudyplan","display_field"); $displayfield = get_config("local_treestudyplan", "display_field");
if ($displayfield == "idnumber") { if ($displayfield == "idnumber") {
$idnumber = trim(preg_replace("/\s+/u", " ",$this->course->idnumber)); $idnumber = trim(preg_replace("/\s+/u", " ", $this->course->idnumber));
if(strlen($idnumber) > 0){ if (strlen($idnumber) > 0) {
return $this->course->idnumber; return $this->course->idnumber;
} }
} else if(strpos( $displayfield ,"customfield_") === 0) { } else if (strpos( $displayfield , "customfield_") === 0) {
$fieldname = substr($displayfield,strlen("customfield_")); $fieldname = substr($displayfield, strlen("customfield_"));
$handler = \core_customfield\handler::get_handler('core_course', 'course'); $handler = \core_customfield\handler::get_handler('core_course', 'course');
$datas = $handler->get_instance_data($this->course->id); $datas = $handler->get_instance_data($this->course->id);
foreach($datas as $data){ foreach ($datas as $data) {
if($data->get_field()->get('shortname') == $fieldname){ if ($data->get_field()->get('shortname') == $fieldname) {
$value = trim(preg_replace("/\s+/u", " ",$data->get_value())); $value = trim(preg_replace("/\s+/u", " ", $data->get_value()));
if(strlen($value) > 0){ if (strlen($value) > 0) {
return $value; return $value;
} }
} }
} }
} }
// Fallback to shortname when the specified display field fails, since shortname is never empty // Fallback to shortname when the specified display field fails, since shortname is never empty.
return $this->course->shortname; return $this->course->shortname;
} }
public static function simple_structure($value=VALUE_REQUIRED){ public static function simple_structure($value=VALUE_REQUIRED) {
return new \external_single_structure([ return new \external_single_structure([
"id" => new \external_value(PARAM_INT, 'linked course id'), "id" => new \external_value(PARAM_INT, 'linked course id'),
"fullname" => new \external_value(PARAM_TEXT, 'linked course name'), "fullname" => new \external_value(PARAM_TEXT, 'linked course name'),
"shortname" => new \external_value(PARAM_TEXT, 'linked course shortname'), "shortname" => new \external_value(PARAM_TEXT, 'linked course shortname'),
"displayname" => new \external_value(PARAM_TEXT, 'linked course displayname'), "displayname" => new \external_value(PARAM_TEXT, 'linked course displayname'),
"context" => contextinfo::structure(VALUE_OPTIONAL), "context" => contextinfo::structure(VALUE_OPTIONAL),
], 'referenced course information',$value); ], 'referenced course information', $value);
} }
public function simple_model() { public function simple_model() {
@ -160,11 +178,11 @@ class courseinfo {
'displayname' => $this->displayname(), 'displayname' => $this->displayname(),
'context' => $contextinfo->model() 'context' => $contextinfo->model()
]; ];
return $info; return $info;
} }
public static function editor_structure($value=VALUE_REQUIRED){ public static function editor_structure($value=VALUE_REQUIRED) {
return new \external_single_structure([ return new \external_single_structure([
"id" => new \external_value(PARAM_INT, 'linked course id'), "id" => new \external_value(PARAM_INT, 'linked course id'),
"fullname" => new \external_value(PARAM_TEXT, 'linked course name'), "fullname" => new \external_value(PARAM_TEXT, 'linked course name'),
@ -172,7 +190,7 @@ class courseinfo {
"displayname" => new \external_value(PARAM_TEXT, 'linked course displayname'), "displayname" => new \external_value(PARAM_TEXT, 'linked course displayname'),
"context" => contextinfo::structure(VALUE_OPTIONAL), "context" => contextinfo::structure(VALUE_OPTIONAL),
"ctxid" => new \external_value(PARAM_INT, 'course context id name'), "ctxid" => new \external_value(PARAM_INT, 'course context id name'),
"grades" => new \external_multiple_structure(gradeinfo::editor_structure(),'grade list (legacy list)',VALUE_OPTIONAL), "grades" => new \external_multiple_structure(gradeinfo::editor_structure(), 'grade list (legacy list)', VALUE_OPTIONAL),
"completion" => corecompletioninfo::editor_structure(VALUE_OPTIONAL), "completion" => corecompletioninfo::editor_structure(VALUE_OPTIONAL),
"timing" => new \external_value(PARAM_TEXT, '(past|present|future)'), "timing" => new \external_value(PARAM_TEXT, '(past|present|future)'),
"startdate" => new \external_value(PARAM_TEXT, 'Course start date'), "startdate" => new \external_value(PARAM_TEXT, 'Course start date'),
@ -181,15 +199,15 @@ class courseinfo {
"canupdatecourse" => new \external_value(PARAM_BOOL, "If the current user can update this course"), "canupdatecourse" => new \external_value(PARAM_BOOL, "If the current user can update this course"),
"canselectgradables" => new \external_value(PARAM_BOOL, 'Requesting user can change selected gradables'), "canselectgradables" => new \external_value(PARAM_BOOL, 'Requesting user can change selected gradables'),
"tag" => new \external_value(PARAM_TEXT, 'Tag'), "tag" => new \external_value(PARAM_TEXT, 'Tag'),
], 'referenced course information',$value); ], 'referenced course information', $value);
} }
public function editor_model(studyitem $studyitem=null, $usecorecompletioninfo=false) { public function editor_model(studyitem $studyitem=null, $usecorecompletioninfo=false) {
global $DB; global $DB;
$contextinfo = new contextinfo($this->context); $contextinfo = new contextinfo($this->context);
$timing = $this->timing(); $timing = $this->timing();
$info = [ $info = [
'id' => $this->course->id, 'id' => $this->course->id,
'fullname' => $this->course->fullname, 'fullname' => $this->course->fullname,
@ -198,19 +216,19 @@ class courseinfo {
'context' => $contextinfo->model(), 'context' => $contextinfo->model(),
'ctxid' => $this->coursecontext->id, 'ctxid' => $this->coursecontext->id,
'timing' => $timing, 'timing' => $timing,
'startdate' => date("Y-m-d",$this->course->startdate,), 'startdate' => date("Y-m-d", $this->course->startdate, ),
'enddate' => date("Y-m-d",$this->course->enddate), 'enddate' => date("Y-m-d", $this->course->enddate),
'amteacher' => $this->amTeacher(), 'amteacher' => $this->amTeacher(),
'canupdatecourse' => \has_capability("moodle/course:update",$this->coursecontext), 'canupdatecourse' => \has_capability("moodle/course:update", $this->coursecontext),
'canselectgradables' => $this->iCanSelectGradables(), 'canselectgradables' => $this->iCanSelectGradables(),
'tag' => "Editormodel", 'tag' => "Editormodel",
'grades' => [], 'grades' => [],
]; ];
if(!$usecorecompletioninfo){ if (!$usecorecompletioninfo) {
$gradables = gradeinfo::list_course_gradables($this->course,$studyitem); $gradables = gradeinfo::list_course_gradables($this->course, $studyitem);
foreach($gradables as $gradable) { foreach ($gradables as $gradable) {
$info['grades'][] = $gradable->editor_model($studyitem); $info['grades'][] = $gradable->editor_model($studyitem);
} }
} }
@ -222,7 +240,7 @@ class courseinfo {
return $info; return $info;
} }
public static function user_structure($value=VALUE_REQUIRED){ public static function user_structure($value=VALUE_REQUIRED) {
return new \external_single_structure([ return new \external_single_structure([
"id" => new \external_value(PARAM_INT, 'linked course id'), "id" => new \external_value(PARAM_INT, 'linked course id'),
"fullname" => new \external_value(PARAM_TEXT, 'linked course name'), "fullname" => new \external_value(PARAM_TEXT, 'linked course name'),
@ -230,15 +248,15 @@ class courseinfo {
"displayname" => new \external_value(PARAM_TEXT, 'linked course displayname'), "displayname" => new \external_value(PARAM_TEXT, 'linked course displayname'),
"context" => contextinfo::structure(VALUE_OPTIONAL), "context" => contextinfo::structure(VALUE_OPTIONAL),
"ctxid" => new \external_value(PARAM_INT, 'course context id name'), "ctxid" => new \external_value(PARAM_INT, 'course context id name'),
"grades" => new \external_multiple_structure(gradeinfo::user_structure(),'grade list (legacy list)',VALUE_OPTIONAL), "grades" => new \external_multiple_structure(gradeinfo::user_structure(), 'grade list (legacy list)', VALUE_OPTIONAL),
"completion" => corecompletioninfo::user_structure(VALUE_OPTIONAL), "completion" => corecompletioninfo::user_structure(VALUE_OPTIONAL),
"timing" => new \external_value(PARAM_TEXT, '(past|present|future)'), "timing" => new \external_value(PARAM_TEXT, '(past|present|future)'),
"startdate" => new \external_value(PARAM_TEXT, 'Course start date'), "startdate" => new \external_value(PARAM_TEXT, 'Course start date'),
"enddate" => new \external_value(PARAM_TEXT, 'Course end date'), "enddate" => new \external_value(PARAM_TEXT, 'Course end date'),
], 'course information',$value); ], 'course information', $value);
} }
public function user_model($userid,$usecorecompletioninfo=false) { public function user_model($userid, $usecorecompletioninfo=false) {
global $DB; global $DB;
$contextinfo = new contextinfo($this->context); $contextinfo = new contextinfo($this->context);
@ -252,14 +270,14 @@ class courseinfo {
'context' => $contextinfo->model(), 'context' => $contextinfo->model(),
'ctxid' => $this->coursecontext->id, 'ctxid' => $this->coursecontext->id,
'timing' => $timing, 'timing' => $timing,
'startdate' => date("Y-m-d",$this->course->startdate), 'startdate' => date("Y-m-d", $this->course->startdate),
'enddate' => date("Y-m-d",$this->course->enddate), 'enddate' => date("Y-m-d", $this->course->enddate),
'grades' => [], 'grades' => [],
]; ];
if(!$usecorecompletioninfo){ if (!$usecorecompletioninfo) {
$gradables = gradeinfo::list_studyitem_gradables($this->studyitem); $gradables = gradeinfo::list_studyitem_gradables($this->studyitem);
foreach($gradables as $gi) { foreach ($gradables as $gi) {
$info['grades'][] = $gi->user_model($userid); $info['grades'][] = $gi->user_model($userid);
} }
} }

View File

@ -1,4 +1,24 @@
<?php <?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/>.
/**
*
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace local_treestudyplan; namespace local_treestudyplan;
require_once($CFG->libdir.'/externallib.php'); require_once($CFG->libdir.'/externallib.php');
@ -15,22 +35,22 @@ class coursemoduleinfo {
private $cm_info; private $cm_info;
private $db_record; private $db_record;
public function __construct($id){ public function __construct($id) {
global $DB; global $DB;
// Determine the icon for the associated activity // Determine the icon for the associated activity.
$this->id = $id; $this->id = $id;
$this->cm = $DB->get_record("course_modules",["id" => $id]); $this->cm = $DB->get_record("course_modules", ["id" => $id]);
$this->cm_info = \cm_info::create($this->cm); $this->cm_info = \cm_info::create($this->cm);
// $this->db_record = $DB->get_record($this->cm_info->modname,["id" => $this->cm_info->instance]); // $this->db_record = $DB->get_record($this->cm_info->modname, ["id" => $this->cm_info->instance]);.
} }
public function getTitle(){ public function getTitle() {
return $this->cm_info->name; return $this->cm_info->name;
} }
public function setTitle($value){ public function setTitle($value) {
$this->cm_info->set_name($value); $this->cm_info->set_name($value);
// TODO: Actually save this after setting the cminfo // TODO: Actually save this after setting the cminfo.
} }
} }

View File

@ -1,4 +1,25 @@
<?php <?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/>.
/**
*
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace local_treestudyplan; namespace local_treestudyplan;
require_once($CFG->libdir.'/externallib.php'); require_once($CFG->libdir.'/externallib.php');
@ -8,7 +29,7 @@ use \local_treestudyplan\local\helpers\webservicehelper;
use \local_treestudyplan\completionscanner; use \local_treestudyplan\completionscanner;
use \local_treestudyplan\gradingscanner; use \local_treestudyplan\gradingscanner;
class courseservice extends \external_api class courseservice extends \external_api
{ {
const CAP_EDIT = "local/treestudyplan:editstudyplan"; const CAP_EDIT = "local/treestudyplan:editstudyplan";
const CAP_VIEW = "local/treestudyplan:viewuserreports"; const CAP_VIEW = "local/treestudyplan:viewuserreports";
@ -19,86 +40,85 @@ class courseservice extends \external_api
* * * *
************************/ ************************/
public static function map_categories_parameters() public static function map_categories_parameters()
{ {
return new \external_function_parameters( [ return new \external_function_parameters( [
"root_id" => new \external_value(PARAM_INT, 'root category to use as base', VALUE_DEFAULT), "root_id" => new \external_value(PARAM_INT, 'root category to use as base', VALUE_DEFAULT),
] ); ] );
} }
public static function map_categories_returns() public static function map_categories_returns()
{ {
return new \external_multiple_structure(static::map_category_structure(false)); return new \external_multiple_structure(static::map_category_structure(false));
} }
protected static function map_category_structure($lazy=false,$value=VALUE_REQUIRED) protected static function map_category_structure($lazy=false, $value=VALUE_REQUIRED) {
{
$s = [ $s = [
"id" => new \external_value(PARAM_INT, 'course category id'), "id" => new \external_value(PARAM_INT, 'course category id'),
"context_id" => new \external_value(PARAM_INT, 'course category context id'), "context_id" => new \external_value(PARAM_INT, 'course category context id'),
"category" => contextinfo::structure(VALUE_OPTIONAL), "category" => contextinfo::structure(VALUE_OPTIONAL),
"haschildren" => new \external_value(PARAM_BOOL, 'True if the category has child categories'), "haschildren" => new \external_value(PARAM_BOOL, 'True if the category has child categories'),
"hascourses" => new \external_value(PARAM_BOOL, 'True if the category contains courses'), "hascourses" => new \external_value(PARAM_BOOL, 'True if the category contains courses'),
"studyplancount" => new \external_value(PARAM_INT, 'number of linked studyplans',VALUE_OPTIONAL), "studyplancount" => new \external_value(PARAM_INT, 'number of linked studyplans', VALUE_OPTIONAL),
]; ];
if(!$lazy > 0) { if (!$lazy > 0) {
$s["courses"] = new \external_multiple_structure( courseinfo::editor_structure() ); $s["courses"] = new \external_multiple_structure( courseinfo::editor_structure() );
$s["children"] = new \external_multiple_structure( static::map_category_structure(true)); $s["children"] = new \external_multiple_structure( static::map_category_structure(true));
} }
return new \external_single_structure($s,"CourseCat info",$value); return new \external_single_structure($s, "CourseCat info", $value);
} }
public static function map_categories($root_id = 0){ public static function map_categories($root_id = 0) {
global $CFG, $DB; global $CFG, $DB;
$root = \core_course_category::get($root_id); $root = \core_course_category::get($root_id);
$context = $root->get_context(); $context = $root->get_context();
// Make sure the user has access to the context for editing purposes // Make sure the user has access to the context for editing purposes.
webservicehelper::require_capabilities(self::CAP_EDIT,$context); webservicehelper::require_capabilities(self::CAP_EDIT, $context);
// Determine top categories from provided context // Determine top categories from provided context.
if($root->id == 0){ if ($root->id == 0) {
// on the system level, determine the user's topmost allowed catecories // on the system level, determine the user's topmost allowed catecories.
$user_top = \core_course_category::user_top(); $user_top = \core_course_category::user_top();
if($user_top->id == 0){ // top category.. if ($user_top->id == 0) { // top category..
$children = $root->get_children(); // returns a list of çore_course_category, let it overwrite $children $children = $root->get_children(); // returns a list of çore_course_category, let it overwrite $children.
} else { } else {
$children = [$user_top]; $children = [$user_top];
} }
} else if ($root->is_uservisible()){ } else if ($root->is_uservisible()) {
$children = [$root]; $children = [$root];
} }
foreach($children as $cat){ foreach ($children as $cat) {
$list[] = static::map_category($cat,false); $list[] = static::map_category($cat, false);
} }
return $list; return $list;
} }
public static function get_category_parameters() public static function get_category_parameters()
{ {
return new \external_function_parameters( [ return new \external_function_parameters( [
"id" => new \external_value(PARAM_INT, 'id of category'), "id" => new \external_value(PARAM_INT, 'id of category'),
] ); ] );
} }
public static function get_category_returns() public static function get_category_returns()
{ {
return static::map_category_structure(false); return static::map_category_structure(false);
} }
public static function get_category($id){ public static function get_category($id) {
$cat = \core_course_category::get($id); $cat = \core_course_category::get($id);
return static::map_category($cat); return static::map_category($cat);
} }
protected static function map_category(\core_course_category $cat,$lazy=false){ protected static function map_category(\core_course_category $cat, $lazy=false) {
global $DB; global $DB;
$catcontext = $cat->get_context(); $catcontext = $cat->get_context();
$ctx_info = new contextinfo($catcontext); $ctx_info = new contextinfo($catcontext);
$children = $cat->get_children(); // only shows children visible to the current user $children = $cat->get_children(); // only shows children visible to the current user.
$courses = $cat->get_courses(); $courses = $cat->get_courses();
$model = [ $model = [
"id" => $cat->id, "id" => $cat->id,
@ -108,40 +128,39 @@ class courseservice extends \external_api
"hascourses" => !empty($courses), "hascourses" => !empty($courses),
]; ];
if(!$lazy) if (!$lazy) {
{
$model["courses"] = []; $model["courses"] = [];
foreach($courses as $course){ foreach ($courses as $course) {
$courseinfo = new courseinfo($course->id); $courseinfo = new courseinfo($course->id);
$model["courses"][] = $courseinfo->editor_model(); $model["courses"][] = $courseinfo->editor_model();
} }
$model["children"] = []; $model["children"] = [];
foreach($children as $child){ foreach ($children as $child) {
$model["children"][] = static::map_category($child,true); $model["children"][] = static::map_category($child, true);
} }
} }
return $model; return $model;
} }
public static function list_accessible_categories_parameters() public static function list_accessible_categories_parameters()
{ {
return new \external_function_parameters( [ return new \external_function_parameters( [
"operation" => new \external_value(PARAM_TEXT, 'type of operation ["view"|"edit"]',VALUE_DEFAULT),] "operation" => new \external_value(PARAM_TEXT, 'type of operation ["view"|"edit"]', VALUE_DEFAULT), ]
); );
} }
public static function list_accessible_categories_returns() public static function list_accessible_categories_returns()
{ {
return new \external_multiple_structure(static::map_category_structure(true)); return new \external_multiple_structure(static::map_category_structure(true));
} }
public static function list_accessible_categories($operation="edit") public static function list_accessible_categories($operation="edit")
{ {
if($operation == "edit"){ if ($operation == "edit") {
$capability = self::CAP_EDIT; $capability = self::CAP_EDIT;
} else { // $operation == "view" || default } else { // $operation == "view" || default.
$capability = self::CAP_VIEW; $capability = self::CAP_VIEW;
} }
@ -149,35 +168,35 @@ class courseservice extends \external_api
$list = []; $list = [];
/* @var $cat \core_course_category */ /* @var $cat \core_course_category */
foreach($cats as $cat){ foreach ($cats as $cat) {
$list[] = static::map_category($cat,true); $list[] = static::map_category($cat, true);
} }
return $list; return $list;
} }
public static function categories_by_capability($capability,\core_course_category $parent=null){ public static function categories_by_capability($capability, \core_course_category $parent=null) {
// List the categories in which the user has a specific capability // List the categories in which the user has a specific capability.
$list = []; $list = [];
// initialize parent if needed // initialize parent if needed.
if($parent == null){ if ($parent == null) {
$parent = \core_course_category::user_top(); $parent = \core_course_category::user_top();
if(has_capability($capability,$parent->get_context())){ if (has_capability($capability, $parent->get_context())) {
$list[] = $parent; $list[] = $parent;
} }
} }
$children = $parent->get_children(); $children = $parent->get_children();
foreach($children as $child){ foreach ($children as $child) {
// Check if we should add this category // Check if we should add this category.
if(has_capability($capability,$child->get_context())){ if (has_capability($capability, $child->get_context())) {
$list[] = $child; $list[] = $child;
// For optimization purposes, we include all its children now, since they will have inherited the permission // For optimization purposes, we include all its children now, since they will have inherited the permission.
// #PREMATURE_OPTIMIZATION ??? // #PREMATURE_OPTIMIZATION ???.
$list = array_merge($list,self::recursive_child_categories($child)); $list = array_merge($list, self::recursive_child_categories($child));
} else { } else {
if($child->get_children_count() > 0){ if ($child->get_children_count() > 0) {
$list = array_merge($list,self::categories_by_capability($capability,$child)); $list = array_merge($list, self::categories_by_capability($capability, $child));
} }
} }
} }
@ -185,91 +204,91 @@ class courseservice extends \external_api
return $list; return $list;
} }
protected static function recursive_child_categories(\core_course_category $parent){ protected static function recursive_child_categories(\core_course_category $parent) {
$list = []; $list = [];
$children = $parent->get_children(); $children = $parent->get_children();
foreach($children as $child){ foreach ($children as $child) {
$list[] = $child; $list[] = $child;
if($child->get_children_count() > 0){ if ($child->get_children_count() > 0) {
$list = array_merge($list,self::recursive_child_categories($child)); $list = array_merge($list, self::recursive_child_categories($child));
} }
} }
return $list; return $list;
} }
public static function list_used_categories_parameters() public static function list_used_categories_parameters()
{ {
return new \external_function_parameters( [ return new \external_function_parameters( [
"operation" => new \external_value(PARAM_TEXT, 'type of operation ["view"|"edit"]',VALUE_DEFAULT), "operation" => new \external_value(PARAM_TEXT, 'type of operation ["view"|"edit"]', VALUE_DEFAULT),
]); ]);
} }
public static function list_used_categories_returns() public static function list_used_categories_returns()
{ {
return new \external_multiple_structure(static::map_category_structure(true)); return new \external_multiple_structure(static::map_category_structure(true));
} }
public static function list_used_categories($operation='edit') public static function list_used_categories($operation='edit')
{ {
global $DB; global $DB;
if($operation == "edit"){ if ($operation == "edit") {
$capability = self::CAP_EDIT; $capability = self::CAP_EDIT;
} else { // $operation == "view" || default } else { // $operation == "view" || default.
$capability = self::CAP_VIEW; $capability = self::CAP_VIEW;
} }
$context_ids = []; $context_ids = [];
$rs = $DB->get_recordset_sql("SELECT DISTINCT context_id, COUNT(*) as num FROM {local_treestudyplan} $rs = $DB->get_recordset_sql("SELECT DISTINCT context_id, COUNT(*) as num FROM {local_treestudyplan}
GROUP BY context_id"); GROUP BY context_id");
foreach($rs as $r){ foreach ($rs as $r) {
$context_ids[$r->context_id] = $r->num; $context_ids[$r->context_id] = $r->num;
} }
$rs->close(); $rs->close();
// Now filter the categories that the user has acces to by the used context id's
// (That should filter out irrelevant stuff) // Now filter the categories that the user has acces to by the used context id's.
// (That should filter out irrelevant stuff).
$cats = static::categories_by_capability($capability); $cats = static::categories_by_capability($capability);
$list = []; $list = [];
foreach($cats as $cat){ foreach ($cats as $cat) {
$count = 0; $count = 0;
$ctxid = $cat->get_context()->id; $ctxid = $cat->get_context()->id;
if(array_key_exists($ctxid,$context_ids)){ if (array_key_exists($ctxid, $context_ids)) {
$count = $context_ids[$ctxid]; $count = $context_ids[$ctxid];
} }
$o = static::map_category($cat,true); $o = static::map_category($cat, true);
$o["studyplancount"] = $count; $o["studyplancount"] = $count;
$list[] = $o; $list[] = $o;
} }
return $list; return $list;
} }
public static function list_accessible_categories_with_usage($operation='edit'){ public static function list_accessible_categories_with_usage($operation='edit') {
global $DB; global $DB;
if($operation == "edit"){ if ($operation == "edit") {
$capability = self::CAP_EDIT; $capability = self::CAP_EDIT;
} else { // $operation == "view" || default } else { // $operation == "view" || default.
$capability = self::CAP_VIEW; $capability = self::CAP_VIEW;
} }
// retrieve context ids used // retrieve context ids used.
$context_ids = []; $context_ids = [];
$rs = $DB->get_recordset_sql("SELECT DISTINCT context_id, COUNT(*) as num FROM {local_treestudyplan} $rs = $DB->get_recordset_sql("SELECT DISTINCT context_id, COUNT(*) as num FROM {local_treestudyplan}
GROUP BY context_id"); GROUP BY context_id");
foreach($rs as $r){ foreach ($rs as $r) {
$context_ids[$r->context_id] = $r->num; $context_ids[$r->context_id] = $r->num;
} }
$rs->close(); $rs->close();
// Now filter the categories that the user has acces to by the used context id's // Now filter the categories that the user has acces to by the used context id's.
// (That should filter out irrelevant stuff) // (That should filter out irrelevant stuff).
$cats = static::categories_by_capability($capability); $cats = static::categories_by_capability($capability);
$list = []; $list = [];
foreach($cats as $cat){ foreach ($cats as $cat) {
$count = 0; $count = 0;
$ctxid = $cat->get_context()->id; $ctxid = $cat->get_context()->id;
if(array_key_exists($ctxid,$context_ids)){ if (array_key_exists($ctxid, $context_ids)) {
$count = $context_ids[$ctxid]; $count = $context_ids[$ctxid];
} }
$o = new \stdClass(); $o = new \stdClass();
@ -283,38 +302,35 @@ class courseservice extends \external_api
/************************************** /**************************************
* *
* Progress scanners for teacherview * Progress scanners for teacherview
* *
**************************************/ **************************************/
public static function scan_grade_progress_parameters() public static function scan_grade_progress_parameters() {
{
return new \external_function_parameters( [ return new \external_function_parameters( [
"gradeitemid" => new \external_value(PARAM_INT, 'Grade item ID to scan progress for',VALUE_DEFAULT), "gradeitemid" => new \external_value(PARAM_INT, 'Grade item ID to scan progress for', VALUE_DEFAULT),
"studyplanid" => new \external_value(PARAM_INT, 'Study plan id to check progress in',VALUE_DEFAULT), "studyplanid" => new \external_value(PARAM_INT, 'Study plan id to check progress in', VALUE_DEFAULT),
]); ]);
} }
public static function scan_grade_progress_returns() public static function scan_grade_progress_returns() {
{
return gradingscanner::structure(VALUE_REQUIRED); return gradingscanner::structure(VALUE_REQUIRED);
} }
public static function scan_grade_progress($gradeitemid, $studyplanid) public static function scan_grade_progress($gradeitemid, $studyplanid) {
{
global $DB; global $DB;
// Verify access to the study plan // Verify access to the study plan.
$o = studyplan::findById($studyplanid); $o = studyplan::findById($studyplanid);
webservicehelper::require_capabilities(self::CAP_VIEW,$o->context()); webservicehelper::require_capabilities(self::CAP_VIEW, $o->context());
// Retrieve grade item // Retrieve grade item.
$gi = \grade_item::fetch(["id" => $gradeitemid]); $gi = \grade_item::fetch(["id" => $gradeitemid]);
// Validate course is linked to studyplan // Validate course is linked to studyplan.
$courseid = $gi->courseid; $courseid = $gi->courseid;
if(!$o->course_linked($courseid)){ if (!$o->course_linked($courseid)) {
throw new \webservice_access_exception("Course {$courseid} linked to grade item {$gradeitemid} is not linked to studyplan {$o->id()}"); throw new \webservice_access_exception("Course {$courseid} linked to grade item {$gradeitemid} is not linked to studyplan {$o->id()}");
} }
@ -323,44 +339,44 @@ class courseservice extends \external_api
} }
public static function scan_completion_progress_parameters() public static function scan_completion_progress_parameters()
{ {
return new \external_function_parameters( [ return new \external_function_parameters( [
"criteriaid" => new \external_value(PARAM_INT, 'CriteriaID to scan progress for',VALUE_DEFAULT), "criteriaid" => new \external_value(PARAM_INT, 'CriteriaID to scan progress for', VALUE_DEFAULT),
"studyplanid" => new \external_value(PARAM_INT, 'Study plan id to check progress in',VALUE_DEFAULT), "studyplanid" => new \external_value(PARAM_INT, 'Study plan id to check progress in', VALUE_DEFAULT),
"courseid" => new \external_value(PARAM_INT, 'Course id of criteria',VALUE_DEFAULT), "courseid" => new \external_value(PARAM_INT, 'Course id of criteria', VALUE_DEFAULT),
]); ]);
} }
public static function scan_completion_progress_returns() public static function scan_completion_progress_returns()
{ {
return completionscanner::structure(VALUE_REQUIRED); return completionscanner::structure(VALUE_REQUIRED);
} }
public static function scan_completion_progress($criteriaid, $studyplanid,$courseid) public static function scan_completion_progress($criteriaid, $studyplanid, $courseid)
{ {
global $DB; global $DB;
// Verify access to the study plan // Verify access to the study plan.
$o = studyplan::findById($studyplanid); $o = studyplan::findById($studyplanid);
webservicehelper::require_capabilities(self::CAP_VIEW,$o->context()); webservicehelper::require_capabilities(self::CAP_VIEW, $o->context());
$crit = \completion_criteria::fetch(["id" => $criteriaid]); $crit = \completion_criteria::fetch(["id" => $criteriaid]);
if(!$o->course_linked($courseid)){ if (!$o->course_linked($courseid)) {
throw new \webservice_access_exception("Course {$courseid} linked to criteria {$criteriaid} is not linked to studyplan {$o->id()}"); throw new \webservice_access_exception("Course {$courseid} linked to criteria {$criteriaid} is not linked to studyplan {$o->id()}");
} }
$scanner = new completionscanner($crit,\get_course($courseid)); $scanner = new completionscanner($crit, \get_course($courseid));
return $scanner->model(); return $scanner->model();
} }
public static function scan_badge_progress_parameters() public static function scan_badge_progress_parameters()
{ {
return new \external_function_parameters( [ return new \external_function_parameters( [
"badgeid" => new \external_value(PARAM_INT, 'Badge to scan progress for',VALUE_DEFAULT), "badgeid" => new \external_value(PARAM_INT, 'Badge to scan progress for', VALUE_DEFAULT),
"studyplanid" => new \external_value(PARAM_INT, 'Study plan id to limit progress search to (to determine which students to scan)',VALUE_DEFAULT), "studyplanid" => new \external_value(PARAM_INT, 'Study plan id to limit progress search to (to determine which students to scan)', VALUE_DEFAULT),
]); ]);
} }
public static function scan_badge_progress_returns() public static function scan_badge_progress_returns()
{ {
return new \external_single_structure([ return new \external_single_structure([
"total" => new \external_value(PARAM_INT, 'Total number of students scanned'), "total" => new \external_value(PARAM_INT, 'Total number of students scanned'),
@ -368,26 +384,26 @@ class courseservice extends \external_api
]); ]);
} }
public static function scan_badge_progress($badgeid,$studyplanid) public static function scan_badge_progress($badgeid, $studyplanid)
{ {
global $DB; global $DB;
// Check access to the study plan // Check access to the study plan.
$o = studyplan::findById($studyplanid); $o = studyplan::findById($studyplanid);
webservicehelper::require_capabilities(self::CAP_VIEW,$o->context()); webservicehelper::require_capabilities(self::CAP_VIEW, $o->context());
// Validate that badge is linked to studyplan // Validate that badge is linked to studyplan.
if(!$o->badge_linked($badgeid)){ if (!$o->badge_linked($badgeid)) {
throw new \webservice_access_exception("Badge {$badgeid} is not linked to studyplan {$o->id()}"); throw new \webservice_access_exception("Badge {$badgeid} is not linked to studyplan {$o->id()}");
} }
// Get badge info // Get badge info.
$badge = new \core_badges\badge($badgeid); $badge = new \core_badges\badge($badgeid);
$badgeinfo = new badgeinfo($badge); $badgeinfo = new badgeinfo($badge);
// get the connected users // get the connected users.
$students = associationservice::all_associated($studyplanid); $students = associationservice::all_associated($studyplanid);
// Just get the user ids // Just get the user ids.
$studentids = array_map(function ($a){ return $a["id"];},$students); $studentids = array_map(function ($a) { return $a["id"];}, $students);
return [ return [
"total" => count($studentids), "total" => count($studentids),

View File

@ -1,4 +1,24 @@
<?php <?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/>.
/**
*
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace local_treestudyplan; namespace local_treestudyplan;
require_once($CFG->libdir.'/externallib.php'); require_once($CFG->libdir.'/externallib.php');
@ -16,7 +36,7 @@ class gradeinfo {
private $studyitem = null; private $studyitem = null;
private $id; private $id;
private $gradeitem; private $gradeitem;
private $icon; private $icon;
private $link; private $link;
private $gradinglink; private $gradinglink;
@ -36,29 +56,29 @@ class gradeinfo {
private static $sections = []; private static $sections = [];
protected static function getSectionSequence($sectionid){ protected static function getSectionSequence($sectionid) {
global $DB; global $DB;
if(!array_key_exists($sectionid,self::$sections)){ if (!array_key_exists($sectionid, self::$sections)) {
self::$sections[$sectionid] = explode(",",$DB->get_field("course_sections","sequence",["id"=>$sectionid])); self::$sections[$sectionid] = explode(", ", $DB->get_field("course_sections", "sequence", ["id"=>$sectionid]));
} }
return self::$sections[$sectionid]; return self::$sections[$sectionid];
} }
public function getGradeitem(){ public function getGradeitem() {
return $this->gradeitem; return $this->gradeitem;
} }
public function getGradingscanner(){ public function getGradingscanner() {
return $this->gradingscanner; return $this->gradingscanner;
} }
public function getScale(){ public function getScale() {
return $this->scale; return $this->scale;
} }
protected static function get_contentitems() { protected static function get_contentitems() {
global $PAGE; global $PAGE;
if(empty(static::$contentitems)){ if (empty(static::$contentitems)) {
$PAGE->set_context(\context_system::instance()); $PAGE->set_context(\context_system::instance());
static::$contentitems = (new content_item_readonly_repository())->find_all(); static::$contentitems = (new content_item_readonly_repository())->find_all();
} }
@ -67,60 +87,58 @@ class gradeinfo {
public static function get_contentitem($name) { public static function get_contentitem($name) {
$contentitems = static::get_contentitems(); $contentitems = static::get_contentitems();
for($i = 0; $i < count($contentitems); $i++){ for($i = 0; $i < count($contentitems); $i++) {
if($contentitems[$i]->get_name() == $name){ if ($contentitems[$i]->get_name() == $name) {
return $contentitems[$i]; return $contentitems[$i];
} }
} }
return null; return null;
} }
public static function getCourseContextById($id){ public static function getCourseContextById($id) {
$gi = grade_item::fetch(["id" => $id]); $gi = grade_item::fetch(["id" => $id]);
if(!$gi || course_module_instance_pending_deletion($gi->courseid, $gi->itemmodule, $gi->iteminstance)) if (!$gi || course_module_instance_pending_deletion($gi->courseid, $gi->itemmodule, $gi->iteminstance)) {
{ throw new \InvalidArgumentException ("Grade {$id} not found in database". print_r($gi, true));
throw new \InvalidArgumentException ("Grade {$id} not found in database". print_r($gi,true));
} }
return \context_course::instance($gi->courseid);; return \context_course::instance($gi->courseid);;
} }
public function __construct($id,studyitem $studyitem = null){ public function __construct($id, studyitem $studyitem = null) {
global $DB; global $DB;
$this->studyitem = $studyitem; $this->studyitem = $studyitem;
$gi = grade_item::fetch(["id" => $id]); $gi = grade_item::fetch(["id" => $id]);
if(!$gi || course_module_instance_pending_deletion($gi->courseid, $gi->itemmodule, $gi->iteminstance)) if (!$gi || course_module_instance_pending_deletion($gi->courseid, $gi->itemmodule, $gi->iteminstance)) {
{ throw new \InvalidArgumentException ("Grade {$id} not found in database". print_r($gi, true));
throw new \InvalidArgumentException ("Grade {$id} not found in database". print_r($gi,true));
} }
$this->id = $id; $this->id = $id;
$this->gradeitem = $gi; $this->gradeitem = $gi;
// Determine the icon for the associated activity // Determine the icon for the associated activity.
$contentitem = static::get_contentitem($gi->itemmodule); $contentitem = static::get_contentitem($gi->itemmodule);
$this->icon = empty($contentitem)?"":$contentitem->get_icon(); $this->icon = empty($contentitem)?"":$contentitem->get_icon();
// Determine a link to the associated activity // Determine a link to the associated activity.
if($gi->itemtype != "mod" || empty($gi->itemmodule) || empty($gi->iteminstance)){ if ($gi->itemtype != "mod" || empty($gi->itemmodule) || empty($gi->iteminstance)) {
$this->link = ""; $this->link = "";
$this->cmid = 0; $this->cmid = 0;
$this->section = 0; $this->section = 0;
$this->sectionorder = 0; $this->sectionorder = 0;
} }
else { else {
list($c,$cminfo) = get_course_and_cm_from_instance($gi->iteminstance,$gi->itemmodule); list($c, $cminfo) = get_course_and_cm_from_instance($gi->iteminstance, $gi->itemmodule);
$this->cmid = $cminfo->id; $this->cmid = $cminfo->id;
// sort by position in course // sort by position in course.
// // .
$this->section = $cminfo->sectionnum; $this->section = $cminfo->sectionnum;
$ssequence = self::getSectionSequence($cminfo->section); $ssequence = self::getSectionSequence($cminfo->section);
$this->sectionorder = array_search($cminfo->id,$ssequence); $this->sectionorder = array_search($cminfo->id, $ssequence);
$this->link = "/mod/{$gi->itemmodule}/view.php?id={$cminfo->id}"; $this->link = "/mod/{$gi->itemmodule}/view.php?id={$cminfo->id}";
if($gi->itemmodule == 'quiz'){ if ($gi->itemmodule == 'quiz') {
$this->gradinglink = "/mod/{$gi->itemmodule}/report.php?id={$cminfo->id}&mode=grading"; $this->gradinglink = "/mod/{$gi->itemmodule}/report.php?id={$cminfo->id}&mode=grading";
} }
else if($gi->itemmodule == "assign") { else if ($gi->itemmodule == "assign") {
$this->gradinglink = $this->link ."&action=grading"; $this->gradinglink = $this->link ."&action=grading";
} }
else { else {
@ -141,31 +159,31 @@ class gradeinfo {
} }
public function is_selected(){ public function is_selected() {
global $DB; global $DB;
if($this->studyitem){ if ($this->studyitem) {
// Check if selected for this studyitem // Check if selected for this studyitem.
$r = $DB->get_record('local_treestudyplan_gradeinc',['studyitem_id' => $this->studyitem->id(), 'grade_item_id'=> $this->gradeitem->id]); $r = $DB->get_record('local_treestudyplan_gradeinc', ['studyitem_id' => $this->studyitem->id(), 'grade_item_id'=> $this->gradeitem->id]);
if($r && $r->include) { if ($r && $r->include) {
return(true); return(true);
} }
} }
return(false); return(false);
} }
public function is_required(){ public function is_required() {
global $DB; global $DB;
if($this->studyitem){ if ($this->studyitem) {
// Check if selected for this studyitem // Check if selected for this studyitem.
$r = $DB->get_record('local_treestudyplan_gradeinc',['studyitem_id' => $this->studyitem->id(), 'grade_item_id'=> $this->gradeitem->id]); $r = $DB->get_record('local_treestudyplan_gradeinc', ['studyitem_id' => $this->studyitem->id(), 'grade_item_id'=> $this->gradeitem->id]);
if($r && $r->include && $r->required) { if ($r && $r->include && $r->required) {
return(true); return(true);
} }
} }
return(false); return(false);
} }
public static function editor_structure($value=VALUE_REQUIRED){ public static function editor_structure($value=VALUE_REQUIRED) {
return new \external_single_structure([ return new \external_single_structure([
"id" => new \external_value(PARAM_INT, 'grade_item id'), "id" => new \external_value(PARAM_INT, 'grade_item id'),
"cmid" => new \external_value(PARAM_INT, 'course module id'), "cmid" => new \external_value(PARAM_INT, 'course module id'),
@ -178,7 +196,7 @@ class gradeinfo {
"gradinglink" => new \external_value(PARAM_TEXT, 'link to related activity'), "gradinglink" => new \external_value(PARAM_TEXT, 'link to related activity'),
"grading" => gradingscanner::structure(), "grading" => gradingscanner::structure(),
"required" => new \external_value(PARAM_BOOL, 'is required for current studyitem'), "required" => new \external_value(PARAM_BOOL, 'is required for current studyitem'),
], 'referenced course information',$value); ], 'referenced course information', $value);
} }
public function editor_model(studyitem $studyitem = null) { public function editor_model(studyitem $studyitem = null) {
@ -195,15 +213,15 @@ class gradeinfo {
"required" => $this->is_required(), "required" => $this->is_required(),
]; ];
// Unfortunately, lazy loading of the completion data is off, since we need the data to show study item completion... // Unfortunately, lazy loading of the completion data is off, since we need the data to show study item completion...
if($studyitem !== null && $this->is_selected() && has_capability('local/treestudyplan:viewuserreports',$studyitem->studyline()->studyplan()->context()) if ($studyitem !== null && $this->is_selected() && has_capability('local/treestudyplan:viewuserreports', $studyitem->studyline()->studyplan()->context())
&& $this->gradingscanner->is_available()){ && $this->gradingscanner->is_available()) {
$model['grading'] = $this->gradingscanner->model(); $model['grading'] = $this->gradingscanner->model();
} }
return $model; return $model;
} }
public static function user_structure($value=VALUE_REQUIRED){ public static function user_structure($value=VALUE_REQUIRED) {
return new \external_single_structure([ return new \external_single_structure([
"id" => new \external_value(PARAM_INT, 'grade_item id'), "id" => new \external_value(PARAM_INT, 'grade_item id'),
"cmid" => new \external_value(PARAM_INT, 'course module id'), "cmid" => new \external_value(PARAM_INT, 'course module id'),
@ -215,39 +233,39 @@ class gradeinfo {
"completion" => new \external_value(PARAM_TEXT, 'completion state (incomplete|progress|completed|excellent)'), "completion" => new \external_value(PARAM_TEXT, 'completion state (incomplete|progress|completed|excellent)'),
"icon" => new \external_value(PARAM_RAW, 'html for icon of related activity'), "icon" => new \external_value(PARAM_RAW, 'html for icon of related activity'),
"link" => new \external_value(PARAM_TEXT, 'link to related activity'), "link" => new \external_value(PARAM_TEXT, 'link to related activity'),
"pendingsubmission" => new \external_value(PARAM_BOOL, 'is selected for current studyitem',VALUE_OPTIONAL), "pendingsubmission" => new \external_value(PARAM_BOOL, 'is selected for current studyitem', VALUE_OPTIONAL),
"required" => new \external_value(PARAM_BOOL, 'is required for current studyitem'), "required" => new \external_value(PARAM_BOOL, 'is required for current studyitem'),
"selected" => new \external_value(PARAM_BOOL, 'is selected for current studyitem'), "selected" => new \external_value(PARAM_BOOL, 'is selected for current studyitem'),
], 'referenced course information',$value); ], 'referenced course information', $value);
} }
public function user_model($userid) { public function user_model($userid) {
global $DB; global $DB;
$grade = $this->gradeitem->get_final($userid); $grade = $this->gradeitem->get_final($userid);
// convert scale grades to corresponding scale name // convert scale grades to corresponding scale name.
if(!empty($grade)){ if (!empty($grade)) {
if(!is_numeric($grade->finalgrade) && empty($grade->finalgrade)){ if (!is_numeric($grade->finalgrade) && empty($grade->finalgrade)) {
$finalgrade = "-"; $finalgrade = "-";
} }
else if(isset($this->scale)){ else if (isset($this->scale)) {
$finalgrade = $this->scale->get_nearest_item($grade->finalgrade); $finalgrade = $this->scale->get_nearest_item($grade->finalgrade);
} }
else else
{ {
$finalgrade = round($grade->finalgrade,1); $finalgrade = round($grade->finalgrade, 1);
} }
} }
else else
{ {
$finalgrade = "-"; $finalgrade = "-";
} }
// retrieve the aggregator and determine completion // retrieve the aggregator and determine completion.
if(!isset($this->studyitem)){ if (!isset($this->studyitem)) {
throw new \UnexpectedValueException("Study item not set (null) for gradeinfo in report mode"); throw new \UnexpectedValueException("Study item not set (null) for gradeinfo in report mode");
} }
$aggregator = $this->studyitem->studyline()->studyplan()->aggregator(); $aggregator = $this->studyitem->studyline()->studyplan()->aggregator();
$completion = $aggregator->grade_completion($this,$userid); $completion = $aggregator->grade_completion($this, $userid);
$model = [ $model = [
"id" => $this->id, "id" => $this->id,
@ -268,7 +286,7 @@ class gradeinfo {
return $model; return $model;
} }
public function export_model(){ public function export_model() {
return [ return [
"name" => $this->name, "name" => $this->name,
"type" => $this->gradeitem->itemmodule, "type" => $this->gradeitem->itemmodule,
@ -276,113 +294,108 @@ class gradeinfo {
"required" => $this->is_required(), "required" => $this->is_required(),
]; ];
} }
public static function import(studyitem $item,array $model){ public static function import(studyitem $item, array $model) {
if($item->type() == studyitem::COURSE){ if ($item->type() == studyitem::COURSE) {
$course_id = $item->courseid(); $course_id = $item->courseid();
$gradeitems= grade_item::fetch_all(['itemtype' => 'mod', 'courseid' => $course_id]); $gradeitems= grade_item::fetch_all(['itemtype' => 'mod', 'courseid' => $course_id]);
foreach($gradeitems as $gi){ foreach ($gradeitems as $gi) {
$gi_name = empty($outcome)?$gi->itemname:$outcome->name; $gi_name = empty($outcome)?$gi->itemname:$outcome->name;
$gi_type = $gi->itemmodule; $gi_type = $gi->itemmodule;
if($gi_name == $model["name"] && $gi_type == $model["type"]){ if ($gi_name == $model["name"] && $gi_type == $model["type"]) {
// we have a match // we have a match.
if(!isset($model["selected"])){ $model["selected"] = true;} if (!isset($model["selected"])) { $model["selected"] = true;}
if(!isset($model["required"])){ $model["required"] = false;} if (!isset($model["required"])) { $model["required"] = false;}
if($model["selected"] || $model["required"]){ if ($model["selected"] || $model["required"]) {
static::include_grade($gi->id,$item->id(),$model["selected"], $model["required"]); static::include_grade($gi->id, $item->id(), $model["selected"], $model["required"]);
} }
} }
} }
} }
} }
public static function list_course_gradables($course,studyitem $studyitem=null) { public static function list_course_gradables($course, studyitem $studyitem=null) {
$list = []; $list = [];
if(method_exists("\course_modinfo","get_array_of_activities")){ if (method_exists("\course_modinfo", "get_array_of_activities")) {
$activities = \course_modinfo::get_array_of_activities($course); $activities = \course_modinfo::get_array_of_activities($course);
} else { } else {
// Deprecated in Moodle 4.0+, but not yet available in Moodle 3.11 // Deprecated in Moodle 4.0+, but not yet available in Moodle 3.11.
$activities = get_array_of_activities($course->id); $activities = get_array_of_activities($course->id);
} }
foreach($activities as $act) foreach ($activities as $act) {
{ if ($act->visible) {
if($act->visible)
{
$gradeitems= grade_item::fetch_all(['itemtype' => 'mod', 'itemmodule' => $act->mod, 'iteminstance' => $act->id, 'courseid' => $course->id]); $gradeitems= grade_item::fetch_all(['itemtype' => 'mod', 'itemmodule' => $act->mod, 'iteminstance' => $act->id, 'courseid' => $course->id]);
if(!empty($gradeitems)) if (!empty($gradeitems)) {
{ foreach ($gradeitems as $gi) {
foreach($gradeitems as $gi){ if (($gi->gradetype == GRADE_TYPE_VALUE || $gi->gradetype == GRADE_TYPE_SCALE)) {
if(($gi->gradetype == GRADE_TYPE_VALUE || $gi->gradetype == GRADE_TYPE_SCALE))
{
try { try {
$gradable = new static($gi->id,$studyitem); $gradable = new static($gi->id, $studyitem);
$list[] = $gradable; $list[] = $gradable;
} }
catch(\InvalidArgumentException $x){} catch(\InvalidArgumentException $x) {}
} }
} }
} }
} }
} }
usort($list, function($a,$b){ usort($list, function($a, $b) {
$course = $a->coursesort <=> $b->coursesort; $course = $a->coursesort <=> $b->coursesort;
return ($course != 0)?$course:$a->gradeitem->sortorder <=> $b->gradeitem->sortorder; return ($course != 0)?$course:$a->gradeitem->sortorder <=> $b->gradeitem->sortorder;
}); });
return $list; return $list;
} }
public static function list_studyitem_gradables(studyitem $studyitem) public static function list_studyitem_gradables(studyitem $studyitem) {
{
global $DB; global $DB;
$table = 'local_treestudyplan_gradeinc'; $table = 'local_treestudyplan_gradeinc';
$list = []; $list = [];
$records = $DB->get_records($table,['studyitem_id' => $studyitem->id()]); $records = $DB->get_records($table, ['studyitem_id' => $studyitem->id()]);
foreach($records as $r){ foreach ($records as $r) {
if(isset($r->grade_item_id)){ if (isset($r->grade_item_id)) {
try { try {
if($r->include || $r->required){ if ($r->include || $r->required) {
$list[] = new static($r->grade_item_id,$studyitem); $list[] = new static($r->grade_item_id, $studyitem);
} }
} }
catch(\InvalidArgumentException $x){ catch(\InvalidArgumentException $x) {
// on InvalidArgumentException, the grade_item id can no longer be found // on InvalidArgumentException, the grade_item id can no longer be found.
// Remove the link to avoid database record hogging // Remove the link to avoid database record hogging.
$DB->delete_records($table, ['id' => $r->id]); $DB->delete_records($table, ['id' => $r->id]);
} }
} }
} }
usort($list, function($a,$b){ usort($list, function($a, $b) {
$course = $a->coursesort <=> $b->coursesort; $course = $a->coursesort <=> $b->coursesort;
return ($course != 0)?$course:$a->gradeitem->sortorder <=> $b->gradeitem->sortorder; return ($course != 0)?$course:$a->gradeitem->sortorder <=> $b->gradeitem->sortorder;
}); });
return $list; return $list;
} }
public static function include_grade(int $grade_id,int $item_id,bool $include,bool $required=false) { public static function include_grade(int $grade_id, int $item_id, bool $include, bool $required=false) {
global $DB; global $DB;
$table = 'local_treestudyplan_gradeinc'; $table = 'local_treestudyplan_gradeinc';
if($include){ if ($include) {
// make sure a record exits // make sure a record exits.
$r = $DB->get_record($table,['studyitem_id' => $item_id, 'grade_item_id' => $grade_id]); $r = $DB->get_record($table, ['studyitem_id' => $item_id, 'grade_item_id' => $grade_id]);
if($r){ if ($r) {
$r->include = 1; $r->include = 1;
$r->required = boolval($required)?1:0; $r->required = boolval($required)?1:0;
$id = $DB->update_record($table, $r); $id = $DB->update_record($table, $r);
} else { } else {
$DB->insert_record($table, [ $DB->insert_record($table, [
'studyitem_id' => $item_id, 'studyitem_id' => $item_id,
'grade_item_id' => $grade_id, 'grade_item_id' => $grade_id,
'include' => 1, 'include' => 1,
'required' =>boolval($required)?1:0] 'required' =>boolval($required)?1:0]
); );
} }
} else { } else {
// remove if it should not be included // remove if it should not be included.
$r = $DB->get_record($table,['studyitem_id' => $item_id, 'grade_item_id' => $grade_id]); $r = $DB->get_record($table, ['studyitem_id' => $item_id, 'grade_item_id' => $grade_id]);
if($r){ if ($r) {
$DB->delete_records($table, ['id' => $r->id]); $DB->delete_records($table, ['id' => $r->id]);
} }
} }

View File

@ -1,14 +1,34 @@
<?php <?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/>.
/**
*
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace local_treestudyplan; namespace local_treestudyplan;
require_once($CFG->libdir.'/externallib.php'); require_once($CFG->libdir.'/externallib.php');
use \grade_item; use \grade_item;
// $gi->courseid, // $gi->courseid, .
// $gi->itemmodule, // $gi->itemmodule, .
// $gi->iteminstance // $gi->iteminstance.
class gradingscanner class gradingscanner
{ {
private static $mod_supported = []; private static $mod_supported = [];
private static $course_students = []; private static $course_students = [];
@ -16,19 +36,19 @@ class gradingscanner
private $gi = null; private $gi = null;
private $pending_cache = []; private $pending_cache = [];
public static function supported($mod){ public static function supported($mod) {
if(!array_key_exists($mod,self::$mod_supported)){ if (!array_key_exists($mod, self::$mod_supported)) {
self::$mod_supported[$mod] = class_exists("\local_treestudyplan\\local\\ungradedscanners\\{$mod}_scanner"); self::$mod_supported[$mod] = class_exists("\local_treestudyplan\\local\\ungradedscanners\\{$mod}_scanner");
} }
return self::$mod_supported[$mod]; return self::$mod_supported[$mod];
} }
public static function get_course_students($courseid){ public static function get_course_students($courseid) {
global $CFG; global $CFG;
if(!array_key_exists($courseid,self::$course_students)){ if (!array_key_exists($courseid, self::$course_students)) {
$students = []; $students = [];
$context = \context_course::instance($courseid); $context = \context_course::instance($courseid);
foreach (explode(',', $CFG->gradebookroles) as $roleid) { foreach (explode(', ', $CFG->gradebookroles) as $roleid) {
$roleid = trim($roleid); $roleid = trim($roleid);
$students = array_keys(get_role_users($roleid, $context, false, 'u.id', 'u.id ASC')); $students = array_keys(get_role_users($roleid, $context, false, 'u.id', 'u.id ASC'));
} }
@ -37,61 +57,61 @@ class gradingscanner
return self::$course_students[$courseid]; return self::$course_students[$courseid];
} }
public function __construct(grade_item $gi){ public function __construct(grade_item $gi) {
$this->courseid = $gi->courseid; $this->courseid = $gi->courseid;
$this->gi = $gi; $this->gi = $gi;
if(self::supported($gi->itemmodule)) { if (self::supported($gi->itemmodule)) {
$scannerclass = "\local_treestudyplan\\local\ungradedscanners\\{$gi->itemmodule}_scanner"; $scannerclass = "\local_treestudyplan\\local\ungradedscanners\\{$gi->itemmodule}_scanner";
$this->scanner = new $scannerclass($gi); $this->scanner = new $scannerclass($gi);
} }
} }
public function is_available(){ public function is_available() {
return $this->scanner !== null; return $this->scanner !== null;
} }
public function pending($userid){ public function pending($userid) {
if(!array_key_exists($userid, $this->pending_cache)){ if (!array_key_exists($userid, $this->pending_cache)) {
if($this->scanner === null) { if ($this->scanner === null) {
$this->pending_cache[$userid] = false; $this->pending_cache[$userid] = false;
} }
else { else {
$this->pending_cache[$userid] = $this->scanner->has_ungraded_submission($userid);; $this->pending_cache[$userid] = $this->scanner->has_ungraded_submission($userid);;
} }
} }
return $this->pending_cache[$userid]; return $this->pending_cache[$userid];
} }
public static function structure($value=VALUE_OPTIONAL){ public static function structure($value=VALUE_OPTIONAL) {
return new \external_single_structure([ return new \external_single_structure([
"ungraded" => new \external_value(PARAM_INT, 'number of ungraded submissions'), "ungraded" => new \external_value(PARAM_INT, 'number of ungraded submissions'),
"completed" => new \external_value(PARAM_INT, 'number of completed students'), "completed" => new \external_value(PARAM_INT, 'number of completed students'),
"completed_pass" => new \external_value(PARAM_INT, 'number of completed-pass students'), "completed_pass" => new \external_value(PARAM_INT, 'number of completed-pass students'),
"completed_fail" => new \external_value(PARAM_INT, 'number of completed-fail students'), "completed_fail" => new \external_value(PARAM_INT, 'number of completed-fail students'),
"students" => new \external_value(PARAM_INT, 'number of students that should submit'), "students" => new \external_value(PARAM_INT, 'number of students that should submit'),
],"details about gradable submissions",$value); ], "details about gradable submissions", $value);
} }
public function model(){ public function model() {
// Upda // Upda.
$students = self::get_course_students($this->courseid); $students = self::get_course_students($this->courseid);
$completed = 0; $completed = 0;
$ungraded = 0; $ungraded = 0;
$completed_pass = 0; $completed_pass = 0;
$completed_fail = 0; $completed_fail = 0;
foreach($students as $userid){ foreach ($students as $userid) {
if($this->pending($userid)){ if ($this->pending($userid)) {
// First check if the completion needs grading // First check if the completion needs grading.
$ungraded++; $ungraded++;
} else { } else {
$grade = $this->gi->get_final($userid); $grade = $this->gi->get_final($userid);
if(!is_numeric($grade->finalgrade) && empty($grade->finalgrade)){ if (!is_numeric($grade->finalgrade) && empty($grade->finalgrade)) {
//skip //skip.
} }
else else
{ {
//compare grade to minimum grade //compare grade to minimum grade.
if($this->grade_passed($grade)){ if ($this->grade_passed($grade)) {
$completed_pass++; $completed_pass++;
} else { } else {
$completed_fail++; $completed_fail++;
@ -110,43 +130,40 @@ class gradingscanner
} }
// Function copied from bistate aggregator to avoid reference mazes // Function copied from bistate aggregator to avoid reference mazes.
private function grade_passed($grade){ private function grade_passed($grade) {
global $DB; global $DB;
$table = "local_treestudyplan_gradecfg"; $table = "local_treestudyplan_gradecfg";
// first determine if we have a grade_config for this scale or this maximum grade // first determine if we have a grade_config for this scale or this maximum grade.
$finalgrade = $grade->finalgrade; $finalgrade = $grade->finalgrade;
$scale = $this->gi->load_scale(); $scale = $this->gi->load_scale();
if( isset($scale)){ if ( isset($scale)) {
$gradecfg = $DB->get_record($table,["scale_id"=>$scale->id]); $gradecfg = $DB->get_record($table, ["scale_id"=>$scale->id]);
} }
else if($this->gi->grademin == 0) else if ($this->gi->grademin == 0) {
{ $gradecfg = $DB->get_record($table, ["grade_points"=>$this->gi->grademax]);
$gradecfg = $DB->get_record($table,["grade_points"=>$this->gi->grademax]);
} }
else else
{ {
$gradecfg = null; $gradecfg = null;
} }
// for point grades, a provided grade pass overrides the defaults in the gradeconfig // for point grades, a provided grade pass overrides the defaults in the gradeconfig.
// for scales, the configuration in the gradeconfig is leading // for scales, the configuration in the gradeconfig is leading.
if($gradecfg && (isset($scale) || $this->gi->gradepass == 0)) if ($gradecfg && (isset($scale) || $this->gi->gradepass == 0)) {
{ // if so, we need to know if the grade is .
// if so, we need to know if the grade is if ($finalgrade >= $gradecfg->min_completed) {
if($finalgrade >= $gradecfg->min_completed){
return true; return true;
} }
else { else {
return false; return false;
} }
} }
else if($this->gi->gradepass > 0) else if ($this->gi->gradepass > 0) {
{
$range = floatval($this->gi->grademax - $this->gi->grademin); $range = floatval($this->gi->grademax - $this->gi->grademin);
// if no gradeconfig and gradepass is set, use that one to determine config. // if no gradeconfig and gradepass is set, use that one to determine config.
if($finalgrade >= $this->gi->gradepass){ if ($finalgrade >= $this->gi->gradepass) {
return true; return true;
} }
else { else {
@ -154,14 +171,14 @@ class gradingscanner
} }
} }
else { else {
// Blind assumptions if nothing is provided // Blind assumptions if nothing is provided.
// over 55% of range is completed // over 55% of range is completed.
// if range >= 3 and failed is enabled, assume that this means failed // if range >= 3 and failed is enabled, assume that this means failed.
$g = floatval($finalgrade - $this->gi->grademin); $g = floatval($finalgrade - $this->gi->grademin);
$range = floatval($this->gi->grademax - $this->gi->grademin); $range = floatval($this->gi->grademax - $this->gi->grademin);
$score = $g / $range; $score = $g / $range;
if($score > 0.55){ if ($score > 0.55) {
return true; return true;
} }
else { else {

View File

@ -1,4 +1,25 @@
<?php <?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/>.
/**
*
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace local_treestudyplan\local\aggregators; namespace local_treestudyplan\local\aggregators;
@ -12,57 +33,55 @@ class bistate_aggregator extends \local_treestudyplan\aggregator {
public const DEPRECATED = false; public const DEPRECATED = false;
private const DEFAULT_CONDITION = "50"; private const DEFAULT_CONDITION = "50";
private $thresh_excellent = 1.0; // Minimum fraction that must be completed to aggregate as excellent (usually 1.0) private $thresh_excellent = 1.0; // Minimum fraction that must be completed to aggregate as excellent (usually 1.0).
private $thresh_good = 0.8; // Minimum fraction that must be completed to aggregate as good private $thresh_good = 0.8; // Minimum fraction that must be completed to aggregate as good.
private $thresh_completed = 0.66; // Minimum fraction that must be completed to aggregate as completed private $thresh_completed = 0.66; // Minimum fraction that must be completed to aggregate as completed.
private $use_failed = True; // Support failed completion yes/no private $use_failed = True; // Support failed completion yes/no.
private $thresh_progress = 0.33; // Minimum fraction that must be failed to aggregate as failed instead of progress private $thresh_progress = 0.33; // Minimum fraction that must be failed to aggregate as failed instead of progress.
private $accept_pending_as_submitted = False; // Also count ungraded but submitted private $accept_pending_as_submitted = False; // Also count ungraded but submitted .
public function __construct($configstr) { public function __construct($configstr) {
// allow public constructor for testing purposes // allow public constructor for testing purposes.
$this->initialize($configstr); $this->initialize($configstr);
} }
protected function initialize($configstr) { protected function initialize($configstr) {
// First initialize with the defaults // First initialize with the defaults.
foreach(["thresh_excellent", "thresh_good", "thresh_completed","thresh_progress",] as $key){ foreach (["thresh_excellent", "thresh_good", "thresh_completed", "thresh_progress", ] as $key) {
$val = intval(get_config('local_treestudyplan', "bistate_{$key}")); $val = intval(get_config('local_treestudyplan', "bistate_{$key}"));
if($val >= 0 && $val <= 100) if ($val >= 0 && $val <= 100) {
{
$this->$key = floatval($val)/100; $this->$key = floatval($val)/100;
} }
} }
foreach(["use_failed", "accept_pending_as_submitted"] as $key){ foreach (["use_failed", "accept_pending_as_submitted"] as $key) {
$this->$key = boolval(get_config('local_treestudyplan', "bistate_{$key}")); $this->$key = boolval(get_config('local_treestudyplan', "bistate_{$key}"));
} }
// Next, decode json // Next, decode json.
$config = \json_decode($configstr,true); $config = \json_decode($configstr, true);
if(is_array($config)){ if (is_array($config)) {
// copy all valid config settings to this item // copy all valid config settings to this item.
foreach(["thresh_excellent", "thresh_good", "thresh_completed","thresh_progress",] as $key){ foreach (["thresh_excellent", "thresh_good", "thresh_completed", "thresh_progress", ] as $key) {
if(array_key_exists($key,$config)){ if (array_key_exists($key, $config)) {
$val = $config[$key]; $val = $config[$key];
if($val >= 0 && $val <= 100) if ($val >= 0 && $val <= 100) {
{
$this->$key = floatval($val)/100; $this->$key = floatval($val)/100;
} }
} }
} }
foreach(["use_failed", "accept_pending_as_submitted"] as $key){ foreach (["use_failed", "accept_pending_as_submitted"] as $key) {
if(array_key_exists($key,$config)){ if (array_key_exists($key, $config)) {
$this->$key = boolval($config[$key]); $this->$key = boolval($config[$key]);
} }
} }
} else { } else {
} }
} }
// Return active configuration model // Return active configuration model.
public function config_string() { public function config_string() {
return json_encode([ return json_encode([
"thresh_excellent" => 100*$this->thresh_excellent, "thresh_excellent" => 100*$this->thresh_excellent,
@ -74,23 +93,23 @@ class bistate_aggregator extends \local_treestudyplan\aggregator {
]); ]);
} }
public function needSelectGradables(){ return True;} public function needSelectGradables() { return True;}
public function isDeprecated() { return self::DEPRECATED;} public function isDeprecated() { return self::DEPRECATED;}
public function useRequiredGrades() { return True;} public function useRequiredGrades() { return True;}
public function useItemConditions() { return False;} public function useItemConditions() { return False;}
public function aggregate_binary_goals(array $completions, array $required = []){ public function aggregate_binary_goals(array $completions, array $required = []) {
// function is public to allow access for the testing code // function is public to allow access for the testing code.
// return te following conditions // return te following conditions.
// Possible states: // Possible states:.
// - completion::EXCELLENT - At least $thresh_excellent fraction of goals are complete and all required goals are met // - completion::EXCELLENT - At least $thresh_excellent fraction of goals are complete and all required goals are met.
// - completion::GOOD - At least $thresh_good fraction of goals are complete and all required goals are met // - completion::GOOD - At least $thresh_good fraction of goals are complete and all required goals are met.
// - completion::COMPLETED - At least $thresh_complete fraction of goals are completed and all required goals are met // - completion::COMPLETED - At least $thresh_complete fraction of goals are completed and all required goals are met.
// - completion::FAILED - At least $thresh_progress fraction of goals is not failed // - completion::FAILED - At least $thresh_progress fraction of goals is not failed.
// - completion::INCOMPLETE - No goals have been started // - completion::INCOMPLETE - No goals have been started.
// - completion::PROGRESS - All other states // - completion::PROGRESS - All other states.
$total = count($completions); $total = count($completions);
@ -104,7 +123,7 @@ class bistate_aggregator extends \local_treestudyplan\aggregator {
$MIN_PROGRESS = ($this->accept_pending_as_submitted)?completion::PENDING:completion::PROGRESS; $MIN_PROGRESS = ($this->accept_pending_as_submitted)?completion::PENDING:completion::PROGRESS;
foreach($completions as $index => $c) { foreach ($completions as $index => $c) {
$completed += ($c >= completion::COMPLETED)?1:0; $completed += ($c >= completion::COMPLETED)?1:0;
$progress += ($c >= $MIN_PROGRESS)?1:0; $progress += ($c >= $MIN_PROGRESS)?1:0;
@ -118,19 +137,19 @@ class bistate_aggregator extends \local_treestudyplan\aggregator {
$fraction_failed = ($total >0)?(floatval($failed)/floatval($total)):0.0; $fraction_failed = ($total >0)?(floatval($failed)/floatval($total)):0.0;
$fraction_started = ($total >0)?(floatval($started)/floatval($total)):0.0; $fraction_started = ($total >0)?(floatval($started)/floatval($total)):0.0;
if($total == 0){ if ($total == 0) {
return completion::INCOMPLETE; return completion::INCOMPLETE;
} }
if($fraction_completed >= $this->thresh_excellent && $allrequiredmet){ if ($fraction_completed >= $this->thresh_excellent && $allrequiredmet) {
return completion::EXCELLENT; return completion::EXCELLENT;
} }
else if($fraction_completed >= $this->thresh_good && $allrequiredmet){ else if ($fraction_completed >= $this->thresh_good && $allrequiredmet) {
return completion::GOOD; return completion::GOOD;
} }
else if($fraction_completed >= $this->thresh_completed && $allrequiredmet){ else if ($fraction_completed >= $this->thresh_completed && $allrequiredmet) {
return completion::COMPLETED; return completion::COMPLETED;
} }
else if($started == 0){ else if ($started == 0) {
return completion::INCOMPLETE; return completion::INCOMPLETE;
} }
else { else {
@ -138,25 +157,25 @@ class bistate_aggregator extends \local_treestudyplan\aggregator {
} }
} }
public function aggregate_course(courseinfo $courseinfo, studyitem $studyitem, $userid){ public function aggregate_course(courseinfo $courseinfo, studyitem $studyitem, $userid) {
$course = $courseinfo->course(); $course = $courseinfo->course();
$coursefinished = ($course->enddate)?($course->enddate < time()):false; $coursefinished = ($course->enddate)?($course->enddate < time()):false;
// Note: studyitem condition config is not used in this aggregator. // Note: studyitem condition config is not used in this aggregator.
// loop through all associated gradables and count the totals, completed, etc.. // loop through all associated gradables and count the totals, completed, etc..
$completions = []; $completions = [];
$required = []; $required = [];
foreach(gradeinfo::list_studyitem_gradables($studyitem) as $gi){ foreach (gradeinfo::list_studyitem_gradables($studyitem) as $gi) {
$completions[] = $this->grade_completion($gi,$userid); $completions[] = $this->grade_completion($gi, $userid);
if($gi->is_required()){ if ($gi->is_required()) {
// if it's a required grade // if it's a required grade .
// also add it's index in the completion list to the list of required grades // also add it's index in the completion list to the list of required grades .
$required[] = count($completions) - 1; $required[] = count($completions) - 1;
} }
} }
// Combine the aquired completions into one // Combine the aquired completions into one.
$result = self::aggregate_binary_goals($completions,$required); $result = self::aggregate_binary_goals($completions, $required);
if($this->use_failed && $result == completion::PROGRESS && $coursefinished){ if ($this->use_failed && $result == completion::PROGRESS && $coursefinished) {
return completion::FAILED; return completion::FAILED;
} else { } else {
return $result; return $result;
@ -164,35 +183,35 @@ class bistate_aggregator extends \local_treestudyplan\aggregator {
} }
public function aggregate_junction(array $completion, studyitem $studyitem = null, $userid = 0){ public function aggregate_junction(array $completion, studyitem $studyitem = null, $userid = 0) {
// Aggregate multiple incoming states into one junction or finish. // Aggregate multiple incoming states into one junction or finish.
// Possible states: // Possible states:.
// - completion::EXCELLENT - All incoming states are excellent // - completion::EXCELLENT - All incoming states are excellent.
// - completion::GOOD - All incoming states are at least good // - completion::GOOD - All incoming states are at least good.
// - completion::COMPLETED - All incoming states are at least completed // - completion::COMPLETED - All incoming states are at least completed.
// - completion::FAILED - All incoming states are failed // - completion::FAILED - All incoming states are failed.
// - completion::INCOMPLETE - All incoming states are incomplete // - completion::INCOMPLETE - All incoming states are incomplete.
// - completion::PROGRESS - All other states // - completion::PROGRESS - All other states.
// First count all states // First count all states.
$statecount = completion::count_states($completion); $statecount = completion::count_states($completion);
$total = count($completion); $total = count($completion);
if( $total == $statecount[completion::EXCELLENT]){ if ( $total == $statecount[completion::EXCELLENT]) {
return completion::EXCELLENT; return completion::EXCELLENT;
} }
else if ( $total == $statecount[completion::EXCELLENT] + $statecount[completion::GOOD]){ else if ( $total == $statecount[completion::EXCELLENT] + $statecount[completion::GOOD]) {
return completion::GOOD; return completion::GOOD;
} }
else if ( $total == $statecount[completion::EXCELLENT] + $statecount[completion::GOOD] + $statecount[completion::COMPLETED]){ else if ( $total == $statecount[completion::EXCELLENT] + $statecount[completion::GOOD] + $statecount[completion::COMPLETED]) {
return completion::COMPLETED; return completion::COMPLETED;
} }
else if( $statecount[completion::FAILED]){ else if ( $statecount[completion::FAILED]) {
return completion::FAILED; return completion::FAILED;
} }
else if( $total == $statecount[completion::INCOMPLETE]){ else if ( $total == $statecount[completion::INCOMPLETE]) {
return completion::INCOMPLETE; return completion::INCOMPLETE;
} }
else { else {
return completion::PROGRESS; return completion::PROGRESS;
} }
@ -203,21 +222,20 @@ class bistate_aggregator extends \local_treestudyplan\aggregator {
$table = "local_treestudyplan_gradecfg"; $table = "local_treestudyplan_gradecfg";
$gradeitem = $gradeinfo->getGradeitem(); $gradeitem = $gradeinfo->getGradeitem();
$grade = $gradeitem->get_final($userid); $grade = $gradeitem->get_final($userid);
$course = \get_course($gradeitem->courseid); // Fetch course from cache $course = \get_course($gradeitem->courseid); // Fetch course from cache.
$coursefinished = ($course->enddate)?($course->enddate < time()):false; $coursefinished = ($course->enddate)?($course->enddate < time()):false;
if(empty($grade)){ if (empty($grade)) {
return completion::INCOMPLETE; return completion::INCOMPLETE;
} }
else if($grade->finalgrade === NULL) else if ($grade->finalgrade === NULL) {
{ // on assignments, grade NULL means a submission has not yet been graded,.
// 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.
// but on quizes this can also mean a quiz might have been started // Therefor, we treat a NULL result as a reason to check the relevant gradingscanner for presence of pending items.
// Therefor, we treat a NULL result as a reason to check the relevant gradingscanner for presence of pending items
// Since we want old results to be visible until a pending item was graded, we only use this state here. // Since we want old results to be visible until a pending item was graded, we only use this state here.
// Pending items are otherwise expressly indicated by the "pendingsubmission" field in the user model // Pending items are otherwise expressly indicated by the "pendingsubmission" field in the user model.
if($gradeinfo->getGradingscanner()->pending($userid)){ if ($gradeinfo->getGradingscanner()->pending($userid)) {
return completion::PENDING; return completion::PENDING;
} else { } else {
return completion::INCOMPLETE; return completion::INCOMPLETE;
@ -225,49 +243,44 @@ class bistate_aggregator extends \local_treestudyplan\aggregator {
} }
else { else {
$grade = $gradeitem->get_final($userid); $grade = $gradeitem->get_final($userid);
// first determine if we have a grade_config for this scale or this maximum grade // first determine if we have a grade_config for this scale or this maximum grade.
$finalgrade = $grade->finalgrade; $finalgrade = $grade->finalgrade;
$scale = $gradeinfo->getScale(); $scale = $gradeinfo->getScale();
if( isset($scale)){ if ( isset($scale)) {
$gradecfg = $DB->get_record($table,["scale_id"=>$scale->id]); $gradecfg = $DB->get_record($table, ["scale_id"=>$scale->id]);
} }
else if($gradeitem->grademin == 0) else if ($gradeitem->grademin == 0) {
{ $gradecfg = $DB->get_record($table, ["grade_points"=>$gradeitem->grademax]);
$gradecfg = $DB->get_record($table,["grade_points"=>$gradeitem->grademax]);
} }
else else
{ {
$gradecfg = null; $gradecfg = null;
} }
// for point grades, a provided grade pass overrides the defaults in the gradeconfig // for point grades, a provided grade pass overrides the defaults in the gradeconfig.
// for scales, the configuration in the gradeconfig is leading // for scales, the configuration in the gradeconfig is leading.
if($gradecfg && (isset($scale) || $gradeitem->gradepass == 0)) if ($gradecfg && (isset($scale) || $gradeitem->gradepass == 0)) {
{ // if so, we need to know if the grade is .
// if so, we need to know if the grade is if ($finalgrade >= $gradecfg->min_completed) {
if($finalgrade >= $gradecfg->min_completed){ // return completed if completed.
// return completed if completed
return completion::COMPLETED; return completion::COMPLETED;
} }
else if($this->use_failed && $coursefinished) else if ($this->use_failed && $coursefinished) {
{ // return failed if failed is enabled and the grade is less than the minimum grade for progress.
// return failed if failed is enabled and the grade is less than the minimum grade for progress
return completion::FAILED; return completion::FAILED;
} }
else { else {
return completion::PROGRESS; return completion::PROGRESS;
} }
} }
else if($gradeitem->gradepass > 0) else if ($gradeitem->gradepass > 0) {
{
$range = floatval($gradeitem->grademax - $gradeitem->grademin); $range = floatval($gradeitem->grademax - $gradeitem->grademin);
// if no gradeconfig and gradepass is set, use that one to determine config. // if no gradeconfig and gradepass is set, use that one to determine config.
if($finalgrade >= $gradeitem->gradepass){ if ($finalgrade >= $gradeitem->gradepass) {
return completion::COMPLETED; return completion::COMPLETED;
} }
else if($this->use_failed && $coursefinished) else if ($this->use_failed && $coursefinished) {
{
// return failed if failed is enabled and the grade is 1, while there are at leas 3 states. // return failed if failed is enabled and the grade is 1, while there are at leas 3 states.
return completion::FAILED; return completion::FAILED;
} }
@ -276,18 +289,17 @@ class bistate_aggregator extends \local_treestudyplan\aggregator {
} }
} }
else { else {
// Blind assumptions if nothing is provided // Blind assumptions if nothing is provided.
// over 55% of range is completed // over 55% of range is completed.
// if range >= 3 and failed is enabled, assume that this means failed // if range >= 3 and failed is enabled, assume that this means failed.
$g = floatval($finalgrade - $gradeitem->grademin); $g = floatval($finalgrade - $gradeitem->grademin);
$range = floatval($gradeitem->grademax - $gradeitem->grademin); $range = floatval($gradeitem->grademax - $gradeitem->grademin);
$score = $g / $range; $score = $g / $range;
if($score > 0.55){ if ($score > 0.55) {
return completion::COMPLETED; return completion::COMPLETED;
} }
else if($this->use_failed && $coursefinished) else if ($this->use_failed && $coursefinished) {
{
// return failed if failed is enabled and the grade is 1, while there are at leas 3 states. // return failed if failed is enabled and the grade is 1, while there are at leas 3 states.
return completion::FAILED; return completion::FAILED;
} }

View File

@ -1,4 +1,25 @@
<?php <?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/>.
/**
*
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace local_treestudyplan\local\aggregators; namespace local_treestudyplan\local\aggregators;
@ -11,42 +32,42 @@ use \local_treestudyplan\debug;
class core_aggregator extends \local_treestudyplan\aggregator { class core_aggregator extends \local_treestudyplan\aggregator {
public const DEPRECATED = false; public const DEPRECATED = false;
private $accept_pending_as_submitted = False; // Also count ungraded but submitted private $accept_pending_as_submitted = False; // Also count ungraded but submitted .
public function __construct($configstr) { public function __construct($configstr) {
// allow public constructor for testing purposes // allow public constructor for testing purposes.
$this->initialize($configstr); $this->initialize($configstr);
} }
protected function initialize($configstr) { protected function initialize($configstr) {
// First initialize with the defaults // First initialize with the defaults.
foreach(["accept_pending_as_submitted"] as $key){ foreach (["accept_pending_as_submitted"] as $key) {
$this->$key = boolval(get_config('local_treestudyplan', "bistate_{$key}")); $this->$key = boolval(get_config('local_treestudyplan', "bistate_{$key}"));
} }
// Next, decode json // Next, decode json.
$config = \json_decode($configstr,true); $config = \json_decode($configstr, true);
if(is_array($config)){ if (is_array($config)) {
// copy all valid config settings to this item // copy all valid config settings to this item.
foreach(["accept_pending_as_submitted"] as $key){ foreach (["accept_pending_as_submitted"] as $key) {
if(array_key_exists($key,$config)){ if (array_key_exists($key, $config)) {
$this->$key = boolval($config[$key]); $this->$key = boolval($config[$key]);
} }
} }
} else { } else {
} }
} }
// Return active configuration model // Return active configuration model.
public function config_string() { public function config_string() {
return json_encode([ return json_encode([
"accept_pending_as_submitted" => $this->accept_pending_as_submitted, "accept_pending_as_submitted" => $this->accept_pending_as_submitted,
]); ]);
} }
public function needSelectGradables(){ return False;} public function needSelectGradables() { return False;}
public function isDeprecated() { return self::DEPRECATED;} public function isDeprecated() { return self::DEPRECATED;}
public function useRequiredGrades() { return True;} public function useRequiredGrades() { return True;}
public function useItemConditions() { return False;} public function useItemConditions() { return False;}
@ -68,37 +89,37 @@ class core_aggregator extends \local_treestudyplan\aggregator {
* @return void * @return void
*/ */
public function aggregate_course(courseinfo $courseinfo, studyitem $studyitem, $userid){ public function aggregate_course(courseinfo $courseinfo, studyitem $studyitem, $userid) {
// Retrieve the core completion info from the core // Retrieve the core completion info from the core.
$course = $courseinfo->course(); $course = $courseinfo->course();
$completion = new \completion_info($course); $completion = new \completion_info($course);
if ($completion->is_enabled() && $completion->is_tracked_user($userid)){ if ($completion->is_enabled() && $completion->is_tracked_user($userid)) {
if($completion->is_course_complete($userid)){ if ($completion->is_course_complete($userid)) {
// Now, the trick is to determine what constitutes excellent and good completion.... // Now, the trick is to determine what constitutes excellent and good completion....
// TODO: Determine excellent and maybe good completion // TODO: Determine excellent and maybe good completion.
// Option: Use course end grade to determine that... // Option: Use course end grade to determine that...
// Probably needs a config value in the aggregator.... // Probably needs a config value in the aggregator....
return completion::COMPLETED; return completion::COMPLETED;
} else { } else {
// Check if the course is over or not, if it is over, display failed // Check if the course is over or not, if it is over, display failed.
// Else, return PROGRESS // Else, return PROGRESS.
// Retrieve timing through courseinfo // Retrieve timing through courseinfo .
$timing = courseinfo::coursetiming($course); $timing = courseinfo::coursetiming($course);
// Not met and time is passed, means FAILED // Not met and time is passed, means FAILED.
if($timing == "past"){ if ($timing == "past") {
return completion::FAILED; return completion::FAILED;
} }
else { else {
// Check if any of the requirements are being met? // Check if any of the requirements are being met?.
$completions = $completion->get_completions($userid); $completions = $completion->get_completions($userid);
foreach($completions as $c){ foreach ($completions as $c) {
if($c->is_complete()){ if ($c->is_complete()) {
// If so, return progress // If so, return progress.
return completion::PROGRESS; return completion::PROGRESS;
} }
} }
@ -111,42 +132,42 @@ class core_aggregator extends \local_treestudyplan\aggregator {
} }
} }
public function aggregate_junction(array $completion, studyitem $studyitem = null, $userid = 0){ public function aggregate_junction(array $completion, studyitem $studyitem = null, $userid = 0) {
// Aggregate multiple incoming states into one junction or finish. // Aggregate multiple incoming states into one junction or finish.
// Possible states: // Possible states:.
// - completion::EXCELLENT - All incoming states are excellent // - completion::EXCELLENT - All incoming states are excellent.
// - completion::GOOD - All incoming states are at least good // - completion::GOOD - All incoming states are at least good.
// - completion::COMPLETED - All incoming states are at least completed // - completion::COMPLETED - All incoming states are at least completed.
// - completion::FAILED - All incoming states are failed // - completion::FAILED - All incoming states are failed.
// - completion::INCOMPLETE - All incoming states are incomplete // - completion::INCOMPLETE - All incoming states are incomplete.
// - completion::PROGRESS - All other states // - completion::PROGRESS - All other states.
// First count all states // First count all states.
$statecount = completion::count_states($completion); $statecount = completion::count_states($completion);
$total = count($completion); $total = count($completion);
if( $total == $statecount[completion::EXCELLENT]){ if ( $total == $statecount[completion::EXCELLENT]) {
return completion::EXCELLENT; return completion::EXCELLENT;
} }
else if ( $total == $statecount[completion::EXCELLENT] + $statecount[completion::GOOD]){ else if ( $total == $statecount[completion::EXCELLENT] + $statecount[completion::GOOD]) {
return completion::GOOD; return completion::GOOD;
} }
else if ( $total == $statecount[completion::EXCELLENT] + $statecount[completion::GOOD] + $statecount[completion::COMPLETED]){ else if ( $total == $statecount[completion::EXCELLENT] + $statecount[completion::GOOD] + $statecount[completion::COMPLETED]) {
return completion::COMPLETED; return completion::COMPLETED;
} }
else if( $statecount[completion::FAILED]){ else if ( $statecount[completion::FAILED]) {
return completion::FAILED; return completion::FAILED;
} }
else if( $total == $statecount[completion::INCOMPLETE]){ else if ( $total == $statecount[completion::INCOMPLETE]) {
return completion::INCOMPLETE; return completion::INCOMPLETE;
} }
else { else {
return completion::PROGRESS; return completion::PROGRESS;
} }
} }
// CORE COMPLETION DOESN'T REALLY USE THE FUNCTIONS BELOW // CORE COMPLETION DOESN'T REALLY USE THE FUNCTIONS BELOW.
// AGGREGATORS ARE GOING TO BE DEPRECATED ANYWAY... but used in legacy parts of this plugin. // AGGREGATORS ARE GOING TO BE DEPRECATED ANYWAY... but used in legacy parts of this plugin.
public function grade_completion(gradeinfo $gradeinfo, $userid) { public function grade_completion(gradeinfo $gradeinfo, $userid) {
@ -155,18 +176,17 @@ class core_aggregator extends \local_treestudyplan\aggregator {
$gradeitem = $gradeinfo->getGradeitem(); $gradeitem = $gradeinfo->getGradeitem();
$grade = $gradeitem->get_final($userid); $grade = $gradeitem->get_final($userid);
if(empty($grade)){ if (empty($grade)) {
return completion::INCOMPLETE; return completion::INCOMPLETE;
} }
else if($grade->finalgrade === NULL) else if ($grade->finalgrade === NULL) {
{ // on assignments, grade NULL means a submission has not yet been graded,.
// 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.
// but on quizes this can also mean a quiz might have been started // Therefor, we treat a NULL result as a reason to check the relevant gradingscanner for presence of pending items.
// Therefor, we treat a NULL result as a reason to check the relevant gradingscanner for presence of pending items
// Since we want old results to be visible until a pending item was graded, we only use this state here. // Since we want old results to be visible until a pending item was graded, we only use this state here.
// Pending items are otherwise expressly indicated by the "pendingsubmission" field in the user model // Pending items are otherwise expressly indicated by the "pendingsubmission" field in the user model.
if($gradeinfo->getGradingscanner()->pending($userid)){ if ($gradeinfo->getGradingscanner()->pending($userid)) {
return completion::PENDING; return completion::PENDING;
} else { } else {
return completion::INCOMPLETE; return completion::INCOMPLETE;
@ -174,49 +194,44 @@ class core_aggregator extends \local_treestudyplan\aggregator {
} }
else { else {
$grade = $gradeitem->get_final($userid); $grade = $gradeitem->get_final($userid);
// first determine if we have a grade_config for this scale or this maximum grade // first determine if we have a grade_config for this scale or this maximum grade.
$finalgrade = $grade->finalgrade; $finalgrade = $grade->finalgrade;
$scale = $gradeinfo->getScale(); $scale = $gradeinfo->getScale();
if( isset($scale)){ if ( isset($scale)) {
$gradecfg = $DB->get_record($table,["scale_id"=>$scale->id]); $gradecfg = $DB->get_record($table, ["scale_id"=>$scale->id]);
} }
else if($gradeitem->grademin == 0) else if ($gradeitem->grademin == 0) {
{ $gradecfg = $DB->get_record($table, ["grade_points"=>$gradeitem->grademax]);
$gradecfg = $DB->get_record($table,["grade_points"=>$gradeitem->grademax]);
} }
else else
{ {
$gradecfg = null; $gradecfg = null;
} }
// for point grades, a provided grade pass overrides the defaults in the gradeconfig // for point grades, a provided grade pass overrides the defaults in the gradeconfig.
// for scales, the configuration in the gradeconfig is leading // for scales, the configuration in the gradeconfig is leading.
if($gradecfg && (isset($scale) || $gradeitem->gradepass == 0)) if ($gradecfg && (isset($scale) || $gradeitem->gradepass == 0)) {
{ // if so, we need to know if the grade is .
// if so, we need to know if the grade is if ($finalgrade >= $gradecfg->min_completed) {
if($finalgrade >= $gradecfg->min_completed){ // return completed if completed.
// return completed if completed
return completion::COMPLETED; return completion::COMPLETED;
} }
else if($this->use_failed && $finalgrade < $gradecfg->min_progress) else if ($this->use_failed && $finalgrade < $gradecfg->min_progress) {
{ // return failed if failed is enabled and the grade is less than the minimum grade for progress.
// return failed if failed is enabled and the grade is less than the minimum grade for progress
return completion::FAILED; return completion::FAILED;
} }
else { else {
return completion::PROGRESS; return completion::PROGRESS;
} }
} }
else if($gradeitem->gradepass > 0) else if ($gradeitem->gradepass > 0) {
{
$range = floatval($gradeitem->grademax - $gradeitem->grademin); $range = floatval($gradeitem->grademax - $gradeitem->grademin);
// if no gradeconfig and gradepass is set, use that one to determine config. // if no gradeconfig and gradepass is set, use that one to determine config.
if($finalgrade >= $gradeitem->gradepass){ if ($finalgrade >= $gradeitem->gradepass) {
return completion::COMPLETED; return completion::COMPLETED;
} }
else if($this->use_failed && $gradeitem->gradepass >= 3 && $range >= 3 && $finalgrade == 1) else if ($this->use_failed && $gradeitem->gradepass >= 3 && $range >= 3 && $finalgrade == 1) {
{
// return failed if failed is enabled and the grade is 1, while there are at leas 3 states. // return failed if failed is enabled and the grade is 1, while there are at leas 3 states.
return completion::FAILED; return completion::FAILED;
} }
@ -225,18 +240,17 @@ class core_aggregator extends \local_treestudyplan\aggregator {
} }
} }
else { else {
// Blind assumptions if nothing is provided // Blind assumptions if nothing is provided.
// over 55% of range is completed // over 55% of range is completed.
// if range >= 3 and failed is enabled, assume that this means failed // if range >= 3 and failed is enabled, assume that this means failed.
$g = floatval($finalgrade - $gradeitem->grademin); $g = floatval($finalgrade - $gradeitem->grademin);
$range = floatval($gradeitem->grademax - $gradeitem->grademin); $range = floatval($gradeitem->grademax - $gradeitem->grademin);
$score = $g / $range; $score = $g / $range;
if($score > 0.55){ if ($score > 0.55) {
return completion::COMPLETED; return completion::COMPLETED;
} }
else if($this->use_failed && $range >= 3 && $finalgrade == 1) else if ($this->use_failed && $range >= 3 && $finalgrade == 1) {
{
// return failed if failed is enabled and the grade is 1, while there are at leas 3 states. // return failed if failed is enabled and the grade is 1, while there are at leas 3 states.
return completion::FAILED; return completion::FAILED;
} }

View File

@ -1,4 +1,25 @@
<?php <?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/>.
/**
*
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace local_treestudyplan\local\aggregators; namespace local_treestudyplan\local\aggregators;
@ -11,24 +32,23 @@ class tristate_aggregator extends \local_treestudyplan\aggregator {
public const DEPRECATED = true; public const DEPRECATED = true;
private const DEFAULT_CONDITION = "50"; private const DEFAULT_CONDITION = "50";
public function needSelectGradables(){ return True;} public function needSelectGradables() { return True;}
public function isDeprecated() { return self::DEPRECATED;} public function isDeprecated() { return self::DEPRECATED;}
public function useRequiredGrades() { return False;} public function useRequiredGrades() { return False;}
public function useItemConditions() { return True;} public function useItemConditions() { return True;}
protected function aggregate_completion(array $a, $condition = "50") { protected function aggregate_completion(array $a, $condition = "50") {
if(in_array($condition, ['ALL','67','50','ANY'])){ if (in_array($condition, ['ALL', '67', '50', 'ANY'])) {
// condition is one of the valid conditions // condition is one of the valid conditions.
$c_completed = 0; $c_completed = 0;
$c_excellent = 0; $c_excellent = 0;
$c_progress = 0; $c_progress = 0;
$c_pending = 0; $c_pending = 0;
$count = sizeof($a); $count = sizeof($a);
if($count > 0) if ($count > 0) {
{
foreach($a as $c) { foreach ($a as $c) {
$c_progress += ($c>=completion::PROGRESS)?1:0; $c_progress += ($c>=completion::PROGRESS)?1:0;
$c_completed += ($c>=completion::COMPLETED)?1:0; $c_completed += ($c>=completion::COMPLETED)?1:0;
$c_excellent += ($c>=completion::EXCELLENT)?1:0; $c_excellent += ($c>=completion::EXCELLENT)?1:0;
@ -42,17 +62,17 @@ class tristate_aggregator extends \local_treestudyplan\aggregator {
'ANY' => 1, 'ANY' => 1,
][$condition]; ][$condition];
if($c_excellent >= $required) { if ($c_excellent >= $required) {
return completion::EXCELLENT; return completion::EXCELLENT;
} else if ($c_completed >= $required) { } else if ($c_completed >= $required) {
return completion::COMPLETED; return completion::COMPLETED;
} else { } else {
// Return PROGRESS if one or more completions are COMPLETED or EXCELLENT, but the aggregation margin is not met // 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 // state PROGRESS will not carry on if aggregations are chained.
if($c_progress > 0){ if ($c_progress > 0) {
return completion::PROGRESS; return completion::PROGRESS;
} }
else if($c_pending > 0){ else if ($c_pending > 0) {
return completion::PENDING; return completion::PENDING;
} }
else { else {
@ -64,30 +84,30 @@ class tristate_aggregator extends \local_treestudyplan\aggregator {
return completion::INCOMPLETE; return completion::INCOMPLETE;
} }
} }
else else
{ {
// indeterminable, return null // indeterminable, return null.
return null; return null;
} }
} }
public function aggregate_course(courseinfo $courseinfo, studyitem $studyitem, $userid){ public function aggregate_course(courseinfo $courseinfo, studyitem $studyitem, $userid) {
$condition = $studyitem->conditions(); $condition = $studyitem->conditions();
if(empty($condition)){ if (empty($condition)) {
$condition = self::DEFAULT_CONDITION; $condition = self::DEFAULT_CONDITION;
} }
$list = []; $list = [];
foreach(gradeinfo::list_studyitem_gradables($studyitem) as $gi){ foreach (gradeinfo::list_studyitem_gradables($studyitem) as $gi) {
$list[] = $this->grade_completion($gi,$userid); $list[] = $this->grade_completion($gi, $userid);
} }
$completion = self::aggregate_completion($list,$condition); $completion = self::aggregate_completion($list, $condition);
return $completion; return $completion;
} }
public function aggregate_junction(array $completion, studyitem $studyitem, $userid){ public function aggregate_junction(array $completion, studyitem $studyitem, $userid) {
$completed = self::aggregate_completion($completion,$studyitem->conditions()); $completed = self::aggregate_completion($completion, $studyitem->conditions());
// if null result (conditions are unknown/null) - default to ALL // if null result (conditions are unknown/null) - default to ALL.
return isset($completed)?$completed:(self::aggregate_completion($completion,'ALL')); return isset($completed)?$completed:(self::aggregate_completion($completion, 'ALL'));
} }
public function grade_completion(gradeinfo $gradeinfo, $userid) { public function grade_completion(gradeinfo $gradeinfo, $userid) {
@ -95,36 +115,34 @@ class tristate_aggregator extends \local_treestudyplan\aggregator {
$gradeitem = $gradeinfo->getGradeitem(); $gradeitem = $gradeinfo->getGradeitem();
$grade = $gradeitem->get_final($userid); $grade = $gradeitem->get_final($userid);
if(empty($grade)){ if (empty($grade)) {
return completion::INCOMPLETE; return completion::INCOMPLETE;
} }
else if($grade->finalgrade === NULL) else if ($grade->finalgrade === NULL) {
{ // on assignments, grade NULL means a submission has not yet been graded,.
// 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.
// but on quizes this can also mean a quiz might have been started // Therefor, we treat a NULL result as a reason to check the relevant gradingscanner for presence of pending items.
// Therefor, we treat a NULL result as a reason to check the relevant gradingscanner for presence of pending items
// Since we want old results to be visible until a pending item was graded, we only use this state here. // Since we want old results to be visible until a pending item was graded, we only use this state here.
// Pending items are otherwise expressly indicated by the "pendingsubmission" field in the user model // Pending items are otherwise expressly indicated by the "pendingsubmission" field in the user model.
if($gradeinfo->getGradingscanner()->pending($userid)){ if ($gradeinfo->getGradingscanner()->pending($userid)) {
return completion::PENDING; return completion::PENDING;
} else { } else {
return completion::INCOMPLETE; return completion::INCOMPLETE;
} }
} }
else { else {
$finalgrade = $grade->finalgrade; $finalgrade = $grade->finalgrade;
$scale = $gradeinfo->getScale(); $scale = $gradeinfo->getScale();
if($gradeitem->gradepass > 0) if ($gradeitem->gradepass > 0) {
{ // Base completion off of gradepass (if set).
// Base completion off of gradepass (if set) if ($gradeitem->grademax > $gradeitem->gradepass && $finalgrade >= $gradeitem->grademax) {
if($gradeitem->grademax > $gradeitem->gradepass && $finalgrade >= $gradeitem->grademax){ // If gradepass is configured .
// If gradepass is configured
return completion::EXCELLENT; return completion::EXCELLENT;
} }
else if($finalgrade >= $gradeitem->gradepass){ else if ($finalgrade >= $gradeitem->gradepass) {
return completion::COMPLETED; return completion::COMPLETED;
} }
else { else {
@ -132,17 +150,17 @@ class tristate_aggregator extends \local_treestudyplan\aggregator {
} }
} }
else { else {
// Blind assumptions: // Blind assumptions:.
// over 55% of range is completed // over 55% of range is completed.
// over 85% of range is excellent // over 85% of range is excellent.
$g = floatval($finalgrade - $gradeitem->grademin); $g = floatval($finalgrade - $gradeitem->grademin);
$range = floatval($gradeitem->grademax - $gradeitem->grademin); $range = floatval($gradeitem->grademax - $gradeitem->grademin);
$score = $g / $range; $score = $g / $range;
if($score > 0.85){ if ($score > 0.85) {
return completion::EXCELLENT; return completion::EXCELLENT;
} }
else if($score > 0.55){ else if ($score > 0.55) {
return completion::COMPLETED; return completion::COMPLETED;
} }
else { else {

View File

@ -1,4 +1,26 @@
<?php <?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/>.
/**
*
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace local_treestudyplan\local; namespace local_treestudyplan\local;
use Exception; use Exception;
@ -61,180 +83,176 @@ class gradegenerator {
"Praesent nec risus vestibulum quam venenatis tempor.", "Praesent nec risus vestibulum quam venenatis tempor.",
"Nullam rhoncus ex a quam egestas, eu auctor enim lobortis.", "Nullam rhoncus ex a quam egestas, eu auctor enim lobortis.",
"Nam luctus ante id lacus scelerisque, quis blandit ante elementum.", "Nam luctus ante id lacus scelerisque, quis blandit ante elementum.",
]; ];
private function generatedfeedback(){ private function generatedfeedback() {
if(file_exists("/usr/games/fortune")){ if (file_exists("/usr/games/fortune")) {
// get a fortune if it is available // get a fortune if it is available.
return shell_exec("/usr/games/fortune -n 160 -e disclaimer literature science pratchett wisdom education"); return shell_exec("/usr/games/fortune -n 160 -e disclaimer literature science pratchett wisdom education");
} else { } else {
// get a random loremipsum string // get a random loremipsum string.
return self::$loremipsum[rand(0,count(self::$loremipsum)-1)]; return self::$loremipsum[rand(0, count(self::$loremipsum)-1)];
} }
} }
public function __construct(){ public function __construct() {
} }
public function addstudent(string $student){ public function addstudent(string $student) {
if(!array_key_exists($student,$this->table)){ if (!array_key_exists($student, $this->table)) {
$this->table[$student] = [ $this->table[$student] = [
"intelligence" => rand(70,100), "intelligence" => rand(70, 100),
"endurance" => rand(70,100), "endurance" => rand(70, 100),
"skills" => [], "skills" => [],
]; ];
} }
} }
public function addskill(string $student, string $skill){ public function addskill(string $student, string $skill) {
$this->addstudent($student); $this->addstudent($student);
if(!array_key_exists($skill,$this->table[$student]["skills"])){ if (!array_key_exists($skill, $this->table[$student]["skills"])) {
$int = $this->table[$student]["intelligence"]; $int = $this->table[$student]["intelligence"];
$end = $this->table[$student]["endurance"]; $end = $this->table[$student]["endurance"];
$this->table[$student]["skills"][$skill] = [ $this->table[$student]["skills"][$skill] = [
"intelligence" => min(100, $int + rand(-30,30)), "intelligence" => min(100, $int + rand(-30, 30)),
"endurance" => min(100, $end + rand(-10,10)), "endurance" => min(100, $end + rand(-10, 10)),
]; ];
} }
} }
// Below is mostly just for reference in the json file // Below is mostly just for reference in the json file.
public function addUserNameInfo(string $student, $firstname,$lastname){ public function addUserNameInfo(string $student, $firstname, $lastname) {
$this->addstudent($student); $this->addstudent($student);
$this->table[$student]["firstname"] = $firstname; $this->table[$student]["firstname"] = $firstname;
$this->table[$student]["lastname"] = $lastname; $this->table[$student]["lastname"] = $lastname;
} }
public function generateraw($student,$skill, $count ) public function generateraw($student, $skill, $count ) {
{ $this->addskill($student, $skill);
$this->addskill($student,$skill);
$int = $this->table[$student]["skills"][$skill]["intelligence"]; $int = $this->table[$student]["skills"][$skill]["intelligence"];
$end = $this->table[$student]["skills"][$skill]["endurance"]; $end = $this->table[$student]["skills"][$skill]["endurance"];
$results = []; $results = [];
$gaveup = false; $gaveup = false;
for($i=0; $i < $count; $i++){ for($i=0; $i < $count; $i++) {
$r = new \stdClass; $r = new \stdClass;
if($gaveup) { if ($gaveup) {
$r->done = !$gaveup; $r->done = !$gaveup;
} else { } else {
$r->done = (rand(0, $end) > 20); // Determine if the assignment was done $r->done = (rand(0, $end) > 20); // Determine if the assignment was done.
} }
if($r->done){ if ($r->done) {
$score = rand(0,$int) ; $score = rand(0, $int) ;
$r->result = ($score > 20); // determine if the assignment was successful $r->result = ($score > 20); // determine if the assignment was successful.
if(!$r->result){ if (!$r->result) {
$r->failed = !($score > 10); $r->failed = !($score > 10);
} }
} else { } else {
$r->result = false; // make sure a result property is always there $r->result = false; // make sure a result property is always there.
$r->failed = true; $r->failed = true;
} }
// Aways generate a little feedback // Aways generate a little feedback.
$r->fb = $this->generatedfeedback(); $r->fb = $this->generatedfeedback();
$results[] = $r; $results[] = $r;
if(!$gaveup && $i >= 3) { if (!$gaveup && $i >= 3) {
// There is a slight chance the students with low endurance for this course will stop with this course's work entirely // There is a slight chance the students with low endurance for this course will stop with this course's work entirely.
$gaveup = (rand(0,$end) < 15); $gaveup = (rand(0, $end) < 15);
} }
} }
return $results; return $results;
} }
public function generate($student,$skill, array $gradeinfos ){ public function generate($student, $skill, array $gradeinfos ) {
global $DB; global $DB;
$table ="local_treestudyplan_gradecfg"; $table ="local_treestudyplan_gradecfg";
$rlist = []; $rlist = [];
$gen = $this->generateraw($student,$skill, count($gradeinfos)); $gen = $this->generateraw($student, $skill, count($gradeinfos));
for($i=0; $i < count($gradeinfos); $i++){ for($i=0; $i < count($gradeinfos); $i++) {
$g = $gradeinfos[$i]; $g = $gradeinfos[$i];
$gi = $g->getGradeitem(); $gi = $g->getGradeitem();
$gr = $gen[$i]; $gr = $gen[$i];
// First get the configured interpretation for this scale or grade // First get the configured interpretation for this scale or grade.
$scale = $gi->load_scale(); $scale = $gi->load_scale();
if( isset($scale)){ if ( isset($scale)) {
$gradecfg = $DB->get_record($table,["scale_id"=>$scale->id]); $gradecfg = $DB->get_record($table, ["scale_id"=>$scale->id]);
} }
else if($gi->grademin == 0) else if ($gi->grademin == 0) {
{ $gradecfg = $DB->get_record($table, ["grade_points"=>$gi->grademax]);
$gradecfg = $DB->get_record($table,["grade_points"=>$gi->grademax]);
} }
else else
{ {
$gradecfg = null; $gradecfg = null;
} }
// next generate the grade // next generate the grade.
if($gradecfg) if ($gradecfg) {
{ if (!$gr->done) {
if(!$gr->done){ // INCOMPLETE.
// INCOMPLETE // fair chance of teacher forgetting to set incomplete to "no evidence".
// fair chance of teacher forgetting to set incomplete to "no evidence" $grade = 0;// $grade = (rand(0, 100) > 15)?max(1, $gradecfg->min_progress-1):"0";.
$grade = 0;// $grade = (rand(0,100) > 15)?max(1, $gradecfg->min_progress-1):"0";
$r = (object)["gi" => $g, "grade" => $grade, "fb" =>"" ]; $r = (object)["gi" => $g, "grade" => $grade, "fb" =>"" ];
} }
else if(!$gr->result){ else if (!$gr->result) {
$grade = rand($gradecfg->min_progress, $gradecfg->min_completed -1 ); $grade = rand($gradecfg->min_progress, $gradecfg->min_completed -1 );
$r = (object)["gi" => $g, "grade" => $grade, "fb" =>$gr->fb ]; $r = (object)["gi" => $g, "grade" => $grade, "fb" =>$gr->fb ];
} }
else{ else{
// COMPLETED // COMPLETED.
$r = (object)["gi" => $g, "grade" => rand( $gradecfg->min_completed, $gi->grademax ), "fb" =>$gr->fb ]; $r = (object)["gi" => $g, "grade" => rand( $gradecfg->min_completed, $gi->grademax ), "fb" =>$gr->fb ];
} }
$r->gradetext = $r->grade; $r->gradetext = $r->grade;
if( isset($scale)){ if ( isset($scale)) {
$scaleitems = $scale->load_items(); $scaleitems = $scale->load_items();
if($r->grade > 0){ if ($r->grade > 0) {
$r->gradetext = trim($scale->get_nearest_item($r->grade)); $r->gradetext = trim($scale->get_nearest_item($r->grade));
} else { } else {
$r->gradetext = "-"; $r->gradetext = "-";
} }
} }
} }
else if($gi->gradepass > 0) else if ($gi->gradepass > 0) {
{ if (!$gr->done) {
if(!$gr->done){ // INCOMPLETe or FAILED.
// INCOMPLETe or FAILED
$grade = rand(0, $gi->gradepass/2); $grade = rand(0, $gi->gradepass/2);
$r = (object)["gi" => $g, "grade" => $grade, "fb" =>($grade > 0)?$gr->fb:"" ]; $r = (object)["gi" => $g, "grade" => $grade, "fb" =>($grade > 0)?$gr->fb:"" ];
} }
else if(!$gr->result){ else if (!$gr->result) {
//PROGRESS //PROGRESS.
$r = (object)["gi" => $g, "grade" => rand( round($gi->gradepass/2), $gi->gradepass -1 ), "fb" =>$gr->fb ]; $r = (object)["gi" => $g, "grade" => rand( round($gi->gradepass/2), $gi->gradepass -1 ), "fb" =>$gr->fb ];
} }
else{ else{
// COMPLETED // COMPLETED.
$r = (object)["gi" => $g, "grade" => rand( $gi->gradepass, $gi->grademax ), "fb" =>$gr->fb ]; $r = (object)["gi" => $g, "grade" => rand( $gi->gradepass, $gi->grademax ), "fb" =>$gr->fb ];
} }
$r->gradetext = $r->grade; $r->gradetext = $r->grade;
} }
else { else {
// Blind assumptions if nothing is provided // Blind assumptions if nothing is provided.
// over 55% of range is completed // over 55% of range is completed.
// under 35% is not done // under 35% is not done.
$range = floatval($gi->grademax - $gi->grademin); $range = floatval($gi->grademax - $gi->grademin);
if(!$gr->done){ if (!$gr->done) {
// INCOMPLETe or FAILED // INCOMPLETe or FAILED.
$grade = rand(0, round($range * 0.35) - 1); $grade = rand(0, round($range * 0.35) - 1);
$r = (object)["gi" => $g, "grade" => $gi->grademin+$grade, "fb" =>($grade > 0)?$gr->fb:"" ]; $r = (object)["gi" => $g, "grade" => $gi->grademin+$grade, "fb" =>($grade > 0)?$gr->fb:"" ];
} }
else if(!$gr->result){ else if (!$gr->result) {
//PROGRESS //PROGRESS.
$r = (object)["gi" => $g, "grade" => $gi->grademin+rand(round($range * 0.35),round($range * 0.55) - 1 ), "fb" =>$gr->fb ]; $r = (object)["gi" => $g, "grade" => $gi->grademin+rand(round($range * 0.35), round($range * 0.55) - 1 ), "fb" =>$gr->fb ];
} }
else{ else{
// COMPLETED // COMPLETED.
$r = (object)["gi" => $g, "grade" => $gi->grademin+rand(round($range * 0.55) , $range ), "fb" =>$gr->fb ]; $r = (object)["gi" => $g, "grade" => $gi->grademin+rand(round($range * 0.55) , $range ), "fb" =>$gr->fb ];
} }
@ -242,50 +260,49 @@ class gradegenerator {
} }
$rlist[] = $r; $rlist[] = $r;
} }
return $rlist; return $rlist;
} }
public function getstats($student){ public function getstats($student) {
return $this->table[$student]; return $this->table[$student];
} }
public function serialize(): ?string{ public function serialize(): ?string{
return json_encode([ return json_encode([
"table" => $this->table],JSON_PRETTY_PRINT); "table" => $this->table], JSON_PRETTY_PRINT);
} }
public function unserialize(string $data): void { public function unserialize(string $data): void {
$o = json_decode($data,true); $o = json_decode($data, true);
$this->table = $o["table"]; $this->table = $o["table"];
} }
public function toFile(string $filename){ public function toFile(string $filename) {
$filename = self::expand_tilde($filename); $filename = self::expand_tilde($filename);
file_put_contents($filename,$this->serialize()); file_put_contents($filename, $this->serialize());
} }
public function fromFile(string $filename){ public function fromFile(string $filename) {
$filename = self::expand_tilde($filename); $filename = self::expand_tilde($filename);
if(file_exists($filename)){ if (file_exists($filename)) {
try{ try{
$json = file_get_contents($filename); $json = file_get_contents($filename);
$this->unserialize($json); $this->unserialize($json);
} catch(Exception $x){ } catch(Exception $x) {
cli_problem("ERROR loading from file"); cli_problem("ERROR loading from file");
throw $x; // Throw X up again to show the output throw $x; // Throw X up again to show the output.
} }
} }
} }
private static function expand_tilde($path) private static function expand_tilde($path) {
{
if (function_exists('posix_getuid') && strpos($path, '~') !== false) { if (function_exists('posix_getuid') && strpos($path, '~') !== false) {
$info = posix_getpwuid(posix_getuid()); $info = posix_getpwuid(posix_getuid());
$path = str_replace('~', $info['dir'], $path); $path = str_replace('~', $info['dir'], $path);
} }
return $path; return $path;
} }

View File

@ -1,49 +1,67 @@
<?php <?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/>.
/**
*
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace local_treestudyplan\local\helpers; namespace local_treestudyplan\local\helpers;
use DateTime; use DateTime;
class debugger { class debugger {
private $fname; private $fname;
private $tag; private $tag;
private $enabled = false; private $enabled = false;
public function __construct($filename,$tag) public function __construct($filename, $tag) {
{
global $CFG; global $CFG;
$this->fname = $filename; $this->fname = $filename;
$this->tag = $tag; $this->tag = $tag;
// assume debug environment if cachejs is false // assume debug environment if cachejs is false.
$this->enabled = (isset($CFG->cachejs) && $CFG->cachejs == false); $this->enabled = (isset($CFG->cachejs) && $CFG->cachejs == false);
} }
public function dump($object,string $tag="") public function dump($object, string $tag="") {
{ if (strlen($tag) > 0) {
if(strlen($tag) > 0){
$tag .= ":\n"; $tag .= ":\n";
} }
$this->writeblock($tag.print_r($object,true)); $this->writeblock($tag.print_r($object, true));
} }
public function write($str) public function write($str) {
{
$this->writeblock($str); $this->writeblock($str);
} }
private function writeblock($str){ private function writeblock($str) {
if($this->enabled){ if ($this->enabled) {
$now = new DateTime(); $now = new DateTime();
$tagline = "[ {$this->tag} - ".$now->format("c")." ]"; $tagline = "[ {$this->tag} - ".$now->format("c")." ]";
$fp = fopen($this->fname,"a"); $fp = fopen($this->fname, "a");
fwrite($fp,$tagline . ":\n".$str."\n"); fwrite($fp, $tagline . ":\n".$str."\n");
fclose($fp); fclose($fp);
} }
} }

View File

@ -1,4 +1,25 @@
<?php <?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/>.
/**
*
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace local_treestudyplan\local\helpers; namespace local_treestudyplan\local\helpers;
require_once($CFG->dirroot.'/webservice/lib.php'); require_once($CFG->dirroot.'/webservice/lib.php');
@ -10,28 +31,28 @@ class webservicehelper {
private static $validated_contexts = []; private static $validated_contexts = [];
/** /**
* Test for capability in the given context for the current user and throw a \webservice_access_exception if not * Test for capability in the given context for the current user and throw a \webservice_access_exception if not
* Note: The context is not validate * Note: The context is not validate
* @param array|string $capability One or more capabilities to be tested OR wise (if one capability is given, the function passes) * @param array|string $capability One or more capabilities to be tested OR wise (if one capability is given, the function passes)
* @param \context $context The context in which to check for the capability. * @param \context $context The context in which to check for the capability.
* @throws \webservice_access_exception If none of the capabilities provided are given to the current user * @throws \webservice_access_exception If none of the capabilities provided are given to the current user
* @return boolean * @return boolean
*/ */
public static function has_capabilities($capability,$context){ public static function has_capabilities($capability, $context) {
if($context == null){ if ($context == null) {
$context = \context_system::instance(); $context = \context_system::instance();
} }
if(is_array($capability)){ if (is_array($capability)) {
//TODO: replace this by accesslib function \has_any_capability() //TODO: replace this by accesslib function \has_any_capability().
foreach($capability as $cap){ foreach ($capability as $cap) {
if(has_capability($cap,$context)){ if (has_capability($cap, $context)) {
return true; return true;
} }
} }
} }
elseif(has_capability($capability,$context)){ else if (has_capability($capability, $context)) {
return true; return true;
} }
} }
@ -40,34 +61,34 @@ class webservicehelper {
* Test if the current user has a certain capability in any of the categories they have access to * Test if the current user has a certain capability in any of the categories they have access to
* @param string $capability The capability to scan for in the categories * @param string $capability The capability to scan for in the categories
* @param \core_course_category $parent The parent category to use as a scanning base. Used in recursing Leave empty if calling this function * @param \core_course_category $parent The parent category to use as a scanning base. Used in recursing Leave empty if calling this function
* @return boolean * @return boolean
*/ */
public static function has_capability_in_any_category($capability,\core_course_category $parent=null){ public static function has_capability_in_any_category($capability, \core_course_category $parent=null) {
// List the categories in which the user has a specific capability // List the categories in which the user has a specific capability.
$list = []; $list = [];
// initialize parent if needed // initialize parent if needed.
if($parent == null){ if ($parent == null) {
$parent = \core_course_category::user_top(); $parent = \core_course_category::user_top();
if(has_capability($capability,$parent->get_context())){ if (has_capability($capability, $parent->get_context())) {
$list[] = $parent; $list[] = $parent;
} }
} }
$children = $parent->get_children(); $children = $parent->get_children();
// Since the change for a category permission is greatest at the lower levels, // Since the change for a category permission is greatest at the lower levels,.
// we scan in two stages, to focus the search more on the lower levels instead of diving deep into the first category // we scan in two stages, to focus the search more on the lower levels instead of diving deep into the first category.
// Stage one (surface check): check all children for the capability // Stage one (surface check): check all children for the capability.
foreach($children as $child){ foreach ($children as $child) {
// Check if we should add this category // Check if we should add this category.
if(has_capability($capability,$child->get_context())){ if (has_capability($capability, $child->get_context())) {
return true; return true;
} }
} }
// Stage two (deep dive): recurse into the child categories // Stage two (deep dive): recurse into the child categories.
foreach($children as $child){ foreach ($children as $child) {
if($child->get_children_count() > 0){ if ($child->get_children_count() > 0) {
if(self::has_capability_in_any_category($capability,$child)){ if (self::has_capability_in_any_category($capability, $child)) {
return true; return true;
} }
} }
@ -76,17 +97,17 @@ class webservicehelper {
} }
/** /**
* Test for capability in the given context for the current user and throw a \webservice_access_exception if not * Test for capability in the given context for the current user and throw a \webservice_access_exception if not
* @param array|string $capability One or more capabilities to be tested OR wise (if one capability is given, the function passes) * @param array|string $capability One or more capabilities to be tested OR wise (if one capability is given, the function passes)
* @param \context $context The context in which to check for the capability. Leave empty to use the system context. * @param \context $context The context in which to check for the capability. Leave empty to use the system context.
* @param bool $validate Validate the context before checking capabilities * @param bool $validate Validate the context before checking capabilities
* @throws \webservice_access_exception If none of the capabilities provided are given to the current user * @throws \webservice_access_exception If none of the capabilities provided are given to the current user
*/ */
public static function require_capabilities($capability,$context=null,$validate=true){ public static function require_capabilities($capability, $context=null, $validate=true) {
if($validate) { if ($validate) {
\external_api::validate_context($context); \external_api::validate_context($context);
} }
if(! static::has_capabilities($capability,$context)){ if (! static::has_capabilities($capability, $context)) {
throw new \webservice_access_exception("The capability {$capability} is required on this context ({$context->get_context_name()})"); throw new \webservice_access_exception("The capability {$capability} is required on this context ({$context->get_context_name()})");
} }
} }
@ -98,32 +119,32 @@ class webservicehelper {
* @throws \InvalidArgumentException When the context is not found * @throws \InvalidArgumentException When the context is not found
*/ */
public static function find_context($contextid): \context{ public static function find_context($contextid): \context{
if(isset($contextid) && is_int($contextid) && $contextid > 0){ if (isset($contextid) && is_int($contextid) && $contextid > 0) {
if(!in_array($contextid,self::$validated_contexts)){ // Cache the context and make sure it is only validated once... if (!in_array($contextid, self::$validated_contexts)) { // Cache the context and make sure it is only validated once...
try{ try{
$context = \context::instance_by_id($contextid); $context = \context::instance_by_id($contextid);
} }
catch(\dml_missing_record_exception $x){ catch(\dml_missing_record_exception $x) {
throw new \InvalidArgumentException("Context {$contextid} not available"); // Just throw it up again. catch is included here to make sure we know it throws this exception throw new \InvalidArgumentException("Context {$contextid} not available"); // Just throw it up again. catch is included here to make sure we know it throws this exception.
} }
// Validate the found context // Validate the found context.
\external_api::validate_context($context); \external_api::validate_context($context);
self::$validated_contexts[$contextid] = $context; self::$validated_contexts[$contextid] = $context;
} }
return self::$validated_contexts[$contextid]; return self::$validated_contexts[$contextid];
} }
else{ else{
return static::system_context(); // This function ensures the system context is validated just once this call return static::system_context(); // This function ensures the system context is validated just once this call.
} }
} }
/** /**
* Return the validated system context (validation happens only once for this call) * Return the validated system context (validation happens only once for this call)
* @return \context_system The system context, validated to use as this context * @return \context_system The system context, validated to use as this context
*/ */
public static function system_context(): \context_system { public static function system_context(): \context_system {
if(!isset(static::$systemcontext)){ if (!isset(static::$systemcontext)) {
static::$systemcontext = \context_system::instance(); static::$systemcontext = \context_system::instance();
\external_api::validate_context(static::$systemcontext); \external_api::validate_context(static::$systemcontext);
} }

View File

@ -1,66 +1,86 @@
<?php <?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/>.
/**
*
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace local_treestudyplan\local\ungradedscanners; namespace local_treestudyplan\local\ungradedscanners;
class assign_scanner extends scanner_base { class assign_scanner extends scanner_base {
protected function get_ungraded_submissions(){ protected function get_ungraded_submissions() {
global $DB; global $DB;
//SELECT asgn_sub.id as submissionid, a.id as instanceid, asgn_sub.userid as userid, asgn_sub.timemodified as timesubmitted, asgn_sub.attemptnumber , a.maxattempts //SELECT asgn_sub.id as submissionid, a.id as instanceid, asgn_sub.userid as userid, asgn_sub.timemodified as timesubmitted, asgn_sub.attemptnumber , a.maxattempts.
$sql = "SELECT DISTINCT asgn_sub.userid $sql = "SELECT DISTINCT asgn_sub.userid
FROM {assign_submission} asgn_sub FROM {assign_submission} asgn_sub
JOIN {assign} a ON a.id = asgn_sub.assignment JOIN {assign} a ON a.id = asgn_sub.assignment
LEFT JOIN {assign_grades} ag ON ag.assignment = asgn_sub.assignment AND ag.userid = asgn_sub.userid AND LEFT JOIN {assign_grades} ag ON ag.assignment = asgn_sub.assignment AND ag.userid = asgn_sub.userid AND
asgn_sub.attemptnumber = ag.attemptnumber asgn_sub.attemptnumber = ag.attemptnumber
WHERE a.id = {$this->gi->iteminstance} WHERE a.id = {$this->gi->iteminstance}
AND asgn_sub.status = 'submitted' AND asgn_sub.status = 'submitted'
AND asgn_sub.userid > 0 AND asgn_sub.userid > 0
AND a.grade <> 0 AND (ag.id IS NULL OR asgn_sub.timemodified >= ag.timemodified) AND a.grade <> 0 AND (ag.id IS NULL OR asgn_sub.timemodified >= ag.timemodified)
"; ";
return $DB->get_fieldset_sql($sql); return $DB->get_fieldset_sql($sql);
} }
protected function get_graded_users(){ protected function get_graded_users() {
global $DB; global $DB;
$sql = "SELECT DISTINCT g.userid $sql = "SELECT DISTINCT g.userid
FROM {grade_grades} g FROM {grade_grades} g
LEFT JOIN {grade_items} gi on g.itemid = gi.id LEFT JOIN {grade_items} gi on g.itemid = gi.id
WHERE gi.itemmodule = 'assign' AND gi.iteminstance = {$this->gi->iteminstance} WHERE gi.itemmodule = 'assign' AND gi.iteminstance = {$this->gi->iteminstance}
AND g.finalgrade IS NOT NULL"; // MAy turn out to be needed, dunno AND g.finalgrade IS NOT NULL"; // MAy turn out to be needed, dunno.
return $DB->get_fieldset_sql($sql); return $DB->get_fieldset_sql($sql);
} }
public function count_ungraded($course_userids=[]){ public function count_ungraded($course_userids=[]) {
$ungraded = $this->get_ungraded_submissions(); $ungraded = $this->get_ungraded_submissions();
if(count($course_userids) > 0){ if (count($course_userids) > 0) {
$ungraded = array_intersect($ungraded,$course_userids); $ungraded = array_intersect($ungraded, $course_userids);
} }
return count($ungraded); return count($ungraded);
} }
public function count_graded($course_userids=[]){ public function count_graded($course_userids=[]) {
$ungraded = $this->get_ungraded_submissions(); $ungraded = $this->get_ungraded_submissions();
$graded = $this->get_graded_users(); $graded = $this->get_graded_users();
if(count($course_userids) > 0){ if (count($course_userids) > 0) {
$ungraded = array_intersect($ungraded,$course_userids); $ungraded = array_intersect($ungraded, $course_userids);
$graded = array_intersect($graded,$course_userids); $graded = array_intersect($graded, $course_userids);
} }
// determine how many id's have a grade, but also an ungraded submission // determine how many id's have a grade, but also an ungraded submission.
$dual = array_intersect($ungraded,$graded); $dual = array_intersect($ungraded, $graded);
// subtract those from the graded count // subtract those from the graded count.
return count($graded) - count($dual); return count($graded) - count($dual);
} }
public function has_ungraded_submission($userid) public function has_ungraded_submission($userid) {
{
$ungraded = $this->get_ungraded_submissions(); $ungraded = $this->get_ungraded_submissions();
return in_array($userid,$ungraded); return in_array($userid, $ungraded);
} }
} }

View File

@ -1,17 +1,38 @@
<?php <?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/>.
/**
*
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace local_treestudyplan\local\ungradedscanners; namespace local_treestudyplan\local\ungradedscanners;
require_once($CFG->dirroot.'/question/engine/states.php'); // for reading question state require_once($CFG->dirroot.'/question/engine/states.php'); // for reading question state.
class quiz_scanner extends scanner_base { class quiz_scanner extends scanner_base {
protected function get_ungraded_submissions(){ protected function get_ungraded_submissions() {
// count all users who have one or more questions that still need grading // count all users who have one or more questions that still need grading.
global $DB; global $DB;
// First find all question attempts that need grading // First find all question attempts that need grading.
$sql = "SELECT qza.id as submissionid, qza.userid as userid, qas.questionattemptid as attempt_id, qas.sequencenumber as sequencenumber $sql = "SELECT qza.id as submissionid, qza.userid as userid, qas.questionattemptid as attempt_id, qas.sequencenumber as sequencenumber
FROM {question_attempt_steps} qas FROM {question_attempt_steps} qas
JOIN {question_attempts} qna ON qas.questionattemptid = qna.id JOIN {question_attempts} qna ON qas.questionattemptid = qna.id
@ -20,48 +41,47 @@ class quiz_scanner extends scanner_base {
$rs = $DB->get_recordset_sql($sql); $rs = $DB->get_recordset_sql($sql);
$submissions = []; $submissions = [];
foreach($rs as $r){ foreach ($rs as $r) {
// Now, check if // Now, check if .
$maxstate_sql = "SELECT MAX(qas.sequencenumber) FROM {question_attempt_steps} qas WHERE qas.questionattemptid = {$r->attempt_id}"; $maxstate_sql = "SELECT MAX(qas.sequencenumber) FROM {question_attempt_steps} qas WHERE qas.questionattemptid = {$r->attempt_id}";
$max = $DB->get_field_sql($maxstate_sql); $max = $DB->get_field_sql($maxstate_sql);
if($r->sequencenumber == $max){ if ($r->sequencenumber == $max) {
$submissions[$r->userid] = true; // set array index based on user id, to avoid checking if value is in array $submissions[$r->userid] = true; // set array index based on user id, to avoid checking if value is in array.
} }
} }
$rs->close(); $rs->close();
return array_keys($submissions); return array_keys($submissions);
} }
public function count_ungraded($course_userids=[]){ public function count_ungraded($course_userids=[]) {
$ungraded = $this->get_ungraded_submissions(); $ungraded = $this->get_ungraded_submissions();
if(count($course_userids) > 0){ if (count($course_userids) > 0) {
$ungraded = array_intersect($ungraded,$course_userids); $ungraded = array_intersect($ungraded, $course_userids);
} }
return count($ungraded); return count($ungraded);
} }
public function count_graded($course_userids=[]){ public function count_graded($course_userids=[]) {
// count all users who submitted one or more finished tests. // count all users who submitted one or more finished tests.
global $DB; global $DB;
$sql = "SELECT DISTINCT g.userid $sql = "SELECT DISTINCT g.userid
FROM {grade_grades} g FROM {grade_grades} g
LEFT JOIN {grade_items} gi on g.itemid = gi.id LEFT JOIN {grade_items} gi on g.itemid = gi.id
WHERE gi.itemmodule = 'quiz' AND gi.iteminstance = {$this->gi->iteminstance} WHERE gi.itemmodule = 'quiz' AND gi.iteminstance = {$this->gi->iteminstance}
AND g.finalgrade IS NOT NULL"; // MAy turn out to be needed, dunno AND g.finalgrade IS NOT NULL"; // MAy turn out to be needed, dunno.
$graded = $DB->get_fieldset_sql($sql); $graded = $DB->get_fieldset_sql($sql);
if(count($course_userids) > 0){ if (count($course_userids) > 0) {
$graded = array_intersect($graded,$course_userids); $graded = array_intersect($graded, $course_userids);
} }
return count($graded); return count($graded);
} }
public function has_ungraded_submission($userid) public function has_ungraded_submission($userid) {
{
$ungraded = $this->get_ungraded_submissions(); $ungraded = $this->get_ungraded_submissions();
return in_array($userid,$ungraded); return in_array($userid, $ungraded);
} }
} }

View File

@ -1,4 +1,25 @@
<?php <?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/>.
/**
*
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace local_treestudyplan\local\ungradedscanners; namespace local_treestudyplan\local\ungradedscanners;
use \grade_item; use \grade_item;
@ -6,7 +27,7 @@ use \grade_item;
abstract class scanner_base { abstract class scanner_base {
protected $gi; protected $gi;
public function __construct(grade_item $gi){ public function __construct(grade_item $gi) {
$this->gi = $gi; $this->gi = $gi;
} }

View File

@ -1,4 +1,25 @@
<?php <?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/>.
/**
*
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace local_treestudyplan; namespace local_treestudyplan;
require_once($CFG->libdir.'/externallib.php'); require_once($CFG->libdir.'/externallib.php');
@ -9,71 +30,71 @@ class period {
private static $PAGECACHE = []; private static $PAGECACHE = [];
private $r; // Holds database record private $r; // Holds database record.
private $id; private $id;
private $page; private $page;
public function aggregator(){ public function aggregator() {
return $this->studyplan->aggregator(); return $this->studyplan->aggregator();
} }
// Cache constructors to avoid multiple creation events in one session. // Cache constructors to avoid multiple creation events in one session.
public static function findById($id): self { public static function findById($id): self {
if(!array_key_exists($id,self::$CACHE)){ if (!array_key_exists($id, self::$CACHE)) {
self::$CACHE[$id] = new self($id); self::$CACHE[$id] = new self($id);
} }
return self::$CACHE[$id]; return self::$CACHE[$id];
} }
/** /**
* Find a period by page and period number [1..$page->periods()] * Find a period by page and period number [1..$page->periods()]
*/ */
public static function find(studyplanpage $page,$periodnr): self{ public static function find(studyplanpage $page, $periodnr): self{
global $DB; global $DB;
if($periodnr < 1){ if ($periodnr < 1) {
// Clamp period index // Clamp period index .
$periodnr = 1; $periodnr = 1;
} }
try{ try{
$id = $DB->get_field(self::TABLE,"id",["page_id"=>$page->id(), "period" => $periodnr],MUST_EXIST); $id = $DB->get_field(self::TABLE, "id", ["page_id"=>$page->id(), "period" => $periodnr], MUST_EXIST);
$period = self::findById($id); $period = self::findById($id);
} catch(\dml_missing_record_exception $x){ } catch(\dml_missing_record_exception $x) {
// Period does not exist - create one ... // Period does not exist - create one ...
// Make a best guess estimate of the start and end date, based on surrounding periods, // Make a best guess estimate of the start and end date, based on surrounding periods,.
// or specified duration of the page and the sequence of the periods // or specified duration of the page and the sequence of the periods .
$pcount = $page->periods(); $pcount = $page->periods();
$ystart = $page->startdate()->getTimestamp(); $ystart = $page->startdate()->getTimestamp();
$yend = $page->enddate()->getTimestamp(); $yend = $page->enddate()->getTimestamp();
// Estimate the period's timing to make a reasonable first guess // Estimate the period's timing to make a reasonable first guess.
$ydelta = $yend - $ystart; $ydelta = $yend - $ystart;
$ptime = $ydelta / $pcount; $ptime = $ydelta / $pcount;
try{ try{
// Check if we have a previous period to glance the end date of as a reference // Check if we have a previous period to glance the end date of as a reference.
$startdate = $DB->get_field(self::TABLE,"enddate",["page_id"=>$page->id(), "period" => $periodnr-1],MUST_EXIST); $startdate = $DB->get_field(self::TABLE, "enddate", ["page_id"=>$page->id(), "period" => $periodnr-1], MUST_EXIST);
$pstart = strtotime($startdate)+(24*60*60); // Add one day $pstart = strtotime($startdate)+(24*60*60); // Add one day.
} catch(\dml_missing_record_exception $x2){ } catch(\dml_missing_record_exception $x2) {
// If not, do a fair guess // If not, do a fair guess.
$pstart = $ystart + (($periodnr-1)*$ptime); $pstart = $ystart + (($periodnr-1)*$ptime);
} }
try{ try{
// Check if we have a next period to glance the start date of as a reference // Check if we have a next period to glance the start date of as a reference.
$enddate = $DB->get_field(self::TABLE,"startdate",["page_id"=>$page->id(), "period" => $periodnr+1],MUST_EXIST); $enddate = $DB->get_field(self::TABLE, "startdate", ["page_id"=>$page->id(), "period" => $periodnr+1], MUST_EXIST);
$pstart = strtotime($enddate)-(24*60*60); // subtract one day $pstart = strtotime($enddate)-(24*60*60); // subtract one day.
} catch(\dml_missing_record_exception $x2){ } catch(\dml_missing_record_exception $x2) {
// If not, do a fair guess // If not, do a fair guess.
$pend = $pstart + $ptime; $pend = $pstart + $ptime;
} }
// And create the period // And create the period.
$period = self::add([ $period = self::add([
'page_id' => $page->id(), 'page_id' => $page->id(),
'period' => $periodnr, 'period' => $periodnr,
'fullname' => \get_string("period_default_fullname","local_treestudyplan",$periodnr), 'fullname' => \get_string("period_default_fullname", "local_treestudyplan", $periodnr),
'shortname' => \get_string("period_default_shortname","local_treestudyplan",$periodnr), 'shortname' => \get_string("period_default_shortname", "local_treestudyplan", $periodnr),
'startdate' => date("Y-m-d",$pstart), 'startdate' => date("Y-m-d", $pstart),
'enddate' => date("Y-m-d",$pend), 'enddate' => date("Y-m-d", $pend),
]); ]);
} }
return $period; return $period;
@ -81,26 +102,26 @@ class period {
// Cache constructors to avoid multiple creation events in one session. // Cache constructors to avoid multiple creation events in one session.
public static function findForPage(studyplanpage $page): array { public static function findForPage(studyplanpage $page): array {
if(!array_key_exists($page->id(),self::$PAGECACHE)){ if (!array_key_exists($page->id(), self::$PAGECACHE)) {
$periods = []; $periods = [];
// find and add the periods to an array with the period sequence as a key // find and add the periods to an array with the period sequence as a key.
for($i=1; $i <= $page->periods(); $i++){ for($i=1; $i <= $page->periods(); $i++) {
$period = self::find($page,$i); $period = self::find($page, $i);
$periods[$i] = $period; $periods[$i] = $period;
} }
self::$PAGECACHE[$page->id()] = $periods; self::$PAGECACHE[$page->id()] = $periods;
} }
return self::$PAGECACHE[$page->id()]; return self::$PAGECACHE[$page->id()];
} }
private function __construct($id) { private function __construct($id) {
global $DB; global $DB;
$this->id = $id; $this->id = $id;
$this->r = $DB->get_record(self::TABLE,['id' => $id]); $this->r = $DB->get_record(self::TABLE, ['id' => $id]);
$this->page = studyplanpage::findById($this->r->page_id); $this->page = studyplanpage::findById($this->r->page_id);
} }
public function id(){ public function id() {
return $this->id; return $this->id;
} }
@ -108,37 +129,37 @@ class period {
return $this->page->studyplan(); return $this->page->studyplan();
} }
public function page(){ public function page() {
return $this->page; return $this->page;
} }
public function shortname(){ public function shortname() {
return $this->r->shortname; return $this->r->shortname;
} }
public function fullname(){ public function fullname() {
return $this->r->fullname; return $this->r->fullname;
} }
public function period(){ public function period() {
return $this->r->period; return $this->r->period;
} }
public function startdate(){ public function startdate() {
return new \DateTime($this->r->startdate); return new \DateTime($this->r->startdate);
} }
public function enddate(){ public function enddate() {
if($this->r->enddate && strlen($this->r->enddate) > 0){ if ($this->r->enddate && strlen($this->r->enddate) > 0) {
return new \DateTime($this->r->enddate); return new \DateTime($this->r->enddate);
} }
else{ else{
// return a date 100 years into the future // return a date 100 years into the future.
return (new \DateTime($this->r->startdate))->add(new \DateInterval("P100Y")); return (new \DateTime($this->r->startdate))->add(new \DateInterval("P100Y"));
} }
} }
public static function structure($value=VALUE_REQUIRED){ public static function structure($value=VALUE_REQUIRED) {
return new \external_single_structure([ return new \external_single_structure([
"id" => new \external_value(PARAM_INT, 'id of period'), "id" => new \external_value(PARAM_INT, 'id of period'),
"fullname" => new \external_value(PARAM_TEXT, 'Full name of period'), "fullname" => new \external_value(PARAM_TEXT, 'Full name of period'),
@ -146,10 +167,10 @@ class period {
"period" => new \external_value(PARAM_INT, 'period sequence'), "period" => new \external_value(PARAM_INT, 'period sequence'),
"startdate" => new \external_value(PARAM_TEXT, 'start date of period'), "startdate" => new \external_value(PARAM_TEXT, 'start date of period'),
"enddate" => new \external_value(PARAM_TEXT, 'end date of period'), "enddate" => new \external_value(PARAM_TEXT, 'end date of period'),
],'Period info',$value); ], 'Period info', $value);
} }
public function model(){ public function model() {
return [ return [
'id' => $this->r->id, 'id' => $this->r->id,
'fullname' => $this->r->fullname, 'fullname' => $this->r->fullname,
@ -164,62 +185,62 @@ class period {
* Do not use directly to add periods, unless performing import * Do not use directly to add periods, unless performing import
* The static find() and findForPage() functions create the period if needed * The static find() and findForPage() functions create the period if needed
*/ */
public static function add($fields){ public static function add($fields) {
global $DB; global $DB;
if(!isset($fields['page_id'])){ if (!isset($fields['page_id'])) {
throw new \InvalidArgumentException("parameter 'page_id' missing"); throw new \InvalidArgumentException("parameter 'page_id' missing");
} }
if(!isset($fields['period'])){ if (!isset($fields['period'])) {
throw new \InvalidArgumentException("parameter 'period' missing"); throw new \InvalidArgumentException("parameter 'period' missing");
} }
if($DB->record_exists(self::TABLE,["page_id"=>$fields["page_id"], "period"=>$fields["period"]])){ if ($DB->record_exists(self::TABLE, ["page_id"=>$fields["page_id"], "period"=>$fields["period"]])) {
throw new \InvalidArgumentException("record already exists for specified page and period"); throw new \InvalidArgumentException("record already exists for specified page and period");
} }
$addable = ['page_id','fullname','shortname','period','startdate','enddate']; $addable = ['page_id', 'fullname', 'shortname', 'period', 'startdate', 'enddate'];
$info = [ ]; $info = [ ];
foreach($addable as $f){ foreach ($addable as $f) {
if(array_key_exists($f,$fields)){ if (array_key_exists($f, $fields)) {
$info[$f] = $fields[$f]; $info[$f] = $fields[$f];
} }
} }
$id = $DB->insert_record(self::TABLE, $info); $id = $DB->insert_record(self::TABLE, $info);
unset(self::$PAGECACHE[$fields['page_id']]); // invalidate the cache for this page unset(self::$PAGECACHE[$fields['page_id']]); // invalidate the cache for this page.
return self::findById($id); // make sure the new page is immediately cached return self::findById($id); // make sure the new page is immediately cached.
} }
public function edit($fields){ public function edit($fields) {
global $DB; global $DB;
$editable = ['fullname','shortname','startdate','enddate']; $editable = ['fullname', 'shortname', 'startdate', 'enddate'];
$info = ['id' => $this->id,]; $info = ['id' => $this->id, ];
foreach($editable as $f){ foreach ($editable as $f) {
if(array_key_exists($f,$fields)){ if (array_key_exists($f, $fields)) {
$info[$f] = $fields[$f]; $info[$f] = $fields[$f];
} }
} }
$DB->update_record(self::TABLE, $info); $DB->update_record(self::TABLE, $info);
//reload record after edit //reload record after edit.
$this->r = $DB->get_record(self::TABLE,['id' => $this->id],"*",MUST_EXIST); $this->r = $DB->get_record(self::TABLE, ['id' => $this->id], "*", MUST_EXIST);
unset(self::$PAGECACHE[$this->r->page_id]); // invalidate the cache for this page unset(self::$PAGECACHE[$this->r->page_id]); // invalidate the cache for this page.
return $this; return $this;
} }
public function delete(){ public function delete() {
global $DB; global $DB;
$DB->delete_records(self::TABLE, ['id' => $this->id]); $DB->delete_records(self::TABLE, ['id' => $this->id]);
unset(self::$PAGECACHE[$this->r->page_id]); // invalidate the cache for this page unset(self::$PAGECACHE[$this->r->page_id]); // invalidate the cache for this page.
return success::success(); return success::success();
} }
public static function page_structure($value=VALUE_REQUIRED){ public static function page_structure($value=VALUE_REQUIRED) {
return new \external_multiple_structure(self::structure(),"The periods in the page",$value); return new \external_multiple_structure(self::structure(), "The periods in the page", $value);
} }
public static function page_model(studyplanpage $page){ public static function page_model(studyplanpage $page) {
$model = []; $model = [];
foreach(self::findForPage($page) as $p){ foreach (self::findForPage($page) as $p) {
$model[] = $p->model(); $model[] = $p->model();
} }
return $model; return $model;

View File

@ -1,4 +1,25 @@
<?php <?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/>.
/**
*
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace local_treestudyplan\privacy; namespace local_treestudyplan\privacy;
use core_privacy\local\metadata\collection; use core_privacy\local\metadata\collection;
@ -12,7 +33,7 @@ use \core_privacy\local\request\helper;
use \core_privacy\local\request\transform; use \core_privacy\local\request\transform;
use tool_dataprivacy\context_instance; use tool_dataprivacy\context_instance;
class provider implements \core_privacy\local\metadata\provider, class provider implements \core_privacy\local\metadata\provider,
\core_privacy\local\request\plugin\provider, \core_privacy\local\request\plugin\provider,
\core_privacy\local\request\core_userlist_provider \core_privacy\local\request\core_userlist_provider
{ {
@ -57,15 +78,15 @@ class provider implements \core_privacy\local\metadata\provider,
*/ */
public static function get_contexts_for_userid(int $userid) : contextlist { public static function get_contexts_for_userid(int $userid) : contextlist {
$contextlist = new \core_privacy\local\request\contextlist(); $contextlist = new \core_privacy\local\request\contextlist();
$contextlist->add_system_context(); // For invitations $contextlist->add_system_context(); // For invitations.
// add contexts for linked studyplans // add contexts for linked studyplans.
$sql = "SELECT s.context_id FROM {local_treestudyplan} s $sql = "SELECT s.context_id FROM {local_treestudyplan} s
INNER JOIN {local_treestudyplan_user} a ON a.studyplan_id = s.id INNER JOIN {local_treestudyplan_user} a ON a.studyplan_id = s.id
WHERE ( a.user_id = :userid ) WHERE ( a.user_id = :userid )
"; ";
$contextlist->add_from_sql($sql, ['userid' => $userid]); $contextlist->add_from_sql($sql, ['userid' => $userid]);
return $contextlist; return $contextlist;
} }
@ -74,39 +95,39 @@ class provider implements \core_privacy\local\metadata\provider,
* *
* @param approved_contextlist $contextlist The approved contexts to export information for. * @param approved_contextlist $contextlist The approved contexts to export information for.
*/ */
public static function export_user_data(approved_contextlist $contextlist){ public static function export_user_data(approved_contextlist $contextlist) {
global $DB; global $DB;
foreach ($contextlist->get_contexts() as $context) { foreach ($contextlist->get_contexts() as $context) {
$user = $contextlist->get_user(); $user = $contextlist->get_user();
if($context instanceof \context_system){ if ($context instanceof \context_system) {
// Export invitations // Export invitations.
$sql = "SELECT * FROM {local_treestudyplan_invit} i $sql = "SELECT * FROM {local_treestudyplan_invit} i
WHERE ( aiuser_id = :userid ) WHERE ( aiuser_id = :userid )
"; ";
$records = $DB->get_records_sql($sql,["userid" => $user->id]); $records = $DB->get_records_sql($sql, ["userid" => $user->id]);
foreach($records as $r) { foreach ($records as $r) {
static::export_invitation_data_for_user($r); static::export_invitation_data_for_user($r);
} }
// Export empty associations // Export empty associations.
$sql = "SELECT * FROM {local_treestudyplan} s $sql = "SELECT * FROM {local_treestudyplan} s
INNER JOIN {local_treestudyplan_user} a ON a.studyplan_id = s.id INNER JOIN {local_treestudyplan_user} a ON a.studyplan_id = s.id
WHERE ( a.user_id = :userid AND (s.context_id IS NULL or s.context_id = 0) WHERE ( a.user_id = :userid AND (s.context_id IS NULL or s.context_id = 0)
"; ";
$records = $DB->get_records_sql($sql,["userid" => $user->id, "contextid" => $context->id]); $records = $DB->get_records_sql($sql, ["userid" => $user->id, "contextid" => $context->id]);
foreach($records as $r) { foreach ($records as $r) {
static::export_studyplan_data_for_user($r); static::export_studyplan_data_for_user($r);
} }
} else if ($context->contextlevel == CONTEXT_COURSECAT){ } else if ($context->contextlevel == CONTEXT_COURSECAT) {
// Export studyplan associations // Export studyplan associations.
$sql = "SELECT * FROM {local_treestudyplan} s $sql = "SELECT * FROM {local_treestudyplan} s
INNER JOIN {local_treestudyplan_user} a ON a.studyplan_id = s.id INNER JOIN {local_treestudyplan_user} a ON a.studyplan_id = s.id
WHERE ( a.user_id = :userid AND s.context_id = :contextid) WHERE ( a.user_id = :userid AND s.context_id = :contextid)
"; ";
$records = $DB->get_records_sql($sql,["userid" => $user->id, "contextid" => $context->id]); $records = $DB->get_records_sql($sql, ["userid" => $user->id, "contextid" => $context->id]);
foreach($records as $r) { foreach ($records as $r) {
static::export_studyplan_data_for_user($r); static::export_studyplan_data_for_user($r);
} }
} }
@ -149,15 +170,15 @@ class provider implements \core_privacy\local\metadata\provider,
* *
* @param context $context The specific context to delete data for. * @param context $context The specific context to delete data for.
*/ */
public static function delete_data_for_all_users_in_context(\context $context){ public static function delete_data_for_all_users_in_context(\context $context) {
global $DB; global $DB;
// find studyplans in context // find studyplans in context.
if($context->contextlevel == CONTEXT_COURSECAT){ if ($context->contextlevel == CONTEXT_COURSECAT) {
$sql = "SELECT s.id FROM {local_treestudyplan} WHERE ( a.user_id = :userid AND s.context_id = :contextid)"; $sql = "SELECT s.id FROM {local_treestudyplan} WHERE ( a.user_id = :userid AND s.context_id = :contextid)";
$plan_ids = $DB->get_fieldset_sql($sql,["contextid"=>$context->id]); $plan_ids = $DB->get_fieldset_sql($sql, ["contextid"=>$context->id]);
foreach($plan_ids as $plan_id){ foreach ($plan_ids as $plan_id) {
$DB->delete_records("local_treestudyplan_user",["studyplan_id" => $plan_id]); $DB->delete_records("local_treestudyplan_user", ["studyplan_id" => $plan_id]);
} }
} }
} }
@ -167,35 +188,35 @@ class provider implements \core_privacy\local\metadata\provider,
* *
* @param approved_contextlist $contextlist The approved contexts and user information to delete information for. * @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
*/ */
public static function delete_data_for_user(approved_contextlist $contextlist){ public static function delete_data_for_user(approved_contextlist $contextlist) {
global $DB; global $DB;
$user = $contextlist->get_user(); $user = $contextlist->get_user();
foreach ($contextlist->get_contexts() as $context) { foreach ($contextlist->get_contexts() as $context) {
if($context->contextlevel == CONTEXT_SYSTEM){
if ($context->contextlevel == CONTEXT_SYSTEM) {
$sql = "SELECT s.id FROM {local_treestudyplan} INNER JOIN {local_treestudyplan_user} a ON a.studyplan_id = s.id $sql = "SELECT s.id FROM {local_treestudyplan} INNER JOIN {local_treestudyplan_user} a ON a.studyplan_id = s.id
WHERE ( a.user_id = :userid AND ( s.context_id IS NULL OR s.context_id == 0 OR s.context_id = :contextid))"; WHERE ( a.user_id = :userid AND ( s.context_id IS NULL OR s.context_id == 0 OR s.context_id = :contextid))";
$plan_ids = $DB->get_fieldset_sql($sql,["contextid"=>$context->id, "userid" => $user->id]); $plan_ids = $DB->get_fieldset_sql($sql, ["contextid"=>$context->id, "userid" => $user->id]);
foreach($plan_ids as $plan_id){ foreach ($plan_ids as $plan_id) {
$DB->delete_records("local_treestudyplan_user",["studyplan_id" => $plan_id,"user_id" => $user->id]); $DB->delete_records("local_treestudyplan_user", ["studyplan_id" => $plan_id, "user_id" => $user->id]);
} }
// Also delete all invitations for this user // Also delete all invitations for this user.
$DB->delete_records("local_treestudyplan_invit",["user_id" => $user->id]); $DB->delete_records("local_treestudyplan_invit", ["user_id" => $user->id]);
} else if ($context->contextlevel == CONTEXT_COURSECAT){ } else if ($context->contextlevel == CONTEXT_COURSECAT) {
$sql = "SELECT s.id FROM {local_treestudyplan} INNER JOIN {local_treestudyplan_user} a ON a.studyplan_id = s.id $sql = "SELECT s.id FROM {local_treestudyplan} INNER JOIN {local_treestudyplan_user} a ON a.studyplan_id = s.id
WHERE ( a.user_id = :userid AND s.context_id = :contextid)"; WHERE ( a.user_id = :userid AND s.context_id = :contextid)";
$plan_ids = $DB->get_fieldset_sql($sql,["contextid"=>$context->id, "userid" => $user->id]); $plan_ids = $DB->get_fieldset_sql($sql, ["contextid"=>$context->id, "userid" => $user->id]);
foreach($plan_ids as $plan_id){ foreach ($plan_ids as $plan_id) {
$DB->delete_records("local_treestudyplan_user",["studyplan_id" => $plan_id,"user_id" => $user->id]); $DB->delete_records("local_treestudyplan_user", ["studyplan_id" => $plan_id, "user_id" => $user->id]);
} }
} }
} }
} }
/** /**
@ -203,16 +224,16 @@ class provider implements \core_privacy\local\metadata\provider,
* *
* @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination. * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination.
*/ */
public static function get_users_in_context(userlist $userlist){ public static function get_users_in_context(userlist $userlist) {
$context = $userlist->get_context(); $context = $userlist->get_context();
if ($context instanceof \context_system) { if ($context instanceof \context_system) {
// Add all invitations // Add all invitations.
$sql = "SELECT i.user_id as userid $sql = "SELECT i.user_id as userid
FROM {local_treestudyplan_invit} i;"; FROM {local_treestudyplan_invit} i;";
$userlist->add_from_sql('userid', $sql, []); $userlist->add_from_sql('userid', $sql, []);
// also add "contextless studyplans, they are considered in system context" // also add "contextless studyplans, they are considered in system context".
$sql = "SELECT a.user_id as userid FROM {local_treestudyplan_user} a $sql = "SELECT a.user_id as userid FROM {local_treestudyplan_user} a
INNER JOIN {local_treestudyplan} s ON a.studyplan_id = s.id INNER JOIN {local_treestudyplan} s ON a.studyplan_id = s.id
WHERE ( a.context_id is NULL or a.context_id = 0) WHERE ( a.context_id is NULL or a.context_id = 0)
@ -221,7 +242,7 @@ class provider implements \core_privacy\local\metadata\provider,
} }
// add the links to all study plans in this context // add the links to all study plans in this context.
$sql = "SELECT a.user_id as userid FROM {local_treestudyplan_user} a $sql = "SELECT a.user_id as userid FROM {local_treestudyplan_user} a
INNER JOIN {local_treestudyplan} s ON a.studyplan_id = s.id INNER JOIN {local_treestudyplan} s ON a.studyplan_id = s.id
WHERE ( a.context_id = :contextid ) WHERE ( a.context_id = :contextid )
@ -235,36 +256,36 @@ class provider implements \core_privacy\local\metadata\provider,
* *
* @param approved_userlist $userlist The approved context and user information to delete information for. * @param approved_userlist $userlist The approved context and user information to delete information for.
*/ */
public static function delete_data_for_users(approved_userlist $userlist){ public static function delete_data_for_users(approved_userlist $userlist) {
global $DB; global $DB;
$context = $userlist->get_context(); $context = $userlist->get_context();
$users = $userlist->get_userids(); $users = $userlist->get_userids();
list($userinsql, $userinparams) = $DB->get_in_or_equal($users, SQL_PARAMS_NAMED,'user'); list($userinsql, $userinparams) = $DB->get_in_or_equal($users, SQL_PARAMS_NAMED, 'user');
$plan_ids = []; $plan_ids = [];
if($context->contextlevel == CONTEXT_SYSTEM){ if ($context->contextlevel == CONTEXT_SYSTEM) {
// Determine the relevant plan_ids for this context // Determine the relevant plan_ids for this context.
$sql = "SELECT s.id FROM {local_treestudyplan} $sql = "SELECT s.id FROM {local_treestudyplan}
WHERE ( s.context_id IS NULL OR s.context_id == 0 OR s.context_id = :contextid)) "; WHERE ( s.context_id IS NULL OR s.context_id == 0 OR s.context_id = :contextid)) ";
$plan_ids = $DB->get_fieldset_sql($sql,["contextid"=>$context->id,]); $plan_ids = $DB->get_fieldset_sql($sql, ["contextid"=>$context->id, ]);
// If plan ids not empty, they will be processed later // If plan ids not empty, they will be processed later.
// Also delete all invitations for these users
$sql = "user_id {$userinsql}";
$DB->delete_records_select("local_treestudyplan_invit",$sql,$userinparams);
} else if ($context->contextlevel == CONTEXT_COURSECAT){ // Also delete all invitations for these users.
$sql = "SELECT s.id FROM {local_treestudyplan} $sql = "user_id {$userinsql}";
$DB->delete_records_select("local_treestudyplan_invit", $sql, $userinparams);
} else if ($context->contextlevel == CONTEXT_COURSECAT) {
$sql = "SELECT s.id FROM {local_treestudyplan}
WHERE (s.context_id = :contextid)"; WHERE (s.context_id = :contextid)";
$plan_ids = $DB->get_fieldset_sql($sql,["contextid"=>$context->id,]); $plan_ids = $DB->get_fieldset_sql($sql, ["contextid"=>$context->id, ]);
// If plan ids not empty, they will be processed later // If plan ids not empty, they will be processed later.
} }
// Now delete the studyplan associations if relevant // Now delete the studyplan associations if relevant.
if(count($plan_ids) >0 && count($users) >0){ if (count($plan_ids) >0 && count($users) >0) {
list($planinsql,$planinputparams) = $DB->get_in_or_equal($plan_ids, SQL_PARAMS_NAMED,'plan'); list($planinsql, $planinputparams) = $DB->get_in_or_equal($plan_ids, SQL_PARAMS_NAMED, 'plan');
$params = $userinparams+$planinputparams; $params = $userinparams+$planinputparams;
$sql = "user_id {$userinsql} and studyplan_id {$planinsql}"; $sql = "user_id {$userinsql} and studyplan_id {$planinsql}";
$DB->delete_records_select('local_treestudyplan_user', $sql, $params); $DB->delete_records_select('local_treestudyplan_user', $sql, $params);

View File

@ -1,16 +1,37 @@
<?php <?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/>.
/**
*
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once("$CFG->libdir/formslib.php"); require_once("$CFG->libdir/formslib.php");
require_once("$CFG->dirroot/local/treestudyplan/lib.php"); require_once("$CFG->dirroot/local/treestudyplan/lib.php");
class reportinvite_form extends moodleform { class reportinvite_form extends moodleform {
//Add elements to form //Add elements to form.
const GOALS_EDITOR_OPTIONS = array('trusttext'=>true, 'subdirs'=>true, 'maxfiles'=>0,'maxbytes'=>5*1024*1025); const GOALS_EDITOR_OPTIONS = array('trusttext'=>true, 'subdirs'=>true, 'maxfiles'=>0, 'maxbytes'=>5*1024*1025);
public function definition() { public function definition() {
global $CFG; global $CFG;
// 'code', 'revision', 'description', 'goals', 'complexity', 'points', 'studyhours' // 'code', 'revision', 'description', 'goals', 'complexity', 'points', 'studyhours'.
$mform = $this->_form; // Don't forget the underscore! $mform = $this->_form; // Don't forget the underscore! .
$mform->addElement('hidden', 'add', 0); $mform->addElement('hidden', 'add', 0);
$mform->setType('add', PARAM_ALPHANUM); $mform->setType('add', PARAM_ALPHANUM);
@ -18,25 +39,25 @@ class reportinvite_form extends moodleform {
$mform->addElement('hidden', 'update', 0); $mform->addElement('hidden', 'update', 0);
$mform->setType('update', PARAM_INT); $mform->setType('update', PARAM_INT);
// $mform->addElement('static', 'desc_new', get_string('invite_desc_new','local_treestudyplan')); // Add elements to your form // $mform->addElement('static', 'desc_new', get_string('invite_desc_new', 'local_treestudyplan')); // Add elements to your form.
// $mform->addElement('static', 'desc_edit', get_string('invite_desc_edit','local_treestudyplan')); // Add elements to your form // $mform->addElement('static', 'desc_edit', get_string('invite_desc_edit', 'local_treestudyplan')); // Add elements to your form.
$mform->addElement('text', 'name', get_string('invite_name','local_treestudyplan'), array('size' => 50)); // Add elements to your form $mform->addElement('text', 'name', get_string('invite_name', 'local_treestudyplan'), array('size' => 50)); // Add elements to your form.
$mform->setType('name', PARAM_NOTAGS); //Set type of element $mform->setType('name', PARAM_NOTAGS); //Set type of element.
$mform->setDefault('name', ''); //Default value $mform->setDefault('name', ''); //Default value.
$mform->addRule('name', get_string('required'), 'required', null, 'client'); $mform->addRule('name', get_string('required'), 'required', null, 'client');
$mform->addElement('text', 'email', get_string('invite_email','local_treestudyplan'), array('size' => 20)); // Add elements to your form $mform->addElement('text', 'email', get_string('invite_email', 'local_treestudyplan'), array('size' => 20)); // Add elements to your form.
$mform->setType('email', PARAM_NOTAGS); //Set type of element $mform->setType('email', PARAM_NOTAGS); //Set type of element.
$mform->setDefault('email', ''); //Default value $mform->setDefault('email', ''); //Default value.
$mform->addRule('email', get_string('required'), 'required', null, 'client'); $mform->addRule('email', get_string('required'), 'required', null, 'client');
$mform->addRule('email', get_string('email'), 'email', null, 'client'); $mform->addRule('email', get_string('email'), 'email', null, 'client');
$mform->addElement('static', get_string('invite_email','local_treestudyplan') ); // Add elements to your form $mform->addElement('static', get_string('invite_email', 'local_treestudyplan') ); // Add elements to your form.
$this->add_action_buttons(); $this->add_action_buttons();
} }
//Custom validation should be added here //Custom validation should be added here.
function validation($data, $files) { function validation($data, $files) {
return array(); return array();
} }
@ -48,17 +69,17 @@ class reportinvite_form extends moodleform {
function get_data() function get_data()
{ {
global $DB,$USER; global $DB, $USER;
$data = parent::get_data(); $data = parent::get_data();
if($data != NULL) if ($data != NULL)
{ {
if(empty($data->user_id)) if (empty($data->user_id))
{ {
$data->user_id = $USER->id; $data->user_id = $USER->id;
} }
if(empty($data->update)) if (empty($data->update))
{ {
$date = new DateTime("now", core_date::get_user_timezone_object()); $date = new DateTime("now", core_date::get_user_timezone_object());
$date->setTime(0, 0, 0); $date->setTime(0, 0, 0);
@ -66,9 +87,9 @@ class reportinvite_form extends moodleform {
$data->idate = $date->getTimeStamp(); $data->idate = $date->getTimeStamp();
} }
if(empty($data->update)) if (empty($data->update))
{ {
//create a new random key for the invite //create a new random key for the invite.
do { do {
$length = 20; $length = 20;
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
@ -78,7 +99,7 @@ class reportinvite_form extends moodleform {
$randomkey .= $characters[rand(0, $charactersLength - 1)]; $randomkey .= $characters[rand(0, $charactersLength - 1)];
} }
// Double check that the key is unique before inserting // Double check that the key is unique before inserting.
} while($DB->record_exists_select("local_treestudyplan_invit", $DB->sql_compare_text("invitekey"). " = " . $DB->sql_compare_text(":invitekey"), ['invitekey' => $randomkey])); } while($DB->record_exists_select("local_treestudyplan_invit", $DB->sql_compare_text("invitekey"). " = " . $DB->sql_compare_text(":invitekey"), ['invitekey' => $randomkey]));
$data->invitekey = $randomkey; $data->invitekey = $randomkey;

View File

@ -1,4 +1,25 @@
<?php <?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/>.
/**
*
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace local_treestudyplan; namespace local_treestudyplan;
require_once($CFG->libdir.'/externallib.php'); require_once($CFG->libdir.'/externallib.php');
@ -6,7 +27,7 @@ use \local_treestudyplan\local\helpers\webservicehelper;
require_once($CFG->libdir.'/badgeslib.php'); require_once($CFG->libdir.'/badgeslib.php');
class studentstudyplanservice extends \external_api class studentstudyplanservice extends \external_api
{ {
const CAP_VIEWOTHER = "local/treestudyplan:viewuserreports"; const CAP_VIEWOTHER = "local/treestudyplan:viewuserreports";
/************************ /************************
@ -15,29 +36,28 @@ class studentstudyplanservice extends \external_api
* * * *
************************/ ************************/
public static function list_user_studyplans_parameters() public static function list_user_studyplans_parameters()
{ {
return new \external_function_parameters([ return new \external_function_parameters([
"userid" => new \external_value(PARAM_INT, 'id of student', VALUE_DEFAULT), "userid" => new \external_value(PARAM_INT, 'id of student', VALUE_DEFAULT),
]); ]);
} }
public static function list_user_studyplans_returns() public static function list_user_studyplans_returns()
{ {
return new \external_multiple_structure( return new \external_multiple_structure(
studyplan::simple_structure() studyplan::simple_structure()
); );
} }
private static function list_user_studyplans($userid){ private static function list_user_studyplans($userid) {
global $CFG, $DB; global $CFG, $DB;
$list = []; $list = [];
$studyplans = studyplan::find_for_user($userid); $studyplans = studyplan::find_for_user($userid);
foreach($studyplans as $studyplan) foreach ($studyplans as $studyplan) {
{ // only include studyplans in the context the user has permissions for.
// only include studyplans in the context the user has permissions for if (webservicehelper::has_capabilities(self::CAP_VIEWOTHER, $studyplan->context(), false)) {
if(webservicehelper::has_capabilities(self::CAP_VIEWOTHER,$studyplan->context(),false)){
$list[] =$studyplan->simple_model(); $list[] =$studyplan->simple_model();
} }
} }
@ -50,30 +70,28 @@ class studentstudyplanservice extends \external_api
* * * *
************************/ ************************/
public static function get_user_studyplans_parameters() public static function get_user_studyplans_parameters()
{ {
return new \external_function_parameters( [ return new \external_function_parameters( [
"userid" => new \external_value(PARAM_INT, 'id of user'), "userid" => new \external_value(PARAM_INT, 'id of user'),
] ); ] );
} }
public static function get_user_studyplans_returns() public static function get_user_studyplans_returns()
{ {
return new \external_multiple_structure( return new \external_multiple_structure(
studyplan::user_structure() studyplan::user_structure()
); );
} }
public static function get_user_studyplans($userid) public static function get_user_studyplans($userid) {
{
global $CFG, $DB; global $CFG, $DB;
$studyplans = studyplan::find_for_user($userid); $studyplans = studyplan::find_for_user($userid);
$map = []; $map = [];
foreach($studyplans as $studyplan) foreach ($studyplans as $studyplan) {
{ // only include studyplans in the context the user has permissions for.
// only include studyplans in the context the user has permissions for if (webservicehelper::has_capabilities(self::CAP_VIEWOTHER, $studyplan->context(), false)) {
if(webservicehelper::has_capabilities(self::CAP_VIEWOTHER,$studyplan->context(),false)){
$map[] = $studyplan->user_model($userid); $map[] = $studyplan->user_model($userid);
} }
} }
@ -87,7 +105,7 @@ class studentstudyplanservice extends \external_api
* * * *
************************/ ************************/
public static function get_user_studyplan_parameters() public static function get_user_studyplan_parameters()
{ {
return new \external_function_parameters( [ return new \external_function_parameters( [
"userid" => new \external_value(PARAM_INT, 'id of user'), "userid" => new \external_value(PARAM_INT, 'id of user'),
@ -95,18 +113,17 @@ class studentstudyplanservice extends \external_api
] ); ] );
} }
public static function get_user_studyplan_returns() public static function get_user_studyplan_returns()
{ {
return studyplan::user_structure(); return studyplan::user_structure();
} }
public static function get_user_studyplan($userid,$studyplanid) public static function get_user_studyplan($userid, $studyplanid) {
{
global $CFG, $DB; global $CFG, $DB;
$studyplan = studyplan::findById($studyplanid); $studyplan = studyplan::findById($studyplanid);
webservicehelper::require_capabilities(self::CAP_VIEWOTHER,$studyplan->context()); webservicehelper::require_capabilities(self::CAP_VIEWOTHER, $studyplan->context());
if($studyplan->has_linked_user($userid)){ if ($studyplan->has_linked_user($userid)) {
return $studyplan->user_model($userid); return $studyplan->user_model($userid);
} }
else { else {
@ -120,38 +137,36 @@ class studentstudyplanservice extends \external_api
* * * *
****************************/ ****************************/
public static function get_invited_studyplan_parameters() public static function get_invited_studyplan_parameters()
{ {
return new \external_function_parameters( [ return new \external_function_parameters( [
"invitekey" => new \external_value(PARAM_RAW, 'invite key'), "invitekey" => new \external_value(PARAM_RAW, 'invite key'),
] ); ] );
} }
public static function get_invited_studyplan_returns() public static function get_invited_studyplan_returns()
{ {
return new \external_multiple_structure( return new \external_multiple_structure(
studyplan::user_structure() studyplan::user_structure()
); );
} }
public static function get_invited_studyplan($invitekey) public static function get_invited_studyplan($invitekey) {
{
global $CFG, $DB; global $CFG, $DB;
$invite = $DB->get_record_select("local_treestudyplan_invit", $DB->sql_compare_text("invitekey"). " = " . $DB->sql_compare_text(":invitekey"), ['invitekey' => $invitekey]); $invite = $DB->get_record_select("local_treestudyplan_invit", $DB->sql_compare_text("invitekey"). " = " . $DB->sql_compare_text(":invitekey"), ['invitekey' => $invitekey]);
if(empty($invite)){ if (empty($invite)) {
return []; return [];
} }
// Validate context now // Validate context now.
\external_api::validate_context(\context_system::instance()); \external_api::validate_context(\context_system::instance());
$userid = $invite->user_id; $userid = $invite->user_id;
$map = []; $map = [];
$studyplans = studyplan::find_for_user($userid); $studyplans = studyplan::find_for_user($userid);
foreach($studyplans as $studyplan) foreach ($studyplans as $studyplan) {
{
$map[] = $studyplan->user_model($userid); $map[] = $studyplan->user_model($userid);
} }
return $map; return $map;
@ -163,26 +178,25 @@ class studentstudyplanservice extends \external_api
* * * *
************************/ ************************/
public static function list_own_studyplans_parameters() public static function list_own_studyplans_parameters()
{ {
return new \external_function_parameters([]); return new \external_function_parameters([]);
} }
public static function list_own_studyplans_returns() public static function list_own_studyplans_returns()
{ {
return new \external_multiple_structure( return new \external_multiple_structure(
studyplan::simple_structure() studyplan::simple_structure()
); );
} }
private static function list_own_studyplans(){ private static function list_own_studyplans() {
global $CFG, $DB, $USER; global $CFG, $DB, $USER;
$userid = $USER->id; $userid = $USER->id;
$list = []; $list = [];
$studyplans = studyplan::find_for_user($userid); $studyplans = studyplan::find_for_user($userid);
foreach($studyplans as $studyplan) foreach ($studyplans as $studyplan) {
{
$list[] =$studyplan->simple_model(); $list[] =$studyplan->simple_model();
} }
return $list; return $list;
@ -194,22 +208,21 @@ class studentstudyplanservice extends \external_api
* * * *
************************/ ************************/
public static function get_own_studyplan_parameters() public static function get_own_studyplan_parameters()
{ {
return new \external_function_parameters( [ return new \external_function_parameters( [
"id" => new \external_value(PARAM_INT, 'id of specific studyplan to provide', VALUE_DEFAULT), "id" => new \external_value(PARAM_INT, 'id of specific studyplan to provide', VALUE_DEFAULT),
] ); ] );
} }
public static function get_own_studyplan_returns() public static function get_own_studyplan_returns()
{ {
return new \external_multiple_structure( return new \external_multiple_structure(
studyplan::user_structure() studyplan::user_structure()
); );
} }
public static function get_own_studyplan($id=null) public static function get_own_studyplan($id=null) {
{
global $USER; global $USER;
// Validate this call in the system context. // Validate this call in the system context.
@ -219,8 +232,8 @@ class studentstudyplanservice extends \external_api
$studyplans = studyplan::find_for_user($userid); $studyplans = studyplan::find_for_user($userid);
if(isset($id) && $id > 0){ if (isset($id) && $id > 0) {
if(isset($studyplans[$id])){ if (isset($studyplans[$id])) {
$studyplan = $studyplans[$id]; $studyplan = $studyplans[$id];
return [$studyplan->user_model($userid)]; return [$studyplan->user_model($userid)];
} else { } else {
@ -229,7 +242,7 @@ class studentstudyplanservice extends \external_api
} }
else { else {
$map = []; $map = [];
foreach($studyplans as $studyplan){ foreach ($studyplans as $studyplan) {
$map[] = $studyplan->user_model($userid); $map[] = $studyplan->user_model($userid);
} }
return $map; return $map;
@ -242,30 +255,29 @@ class studentstudyplanservice extends \external_api
* * * *
***************************/ ***************************/
public static function get_teaching_studyplans_parameters() public static function get_teaching_studyplans_parameters()
{ {
return new \external_function_parameters( [ return new \external_function_parameters( [
"id" => new \external_value(PARAM_INT, 'id of specific studyplan to provide', VALUE_DEFAULT), "id" => new \external_value(PARAM_INT, 'id of specific studyplan to provide', VALUE_DEFAULT),
] ); ] );
} }
public static function get_teaching_studyplans_returns() public static function get_teaching_studyplans_returns()
{ {
return new \external_multiple_structure( return new \external_multiple_structure(
studyplan::editor_structure() studyplan::editor_structure()
); );
} }
public static function get_teaching_studyplans($id=null) public static function get_teaching_studyplans($id=null) {
{
global $CFG, $DB, $USER; global $CFG, $DB, $USER;
$userid = $USER->id; $userid = $USER->id;
\external_api::validate_context(\context_system::instance()); \external_api::validate_context(\context_system::instance());
$studyplans = teachingfinder::list_my_plans(); $studyplans = teachingfinder::list_my_plans();
if(isset($id) && $id > 0){ if (isset($id) && $id > 0) {
if(isset($studyplans[$id])){ if (isset($studyplans[$id])) {
$studyplan = $studyplans[$id]; $studyplan = $studyplans[$id];
return [$studyplan->editor_model($userid)]; return [$studyplan->editor_model($userid)];
} else { } else {
@ -274,7 +286,7 @@ class studentstudyplanservice extends \external_api
} }
else { else {
$map = []; $map = [];
foreach($studyplans as $studyplan){ foreach ($studyplans as $studyplan) {
$map[] = $studyplan->editor_model($userid); $map[] = $studyplan->editor_model($userid);
} }
return $map; return $map;

View File

@ -1,4 +1,25 @@
<?php <?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/>.
/**
*
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace local_treestudyplan; namespace local_treestudyplan;
require_once($CFG->libdir.'/externallib.php'); require_once($CFG->libdir.'/externallib.php');
@ -16,9 +37,9 @@ class studyitem {
public const TABLE = "local_treestudyplan_item"; public const TABLE = "local_treestudyplan_item";
private static $STUDYITEM_CACHE = []; private static $STUDYITEM_CACHE = [];
private $r; // Holds database record private $r; // Holds database record.
private $id; private $id;
private $courseinfo = null; private $courseinfo = null;
private $studyline; private $studyline;
private $aggregator; private $aggregator;
@ -36,9 +57,9 @@ class studyitem {
} }
public static function findById($id): self { public static function findById($id): self {
if(!array_key_exists($id,self::$STUDYITEM_CACHE)){ if (!array_key_exists($id, self::$STUDYITEM_CACHE)) {
self::$STUDYITEM_CACHE[$id] = new self($id); self::$STUDYITEM_CACHE[$id] = new self($id);
} }
return self::$STUDYITEM_CACHE[$id]; return self::$STUDYITEM_CACHE[$id];
} }
@ -46,38 +67,38 @@ class studyitem {
public function __construct($id) { public function __construct($id) {
global $DB; global $DB;
$this->id = $id; $this->id = $id;
$this->r = $DB->get_record(self::TABLE,['id' => $id],"*",MUST_EXIST); $this->r = $DB->get_record(self::TABLE, ['id' => $id], "*", MUST_EXIST);
$this->studyline = studyline::findById($this->r->line_id); $this->studyline = studyline::findById($this->r->line_id);
$this->aggregator = $this->studyline()->studyplan()->aggregator(); $this->aggregator = $this->studyline()->studyplan()->aggregator();
} }
public function id(){ public function id() {
return $this->id; return $this->id;
} }
public function slot(){ public function slot() {
return $this->r->slot; return $this->r->slot;
} }
public function layer(){ public function layer() {
return $this->r->layer; return $this->r->layer;
} }
public function type(){ public function type() {
return $this->r->type; return $this->r->type;
} }
public function courseid(){ public function courseid() {
return $this->r->course_id; return $this->r->course_id;
} }
public static function exists($id){ public static function exists($id) {
global $DB; global $DB;
return is_numeric($id) && $DB->record_exists(self::TABLE, array('id' => $id)); return is_numeric($id) && $DB->record_exists(self::TABLE, array('id' => $id));
} }
public static function editor_structure($value=VALUE_REQUIRED){ public static function editor_structure($value=VALUE_REQUIRED) {
return new \external_single_structure([ return new \external_single_structure([
"id" => new \external_value(PARAM_INT, 'id of study item'), "id" => new \external_value(PARAM_INT, 'id of study item'),
"type" => new \external_value(PARAM_TEXT, 'shortname of study item'), "type" => new \external_value(PARAM_TEXT, 'shortname of study item'),
@ -96,17 +117,17 @@ class studyitem {
} }
public function editor_model(){ public function editor_model() {
return $this->generate_model("editor"); return $this->generate_model("editor");
} }
private function generate_model($mode){ private function generate_model($mode) {
// Mode parameter is used to geep this function for both editor model and export model // 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) // (Export model results in fewer parameters on children, but is otherwise basically the same as this function).
global $DB; global $DB;
$model = [ $model = [
'id' => $this->r->id, // Id is needed in export model because of link references 'id' => $this->r->id, // Id is needed in export model because of link references.
'type' => $this->isValid()?$this->r->type:self::INVALID, 'type' => $this->isValid()?$this->r->type:self::INVALID,
'conditions' => $this->r->conditions, 'conditions' => $this->r->conditions,
'slot' => $this->r->slot, 'slot' => $this->r->slot,
@ -118,68 +139,67 @@ class studyitem {
"out" => [], "out" => [],
] ]
]; ];
if($mode == "export"){ if ($mode == "export") {
// remove slot and layer // remove slot and layer.
unset($model["slot"]); unset($model["slot"]);
unset($model["layer"]); unset($model["layer"]);
unset($model["continuation_id"]); unset($model["continuation_id"]);
$model["connections"] = []; // In export mode, connections is just an array of outgoing connections $model["connections"] = []; // In export mode, connections is just an array of outgoing connections.
if(!isset($this->r->conditions)){ if (!isset($this->r->conditions)) {
unset($model["conditions"]); unset($model["conditions"]);
} }
} }
// Add course link if available // Add course link if available.
$ci = $this->getcourseinfo(); $ci = $this->getcourseinfo();
if(isset($ci)){ if (isset($ci)) {
if($mode == "export"){ if ($mode == "export") {
$model['course'] = $ci->shortname(); $model['course'] = $ci->shortname();
} else { } else {
$model['course'] = $ci->editor_model($this,$this->aggregator->usecorecompletioninfo()); $model['course'] = $ci->editor_model($this, $this->aggregator->usecorecompletioninfo());
} }
} }
// Add badge info if available // Add badge info if available.
if(is_numeric($this->r->badge_id) && $DB->record_exists('badge', array('id' => $this->r->badge_id))) if (is_numeric($this->r->badge_id) && $DB->record_exists('badge', array('id' => $this->r->badge_id))) {
{
$badge = new \core_badges\badge($this->r->badge_id); $badge = new \core_badges\badge($this->r->badge_id);
$badgeinfo = new badgeinfo($badge); $badgeinfo = new badgeinfo($badge);
if($mode == "export"){ if ($mode == "export") {
$model['badge'] = $badgeinfo->name(); $model['badge'] = $badgeinfo->name();
} else { } else {
// Also supply a list of linked users, so the badgeinfo can give stats on // Also supply a list of linked users, so the badgeinfo can give stats on .
// the amount issued, related to this studyplan // the amount issued, related to this studyplan.
$studentids = $this->studyline()->studyplan()->find_linked_userids(); $studentids = $this->studyline()->studyplan()->find_linked_userids();
$model['badge'] = $badgeinfo->editor_model($studentids); $model['badge'] = $badgeinfo->editor_model($studentids);
} }
} }
if($mode == "export"){ if ($mode == "export") {
// Also export gradables // Also export gradables.
$gradables = gradeinfo::list_studyitem_gradables($this); $gradables = gradeinfo::list_studyitem_gradables($this);
if(count($gradables) > 0){ if (count($gradables) > 0) {
$model["gradables"] = []; $model["gradables"] = [];
foreach($gradables as $g){ foreach ($gradables as $g) {
$model["gradables"][] = $g->export_model();; $model["gradables"][] = $g->export_model();;
} }
} }
} }
// Add incoming and outgoing connection info // Add incoming and outgoing connection info.
$conn_out = studyitemconnection::find_outgoing($this->id); $conn_out = studyitemconnection::find_outgoing($this->id);
if($mode == "export"){ if ($mode == "export") {
foreach($conn_out as $c) { foreach ($conn_out as $c) {
$model["connections"][] = $c->to_id(); $model["connections"][] = $c->to_id();
} }
} }
else { else {
foreach($conn_out as $c) { foreach ($conn_out as $c) {
$model['connections']['out'][$c->to_id()] = $c->model(); $model['connections']['out'][$c->to_id()] = $c->model();
} }
$conn_in = studyitemconnection::find_incoming($this->id); $conn_in = studyitemconnection::find_incoming($this->id);
foreach($conn_in as $c) { foreach ($conn_in as $c) {
$model['connections']['in'][$c->from_id()] = $c->model(); $model['connections']['in'][$c->from_id()] = $c->model();
} }
} }
@ -188,80 +208,77 @@ class studyitem {
} }
public static function add($fields,$import=false) public static function add($fields, $import=false) {
{
global $DB; global $DB;
$addable = ['line_id','type','layer','conditions','slot','competency_id','course_id','badge_id','continuation_id','span']; $addable = ['line_id', 'type', 'layer', 'conditions', 'slot', 'competency_id', 'course_id', 'badge_id', 'continuation_id', 'span'];
$info = [ 'layer' => 0, ]; $info = [ 'layer' => 0, ];
foreach($addable as $f){ foreach ($addable as $f) {
if(array_key_exists($f,$fields)){ if (array_key_exists($f, $fields)) {
$info[$f] = $fields[$f]; $info[$f] = $fields[$f];
} }
} }
$id = $DB->insert_record(self::TABLE, $info); $id = $DB->insert_record(self::TABLE, $info);
$item = self::findById($id); $item = self::findById($id);
if($item->type() == self::COURSE){ if ($item->type() == self::COURSE) {
// Signal the studyplan that a course has been added so it can be marked for csync cascading // Signal the studyplan that a course has been added so it can be marked for csync cascading.
$item->studyline()->studyplan()->mark_csync_changed(); $item->studyline()->studyplan()->mark_csync_changed();
} }
return $item; return $item;
} }
public function edit($fields) public function edit($fields) {
{
global $DB; global $DB;
$editable = ['conditions','course_id','continuation_id','span']; $editable = ['conditions', 'course_id', 'continuation_id', 'span'];
$info = ['id' => $this->id,]; $info = ['id' => $this->id, ];
foreach($editable as $f){ foreach ($editable as $f) {
if(array_key_exists($f,$fields) && isset($fields[$f])){ if (array_key_exists($f, $fields) && isset($fields[$f])) {
$info[$f] = $fields[$f]; $info[$f] = $fields[$f];
} }
} }
$DB->update_record(self::TABLE, $info); $DB->update_record(self::TABLE, $info);
//reload record after edit //reload record after edit.
$this->r = $DB->get_record(self::TABLE,['id' => $this->id],"*",MUST_EXIST); $this->r = $DB->get_record(self::TABLE, ['id' => $this->id], "*", MUST_EXIST);
return $this; return $this;
} }
public function isValid(){ public function isValid() {
// Check if referenced courses, badges and/or competencies still exist // Check if referenced courses, badges and/or competencies still exist.
if($this->r->type == static::COURSE){ if ($this->r->type == static::COURSE) {
return courseinfo::exists($this->r->course_id); return courseinfo::exists($this->r->course_id);
} }
else if($this->r->type == static::BADGE){ else if ($this->r->type == static::BADGE) {
return badgeinfo::exists($this->r->badge_id); return badgeinfo::exists($this->r->badge_id);
} }
else { else {
return true; return true;
} }
} }
public function delete($force=false) public function delete($force=false) {
{
global $DB; global $DB;
// check if this item is referenced in a START item // check if this item is referenced in a START item.
if($force){ if ($force) {
// clear continuation id from any references to this item // clear continuation id from any references to this item.
$records = $DB->get_records(self::TABLE,['continuation_id' => $this->id]); $records = $DB->get_records(self::TABLE, ['continuation_id' => $this->id]);
foreach($records as $r){ foreach ($records as $r) {
$r->continuation_id = 0; $r->continuation_id = 0;
$DB->update_record(self::TABLE,$r); $DB->update_record(self::TABLE, $r);
} }
} }
if($DB->count_records(self::TABLE,['continuation_id' => $this->id]) > 0){ if ($DB->count_records(self::TABLE, ['continuation_id' => $this->id]) > 0) {
return success::fail('Cannot remove: item is referenced by another item'); return success::fail('Cannot remove: item is referenced by another item');
} }
else else
{ {
// delete al related connections to this item // delete al related connections to this item.
studyitemconnection::clear($this->id); studyitemconnection::clear($this->id);
// delete all grade inclusion references to this item // delete all grade inclusion references to this item.
$DB->delete_records("local_treestudyplan_gradeinc",['studyitem_id' => $this->id]); $DB->delete_records("local_treestudyplan_gradeinc", ['studyitem_id' => $this->id]);
// delete the item itself // delete the item itself.
$DB->delete_records(self::TABLE, ['id' => $this->id]); $DB->delete_records(self::TABLE, ['id' => $this->id]);
return success::success(); return success::success();
@ -273,13 +290,11 @@ class studyitem {
* reorder_studyitems * * reorder_studyitems *
* * * *
************************/ ************************/
public static function reorder($resequence) public static function reorder($resequence) {
{
global $DB; global $DB;
foreach($resequence as $sq) foreach ($resequence as $sq) {
{
$DB->update_record(self::TABLE, [ $DB->update_record(self::TABLE, [
'id' => $sq['id'], 'id' => $sq['id'],
'line_id' => $sq['line_id'], 'line_id' => $sq['line_id'],
@ -291,43 +306,42 @@ class studyitem {
return success::success(); return success::success();
} }
public static function find_studyline_children(studyline $line) public static function find_studyline_children(studyline $line) {
{
global $DB; global $DB;
$list = []; $list = [];
$ids = $DB->get_fieldset_select(self::TABLE,"id","line_id = :line_id ORDER BY layer",['line_id' => $line->id()]); $ids = $DB->get_fieldset_select(self::TABLE, "id", "line_id = :line_id ORDER BY layer", ['line_id' => $line->id()]);
foreach($ids as $id) { foreach ($ids as $id) {
$item = self::findById($id,$line); $item = self::findById($id, $line);
$list[] = $item; $list[] = $item;
} }
return $list; return $list;
} }
private static function link_structure($value=VALUE_REQUIRED){ private static function link_structure($value=VALUE_REQUIRED) {
return new \external_single_structure([ return new \external_single_structure([
"id" => new \external_value(PARAM_INT, 'id of study item'), "id" => new \external_value(PARAM_INT, 'id of study item'),
"type" => new \external_value(PARAM_TEXT, 'type of study item'), "type" => new \external_value(PARAM_TEXT, 'type of study item'),
"completion" => completion::structure(), "completion" => completion::structure(),
"studyline" => new \external_value(PARAM_TEXT, 'reference label of studyline'), "studyline" => new \external_value(PARAM_TEXT, 'reference label of studyline'),
"studyplan" => new \external_value(PARAM_TEXT, 'reference label of studyplan'), "studyplan" => new \external_value(PARAM_TEXT, 'reference label of studyplan'),
], 'basic info of referenced studyitem', $value); ], 'basic info of referenced studyitem', $value);
} }
private function link_model($userid){ private function link_model($userid) {
global $DB; global $DB;
$line = $DB->get_record(studyline::TABLE,['id' => $this->r->line_id]); $line = $DB->get_record(studyline::TABLE, ['id' => $this->r->line_id]);
$plan = $DB->get_record(studyplan::TABLE,['id' => $line->studyplan_id]); $plan = $DB->get_record(studyplan::TABLE, ['id' => $line->studyplan_id]);
return [ return [
"id" => $this->r->id, "id" => $this->r->id,
"type" => $this->r->type, "type" => $this->r->type,
"completion" => $this->completion($userid), "completion" => $this->completion($userid),
"studyline" => $line->name(), "studyline" => $line->name(),
"studyplan" => $plan->name(), "studyplan" => $plan->name(),
]; ];
} }
public static function user_structure($value=VALUE_REQUIRED){ public static function user_structure($value=VALUE_REQUIRED) {
return new \external_single_structure([ return new \external_single_structure([
"id" => new \external_value(PARAM_INT, 'id of study item'), "id" => new \external_value(PARAM_INT, 'id of study item'),
"type" => new \external_value(PARAM_TEXT, 'type of study item'), "type" => new \external_value(PARAM_TEXT, 'type of study item'),
@ -342,11 +356,11 @@ class studyitem {
'in' => new \external_multiple_structure(studyitemconnection::structure()), 'in' => new \external_multiple_structure(studyitemconnection::structure()),
'out' => new \external_multiple_structure(studyitemconnection::structure()), 'out' => new \external_multiple_structure(studyitemconnection::structure()),
]), ]),
],'Study item info',$value); ], 'Study item info', $value);
} }
public function user_model($userid){ public function user_model($userid) {
global $CFG, $DB; global $CFG, $DB;
$model = [ $model = [
@ -362,35 +376,32 @@ class studyitem {
] ]
]; ];
// Add badge info if available // Add badge info if available.
if(badgeinfo::exists($this->r->badge_id)) if (badgeinfo::exists($this->r->badge_id)) {
{
$badge = new \core_badges\badge($this->r->badge_id); $badge = new \core_badges\badge($this->r->badge_id);
$badgeinfo = new badgeinfo($badge); $badgeinfo = new badgeinfo($badge);
$model['badge'] = $badgeinfo->user_model($userid); $model['badge'] = $badgeinfo->user_model($userid);
} }
// Add continuation_info if available // Add continuation_info if available.
if(self::exists($this->r->continuation_id)) if (self::exists($this->r->continuation_id)) {
{
$c_item = self::findById($this->r->continuation_id); $c_item = self::findById($this->r->continuation_id);
$model['continuation'] = $c_item->link_model($userid); $model['continuation'] = $c_item->link_model($userid);
} }
// Add course if available // Add course if available.
if(courseinfo::exists($this->r->course_id)) if (courseinfo::exists($this->r->course_id)) {
{
$cinfo = $this->getcourseinfo(); $cinfo = $this->getcourseinfo();
$model['course'] = $cinfo->user_model($userid,$this->aggregator->usecorecompletioninfo()); $model['course'] = $cinfo->user_model($userid, $this->aggregator->usecorecompletioninfo());
} }
// Add incoming and outgoing connection info // Add incoming and outgoing connection info.
$conn_out = studyitemconnection::find_outgoing($this->id); $conn_out = studyitemconnection::find_outgoing($this->id);
foreach($conn_out as $c) { foreach ($conn_out as $c) {
$model['connections']['out'][$c->to_id()] = $c->model(); $model['connections']['out'][$c->to_id()] = $c->model();
} }
$conn_in = studyitemconnection::find_incoming($this->id); $conn_in = studyitemconnection::find_incoming($this->id);
foreach($conn_in as $c) { foreach ($conn_in as $c) {
$model['connections']['in'][$c->from_id()] = $c->model(); $model['connections']['in'][$c->from_id()] = $c->model();
} }
@ -398,9 +409,8 @@ class studyitem {
} }
public function getcourseinfo() public function getcourseinfo() {
{ if (empty($this->courseinfo) && courseinfo::exists($this->r->course_id)) {
if(empty($this->courseinfo) && courseinfo::exists($this->r->course_id)){
$this->courseinfo = new courseinfo($this->r->course_id, $this); $this->courseinfo = new courseinfo($this->r->course_id, $this);
} }
return $this->courseinfo; return $this->courseinfo;
@ -409,47 +419,46 @@ class studyitem {
private function completion($userid) { private function completion($userid) {
global $DB; global $DB;
if($this->isValid()){ if ($this->isValid()) {
if(strtolower($this->r->type) == 'course'){ if (strtolower($this->r->type) == 'course') {
// determine competency by competency completion // determine competency by competency completion.
$courseinfo = $this->getcourseinfo(); $courseinfo = $this->getcourseinfo();
return $this->aggregator->aggregate_course($courseinfo,$this,$userid); return $this->aggregator->aggregate_course($courseinfo, $this, $userid);
} }
elseif(strtolower($this->r->type) =='start'){ else if (strtolower($this->r->type) =='start') {
// Does not need to use aggregator. // Does not need to use aggregator.
// Either true, or the completion of the reference // Either true, or the completion of the reference.
if(self::exists($this->r->continuation_id)){ if (self::exists($this->r->continuation_id)) {
$c_item = self::findById($this->r->continuation_id); $c_item = self::findById($this->r->continuation_id);
return $c_item->completion($userid); return $c_item->completion($userid);
} else { } else {
return completion::COMPLETED; return completion::COMPLETED;
} }
} }
elseif(in_array(strtolower($this->r->type),['junction','finish'])){ else if (in_array(strtolower($this->r->type), ['junction', 'finish'])) {
// completion of the linked items, according to the rule // completion of the linked items, according to the rule.
$in_completed = []; $in_completed = [];
// Retrieve incoming connections // Retrieve incoming connections.
$incoming = $DB->get_records(studyitemconnection::TABLE,['to_id' => $this->r->id]); $incoming = $DB->get_records(studyitemconnection::TABLE, ['to_id' => $this->r->id]);
foreach($incoming as $conn){ foreach ($incoming as $conn) {
$item = self::findById($conn->from_id); $item = self::findById($conn->from_id);
$in_completed[] = $item->completion($userid); $in_completed[] = $item->completion($userid);
} }
return $this->aggregator->aggregate_junction($in_completed,$this,$userid); return $this->aggregator->aggregate_junction($in_completed, $this, $userid);
} }
elseif(strtolower($this->r->type) =='badge'){ else if (strtolower($this->r->type) =='badge') {
global $DB; global $DB;
// badge awarded // badge awarded.
if(badgeinfo::exists($this->r->badge_id)) if (badgeinfo::exists($this->r->badge_id)) {
{
$badge = new \core_badges\badge($this->r->badge_id); $badge = new \core_badges\badge($this->r->badge_id);
if($badge->is_issued($userid)){ if ($badge->is_issued($userid)) {
if($badge->can_expire()){ if ($badge->can_expire()) {
// get the issued badges and check if any of them have not expired yet // get the issued badges and check if any of them have not expired yet.
$badges_issued = $DB->get_records("badge_issued",["badge_id" => $this->r->badge_id, "user_id" => $userid]); $badges_issued = $DB->get_records("badge_issued", ["badge_id" => $this->r->badge_id, "user_id" => $userid]);
$notexpired = false; $notexpired = false;
$now = time(); $now = time();
foreach($badges_issued as $bi){ foreach ($badges_issued as $bi) {
if($bi->dateexpire == null || $bi->dateexpire > $now){ if ($bi->dateexpire == null || $bi->dateexpire > $now) {
$notexpired = true; $notexpired = true;
break; break;
} }
@ -467,57 +476,57 @@ class studyitem {
} }
} }
else { else {
// return incomplete for other types // return incomplete for other types.
return completion::INCOMPLETE; return completion::INCOMPLETE;
} }
} }
else { else {
// return incomplete for other types // return incomplete for other types.
return completion::INCOMPLETE; return completion::INCOMPLETE;
} }
} }
public function duplicate($new_line){ public function duplicate($new_line) {
global $DB; global $DB;
// clone the database fields // clone the database fields.
$fields = clone $this->r; $fields = clone $this->r;
// set new line id // set new line id.
unset($fields->id); unset($fields->id);
$fields->line_id = $new_line->id(); $fields->line_id = $new_line->id();
//create new record with the new data //create new record with the new data.
$id = $DB->insert_record(self::TABLE, (array)$fields); $id = $DB->insert_record(self::TABLE, (array)$fields);
$new = self::findById($id,$new_line); $new = self::findById($id, $new_line);
// copy the grading info if relevant // copy the grading info if relevant.
$gradables = gradeinfo::list_studyitem_gradables($this); $gradables = gradeinfo::list_studyitem_gradables($this);
foreach($gradables as $g){ foreach ($gradables as $g) {
gradeinfo::include_grade($g->getGradeitem()->id,$new,true); gradeinfo::include_grade($g->getGradeitem()->id, $new->id(), true);
} }
return $new; return $new;
} }
public function export_model(){ public function export_model() {
return $this->generate_model("export"); return $this->generate_model("export");
} }
public static function import_item($model){ public static function import_item($model) {
unset($model["course_id"]); unset($model["course_id"]);
unset($model["competency_id"]); unset($model["competency_id"]);
unset($model["badge_id"]); unset($model["badge_id"]);
unset($model["continuation_id"]); unset($model["continuation_id"]);
if(isset($model["course"])){ if (isset($model["course"])) {
$model["course_id"] = courseinfo::id_from_shortname(($model["course"])); $model["course_id"] = courseinfo::id_from_shortname(($model["course"]));
} }
if(isset($model["badge"])){ if (isset($model["badge"])) {
$model["badge_id"] = badgeinfo::id_from_name(($model["badge"])); $model["badge_id"] = badgeinfo::id_from_name(($model["badge"]));
} }
$item = self::add($model,true); $item = self::add($model, true);
if(isset($model["course_id"])){ if (isset($model["course_id"])) {
// attempt to import the gradables // attempt to import the gradables.
foreach($model["gradables"] as $gradable){ foreach ($model["gradables"] as $gradable) {
gradeinfo::import($item,$gradable); gradeinfo::import($item, $gradable);
} }
} }

View File

@ -1,4 +1,25 @@
<?php <?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/>.
/**
*
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace local_treestudyplan; namespace local_treestudyplan;
require_once($CFG->libdir.'/externallib.php'); require_once($CFG->libdir.'/externallib.php');
@ -10,96 +31,92 @@ class studyitemconnection {
private $id; private $id;
protected function __construct($r){ protected function __construct($r) {
$this->r = $r; $this->r = $r;
$this->id = $r->id; $this->id = $r->id;
} }
public static function structure($value=VALUE_REQUIRED){ public static function structure($value=VALUE_REQUIRED) {
return new \external_single_structure([ return new \external_single_structure([
'id' => new \external_value(PARAM_INT, 'id of connection'), 'id' => new \external_value(PARAM_INT, 'id of connection'),
'from_id' => new \external_value(PARAM_INT, 'id of start item'), 'from_id' => new \external_value(PARAM_INT, 'id of start item'),
'to_id' => new \external_value(PARAM_INT, 'id of end item'), 'to_id' => new \external_value(PARAM_INT, 'id of end item'),
],'',$value); ], '', $value);
} }
public function model(){ public function model() {
return ['id' => $this->r->id, 'from_id' => $this->r->from_id, 'to_id' => $this->r->to_id]; return ['id' => $this->r->id, 'from_id' => $this->r->from_id, 'to_id' => $this->r->to_id];
} }
public function from_item(){ public function from_item() {
return studyitem::findById($this->r->from_id); return studyitem::findById($this->r->from_id);
} }
public function to_item(){ public function to_item() {
return studyitem::findById($this->r->to_id); return studyitem::findById($this->r->to_id);
} }
public function from_id(){ public function from_id() {
return $this->r->from_id; return $this->r->from_id;
} }
public function to_id(){ public function to_id() {
return $this->r->to_id; return $this->r->to_id;
} }
public static function find_outgoing($item_id){ public static function find_outgoing($item_id) {
global $DB; global $DB;
$list = []; $list = [];
$conn_out = $DB->get_records(self::TABLE,['from_id' => $item_id]); $conn_out = $DB->get_records(self::TABLE, ['from_id' => $item_id]);
foreach($conn_out as $c) { foreach ($conn_out as $c) {
$list[] = new self($c); $list[] = new self($c);
} }
return $list; return $list;
} }
public static function find_incoming($item_id){ public static function find_incoming($item_id) {
global $DB; global $DB;
$list = []; $list = [];
$conn_in = $DB->get_records(self::TABLE,['to_id' => $item_id]); $conn_in = $DB->get_records(self::TABLE, ['to_id' => $item_id]);
foreach($conn_in as $c) { foreach ($conn_in as $c) {
$list[] = new self($c); $list[] = new self($c);
} }
return $list; return $list;
} }
public static function connect($from_id,$to_id) public static function connect($from_id, $to_id) {
{
global $DB; global $DB;
//check if link already exists //check if link already exists.
if(!$DB->record_exists(self::TABLE, ['from_id' => $from_id, 'to_id' => $to_id])) if (!$DB->record_exists(self::TABLE, ['from_id' => $from_id, 'to_id' => $to_id])) {
{
$id = $DB->insert_record(self::TABLE, [ $id = $DB->insert_record(self::TABLE, [
'from_id' => $from_id, 'from_id' => $from_id,
'to_id' => $to_id, 'to_id' => $to_id,
]); ]);
return new self($DB->get_record(self::TABLE,['id' => $id])); return new self($DB->get_record(self::TABLE, ['id' => $id]));
} else { } else {
return new self($DB->get_record(self::TABLE,['from_id' => $from_id, 'to_id' => $to_id])); return new self($DB->get_record(self::TABLE, ['from_id' => $from_id, 'to_id' => $to_id]));
} }
} }
public static function disconnect($from_id,$to_id) public static function disconnect($from_id, $to_id) {
{
global $DB; global $DB;
if($DB->record_exists(self::TABLE, ['from_id' => $from_id, 'to_id' => $to_id])) if ($DB->record_exists(self::TABLE, ['from_id' => $from_id, 'to_id' => $to_id])) {
{
$DB->delete_records(self::TABLE, [ $DB->delete_records(self::TABLE, [
'from_id' => $from_id, 'from_id' => $from_id,
'to_id' => $to_id, 'to_id' => $to_id,
]); ]);
return success::success('Items Disconnected'); return success::success('Items Disconnected');
} else { } else {
return success::success('Connection does not exist'); return success::success('Connection does not exist');
} }
} }
public static function clear($id) { public static function clear($id) {
global $DB; global $DB;

View File

@ -1,4 +1,25 @@
<?php <?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/>.
/**
*
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace local_treestudyplan; namespace local_treestudyplan;
require_once($CFG->libdir.'/externallib.php'); require_once($CFG->libdir.'/externallib.php');
@ -21,10 +42,10 @@ class studyline {
]; ];
public const TABLE = "local_treestudyplan_line"; public const TABLE = "local_treestudyplan_line";
private static $STUDYLINE_CACHE = []; private static $STUDYLINE_CACHE = [];
private $r; // Holds database record private $r; // Holds database record.
private $id; private $id;
private $page; private $page;
private $studyplan; private $studyplan;
@ -42,54 +63,54 @@ class studyline {
} }
public static function findById($id): self { public static function findById($id): self {
if(!array_key_exists($id,self::$STUDYLINE_CACHE)){ if (!array_key_exists($id, self::$STUDYLINE_CACHE)) {
self::$STUDYLINE_CACHE[$id] = new self($id); self::$STUDYLINE_CACHE[$id] = new self($id);
} }
return self::$STUDYLINE_CACHE[$id]; return self::$STUDYLINE_CACHE[$id];
} }
private function __construct($id) { private function __construct($id) {
global $DB; global $DB;
$this->id = $id; $this->id = $id;
$this->r = $DB->get_record(self::TABLE,['id' => $id]); $this->r = $DB->get_record(self::TABLE, ['id' => $id]);
$this->page = studyplanpage::findById($this->r->page_id); $this->page = studyplanpage::findById($this->r->page_id);
$this->studyplan = $this->page->studyplan(); $this->studyplan = $this->page->studyplan();
} }
public function id(){ public function id() {
return $this->id; return $this->id;
} }
public function name(){ public function name() {
return $this->r->name; return $this->r->name;
} }
public function shortname(){ public function shortname() {
return $this->r->shortname; return $this->r->shortname;
} }
public static function editor_structure($value=VALUE_REQUIRED){ public static function editor_structure($value=VALUE_REQUIRED) {
return new \external_single_structure([ return new \external_single_structure([
"id" => new \external_value(PARAM_INT, 'id of studyline'), "id" => new \external_value(PARAM_INT, 'id of studyline'),
"name" => new \external_value(PARAM_TEXT, 'shortname of studyline'), "name" => new \external_value(PARAM_TEXT, 'shortname of studyline'),
"shortname"=> new \external_value(PARAM_TEXT, 'idnumber of studyline'), "shortname"=> new \external_value(PARAM_TEXT, 'idnumber of studyline'),
"color"=> new \external_value(PARAM_TEXT, 'description of studyline'), "color"=> new \external_value(PARAM_TEXT, 'description of studyline'),
"sequence" => new \external_value(PARAM_INT, 'order of studyline'), "sequence" => new \external_value(PARAM_INT, 'order of studyline'),
"slots" => new \external_multiple_structure( "slots" => new \external_multiple_structure(
new \external_single_structure([ new \external_single_structure([
self::SLOTSET_COMPETENCY => new \external_multiple_structure(studyitem::editor_structure(),'competency items',VALUE_OPTIONAL), 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'), self::SLOTSET_FILTER => new \external_multiple_structure(studyitem::editor_structure(), 'filter items'),
]) ])
) )
]); ]);
} }
public function editor_model(){ public function editor_model() {
return $this->generate_model("editor"); return $this->generate_model("editor");
} }
protected function generate_model($mode){ protected function generate_model($mode) {
// Mode parameter is used to geep this function for both editor model and export model // 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) // (Export model results in fewer parameters on children, but is otherwise basically the same as this function).
global $DB; global $DB;
$model = [ $model = [
@ -100,27 +121,27 @@ class studyline {
'sequence' => $this->r->sequence, 'sequence' => $this->r->sequence,
'slots' => [], 'slots' => [],
]; ];
if($mode == "export"){ if ($mode == "export") {
// Id and sequence are not used in export model // Id and sequence are not used in export model.
unset($model["id"]); unset($model["id"]);
unset($model["sequence"]); unset($model["sequence"]);
} }
// TODO: Make this a little nicer // TODO: Make this a little nicer.
// Get the number of slots // Get the number of slots.
// As a safety data integrity measure, if there are any items in a higher slot than currently allowed, // 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 // 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. // 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]); $max_slot = $DB->get_field_select(studyitem::TABLE, "MAX(slot)", "line_id = :lineid", ['lineid' => $this->id]);
$num_slots = max($this->page->periods(),$max_slot +1); $num_slots = max($this->page->periods(), $max_slot +1);
// Create the required amount of slots // Create the required amount of slots.
for($i=0; $i < $num_slots+1; $i++){ for($i=0; $i < $num_slots+1; $i++) {
if($mode == "export") { if ($mode == "export") {
// Export mode does not separate between filter or competency type, since that is determined automatically // Export mode does not separate between filter or competency type, since that is determined automatically.
$slots = []; $slots = [];
} else { } else {
if($i > 0) { if ($i > 0) {
$slots = [self::SLOTSET_COMPETENCY => [], self::SLOTSET_FILTER => []]; $slots = [self::SLOTSET_COMPETENCY => [], self::SLOTSET_FILTER => []];
} else { } else {
$slots = [self::SLOTSET_FILTER => []]; $slots = [self::SLOTSET_FILTER => []];
@ -128,25 +149,24 @@ class studyline {
} }
$model['slots'][$i] = $slots; $model['slots'][$i] = $slots;
} }
$children = studyitem::find_studyline_children($this); $children = studyitem::find_studyline_children($this);
foreach($children as $c) foreach ($children as $c) {
{ if ($mode == "export") {
if($mode == "export") {
$model['slots'][$c->slot()][] = $c->export_model(); $model['slots'][$c->slot()][] = $c->export_model();
} else { } else {
$slotset = null; $slotset = null;
if($c->slot() > 0) { if ($c->slot() > 0) {
if(in_array($c->type(),self::COMPETENCY_TYPES)) { if (in_array($c->type(), self::COMPETENCY_TYPES)) {
$slotset = self::SLOTSET_COMPETENCY; $slotset = self::SLOTSET_COMPETENCY;
} else if(in_array($c->type(),self::FILTER_TYPES)) { } else if (in_array($c->type(), self::FILTER_TYPES)) {
$slotset = self::SLOTSET_FILTER; $slotset = self::SLOTSET_FILTER;
} }
} }
else if(in_array($c->type(),self::FILTER0_TYPES)) { else if (in_array($c->type(), self::FILTER0_TYPES)) {
$slotset = self::SLOTSET_FILTER; $slotset = self::SLOTSET_FILTER;
} }
if(isset($slotset)) { if (isset($slotset)) {
$model['slots'][$c->slot()][$slotset][] = $c->editor_model(); $model['slots'][$c->slot()][$slotset][] = $c->editor_model();
} }
} }
@ -154,19 +174,19 @@ class studyline {
return $model; return $model;
} }
public static function add($fields){ public static function add($fields) {
global $DB; global $DB;
if(!isset($fields['page_id'])){ if (!isset($fields['page_id'])) {
throw new \InvalidArgumentException("parameter 'page_id' missing"); throw new \InvalidArgumentException("parameter 'page_id' missing");
} }
$page_id = $fields['page_id']; $page_id = $fields['page_id'];
$sqmax = $DB->get_field_select(self::TABLE,"MAX(sequence)","page_id = :page_id",['page_id' => $page_id]); $sqmax = $DB->get_field_select(self::TABLE, "MAX(sequence)", "page_id = :page_id", ['page_id' => $page_id]);
$addable = ['page_id','name','shortname','color']; $addable = ['page_id', 'name', 'shortname', 'color'];
$info = ['sequence' => $sqmax+1]; $info = ['sequence' => $sqmax+1];
foreach($addable as $f){ foreach ($addable as $f) {
if(array_key_exists($f,$fields)){ if (array_key_exists($f, $fields)) {
$info[$f] = $fields[$f]; $info[$f] = $fields[$f];
} }
} }
@ -174,32 +194,32 @@ class studyline {
return self::findById($id); return self::findById($id);
} }
public function edit($fields){ public function edit($fields) {
global $DB; global $DB;
$editable = ['name','shortname','color']; $editable = ['name', 'shortname', 'color'];
$info = ['id' => $this->id,]; $info = ['id' => $this->id, ];
foreach($editable as $f){ foreach ($editable as $f) {
if(array_key_exists($f,$fields)){ if (array_key_exists($f, $fields)) {
$info[$f] = $fields[$f]; $info[$f] = $fields[$f];
} }
} }
$DB->update_record(self::TABLE, $info); $DB->update_record(self::TABLE, $info);
//reload record after edit //reload record after edit.
$this->r = $DB->get_record(self::TABLE,['id' => $this->id],"*",MUST_EXIST); $this->r = $DB->get_record(self::TABLE, ['id' => $this->id], "*", MUST_EXIST);
return $this; return $this;
} }
public function delete($force = false){ public function delete($force = false) {
global $DB; global $DB;
if($force){ if ($force) {
$children = studyitem::find_studyline_children($this); $children = studyitem::find_studyline_children($this);
foreach($children as $c){ foreach ($children as $c) {
$c->delete($force); $c->delete($force);
} }
} }
// check if this item has study items in it // check if this item has study items in it.
if($DB->count_records(studyitem::TABLE,['line_id' => $this->id]) > 0){ if ($DB->count_records(studyitem::TABLE, ['line_id' => $this->id]) > 0) {
return success::fail('cannot delete studyline with items'); return success::fail('cannot delete studyline with items');
} }
else else
@ -209,12 +229,10 @@ class studyline {
} }
} }
public static function reorder($resequence) public static function reorder($resequence) {
{
global $DB; global $DB;
foreach($resequence as $sq) foreach ($resequence as $sq) {
{
$DB->update_record(self::TABLE, [ $DB->update_record(self::TABLE, [
'id' => $sq['id'], 'id' => $sq['id'],
'sequence' => $sq['sequence'], 'sequence' => $sq['sequence'],
@ -224,36 +242,35 @@ class studyline {
return success::success(); return success::success();
} }
public static function find_page_children(studyplanpage $page) public static function find_page_children(studyplanpage $page) {
{
global $DB; global $DB;
$list = []; $list = [];
$ids = $DB->get_fieldset_select(self::TABLE,"id","page_id = :page_id ORDER BY sequence", $ids = $DB->get_fieldset_select(self::TABLE, "id", "page_id = :page_id ORDER BY sequence",
['page_id' => $page->id()]); ['page_id' => $page->id()]);
foreach($ids as $id) { foreach ($ids as $id) {
$list[] = self::findById($id); $list[] = self::findById($id);
} }
return $list; return $list;
} }
public static function user_structure($value=VALUE_REQUIRED){ public static function user_structure($value=VALUE_REQUIRED) {
return new \external_single_structure([ return new \external_single_structure([
"id" => new \external_value(PARAM_INT, 'id of studyline'), "id" => new \external_value(PARAM_INT, 'id of studyline'),
"name" => new \external_value(PARAM_TEXT, 'shortname of studyline'), "name" => new \external_value(PARAM_TEXT, 'shortname of studyline'),
"shortname"=> new \external_value(PARAM_TEXT, 'idnumber of studyline'), "shortname"=> new \external_value(PARAM_TEXT, 'idnumber of studyline'),
"color"=> new \external_value(PARAM_TEXT, 'description of studyline'), "color"=> new \external_value(PARAM_TEXT, 'description of studyline'),
"sequence" => new \external_value(PARAM_INT, 'order of studyline'), "sequence" => new \external_value(PARAM_INT, 'order of studyline'),
"slots" => new \external_multiple_structure( "slots" => new \external_multiple_structure(
new \external_single_structure([ new \external_single_structure([
self::SLOTSET_COMPETENCY => new \external_multiple_structure(studyitem::user_structure(),'competency items',VALUE_OPTIONAL), 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'), self::SLOTSET_FILTER => new \external_multiple_structure(studyitem::user_structure(), 'filter items'),
]) ])
) )
],'Studyline with user info',$value); ], 'Studyline with user info', $value);
} }
public function user_model($userid){ public function user_model($userid) {
// TODO: Integrate this function into generate_model() for ease of maintenance // TODO: Integrate this function into generate_model() for ease of maintenance.
global $DB; global $DB;
@ -266,16 +283,16 @@ class studyline {
'slots' => [], 'slots' => [],
]; ];
// Get the number of slots // Get the number of slots.
// As a safety data integrity measure, if there are any items in a higher slot than currently allowed, // 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 // 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. // 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]); $max_slot = $DB->get_field_select(studyitem::TABLE, "MAX(slot)", "line_id = :lineid", ['lineid' => $this->id]);
$num_slots = max($this->page->periods(),$max_slot +1); $num_slots = max($this->page->periods(), $max_slot +1);
// Create the required amount of slots // Create the required amount of slots.
for($i=0; $i < $num_slots+1; $i++){ for($i=0; $i < $num_slots+1; $i++) {
if($i > 0) { if ($i > 0) {
$slots = [self::SLOTSET_COMPETENCY => [], self::SLOTSET_FILTER => []]; $slots = [self::SLOTSET_COMPETENCY => [], self::SLOTSET_FILTER => []];
} else { } else {
$slots = [self::SLOTSET_FILTER => []]; $slots = [self::SLOTSET_FILTER => []];
@ -284,21 +301,20 @@ class studyline {
} }
$children = studyitem::find_studyline_children($this); $children = studyitem::find_studyline_children($this);
foreach($children as $c) foreach ($children as $c) {
{ if ($c->isValid()) {
if($c->isValid()){
$slotset = null; $slotset = null;
if($c->slot() > 0) { if ($c->slot() > 0) {
if(in_array($c->type(),self::COMPETENCY_TYPES)) { if (in_array($c->type(), self::COMPETENCY_TYPES)) {
$slotset = self::SLOTSET_COMPETENCY; $slotset = self::SLOTSET_COMPETENCY;
} else if(in_array($c->type(),self::FILTER_TYPES)) { } else if (in_array($c->type(), self::FILTER_TYPES)) {
$slotset = self::SLOTSET_FILTER; $slotset = self::SLOTSET_FILTER;
} }
} }
else if(in_array($c->type(),self::FILTER0_TYPES)) { else if (in_array($c->type(), self::FILTER0_TYPES)) {
$slotset = self::SLOTSET_FILTER; $slotset = self::SLOTSET_FILTER;
} }
if(isset($slotset)) { if (isset($slotset)) {
$model['slots'][$c->slot()][$slotset][] = $c->user_model($userid); $model['slots'][$c->slot()][$slotset][] = $c->user_model($userid);
} }
} }
@ -309,45 +325,41 @@ class studyline {
} }
public function duplicate($new_studyplan,&$translation){ public function duplicate($new_studyplan, &$translation) {
global $DB; global $DB;
// clone the database fields // clone the database fields.
$fields = clone $this->r; $fields = clone $this->r;
// set new studyplan id // set new studyplan id.
unset($fields->id); unset($fields->id);
$fields->studyplan_id = $new_studyplan->id(); $fields->studyplan_id = $new_studyplan->id();
// create new record with the new data // create new record with the new data.
$id = $DB->insert_record(self::TABLE, (array)$fields); $id = $DB->insert_record(self::TABLE, (array)$fields);
$new = self::findById($id); $new = self::findById($id);
// Next copy all the study items for this studyline // Next copy all the study items for this studyline.
// and record the original and copy id's in the $translation array // and record the original and copy id's in the $translation array.
// so the calling function can connect the new studyitems as required // so the calling function can connect the new studyitems as required.
$children = studyitem::find_studyline_children($this); $children = studyitem::find_studyline_children($this);
$translation = []; $translation = [];
foreach($children as $c) foreach ($children as $c) {
{
$newchild = $c->duplicate($new); $newchild = $c->duplicate($new);
$translation[$c->id()] = $newchild->id(); $translation[$c->id()] = $newchild->id();
} }
return $new; return $new;
} }
public function export_model() public function export_model() {
{
return $this->generate_model("export"); return $this->generate_model("export");
} }
public function import_studyitems($model,&$itemtranslation,&$connections){ public function import_studyitems($model, &$itemtranslation, &$connections) {
global $DB; global $DB;
foreach($model as $slot=>$slotmodel) foreach ($model as $slot=>$slotmodel) {
{
$courselayer = 0; $courselayer = 0;
$filterlayer = 0; $filterlayer = 0;
foreach($slotmodel as $itemmodel) foreach ($slotmodel as $itemmodel) {
{ if ($itemmodel["type"] == "course") {
if($itemmodel["type"] == "course"){
$itemmodel["layer"] = $courselayer; $itemmodel["layer"] = $courselayer;
$courselayer++; $courselayer++;
}else { }else {
@ -359,14 +371,14 @@ class studyline {
$itemmodel["line_id"] = $this->id(); $itemmodel["line_id"] = $this->id();
$item = studyitem::import_item($itemmodel); $item = studyitem::import_item($itemmodel);
if(!empty($item)){ if (!empty($item)) {
$itemtranslation[$itemmodel["id"]] = $item->id(); $itemtranslation[$itemmodel["id"]] = $item->id();
if(count($itemmodel["connections"]) > 0){ if (count($itemmodel["connections"]) > 0) {
if(! isset($connections[$item->id()]) || ! is_array($connections[$item->id()])){ if (! isset($connections[$item->id()]) || ! is_array($connections[$item->id()])) {
$connections[$item->id()] = []; $connections[$item->id()] = [];
} }
foreach($itemmodel["connections"] as $to_id){ foreach ($itemmodel["connections"] as $to_id) {
$connections[$item->id()][] = $to_id; $connections[$item->id()][] = $to_id;
} }
} }

View File

@ -1,4 +1,25 @@
<?php <?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/>.
/**
*
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace local_treestudyplan; namespace local_treestudyplan;
require_once($CFG->libdir.'/externallib.php'); require_once($CFG->libdir.'/externallib.php');
@ -10,50 +31,50 @@ class studyplan {
private static $STUDYPLAN_CACHE = []; private static $STUDYPLAN_CACHE = [];
private $r; // Holds database record private $r; // Holds database record.
private $id; private $id;
private $aggregator; private $aggregator;
private $context = null; // Hold context object once retrieved private $context = null; // Hold context object once retrieved.
private $linked_userids = null; // cache lookup of linked users (saves queries) private $linked_userids = null; // cache lookup of linked users (saves queries).
private $page_cache = null; private $page_cache = null;
public function aggregator(){ public function aggregator() {
return $this->aggregator; return $this->aggregator;
} }
// Cache constructors to avoid multiple creation events in one session. // Cache constructors to avoid multiple creation events in one session.
public static function findById($id): self { public static function findById($id): self {
if(!array_key_exists($id,self::$STUDYPLAN_CACHE)){ if (!array_key_exists($id, self::$STUDYPLAN_CACHE)) {
self::$STUDYPLAN_CACHE[$id] = new self($id); self::$STUDYPLAN_CACHE[$id] = new self($id);
} }
return self::$STUDYPLAN_CACHE[$id]; return self::$STUDYPLAN_CACHE[$id];
} }
private function __construct($id) { private function __construct($id) {
global $DB; global $DB;
$this->id = $id; $this->id = $id;
$this->r = $DB->get_record(self::TABLE,['id' => $id]); $this->r = $DB->get_record(self::TABLE, ['id' => $id]);
$this->aggregator = aggregator::createOrDefault($this->r->aggregation, $this->r->aggregation_config); $this->aggregator = aggregator::createOrDefault($this->r->aggregation, $this->r->aggregation_config);
} }
public function id(){ public function id() {
return $this->id; return $this->id;
} }
public function shortname(){ public function shortname() {
return $this->r->shortname; return $this->r->shortname;
} }
public function name(){ public function name() {
return $this->r->name; return $this->r->name;
} }
public function pages(){ public function pages() {
// cached version of find_studyplan_children. // cached version of find_studyplan_children.
// (may be premature optimization, since // (may be premature optimization, since .
// find_studyplan_children also does some caching) // find_studyplan_children also does some caching).
if(empty($this->page_cache)){ if (empty($this->page_cache)) {
$this->page_cache = studyplanpage::find_studyplan_children($this); $this->page_cache = studyplanpage::find_studyplan_children($this);
} }
return $this->page_cache; return $this->page_cache;
@ -63,18 +84,18 @@ class studyplan {
* Return the context this studyplan is associated to * Return the context this studyplan is associated to
*/ */
public function context(): \context{ public function context(): \context{
if(!isset($this->context)){ if (!isset($this->context)) {
try{ try{
$this->context = contextinfo::by_id($this->r->context_id)->context; $this->context = contextinfo::by_id($this->r->context_id)->context;
} }
catch(\dml_missing_record_exception $x){ catch(\dml_missing_record_exception $x) {
throw new \InvalidArgumentException("Context {$this->r->context_id} not available"); // Just throw it up again. catch is included here to make sure we know it throws this exception throw new \InvalidArgumentException("Context {$this->r->context_id} not available"); // Just throw it up again. catch is included here to make sure we know it throws this exception.
} }
} }
return $this->context; return $this->context;
} }
public static function simple_structure($value=VALUE_REQUIRED){ public static function simple_structure($value=VALUE_REQUIRED) {
return new \external_single_structure([ return new \external_single_structure([
"id" => new \external_value(PARAM_INT, 'id of studyplan'), "id" => new \external_value(PARAM_INT, 'id of studyplan'),
"name" => new \external_value(PARAM_TEXT, 'name of studyplan'), "name" => new \external_value(PARAM_TEXT, 'name of studyplan'),
@ -85,13 +106,13 @@ class studyplan {
"aggregation" => new \external_value(PARAM_TEXT, 'selected aggregator'), "aggregation" => new \external_value(PARAM_TEXT, 'selected aggregator'),
"aggregation_config" => new \external_value(PARAM_TEXT, 'config string for aggregator'), "aggregation_config" => new \external_value(PARAM_TEXT, 'config string for aggregator'),
"aggregation_info" => aggregator::basic_structure(), "aggregation_info" => aggregator::basic_structure(),
"pages" => new \external_multiple_structure(studyplanpage::simple_structure(),'pages'), "pages" => new \external_multiple_structure(studyplanpage::simple_structure(), 'pages'),
],'Basic studyplan info',$value); ], 'Basic studyplan info', $value);
} }
public function simple_model(){ public function simple_model() {
$pages = []; $pages = [];
foreach($this->pages() as $p){ foreach ($this->pages() as $p) {
$pages[] = $p->simple_model(); $pages[] = $p->simple_model();
} }
@ -109,7 +130,7 @@ class studyplan {
]; ];
} }
public static function editor_structure($value=VALUE_REQUIRED){ public static function editor_structure($value=VALUE_REQUIRED) {
return new \external_single_structure([ return new \external_single_structure([
"id" => new \external_value(PARAM_INT, 'id of studyplan'), "id" => new \external_value(PARAM_INT, 'id of studyplan'),
"name" => new \external_value(PARAM_TEXT, 'name of studyplan'), "name" => new \external_value(PARAM_TEXT, 'name of studyplan'),
@ -127,12 +148,12 @@ class studyplan {
"id" => new \external_value(PARAM_INT, 'id of scale'), "id" => new \external_value(PARAM_INT, 'id of scale'),
"name" => new \external_value(PARAM_TEXT, 'name of scale'), "name" => new \external_value(PARAM_TEXT, 'name of scale'),
])), ])),
],"Scale forcing on stuff", VALUE_OPTIONAL), ], "Scale forcing on stuff", VALUE_OPTIONAL),
],"Advanced features available", VALUE_OPTIONAL), ], "Advanced features available", VALUE_OPTIONAL),
],'Studyplan full structure',$value); ], 'Studyplan full structure', $value);
} }
public function editor_model(){ public function editor_model() {
global $DB; global $DB;
$model = [ $model = [
@ -144,25 +165,24 @@ class studyplan {
'context_id' => $this->context()->id, 'context_id' => $this->context()->id,
"aggregation" => $this->r->aggregation, "aggregation" => $this->r->aggregation,
"aggregation_config" => $this->aggregator->config_string(), "aggregation_config" => $this->aggregator->config_string(),
'aggregation_info' => $this->aggregator->basic_model(), 'aggregation_info' => $this->aggregator->basic_model(),
'pages' => [], 'pages' => [],
]; ];
foreach($this->pages() as $p) foreach ($this->pages() as $p) {
{
$model['pages'][] = $p->editor_model(); $model['pages'][] = $p->editor_model();
} }
if(has_capability('local/treestudyplan:forcescales', \context_system::instance())){ if (has_capability('local/treestudyplan:forcescales', \context_system::instance())) {
if(!array_key_exists('advanced',$model)){ if (!array_key_exists('advanced', $model)) {
// Create advanced node if it does not exist // Create advanced node if it does not exist.
$model['advanced'] = []; $model['advanced'] = [];
} }
// get a list of available scales // get a list of available scales.
$scales = array_map( function($scale){ $scales = array_map( function($scale) {
return [ "id" => $scale->id, "name" => $scale->name,]; return [ "id" => $scale->id, "name" => $scale->name, ];
}, \grade_scale::fetch_all(array('courseid'=>0)) ) ; }, \grade_scale::fetch_all(array('courseid'=>0)) ) ;
$model['advanced']['force_scales'] = [ $model['advanced']['force_scales'] = [
@ -174,32 +194,32 @@ class studyplan {
return $model; return $model;
} }
public static function add($fields,$bare=false){ public static function add($fields, $bare=false) {
global $CFG, $DB; global $CFG, $DB;
$addable = ['name','shortname','description','idnumber','context_id','aggregation','aggregation_config']; $addable = ['name', 'shortname', 'description', 'idnumber', 'context_id', 'aggregation', 'aggregation_config'];
$info = ['enddate' => null ]; $info = ['enddate' => null ];
foreach($addable as $f){ foreach ($addable as $f) {
if(array_key_exists($f,$fields)){ if (array_key_exists($f, $fields)) {
$info[$f] = $fields[$f]; $info[$f] = $fields[$f];
} }
} }
$id = $DB->insert_record(self::TABLE, $info); $id = $DB->insert_record(self::TABLE, $info);
$plan = self::findById($id); // make sure the new studyplan is immediately cached $plan = self::findById($id); // make sure the new studyplan is immediately cached.
// Start temporary skräpp code // Start temporary skräpp code.
// Add a single page and copy the names.This keeps the data sane until the upgrade to // Add a single page and copy the names.This keeps the data sane until the upgrade to .
// real page management is done // real page management is done.
// On import, adding an empty page messes things up for now, so we have an option to skip this.... // On import, adding an empty page messes things up for now, so we have an option to skip this....
// TODO: Remove this when proper page management is implemented // TODO: Remove this when proper page management is implemented.
if(!$bare){ if (!$bare) {
$pageaddable = ['name','shortname','description','periods','startdate','enddate']; $pageaddable = ['name', 'shortname', 'description', 'periods', 'startdate', 'enddate'];
$pageinfo = ['studyplan_id' => $id]; $pageinfo = ['studyplan_id' => $id];
foreach($pageaddable as $f){ foreach ($pageaddable as $f) {
if(array_key_exists($f,$fields)){ if (array_key_exists($f, $fields)) {
if($f == "name"){ if ($f == "name") {
$pageinfo["fullname"] = $fields[$f]; $pageinfo["fullname"] = $fields[$f];
} else { } else {
$pageinfo[$f] = $fields[$f]; $pageinfo[$f] = $fields[$f];
@ -210,41 +230,41 @@ class studyplan {
$page = studyplanpage::add($pageinfo); $page = studyplanpage::add($pageinfo);
$plan->page_cache = [$page]; $plan->page_cache = [$page];
} }
// End temporary skräpp code // End temporary skräpp code.
return $plan; return $plan;
} }
public function edit($fields){ public function edit($fields) {
global $DB; global $DB;
$editable = ['name','shortname','description','idnumber','context_id','aggregation','aggregation_config']; $editable = ['name', 'shortname', 'description', 'idnumber', 'context_id', 'aggregation', 'aggregation_config'];
$info = ['id' => $this->id,]; $info = ['id' => $this->id, ];
foreach($editable as $f){ foreach ($editable as $f) {
if(array_key_exists($f,$fields)){ if (array_key_exists($f, $fields)) {
$info[$f] = $fields[$f]; $info[$f] = $fields[$f];
} }
} }
$DB->update_record(self::TABLE, $info); $DB->update_record(self::TABLE, $info);
//reload record after edit //reload record after edit.
$this->r = $DB->get_record(self::TABLE,['id' => $this->id],"*",MUST_EXIST); $this->r = $DB->get_record(self::TABLE, ['id' => $this->id], "*", MUST_EXIST);
//reload the context...
$this->context = null;
$this->context();
// reload aggregator
$this->aggregator = aggregator::createOrDefault($this->r->aggregation, $this->r->aggregation_config);
// Start temporary skräpp code //reload the context...
// TODO: Until proper page editing is implemented, copy data from studyplan to it's first page $this->context = null;
$this->context();
// reload aggregator.
$this->aggregator = aggregator::createOrDefault($this->r->aggregation, $this->r->aggregation_config);
// Start temporary skräpp code.
// TODO: Until proper page editing is implemented, copy data from studyplan to it's first page.
// This keeps the data sane until the upgrade is done. // This keeps the data sane until the upgrade is done.
if(count($this->pages()) == 1){ if (count($this->pages()) == 1) {
// update the info to the page as well // update the info to the page as well.
$page = $this->pages()[0]; $page = $this->pages()[0];
$pageeditable = ['name','shortname','description','periods','startdate','enddate']; $pageeditable = ['name', 'shortname', 'description', 'periods', 'startdate', 'enddate'];
$pageinfo = []; $pageinfo = [];
foreach($pageeditable as $f){ foreach ($pageeditable as $f) {
if(array_key_exists($f,$fields)){ if (array_key_exists($f, $fields)) {
if($f == "name"){ if ($f == "name") {
$pageinfo["fullname"] = $fields[$f]; $pageinfo["fullname"] = $fields[$f];
}else { }else {
$pageinfo[$f] = $fields[$f]; $pageinfo[$f] = $fields[$f];
@ -253,22 +273,22 @@ class studyplan {
} }
$page->edit($pageinfo); $page->edit($pageinfo);
} }
// End temporary skräpp code // End temporary skräpp code.
return $this; return $this;
} }
public function delete($force=false){ public function delete($force=false) {
global $DB; global $DB;
if($force){ if ($force) {
$children = studyplanpage::find_studyplan_children($this); $children = studyplanpage::find_studyplan_children($this);
foreach($children as $c){ foreach ($children as $c) {
$c->delete($force); $c->delete($force);
} }
} }
if($DB->count_records('local_treestudyplan_page',['studyplan_id' => $this->id]) > 0){ if ($DB->count_records('local_treestudyplan_page', ['studyplan_id' => $this->id]) > 0) {
return success::fail('cannot delete studyplan that still has pages'); return success::fail('cannot delete studyplan that still has pages');
} }
else else
@ -278,25 +298,24 @@ class studyplan {
} }
} }
public static function find_all($contextid=-1){ public static function find_all($contextid=-1) {
global $DB, $USER; global $DB, $USER;
$list = []; $list = [];
if($contextid <= 0){ if ($contextid <= 0) {
$ids = $DB->get_fieldset_select(self::TABLE,"id",""); $ids = $DB->get_fieldset_select(self::TABLE, "id", "");
} }
else{ else{
if($contextid == 1){ if ($contextid == 1) {
$contextid = 1; $contextid = 1;
$where = "context_id <= :contextid OR context_id IS NULL"; $where = "context_id <= :contextid OR context_id IS NULL";
} else { } else {
$where = "context_id = :contextid"; $where = "context_id = :contextid";
} }
$ids = $DB->get_fieldset_select(self::TABLE,"id",$where,["contextid" => $contextid]); $ids = $DB->get_fieldset_select(self::TABLE, "id", $where, ["contextid" => $contextid]);
} }
foreach($ids as $id) foreach ($ids as $id) {
{
$list[] = studyplan::findById($id); $list[] = studyplan::findById($id);
} }
return $list; return $list;
@ -307,38 +326,36 @@ class studyplan {
$list = []; $list = [];
$where = "shortname = :shortname AND context_id = :contextid"; $where = "shortname = :shortname AND context_id = :contextid";
if($contextid == 0){ if ($contextid == 0) {
$where .= "OR context_id IS NULL"; $where .= "OR context_id IS NULL";
} }
$ids = $DB->get_fieldset_select(self::TABLE,"id",$where,["shortname"=>$shortname, "contextid" => $contextid]); $ids = $DB->get_fieldset_select(self::TABLE, "id", $where, ["shortname"=>$shortname, "contextid" => $contextid]);
foreach($ids as $id) foreach ($ids as $id) {
{
$list[] = studyplan::findById($id); $list[] = studyplan::findById($id);
} }
return $list; return $list;
} }
public static function find_for_user($userid) public static function find_for_user($userid) {
{
global $DB; global $DB;
$sql = "SELECT s.id FROM {local_treestudyplan} s $sql = "SELECT s.id FROM {local_treestudyplan} s
INNER JOIN {local_treestudyplan_cohort} j ON j.studyplan_id = s.id INNER JOIN {local_treestudyplan_cohort} j ON j.studyplan_id = s.id
INNER JOIN {cohort_members} cm ON j.cohort_id = cm.cohortid INNER JOIN {cohort_members} cm ON j.cohort_id = cm.cohortid
WHERE cm.userid = :userid"; WHERE cm.userid = :userid";
$cohort_plan_ids = $DB->get_fieldset_sql($sql, ['userid' => $userid]); $cohort_plan_ids = $DB->get_fieldset_sql($sql, ['userid' => $userid]);
$sql = "SELECT s.id FROM {local_treestudyplan} s $sql = "SELECT s.id FROM {local_treestudyplan} s
INNER JOIN {local_treestudyplan_user} j ON j.studyplan_id = s.id INNER JOIN {local_treestudyplan_user} j ON j.studyplan_id = s.id
WHERE j.user_id = :userid"; WHERE j.user_id = :userid";
$user_plan_ids = $DB->get_fieldset_sql($sql, ['userid' => $userid]); $user_plan_ids = $DB->get_fieldset_sql($sql, ['userid' => $userid]);
$plans = []; $plans = [];
foreach($cohort_plan_ids as $id) { foreach ($cohort_plan_ids as $id) {
$plans[$id] = self::findById($id); $plans[$id] = self::findById($id);
} }
foreach($user_plan_ids as $id) { foreach ($user_plan_ids as $id) {
if(!array_key_exists($id,$plans)){ if (!array_key_exists($id, $plans)) {
$plans[$id] = self::findById($id); $plans[$id] = self::findById($id);
} }
} }
@ -347,17 +364,16 @@ class studyplan {
} }
static public function exist_for_user($userid) static public function exist_for_user($userid) {
{
global $DB; global $DB;
$count = 0; $count = 0;
$sql = "SELECT s.* FROM {local_treestudyplan} s $sql = "SELECT s.* FROM {local_treestudyplan} s
INNER JOIN {local_treestudyplan_cohort} j ON j.studyplan_id = s.id INNER JOIN {local_treestudyplan_cohort} j ON j.studyplan_id = s.id
INNER JOIN {cohort_members} cm ON j.cohort_id = cm.cohortid INNER JOIN {cohort_members} cm ON j.cohort_id = cm.cohortid
WHERE cm.userid = :userid"; WHERE cm.userid = :userid";
$count += $DB->count_records_sql($sql, ['userid' => $userid]); $count += $DB->count_records_sql($sql, ['userid' => $userid]);
$sql = "SELECT s.* FROM {local_treestudyplan} s $sql = "SELECT s.* FROM {local_treestudyplan} s
INNER JOIN {local_treestudyplan_user} j ON j.studyplan_id = s.id INNER JOIN {local_treestudyplan_user} j ON j.studyplan_id = s.id
WHERE j.user_id = :userid"; WHERE j.user_id = :userid";
$count += $DB->count_records_sql($sql, ['userid' => $userid]); $count += $DB->count_records_sql($sql, ['userid' => $userid]);
@ -365,50 +381,50 @@ class studyplan {
return ($count > 0); return ($count > 0);
} }
/** /**
* Retrieve the users linked to this studyplan. * Retrieve the users linked to this studyplan.
* @return array of User objects * @return array of User objects
*/ */
public function find_linked_users(){ public function find_linked_users() {
global $DB; global $DB;
$users = []; $users = [];
$uids = $this->find_linked_userids(); $uids = $this->find_linked_userids();
foreach($uids as $uid){ foreach ($uids as $uid) {
$users[] = $DB->get_record("user",["id"=>$uid]); $users[] = $DB->get_record("user", ["id"=>$uid]);
} }
return $users; return $users;
} }
/** /**
* Retrieve the user id's of the users linked to this studyplan. * Retrieve the user id's of the users linked to this studyplan.
* @return array of int (User Id) * @return array of int (User Id)
*/ */
public function find_linked_userids(): array { public function find_linked_userids(): array {
global $DB; global $DB;
if($this->linked_userids === null){ if ($this->linked_userids === null) {
$uids = []; $uids = [];
// First get directly linked userids // First get directly linked userids.
$sql = "SELECT j.user_id FROM {local_treestudyplan_user} j $sql = "SELECT j.user_id FROM {local_treestudyplan_user} j
WHERE j.studyplan_id = :planid"; WHERE j.studyplan_id = :planid";
$ulist = $DB->get_fieldset_sql($sql, ['planid' => $this->id]); $ulist = $DB->get_fieldset_sql($sql, ['planid' => $this->id]);
$uids = array_merge($uids,$ulist); $uids = array_merge($uids, $ulist);
foreach($ulist as $uid){ foreach ($ulist as $uid) {
$users[] = $DB->get_record("user",["id"=>$uid]); $users[] = $DB->get_record("user", ["id"=>$uid]);
} }
// Next het users linked though cohort // Next het users linked though cohort.
$sql = "SELECT cm.userid FROM {local_treestudyplan_cohort} j $sql = "SELECT cm.userid FROM {local_treestudyplan_cohort} j
INNER JOIN {cohort_members} cm ON j.cohort_id = cm.cohortid INNER JOIN {cohort_members} cm ON j.cohort_id = cm.cohortid
WHERE j.studyplan_id = :planid"; WHERE j.studyplan_id = :planid";
$ulist = $DB->get_fieldset_sql($sql, ['planid' => $this->id]); $ulist = $DB->get_fieldset_sql($sql, ['planid' => $this->id]);
$uids = array_merge($uids,$ulist); $uids = array_merge($uids, $ulist);
$this->linked_userids = array_unique($uids); $this->linked_userids = array_unique($uids);
} }
@ -416,23 +432,23 @@ class studyplan {
} }
/** Check if this studyplan is linked to a particular user /** Check if this studyplan is linked to a particular user
* @param bool|stdClass $user The userid or user record of the user * @param bool|stdClass $user The userid or user record of the user
*/ */
public function has_linked_user($user){ public function has_linked_user($user) {
if(is_int($user)){ if (is_int($user)) {
$userid = $user; $userid = $user;
} else { } else {
$userid = $user->id; $userid = $user->id;
} }
$uids = $this->find_linked_userids(); $uids = $this->find_linked_userids();
if(in_array($userid,$uids)){ if (in_array($userid, $uids)) {
return true; return true;
} else { } else {
return false; return false;
} }
} }
public static function user_structure($value=VALUE_REQUIRED){ public static function user_structure($value=VALUE_REQUIRED) {
return new \external_single_structure([ return new \external_single_structure([
"id" => new \external_value(PARAM_INT, 'id of studyplan'), "id" => new \external_value(PARAM_INT, 'id of studyplan'),
"name" => new \external_value(PARAM_TEXT, 'name of studyplan'), "name" => new \external_value(PARAM_TEXT, 'name of studyplan'),
@ -441,11 +457,11 @@ class studyplan {
"idnumber"=> new \external_value(PARAM_TEXT, 'idnumber of curriculum'), "idnumber"=> new \external_value(PARAM_TEXT, 'idnumber of curriculum'),
"pages" => new \external_multiple_structure(studyplanpage::user_structure()), "pages" => new \external_multiple_structure(studyplanpage::user_structure()),
"aggregation_info" => aggregator::basic_structure(), "aggregation_info" => aggregator::basic_structure(),
],'Studyplan with user info',$value); ], 'Studyplan with user info', $value);
} }
public function user_model($userid){ public function user_model($userid) {
$model = [ $model = [
'id' => $this->r->id, 'id' => $this->r->id,
@ -457,59 +473,53 @@ class studyplan {
'aggregation_info' => $this->aggregator->basic_model(), 'aggregation_info' => $this->aggregator->basic_model(),
]; ];
foreach($this->pages() as $p) foreach ($this->pages() as $p) {
{
$model['pages'][] = $p->user_model($userid); $model['pages'][] = $p->user_model($userid);
} }
return $model; return $model;
} }
public static function duplicate_plan($plan_id,$name,$shortname) public static function duplicate_plan($plan_id, $name, $shortname) {
{
$ori = self::findById($plan_id); $ori = self::findById($plan_id);
$new = $ori->duplicate($name,$shortname); $new = $ori->duplicate($name, $shortname);
return $new->simple_model(); return $new->simple_model();
} }
public function duplicate($name,$shortname) public function duplicate($name, $shortname) {
{ // First duplicate the studyplan structure.
// First duplicate the studyplan structure
$newplan =studyplan::add([ $newplan =studyplan::add([
'name' => $name, 'name' => $name,
'shortname' => $shortname, 'shortname' => $shortname,
'description' => $this->r->description, 'description' => $this->r->description,
]); ]);
// next, copy the studylines
foreach($this->pages() as $p){ // next, copy the studylines.
foreach ($this->pages() as $p) {
$newchild = $p->duplicate($newplan); $newchild = $p->duplicate($newplan);
} }
return $newplan; return $newplan;
} }
public static function export_structure() public static function export_structure() {
{
return new \external_single_structure([ return new \external_single_structure([
"format" => new \external_value(PARAM_TEXT, 'format of studyplan export'), "format" => new \external_value(PARAM_TEXT, 'format of studyplan export'),
"content"=> new \external_value(PARAM_TEXT, 'exported studyplan content'), "content"=> new \external_value(PARAM_TEXT, 'exported studyplan content'),
],'Exported studyplan'); ], 'Exported studyplan');
} }
public function export_plan() public function export_plan() {
{
$model = $this->export_model(); $model = $this->export_model();
$json = json_encode([ $json = json_encode([
"type"=>"studyplan", "type"=>"studyplan",
"version"=>2.0, "version"=>2.0,
"studyplan"=>$model "studyplan"=>$model
],\JSON_PRETTY_PRINT); ], \JSON_PRETTY_PRINT);
return [ "format" => "application/json", "content" => $json]; return [ "format" => "application/json", "content" => $json];
} }
public function export_model() public function export_model() {
{
$model = [ $model = [
'name' => $this->r->name, 'name' => $this->r->name,
'shortname' => $this->r->shortname, 'shortname' => $this->r->shortname,
@ -522,32 +532,29 @@ class studyplan {
return $model; return $model;
} }
public function export_pages_model() public function export_pages_model() {
{
$pages = []; $pages = [];
foreach($this->pages() as $p) foreach ($this->pages() as $p) {
{
$pages[] = $p->export_model(); $pages[] = $p->export_model();
} }
return $pages; return $pages;
} }
public static function import_studyplan($content,$format="application/json",$context_id=1) public static function import_studyplan($content, $format="application/json", $context_id=1) {
{ if ($format != "application/json") { return false;}
if($format != "application/json") { return false;}
$content = json_decode($content, true);
$content = json_decode($content,true); if ($content["type"] == "studyplan" && $content["version"] >= 2.0) {
if($content["type"] == "studyplan" && $content["version"] >= 2.0){
// Make sure the aggregation_config is re-encoded as json text.
// Make sure the aggregation_config is re-encoded as json text
$content["studyplan"]["aggregation_config"] = json_encode($content["studyplan"]["aggregation_config"]); $content["studyplan"]["aggregation_config"] = json_encode($content["studyplan"]["aggregation_config"]);
// And make sure the context_id is set to the provided context for import // And make sure the context_id is set to the provided context for import.
$content["studyplan"]["context_id"] = $context_id; $content["studyplan"]["context_id"] = $context_id;
// Create a new plan, based on the given parameters - this is the import studyplan part // Create a new plan, based on the given parameters - this is the import studyplan part.
$plan = self::add($content["studyplan"],true); $plan = self::add($content["studyplan"], true);
// Now import each page // Now import each page.
return $plan->import_pages_model($content["studyplan"]["pages"]); return $plan->import_pages_model($content["studyplan"]["pages"]);
} }
@ -557,17 +564,16 @@ class studyplan {
} }
} }
public function import_pages($content,$format="application/json") public function import_pages($content, $format="application/json") {
{ if ($format != "application/json") { return false;}
if($format != "application/json") { return false;} $content = json_decode($content, true);
$content = json_decode($content,true); if ($content["version"] >= 2.0) {
if($content["version"] >= 2.0){ if ($content["type"] == "studyplanpage") {
if($content["type"] == "studyplanpage"){ // import single page from a studyplanpage (wrapped in array of one page).
// import single page from a studyplanpage (wrapped in array of one page)
return $this->import_pages_model([$content["page"]]); return $this->import_pages_model([$content["page"]]);
} }
else if($content["type"] == "studyplan"){ else if ($content["type"] == "studyplan") {
// Import all pages from the studyplan // Import all pages from the studyplan.
return $this->import_pages_model($content["studyplan"]["pages"]); return $this->import_pages_model($content["studyplan"]["pages"]);
} }
} }
@ -576,10 +582,9 @@ class studyplan {
} }
} }
protected function import_pages_model($model) protected function import_pages_model($model) {
{
$this->pages(); // make sure the page cache is initialized, since we will be adding to it. $this->pages(); // make sure the page cache is initialized, since we will be adding to it.
foreach($model as $p){ foreach ($model as $p) {
$p["studyplan_id"] = $this->id(); $p["studyplan_id"] = $this->id();
$page = studyplanpage::add($p); $page = studyplanpage::add($p);
$this->page_cache[] = $page; $this->page_cache[] = $page;
@ -592,38 +597,38 @@ class studyplan {
/** /**
* Mark the studyplan as changed regarding courses and associated cohorts * Mark the studyplan as changed regarding courses and associated cohorts
*/ */
public function mark_csync_changed(){ public function mark_csync_changed() {
global $DB; global $DB;
$DB->update_record(self::TABLE, ['id' => $this->id,"csync_flag" => 1]); $DB->update_record(self::TABLE, ['id' => $this->id, "csync_flag" => 1]);
$this->r->csync_flag = 1; //manually set it in the cache, if something unexpected happened, an exception has already been thrown anyway $this->r->csync_flag = 1; //manually set it in the cache, if something unexpected happened, an exception has already been thrown anyway.
} }
/** /**
* Clear the csync mark * Clear the csync mark
*/ */
public function clear_csync_changed(){ public function clear_csync_changed() {
global $DB; global $DB;
$DB->update_record(self::TABLE, ['id' => $this->id,"csync_flag" => 0]); $DB->update_record(self::TABLE, ['id' => $this->id, "csync_flag" => 0]);
$this->r->csync_flag = 0; //manually set it in the cache, if something unexpected happened, an exception has already been thrown anyway $this->r->csync_flag = 0; //manually set it in the cache, if something unexpected happened, an exception has already been thrown anyway.
} }
public function has_csync_changed(){ public function has_csync_changed() {
return ($this->r->csync_flag > 0)?true:false; return ($this->r->csync_flag > 0)?true:false;
} }
/** /**
* See if the specified course id is linked in this studyplan * See if the specified course id is linked in this studyplan
*/ */
public function course_linked($courseid){ public function course_linked($courseid) {
global $DB; global $DB;
$sql = "SELECT COUNT(i.id) $sql = "SELECT COUNT(i.id)
FROM {local_treestudyplan} FROM {local_treestudyplan}
INNER JOIN {local_treestudyplan_line} l ON p.id = l.studyplan_id INNER JOIN {local_treestudyplan_line} l ON p.id = l.studyplan_id
INNER JOIN {local_treestudyplan_item} i ON l.id = i.line_id INNER JOIN {local_treestudyplan_item} i ON l.id = i.line_id
WHERE p.id = :planid WHERE p.id = :planid
AND i.course_id = :courseid"; AND i.course_id = :courseid";
$count = $DB->get_field_sql($sql,["courseid" => $courseid, "planid" => $this->id]); $count = $DB->get_field_sql($sql, ["courseid" => $courseid, "planid" => $this->id]);
return ($count > 0)?true:false; return ($count > 0)?true:false;
} }
@ -632,7 +637,7 @@ class studyplan {
* List the course id is linked in this studyplan * List the course id is linked in this studyplan
* Used for cohort enrolment cascading * Used for cohort enrolment cascading
*/ */
public function get_linked_course_ids(){ public function get_linked_course_ids() {
global $DB; global $DB;
$sql = "SELECT i.course_id $sql = "SELECT i.course_id
@ -641,7 +646,7 @@ class studyplan {
INNER JOIN {local_treestudyplan_line} l ON pg.id = l.page_id INNER JOIN {local_treestudyplan_line} l ON pg.id = l.page_id
INNER JOIN {local_treestudyplan_item} i ON l.id = i.line_id INNER JOIN {local_treestudyplan_item} i ON l.id = i.line_id
WHERE p.id = :studyplan_id AND i.type = :itemtype"; WHERE p.id = :studyplan_id AND i.type = :itemtype";
$fields = $DB->get_fieldset_sql($sql,["studyplan_id" => $this->id,"itemtype" => studyitem::COURSE]); $fields = $DB->get_fieldset_sql($sql, ["studyplan_id" => $this->id, "itemtype" => studyitem::COURSE]);
return $fields; return $fields;
} }
@ -649,7 +654,7 @@ class studyplan {
/** /**
* List the cohort id's associated with this studyplan * List the cohort id's associated with this studyplan
*/ */
public function get_linked_cohort_ids(){ public function get_linked_cohort_ids() {
global $CFG, $DB; global $CFG, $DB;
$sql = "SELECT DISTINCT j.cohort_id FROM {local_treestudyplan_cohort} j $sql = "SELECT DISTINCT j.cohort_id FROM {local_treestudyplan_cohort} j
@ -661,7 +666,7 @@ class studyplan {
/** /**
* List the user id's explicitly associated with this studyplan * List the user id's explicitly associated with this studyplan
*/ */
public function get_linked_user_ids(){ public function get_linked_user_ids() {
global $CFG, $DB; global $CFG, $DB;
$sql = "SELECT DISTINCT j.user_id FROM {local_treestudyplan_user} j $sql = "SELECT DISTINCT j.user_id FROM {local_treestudyplan_user} j
@ -672,16 +677,16 @@ class studyplan {
/** /**
* See if the specified course id is linked in this studyplan * See if the specified course id is linked in this studyplan
*/ */
public function badge_linked($badgeid){ public function badge_linked($badgeid) {
global $DB; global $DB;
$sql = "SELECT COUNT(i.id) $sql = "SELECT COUNT(i.id)
FROM {local_treestudyplan} FROM {local_treestudyplan}
INNER JOIN {local_treestudyplan_line} l ON p.id = l.studyplan_id INNER JOIN {local_treestudyplan_line} l ON p.id = l.studyplan_id
INNER JOIN {local_treestudyplan_item} i ON l.id = i.line_id INNER JOIN {local_treestudyplan_item} i ON l.id = i.line_id
WHERE p.id = :planid WHERE p.id = :planid
AND i.badge_id = :badgeid"; AND i.badge_id = :badgeid";
$count = $DB->get_field_sql($sql,["badgeid" => $badgeid, "planid" => $this->id]); $count = $DB->get_field_sql($sql, ["badgeid" => $badgeid, "planid" => $this->id]);
return ($count > 0)?true:false; return ($count > 0)?true:false;
} }

View File

@ -1,4 +1,25 @@
<?php <?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/>.
/**
*
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace local_treestudyplan; namespace local_treestudyplan;
require_once($CFG->libdir.'/externallib.php'); require_once($CFG->libdir.'/externallib.php');
@ -10,30 +31,30 @@ class studyplanpage {
private static $CACHE = []; private static $CACHE = [];
private $r; // Holds database record private $r; // Holds database record.
private $id; private $id;
private $studyplan; private $studyplan;
public function aggregator(){ public function aggregator() {
return $this->studyplan->aggregator(); return $this->studyplan->aggregator();
} }
// Cache constructors to avoid multiple creation events in one session. // Cache constructors to avoid multiple creation events in one session.
public static function findById($id): self { public static function findById($id): self {
if(!array_key_exists($id,self::$CACHE)){ if (!array_key_exists($id, self::$CACHE)) {
self::$CACHE[$id] = new self($id); self::$CACHE[$id] = new self($id);
} }
return self::$CACHE[$id]; return self::$CACHE[$id];
} }
private function __construct($id) { private function __construct($id) {
global $DB; global $DB;
$this->id = $id; $this->id = $id;
$this->r = $DB->get_record(self::TABLE,['id' => $id]); $this->r = $DB->get_record(self::TABLE, ['id' => $id]);
$this->studyplan = studyplan::findById($this->r->studyplan_id); $this->studyplan = studyplan::findById($this->r->studyplan_id);
} }
public function id(){ public function id() {
return $this->id; return $this->id;
} }
@ -41,34 +62,34 @@ class studyplanpage {
return $this->studyplan; return $this->studyplan;
} }
public function shortname(){ public function shortname() {
return $this->r->shortname; return $this->r->shortname;
} }
public function periods(){ public function periods() {
return $this->r->periods; return $this->r->periods;
} }
public function fullname(){ public function fullname() {
return $this->r->fullname; return $this->r->fullname;
} }
public function startdate(){ public function startdate() {
return new \DateTime($this->r->startdate); return new \DateTime($this->r->startdate);
} }
public function enddate(){ public function enddate() {
if($this->r->enddate && strlen($this->r->enddate) > 0){ if ($this->r->enddate && strlen($this->r->enddate) > 0) {
return new \DateTime($this->r->enddate); return new \DateTime($this->r->enddate);
} }
else{ else{
// return a date 100 years into the future // return a date 100 years into the future.
return (new \DateTime($this->r->startdate))->add(new \DateInterval("P100Y")); return (new \DateTime($this->r->startdate))->add(new \DateInterval("P100Y"));
} }
} }
public static function simple_structure($value=VALUE_REQUIRED){ public static function simple_structure($value=VALUE_REQUIRED) {
return new \external_single_structure([ return new \external_single_structure([
"id" => new \external_value(PARAM_INT, 'id of studyplan page'), "id" => new \external_value(PARAM_INT, 'id of studyplan page'),
"fullname" => new \external_value(PARAM_TEXT, 'name of studyplan page'), "fullname" => new \external_value(PARAM_TEXT, 'name of studyplan page'),
@ -78,10 +99,10 @@ class studyplanpage {
"startdate" => new \external_value(PARAM_TEXT, 'start date of studyplan'), "startdate" => new \external_value(PARAM_TEXT, 'start date of studyplan'),
"enddate" => new \external_value(PARAM_TEXT, 'end date of studyplan'), "enddate" => new \external_value(PARAM_TEXT, 'end date of studyplan'),
"perioddesc" => period::page_structure(), "perioddesc" => period::page_structure(),
],'Studyplan page basic info',$value); ], 'Studyplan page basic info', $value);
} }
public function simple_model(){ public function simple_model() {
return [ return [
'id' => $this->r->id, 'id' => $this->r->id,
'fullname' => $this->r->fullname, 'fullname' => $this->r->fullname,
@ -94,7 +115,7 @@ class studyplanpage {
]; ];
} }
public static function editor_structure($value=VALUE_REQUIRED){ public static function editor_structure($value=VALUE_REQUIRED) {
return new \external_single_structure([ return new \external_single_structure([
"id" => new \external_value(PARAM_INT, 'id of studyplan'), "id" => new \external_value(PARAM_INT, 'id of studyplan'),
"fullname" => new \external_value(PARAM_TEXT, 'name of studyplan page'), "fullname" => new \external_value(PARAM_TEXT, 'name of studyplan page'),
@ -105,10 +126,10 @@ class studyplanpage {
"enddate" => new \external_value(PARAM_TEXT, 'end date of studyplan page'), "enddate" => new \external_value(PARAM_TEXT, 'end date of studyplan page'),
"studylines" => new \external_multiple_structure(studyline::editor_structure()), "studylines" => new \external_multiple_structure(studyline::editor_structure()),
"perioddesc" => period::page_structure(), "perioddesc" => period::page_structure(),
],'Studyplan page full structure',$value); ], 'Studyplan page full structure', $value);
} }
public function editor_model(){ public function editor_model() {
global $DB; global $DB;
$model = [ $model = [
@ -124,68 +145,67 @@ class studyplanpage {
]; ];
$children = studyline::find_page_children($this); $children = studyline::find_page_children($this);
foreach($children as $c) foreach ($children as $c) {
{
$model['studylines'][] = $c->editor_model(); $model['studylines'][] = $c->editor_model();
} }
return $model; return $model;
} }
public static function add($fields){ public static function add($fields) {
global $CFG, $DB; global $CFG, $DB;
if(!isset($fields['studyplan_id'])){ if (!isset($fields['studyplan_id'])) {
throw new \InvalidArgumentException("parameter 'studyplan_id' missing"); throw new \InvalidArgumentException("parameter 'studyplan_id' missing");
} }
$addable = ['studyplan_id','fullname','shortname','description','periods','startdate','enddate']; $addable = ['studyplan_id', 'fullname', 'shortname', 'description', 'periods', 'startdate', 'enddate'];
$info = ['enddate' => null ]; $info = ['enddate' => null ];
foreach($addable as $f){ foreach ($addable as $f) {
if(array_key_exists($f,$fields)){ if (array_key_exists($f, $fields)) {
$info[$f] = $fields[$f]; $info[$f] = $fields[$f];
} }
} }
if(!isset($addable['periods'])){ if (!isset($addable['periods'])) {
$addable['periods'] = 4; $addable['periods'] = 4;
} else if($addable['periods'] < 1){ } else if ($addable['periods'] < 1) {
$addable['periods'] = 1; $addable['periods'] = 1;
} }
$id = $DB->insert_record(self::TABLE, $info); $id = $DB->insert_record(self::TABLE, $info);
return self::findById($id); // make sure the new page is immediately cached return self::findById($id); // make sure the new page is immediately cached.
} }
public function edit($fields){ public function edit($fields) {
global $DB; global $DB;
$editable = ['fullname','shortname','description','periods','startdate','enddate']; $editable = ['fullname', 'shortname', 'description', 'periods', 'startdate', 'enddate'];
$info = ['id' => $this->id,]; $info = ['id' => $this->id, ];
foreach($editable as $f){ foreach ($editable as $f) {
if(array_key_exists($f,$fields)){ if (array_key_exists($f, $fields)) {
$info[$f] = $fields[$f]; $info[$f] = $fields[$f];
} }
} }
if(isset($info['periods']) && $info['periods'] < 1){ if (isset($info['periods']) && $info['periods'] < 1) {
$info['periods'] = 1; $info['periods'] = 1;
} }
$DB->update_record(self::TABLE, $info); $DB->update_record(self::TABLE, $info);
//reload record after edit //reload record after edit.
$this->r = $DB->get_record(self::TABLE,['id' => $this->id],"*",MUST_EXIST); $this->r = $DB->get_record(self::TABLE, ['id' => $this->id], "*", MUST_EXIST);
return $this; return $this;
} }
public function delete($force=false){ public function delete($force=false) {
global $DB; global $DB;
if($force){ if ($force) {
$children = studyline::find_page_children($this); $children = studyline::find_page_children($this);
foreach($children as $c){ foreach ($children as $c) {
$c->delete($force); $c->delete($force);
} }
} }
if($DB->count_records('local_treestudyplan_line',['page_id' => $this->id]) > 0){ if ($DB->count_records('local_treestudyplan_line', ['page_id' => $this->id]) > 0) {
return success::fail('cannot delete studyplan page that still has studylines'); return success::fail('cannot delete studyplan page that still has studylines');
} }
else else
@ -195,7 +215,7 @@ class studyplanpage {
} }
} }
public static function user_structure($value=VALUE_REQUIRED){ public static function user_structure($value=VALUE_REQUIRED) {
return new \external_single_structure([ return new \external_single_structure([
"id" => new \external_value(PARAM_INT, 'id of studyplan page'), "id" => new \external_value(PARAM_INT, 'id of studyplan page'),
"fullname" => new \external_value(PARAM_TEXT, 'name of studyplan page'), "fullname" => new \external_value(PARAM_TEXT, 'name of studyplan page'),
@ -206,10 +226,10 @@ class studyplanpage {
"enddate" => new \external_value(PARAM_TEXT, 'end date of studyplan page'), "enddate" => new \external_value(PARAM_TEXT, 'end date of studyplan page'),
"studylines" => new \external_multiple_structure(studyline::user_structure()), "studylines" => new \external_multiple_structure(studyline::user_structure()),
"perioddesc" => period::page_structure(), "perioddesc" => period::page_structure(),
],'Studyplan page with user info',$value); ], 'Studyplan page with user info', $value);
} }
public function user_model($userid){ public function user_model($userid) {
$model = [ $model = [
'id' => $this->r->id, 'id' => $this->r->id,
@ -224,35 +244,31 @@ class studyplanpage {
]; ];
$children = studyline::find_page_children($this); $children = studyline::find_page_children($this);
foreach($children as $c) foreach ($children as $c) {
{
$model['studylines'][] = $c->user_model($userid); $model['studylines'][] = $c->user_model($userid);
} }
return $model; return $model;
} }
public static function find_studyplan_children(studyplan $plan) public static function find_studyplan_children(studyplan $plan) {
{
global $DB; global $DB;
$list = []; $list = [];
$ids = $DB->get_fieldset_select(self::TABLE,"id","studyplan_id = :plan_id ORDER BY startdate", $ids = $DB->get_fieldset_select(self::TABLE, "id", "studyplan_id = :plan_id ORDER BY startdate",
['plan_id' => $plan->id()]); ['plan_id' => $plan->id()]);
foreach($ids as $id) { foreach ($ids as $id) {
$list[] = self::findById($id); $list[] = self::findById($id);
} }
return $list; return $list;
} }
public static function duplicate_page($page_id,$name,$shortname) public static function duplicate_page($page_id, $name, $shortname) {
{
$ori = self::findById($page_id); $ori = self::findById($page_id);
$new = $ori->duplicate($name,$shortname); $new = $ori->duplicate($name, $shortname);
return $new->simple_model(); return $new->simple_model();
} }
public function duplicate($new_studyplan) public function duplicate($new_studyplan) {
{ // First duplicate the studyplan structure.
// First duplicate the studyplan structure
$new = studyplanpage::add([ $new = studyplanpage::add([
'studyplan_id' => $new_studyplan->id(), 'studyplan_id' => $new_studyplan->id(),
'fullname' => $this->r->fullname, 'fullname' => $this->r->fullname,
@ -262,101 +278,98 @@ class studyplanpage {
'startdate' => $this->r->startdate, 'startdate' => $this->r->startdate,
'enddate' => empty($this->r->enddate)?null:$this->r->enddate, 'enddate' => empty($this->r->enddate)?null:$this->r->enddate,
]); ]);
// next, copy the studylines // next, copy the studylines.
$children = studyline::find_page_children($this); $children = studyline::find_page_children($this);
$itemtranslation = []; $itemtranslation = [];
$linetranslation = []; $linetranslation = [];
foreach($children as $c){ foreach ($children as $c) {
$newchild = $c->duplicate($this,$itemtranslation); $newchild = $c->duplicate($this, $itemtranslation);
$linetranslation[$c->id()] = $newchild->id(); $linetranslation[$c->id()] = $newchild->id();
} }
// now the itemtranslation array contains all of the old child id's as keys and all of the related new ids as values // now the itemtranslation array contains all of the old child id's as keys and all of the related new ids as values.
// (feature of the studyline::duplicate function) // (feature of the studyline::duplicate function).
// use this to recreate the lines in the new plan // use this to recreate the lines in the new plan.
foreach(array_keys($itemtranslation) as $item_id){ foreach (array_keys($itemtranslation) as $item_id) {
// copy based on the outgoing connections of each item, to avoid duplicates // copy based on the outgoing connections of each item, to avoid duplicates.
$connections = studyitemconnection::find_outgoing($item_id); $connections = studyitemconnection::find_outgoing($item_id);
foreach($connections as $conn){ foreach ($connections as $conn) {
studyitemconnection::connect($itemtranslation[$conn->from_id],$itemtranslation[$conn->to_id]); studyitemconnection::connect($itemtranslation[$conn->from_id], $itemtranslation[$conn->to_id]);
} }
} }
return $new; return $new;
} }
public static function export_structure() public static function export_structure() {
{
return new \external_single_structure([ return new \external_single_structure([
"format" => new \external_value(PARAM_TEXT, 'format of studyplan export'), "format" => new \external_value(PARAM_TEXT, 'format of studyplan export'),
"content"=> new \external_value(PARAM_TEXT, 'exported studyplan content'), "content"=> new \external_value(PARAM_TEXT, 'exported studyplan content'),
],'Exported studyplan'); ], 'Exported studyplan');
} }
public function export_page() public function export_page() {
{
$model = $this->export_model(); $model = $this->export_model();
$json = json_encode([ $json = json_encode([
"type"=>"studyplanpage", "type"=>"studyplanpage",
"version"=>2.0, "version"=>2.0,
"page"=>$model "page"=>$model
],\JSON_PRETTY_PRINT); ], \JSON_PRETTY_PRINT);
return [ "format" => "application/json", "content" => $json]; return [ "format" => "application/json", "content" => $json];
} }
public function export_page_csv() public function export_page_csv() {
{
$plist = period::findForPage($this); $plist = period::findForPage($this);
$model = $this->editor_model(); $model = $this->editor_model();
$periods = intval($model["periods"]); $periods = intval($model["periods"]);
// First line // First line.
$csv = "\"\""; $csv = "\"\"";
for($i = 1; $i <= $periods; $i++){ for($i = 1; $i <= $periods; $i++) {
$name = $plist[$i]->shortname(); $name = $plist[$i]->shortname();
$csv .= ",\"{$name}\""; $csv .= ", \"{$name}\"";
} }
$csv .= "\r\n"; $csv .= "\r\n";
// next, make one line per studyline // next, make one line per studyline.
foreach($model["studylines"] as $line){ foreach ($model["studylines"] as $line) {
// determine how many fields are simultaneous in the line at maximum // determine how many fields are simultaneous in the line at maximum.
$maxlines = 1; $maxlines = 1;
for($i = 1; $i <= $periods; $i++){ for($i = 1; $i <= $periods; $i++) {
if(count($line["slots"]) > $i){ if (count($line["slots"]) > $i) {
$ct = 0; $ct = 0;
foreach($line["slots"][$i][studyline::SLOTSET_COMPETENCY] as $itm){ foreach ($line["slots"][$i][studyline::SLOTSET_COMPETENCY] as $itm) {
if($itm["type"] == "course"){ if ($itm["type"] == "course") {
$ct += 1; $ct += 1;
} }
} }
if($ct > $maxlines){ if ($ct > $maxlines) {
$maxlines = $ct; $maxlines = $ct;
} }
} }
} }
for($lct = 0; $lct < $maxlines; $lct++){ for($lct = 0; $lct < $maxlines; $lct++) {
$csv .= "\"{$line["name"]}\""; $csv .= "\"{$line["name"]}\"";
for($i = 1; $i <= $periods; $i++){ for($i = 1; $i <= $periods; $i++) {
$filled = false; $filled = false;
if(count($line["slots"]) > $i){ if (count($line["slots"]) > $i) {
$ct = 0; $ct = 0;
foreach($line["slots"][$i][studyline::SLOTSET_COMPETENCY] as $itm){ foreach ($line["slots"][$i][studyline::SLOTSET_COMPETENCY] as $itm) {
if($itm["type"] == "course"){ if ($itm["type"] == "course") {
if($ct == $lct){ if ($ct == $lct) {
$csv .= ",\""; $csv .= ", \"";
$csv .= $itm["course"]["fullname"]; $csv .= $itm["course"]["fullname"];
$csv .= "\r\n"; $csv .= "\r\n";
$first = true; $first = true;
foreach($itm["course"]["grades"] as $g){ foreach ($itm["course"]["grades"] as $g) {
if($g["selected"]){ if ($g["selected"]) {
if($first){ if ($first) {
$first = false; $first = false;
} }
else{ else{
$csv .= "\r\n"; $csv .= "\r\n";
} }
$csv .= "- ".str_replace('"', '\'', $g["name"]); $csv .= "- ".str_replace('"', '\'', $g["name"]);
@ -369,9 +382,9 @@ class studyplanpage {
$ct++; $ct++;
} }
} }
} }
if(!$filled) { if (!$filled) {
$csv .= ",\"\""; $csv .= ", \"\"";
} }
} }
$csv .= "\r\n"; $csv .= "\r\n";
@ -381,29 +394,28 @@ class studyplanpage {
return [ "format" => "text/csv", "content" => $csv]; return [ "format" => "text/csv", "content" => $csv];
} }
public function export_studylines(){ public function export_studylines() {
$model = $this->export_studylines_model(); $model = $this->export_studylines_model();
$json = json_encode([ $json = json_encode([
"type"=>"studylines", "type"=>"studylines",
"version"=>2.0, "version"=>2.0,
"studylines"=>$model, "studylines"=>$model,
],\JSON_PRETTY_PRINT); ], \JSON_PRETTY_PRINT);
return [ "format" => "application/json", "content" => $json]; return [ "format" => "application/json", "content" => $json];
} }
public function export_periods(){ public function export_periods() {
$model = period::page_model($this); $model = period::page_model($this);
$json = json_encode([ $json = json_encode([
"type"=>"periods", "type"=>"periods",
"version"=>2.0, "version"=>2.0,
"perioddesc"=>$model, "perioddesc"=>$model,
],\JSON_PRETTY_PRINT); ], \JSON_PRETTY_PRINT);
return [ "format" => "application/json", "content" => $json]; return [ "format" => "application/json", "content" => $json];
} }
public function export_model() public function export_model() {
{
$model = [ $model = [
'fullname' => $this->r->fullname, 'fullname' => $this->r->fullname,
'shortname' => $this->r->shortname, 'shortname' => $this->r->shortname,
@ -417,26 +429,23 @@ class studyplanpage {
return $model; return $model;
} }
public function export_studylines_model() public function export_studylines_model() {
{
$children = studyline::find_page_children($this); $children = studyline::find_page_children($this);
$lines = []; $lines = [];
foreach($children as $c) foreach ($children as $c) {
{
$lines[] = $c->export_model(); $lines[] = $c->export_model();
} }
return $lines; return $lines;
} }
public function import_periods($content,$format="application/json") public function import_periods($content, $format="application/json") {
{ if ($format != "application/json") { return false;}
if($format != "application/json") { return false;} $content = json_decode($content, true);
$content = json_decode($content,true); if ($content["type"] == "periods" && $content["version"] >= 2.0) {
if($content["type"] == "periods" && $content["version"] >= 2.0){
return $this->import_periods_model($content["perioddesc"]); return $this->import_periods_model($content["perioddesc"]);
} }
else if($content["type"] == "studyplanpage" && $content["version"] >= 2.0){ else if ($content["type"] == "studyplanpage" && $content["version"] >= 2.0) {
return $this->import_periods_model($content["page"]["perioddesc"]); return $this->import_periods_model($content["page"]["perioddesc"]);
} }
else { else {
@ -444,17 +453,16 @@ class studyplanpage {
} }
} }
public function import_studylines($content,$format="application/json") public function import_studylines($content, $format="application/json") {
{ if ($format != "application/json") { return false;}
if($format != "application/json") { return false;} $content = json_decode($content, true);
$content = json_decode($content,true); if ($content["type"] == "studylines" && $content["version"] >= 2.0) {
if($content["type"] == "studylines" && $content["version"] >= 2.0){
return $this->import_studylines_model($content["studylines"]); return $this->import_studylines_model($content["studylines"]);
} }
else if($content["type"] == "studyplanpage" && $content["version"] >= 2.0){ else if ($content["type"] == "studyplanpage" && $content["version"] >= 2.0) {
return $this->import_studylines_model($content["page"]["studylines"]); return $this->import_studylines_model($content["page"]["studylines"]);
} }
else if($content["type"] == "studyplan" && $content["version"] >= 2.0){ else if ($content["type"] == "studyplan" && $content["version"] >= 2.0) {
return $this->import_studylines_model($content["studyplan"]["pages"][0]["studylines"]); return $this->import_studylines_model($content["studyplan"]["pages"][0]["studylines"]);
} }
else { else {
@ -462,52 +470,51 @@ class studyplanpage {
} }
} }
protected function find_studyline_by_shortname($shortname){ protected function find_studyline_by_shortname($shortname) {
$children = studyline::find_page_children($this); $children = studyline::find_page_children($this);
foreach($children as $l){ foreach ($children as $l) {
if($shortname == $l->shortname()){ if ($shortname == $l->shortname()) {
return $l; return $l;
} }
} }
return null; return null;
} }
public function import_periods_model($model){ public function import_periods_model($model) {
$periods = period::findForPage($this); $periods = period::findForPage($this);
foreach($model as $pmodel){ foreach ($model as $pmodel) {
$pi = $pmodel["period"]; $pi = $pmodel["period"];
if(array_key_exists($pi,$periods)){ if (array_key_exists($pi, $periods)) {
$periods[$pi]->edit($pmodel); $periods[$pi]->edit($pmodel);
} }
} }
} }
public function import_studylines_model($model) public function import_studylines_model($model) {
{ // First attempt to map each studyline model to an existing or new line.
// First attempt to map each studyline model to an existing or new line
$line_map = []; $line_map = [];
foreach($model as $ix => $linemodel){ foreach ($model as $ix => $linemodel) {
$line = $this->find_studyline_by_shortname($linemodel["shortname"]); $line = $this->find_studyline_by_shortname($linemodel["shortname"]);
if(empty($line)){ if (empty($line)) {
$linemodel["page_id"] = $this->id; $linemodel["page_id"] = $this->id;
$line = studyline::add($linemodel); $line = studyline::add($linemodel);
} else { } else {
//$line->edit($linemodel); // Update the line with the settings from the imported file //$line->edit($linemodel); // Update the line with the settings from the imported file.
} }
$line_map[$ix] = $line; $line_map[$ix] = $line;
} }
// next, let each study line import the study items // next, let each study line import the study items.
$itemtranslation = []; $itemtranslation = [];
$connections = []; $connections = [];
foreach($model as $ix => $linemodel){ foreach ($model as $ix => $linemodel) {
$line_map[$ix]->import_studyitems($linemodel["slots"],$itemtranslation,$connections); $line_map[$ix]->import_studyitems($linemodel["slots"], $itemtranslation, $connections);
} }
// Finally, create the links between the study items // Finally, create the links between the study items.
foreach($connections as $from => $dests){ foreach ($connections as $from => $dests) {
foreach($dests as $to){ foreach ($dests as $to) {
studyitemconnection::connect($from,$itemtranslation[$to]); studyitemconnection::connect($from, $itemtranslation[$to]);
} }
} }
return true; return true;

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,24 @@
<?php <?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/>.
/**
*
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace local_treestudyplan; namespace local_treestudyplan;
@ -7,21 +27,20 @@ class success {
private $success; private $success;
private $msg; private $msg;
public static function success($msg=""){ public static function success($msg="") {
return new self(true,$msg); return new self(true, $msg);
} }
public static function fail($msg=""){ public static function fail($msg="") {
return new self(false,$msg); return new self(false, $msg);
} }
public function __construct($success,$msg){ public function __construct($success, $msg) {
$this->success = ($success)?true:false; $this->success = ($success)?true:false;
$this->msg = $msg; $this->msg = $msg;
} }
public static function structure() public static function structure() {
{
return new \external_single_structure([ return new \external_single_structure([
"success" => new \external_value(PARAM_BOOL, 'operation completed succesfully'), "success" => new \external_value(PARAM_BOOL, 'operation completed succesfully'),
"msg" => new \external_value(PARAM_TEXT, 'message'), "msg" => new \external_value(PARAM_TEXT, 'message'),
@ -32,11 +51,11 @@ class success {
return ["success" => $this->success, "msg"=> $this->msg]; return ["success" => $this->success, "msg"=> $this->msg];
} }
public function successful(){ public function successful() {
return $this->success; return $this->success;
} }
public function msg(){ public function msg() {
return $this->msg; return $this->msg;
} }

View File

@ -1,4 +1,25 @@
<?php <?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/>.
/**
*
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace local_treestudyplan\task; namespace local_treestudyplan\task;
require_once($CFG->dirroot.'/course/externallib.php'); require_once($CFG->dirroot.'/course/externallib.php');
use local_treestudyplan\studyplan; use local_treestudyplan\studyplan;
@ -20,24 +41,24 @@ class autocohortsync extends \core\task\scheduled_task {
* Execute the task. * Execute the task.
*/ */
public function execute() { public function execute() {
if(get_config("local_treestudyplan","csync_enable")){ if (get_config("local_treestudyplan", "csync_enable")) {
\mtrace("Automatic csync cascading enabled"); \mtrace("Automatic csync cascading enabled");
$studyplans = studyplan::find_all(); $studyplans = studyplan::find_all();
foreach($studyplans as $studyplan) { foreach ($studyplans as $studyplan) {
// Only process studyplans that have been marked for change because // Only process studyplans that have been marked for change because .
// a cohort change has occurred or a course has been added.... // a cohort change has occurred or a course has been added....
if($studyplan->has_csync_changed()){ if ($studyplan->has_csync_changed()) {
\mtrace("Studyplan {$studyplan->shortname()} needs processing"); \mtrace("Studyplan {$studyplan->shortname()} needs processing");
$enroller = new cascadecohortsync($studyplan); $enroller = new cascadecohortsync($studyplan);
$enroller->sync(); $enroller->sync();
if(get_config("local_treestudyplan","csync_users")){ if (get_config("local_treestudyplan", "csync_users")) {
$userenroller = new cascadeusersync($studyplan); $userenroller = new cascadeusersync($studyplan);
$userenroller->sync(); $userenroller->sync();
} }
$studyplan->clear_csync_changed(); $studyplan->clear_csync_changed();
} }
} }
\mtrace("Task done"); \mtrace("Task done");
} else { } else {

View File

@ -1,11 +1,32 @@
<?php <?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/>.
/**
*
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace local_treestudyplan\task; namespace local_treestudyplan\task;
require_once($CFG->dirroot.'/course/externallib.php'); require_once($CFG->dirroot.'/course/externallib.php');
use local_treestudyplan\teachingfinder; use local_treestudyplan\teachingfinder;
class refreshteacherlist extends \core\task\scheduled_task { class refreshteacherlist extends \core\task\scheduled_task {
const CACHE_TIME = 4 * 60 * 60; // 2 hours const CACHE_TIME = 4 * 60 * 60; // 2 hours.
/** /**
* Return the task's name as shown in admin screens. * Return the task's name as shown in admin screens.
* *
@ -21,9 +42,9 @@ class refreshteacherlist extends \core\task\scheduled_task {
public function execute() { public function execute() {
\mtrace("Ververs lijst met leraren"); \mtrace("Ververs lijst met leraren");
$teacher_ids = teachingfinder::list_teacher_ids(); $teacher_ids = teachingfinder::list_teacher_ids();
foreach($teacher_ids as $tid){ foreach ($teacher_ids as $tid) {
$utime = teachingfinder::get_update_time($tid); $utime = teachingfinder::get_update_time($tid);
if(time() - $utime > self::CACHE_TIME){ if (time() - $utime > self::CACHE_TIME) {
\mtrace("Teacher {$tid} is due for a refresh of the list"); \mtrace("Teacher {$tid} is due for a refresh of the list");
teachingfinder::update_teaching_cache($tid); teachingfinder::update_teaching_cache($tid);
} }

View File

@ -1,22 +1,43 @@
<?php <?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/>.
/**
*
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace local_treestudyplan; namespace local_treestudyplan;
class teachingfinder { class teachingfinder {
const TABLE = "local_treestudyplan_teachers"; const TABLE = "local_treestudyplan_teachers";
public static function list_my_plans(){ public static function list_my_plans() {
global $USER,$DB; global $USER, $DB;
$userid = $USER->id; $userid = $USER->id;
$records = $DB->get_records(self::TABLE,['teacher_id' => $userid]); $records = $DB->get_records(self::TABLE, ['teacher_id' => $userid]);
if(count($records) == 0){ if (count($records) == 0) {
// initiate a search if the cache is empty // initiate a search if the cache is empty.
self::update_teaching_cache($userid); self::update_teaching_cache($userid);
$DB->get_records(self::TABLE,['teacher_id' => $userid]); $DB->get_records(self::TABLE, ['teacher_id' => $userid]);
} }
$list = []; $list = [];
foreach($records as $r){ foreach ($records as $r) {
$list[] = studyplan::findById($r->studyplan_id); $list[] = studyplan::findById($r->studyplan_id);
} }
return $list; return $list;
@ -28,56 +49,55 @@ class teachingfinder {
* (Has the mod/assign::grade capability in one of the linked courses) * (Has the mod/assign::grade capability in one of the linked courses)
* TODO: OPTIMIZE THIS CHECK!!! * TODO: OPTIMIZE THIS CHECK!!!
*/ */
public static function update_teaching_cache($userid){ public static function update_teaching_cache($userid) {
global $DB; global $DB;
$list = []; $list = [];
// First find all active study plans // First find all active study plans.
$sql = "SELECT p.id FROM {local_treestudyplan_page} p $sql = "SELECT p.id FROM {local_treestudyplan_page} p
WHERE startdate <= NOW() and enddate >= NOW()"; WHERE startdate <= NOW() and enddate >= NOW()";
$page_ids = $DB->get_fieldset_sql($sql, []); $page_ids = $DB->get_fieldset_sql($sql, []);
// then parse them to see if the user has the grading permission in any of them // then parse them to see if the user has the grading permission in any of them .
// (Which would make them a teacher for all intents and purposes) // (Which would make them a teacher for all intents and purposes).
foreach($page_ids as $page_id) { foreach ($page_ids as $page_id) {
$sql = "SELECT i.course_id FROM {local_treestudyplan_item} i $sql = "SELECT i.course_id FROM {local_treestudyplan_item} i
INNER JOIN {local_treestudyplan_line} l ON i.line_id = l.id INNER JOIN {local_treestudyplan_line} l ON i.line_id = l.id
WHERE l.page_id = :page_id AND i.course_id IS NOT NULL"; WHERE l.page_id = :page_id AND i.course_id IS NOT NULL";
$course_ids = $DB->get_fieldset_sql($sql, ["page_id" => $page_id]); $course_ids = $DB->get_fieldset_sql($sql, ["page_id" => $page_id]);
$linked = false; $linked = false;
foreach($course_ids as $cid){ foreach ($course_ids as $cid) {
$coursecontext = \context_course::instance($cid); $coursecontext = \context_course::instance($cid);
if (is_enrolled($coursecontext, $userid, 'mod/assign:grade')){ if (is_enrolled($coursecontext, $userid, 'mod/assign:grade')) {
$linked = true; $linked = true;
break; // No need to search further break; // No need to search further.
} }
} }
if($linked) if ($linked) {
{
$list[] = $page_id; $list[] = $page_id;
} }
} }
// Now, clear the database of all records for this user // Now, clear the database of all records for this user.
$DB->delete_records(self::TABLE,["teacher_id"=>$userid]); $DB->delete_records(self::TABLE, ["teacher_id"=>$userid]);
// And add new records for the found studyplans // And add new records for the found studyplans.
$now = time(); $now = time();
foreach($list as $page_id){ foreach ($list as $page_id) {
// Retrieve the studyplan id from the page // Retrieve the studyplan id from the page.
//TODO: Change this when page management is implemented to return the page instead of the plan //TODO: Change this when page management is implemented to return the page instead of the plan.
$planid = $DB->get_field("local_treestudyplan_page","studyplan_id",["id" => $page_id]); $planid = $DB->get_field("local_treestudyplan_page", "studyplan_id", ["id" => $page_id]);
$DB->insert_record(self::TABLE,["teacher_id"=>$userid,"studyplan_id"=>$planid,"update_time"=>$now]); $DB->insert_record(self::TABLE, ["teacher_id"=>$userid, "studyplan_id"=>$planid, "update_time"=>$now]);
} }
return $list; return $list;
} }
public static function list_teacher_ids(){ public static function list_teacher_ids() {
global $DB; global $DB;
return $DB->get_fieldset_sql("SELECT DISTINCT teacher_id FROM {".self::TABLE."}"); return $DB->get_fieldset_sql("SELECT DISTINCT teacher_id FROM {".self::TABLE."}");
} }

View File

@ -1,4 +1,25 @@
<?php <?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/>.
/**
*
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace local_treestudyplan; namespace local_treestudyplan;
use \local_treestudyplan\local\gradegenerator; use \local_treestudyplan\local\gradegenerator;
@ -39,13 +60,13 @@ if ($options['help']) {
cli_writeln($usage); cli_writeln($usage);
exit(2); exit(2);
} }
//cli_writeln(print_r($options,true)); //cli_writeln(print_r($options, true));.
if (empty($options['studyplan']) && empty($options["all"])) { if (empty($options['studyplan']) && empty($options["all"])) {
cli_error('Missing mandatory argument studyplan.', 2); cli_error('Missing mandatory argument studyplan.', 2);
} }
if(!empty($options["all"])){ if (!empty($options["all"])) {
$plans = studyplan::find_all(); $plans = studyplan::find_all();
} else { } else {
$plans = studyplan::find_by_shortname($options["studyplan"]); $plans = studyplan::find_by_shortname($options["studyplan"]);
@ -56,22 +77,22 @@ $generator = new gradegenerator();
$generator->fromFile($options["file"]); $generator->fromFile($options["file"]);
cli_writeln(count($plans)." studyplans found:"); cli_writeln(count($plans)." studyplans found:");
foreach($plans as $plan){ foreach ($plans as $plan) {
cli_heading($plan->name()); cli_heading($plan->name());
$users = $plan->find_linked_users(); $users = $plan->find_linked_users();
foreach($users as $u){ foreach ($users as $u) {
$generator->addstudent($u->username); $generator->addstudent($u->username);
$generator->addUserNameInfo($u->username,$u->firstname,$u->lastname); $generator->addUserNameInfo($u->username, $u->firstname, $u->lastname);
cli_writeln(" - {$u->firstname} {$u->lastname} / {$u->username}"); cli_writeln(" - {$u->firstname} {$u->lastname} / {$u->username}");
} }
foreach($plan->pages() as $page){ foreach ($plan->pages() as $page) {
$lines = studyline::find_page_children($page); $lines = studyline::find_page_children($page);
foreach($lines as $line){ foreach ($lines as $line) {
cli_writeln(" ** {$line->name()} **"); cli_writeln(" ** {$line->name()} **");
$items = studyitem::find_studyline_children($line); $items = studyitem::find_studyline_children($line);
foreach($users as $u){ foreach ($users as $u) {
$generator->addskill($u->username,$line->shortname()); $generator->addskill($u->username, $line->shortname());
} }
} }
} }

View File

@ -1,4 +1,25 @@
<?php <?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/>.
/**
*
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace local_treestudyplan; namespace local_treestudyplan;
use \local_treestudyplan\local\gradegenerator; use \local_treestudyplan\local\gradegenerator;
@ -46,7 +67,7 @@ if ($options['help']) {
exit(2); exit(2);
} }
///////////////////////////////// /////////////////////////////////.
$user = get_admin(); $user = get_admin();
if (!$user) { if (!$user) {
@ -65,14 +86,14 @@ login_attempt_valid($user);
complete_user_login($user); complete_user_login($user);
//////////////////////////////// ////////////////////////////////.
if (empty($options['studyplan']) && empty($options["all"])) { if (empty($options['studyplan']) && empty($options["all"])) {
cli_error('Missing mandatory argument studyplan.', 2); cli_error('Missing mandatory argument studyplan.', 2);
} }
if(!empty($options["all"])){ if (!empty($options["all"])) {
$plans = studyplan::find_all(); $plans = studyplan::find_all();
} else { } else {
$plans = studyplan::find_by_shortname($options["studyplan"]); $plans = studyplan::find_by_shortname($options["studyplan"]);
@ -85,64 +106,64 @@ $generator->fromFile($options["file"]);
$assignments = []; $assignments = [];
cli_writeln(count($plans)." studyplans found:"); cli_writeln(count($plans)." studyplans found:");
foreach($plans as $plan){ foreach ($plans as $plan) {
cli_heading($plan->name()); cli_heading($plan->name());
$users = $plan->find_linked_users(); $users = $plan->find_linked_users();
foreach($plan->pages() as $page){ foreach ($plan->pages() as $page) {
$lines = studyline::find_page_children($page); $lines = studyline::find_page_children($page);
foreach($lines as $line){ foreach ($lines as $line) {
cli_writeln(" ** {$line->name()} **"); cli_writeln(" ** {$line->name()} **");
$items = studyitem::find_studyline_children($line); $items = studyitem::find_studyline_children($line);
foreach($items as $item){ foreach ($items as $item) {
if($item->type() == studyitem::COURSE) { // only handle courses for now if ($item->type() == studyitem::COURSE) { // only handle courses for now.
$courseinfo = $item->getcourseinfo(); $courseinfo = $item->getcourseinfo();
cli_writeln(" # {$courseinfo->shortname()}"); cli_writeln(" # {$courseinfo->shortname()}");
if($courseinfo->course()->startdate <= time()){ if ($courseinfo->course()->startdate <= time()) {
foreach($users as $u){ foreach ($users as $u) {
cli_writeln(" -> {$u->firstname} {$u->lastname} <-"); cli_writeln(" -> {$u->firstname} {$u->lastname} <-");
$gradables = gradeinfo::list_studyitem_gradables($item); $gradables = gradeinfo::list_studyitem_gradables($item);
$gen = $generator->generate($u->username,$line->shortname(),$gradables); $gen = $generator->generate($u->username, $line->shortname(), $gradables);
foreach($gen as $gg){ foreach ($gen as $gg) {
$g = $gg->gi; $g = $gg->gi;
$gi = $g->getGradeitem(); $gi = $g->getGradeitem();
$name = $gi->itemname; $name = $gi->itemname;
$grade = $gg->gradetext; $grade = $gg->gradetext;
cli_write (" - {$name} = {$grade}"); cli_write (" - {$name} = {$grade}");
// Check if the item is alreaady graded for this user // Check if the item is alreaady graded for this user.
$existing = $count = $DB->count_records_select('grade_grades','itemid = :gradeitemid AND finalgrade IS NOT NULL and userid = :userid', $existing = $count = $DB->count_records_select('grade_grades', 'itemid = :gradeitemid AND finalgrade IS NOT NULL and userid = :userid',
['gradeitemid' => $gi->id, 'userid' => $u->id]); ['gradeitemid' => $gi->id, 'userid' => $u->id]);
if(!$existing){ if (!$existing) {
if($gg->grade > 0){ if ($gg->grade > 0) {
if($gi->itemmodule == "assign"){ if ($gi->itemmodule == "assign") {
// If it is an assignment, submit though that interface // If it is an assignment, submit though that interface .
list($c,$cminfo) = get_course_and_cm_from_instance($gi->iteminstance,$gi->itemmodule); list($c, $cminfo) = get_course_and_cm_from_instance($gi->iteminstance, $gi->itemmodule);
$cm_ctx = \context_module::instance($cminfo->id); $cm_ctx = \context_module::instance($cminfo->id);
$a = new \assign($cm_ctx,$cminfo,$c); $a = new \assign($cm_ctx, $cminfo, $c);
$ug = $a->get_user_grade($u->id,true); $ug = $a->get_user_grade($u->id, true);
$ug->grade = grade_floatval($gg->grade); $ug->grade = grade_floatval($gg->grade);
$ug->grader = $USER->id; $ug->grader = $USER->id;
$ug->feedbacktext = nl2br( htmlspecialchars($gg->fb)); $ug->feedbacktext = nl2br( htmlspecialchars($gg->fb));
$ug->feedbackformat = FORMAT_HTML; $ug->feedbackformat = FORMAT_HTML;
//print_r($ug); //print_r($ug);.
if(!$options["dryrun"]){ if (!$options["dryrun"]) {
$a->update_grade($ug); $a->update_grade($ug);
grade_regrade_final_grades($c->id,$u->id,$gi); grade_regrade_final_grades($c->id, $u->id, $gi);
cli_writeln(" ... Stored"); cli_writeln(" ... Stored");
} else { } else {
cli_writeln(" ... (Dry Run)"); cli_writeln(" ... (Dry Run)");
} }
} else { } else {
// Otherwise, set the grade through the manual grading override // Otherwise, set the grade through the manual grading override.
cli_writeln(" ... Cannot store"); cli_writeln(" ... Cannot store");
} }
@ -155,7 +176,7 @@ foreach($plans as $plan){
} }
} }
} }
else else
{ {
cli_writeln(" Skipping since it has not started yet"); cli_writeln(" Skipping since it has not started yet");
} }

View File

@ -1,4 +1,24 @@
<?php <?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/>.
/**
*
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$capabilities = [ $capabilities = [

View File

@ -1,4 +1,24 @@
<?php <?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/>.
/**
*
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$services = [ $services = [
"Competency listing" => [ "Competency listing" => [
@ -34,530 +54,530 @@ $services = [
$functions = [ $functions = [
/*************************** /***************************
* Studyplan functions * Studyplan functions
***************************/ ***************************/
'local_treestudyplan_list_studyplans' => [ //web service function name 'local_treestudyplan_list_studyplans' => [ //web service function name.
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function 'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function.
'methodname' => 'list_studyplans', //external function name 'methodname' => 'list_studyplans', //external function name.
'description' => 'List available studyplans', //human readable description of the web service function 'description' => 'List available studyplans', //human readable description of the web service function.
'type' => 'read', //database rights of the web service function (read, write) 'type' => 'read', //database rights of the web service function (read, write).
'ajax' => true, 'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan, local/treestudyplan:viewuserreports', // Advises the admin which capabilities are required 'capabilities' => 'local/treestudyplan:editstudyplan, local/treestudyplan:viewuserreports', // Advises the admin which capabilities are required.
'loginrequired' => true,
],
'local_treestudyplan_get_studyplan_map' => [ //web service function name
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function
'methodname' => 'get_studyplan_map', //external function name
'description' => 'Retrieve studyplan map', //human readable description of the web service function
'type' => 'read', //database rights of the web service function (read, write)
'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan, local/treestudyplan:viewuserreports', // Advises the admin which capabilities are required
'loginrequired' => true,
],
'local_treestudyplan_get_studyline_map' => [ //web service function name
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function
'methodname' => 'get_studyline_map', //external function name
'description' => 'Retrieve studyline map', //human readable description of the web service function
'type' => 'read', //database rights of the web service function (read, write)
'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required
'loginrequired' => true,
],
'local_treestudyplan_add_studyplan' => [ //web service function name
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function
'methodname' => 'add_studyplan', //external function name
'description' => 'Add studyplan', //human readable description of the web service function
'type' => 'write', //database rights of the web service function (read, write)
'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required
'loginrequired' => true,
],
'local_treestudyplan_add_studyline' => [ //web service function name
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function
'methodname' => 'add_studyline', //external function name
'description' => 'Add studyline', //human readable description of the web service function
'type' => 'write', //database rights of the web service function (read, write)
'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required
'loginrequired' => true,
],
'local_treestudyplan_edit_studyplan' => [ //web service function name
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function
'methodname' => 'edit_studyplan', //external function name
'description' => 'Edit studyplan', //human readable description of the web service function
'type' => 'write', //database rights of the web service function (read, write)
'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required
'loginrequired' => true,
],
'local_treestudyplan_edit_studyline' => [ //web service function name
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function
'methodname' => 'edit_studyline', //external function name
'description' => 'Edit studyline', //human readable description of the web service function
'type' => 'write', //database rights of the web service function (read, write)
'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required
'loginrequired' => true,
],
'local_treestudyplan_delete_studyplan' => [ //web service function name
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function
'methodname' => 'delete_studyplan', //external function name
'description' => 'Delete studyplan', //human readable description of the web service function
'type' => 'write', //database rights of the web service function (read, write)
'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required
'loginrequired' => true,
],
'local_treestudyplan_delete_studyline' => [ //web service function name
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function
'methodname' => 'delete_studyline', //external function name
'description' => 'Delete studyline', //human readable description of the web service function
'type' => 'write', //database rights of the web service function (read, write)
'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required
'loginrequired' => true,
],
'local_treestudyplan_reorder_studylines' => [ //web service function name
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function
'methodname' => 'reorder_studylines', //external function name
'description' => 'Reorder studylines', //human readable description of the web service function
'type' => 'write', //database rights of the web service function (read, write)
'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required
'loginrequired' => true,
],
'local_treestudyplan_get_studyitem' => [ //web service function name
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function
'methodname' => 'get_studyitem', //external function name
'description' => 'Retrieve study item', //human readable description of the web service function
'type' => 'read', //database rights of the web service function (read, write)
'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required
'loginrequired' => true, 'loginrequired' => true,
], ],
'local_treestudyplan_get_studyplan_map' => [ //web service function name.
'local_treestudyplan_add_studyitem' => [ //web service function name 'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function.
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function 'methodname' => 'get_studyplan_map', //external function name.
'methodname' => 'add_studyitem', //external function name 'description' => 'Retrieve studyplan map', //human readable description of the web service function.
'description' => 'Add study item', //human readable description of the web service function 'type' => 'read', //database rights of the web service function (read, write).
'type' => 'write', //database rights of the web service function (read, write)
'ajax' => true, 'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required 'capabilities' => 'local/treestudyplan:editstudyplan, local/treestudyplan:viewuserreports', // Advises the admin which capabilities are required.
'loginrequired' => true, 'loginrequired' => true,
], ],
'local_treestudyplan_edit_studyitem' => [ //web service function name
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function
'methodname' => 'edit_studyitem', //external function name
'description' => 'Edit study item', //human readable description of the web service function
'type' => 'write', //database rights of the web service function (read, write)
'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required
'loginrequired' => true,
],
'local_treestudyplan_reorder_studyitems' => [ //web service function name
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function
'methodname' => 'reorder_studyitems', //external function name
'description' => 'Reorder study items', //human readable description of the web service function
'type' => 'write', //database rights of the web service function (read, write)
'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required
'loginrequired' => true,
],
'local_treestudyplan_delete_studyitem' => [ //web service function name 'local_treestudyplan_get_studyline_map' => [ //web service function name.
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function 'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function.
'methodname' => 'delete_studyitem', //external function name 'methodname' => 'get_studyline_map', //external function name.
'description' => 'Delete study item', //human readable description of the web service function 'description' => 'Retrieve studyline map', //human readable description of the web service function.
'type' => 'write', //database rights of the web service function (read, write) 'type' => 'read', //database rights of the web service function (read, write).
'ajax' => true, 'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required 'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required.
'loginrequired' => true, 'loginrequired' => true,
], ],
'local_treestudyplan_connect_studyitems' => [ //web service function name
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function 'local_treestudyplan_add_studyplan' => [ //web service function name.
'methodname' => 'connect_studyitems', //external function name 'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function.
'description' => 'Connect study items', //human readable description of the web service function 'methodname' => 'add_studyplan', //external function name.
'type' => 'write', //database rights of the web service function (read, write) 'description' => 'Add studyplan', //human readable description of the web service function.
'type' => 'write', //database rights of the web service function (read, write).
'ajax' => true, 'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required 'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required.
'loginrequired' => true, 'loginrequired' => true,
], ],
'local_treestudyplan_disconnect_studyitems' => [ //web service function name
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function 'local_treestudyplan_add_studyline' => [ //web service function name.
'methodname' => 'disconnect_studyitems', //external function name 'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function.
'description' => 'Disconnect study items', //human readable description of the web service function 'methodname' => 'add_studyline', //external function name.
'type' => 'write', //database rights of the web service function (read, write) 'description' => 'Add studyline', //human readable description of the web service function.
'type' => 'write', //database rights of the web service function (read, write).
'ajax' => true, 'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required 'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required.
'loginrequired' => true, 'loginrequired' => true,
], ],
'local_treestudyplan_edit_studyplan' => [ //web service function name.
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function.
'methodname' => 'edit_studyplan', //external function name.
'description' => 'Edit studyplan', //human readable description of the web service function.
'type' => 'write', //database rights of the web service function (read, write).
'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required.
'loginrequired' => true,
],
'local_treestudyplan_edit_studyline' => [ //web service function name.
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function.
'methodname' => 'edit_studyline', //external function name.
'description' => 'Edit studyline', //human readable description of the web service function.
'type' => 'write', //database rights of the web service function (read, write).
'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required.
'loginrequired' => true,
],
'local_treestudyplan_delete_studyplan' => [ //web service function name.
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function.
'methodname' => 'delete_studyplan', //external function name.
'description' => 'Delete studyplan', //human readable description of the web service function.
'type' => 'write', //database rights of the web service function (read, write).
'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required.
'loginrequired' => true,
],
'local_treestudyplan_delete_studyline' => [ //web service function name.
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function.
'methodname' => 'delete_studyline', //external function name.
'description' => 'Delete studyline', //human readable description of the web service function.
'type' => 'write', //database rights of the web service function (read, write).
'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required.
'loginrequired' => true,
],
'local_treestudyplan_reorder_studylines' => [ //web service function name.
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function.
'methodname' => 'reorder_studylines', //external function name.
'description' => 'Reorder studylines', //human readable description of the web service function.
'type' => 'write', //database rights of the web service function (read, write).
'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required.
'loginrequired' => true,
],
'local_treestudyplan_get_studyitem' => [ //web service function name.
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function.
'methodname' => 'get_studyitem', //external function name.
'description' => 'Retrieve study item', //human readable description of the web service function.
'type' => 'read', //database rights of the web service function (read, write).
'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required.
'loginrequired' => true,
],
'local_treestudyplan_add_studyitem' => [ //web service function name.
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function.
'methodname' => 'add_studyitem', //external function name.
'description' => 'Add study item', //human readable description of the web service function.
'type' => 'write', //database rights of the web service function (read, write).
'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required.
'loginrequired' => true,
],
'local_treestudyplan_edit_studyitem' => [ //web service function name.
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function.
'methodname' => 'edit_studyitem', //external function name.
'description' => 'Edit study item', //human readable description of the web service function.
'type' => 'write', //database rights of the web service function (read, write).
'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required.
'loginrequired' => true,
],
'local_treestudyplan_reorder_studyitems' => [ //web service function name.
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function.
'methodname' => 'reorder_studyitems', //external function name.
'description' => 'Reorder study items', //human readable description of the web service function.
'type' => 'write', //database rights of the web service function (read, write).
'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required.
'loginrequired' => true,
],
'local_treestudyplan_delete_studyitem' => [ //web service function name.
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function.
'methodname' => 'delete_studyitem', //external function name.
'description' => 'Delete study item', //human readable description of the web service function.
'type' => 'write', //database rights of the web service function (read, write).
'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required.
'loginrequired' => true,
],
'local_treestudyplan_connect_studyitems' => [ //web service function name.
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function.
'methodname' => 'connect_studyitems', //external function name.
'description' => 'Connect study items', //human readable description of the web service function.
'type' => 'write', //database rights of the web service function (read, write).
'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required.
'loginrequired' => true,
],
'local_treestudyplan_disconnect_studyitems' => [ //web service function name.
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function.
'methodname' => 'disconnect_studyitems', //external function name.
'description' => 'Disconnect study items', //human readable description of the web service function.
'type' => 'write', //database rights of the web service function (read, write).
'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required.
'loginrequired' => true,
],
/*************************** /***************************
* Badge functions * Badge functions
***************************/ ***************************/
'local_treestudyplan_list_badges' => [ //web service function name 'local_treestudyplan_list_badges' => [ //web service function name.
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function 'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function.
'methodname' => 'list_badges', //external function name 'methodname' => 'list_badges', //external function name.
'description' => 'List availabel site badges', //human readable description of the web service function 'description' => 'List availabel site badges', //human readable description of the web service function.
'type' => 'read', //database rights of the web service function (read, write) 'type' => 'read', //database rights of the web service function (read, write).
'ajax' => true, 'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required 'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required.
'loginrequired' => true, 'loginrequired' => true,
], ],
/*************************** /***************************
* Association functions * Association functions
***************************/ ***************************/
'local_treestudyplan_list_cohort' => [ //web service function name 'local_treestudyplan_list_cohort' => [ //web service function name.
'classname' => '\local_treestudyplan\associationservice', //class containing the external function 'classname' => '\local_treestudyplan\associationservice', //class containing the external function.
'methodname' => 'list_cohort', //external function name 'methodname' => 'list_cohort', //external function name.
'description' => 'List available cohorts', //human readable description of the web service function 'description' => 'List available cohorts', //human readable description of the web service function.
'type' => 'read', //database rights of the web service function (read, write) 'type' => 'read', //database rights of the web service function (read, write).
'ajax' => true, 'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required 'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required.
'loginrequired' => true, 'loginrequired' => true,
], ],
'local_treestudyplan_find_user' => [ //web service function name 'local_treestudyplan_find_user' => [ //web service function name.
'classname' => '\local_treestudyplan\associationservice', //class containing the external function 'classname' => '\local_treestudyplan\associationservice', //class containing the external function.
'methodname' => 'find_user', //external function name 'methodname' => 'find_user', //external function name.
'description' => 'Find user', //human readable description of the web service function 'description' => 'Find user', //human readable description of the web service function.
'type' => 'read', //database rights of the web service function (read, write) 'type' => 'read', //database rights of the web service function (read, write).
'ajax' => true, 'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required 'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required.
'loginrequired' => true, 'loginrequired' => true,
], ],
'local_treestudyplan_connect_cohort' => [ //web service function name 'local_treestudyplan_connect_cohort' => [ //web service function name.
'classname' => '\local_treestudyplan\associationservice', //class containing the external function 'classname' => '\local_treestudyplan\associationservice', //class containing the external function.
'methodname' => 'connect_cohort', //external function name 'methodname' => 'connect_cohort', //external function name.
'description' => 'Connect cohort to studyplan', //human readable description of the web service function 'description' => 'Connect cohort to studyplan', //human readable description of the web service function.
'type' => 'read', //database rights of the web service function (read, write) 'type' => 'read', //database rights of the web service function (read, write).
'ajax' => true, 'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required 'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required.
'loginrequired' => true, 'loginrequired' => true,
], ],
'local_treestudyplan_disconnect_cohort' => [ //web service function name 'local_treestudyplan_disconnect_cohort' => [ //web service function name.
'classname' => '\local_treestudyplan\associationservice', //class containing the external function 'classname' => '\local_treestudyplan\associationservice', //class containing the external function.
'methodname' => 'disconnect_cohort', //external function name 'methodname' => 'disconnect_cohort', //external function name.
'description' => 'Disconnect cohort from study plan', //human readable description of the web service function 'description' => 'Disconnect cohort from study plan', //human readable description of the web service function.
'type' => 'read', //database rights of the web service function (read, write) 'type' => 'read', //database rights of the web service function (read, write).
'ajax' => true, 'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required 'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required.
'loginrequired' => true, 'loginrequired' => true,
], ],
'local_treestudyplan_connect_user' => [ //web service function name 'local_treestudyplan_connect_user' => [ //web service function name.
'classname' => '\local_treestudyplan\associationservice', //class containing the external function 'classname' => '\local_treestudyplan\associationservice', //class containing the external function.
'methodname' => 'connect_user', //external function name 'methodname' => 'connect_user', //external function name.
'description' => 'Connect user to study plan', //human readable description of the web service function 'description' => 'Connect user to study plan', //human readable description of the web service function.
'type' => 'read', //database rights of the web service function (read, write) 'type' => 'read', //database rights of the web service function (read, write).
'ajax' => true, 'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required 'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required.
'loginrequired' => true, 'loginrequired' => true,
], ],
'local_treestudyplan_disconnect_user' => [ //web service function name 'local_treestudyplan_disconnect_user' => [ //web service function name.
'classname' => '\local_treestudyplan\associationservice', //class containing the external function 'classname' => '\local_treestudyplan\associationservice', //class containing the external function.
'methodname' => 'disconnect_user', //external function name 'methodname' => 'disconnect_user', //external function name.
'description' => 'Disconnect user from studyplan', //human readable description of the web service function 'description' => 'Disconnect user from studyplan', //human readable description of the web service function.
'type' => 'read', //database rights of the web service function (read, write) 'type' => 'read', //database rights of the web service function (read, write).
'ajax' => true, 'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required 'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required.
'loginrequired' => true, 'loginrequired' => true,
], ],
'local_treestudyplan_associated_users' => [ //web service function name 'local_treestudyplan_associated_users' => [ //web service function name.
'classname' => '\local_treestudyplan\associationservice', //class containing the external function 'classname' => '\local_treestudyplan\associationservice', //class containing the external function.
'methodname' => 'associated_users', //external function name 'methodname' => 'associated_users', //external function name.
'description' => 'List users associated with a studyplan', //human readable description of the web service function 'description' => 'List users associated with a studyplan', //human readable description of the web service function.
'type' => 'read', //database rights of the web service function (read, write) 'type' => 'read', //database rights of the web service function (read, write).
'ajax' => true, 'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required 'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required.
'loginrequired' => true, 'loginrequired' => true,
], ],
'local_treestudyplan_associated_cohorts' => [ //web service function name 'local_treestudyplan_associated_cohorts' => [ //web service function name.
'classname' => '\local_treestudyplan\associationservice', //class containing the external function 'classname' => '\local_treestudyplan\associationservice', //class containing the external function.
'methodname' => 'associated_cohorts', //external function name 'methodname' => 'associated_cohorts', //external function name.
'description' => 'List cohorts associated with a studyplan', //human readable description of the web service function 'description' => 'List cohorts associated with a studyplan', //human readable description of the web service function.
'type' => 'read', //database rights of the web service function (read, write) 'type' => 'read', //database rights of the web service function (read, write).
'ajax' => true, 'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required 'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required.
'loginrequired' => true, 'loginrequired' => true,
], ],
'local_treestudyplan_list_user_studyplans' => [ //web service function name 'local_treestudyplan_list_user_studyplans' => [ //web service function name.
'classname' => '\local_treestudyplan\studentstudyplanservice', //class containing the external function 'classname' => '\local_treestudyplan\studentstudyplanservice', //class containing the external function.
'methodname' => 'list_user_studyplans', //external function name 'methodname' => 'list_user_studyplans', //external function name.
'description' => 'List user studyplans', //human readable description of the web service function 'description' => 'List user studyplans', //human readable description of the web service function.
'type' => 'read', //database rights of the web service function (read, write) 'type' => 'read', //database rights of the web service function (read, write).
'ajax' => true, 'ajax' => true,
'capabilities' => 'local/treestudyplan:viewuserreports', // Advises the admin which capabilities are required 'capabilities' => 'local/treestudyplan:viewuserreports', // Advises the admin which capabilities are required.
'loginrequired' => true, 'loginrequired' => true,
], ],
'local_treestudyplan_get_user_studyplans' => [ //web service function name 'local_treestudyplan_get_user_studyplans' => [ //web service function name.
'classname' => '\local_treestudyplan\studentstudyplanservice', //class containing the external function 'classname' => '\local_treestudyplan\studentstudyplanservice', //class containing the external function.
'methodname' => 'get_user_studyplans', //external function name 'methodname' => 'get_user_studyplans', //external function name.
'description' => 'Retrieve user studyplan', //human readable description of the web service function 'description' => 'Retrieve user studyplan', //human readable description of the web service function.
'type' => 'read', //database rights of the web service function (read, write) 'type' => 'read', //database rights of the web service function (read, write).
'ajax' => true, 'ajax' => true,
'capabilities' => 'local/treestudyplan:viewuserreports', // Advises the admin which capabilities are required 'capabilities' => 'local/treestudyplan:viewuserreports', // Advises the admin which capabilities are required.
'loginrequired' => true, 'loginrequired' => true,
], ],
'local_treestudyplan_get_user_studyplan' => [ //web service function name 'local_treestudyplan_get_user_studyplan' => [ //web service function name.
'classname' => '\local_treestudyplan\studentstudyplanservice', //class containing the external function 'classname' => '\local_treestudyplan\studentstudyplanservice', //class containing the external function.
'methodname' => 'get_user_studyplan', //external function name 'methodname' => 'get_user_studyplan', //external function name.
'description' => 'Retrieve user studyplan', //human readable description of the web service function 'description' => 'Retrieve user studyplan', //human readable description of the web service function.
'type' => 'read', //database rights of the web service function (read, write) 'type' => 'read', //database rights of the web service function (read, write).
'ajax' => true, 'ajax' => true,
'capabilities' => 'local/treestudyplan:viewuserreports', // Advises the admin which capabilities are required 'capabilities' => 'local/treestudyplan:viewuserreports', // Advises the admin which capabilities are required.
'loginrequired' => true, 'loginrequired' => true,
], ],
'local_treestudyplan_get_invited_studyplan' => [ //web service function name 'local_treestudyplan_get_invited_studyplan' => [ //web service function name.
'classname' => '\local_treestudyplan\studentstudyplanservice', //class containing the external function 'classname' => '\local_treestudyplan\studentstudyplanservice', //class containing the external function.
'methodname' => 'get_invited_studyplan', //external function name 'methodname' => 'get_invited_studyplan', //external function name.
'description' => 'Retrieve user studyplan based on invite', //human readable description of the web service function 'description' => 'Retrieve user studyplan based on invite', //human readable description of the web service function.
'type' => 'read', //database rights of the web service function (read, write) 'type' => 'read', //database rights of the web service function (read, write).
'ajax' => true, 'ajax' => true,
'capabilities' => '', // Advises the admin which capabilities are required 'capabilities' => '', // Advises the admin which capabilities are required.
'loginrequired' => false, 'loginrequired' => false,
], ],
'local_treestudyplan_list_own_studyplans' => [ //web service function name 'local_treestudyplan_list_own_studyplans' => [ //web service function name.
'classname' => '\local_treestudyplan\studentstudyplanservice', //class containing the external function 'classname' => '\local_treestudyplan\studentstudyplanservice', //class containing the external function.
'methodname' => 'list_own_studyplans', //external function name 'methodname' => 'list_own_studyplans', //external function name.
'description' => 'List own studyplans', //human readable description of the web service function 'description' => 'List own studyplans', //human readable description of the web service function.
'type' => 'read', //database rights of the web service function (read, write) 'type' => 'read', //database rights of the web service function (read, write).
'ajax' => true, 'ajax' => true,
'capabilities' => '', // Advises the admin which capabilities are required 'capabilities' => '', // Advises the admin which capabilities are required.
'loginrequired' => true,
],
'local_treestudyplan_get_own_studyplan' => [ //web service function name
'classname' => '\local_treestudyplan\studentstudyplanservice', //class containing the external function
'methodname' => 'get_own_studyplan', //external function name
'description' => 'Retrieve own studyplan', //human readable description of the web service function
'type' => 'read', //database rights of the web service function (read, write)
'ajax' => true,
'capabilities' => '', // Advises the admin which capabilities are required
'loginrequired' => true,
],
'local_treestudyplan_map_categories' => [ //web service function name
'classname' => '\local_treestudyplan\courseservice', //class containing the external function
'methodname' => 'map_categories', //external function name
'description' => 'List available root categories', //human readable description of the web service function
'type' => 'read', //database rights of the web service function (read, write)
'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required
'loginrequired' => true,
],
'local_treestudyplan_get_category' => [ //web service function name
'classname' => '\local_treestudyplan\courseservice', //class containing the external function
'methodname' => 'get_category', //external function name
'description' => 'Get details for specified category', //human readable description of the web service function
'type' => 'read', //database rights of the web service function (read, write)
'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required
'loginrequired' => true,
],
'local_treestudyplan_include_grade' => [ //web service function name
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function
'methodname' => 'include_grade', //external function name
'description' => 'Include gradable in result', //human readable description of the web service function
'type' => 'read', //database rights of the web service function (read, write)
'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan, local/treestudyplan:selectowngradables', // Advises the admin which capabilities are required
'loginrequired' => true, 'loginrequired' => true,
], ],
'local_treestudyplan_all_associated' => [ //web service function name 'local_treestudyplan_get_own_studyplan' => [ //web service function name.
'classname' => '\local_treestudyplan\associationservice', //class containing the external function 'classname' => '\local_treestudyplan\studentstudyplanservice', //class containing the external function.
'methodname' => 'all_associated', //external function name 'methodname' => 'get_own_studyplan', //external function name.
'description' => 'List associated users', //human readable description of the web service function 'description' => 'Retrieve own studyplan', //human readable description of the web service function.
'type' => 'read', //database rights of the web service function (read, write) 'type' => 'read', //database rights of the web service function (read, write).
'ajax' => true, 'ajax' => true,
'capabilities' => 'local/treestudyplan:viewuserreports', // Advises the admin which capabilities are required 'capabilities' => '', // Advises the admin which capabilities are required.
'loginrequired' => true,
],
'local_treestudyplan_list_aggregators' => [ //web service function name
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function
'methodname' => 'list_aggregators', //external function name
'description' => 'List available aggregators', //human readable description of the web service function
'type' => 'read', //database rights of the web service function (read, write)
'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required
'loginrequired' => true,
],
'local_treestudyplan_disable_autoenddate' => [ //web service function name
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function
'methodname' => 'disable_autoenddate', //external function name
'description' => 'Disable automatic end dates on courses in the study plan', //human readable description of the web service function
'type' => 'write', //database rights of the web service function (read, write)
'ajax' => true,
'capabilities' => 'local/treestudyplan:forcescales', // Advises the admin which capabilities are required
'loginrequired' => true,
],
'local_treestudyplan_force_studyplan_scale' => [ //web service function name
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function
'methodname' => 'force_studyplan_scale', //external function name
'description' => 'Change all associated gradables to the chosen scale if possible', //human readable description of the web service function
'type' => 'write', //database rights of the web service function (read, write)
'ajax' => true,
'capabilities' => 'local/treestudyplan:forcescales', // Advises the admin which capabilities are required
'loginrequired' => true,
],
'local_treestudyplan_list_scales' => [ //web service function name
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function
'methodname' => 'list_scales', //external function name
'description' => 'List system scales', //human readable description of the web service function
'type' => 'write', //database rights of the web service function (read, write)
'ajax' => true,
'capabilities' => 'local/treestudyplan:forcescales', // Advises the admin which capabilities are required
'loginrequired' => true, 'loginrequired' => true,
], ],
'local_treestudyplan_duplicate_plan' => [ //web service function name 'local_treestudyplan_map_categories' => [ //web service function name.
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function 'classname' => '\local_treestudyplan\courseservice', //class containing the external function.
'methodname' => 'duplicate_plan', //external function name 'methodname' => 'map_categories', //external function name.
'description' => 'Copy studyplan', //human readable description of the web service function 'description' => 'List available root categories', //human readable description of the web service function.
'type' => 'write', //database rights of the web service function (read, write) 'type' => 'read', //database rights of the web service function (read, write).
'ajax' => true, 'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required 'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required.
'loginrequired' => true, 'loginrequired' => true,
], ],
'local_treestudyplan_export_plan' => [ //web service function name 'local_treestudyplan_get_category' => [ //web service function name.
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function 'classname' => '\local_treestudyplan\courseservice', //class containing the external function.
'methodname' => 'export_plan', //external function name 'methodname' => 'get_category', //external function name.
'description' => 'Export study plan', //human readable description of the web service function 'description' => 'Get details for specified category', //human readable description of the web service function.
'type' => 'read', //database rights of the web service function (read, write) 'type' => 'read', //database rights of the web service function (read, write).
'ajax' => true, 'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required 'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required.
'loginrequired' => true, 'loginrequired' => true,
], ],
'local_treestudyplan_export_studylines' => [ //web service function name 'local_treestudyplan_include_grade' => [ //web service function name.
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function 'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function.
'methodname' => 'export_studylines', //external function name 'methodname' => 'include_grade', //external function name.
'description' => 'Export study plan', //human readable description of the web service function 'description' => 'Include gradable in result', //human readable description of the web service function.
'type' => 'read', //database rights of the web service function (read, write) 'type' => 'read', //database rights of the web service function (read, write).
'ajax' => true, 'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required 'capabilities' => 'local/treestudyplan:editstudyplan, local/treestudyplan:selectowngradables', // Advises the admin which capabilities are required.
'loginrequired' => true,
],
'local_treestudyplan_import_plan' => [ //web service function name
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function
'methodname' => 'import_plan', //external function name
'description' => 'Import study plan', //human readable description of the web service function
'type' => 'write', //database rights of the web service function (read, write)
'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required
'loginrequired' => true, 'loginrequired' => true,
], ],
'local_treestudyplan_import_studylines' => [ //web service function name 'local_treestudyplan_all_associated' => [ //web service function name.
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function 'classname' => '\local_treestudyplan\associationservice', //class containing the external function.
'methodname' => 'import_studylines', //external function name 'methodname' => 'all_associated', //external function name.
'description' => 'Import study plan', //human readable description of the web service function 'description' => 'List associated users', //human readable description of the web service function.
'type' => 'write', //database rights of the web service function (read, write) 'type' => 'read', //database rights of the web service function (read, write).
'ajax' => true, 'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required 'capabilities' => 'local/treestudyplan:viewuserreports', // Advises the admin which capabilities are required.
'loginrequired' => true, 'loginrequired' => true,
], ],
'local_treestudyplan_edit_period' => [ //web service function name 'local_treestudyplan_list_aggregators' => [ //web service function name.
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function 'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function.
'methodname' => 'edit_period', //external function name 'methodname' => 'list_aggregators', //external function name.
'description' => 'Edit period name and timing', //human readable description of the web service function 'description' => 'List available aggregators', //human readable description of the web service function.
'type' => 'write', //database rights of the web service function (read, write) 'type' => 'read', //database rights of the web service function (read, write).
'ajax' => true, 'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required 'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required.
'loginrequired' => true,
],
'local_treestudyplan_submit_cm_editform' => [ //web service function name
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function
'methodname' => 'submit_cm_editform', //external function name
'description' => 'Submit course module edit form', //human readable description of the web service function
'type' => 'write', //database rights of the web service function (read, write)
'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required
'loginrequired' => true, 'loginrequired' => true,
], ],
'local_treestudyplan_get_teaching_studyplans' => [ //web service function name 'local_treestudyplan_disable_autoenddate' => [ //web service function name.
'classname' => '\local_treestudyplan\studentstudyplanservice', //class containing the external function 'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function.
'methodname' => 'get_teaching_studyplans', //external function name 'methodname' => 'disable_autoenddate', //external function name.
'description' => 'Get the studyplans I currently teach in', //human readable description of the web service function 'description' => 'Disable automatic end dates on courses in the study plan', //human readable description of the web service function.
'type' => 'read', //database rights of the web service function (read, write) 'type' => 'write', //database rights of the web service function (read, write).
'ajax' => true, 'ajax' => true,
'capabilities' => 'local/treestudyplan:viewuserreports', // Advises the admin which capabilities are required 'capabilities' => 'local/treestudyplan:forcescales', // Advises the admin which capabilities are required.
'loginrequired' => true, 'loginrequired' => true,
], ],
'local_treestudyplan_list_accessible_categories' => [ //web service function name 'local_treestudyplan_force_studyplan_scale' => [ //web service function name.
'classname' => '\local_treestudyplan\courseservice', //class containing the external function 'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function.
'methodname' => 'list_accessible_categories', //external function name 'methodname' => 'force_studyplan_scale', //external function name.
'description' => 'Get categories accessible to the current user', //human readable description of the web service function 'description' => 'Change all associated gradables to the chosen scale if possible', //human readable description of the web service function.
'type' => 'read', //database rights of the web service function (read, write) 'type' => 'write', //database rights of the web service function (read, write).
'ajax' => true,
'capabilities' => 'local/treestudyplan:forcescales', // Advises the admin which capabilities are required.
'loginrequired' => true,
],
'local_treestudyplan_list_scales' => [ //web service function name.
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function.
'methodname' => 'list_scales', //external function name.
'description' => 'List system scales', //human readable description of the web service function.
'type' => 'write', //database rights of the web service function (read, write).
'ajax' => true,
'capabilities' => 'local/treestudyplan:forcescales', // Advises the admin which capabilities are required.
'loginrequired' => true,
],
'local_treestudyplan_duplicate_plan' => [ //web service function name.
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function.
'methodname' => 'duplicate_plan', //external function name.
'description' => 'Copy studyplan', //human readable description of the web service function.
'type' => 'write', //database rights of the web service function (read, write).
'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required.
'loginrequired' => true,
],
'local_treestudyplan_export_plan' => [ //web service function name.
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function.
'methodname' => 'export_plan', //external function name.
'description' => 'Export study plan', //human readable description of the web service function.
'type' => 'read', //database rights of the web service function (read, write).
'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required.
'loginrequired' => true,
],
'local_treestudyplan_export_studylines' => [ //web service function name.
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function.
'methodname' => 'export_studylines', //external function name.
'description' => 'Export study plan', //human readable description of the web service function.
'type' => 'read', //database rights of the web service function (read, write).
'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required.
'loginrequired' => true,
],
'local_treestudyplan_import_plan' => [ //web service function name.
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function.
'methodname' => 'import_plan', //external function name.
'description' => 'Import study plan', //human readable description of the web service function.
'type' => 'write', //database rights of the web service function (read, write).
'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required.
'loginrequired' => true,
],
'local_treestudyplan_import_studylines' => [ //web service function name.
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function.
'methodname' => 'import_studylines', //external function name.
'description' => 'Import study plan', //human readable description of the web service function.
'type' => 'write', //database rights of the web service function (read, write).
'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required.
'loginrequired' => true,
],
'local_treestudyplan_edit_period' => [ //web service function name.
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function.
'methodname' => 'edit_period', //external function name.
'description' => 'Edit period name and timing', //human readable description of the web service function.
'type' => 'write', //database rights of the web service function (read, write).
'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required.
'loginrequired' => true,
],
'local_treestudyplan_submit_cm_editform' => [ //web service function name.
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function.
'methodname' => 'submit_cm_editform', //external function name.
'description' => 'Submit course module edit form', //human readable description of the web service function.
'type' => 'write', //database rights of the web service function (read, write).
'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required.
'loginrequired' => true,
],
'local_treestudyplan_get_teaching_studyplans' => [ //web service function name.
'classname' => '\local_treestudyplan\studentstudyplanservice', //class containing the external function.
'methodname' => 'get_teaching_studyplans', //external function name.
'description' => 'Get the studyplans I currently teach in', //human readable description of the web service function.
'type' => 'read', //database rights of the web service function (read, write).
'ajax' => true,
'capabilities' => 'local/treestudyplan:viewuserreports', // Advises the admin which capabilities are required.
'loginrequired' => true,
],
'local_treestudyplan_list_accessible_categories' => [ //web service function name.
'classname' => '\local_treestudyplan\courseservice', //class containing the external function.
'methodname' => 'list_accessible_categories', //external function name.
'description' => 'Get categories accessible to the current user', //human readable description of the web service function.
'type' => 'read', //database rights of the web service function (read, write).
'ajax' => true, 'ajax' => true,
'loginrequired' => true, 'loginrequired' => true,
], ],
'local_treestudyplan_list_used_categories' => [ //web service function name 'local_treestudyplan_list_used_categories' => [ //web service function name.
'classname' => '\local_treestudyplan\courseservice', //class containing the external function 'classname' => '\local_treestudyplan\courseservice', //class containing the external function.
'methodname' => 'list_used_categories', //external function name 'methodname' => 'list_used_categories', //external function name.
'description' => 'Get categories hosting a studyplan', //human readable description of the web service function 'description' => 'Get categories hosting a studyplan', //human readable description of the web service function.
'type' => 'read', //database rights of the web service function (read, write) 'type' => 'read', //database rights of the web service function (read, write).
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required 'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required.
'ajax' => true, 'ajax' => true,
'loginrequired' => true, 'loginrequired' => true,
], ],
'local_treestudyplan_scan_badge_progress' => [ //web service function name 'local_treestudyplan_scan_badge_progress' => [ //web service function name.
'classname' => '\local_treestudyplan\courseservice', //class containing the external function 'classname' => '\local_treestudyplan\courseservice', //class containing the external function.
'methodname' => 'scan_badge_progress', //external function name 'methodname' => 'scan_badge_progress', //external function name.
'description' => 'Scan progress of students in attaining badge', //human readable description of the web service function 'description' => 'Scan progress of students in attaining badge', //human readable description of the web service function.
'type' => 'read', //database rights of the web service function (read, write) 'type' => 'read', //database rights of the web service function (read, write).
'capabilities' => 'local/treestudyplan:viewuserreports', // Advises the admin which capabilities are required 'capabilities' => 'local/treestudyplan:viewuserreports', // Advises the admin which capabilities are required.
'ajax' => true, 'ajax' => true,
'loginrequired' => true, 'loginrequired' => true,
], ],
'local_treestudyplan_scan_completion_progress' => [ //web service function name 'local_treestudyplan_scan_completion_progress' => [ //web service function name.
'classname' => '\local_treestudyplan\courseservice', //class containing the external function 'classname' => '\local_treestudyplan\courseservice', //class containing the external function.
'methodname' => 'scan_completion_progress', //external function name 'methodname' => 'scan_completion_progress', //external function name.
'description' => 'Scan progress of students in attaining completions', //human readable description of the web service function 'description' => 'Scan progress of students in attaining completions', //human readable description of the web service function.
'type' => 'read', //database rights of the web service function (read, write) 'type' => 'read', //database rights of the web service function (read, write).
'capabilities' => 'local/treestudyplan:viewuserreports', // Advises the admin which capabilities are required 'capabilities' => 'local/treestudyplan:viewuserreports', // Advises the admin which capabilities are required.
'ajax' => true, 'ajax' => true,
'loginrequired' => true, 'loginrequired' => true,
], ],
'local_treestudyplan_scan_grade_progress' => [ //web service function name 'local_treestudyplan_scan_grade_progress' => [ //web service function name.
'classname' => '\local_treestudyplan\courseservice', //class containing the external function 'classname' => '\local_treestudyplan\courseservice', //class containing the external function.
'methodname' => 'scan_grade_progress', //external function name 'methodname' => 'scan_grade_progress', //external function name.
'description' => 'Scan progress of students in attaining grades', //human readable description of the web service function 'description' => 'Scan progress of students in attaining grades', //human readable description of the web service function.
'type' => 'read', //database rights of the web service function (read, write) 'type' => 'read', //database rights of the web service function (read, write).
'capabilities' => 'local/treestudyplan:viewuserreports', // Advises the admin which capabilities are required 'capabilities' => 'local/treestudyplan:viewuserreports', // Advises the admin which capabilities are required.
'ajax' => true, 'ajax' => true,
'loginrequired' => true, 'loginrequired' => true,
], ],
'local_treestudyplan_cascade_cohortsync' => [ //web service function name 'local_treestudyplan_cascade_cohortsync' => [ //web service function name.
'classname' => '\local_treestudyplan\associationservice', //class containing the external function 'classname' => '\local_treestudyplan\associationservice', //class containing the external function.
'methodname' => 'cascade_cohortsync', //external function name 'methodname' => 'cascade_cohortsync', //external function name.
'description' => 'Sync cohortsync to studyplan association', //human readable description of the web service function 'description' => 'Sync cohortsync to studyplan association', //human readable description of the web service function.
'type' => 'read', //database rights of the web service function (read, write) 'type' => 'read', //database rights of the web service function (read, write).
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required 'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required.
'ajax' => true, 'ajax' => true,
'loginrequired' => true, 'loginrequired' => true,
], ],
'local_treestudyplan_course_period_timing' => [ //web service function name 'local_treestudyplan_course_period_timing' => [ //web service function name.
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function 'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function.
'methodname' => 'course_period_timing', //external function name 'methodname' => 'course_period_timing', //external function name.
'description' => 'Chenge course start and end times to match period', //human readable description of the web service function 'description' => 'Chenge course start and end times to match period', //human readable description of the web service function.
'type' => 'read', //database rights of the web service function (read, write) 'type' => 'read', //database rights of the web service function (read, write).
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required 'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required.
'ajax' => true, 'ajax' => true,
'loginrequired' => true, 'loginrequired' => true,
], ],
'local_treestudyplan_set_studyitem_span' => [ //web service function name 'local_treestudyplan_set_studyitem_span' => [ //web service function name.
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function 'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function.
'methodname' => 'set_studyitem_span', //external function name 'methodname' => 'set_studyitem_span', //external function name.
'description' => 'Change the span of a course item', //human readable description of the web service function 'description' => 'Change the span of a course item', //human readable description of the web service function.
'type' => 'read', //database rights of the web service function (read, write) 'type' => 'read', //database rights of the web service function (read, write).
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required 'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required.
'ajax' => true, 'ajax' => true,
'loginrequired' => true, 'loginrequired' => true,
], ],
]; ];

View File

@ -1,4 +1,25 @@
<?php <?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/>.
/**
*
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$tasks = [ $tasks = [
[ [
'classname' => 'local_treestudyplan\task\autocohortsync', 'classname' => 'local_treestudyplan\task\autocohortsync',

View File

@ -1,4 +1,25 @@
<?php <?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/>.
/**
*
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
function xmldb_local_treestudyplan_upgrade($oldversion) { function xmldb_local_treestudyplan_upgrade($oldversion) {
global $DB; global $DB;
$dbman = $DB->get_manager(); $dbman = $DB->get_manager();
@ -266,9 +287,9 @@ function xmldb_local_treestudyplan_upgrade($oldversion) {
if (!$dbman->table_exists($table)) { if (!$dbman->table_exists($table)) {
$dbman->create_table($table); $dbman->create_table($table);
// Create a page 0 with it's data copied // Create a page 0 with it's data copied.
$plans = $DB->get_recordset("local_treestudyplan"); $plans = $DB->get_recordset("local_treestudyplan");
foreach($plans as $p){ foreach ($plans as $p) {
$o = [ "studyplan_id" => $p->id, $o = [ "studyplan_id" => $p->id,
"periods" => $p->slots, "periods" => $p->slots,
"fullname" => $p->name, "fullname" => $p->name,
@ -277,17 +298,17 @@ function xmldb_local_treestudyplan_upgrade($oldversion) {
"startdate" => $p->startdate, "startdate" => $p->startdate,
"enddate" => $p->enddate, "enddate" => $p->enddate,
]; ];
$pageid = $DB->insert_record("local_treestudyplan_page",$o); $pageid = $DB->insert_record("local_treestudyplan_page", $o);
// Now find all studyline references to the studyplan // Now find all studyline references to the studyplan .
// And update their record to reference the page instead of the plan // And update their record to reference the page instead of the plan.
$lines = $DB->get_recordset("local_treestudyplan_line",["studyplan_id" => $p->id]); $lines = $DB->get_recordset("local_treestudyplan_line", ["studyplan_id" => $p->id]);
foreach($lines as $l){ foreach ($lines as $l) {
$lo = [ $lo = [
"id" => $l->id, "id" => $l->id,
"page_id"=> $pageid, "page_id"=> $pageid,
]; ];
$DB->update_record("local_treestudyplan_line",$lo); $DB->update_record("local_treestudyplan_line", $lo);
} }
$lines->close(); $lines->close();
} }
@ -347,16 +368,16 @@ function xmldb_local_treestudyplan_upgrade($oldversion) {
if (!$dbman->table_exists($table)) { if (!$dbman->table_exists($table)) {
$dbman->create_table($table); $dbman->create_table($table);
// Create a period page for all slots in the study // Create a period page for all slots in the study.
$recordset = $DB->get_recordset("local_treestudyplan_page"); $recordset = $DB->get_recordset("local_treestudyplan_page");
foreach($recordset as $r){ foreach ($recordset as $r) {
// Make a best guess for the periods based on the specified period for the plan and the // Make a best guess for the periods based on the specified period for the plan and the .
$pcount = $r->periods; $pcount = $r->periods;
$ystart = strtotime($r->startdate)+0; $ystart = strtotime($r->startdate)+0;
$yend = strtotime($r->enddate)+0; $yend = strtotime($r->enddate)+0;
if($yend < $ystart){ if ($yend < $ystart) {
// If no end time is given, assume a year duration for period calculations // If no end time is given, assume a year duration for period calculations.
$ydelta = (365*24*60*60)/$pcount; $ydelta = (365*24*60*60)/$pcount;
} }
else{ else{
@ -364,19 +385,19 @@ function xmldb_local_treestudyplan_upgrade($oldversion) {
} }
$ptime = $ydelta / $pcount; $ptime = $ydelta / $pcount;
for($i=0; $i < $pcount; $i++){ for($i=0; $i < $pcount; $i++) {
$pnum = $i+1; $pnum = $i+1;
$pstart = $ystart + ($i*$ptime); $pstart = $ystart + ($i*$ptime);
$pend = ($pstart + $ptime)-(24*60*60); // minus one day $pend = ($pstart + $ptime)-(24*60*60); // minus one day.
$o = [ "page_id" => $r->id, $o = [ "page_id" => $r->id,
"period" => $pnum, "period" => $pnum,
"fullname" => "Period {$pnum}", "fullname" => "Period {$pnum}",
"shortname" => "P{$pnum}", "shortname" => "P{$pnum}",
"startdate" => date("Y-m-d",$pstart), "startdate" => date("Y-m-d", $pstart),
"enddate" => date("Y-m-d",$pend) "enddate" => date("Y-m-d", $pend)
]; ];
$DB->insert_record("local_treestudyplan_period",$o); $DB->insert_record("local_treestudyplan_period", $o);
} }
} }
$recordset->close(); $recordset->close();
@ -458,8 +479,8 @@ function xmldb_local_treestudyplan_upgrade($oldversion) {
if ($oldversion < 2023082100) { if ($oldversion < 2023082100) {
// Up to version 20230821 the studyplan_id field still existed in the install.xml file // Up to version 20230821 the studyplan_id field still existed in the install.xml file.
// Below code remedies that - even though this is a repeat of part of an earlier upgrade // Below code remedies that - even though this is a repeat of part of an earlier upgrade.
// Define field studyplan_id to be dropped from local_treestudyplan_line. // Define field studyplan_id to be dropped from local_treestudyplan_line.
$table = new xmldb_table('local_treestudyplan_line'); $table = new xmldb_table('local_treestudyplan_line');

32
doc.php
View File

@ -1,11 +1,32 @@
<?php <?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/>.
/**
*
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once("../../config.php"); require_once("../../config.php");
require_once($CFG->libdir.'/weblib.php'); require_once($CFG->libdir.'/weblib.php');
$systemcontext = context_system::instance(); $systemcontext = context_system::instance();
$PAGE->set_url("/local/treestudyplan/doc.php",array()); $PAGE->set_url("/local/treestudyplan/doc.php", array());
$PAGE->requires->css(new moodle_url($CFG->wwwroot.'/local/treestudyplan/css/devstyles.css')); $PAGE->requires->css(new moodle_url($CFG->wwwroot.'/local/treestudyplan/css/devstyles.css'));
$PAGE->set_pagelayout('base'); $PAGE->set_pagelayout('base');
$PAGE->set_context($systemcontext); $PAGE->set_context($systemcontext);
@ -14,16 +35,15 @@ require_login();
$pi = $_SERVER['PATH_INFO']; $pi = $_SERVER['PATH_INFO'];
$file = $CFG->dirroot."/local/treestudyplan/doc".$pi; $file = $CFG->dirroot."/local/treestudyplan/doc".$pi;
// Fallback to index // Fallback to index.
if(!file_exists($file)){ if (!file_exists($file)) {
$file = $CFG->dirroot."/local/treestudyplan/doc/index.htm"; $file = $CFG->dirroot."/local/treestudyplan/doc/index.htm";
} }
$mime = mime_content_type($file); $mime = mime_content_type($file);
$text_types = ["text/html","text/plain"]; $text_types = ["text/html", "text/plain"];
if( in_array($mime,$text_types)) if ( in_array($mime, $text_types)) {
{
print $OUTPUT->header(); print $OUTPUT->header();
print file_get_contents($file); print file_get_contents($file);
print $OUTPUT->footer(); print $OUTPUT->footer();

View File

@ -1,23 +1,32 @@
<?php <?php
if(isset($_SERVER['SCRIPT_FILENAME'])) // This file is part of the Studyplan plugin for Moodle
{ //
// If SCRIPT_FILENAME is set, use that so the symlinked directories the developmen environment uses are handled correctly // Moodle is free software: you can redistribute it and/or modify
$root = dirname(dirname(dirname($_SERVER['SCRIPT_FILENAME']))); // it under the terms of the GNU General Public License as published by
error_log("Using {$root}/config.php"); // the Free Software Foundation, either version 3 of the License, or
require_once($root."/config.php"); // (at your option) any later version.
} //
else // Moodle is distributed in the hope that it will be useful,
{ // but WITHOUT ANY WARRANTY; without even the implied warranty of
// If not, assume the cwd is not symlinked and proceed as we are used to // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
require_once("../../config.php"); // 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
*/
require_once("../../config.php");
require_once("./lib.php"); require_once("./lib.php");
require_once($CFG->libdir.'/weblib.php'); require_once($CFG->libdir.'/weblib.php');
require_once($CFG->dirroot.'/local/treestudyplan/classes/reportinvite_form.php'); require_once($CFG->dirroot.'/local/treestudyplan/classes/reportinvite_form.php');
$add = optional_param('add', '', PARAM_ALPHANUM); // module name $add = optional_param('add', '', PARAM_ALPHANUM); // module name.
$update = optional_param('update', 0, PARAM_INT); $update = optional_param('update', 0, PARAM_INT);
$resend = optional_param('resend', 0, PARAM_INT); $resend = optional_param('resend', 0, PARAM_INT);
$delete = optional_param('delete', 0, PARAM_INT); $delete = optional_param('delete', 0, PARAM_INT);
@ -28,8 +37,7 @@ $PAGE->set_url("/local/treestudyplan/edit-invite.php");
$PAGE->set_pagelayout('base'); $PAGE->set_pagelayout('base');
$PAGE->set_context($systemcontext); $PAGE->set_context($systemcontext);
if($update > 0) if ($update > 0) {
{
$PAGE->set_title(get_string('invite_desc_edit', 'local_treestudyplan')); $PAGE->set_title(get_string('invite_desc_edit', 'local_treestudyplan'));
$PAGE->set_heading(get_string('invite_desc_edit', 'local_treestudyplan')); $PAGE->set_heading(get_string('invite_desc_edit', 'local_treestudyplan'));
} }
@ -40,40 +48,37 @@ else
} }
// Check if user has capability to manage study plan units // Check if user has capability to manage study plan units.
require_login(); require_login();
print $OUTPUT->header(); print $OUTPUT->header();
if(!empty($add)) if (!empty($add)) {
{
$data = array( $data = array(
'add' => 1, 'add' => 1,
); );
} }
else if(!empty($update)) else if (!empty($update)) {
{
$data = $DB->get_record("local_treestudyplan_invit", array('id' => $update)); $data = $DB->get_record("local_treestudyplan_invit", array('id' => $update));
$data->update = $update; $data->update = $update;
if(empty($data) || $data->user_id != $USER->id) if (empty($data) || $data->user_id != $USER->id)
{ {
print_error('invalidaction'); print_error('invalidaction');
exit; exit;
} }
} }
else if(!empty($resend)) else if (!empty($resend)) {
{
$data = $DB->get_record("local_treestudyplan_invit", array('id' => $resend)); $data = $DB->get_record("local_treestudyplan_invit", array('id' => $resend));
$data->resend = $resend; $data->resend = $resend;
if(empty($data) || $data->user_id != $USER->id) if (empty($data) || $data->user_id != $USER->id)
{ {
print_error('invalidaction'); print_error('invalidaction');
exit; exit;
} }
// Do some resending of an invitation // Do some resending of an invitation.
local_treestudyplan_send_invite($data->id); local_treestudyplan_send_invite($data->id);
@ -82,11 +87,10 @@ else if(!empty($resend))
print $OUTPUT->footer(); print $OUTPUT->footer();
exit; exit;
} }
else if(!empty($delete)) else if (!empty($delete)) {
{
$data = $DB->get_record("local_treestudyplan_invit", array('id' => $delete)); $data = $DB->get_record("local_treestudyplan_invit", array('id' => $delete));
$data->delete = $delete; $data->delete = $delete;
if(empty($data) || $data->user_id != $USER->id) if (empty($data) || $data->user_id != $USER->id)
{ {
print_error('invalidaction'); print_error('invalidaction');
exit; exit;
@ -105,13 +109,11 @@ else {
$mform = new reportinvite_form(); $mform = new reportinvite_form();
$mform->set_data($data); $mform->set_data($data);
if($mform->is_cancelled()) if ($mform->is_cancelled()) {
{
redirect("$CFG->wwwroot/local/treestudyplan/invitations.php"); redirect("$CFG->wwwroot/local/treestudyplan/invitations.php");
} }
else if ($data = $mform->get_data()) else if ($data = $mform->get_data()) {
{ if (!empty($data->update))
if(!empty($data->update))
{ {
$id = $data->update; $id = $data->update;
@ -123,25 +125,25 @@ else if ($data = $mform->get_data())
redirect("$CFG->wwwroot/local/treestudyplan/invitations.php"); redirect("$CFG->wwwroot/local/treestudyplan/invitations.php");
} }
else if(!empty($data->add)) else if (!empty($data->add))
{ {
$id = $DB->insert_record("local_treestudyplan_invit",$data, true); $id = $DB->insert_record("local_treestudyplan_invit", $data, true);
// Send invitaion mail // Send invitaion mail.
local_treestudyplan_send_invite($id); local_treestudyplan_send_invite($id);
redirect("$CFG->wwwroot/local/treestudyplan/invitations.php?sent={$id}"); redirect("$CFG->wwwroot/local/treestudyplan/invitations.php?sent={$id}");
} }
else if(!empty($data->resend)) else if (!empty($data->resend))
{ {
} }
else if(!empty($data->delete)) else if (!empty($data->delete))
{ {
} }
else else
{ {
print_error("invaliddata"); print_error("invaliddata");
} }
@ -152,7 +154,7 @@ else if ($data = $mform->get_data())
else else
{ {
$data = null; $data = null;
if($unitid > 0) if ($unitid > 0)
{ {
} }

View File

@ -1,4 +1,25 @@
<?php <?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/>.
/**
*
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once("../../config.php"); require_once("../../config.php");
require_once($CFG->libdir.'/weblib.php'); require_once($CFG->libdir.'/weblib.php');
@ -7,71 +28,70 @@ use \local_treestudyplan\courseservice;
$systemcontext = context_system::instance(); $systemcontext = context_system::instance();
$PAGE->set_url("/local/treestudyplan/edit-plan.php",array()); $PAGE->set_url("/local/treestudyplan/edit-plan.php", array());
require_login(); require_login();
// Figure out the context (category or system, based on either category or context parameter) // Figure out the context (category or system, based on either category or context parameter).
$categoryid = optional_param('categoryid', 0, PARAM_INT); // Category id $categoryid = optional_param('categoryid', 0, PARAM_INT); // Category id.
$contextid = optional_param('contextid', 0, PARAM_INT); // Context id $contextid = optional_param('contextid', 0, PARAM_INT); // Context id.
if($categoryid > 0){ if ($categoryid > 0) {
$studyplancontext = context_coursecat::instance($categoryid); $studyplancontext = context_coursecat::instance($categoryid);
} }
elseif($contextid > 0) else if ($contextid > 0) {
{
$studyplancontext = context::instance_by_id($contextid); $studyplancontext = context::instance_by_id($contextid);
if(in_array($studyplancontext->contextlevel,[CONTEXT_SYSTEM,CONTEXT_COURSECAT])) if (in_array($studyplancontext->contextlevel, [CONTEXT_SYSTEM, CONTEXT_COURSECAT]))
{ {
$categoryid = $studyplancontext->instanceid; $categoryid = $studyplancontext->instanceid;
} }
else else
{ {
$studyplancontext = $systemcontext; $studyplancontext = $systemcontext;
} }
} }
else else
{ {
// If no context is selected, find the first available one // If no context is selected, find the first available one.
$available_contexts = courseservice::list_accessible_categories_with_usage("edit"); $available_contexts = courseservice::list_accessible_categories_with_usage("edit");
$contextid=1; // fallback to system context $contextid=1; // fallback to system context.
foreach($available_contexts as $ctx){ foreach ($available_contexts as $ctx) {
if($ctx->count > 0){ if ($ctx->count > 0) {
$contextid = $ctx->ctxid; $contextid = $ctx->ctxid;
break; break;
} }
} }
// reload page with selected category // reload page with selected category.
$url = new \moodle_url('/local/treestudyplan/edit-plan.php',["contextid" => $contextid]); $url = new \moodle_url('/local/treestudyplan/edit-plan.php', ["contextid" => $contextid]);
header('Location: '.$url->out(false), true, 302); header('Location: '.$url->out(false), true, 302);
exit; exit;
} }
require_capability('local/treestudyplan:editstudyplan',$studyplancontext); require_capability('local/treestudyplan:editstudyplan', $studyplancontext);
$contextname = $studyplancontext->get_context_name(false,false); $contextname = $studyplancontext->get_context_name(false, false);
$PAGE->set_pagelayout('coursecategory'); $PAGE->set_pagelayout('coursecategory');
$PAGE->set_context($studyplancontext); $PAGE->set_context($studyplancontext);
$PAGE->set_title(get_string('cfg_plans','local_treestudyplan')." - ".$contextname); $PAGE->set_title(get_string('cfg_plans', 'local_treestudyplan')." - ".$contextname);
$PAGE->set_heading($contextname); $PAGE->set_heading($contextname);
if($studyplancontext->id > 1){ if ($studyplancontext->id > 1) {
navigation_node::override_active_url(new moodle_url('/course/index.php', ['categoryid' => $categoryid ])); navigation_node::override_active_url(new moodle_url('/course/index.php', ['categoryid' => $categoryid ]));
$PAGE->navbar->add(get_string('cfg_plans','local_treestudyplan')); $PAGE->navbar->add(get_string('cfg_plans', 'local_treestudyplan'));
} }
// Load javascripts and specific css // Load javascripts and specific css.
$PAGE->requires->css(new moodle_url($CFG->wwwroot.'/local/treestudyplan/css/bootstrap-vue/bootstrap-vue.css')); $PAGE->requires->css(new moodle_url($CFG->wwwroot.'/local/treestudyplan/css/bootstrap-vue/bootstrap-vue.css'));
$PAGE->requires->css(new moodle_url($CFG->wwwroot.'/local/treestudyplan/css/devstyles.css')); $PAGE->requires->css(new moodle_url($CFG->wwwroot.'/local/treestudyplan/css/devstyles.css'));
$PAGE->requires->js_call_amd('local_treestudyplan/page-edit-plan', 'init', [$studyplancontext->id,$categoryid]); $PAGE->requires->js_call_amd('local_treestudyplan/page-edit-plan', 'init', [$studyplancontext->id, $categoryid]);
$catlist = courseservice::list_accessible_categories_with_usage("edit"); $catlist = courseservice::list_accessible_categories_with_usage("edit");
//Local translate function //Local translate function.
function t($str, $param=null, $plugin='local_treestudyplan'){ function t($str, $param=null, $plugin='local_treestudyplan') {
print get_string($str,$plugin,$param); print get_string($str, $plugin, $param);
} }
print $OUTPUT->header(); print $OUTPUT->header();
@ -86,11 +106,11 @@ print $OUTPUT->header();
</div> </div>
</div> </div>
<div v-cloak> <div v-cloak>
<div v-if='!activestudyplan && usedcontexts' class='ml-3 mb-3'> <div v-if='!activestudyplan && usedcontexts' class='ml-3 mb-3'>
<b-form-select text='<?php print($contextname);?>' :value="contextid"> <b-form-select text='<?php print($contextname);?>' :value="contextid">
<b-form-select-option v-for='ctx in usedcontexts' :key='ctx.id' :value="ctx.context_id" @click='switchContext(ctx)' <b-form-select-option v-for='ctx in usedcontexts' :key='ctx.id' :value="ctx.context_id" @click='switchContext(ctx)'
:active="ctx.context_id == contextid" :class="(ctx.studyplancount > 0)?'font-weight-bold':''" :active="ctx.context_id == contextid" :class="(ctx.studyplancount > 0)?'font-weight-bold':''"
><span v-for="(p,i) in ctx.category.path"><span v-if="i>0"> / </span>{{ p }}</span> <span>({{ ctx.studyplancount }})</b-form-select-option> ><span v-for="(p, i) in ctx.category.path"><span v-if="i>0"> / </span>{{ p }}</span> <span>({{ ctx.studyplancount }})</b-form-select-option>
</b-form-select> </b-form-select>
</div> </div>
<h3 v-else><?php print $contextname; ?></h3> <h3 v-else><?php print $contextname; ?></h3>
@ -98,7 +118,7 @@ print $OUTPUT->header();
<a href='#' v-if='activestudyplan' @click.prevent='closeStudyplan'><i style='font-size: 150%;' class='fa fa-chevron-left'></i> <?php t('back');?></a> <a href='#' v-if='activestudyplan' @click.prevent='closeStudyplan'><i style='font-size: 150%;' class='fa fa-chevron-left'></i> <?php t('back');?></a>
<span v-if='activestudyplan'><?php t("studyplan_select"); ?></span>&nbsp; <span v-if='activestudyplan'><?php t("studyplan_select"); ?></span>&nbsp;
<b-form-select v-if='activestudyplan' lazy :text='dropdown_title' v-model='activestudyplan.id'> <b-form-select v-if='activestudyplan' lazy :text='dropdown_title' v-model='activestudyplan.id'>
<b-form-select-option v-for='(studyplan,planindex) in studyplans' :value="studyplan.id" :key='studyplan.id' @click='selectStudyplan(studyplan)'>{{ studyplan.name }}</b-form-select-option> <b-form-select-option v-for='(studyplan, planindex) in studyplans' :value="studyplan.id" :key='studyplan.id' @click='selectStudyplan(studyplan)'>{{ studyplan.name }}</b-form-select-option>
</b-form-select>&nbsp; </b-form-select>&nbsp;
<t-studyplan-edit <t-studyplan-edit
@creating="" @creating=""
@ -122,9 +142,9 @@ print $OUTPUT->header();
<div v-else class='t-studyplan-notselected'> <div v-else class='t-studyplan-notselected'>
<p><?php t("studyplan_noneselected"); ?></p> <p><?php t("studyplan_noneselected"); ?></p>
<b-card-group deck> <b-card-group deck>
<s-studyplan-card <s-studyplan-card
v-for='(studyplan,planindex) in studyplans' v-for='(studyplan, planindex) in studyplans'
:key='studyplan.id' :key='studyplan.id'
v-model='studyplans[planindex]' v-model='studyplans[planindex]'
open open
@open='selectStudyplan(studyplan)' @open='selectStudyplan(studyplan)'
@ -151,7 +171,7 @@ print $OUTPUT->header();
<div class='t-toolbox-preface'> <div class='t-toolbox-preface'>
<b-form-checkbox v-model="toolbox.right" switch><?php t("toolbar-right");?></b-form-checkbox> <b-form-checkbox v-model="toolbox.right" switch><?php t("toolbar-right");?></b-form-checkbox>
</div> </div>
<b-tabs content-class='mt-3'> <b-tabs content-class='mt-3'>
<b-tab title="<?php t('courses')?>"> <b-tab title="<?php t('courses')?>">
<t-coursecat-list v-model="courses"></t-coursecat-list> <t-coursecat-list v-model="courses"></t-coursecat-list>
</b-tab> </b-tab>

View File

@ -1,14 +1,34 @@
<?php <?php
if(isset($_SERVER['SCRIPT_FILENAME'])) // This file is part of the Studyplan plugin for Moodle
{ //
// If SCRIPT_FILENAME is set, use that so the symlinked directories the developmen environment uses are handled correctly // 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
*/
if (isset($_SERVER['SCRIPT_FILENAME'])) {
// If SCRIPT_FILENAME is set, use that so the symlinked directories the developmen environment uses are handled correctly.
$root = dirname(dirname(dirname($_SERVER['SCRIPT_FILENAME']))); $root = dirname(dirname(dirname($_SERVER['SCRIPT_FILENAME'])));
error_log("Using {$root}/config.php"); error_log("Using {$root}/config.php");
require_once($root."/config.php"); require_once($root."/config.php");
} }
else else
{ {
// If not, assume the cwd is not symlinked and proceed as we are used to // If not, assume the cwd is not symlinked and proceed as we are used to.
require_once("../../config.php"); require_once("../../config.php");
} }
@ -19,10 +39,10 @@ use local_treestudyplan;
$INVITED_URL = "/local/treestudyplan/invited.php"; $INVITED_URL = "/local/treestudyplan/invited.php";
//admin_externalpage_setup('major'); //admin_externalpage_setup('major');.
$systemcontext = context_system::instance(); $systemcontext = context_system::instance();
$PAGE->set_url("/local/treestudyplan/invitations.php",array()); $PAGE->set_url("/local/treestudyplan/invitations.php", array());
require_login(); require_login();
$PAGE->set_pagelayout('base'); $PAGE->set_pagelayout('base');
@ -30,16 +50,15 @@ $PAGE->set_context($systemcontext);
$PAGE->set_title(get_string('manage_invites', 'local_treestudyplan')); $PAGE->set_title(get_string('manage_invites', 'local_treestudyplan'));
$PAGE->set_heading(get_string('manage_invites', 'local_treestudyplan')); $PAGE->set_heading(get_string('manage_invites', 'local_treestudyplan'));
// Load javascripts // Load javascripts.
$PAGE->requires->js_call_amd('local_treestudyplan/page-invitemanager', 'init'); $PAGE->requires->js_call_amd('local_treestudyplan/page-invitemanager', 'init');
$PAGE->requires->js_call_amd('local_treestudyplan/buttonlinks', 'init'); $PAGE->requires->js_call_amd('local_treestudyplan/buttonlinks', 'init');
// retrieve list of courses that the student is enrolled in // retrieve list of courses that the student is enrolled in.
$sent = optional_param('sent','',PARAM_INT); $sent = optional_param('sent', '', PARAM_INT);
if(!empty($sent)) if (!empty($sent)) {
{
$invite = $DB->get_record('local_treestudyplan_invit', array('id' => $sent)); $invite = $DB->get_record('local_treestudyplan_invit', array('id' => $sent));
\core\notification::success(get_string('invite_resent_msg','local_treestudyplan',$invite)); \core\notification::success(get_string('invite_resent_msg', 'local_treestudyplan', $invite));
}; };
@ -49,19 +68,18 @@ print "<p>".get_string('invite_description', 'local_treestudyplan')."</p>";
$invites = $DB->get_records('local_treestudyplan_invit', array('user_id' => $USER->id)); $invites = $DB->get_records('local_treestudyplan_invit', array('user_id' => $USER->id));
print "<h3>".get_string('invite_tablecaption','local_treestudyplan')."</h3>"; print "<h3>".get_string('invite_tablecaption', 'local_treestudyplan')."</h3>";
print "<table class='m-manage_invites'>"; print "<table class='m-manage_invites'>";
print "<thead>"; print "<thead>";
print "<th>".get_string('invite_name','local_treestudyplan')."</th>"; print "<th>".get_string('invite_name', 'local_treestudyplan')."</th>";
print "<th>".get_string('invite_email','local_treestudyplan')."</th>"; print "<th>".get_string('invite_email', 'local_treestudyplan')."</th>";
print "<th>".get_string('invite_date','local_treestudyplan')."</th>"; print "<th>".get_string('invite_date', 'local_treestudyplan')."</th>";
print "<th>&nbsp;</th>"; print "<th>&nbsp;</th>";
print "</thead>"; print "</thead>";
print "<tbody>"; print "<tbody>";
if(count($invites) > 0) if (count($invites) > 0) {
{ foreach ($invites as $invite)
foreach($invites as $invite)
{ {
$testlink = $INVITED_URL."?key={$invite->invitekey}"; $testlink = $INVITED_URL."?key={$invite->invitekey}";
print "<tr data-id='{$invite->id}'>"; print "<tr data-id='{$invite->id}'>";
@ -70,27 +88,27 @@ if(count($invites) > 0)
print "<td data-field='date'>".userdate($invite->idate, "%x")."</td>"; print "<td data-field='date'>".userdate($invite->idate, "%x")."</td>";
print "<td data-field='control'>"; print "<td data-field='control'>";
print "<a class='m-action-view ' href='{$testlink}' title='".get_string('invite_tooltip_testlink','local_treestudyplan')."'><i class='fa fa-eye'></i></a>"; print "<a class='m-action-view ' href='{$testlink}' title='".get_string('invite_tooltip_testlink', 'local_treestudyplan')."'><i class='fa fa-eye'></i></a>";
print "<a class='m-action-resend m-action-confirm'"; print "<a class='m-action-resend m-action-confirm'";
print " data-confirmtext='".get_string('invite_confirm_resend','local_treestudyplan',$invite->name)."'"; print " data-confirmtext='".get_string('invite_confirm_resend', 'local_treestudyplan', $invite->name)."'";
print " data-confirmbtn='".get_string('send','local_treestudyplan')."'"; print " data-confirmbtn='".get_string('send', 'local_treestudyplan')."'";
print " href='#' data-actionhref='edit-invite.php?resend={$invite->id}' title='".get_string('invite_tooltip_resend','local_treestudyplan')."'"; print " href='#' data-actionhref='edit-invite.php?resend={$invite->id}' title='".get_string('invite_tooltip_resend', 'local_treestudyplan')."'";
print " ><i class='fa fa-envelope'></i></a>"; print " ><i class='fa fa-envelope'></i></a>";
print "<a href='edit-invite.php?update={$invite->id}'><i class='fa fa-pencil' title='".get_string('invite_tooltip_edit','local_treestudyplan')."'></i></a>"; print "<a href='edit-invite.php?update={$invite->id}'><i class='fa fa-pencil' title='".get_string('invite_tooltip_edit', 'local_treestudyplan')."'></i></a>";
print "<a class='m-action-delete m-action-confirm'"; print "<a class='m-action-delete m-action-confirm'";
print " data-confirmtext='".get_string('invite_confirm_delete','local_treestudyplan', $invite->name)."'"; print " data-confirmtext='".get_string('invite_confirm_delete', 'local_treestudyplan', $invite->name)."'";
print " data-confirmbtn='".get_string('delete')."'"; print " data-confirmbtn='".get_string('delete')."'";
print " href='#' data-actionhref='edit-invite.php?delete={$invite->id}' title='".get_string('invite_tooltip_delete','local_treestudyplan')."'"; print " href='#' data-actionhref='edit-invite.php?delete={$invite->id}' title='".get_string('invite_tooltip_delete', 'local_treestudyplan')."'";
print " ><i class='fa fa-trash'></i></a>"; print " ><i class='fa fa-trash'></i></a>";
print "</td>"; print "</td>";
} }
} }
else else
{ {
print "<tr><td colspan='6'>".get_string('invite_table_empty','local_treestudyplan')."</td></tr>"; print "<tr><td colspan='6'>".get_string('invite_table_empty', 'local_treestudyplan')."</td></tr>";
} }
print "</tbody></table>"; print "</tbody></table>";

View File

@ -1,24 +1,43 @@
<?php <?php
// If not, assume the cwd is not symlinked and proceed as we are used to // 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
*/
require_once("../../config.php"); require_once("../../config.php");
//Local translate function //Local translate function.
function t($str, $param=null, $plugin='local_treestudyplan'){ function t($str, $param=null, $plugin='local_treestudyplan') {
print get_string($str,$plugin,$param); print get_string($str, $plugin, $param);
} }
$systemcontext = context_system::instance(); $systemcontext = context_system::instance();
$PAGE->set_pagelayout('base'); $PAGE->set_pagelayout('base');
$PAGE->set_context($systemcontext); $PAGE->set_context($systemcontext);
// See if we can get a valid user for this invited // See if we can get a valid user for this invited.
$invitekey = optional_param('key', '', PARAM_ALPHANUM); // module name $invitekey = optional_param('key', '', PARAM_ALPHANUM); // module name.
$PAGE->set_url("/local/treestudyplan/invited.php",array('key' => $invitekey)); $PAGE->set_url("/local/treestudyplan/invited.php", array('key' => $invitekey));
$invite = $DB->get_record_select("local_treestudyplan_invit", $DB->sql_compare_text("invitekey"). " = " . $DB->sql_compare_text(":invitekey"), ['invitekey' => $invitekey]); $invite = $DB->get_record_select("local_treestudyplan_invit", $DB->sql_compare_text("invitekey"). " = " . $DB->sql_compare_text(":invitekey"), ['invitekey' => $invitekey]);
if(empty($invite)) if (empty($invite)) {
{
$PAGE->set_title(get_string('invalid_invitekey_title', 'local_treestudyplan')); $PAGE->set_title(get_string('invalid_invitekey_title', 'local_treestudyplan'));
$PAGE->set_heading(get_string('invalid_invitekey_title', 'local_treestudyplan')); $PAGE->set_heading(get_string('invalid_invitekey_title', 'local_treestudyplan'));
@ -26,10 +45,10 @@ if(empty($invite))
print $OUTPUT->header(); print $OUTPUT->header();
// render page for skill level 0 (global) // render page for skill level 0 (global).
print "<div class='box errorbox alert alert-danger'>"; print "<div class='box errorbox alert alert-danger'>";
print get_string('invalid_invitekey_error','local_treestudyplan'); print get_string('invalid_invitekey_error', 'local_treestudyplan');
print "</div>"; print "</div>";
print $OUTPUT->footer(); print $OUTPUT->footer();
@ -38,10 +57,10 @@ if(empty($invite))
} }
else else
{ {
// Load javascripts and specific css // Load javascripts and specific css.
$PAGE->requires->css(new moodle_url($CFG->wwwroot.'/local/treestudyplan/css/bootstrap-vue/bootstrap-vue.css')); $PAGE->requires->css(new moodle_url($CFG->wwwroot.'/local/treestudyplan/css/bootstrap-vue/bootstrap-vue.css'));
$PAGE->requires->css(new moodle_url($CFG->wwwroot.'/local/treestudyplan/css/devstyles.css')); $PAGE->requires->css(new moodle_url($CFG->wwwroot.'/local/treestudyplan/css/devstyles.css'));
$PAGE->requires->js_call_amd('local_treestudyplan/page-myreport', 'init',['invited',$invitekey]); $PAGE->requires->js_call_amd('local_treestudyplan/page-myreport', 'init', ['invited', $invitekey]);
$student = $DB->get_record('user', array('id' => $invite->user_id)); $student = $DB->get_record('user', array('id' => $invite->user_id));
$PAGE->set_title(get_string('report_invited', 'local_treestudyplan', "{$student->firstname} {$student->lastname}" )); $PAGE->set_title(get_string('report_invited', 'local_treestudyplan', "{$student->firstname} {$student->lastname}" ));

View File

@ -1,4 +1,25 @@
<?php <?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/>.
/**
*
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['pluginname'] = 'Studyplans'; $string['pluginname'] = 'Studyplans';
$string['privacy:metadata'] = 'This plugin stores a link between users and their associated study plans. It also needs to store an email address and user provided handle, which may be a name, to send invitations to view the study plan to whomever the user chooses by email.'; $string['privacy:metadata'] = 'This plugin stores a link between users and their associated study plans. It also needs to store an email address and user provided handle, which may be a name, to send invitations to view the study plan to whomever the user chooses by email.';
@ -40,18 +61,18 @@ $string['invite_confirm_delete'] = 'Are your sure tou want to delete/revoke the
$string['invite_desc_new'] = "Create a new invitation"; $string['invite_desc_new'] = "Create a new invitation";
$string['invite_desc_edit'] = "Edit an existing invitation"; $string['invite_desc_edit'] = "Edit an existing invitation";
$string['invite_name'] = "Name"; $string['invite_name'] = "Name";
$string['invite_email'] = "Email"; $string['invite_email'] = "Email";
$string['invite_date'] = "Date"; $string['invite_date'] = "Date";
$string['invite_resent_msg'] = 'The invitation for {$a->name}<{$a->email}> has been sent'; $string['invite_resent_msg'] = 'The invitation for {$a->name}<{$a->email}> has been sent';
$string['invite_mail_subject'] = 'Shared grade card of {$a->sender}'; $string['invite_mail_subject'] = 'Shared grade card of {$a->sender}';
$string['invite_mail_text'] = ' $string['invite_mail_text'] = '
<p>Dear {$a->invitee},</p> <p>Dear {$a->invitee}, </p>
<p>I\'d like to invite you to view my study plan and progess.</p> <p>I\'d like to invite you to view my study plan and progess.</p>
<p>The link below gives you access at any time to view the most recent results. Feel free to bookmark this link in your browser.</p> <p>The link below gives you access at any time to view the most recent results. Feel free to bookmark this link in your browser.</p>
<p>Click the link below to view the study plan:<br> <p>Click the link below to view the study plan:<br>
<a href="{$a->link}">{$a->link}</a></p> <a href="{$a->link}">{$a->link}</a></p>
<p>Kind regards,<br> <p>Kind regards, <br>
{$a->sender}</p> {$a->sender}</p>
'; ';

View File

@ -1,4 +1,24 @@
<?php <?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/>.
/**
*
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['pluginname'] = 'Studieplannen'; $string['pluginname'] = 'Studieplannen';
@ -41,12 +61,12 @@ $string['invite_confirm_delete'] = 'Weet je zeker dat je de uitnodiging voor {$a
$string['invite_desc_new'] = "Nieuwe uitnodiging maken"; $string['invite_desc_new'] = "Nieuwe uitnodiging maken";
$string['invite_desc_edit'] = "Uitnodiging bewerken"; $string['invite_desc_edit'] = "Uitnodiging bewerken";
$string['invite_name'] = "Naam"; $string['invite_name'] = "Naam";
$string['invite_email'] = "Email"; $string['invite_email'] = "Email";
$string['invite_date'] = "Datum"; $string['invite_date'] = "Datum";
$string['invite_resent_msg'] = 'De uitnodiging naar {$a->name}<{$a->email}> is verzonden'; $string['invite_resent_msg'] = 'De uitnodiging naar {$a->name}<{$a->email}> is verzonden';
$string['invite_mail_subject'] = 'Gedeeld rapport van {$a->sender}'; $string['invite_mail_subject'] = 'Gedeeld rapport van {$a->sender}';
$string['invite_mail_text'] = ' $string['invite_mail_text'] = '
<p>Beste {$a->invitee},</p> <p>Beste {$a->invitee}, </p>
<p>Bij deze wil ik je graag uitnodigen om mijn studieplan en studievoortgang te bekijken.</p> <p>Bij deze wil ik je graag uitnodigen om mijn studieplan en studievoortgang te bekijken.</p>
<p>Via de link hieronder kun je op elk moment het meest recente resultatenoverzicht bekijken. Je kunt deze link ook bewaren als bookmark in je browser.</p> <p>Via de link hieronder kun je op elk moment het meest recente resultatenoverzicht bekijken. Je kunt deze link ook bewaren als bookmark in je browser.</p>
@ -54,7 +74,7 @@ $string['invite_mail_text'] = '
<p>Klik op de volgende link om het studieplan te bekijken:<br> <p>Klik op de volgende link om het studieplan te bekijken:<br>
<a href="{$a->link}">{$a->link}</a></p> <a href="{$a->link}">{$a->link}</a></p>
<p>Met vriendelijke groet,<br> <p>Met vriendelijke groet, <br>
{$a->sender}</p> {$a->sender}</p>
'; ';

185
lib.php
View File

@ -1,4 +1,25 @@
<?php <?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/>.
/**
*
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once($CFG->dirroot.'/course/modlib.php'); require_once($CFG->dirroot.'/course/modlib.php');
use local_treestudyplan\local\helpers\webservicehelper; use local_treestudyplan\local\helpers\webservicehelper;
@ -15,47 +36,47 @@ function local_treestudyplan_extend_navigation(global_navigation $navigation) {
$systemcontext = context_system::instance(); $systemcontext = context_system::instance();
// Moodle 4.0-4.2 do not yet support customizing the primary navigation bar (it is a planned feature though) // Moodle 4.0-4.2 do not yet support customizing the primary navigation bar (it is a planned feature though).
// For now, go to theme settings and add the following into "Custom menu items" // For now, go to theme settings and add the following into "Custom menu items".
// [your name for my studyplan]|/local/treestudyplan/myreport.php // [your name for my studyplan]|/local/treestudyplan/myreport.php.
// [your name for studyplan viewing]|/local/treestudyplan/view-plan.php // [your name for studyplan viewing]|/local/treestudyplan/view-plan.php.
// [your name for studyplan managing]|/local/treestudyplan/edit-plan.php // [your name for studyplan managing]|/local/treestudyplan/edit-plan.php.
// For example: // For example:.
// Mijn studieplan|/local/treestudyplan/myreport.php // Mijn studieplan|/local/treestudyplan/myreport.php.
// Studieplannen|/local/treestudyplan/view-plan.php // Studieplannen|/local/treestudyplan/view-plan.php.
// Studieplannen beheren|/local/treestudyplan/edit-plan.php // Studieplannen beheren|/local/treestudyplan/edit-plan.php.
// Using some javascript magic we'll hide the links that are not accessible // Using some javascript magic we'll hide the links that are not accessible.
// (Since the Output API does not easily support inline style tags, adding one through Javascript is easier, // (Since the Output API does not easily support inline style tags, adding one through Javascript is easier,.
// and not much more complex than loading a separate stylesheet for each link we want to hide) // and not much more complex than loading a separate stylesheet for each link we want to hide).
// we will add all the hrefs that should be hidden to this variable below // we will add all the hrefs that should be hidden to this variable below.
$hide_primary_hrefs = []; $hide_primary_hrefs = [];
if($USER->id > 1) // Don't show if user is not logged in (id == 0) or is guest user (id == 1) if ($USER->id > 1) // Don't show if user is not logged in (id == 0) or is guest user (id == 1).
{ {
$userstudyplans = studyplan::find_for_user($USER->id); $userstudyplans = studyplan::find_for_user($USER->id);
if(!empty($userstudyplans)) if (!empty($userstudyplans))
{ {
// create studyplan node // create studyplan node.
$node = navigation_node::create( $node = navigation_node::create(
get_string("link_myreport","local_treestudyplan"), get_string("link_myreport", "local_treestudyplan"),
new moodle_url($CFG->wwwroot . "/local/treestudyplan/myreport.php", array()), new moodle_url($CFG->wwwroot . "/local/treestudyplan/myreport.php", array()),
global_navigation::TYPE_SYSTEM, global_navigation::TYPE_SYSTEM,
null, null,
"local_treestudyplan_myreport", "local_treestudyplan_myreport",
new pix_icon("myreport", '', 'local_treestudyplan') new pix_icon("myreport", '', 'local_treestudyplan')
); );
$node->showinflatnavigation = true; $node->showinflatnavigation = true;
$node->showinsecondarynavigation=true; $node->showinsecondarynavigation=true;
// create invitenode node // create invitenode node.
$invitenode = navigation_node::create( $invitenode = navigation_node::create(
get_string("manage_invites","local_treestudyplan"), get_string("manage_invites", "local_treestudyplan"),
new moodle_url($CFG->wwwroot . "/local/treestudyplan/invitations.php", array()), new moodle_url($CFG->wwwroot . "/local/treestudyplan/invitations.php", array()),
global_navigation::TYPE_CUSTOM , global_navigation::TYPE_CUSTOM ,
null, null,
"local_treestudyplan_invitemgmt", "local_treestudyplan_invitemgmt",
new pix_icon("invitemgmt", '', 'local_treestudyplan') new pix_icon("invitemgmt", '', 'local_treestudyplan')
); );
@ -63,68 +84,68 @@ function local_treestudyplan_extend_navigation(global_navigation $navigation) {
$node->add_node($invitenode); $node->add_node($invitenode);
$navigation->add_node($node,'mycourses'); $navigation->add_node($node, 'mycourses');
} }
else { else {
$hide_primary_hrefs[] = "/local/treestudyplan/myreport.php"; $hide_primary_hrefs[] = "/local/treestudyplan/myreport.php";
} }
if( has_capability('local/treestudyplan:viewuserreports',context_system::instance()) if ( has_capability('local/treestudyplan:viewuserreports', context_system::instance())
|| webservicehelper::has_capability_in_any_category('local/treestudyplan:viewuserreports')) || webservicehelper::has_capability_in_any_category('local/treestudyplan:viewuserreports'))
{ {
$node = navigation_node::create( $node = navigation_node::create(
get_string("link_viewplan","local_treestudyplan"), get_string("link_viewplan", "local_treestudyplan"),
new moodle_url($CFG->wwwroot . "/local/treestudyplan/view-plan.php", array()), new moodle_url($CFG->wwwroot . "/local/treestudyplan/view-plan.php", array()),
global_navigation::TYPE_SYSTEM , global_navigation::TYPE_SYSTEM ,
null, null,
"local_treestudyplan_viewplan", "local_treestudyplan_viewplan",
new pix_icon("viewplans", '', 'local_treestudyplan') new pix_icon("viewplans", '', 'local_treestudyplan')
); );
$node->showinflatnavigation = true; $node->showinflatnavigation = true;
$node->showinsecondarynavigation=true; $node->showinsecondarynavigation=true;
$navigation->add_node($node,'mycourses'); $navigation->add_node($node, 'mycourses');
} }
else { else {
$hide_primary_hrefs[] = "/local/treestudyplan/view-plan.php"; $hide_primary_hrefs[] = "/local/treestudyplan/view-plan.php";
} }
if( has_capability('local/treestudyplan:editstudyplan',context_system::instance()) if ( has_capability('local/treestudyplan:editstudyplan', context_system::instance())
|| webservicehelper::has_capability_in_any_category('local/treestudyplan:editstudyplan') || webservicehelper::has_capability_in_any_category('local/treestudyplan:editstudyplan')
) )
{ {
$node = navigation_node::create( $node = navigation_node::create(
get_string("cfg_plans","local_treestudyplan"), get_string("cfg_plans", "local_treestudyplan"),
new moodle_url($CFG->wwwroot . "/local/treestudyplan/edit-plan.php", array()), new moodle_url($CFG->wwwroot . "/local/treestudyplan/edit-plan.php", array()),
global_navigation::TYPE_SYSTEM , global_navigation::TYPE_SYSTEM ,
null, null,
"local_treestudyplan_editplan", "local_treestudyplan_editplan",
new pix_icon("viewplans", '', 'local_treestudyplan') new pix_icon("viewplans", '', 'local_treestudyplan')
); );
$node->showinflatnavigation = true; $node->showinflatnavigation = true;
$node->showinsecondarynavigation=true; $node->showinsecondarynavigation=true;
$navigation->add_node($node,'mycourses'); $navigation->add_node($node, 'mycourses');
} }
else { else {
$hide_primary_hrefs[] = "/local/treestudyplan/edit-plan.php"; $hide_primary_hrefs[] = "/local/treestudyplan/edit-plan.php";
} }
} }
else { else {
$hide_primary_hrefs[] = "/local/treestudyplan/myreport.php"; $hide_primary_hrefs[] = "/local/treestudyplan/myreport.php";
$hide_primary_hrefs[] = "/local/treestudyplan/edit-plan.php"; $hide_primary_hrefs[] = "/local/treestudyplan/edit-plan.php";
$hide_primary_hrefs[] = "/local/treestudyplan/view-plan.php"; $hide_primary_hrefs[] = "/local/treestudyplan/view-plan.php";
} }
// create invitenode node // create invitenode node.
$invitenode = navigation_node::create( $invitenode = navigation_node::create(
get_string("nav_invited","local_treestudyplan"), get_string("nav_invited", "local_treestudyplan"),
new moodle_url($CFG->wwwroot . "/local/treestudyplan/invited.php", array()), new moodle_url($CFG->wwwroot . "/local/treestudyplan/invited.php", array()),
global_navigation::TYPE_USER , global_navigation::TYPE_USER ,
null, null,
"local_treestudyplan_invitemgmt", "local_treestudyplan_invitemgmt",
new pix_icon("nav_invited", '', 'local_treestudyplan') new pix_icon("nav_invited", '', 'local_treestudyplan')
); );
$invitenode->showinflatnavigation = false; $invitenode->showinflatnavigation = false;
$navigation->add_node($invitenode,'mycourses'); $navigation->add_node($invitenode, 'mycourses');
// Now using some javascript magic, we'll hide the links that are not accessible // Now using some javascript magic, we'll hide the links that are not accessible.
$PAGE->requires->js_call_amd('local_treestudyplan/primary-nav-tools', 'hide_primary', [$hide_primary_hrefs]); $PAGE->requires->js_call_amd('local_treestudyplan/primary-nav-tools', 'hide_primary', [$hide_primary_hrefs]);
@ -134,27 +155,27 @@ function local_treestudyplan_extend_navigation(global_navigation $navigation) {
function local_treestudyplan_extend_navigation_category_settings($navigation, context_coursecat $coursecategorycontext) { function local_treestudyplan_extend_navigation_category_settings($navigation, context_coursecat $coursecategorycontext) {
global $CFG, $PAGE; global $CFG, $PAGE;
$categoryid = $coursecategorycontext->instanceid; $categoryid = $coursecategorycontext->instanceid;
if(has_capability('local/treestudyplan:editstudyplan',$coursecategorycontext)){ if (has_capability('local/treestudyplan:editstudyplan', $coursecategorycontext)) {
$node = $navigation->add( $node = $navigation->add(
get_string('treestudyplan:editstudyplan',"local_treestudyplan"), get_string('treestudyplan:editstudyplan', "local_treestudyplan"),
new moodle_url($CFG->wwwroot . "/local/treestudyplan/edit-plan.php", ["categoryid"=>$categoryid]), new moodle_url($CFG->wwwroot . "/local/treestudyplan/edit-plan.php", ["categoryid"=>$categoryid]),
global_navigation::TYPE_CATEGORY, global_navigation::TYPE_CATEGORY,
null, null,
"local_treestudyplan_editplan", "local_treestudyplan_editplan",
new pix_icon("editplans", '', 'local_treestudyplan') new pix_icon("editplans", '', 'local_treestudyplan')
); );
//$node->make_active(); //$node->make_active();.
} }
if(has_capability('local/treestudyplan:viewuserreports',$coursecategorycontext)){ if (has_capability('local/treestudyplan:viewuserreports', $coursecategorycontext)) {
$node = $navigation->add( $node = $navigation->add(
get_string('link_viewplan',"local_treestudyplan"), get_string('link_viewplan', "local_treestudyplan"),
new moodle_url($CFG->wwwroot . "/local/treestudyplan/view-plan.php", ["categoryid"=>$categoryid]), new moodle_url($CFG->wwwroot . "/local/treestudyplan/view-plan.php", ["categoryid"=>$categoryid]),
global_navigation::TYPE_CATEGORY, global_navigation::TYPE_CATEGORY,
null, null,
"local_treestudyplan_viewplan", "local_treestudyplan_viewplan",
new pix_icon("viewplans", '', 'local_treestudyplan') new pix_icon("viewplans", '', 'local_treestudyplan')
); );
//$node->make_active(); //$node->make_active();.
} }
} }
@ -184,50 +205,49 @@ function local_treestudyplan_reset_fontawesome_icon_map() {
$instance->get_icon_name_map(); $instance->get_icon_name_map();
} }
function local_treestudyplan_send_invite($inviteid) function local_treestudyplan_send_invite($inviteid) {
{ global $DB, $USER, $CFG;
global $DB,$USER,$CFG;
$invite = $DB->get_record("local_treestudyplan_invit", array('id' => $inviteid)); $invite = $DB->get_record("local_treestudyplan_invit", array('id' => $inviteid));
$noreply = 'noreply@' . get_host_from_url($CFG->wwwroot); $noreply = 'noreply@' . get_host_from_url($CFG->wwwroot);
$mailer = get_mailer(); $mailer = get_mailer();
$mailer->setFrom($noreply,"{$USER->firstname} {$USER->lastname}"); $mailer->setFrom($noreply, "{$USER->firstname} {$USER->lastname}");
$mailer->addAddress($invite->email,$invite->name); $mailer->addAddress($invite->email, $invite->name);
$mailer->addReplyTo($USER->email,"{$USER->firstname} {$USER->lastname}"); $mailer->addReplyTo($USER->email, "{$USER->firstname} {$USER->lastname}");
$invitehref = $CFG->wwwroot."/local/treestudyplan/invited.php?key={$invite->invitekey}"; $invitehref = $CFG->wwwroot."/local/treestudyplan/invited.php?key={$invite->invitekey}";
$data = [ 'permissions'=> '', $data = [ 'permissions'=> '',
'invitee' => $invite->name, 'invitee' => $invite->name,
'sender' => "{$USER->firstname} {$USER->lastname}", 'sender' => "{$USER->firstname} {$USER->lastname}",
'link' => $invitehref]; 'link' => $invitehref];
if($invite->allow_details || $invite->allow_calendar || $invite->allow_badges) if ($invite->allow_details || $invite->allow_calendar || $invite->allow_badges)
{ {
$data['permissions'] = get_string('invite_mail_permissions','local_treestudyplan'); $data['permissions'] = get_string('invite_mail_permissions', 'local_treestudyplan');
$data['permissions'] .= "<ul>\n"; $data['permissions'] .= "<ul>\n";
if($invite->allow_details ) if ($invite->allow_details )
{ {
$data['permissions'] .= "<li>".get_string('invite_allow_details','local_treestudyplan')."</li>\n"; $data['permissions'] .= "<li>".get_string('invite_allow_details', 'local_treestudyplan')."</li>\n";
} }
if($invite->allow_calendar) if ($invite->allow_calendar)
{ {
$data['permissions'] .= "<li>".get_string('invite_allow_calendar','local_treestudyplan')."</li>\n"; $data['permissions'] .= "<li>".get_string('invite_allow_calendar', 'local_treestudyplan')."</li>\n";
} }
if($invite->allow_badges) if ($invite->allow_badges)
{ {
$data['permissions'] .= "<li>".get_string('invite_allow_badges','local_treestudyplan')."</li>\n"; $data['permissions'] .= "<li>".get_string('invite_allow_badges', 'local_treestudyplan')."</li>\n";
} }
$data['permissions'] .= "</ul></p>\n"; $data['permissions'] .= "</ul></p>\n";
} }
$body = get_string('invite_mail_text','local_treestudyplan',$data); $body = get_string('invite_mail_text', 'local_treestudyplan', $data);
$subject = get_string('invite_mail_subject','local_treestudyplan', $data); $subject = get_string('invite_mail_subject', 'local_treestudyplan', $data);
$html = " $html = "
<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'> <!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>.
<html xmlns='http://www.w3.org/1999/xhtml'> <html xmlns='http://www.w3.org/1999/xhtml'>.
<head> <head>
<meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /> <meta http-equiv='Content-Type' content='text/html; charset=UTF-8' />
<title>{$subject}</title> <title>{$subject}</title>
@ -247,8 +267,7 @@ function local_treestudyplan_send_invite($inviteid)
} }
function local_treestudyplan_find_cohortmembers($cohortid) function local_treestudyplan_find_cohortmembers($cohortid) {
{
global $DB; global $DB;
// By default wherecondition retrieves all users except the deleted, not confirmed and guest. // By default wherecondition retrieves all users except the deleted, not confirmed and guest.
$params = ['cohortid' => $cohortid]; $params = ['cohortid' => $cohortid];
@ -257,24 +276,22 @@ function local_treestudyplan_find_cohortmembers($cohortid)
WHERE u.suspended = 0 AND u.id > 1 WHERE u.suspended = 0 AND u.id > 1
ORDER BY u.lastname ORDER BY u.lastname
"; ";
$availableusers = $DB->get_records_sql($sql,$params); $availableusers = $DB->get_records_sql($sql, $params);
return $availableusers; return $availableusers;
} }
function local_treestudyplan_get_cohort_path($cohort) function local_treestudyplan_get_cohort_path($cohort) {
{
$cohortcontext = context::instance_by_id($cohort->contextid); $cohortcontext = context::instance_by_id($cohort->contextid);
if($cohortcontext && $cohortcontext->id != SYSCONTEXTID) if ($cohortcontext && $cohortcontext->id != SYSCONTEXTID) {
{
$ctxpath = array_map( $ctxpath = array_map(
function($ctx){ return $ctx->get_context_name(false);}, function($ctx) { return $ctx->get_context_name(false);},
$cohortcontext->get_parent_contexts(true) $cohortcontext->get_parent_contexts(true)
); );
array_pop($ctxpath); // pop system context off the list array_pop($ctxpath); // pop system context off the list.
$ctxpath = array_reverse($ctxpath); $ctxpath = array_reverse($ctxpath);
$ctxpath[] = $cohort->name; $ctxpath[] = $cohort->name;
return implode(" / ",$ctxpath); return implode(" / ", $ctxpath);
} }
else else
{ {
@ -283,13 +300,13 @@ function local_treestudyplan_get_cohort_path($cohort)
} }
function local_treestudyplan_output_fragment_mod_edit_form($args){ function local_treestudyplan_output_fragment_mod_edit_form($args) {
global $CFG; global $CFG;
global $DB; global $DB;
$args = (object)$args; $args = (object)$args;
$context = $args->context; $context = $args->context;
if(empty($args->cmid)){ if (empty($args->cmid)) {
return "RANDOM!"; return "RANDOM!";
} }
@ -299,8 +316,8 @@ function local_treestudyplan_output_fragment_mod_edit_form($args){
// Check the course exists. // Check the course exists.
$course = \get_course($cm->course); $course = \get_course($cm->course);
// require_login // require_login.
require_login($course, false, $cm); // needed to setup proper $COURSE require_login($course, false, $cm); // needed to setup proper $COURSE.
list($cm, $context, $module, $data, $cw) = \get_moduleinfo_data($cm, $course); list($cm, $context, $module, $data, $cw) = \get_moduleinfo_data($cm, $course);

View File

@ -1,4 +1,25 @@
<?php <?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/>.
/**
*
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once("../../config.php"); require_once("../../config.php");
require_once($CFG->libdir.'/weblib.php'); require_once($CFG->libdir.'/weblib.php');
@ -7,22 +28,22 @@ use local_treestudyplan;
$systemcontext = context_system::instance(); $systemcontext = context_system::instance();
$PAGE->set_url("/local/treestudyplan/myreport.php",array()); $PAGE->set_url("/local/treestudyplan/myreport.php", array());
require_login(); require_login();
$PAGE->set_pagelayout('embedded'); $PAGE->set_pagelayout('embedded');
$PAGE->set_context($systemcontext); $PAGE->set_context($systemcontext);
$PAGE->set_title(get_string('report_invited','local_treestudyplan',"{$USER->firstname} {$USER->lastname}")); $PAGE->set_title(get_string('report_invited', 'local_treestudyplan', "{$USER->firstname} {$USER->lastname}"));
$PAGE->set_heading(get_string('report_invited','local_treestudyplan',"{$USER->firstname} {$USER->lastname}")); $PAGE->set_heading(get_string('report_invited', 'local_treestudyplan', "{$USER->firstname} {$USER->lastname}"));
// Load javascripts and specific css // Load javascripts and specific css.
$PAGE->requires->css(new moodle_url($CFG->wwwroot.'/local/treestudyplan/css/bootstrap-vue/bootstrap-vue.css')); $PAGE->requires->css(new moodle_url($CFG->wwwroot.'/local/treestudyplan/css/bootstrap-vue/bootstrap-vue.css'));
$PAGE->requires->css(new moodle_url($CFG->wwwroot.'/local/treestudyplan/css/devstyles.css')); $PAGE->requires->css(new moodle_url($CFG->wwwroot.'/local/treestudyplan/css/devstyles.css'));
$PAGE->requires->js_call_amd('local_treestudyplan/page-myreport', 'init'); $PAGE->requires->js_call_amd('local_treestudyplan/page-myreport', 'init');
//Local translate function //Local translate function.
function t($str, $param=null, $plugin='local_treestudyplan'){ function t($str, $param=null, $plugin='local_treestudyplan') {
print get_string($str,$plugin,$param); print get_string($str, $plugin, $param);
} }
print $OUTPUT->header(); print $OUTPUT->header();

View File

@ -1,4 +1,25 @@
<?php <?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/>.
/**
*
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once("../../config.php"); require_once("../../config.php");
require_once($CFG->libdir.'/weblib.php'); require_once($CFG->libdir.'/weblib.php');
@ -7,36 +28,36 @@ use local_treestudyplan;
$systemcontext = context_system::instance(); $systemcontext = context_system::instance();
$PAGE->set_url("/local/treestudyplan/myreport.php",array()); $PAGE->set_url("/local/treestudyplan/myreport.php", array());
require_login(); require_login();
$PAGE->set_pagelayout('base'); $PAGE->set_pagelayout('base');
$PAGE->set_context($systemcontext); $PAGE->set_context($systemcontext);
$teachermode = has_capability("local/treestudyplan:viewuserreports",$systemcontext); $teachermode = has_capability("local/treestudyplan:viewuserreports", $systemcontext);
if($teachermode){ if ($teachermode) {
$PAGE->set_title(get_string('myreport_teachermode','local_treestudyplan')); $PAGE->set_title(get_string('myreport_teachermode', 'local_treestudyplan'));
$PAGE->set_heading(get_string('myreport_teachermode','local_treestudyplan')); $PAGE->set_heading(get_string('myreport_teachermode', 'local_treestudyplan'));
} else { } else {
$PAGE->set_title(get_string('report_invited','local_treestudyplan',"{$USER->firstname} {$USER->lastname}")); $PAGE->set_title(get_string('report_invited', 'local_treestudyplan', "{$USER->firstname} {$USER->lastname}"));
$PAGE->set_heading(get_string('report_invited','local_treestudyplan',"{$USER->firstname} {$USER->lastname}")); $PAGE->set_heading(get_string('report_invited', 'local_treestudyplan', "{$USER->firstname} {$USER->lastname}"));
} }
// Load javascripts and specific css // Load javascripts and specific css.
$PAGE->requires->css(new moodle_url($CFG->wwwroot.'/local/treestudyplan/css/bootstrap-vue/bootstrap-vue.css')); $PAGE->requires->css(new moodle_url($CFG->wwwroot.'/local/treestudyplan/css/bootstrap-vue/bootstrap-vue.css'));
$PAGE->requires->css(new moodle_url($CFG->wwwroot.'/local/treestudyplan/css/devstyles.css')); $PAGE->requires->css(new moodle_url($CFG->wwwroot.'/local/treestudyplan/css/devstyles.css'));
$PAGE->requires->js_call_amd('local_treestudyplan/page-myreport', 'init',[$teachermode?'teaching':'myreport']); $PAGE->requires->js_call_amd('local_treestudyplan/page-myreport', 'init', [$teachermode?'teaching':'myreport']);
//Local translate function //Local translate function.
function t($str, $param=null, $plugin='local_treestudyplan'){ function t($str, $param=null, $plugin='local_treestudyplan') {
print get_string($str,$plugin,$param); print get_string($str, $plugin, $param);
} }
print $OUTPUT->header(); print $OUTPUT->header();
?> ?>
<div class="m-buttonbar" style="margin-bottom: 1em; text-align: right;"> <div class="m-buttonbar" style="margin-bottom: 1em; text-align: right;">
<?php if(!$teachermode){ ?> <?php if (!$teachermode) { ?>
<a class="btn btn-primary" href="invitations.php" id="manage_invites"><i class="fa fa-share"></i> <?php t('manage_invites'); ?></a> <a class="btn btn-primary" href="invitations.php" id="manage_invites"><i class="fa fa-share"></i> <?php t('manage_invites'); ?></a>
<?php } ?> <?php } ?>
</div> </div>

View File

@ -1,168 +0,0 @@
<?php
// If not, assume the cwd is not symlinked and proceed as we are used to
require_once("../../config.php");
require_once($CFG->libdir.'/weblib.php');
require_once($CFG->dirroot.'/grade/querylib.php');
require_once($CFG->dirroot.'/cohort/lib.php');
require_once($CFG->dirroot.'/cohort/locallib.php');
require_once("lib.php");
use local_treestudyplan;
//admin_externalpage_setup('major');
$systemcontext = context_system::instance();
require_login();
require_capability('local/treestudyplan:viewothercards',$systemcontext);
$PAGE->set_pagelayout('base');
$PAGE->set_context($systemcontext);
$PAGE->set_title(get_string('report_index','local_treestudyplan'));
$PAGE->set_heading(get_string('report_index','local_treestudyplan'));
// Load javascripts
//$PAGE->requires->js_call_amd('local_treestudyplan/fixlinks', 'init', ['div.tab-content']);
$PAGE->requires->js_call_amd('block_gradelevel/renderbadge', 'init');
$PAGE->requires->js_call_amd('local_treestudyplan/report', 'init');
//$PAGE->requires->css(new moodle_url($CFG->wwwroot.'/local/treestudyplan/css/eqstyles.css'));
$PAGE->requires->css(new moodle_url($CFG->wwwroot.'/local/treestudyplan/css/bootstrap-toggle.min.css'));
$userid = null;
$studentid = optional_param('studentid', '', PARAM_INT);
$cohortid = optional_param('cohortid', '', PARAM_INT);
if(!empty($studentid)){
$PAGE->set_url("/local/treestudyplan/reports.php",array('studentid' => $studentid));
$student = $DB->get_record("user",['id' => $studentid]);
$userlist = [];
$nextstudent = null;
$prevstudent = null;
if(!empty($cohortid))
{
$cohort = $DB->get_record("cohort",['id' => $cohortid]);
$userlist = array_values(local_treestudyplan_find_cohortmembers($cohortid));
for($i = 0; $i < count($userlist); $i++)
{
if($userlist[$i]->userid == $studentid)
{
if($i > 0)
{
$prevstudent = (object)['id' => $userlist[$i - 1]->userid, 'name' => "{$userlist[$i - 1]->firstname} {$userlist[$i - 1]->lastname}"];
}
if($i < count($userlist) - 1)
{
$nextstudent = (object)['id' => $userlist[$i + 1]->userid, 'name' => "{$userlist[$i + 1]->firstname} {$userlist[$i + 1]->lastname}"];
}
break;
}
}
$cohortpath = local_treestudyplan_get_cohort_path($cohort);
$PAGE->set_title(get_string('report_invited','local_treestudyplan',"{$cohortpath}: {$student->firstname} {$student->lastname}"));
$PAGE->set_heading(get_string('report_invited','local_treestudyplan',"{$cohortpath}: {$student->firstname} {$student->lastname}"));
}
else
{
$PAGE->set_title(get_string('report_invited','local_treestudyplan',"$cohort->{$student->firstname} {$student->lastname}"));
$PAGE->set_heading(get_string('report_invited','local_treestudyplan',"{$student->firstname} {$student->lastname}"));
}
$gradewriter = new local_treestudyplan_cardwriter($studentid,true);
$badgewriter = new local_treestudyplan_badgewriter($studentid);
$calendarwriter = new local_treestudyplan_calendarwriter($studentid);
print $OUTPUT->header();
print "<div class='m-buttonbar' style='margin-bottom: 1.5em; height: 1em;'>";
if(isset($prevstudent))
{
print "<a style='float:left;' href='/local/treestudyplan/reports.php?studentid={$prevstudent->id}&cohortid={$cohortid}'><i class='fa fa-arrow-left'></i> {$prevstudent->name} </a>";
}
if(isset($nextstudent))
{
print "<a style='float:right;' href='/local/treestudyplan/reports.php?studentid={$nextstudent->id}&cohortid={$cohortid}'>{$nextstudent->name} <i class='fa fa-arrow-right'></i></a>";
}
print "</div>";
print "<ul class='nav nav-tabs' role='tablist'>";
print "<li class='nav-item'><a class='nav-link active' href='#link-report' data-toggle='tab' role='tab'>".get_string('nav_report','local_treestudyplan')."</a></li>";
print "<li class='nav-item'><a class='nav-link ' href='#link-badges' data-toggle='tab' role='tab'>".get_string('nav_badges','local_treestudyplan')."</a></li>";
print "<li class='nav-item'><a class='nav-link ' href='#link-calendar' data-toggle='tab' role='tab'>".get_string('nav_calendar','local_treestudyplan')."</a></li>";
print "</ul>";
print "<div class='tab-content mt-3'>";
print "<div class='tab-pane active' id='link-report' data-toggle='tab' role='tab'>";
print $gradewriter->render(true,false);
print "</div>";
print "<div class='tab-pane ' id='link-badges' data-toggle='tab' role='tab'>";
print $badgewriter->render();
print "</div>";
print "<div class='tab-pane' id='link-calendar' data-toggle='tab' role='tab'>";
print $calendarwriter->render();
print "</div>";
print "</div>";
print $OUTPUT->footer();
}
else {
$PAGE->set_url("/local/treestudyplan/reports.php",array());
// show student picker
$cohortlist = $DB->get_records("cohort");
$cohorts = [];
foreach($cohortlist as $c)
{
if($c->visible)
{
$cohortcontext = context::instance_by_id($c->contextid);
if($cohortcontext) // TODO: add check if user has rights in this context
{
$users = local_treestudyplan_find_cohortmembers($c->id);
$cohorts[$c->id] = (object)[
'id' => $c->id,
'cohort' => $c,
'name' => $c->name,
'path' => local_treestudyplan_get_cohort_path($c),
'users' => $users,
];
}
}
}
print $OUTPUT->header();
usort($cohorts,function($a,$b){
return $a->path <=> $b->path;
});
$regex = get_config('local_treestudyplan', 'cohortidregex');
foreach($cohorts as $c)
{
$m = [];
if(preg_match("/".$regex."/",$c->cohort->idnumber,$m) && $c->cohort->visible)
{
print "<legend class='collapse-header'><a data-toggle='collapse' class='collapsed' href='#cohort-{$c->id}' role='button'>{$c->path}</a></legend>";
print "<div id='cohort-{$c->id}' class='collapse'>";
print "<div class='card card-body'><ul class='gradecardlist'>";
foreach($c->users as $u)
{
print "<li class='gradestudent'>";
print "<a href='/local/treestudyplan/reports.php?studentid={$u->userid}&cohortid={$c->id}'>{$u->firstname} {$u->lastname}</a>";
print "</li>";
}
print "</ul>";
print "</div></div>";
}
}
print "</div>";
print $OUTPUT->footer();
}

View File

@ -1,17 +1,17 @@
<?php <?php
// This file is part of Moodle - http://moodle.org/ // This file is part of Moodle - http://moodle.org/.
// //
// Moodle is free software: you can redistribute it and/or modify // Moodle is free software: you can redistribute it and/or modify.
// it under the terms of the GNU General Public License as published by // it under the terms of the GNU General Public License as published by.
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or.
// (at your option) any later version. // (at your option) any later version.
// //
// Moodle is distributed in the hope that it will be useful, // Moodle is distributed in the hope that it will be useful,.
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of.
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the.
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License.
// along with Moodle. If not, see <http://www.gnu.org/licenses/>. // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/** /**
@ -19,7 +19,7 @@
* *
* @package local_chronotable * @package local_chronotable
* @copyright 2017 Alexander Bias, Ulm University <alexander.bias@uni-ulm.de> * @copyright 2017 Alexander Bias, Ulm University <alexander.bias@uni-ulm.de>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later.
*/ */
defined('MOODLE_INTERNAL') || die(); defined('MOODLE_INTERNAL') || die();
@ -29,10 +29,10 @@ use local_treestudyplan\aggregator;
if ($hassiteconfig) { if ($hassiteconfig) {
/************************************** /**************************************
* *
* Main studyplan settings * Main studyplan settings
* *
*************************************/ *************************************/
// Create admin settings category. // Create admin settings category.
$ADMIN->add('courses', new admin_category('local_treestudyplan', $ADMIN->add('courses', new admin_category('local_treestudyplan',
@ -43,15 +43,15 @@ if ($hassiteconfig) {
get_string('settingspage', 'local_treestudyplan', null, true)); get_string('settingspage', 'local_treestudyplan', null, true));
// GOAL AGGREGATION SETTINGS // GOAL AGGREGATION SETTINGS.
$page->add(new admin_setting_heading('local_treestudyplan/aggregation_heading', $page->add(new admin_setting_heading('local_treestudyplan/aggregation_heading',
get_string('setting_aggregation_heading', 'local_treestudyplan'), get_string('setting_aggregation_heading', 'local_treestudyplan'),
get_string('settingdesc_aggregation_heading', 'local_treestudyplan') get_string('settingdesc_aggregation_heading', 'local_treestudyplan')
)); ));
$aggregators = []; $aggregators = [];
foreach(aggregator::list() as $a){ foreach (aggregator::list() as $a) {
$aggregators[$a] = get_string("{$a}_aggregator_title",'local_treestudyplan', null, true); $aggregators[$a] = get_string("{$a}_aggregator_title", 'local_treestudyplan', null, true);
} }
$page->add(new admin_setting_configselect('local_treestudyplan/aggregation_mode', $page->add(new admin_setting_configselect('local_treestudyplan/aggregation_mode',
@ -61,7 +61,7 @@ if ($hassiteconfig) {
$aggregators $aggregators
)); ));
// DISPLAY COURSE INFO SETTINGS // DISPLAY COURSE INFO SETTINGS.
$page->add(new admin_setting_heading('local_treestudyplan/display_heading', $page->add(new admin_setting_heading('local_treestudyplan/display_heading',
get_string('setting_display_heading', 'local_treestudyplan'), get_string('setting_display_heading', 'local_treestudyplan'),
get_string('settingdesc_display_heading', 'local_treestudyplan') get_string('settingdesc_display_heading', 'local_treestudyplan')
@ -70,9 +70,9 @@ if ($hassiteconfig) {
$displayfields = ["shortname" => get_string("shortname"), "idnumber" => get_string("idnumber")]; $displayfields = ["shortname" => get_string("shortname"), "idnumber" => get_string("idnumber")];
$handler = \core_customfield\handler::get_handler('core_course', 'course'); $handler = \core_customfield\handler::get_handler('core_course', 'course');
foreach($handler->get_categories_with_fields() as $cat){ foreach ($handler->get_categories_with_fields() as $cat) {
$catname = $cat->get_formatted_name(); $catname = $cat->get_formatted_name();
foreach($cat->get_fields() as $field) { foreach ($cat->get_fields() as $field) {
$fieldname = $field->get_formatted_name(); $fieldname = $field->get_formatted_name();
$fieldid = $field->get("shortname"); $fieldid = $field->get("shortname");
$displayfields["customfield_".$fieldid] = $catname.": ".$fieldname; $displayfields["customfield_".$fieldid] = $catname.": ".$fieldname;
@ -86,7 +86,7 @@ if ($hassiteconfig) {
$displayfields $displayfields
)); ));
// BISTATE AGGREGATON DEFAULTS // BISTATE AGGREGATON DEFAULTS.
$page->add(new admin_setting_heading('local_treestudyplan/bistate_aggregation_heading', $page->add(new admin_setting_heading('local_treestudyplan/bistate_aggregation_heading',
get_string('setting_bistate_heading', 'local_treestudyplan'), get_string('setting_bistate_heading', 'local_treestudyplan'),
get_string('settingdesc_bistate_heading', 'local_treestudyplan') get_string('settingdesc_bistate_heading', 'local_treestudyplan')
@ -129,10 +129,10 @@ if ($hassiteconfig) {
$ADMIN->add('local_treestudyplan', $page); $ADMIN->add('local_treestudyplan', $page);
/************************************** /**************************************
* *
* Manage plans link (systemwide) * Manage plans link (systemwide)
* *
*************************************/ *************************************/
$ADMIN->add('local_treestudyplan', new admin_externalpage( $ADMIN->add('local_treestudyplan', new admin_externalpage(
'local_treestudyplan_editplans', 'local_treestudyplan_editplans',
@ -141,57 +141,57 @@ if ($hassiteconfig) {
/************************************** /**************************************
* *
* Settings page: Cohort sync * Settings page: Cohort sync
* *
*************************************/ *************************************/
$page_csync = new admin_settingpage('local_treestudyplan_settings_cohortsync', $page_csync = new admin_settingpage('local_treestudyplan_settings_cohortsync',
get_string('settingspage_csync', 'local_treestudyplan', null, true)); get_string('settingspage_csync', 'local_treestudyplan', null, true));
// Description heading // Description heading.
$page_csync->add(new admin_setting_heading('local_treestudyplan/csync_heading', $page_csync->add(new admin_setting_heading('local_treestudyplan/csync_heading',
get_string('setting_csync_heading', 'local_treestudyplan'), get_string('setting_csync_heading', 'local_treestudyplan'),
get_string('settingdesc_csync_heading', 'local_treestudyplan') get_string('settingdesc_csync_heading', 'local_treestudyplan')
)); ));
// Enable/disable cohort sync // Enable/disable cohort sync.
$page_csync->add(new admin_setting_configcheckbox('local_treestudyplan/csync_enable', $page_csync->add(new admin_setting_configcheckbox('local_treestudyplan/csync_enable',
get_string('setting_csync_enable_field', 'local_treestudyplan'), get_string('setting_csync_enable_field', 'local_treestudyplan'),
get_string('settingdesc_csync_enable_field', 'local_treestudyplan'), get_string('settingdesc_csync_enable_field', 'local_treestudyplan'),
false false
)); ));
// Enable/disable autoremove // Enable/disable autoremove.
$page_csync->add(new admin_setting_configcheckbox('local_treestudyplan/csync_autoremove', $page_csync->add(new admin_setting_configcheckbox('local_treestudyplan/csync_autoremove',
get_string('setting_csync_autoremove_field', 'local_treestudyplan'), get_string('setting_csync_autoremove_field', 'local_treestudyplan'),
get_string('settingdesc_csync_autoremove_field', 'local_treestudyplan'), get_string('settingdesc_csync_autoremove_field', 'local_treestudyplan'),
true true
)); ));
// Enable/disable remembering previously added cohort syncs so they're not automatically deleted // Enable/disable remembering previously added cohort syncs so they're not automatically deleted.
$page_csync->add(new admin_setting_configcheckbox('local_treestudyplan/csync_remember_manual_csync', $page_csync->add(new admin_setting_configcheckbox('local_treestudyplan/csync_remember_manual_csync',
get_string('setting_csync_remember_manual_csync_field', 'local_treestudyplan'), get_string('setting_csync_remember_manual_csync_field', 'local_treestudyplan'),
get_string('settingdesc_csync_remember_manual_csync_field', 'local_treestudyplan'), get_string('settingdesc_csync_remember_manual_csync_field', 'local_treestudyplan'),
true true
)); ));
// Enable/disable group creation // Enable/disable group creation.
$page_csync->add(new admin_setting_configcheckbox('local_treestudyplan/csync_creategroup', $page_csync->add(new admin_setting_configcheckbox('local_treestudyplan/csync_creategroup',
get_string('setting_csync_creategroup_field', 'local_treestudyplan'), get_string('setting_csync_creategroup_field', 'local_treestudyplan'),
get_string('settingdesc_csync_creategroup_field', 'local_treestudyplan'), get_string('settingdesc_csync_creategroup_field', 'local_treestudyplan'),
true true
)); ));
// Sync users too yes/no? // Sync users too yes/no?.
$page_csync->add(new admin_setting_configcheckbox('local_treestudyplan/csync_users', $page_csync->add(new admin_setting_configcheckbox('local_treestudyplan/csync_users',
get_string('setting_csync_users_field', 'local_treestudyplan'), get_string('setting_csync_users_field', 'local_treestudyplan'),
get_string('settingdesc_csync_users_field', 'local_treestudyplan'), get_string('settingdesc_csync_users_field', 'local_treestudyplan'),
true true
)); ));
// Select csync enrol role // Select csync enrol role.
if (!during_initial_install()) { if (!during_initial_install()) {
$options = get_default_enrol_roles(context_system::instance()); $options = get_default_enrol_roles(context_system::instance());
$student = get_archetype_roles('student'); $student = get_archetype_roles('student');
@ -206,19 +206,19 @@ if ($hassiteconfig) {
$ADMIN->add('local_treestudyplan', $page_csync); $ADMIN->add('local_treestudyplan', $page_csync);
/************************************** /**************************************
* *
* Grade and scale interpretation * Grade and scale interpretation
* *
*************************************/ *************************************/
$ADMIN->add('local_treestudyplan', new admin_externalpage( $ADMIN->add('local_treestudyplan', new admin_externalpage(
'local_treestudyplan_gradeconfig', 'local_treestudyplan_gradeconfig',
get_string('cfg_grades', 'local_treestudyplan', null, true), get_string('cfg_grades', 'local_treestudyplan', null, true),
$CFG->wwwroot . '/local/treestudyplan/cfg_grades.php')); $CFG->wwwroot . '/local/treestudyplan/cfg_grades.php'));
/************************************** /**************************************
* *
* Add the help link (Temporary until a better place is found) * Add the help link (Temporary until a better place is found)
* *
**************************************/ **************************************/
$ADMIN->add('local_treestudyplan', new admin_externalpage( $ADMIN->add('local_treestudyplan', new admin_externalpage(

View File

@ -1,182 +0,0 @@
<?php
namespace local_treestudyplan;
use \local_treestudyplan\local\aggregators\bistate_aggregator;
class bistate_aggregator_test extends \basic_testcase {
private function goalaggregation_test($configstr, $outcome, $completions,$required){
$ag = new bistate_aggregator($configstr);
// test aggregation with the required data
$result = $ag->aggregate_binary_goals($completions,$required);
// assert if the test is succesful
$this->assertEquals(completion::label($outcome),completion::label($result));
}
private function junctionaggregation_test($configstr, $outcome, $completions){
$ag = new bistate_aggregator($configstr);
// test aggregation with the minimum required data
$result = $ag->aggregate_junction($completions);
// assert if the test is succesful
$this->assertEquals(completion::label($outcome),completion::label($result));
}
public function test_goalaggregation_0() {
$this->goalaggregation_test(
'{"thresh_excellent":100,"thresh_good":85,"thresh_completed":66,"thresh_progress":25,"use_failed":true,"accept_pending_as_submitted":true}',
completion::INCOMPLETE,
[
],
[],
);
}
public function test_goalaggregation_1() {
$this->goalaggregation_test(
'{"thresh_excellent":100,"thresh_good":85,"thresh_completed":66,"thresh_progress":25,"use_failed":true,"accept_pending_as_submitted":true}',
completion::FAILED,
[ // completions
completion::FAILED,
completion::FAILED,
completion::FAILED,
completion::FAILED,
completion::FAILED,
],
[] // required
);
}
public function test_goalaggregation_2() {
$this->goalaggregation_test(
'{"thresh_excellent":100,"thresh_good":85,"thresh_completed":66,"thresh_progress":25,"use_failed":true,"accept_pending_as_submitted":true}',
completion::EXCELLENT,
[
completion::COMPLETED,
completion::COMPLETED,
completion::COMPLETED,
completion::COMPLETED,
completion::COMPLETED,
],
[],
);
}
public function test_goalaggregation_3() {
$this->goalaggregation_test(
'{"thresh_excellent":100,"thresh_good":85,"thresh_completed":66,"thresh_progress":25,"use_failed":true,"accept_pending_as_submitted":true}',
completion::INCOMPLETE,
[
completion::INCOMPLETE,
completion::INCOMPLETE,
completion::INCOMPLETE,
completion::INCOMPLETE,
completion::INCOMPLETE,
],
[],
);
}
public function test_goalaggregation_4() {
$this->goalaggregation_test(
'{"thresh_excellent":100,"thresh_good":85,"thresh_completed":66,"thresh_progress":25,"use_failed":true,"accept_pending_as_submitted":true}',
completion::PROGRESS,
[
completion::COMPLETED,
completion::COMPLETED,
completion::COMPLETED,
completion::PROGRESS,
completion::PROGRESS,
],
[],
);
}
public function test_goalaggregation_5() {
$this->goalaggregation_test(
'{"thresh_excellent":100,"thresh_good":85,"thresh_completed":66,"thresh_progress":25,"use_failed":true,"accept_pending_as_submitted":true}',
completion::COMPLETED,
[
completion::COMPLETED,
completion::COMPLETED,
completion::COMPLETED,
completion::COMPLETED,
completion::PROGRESS,
],
[],
);
}
public function test_goalaggregation_6() {
$this->goalaggregation_test(
'{"thresh_excellent":100,"thresh_good":85,"thresh_completed":66,"thresh_progress":25,"use_failed":true,"accept_pending_as_submitted":true}',
completion::PROGRESS,
[
completion::PROGRESS,
completion::PROGRESS,
completion::PROGRESS,
completion::PROGRESS,
completion::INCOMPLETE,
completion::INCOMPLETE,
],
[],
);
}
public function test_junctionaggregation_0() {
$this->junctionaggregation_test(
'{"thresh_excellent":100,"thresh_good":85,"thresh_completed":66,"thresh_progress":25,"use_failed":true,"accept_pending_as_submitted":true}',
completion::FAILED,
[
completion::FAILED,
completion::FAILED,
completion::FAILED,
completion::FAILED,
],
);
}
public function test_junctionaggregation_1() {
$this->junctionaggregation_test(
'{"thresh_excellent":100,"thresh_good":85,"thresh_completed":66,"thresh_progress":25,"use_failed":true,"accept_pending_as_submitted":true}',
completion::INCOMPLETE,
[
completion::INCOMPLETE,
completion::INCOMPLETE,
completion::INCOMPLETE,
completion::INCOMPLETE,
],
);
}
public function test_junctionaggregation_2() {
$this->junctionaggregation_test(
'{"thresh_excellent":100,"thresh_good":85,"thresh_completed":66,"thresh_progress":25,"use_failed":true,"accept_pending_as_submitted":true}',
completion::FAILED,
[
completion::FAILED,
completion::INCOMPLETE,
completion::INCOMPLETE,
completion::INCOMPLETE,
],
);
}
public function test_junctionaggregation_3() {
$this->junctionaggregation_test(
'{"thresh_excellent":100,"thresh_good":85,"thresh_completed":66,"thresh_progress":25,"use_failed":true,"accept_pending_as_submitted":true}',
completion::PROGRESS,
[
completion::PROGRESS,
completion::INCOMPLETE,
completion::INCOMPLETE,
completion::INCOMPLETE,
],
);
}
}

View File

@ -1,7 +1,29 @@
<?php <?php
$plugin->component = 'local_treestudyplan'; // Recommended since 2.0.2 (MDL-26035). Required since 3.0 (MDL-48494) // This file is part of the Studyplan plugin for Moodle
$plugin->version = 2023082101; // YYYYMMDDHH (year, month, day, iteration) //
$plugin->requires = 2021051700; // YYYYMMDDHH (This is the release version for Moodle 3.11) // 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
*/
defined('MOODLE_INTERNAL') || die();
$plugin->component = 'local_treestudyplan'; // Recommended since 2.0.2 (MDL-26035). Required since 3.0 (MDL-48494).
$plugin->version = 2023082101; // YYYYMMDDHH (year, month, day, iteration).
$plugin->requires = 2021051700; // YYYYMMDDHH (This is the release version for Moodle 3.11).
$plugin->dependencies = [ $plugin->dependencies = [
'theme_boost' => 2019052000, 'theme_boost' => 2019052000,

View File

@ -1,4 +1,25 @@
<?php <?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/>.
/**
*
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once("../../config.php"); require_once("../../config.php");
use \local_treestudyplan\courseservice; use \local_treestudyplan\courseservice;
@ -7,66 +28,65 @@ require_once($CFG->libdir.'/weblib.php');
$systemcontext = context_system::instance(); $systemcontext = context_system::instance();
$PAGE->set_url("/local/treestudyplan/view-plan.php",array()); $PAGE->set_url("/local/treestudyplan/view-plan.php", array());
require_login(); require_login();
// Figure out the context (category or system, based on either category or context parameter) // Figure out the context (category or system, based on either category or context parameter).
$categoryid = optional_param('categoryid', 0, PARAM_INT); // Category id $categoryid = optional_param('categoryid', 0, PARAM_INT); // Category id.
$contextid = optional_param('contextid', 0, PARAM_INT); // Context id $contextid = optional_param('contextid', 0, PARAM_INT); // Context id.
if($categoryid > 0){ if ($categoryid > 0) {
$studyplancontext = context_coursecat::instance($categoryid); $studyplancontext = context_coursecat::instance($categoryid);
} }
elseif($contextid > 0) else if ($contextid > 0) {
{
$studyplancontext = context::instance_by_id($contextid); $studyplancontext = context::instance_by_id($contextid);
if(in_array($studyplancontext->contextlevel,[CONTEXT_SYSTEM,CONTEXT_COURSECAT])) if (in_array($studyplancontext->contextlevel, [CONTEXT_SYSTEM, CONTEXT_COURSECAT]))
{ {
$categoryid = $studyplancontext->instanceid; $categoryid = $studyplancontext->instanceid;
} }
else else
{ {
$studyplancontext = $systemcontext; $studyplancontext = $systemcontext;
} }
} }
else else
{ {
// If no context is selected, find the first available one // If no context is selected, find the first available one.
$available_contexts = courseservice::list_accessible_categories_with_usage("view"); $available_contexts = courseservice::list_accessible_categories_with_usage("view");
$contextid=1; // fallback to system context $contextid=1; // fallback to system context.
foreach($available_contexts as $ctx){ foreach ($available_contexts as $ctx) {
if($ctx->count > 0){ if ($ctx->count > 0) {
$contextid = $ctx->ctxid; $contextid = $ctx->ctxid;
break; break;
} }
} }
// reload page with selected category // reload page with selected category.
$url = new \moodle_url('/local/treestudyplan/view-plan.php',["contextid" => $contextid]); $url = new \moodle_url('/local/treestudyplan/view-plan.php', ["contextid" => $contextid]);
header('Location: '.$url->out(false), true, 302); header('Location: '.$url->out(false), true, 302);
exit; exit;
} }
require_capability('local/treestudyplan:viewuserreports',$studyplancontext); require_capability('local/treestudyplan:viewuserreports', $studyplancontext);
$contextname = $studyplancontext->get_context_name(false,false); $contextname = $studyplancontext->get_context_name(false, false);
$PAGE->set_pagelayout('base'); $PAGE->set_pagelayout('base');
$PAGE->set_context($studyplancontext); $PAGE->set_context($studyplancontext);
$PAGE->set_title(get_string('view_plan','local_treestudyplan')." - ".$contextname); $PAGE->set_title(get_string('view_plan', 'local_treestudyplan')." - ".$contextname);
$PAGE->set_heading(get_string('view_plan','local_treestudyplan')." - ".$contextname); $PAGE->set_heading(get_string('view_plan', 'local_treestudyplan')." - ".$contextname);
if($studyplancontext->id > 1){ if ($studyplancontext->id > 1) {
navigation_node::override_active_url(new moodle_url('/course/index.php', ['categoryid' => $categoryid ])); navigation_node::override_active_url(new moodle_url('/course/index.php', ['categoryid' => $categoryid ]));
$PAGE->navbar->add(get_string('view_plan','local_treestudyplan')); $PAGE->navbar->add(get_string('view_plan', 'local_treestudyplan'));
} }
// Load javascripts and specific css // Load javascripts and specific css.
$PAGE->requires->css(new moodle_url($CFG->wwwroot.'/local/treestudyplan/css/bootstrap-vue/bootstrap-vue.css')); $PAGE->requires->css(new moodle_url($CFG->wwwroot.'/local/treestudyplan/css/bootstrap-vue/bootstrap-vue.css'));
$PAGE->requires->css(new moodle_url($CFG->wwwroot.'/local/treestudyplan/css/devstyles.css')); $PAGE->requires->css(new moodle_url($CFG->wwwroot.'/local/treestudyplan/css/devstyles.css'));
$PAGE->requires->js_call_amd('local_treestudyplan/page-view-plan', 'init',[$studyplancontext->id,$categoryid]); $PAGE->requires->js_call_amd('local_treestudyplan/page-view-plan', 'init', [$studyplancontext->id, $categoryid]);
//Local translate function //Local translate function.
function t($str, $param=null, $plugin='local_treestudyplan'){ function t($str, $param=null, $plugin='local_treestudyplan') {
print get_string($str,$plugin,$param); print get_string($str, $plugin, $param);
} }
print $OUTPUT->header(); print $OUTPUT->header();
@ -78,11 +98,11 @@ print $OUTPUT->header();
</div> </div>
</div> </div>
<div v-cloak> <div v-cloak>
<div v-if='!activestudyplan && usedcontexts' class='ml-3 mb-3'> <div v-if='!activestudyplan && usedcontexts' class='ml-3 mb-3'>
<b-form-select text='<?php print($contextname);?>' :value="contextid"> <b-form-select text='<?php print($contextname);?>' :value="contextid">
<b-form-select-option v-for='ctx in usedcontexts' :key='ctx.id' :value="ctx.context_id" @click='switchContext(ctx)' <b-form-select-option v-for='ctx in usedcontexts' :key='ctx.id' :value="ctx.context_id" @click='switchContext(ctx)'
:active="ctx.context_id == contextid" :class="(ctx.studyplancount > 0)?'font-weight-bold':''" :active="ctx.context_id == contextid" :class="(ctx.studyplancount > 0)?'font-weight-bold':''"
><span v-for="(p,i) in ctx.category.path"><span v-if="i>0"> / </span>{{ p }}</span> <span>({{ ctx.studyplancount }})</b-form-select-option> ><span v-for="(p, i) in ctx.category.path"><span v-if="i>0"> / </span>{{ p }}</span> <span>({{ ctx.studyplancount }})</b-form-select-option>
</b-form-select> </b-form-select>
</div> </div>
<h3 v-else><?php print $contextname; ?></h3> <h3 v-else><?php print $contextname; ?></h3>
@ -90,7 +110,7 @@ print $OUTPUT->header();
<a href='#' v-if='displayedstudyplan' @click.prevent='closeStudyplan'><i style='font-size: 150%;' class='fa fa-chevron-left'></i> <?php t('back');?></a> <a href='#' v-if='displayedstudyplan' @click.prevent='closeStudyplan'><i style='font-size: 150%;' class='fa fa-chevron-left'></i> <?php t('back');?></a>
<span v-if='displayedstudyplan'><?php t("studyplan_select"); ?></span>&nbsp; <span v-if='displayedstudyplan'><?php t("studyplan_select"); ?></span>&nbsp;
<b-form-select v-if='displayedstudyplan' lazy :text='dropdown_title'> <b-form-select v-if='displayedstudyplan' lazy :text='dropdown_title'>
<b-form-select-option v-for='(studyplan,planindex) in studyplans' :key='studyplan.id' @click='selectStudyplan(studyplan)'>{{ studyplan.name }}</b-form-select-option> <b-form-select-option v-for='(studyplan, planindex) in studyplans' :key='studyplan.id' @click='selectStudyplan(studyplan)'>{{ studyplan.name }}</b-form-select-option>
</b-form-select>&nbsp; </b-form-select>&nbsp;
<b-button variant='primary' v-if='associatedstudents && associatedstudents.length > 0' v-b-toggle.toolbox-sidebar><?php t('selectstudent_btn') ?></b-button> <b-button variant='primary' v-if='associatedstudents && associatedstudents.length > 0' v-b-toggle.toolbox-sidebar><?php t('selectstudent_btn') ?></b-button>
</div> </div>
@ -104,9 +124,9 @@ print $OUTPUT->header();
<div v-else class='t-studyplan-notselected'> <div v-else class='t-studyplan-notselected'>
<p><?php t("studyplan_noneselected"); ?></p> <p><?php t("studyplan_noneselected"); ?></p>
<b-card-group deck> <b-card-group deck>
<s-studyplan-card <s-studyplan-card
v-for='(studyplan,planindex) in studyplans' v-for='(studyplan, planindex) in studyplans'
:key='studyplan.id' :key='studyplan.id'
v-model='studyplans[planindex]' v-model='studyplans[planindex]'
open open
@open='selectStudyplan(studyplan)' @open='selectStudyplan(studyplan)'