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 */
/*
<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';
/* 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.
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
// 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;
include('version.php');
require_once('version.php');
$a = explode("_",$plugin->component,2);
$a = explode("_", $plugin->component, 2);
$plugin->type = $a[0];
$plugin->name = $a[1];
$exclude_paths = [
"build", // dir for build zip files
$excludepaths = [
"build", // Dir for build zip files.
"build/*",
"build.*",
"vuemode.sh",
@ -19,28 +41,28 @@ $exclude_paths = [
"*.zip",
];
// Determine some paths
// Determine some paths.
$wd = realpath(dirname(__FILE__));
$parent = dirname($wd);
$plugindirname = basename($wd);
$builddir = $wd."/"."build";
$zipname = $builddir."/"."{$plugin->name}-{$plugin->version}.zip";
// create the exclude line
// Create the exclude line.
$exclude = "-x ";
foreach($exclude_paths as $x){
foreach ($excludepaths as $x) {
$exclude .= "'{$plugindirname}/{$x}' ";
}
if(!is_dir($builddir)){
if (!is_dir($builddir)) {
mkdir($builddir);
if(!is_dir($builddir)){
if (!is_dir($builddir)) {
print("Cannot access dir '{$builddir}' to store zip files\n");
exit(1);
}
}
if(file_exists($zipfile)){
if (file_exists($zipfile)) {
print("Zip file '{$zipfile}' already exists. Exiting...\n");
exit(1);
}

View File

@ -1,12 +1,32 @@
<?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');
admin_externalpage_setup("local_treestudyplan_gradeconfig");
$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);
@ -18,186 +38,184 @@ $scales = \grade_scale::fetch_all_global();
$mappings = $DB->get_records(GRADECFG_TABLE);
$scale_cfgs = [];
$grade_cfgs = [];
foreach($mappings as $cfg){
if(!empty($cfg->scale_id)){
foreach ($mappings as $cfg) {
if (!empty($cfg->scale_id)) {
$scale_cfgs[$cfg->scale_id] = $cfg;
}
elseif(!empty($cfg->grade_points)){
}
else if (!empty($cfg->grade_points)) {
$grade_cfgs[$cfg->grade_points] = $cfg;
}
}
print $OUTPUT->header();
if($_POST["action"] == "update"){
// First loop through the scales to see which need to be updated
foreach($scales as $scale)
{
if(array_key_exists($scale->id,$scale_cfgs)){
if ($_POST["action"] == "update") {
// First loop through the scales to see which need to be updated.
foreach ($scales as $scale) {
if (array_key_exists($scale->id, $scale_cfgs)) {
$scalecfg = $scale_cfgs[$scale->id];
$needupdate = false;
foreach(["min_progress", "min_completed"] as $handle) {
foreach (["min_progress", "min_completed"] as $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]);
if($value != $scalecfg->$handle){
if ($value != $scalecfg->$handle) {
$scalecfg->$handle = $value;
$needupdate = true;
}
}
}
if($needupdate){
$DB->update_record(GRADECFG_TABLE,$scalecfg);
}
if ($needupdate) {
$DB->update_record(GRADECFG_TABLE, $scalecfg);
}
}
}
else {
$scalecfg = (object)[ "scale_id" => $scale->id,];
$scalecfg = (object)[ "scale_id" => $scale->id, ];
$requireinsert = false;
foreach(["min_progress", "min_completed"] as $handle) {
foreach (["min_progress", "min_completed"] as $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]);
$requireinsert = true;
}
}
if($requireinsert){
// Insert into database and add to the list of scale configs
$id = $DB->insert_record(GRADECFG_TABLE,$scalecfg);
$scalecfg = $DB->get_record(GRADECFG_TABLE,['id' => $id]);
if ($requireinsert) {
// Insert into database and add to the list of scale configs.
$id = $DB->insert_record(GRADECFG_TABLE, $scalecfg);
$scalecfg = $DB->get_record(GRADECFG_TABLE, ['id' => $id]);
$scale_cfgs[$id] = $scalecfg;
}
}
}
// Now, loop through the gradepoints to parse
// Now, loop through the gradepoints to parse .
$deletelist = [];
foreach($grade_cfgs as $gradecfg){
foreach ($grade_cfgs as $gradecfg) {
$deletekey = "g_{$gradecfg->grade_points}_delete";
if(array_key_exists($deletekey,$_POST) && boolval($_POST[$deletekey]) === true){
$DB->delete_records(GRADECFG_TABLE,["id" => $gradecfg->id]);
if (array_key_exists($deletekey, $_POST) && boolval($_POST[$deletekey]) === true) {
$DB->delete_records(GRADECFG_TABLE, ["id" => $gradecfg->id]);
$deletelist[] = $gradecfg;
}
else
{
foreach(["min_progress", "min_completed"] as $handle) {
foreach (["min_progress", "min_completed"] as $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]);
}
}
$DB->update_record(GRADECFG_TABLE,$gradecfg);
// reload to ensure proper rounding is done
$grade_cfgs[$gradecfg->grade_points] = $DB->get_record(GRADECFG_TABLE,['id' => $gradecfg->id]);
$DB->update_record(GRADECFG_TABLE, $gradecfg);
// reload to ensure proper rounding is done.
$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($deletelist);
// 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"]) ){
// 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"]) ) {
$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];
$requireinsert = false;
foreach(["min_progress", "min_completed"] as $handle) {
foreach (["min_progress", "min_completed"] as $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]);
$requireinsert = true;
}
}
if($requireinsert){
// Insert into database and add to the list of grade configs
$id = $DB->insert_record(GRADECFG_TABLE,$gradecfg);
// reload to ensure proper rounding is done
$gradecfg = $DB->get_record(GRADECFG_TABLE,['id' => $id]);
if ($requireinsert) {
// Insert into database and add to the list of grade configs.
$id = $DB->insert_record(GRADECFG_TABLE, $gradecfg);
// reload to ensure proper rounding is done.
$gradecfg = $DB->get_record(GRADECFG_TABLE, ['id' => $id]);
$grade_cfgs[$id] = $gradecfg;
}
}
}
}
//process all available scales and load the current configuration for it.
$data = [];
foreach($scales as $scale)
{
foreach ($scales as $scale) {
$scale->load_items();
$scalecfg = null;
if(array_key_exists($scale->id,$scale_cfgs)){
if (array_key_exists($scale->id, $scale_cfgs)) {
$scalecfg = $scale_cfgs[$scale->id];
}
$attrs_c = ['value' => '','disabled' => 'disabled', ];
$attrs_p = ['value' => '','disabled' => 'disabled', ];
$attrs_c = ['value' => '', 'disabled' => 'disabled', ];
$attrs_p = ['value' => '', 'disabled' => 'disabled', ];
if(!isset($scalecfg) || $scalecfg->min_completed == ""){
if (!isset($scalecfg) || $scalecfg->min_completed == "") {
$attrs_c["selected"] = "selected";
}
if(!isset($scalecfg) || $scalecfg->min_progress == ""){
if (!isset($scalecfg) || $scalecfg->min_progress == "") {
$attrs_p["selected"] = "selected";
}
$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);
$key = 1; // Start counting by one, as used in sum aggregations
$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);
$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_p = ["value" => $key];
if(isset($scalecfg)){
if(intval($scalecfg->min_completed) == $key){
if (isset($scalecfg)) {
if (intval($scalecfg->min_completed) == $key) {
$attrs_c["selected"] = "selected";
}
if(intval($scalecfg->min_progress) == $key){
if (intval($scalecfg->min_progress) == $key) {
$attrs_p["selected"] = "selected";
}
}
$options_progress .= html_writer::tag("option",$value,$attrs_p);
$options_completed .= html_writer::tag("option",$value,$attrs_c);
$options_progress .= html_writer::tag("option", $value, $attrs_p);
$options_completed .= html_writer::tag("option", $value, $attrs_c);
$key++;
}
$row = [];
$row[] = $scale->name;
//$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_progress, ['name' => "s_{$scale->id}_min_progress", 'autocomplete' => 'off']) ;.
$row[] = html_writer::tag("select", $options_completed, ['name' => "s_{$scale->id}_min_completed", 'autocomplete' => 'off']) ;
$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']);
$table = new html_table();
$table->id = "";
$table->attributes['class'] = 'generaltable m-roomtable';
$table->tablealign = 'center';
$table->summary = '';//get_string('uploadtimetable_preview', 'local_chronotable');
$table->summary = '';//get_string('uploadtimetable_preview', 'local_chronotable');.
$table->head = [];
$table->data = $data;
$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');
print $OUTPUT->heading(get_string('cfg_grades_desc_head', '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 html_writer::tag('div', html_writer::table($table), ['class'=>'flexible-wrap']);
$data = [];
foreach($grade_cfgs as $g){
foreach ($grade_cfgs as $g) {
$row = [];
$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}_delete", 'type' => 'checkbox', ]) ;
$data[] = $row;
@ -205,8 +223,8 @@ foreach($grade_cfgs as $g){
$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_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_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']) ;
$data[] = $row;
@ -215,13 +233,13 @@ $table = new html_table();
$table->id = "";
$table->attributes['class'] = 'generaltable m-roomtable';
$table->tablealign = 'center';
$table->summary = '';//get_string('uploadtimetable_preview', 'local_chronotable');
$table->summary = '';//get_string('uploadtimetable_preview', 'local_chronotable');.
$table->head = [];
$table->data = $data;
$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('delete',);
$table->head[] = get_string('delete', );
print $OUTPUT->heading(get_string('cfg_grades_grades', 'local_treestudyplan'));

View File

@ -1,4 +1,25 @@
<?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;
require_once($CFG->libdir.'/externallib.php');
@ -7,29 +28,29 @@ abstract class aggregator {
private const FALLBACK = "bistate";
private static $mod_supported = [];
public static function supported($mod){
if(!array_key_exists($mod,self::$mod_supported)){
public static function supported($mod) {
if (!array_key_exists($mod, self::$mod_supported)) {
self::$mod_supported[$mod] = class_exists(self::aggregator_name($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";
}
public static function list(){
// static list, since we'd need to implement a lot of static data for new aggregation methods anyway
public static function list() {
// 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.
return [
"core", # use moodle core completion
"bistate",
"bistate",
"tristate", # deprecated
];
}
public static function create($mod,$configstr){
if(self::supported($mod)){
public static function create($mod, $configstr) {
if (self::supported($mod)) {
$ag_class = self::aggregator_name($mod);
return new $ag_class($configstr);
} else {
@ -37,15 +58,15 @@ abstract class aggregator {
}
}
public static function createOrDefault($mod,$configstr){
public static function createOrDefault($mod, $configstr) {
try {
return self::create($mod,$configstr);
return self::create($mod, $configstr);
}
catch(\ValueError $x){
return self::create(self::FALLBACK,"");
catch(\ValueError $x) {
return self::create(self::FALLBACK, "");
}
}
private function __construct($configstr) {
$this->initialize($configstr);
}
@ -59,60 +80,60 @@ abstract class aggregator {
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();
// Aggregation method makes use of
// Aggregation method makes use of .
public abstract function useItemConditions();
// Whether the aggregation method uses core_completion, or treestudyplan custom completion
public function usecorecompletioninfo(){
// Whether the aggregation method uses core_completion, or treestudyplan custom completion.
public function usecorecompletioninfo() {
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() {
return "";
}
public static function basic_structure($value=VALUE_REQUIRED){
public static function basic_structure($value=VALUE_REQUIRED) {
return new \external_single_structure([
"useRequiredGrades" => new \external_value(PARAM_BOOL, 'id of studyplan'),
"useItemConditions" => new \external_value(PARAM_BOOL, 'name of studyplan'),
],"Aggregator requirements",$value);
], "Aggregator requirements", $value);
}
public function basic_model(){
return [
public function basic_model() {
return [
"useRequiredGrades" => $this->useRequiredGrades(),
"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([
"id" => new \external_value(PARAM_TEXT, 'id of aggregator'),
"name" => new \external_value(PARAM_TEXT, 'name of agregator'),
"deprecated" => new \external_value(PARAM_BOOL, 'if method is deprecated'),
"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 = [];
foreach(self::list() as $agid){
$a = self::create($agid,""); // create new one with empty config string
foreach (self::list() as $agid) {
$a = self::create($agid, ""); // create new one with empty config string.
$list[] = [
'id' => $agid,
'name' => get_string("{$agid}_aggregator_title","local_treestudyplan"),
'name' => get_string("{$agid}_aggregator_title", "local_treestudyplan"),
'deprecated' => $a->isDeprecated(),
'defaultconfig' => $a->config_string(),
];
}
return $list;
}

View File

@ -1,16 +1,37 @@
<?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;
use local_treestudyplan\local\helpers\webservicehelper;
require_once($CFG->libdir.'/externallib.php');
class associationservice extends \external_api
class associationservice extends \external_api
{
const CAP_EDIT = "local/treestudyplan:editstudyplan";
const CAP_VIEW = "local/treestudyplan:viewuserreports";
public static function user_structure(){
public static function user_structure() {
return new \external_single_structure([
"id" => new \external_value(PARAM_INT, 'user id'),
"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 [
"id" => $r->id,
"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([
"id" => new \external_value(PARAM_INT, 'cohort id'),
"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;
$ctx = \context::instance_by_id($r->contextid);
$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);
}
$result = [
"id" => $r->id,
"name" => $r->name,
@ -64,59 +85,59 @@ class associationservice extends \external_api
"description" => $r->description,
"visible" => $r->visible,
"context" => [
"name" => $ctx->get_context_name(false,false),
"shortname" => $ctx->get_context_name(false,true),
"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),
"name" => $ctx->get_context_name(false, false),
"shortname" => $ctx->get_context_name(false, true),
"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),
]
];
];
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),
'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),
] );
}
public static function list_cohort_returns()
public static function list_cohort_returns()
{
return new \external_multiple_structure(self::cohort_structure());
}
// Actual functions
public static function list_cohort($like='',$exclude_id=null,$context_id=1)
// Actual functions.
public static function list_cohort($like='', $exclude_id=null, $context_id=1)
{
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);
webservicehelper::require_capabilities(self::CAP_EDIT, $context);
$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 .= " 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)";
$params['exclude_id'] = $exclude_id;
}
if($context_id > 1){ // system context returns all cohorts, including system cohorts
// otherwise,
if ($context_id > 1) { // system context returns all cohorts, including system cohorts.
// otherwise, .
$sql .= " AND contextid = :context_id";
$params['context_id'] = $context_id;
}
$cohorts = [];
$rs = $DB->get_recordset_sql($sql,$params);
$rs = $DB->get_recordset_sql($sql, $params);
foreach ($rs as $r) {
$cohorts[] = static::make_cohort_model($r);
}
@ -124,28 +145,28 @@ class associationservice extends \external_api
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'),
'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),
] );
}
public static function find_user_returns()
public static function find_user_returns()
{
return new \external_multiple_structure(self::user_structure());
}
// Actual functions
public static function find_user($like,$exclude_id=null,$context_id=1)
// Actual functions.
public static function find_user($like, $exclude_id=null, $context_id=1)
{
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);
webservicehelper::require_capabilities(self::CAP_EDIT,$context);
webservicehelper::require_capabilities(self::CAP_EDIT, $context);
$pattern = "%{$like}%";
$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 .= " 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)";
$params['exclude_id'] = $exclude_id;
}
}
$users = [];
$rs = $DB->get_recordset_sql($sql, $params);
@ -170,7 +191,7 @@ class associationservice extends \external_api
return $users;
}
public static function connect_cohort_parameters()
public static function connect_cohort_parameters()
{
return new \external_function_parameters( [
"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([
"success" => new \external_value(PARAM_BOOL, 'operation completed succesfully'),
@ -186,18 +207,17 @@ class associationservice extends \external_api
]);
}
// Actual functions
public static function connect_cohort($studyplan_id,$cohort_id)
// Actual functions.
public static function connect_cohort($studyplan_id, $cohort_id)
{
global $CFG, $DB;
$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', [
'studyplan_id' => $studyplan_id,
'studyplan_id' => $studyplan_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( [
"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([
"success" => new \external_value(PARAM_BOOL, 'operation completed succesfully'),
@ -226,21 +246,20 @@ class associationservice extends \external_api
]);
}
// Actual functions
public static function disconnect_cohort($studyplan_id,$cohort_id)
// Actual functions.
public static function disconnect_cohort($studyplan_id, $cohort_id)
{
global $CFG, $DB;
$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', [
'studyplan_id' => $studyplan_id,
'studyplan_id' => $studyplan_id,
'cohort_id' => $cohort_id,
]);
$studyplan->mark_csync_changed();
return ['success' => true, 'msg'=>'Cohort Disconnected'];
@ -248,9 +267,9 @@ class associationservice extends \external_api
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( [
"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([
"success" => new \external_value(PARAM_BOOL, 'operation completed succesfully'),
@ -266,18 +285,17 @@ class associationservice extends \external_api
]);
}
// Actual functions
public static function connect_user($studyplan_id,$user_id)
// Actual functions.
public static function connect_user($studyplan_id, $user_id)
{
global $CFG, $DB;
$studyplan = studyplan::findById($studyplan_id);
webservicehelper::require_capabilities(self::CAP_EDIT,$studyplan->context());
if(!$DB->record_exists('local_treestudyplan_user', ['studyplan_id' => $studyplan_id, 'user_id' => $user_id]))
{
webservicehelper::require_capabilities(self::CAP_EDIT, $studyplan->context());
if (!$DB->record_exists('local_treestudyplan_user', ['studyplan_id' => $studyplan_id, 'user_id' => $user_id])) {
$id = $DB->insert_record('local_treestudyplan_user', [
'studyplan_id' => $studyplan_id,
'studyplan_id' => $studyplan_id,
'user_id' => $user_id,
]);
$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( [
"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([
"success" => new \external_value(PARAM_BOOL, 'operation completed succesfully'),
@ -305,17 +323,16 @@ class associationservice extends \external_api
]);
}
// Actual functions
public static function disconnect_user($studyplan_id,$user_id)
// Actual functions.
public static function disconnect_user($studyplan_id, $user_id)
{
global $CFG, $DB;
$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', [
'studyplan_id' => $studyplan_id,
'studyplan_id' => $studyplan_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( [
"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());
}
// Actual functions
public static function associated_users($studyplan_id)
// Actual functions.
public static function associated_users($studyplan_id)
{
global $CFG, $DB;
$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 .= " WHERE j.studyplan_id = :studyplan_id";
$rs = $DB->get_recordset_sql($sql, ['studyplan_id' => $studyplan_id]);
$users = [];
foreach($rs as $u)
{
foreach ($rs as $u) {
$users[] = self::make_user_model($u);
}
$rs->close();
@ -360,31 +376,30 @@ class associationservice extends \external_api
return $users;
}
public static function associated_cohorts_parameters()
public static function associated_cohorts_parameters()
{
return new \external_function_parameters( [
"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());
}
// Actual functions
public static function associated_cohorts($studyplan_id)
// Actual functions.
public static function associated_cohorts($studyplan_id)
{
global $CFG, $DB;
$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 .= " WHERE j.studyplan_id = :studyplan_id";
$rs = $DB->get_recordset_sql($sql, ['studyplan_id' => $studyplan_id]);
$cohorts = [];
foreach($rs as $c)
{
foreach ($rs as $c) {
$cohorts[] = self::make_cohort_model($c);
}
$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( [
"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());
}
// Actual functions
public static function all_associated($studyplan_id)
// Actual functions.
public static function all_associated($studyplan_id)
{
global $CFG, $DB;
$studyplan = studyplan::findById($studyplan_id);
webservicehelper::require_capabilities(self::CAP_VIEW,$studyplan->context());
webservicehelper::require_capabilities(self::CAP_VIEW, $studyplan->context());
$users = [];
// SQL JOIN script selecting all users that have a cohort linked to this studyplan
// or are directly linked
// SQL JOIN script selecting all users that have a cohort linked to this studyplan .
// or are directly linked.
$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 {local_treestudyplan_cohort} tc ON cm.cohortid = tc.cohort_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}
ORDER BY u.lastname, u.firstname";
$rs = $DB->get_recordset_sql($sql);
foreach($rs as $u)
{
foreach ($rs as $u) {
$users[] = self::make_user_model($u);
}
$rs->close();
@ -436,15 +450,15 @@ class associationservice extends \external_api
return $users;
}
public static function sortusermodels(&$list){
return usort($list,function($a,$b){
public static function sortusermodels(&$list) {
return usort($list, function($a, $b) {
$m= [];
if(preg_match("/.*?([A-Z].*)/",$a['lastname'],$m)){
if (preg_match("/.*?([A-Z].*)/", $a['lastname'], $m)) {
$sort_ln_a = $m[1];
} else {
$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];
} else {
$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( [
"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();
}
// Actual functions
public static function cascade_cohortsync($studyplan_id)
// Actual functions.
public static function cascade_cohortsync($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->sync();
if(get_config("local_treestudyplan","csync_users")){
if (get_config("local_treestudyplan", "csync_users")) {
$userenroller = new cascadeusersync($studyplan);
$userenroller->sync();
}
$studyplan->clear_csync_changed(); // Clear the csync required flag
$studyplan->clear_csync_changed(); // Clear the csync required flag.
return success::success()->model();

View File

@ -1,9 +1,30 @@
<?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;
require_once($CFG->libdir.'/externallib.php');
class badgeinfo {
private $badge; // Holds database record
private $badge; // Holds database record.
private const STATUSINFO = [
BADGE_STATUS_INACTIVE => 'inactive',
@ -17,7 +38,7 @@ class badgeinfo {
BADGE_STATUS_ACTIVE => 0,
BADGE_STATUS_INACTIVE_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) {
@ -25,43 +46,42 @@ class badgeinfo {
$this->badge = $badge;
}
public function name(){
public function name() {
return $this->badge->name;
}
public static function id_from_name($name){
public static function id_from_name($name) {
global $DB;
return $DB->get_field("badge", "id", ['name' => $name]);
}
public static function exists($id){
public static function exists($id) {
global $DB;
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([
"id" => new \external_value(PARAM_INT, 'id of badge'),
"infolink" => new \external_value(PARAM_TEXT, 'badge issue information link', VALUE_OPTIONAL),
"name" => new \external_value(PARAM_TEXT, 'badge name'),
"status" => new \external_value(PARAM_TEXT, 'badge 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'),
"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),
"issuedcount" => new \external_value(PARAM_INT, 'number of studyplan students that have got this badge',VALUE_OPTIONAL),
],"Badge info",$value);
"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),
], "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);
// If the user is viewing another user's badge and doesn't have the right capability return only part of the data.
$criteria = [];
foreach($this->badge->get_criteria() as $bc){
foreach ($this->badge->get_criteria() as $bc) {
$criteria[] = $bc->get_title()." ".$bc->get_details();
}
$model = [
@ -72,11 +92,11 @@ class badgeinfo {
'locked' => self::LOCKEDINFO[$this->badge->status],
'criteria' => $criteria,
'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
if(!empty($studentlist) && is_array($studentlist)){
// Add badge issue stats if a studentlist is attached to the request.
if (!empty($studentlist) && is_array($studentlist)) {
$model['studentcount'] = count($studentlist);
$model['issuedcount'] = $this->count_issued($studentlist);
}
@ -84,25 +104,23 @@ class badgeinfo {
return $model;
}
public static function user_structure($value=VALUE_REQUIRED)
{
public static function user_structure($value=VALUE_REQUIRED) {
return new \external_single_structure([
"id" => new \external_value(PARAM_INT, 'id of badge'),
"infolink" => new \external_value(PARAM_TEXT, 'badge issue information link', VALUE_OPTIONAL),
"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'),
"imageurl" => new \external_value(PARAM_TEXT, 'url of badge image'),
"issued" => new \external_value(PARAM_BOOL, 'badge is issued'),
"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),
"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),
"uniquehash" => new \external_value(PARAM_TEXT, 'badge issue hash', 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;
$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.
$criteria = [];
foreach($this->badge->get_criteria() as $bc){
foreach ($this->badge->get_criteria() as $bc) {
$criteria[] = $bc->get_title()."".$bc->get_details();
}
$badge = [
'id' => $this->badge->id,
'name' => $this->badge->name,
'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,
'issued' => $issued,
'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));
$badge['dateissued'] = date("Y-m-d",$issueinfo->dateissued);
if($issueinfo->expiredate){
$badge['dateexpire'] = date("Y-m-d",$issueinfo->dateexpire);
$badge['dateissued'] = date("Y-m-d", $issueinfo->dateissued);
if ($issueinfo->expiredate) {
$badge['dateexpire'] = date("Y-m-d", $issueinfo->dateexpire);
}
$badge['uniquehash'] = $issueinfo->uniquehash;
$badge['issuedlink'] = (new \moodle_url('/badges/badge.php', ['hash' => $issueinfo->uniquehash]))->out(false);
@ -136,11 +154,11 @@ class badgeinfo {
return $badge;
}
function count_issued(array $student_ids){
function count_issued(array $student_ids) {
$issuecount = 0;
foreach($student_ids as $userid){
if($this->badge->is_issued($userid)){
foreach ($student_ids as $userid) {
if ($this->badge->is_issued($userid)) {
$issuecount++;
}
}

View File

@ -1,4 +1,25 @@
<?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;
require_once($CFG->libdir.'/externallib.php');
@ -9,27 +30,27 @@ class cascadecohortsync {
private $studyplan;
private $studyplanid;
function __construct(studyplan $studyplan){
function __construct(studyplan $studyplan) {
$this->studyplan = $studyplan;
$this->studyplanid = $studyplan->id();
}
static private function array_remove_value($array,$value){
static private function array_remove_value($array, $value) {
$a = [];
foreach($array as $v){
if($v != $value){
foreach ($array as $v) {
if ($v != $value) {
$a[] = $v;
}
}
return $a;
}
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;
require_once($CFG->dirroot.'/group/lib.php');
// Check to see if the group name already exists in this course.
if ($DB->record_exists('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->name = $groupname;
$groupid = groups_create_group($groupdata);
return $groupid;
}
function sync(){
function sync() {
global $DB;
/* Explainer:
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.
(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.)
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.
*/
$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();
// And find the cohorts that are linked to this studyplan.
$cohortids = $this->studyplan->get_linked_cohort_ids();
// Next, for each course that is linked:
foreach($courseids as $courseid){
// Next, for each course that is linked:.
foreach ($courseids as $courseid) {
$course = \get_course($courseid);
//\mtrace("Processing Course {$courseid} {$course->shortname}");
// first create any nonexistent links
foreach($cohortids as $cohortid){
$cohort = $DB->get_record('cohort',['id'=>$cohortid]);
//\mtrace("Processing cohort {$cohortid} {$cohort->shortname}");
//\mtrace("Processing Course {$courseid} {$course->shortname}");.
// first create any nonexistent links.
foreach ($cohortids as $cohortid) {
$cohort = $DB->get_record('cohort', ['id'=>$cohortid]);
//\mtrace("Processing cohort {$cohortid} {$cohort->shortname}");.
$instanceparams = [
'courseid' => $courseid,
'customint1' => $cohortid,
'enrol' => self::METHOD,
'roleid' => get_config("local_treestudyplan","csync_roleid"),
'roleid' => get_config("local_treestudyplan", "csync_roleid"),
];
$instancenewparams = [
'customint1' => $cohortid,
'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
// If not, make it (maybe use some of the custom text to list the studyplans involved)
// 1: check if a link exists.
// If not, make it (maybe use some of the custom text to list the studyplans involved).
if ($instance = $DB->get_record('enrol', $instanceparams)) {
//\mtrace("Instance exists");
// it already exists
// check if this studyplan is already referenced in customtext4 in json format
//\mtrace("Instance exists");.
// it already exists.
// 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);
if($plans == false || !is_array(($plans))){
// If the data was not an array (null or garbled), count it as manually added
// This will prevent it's deletion upon
if(get_config("local_treestudyplan","csync_remember_manual_csync")){
if ($plans == false || !is_array(($plans))) {
// If the data was not an array (null or garbled), count it as manually added.
// This will prevent it's deletion upon.
if (get_config("local_treestudyplan", "csync_remember_manual_csync")) {
$plans = ["manual"];
} else {
$plans = [];
}
}
if(!in_array($this->studyplanid ,$plans)){
// if not, add it to the reference
//\mtrace("Adding this plan to the list");
if (!in_array($this->studyplanid , $plans)) {
// if not, add it to the reference.
//\mtrace("Adding this plan to the list");.
$plans[] = (int)($this->studyplanid);
$enrol->update_instance($instance,(object)["customtext4"=>json_encode($plans)]);
$enrol->update_instance($instance, (object)["customtext4"=>json_encode($plans)]);
}
} 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 (get_config("local_treestudyplan","csync_creategroup")) {
// Make or get new new cohort group - but only on creating of instances
if (get_config("local_treestudyplan", "csync_creategroup")) {
// Make or get new new cohort group - but only on creating of instances.
$groupname = $cohort->name." ".strtolower(\get_string('defaultgroupname', 'core_group'));
//\mtrace("Adding group {$groupname} for this method");
// and make sure the
//\mtrace("Adding group {$groupname} for this method");.
// and make sure the .
$instancenewparams['customint2'] = self::uploadenrolmentmethods_get_group($courseid, $groupname);
}
if ($instanceid = $enrol->add_instance($course, $instancenewparams)) {
// also record the (as of yet only) studyplans id requiring this association
// in the customtext4 field in json format
//\mtrace("Instance ({$instanceid} created. Updateing instance with studyplan id");
// also record the (as of yet only) studyplans id requiring this association.
// in the customtext4 field in json format.
//\mtrace("Instance ({$instanceid} created. Updateing instance with studyplan id");.
$instance = $DB->get_record('enrol', array('id' => $instanceid));
$enrol->update_instance($instance,(object)["customtext4"=>json_encode([(int)($this->studyplanid)])]);
//\mtrace("Synchronize the enrolment");
$enrol->update_instance($instance, (object)["customtext4"=>json_encode([(int)($this->studyplanid)])]);
//\mtrace("Synchronize the enrolment");.
// Successfully added a valid new instance, so now instantiate it.
// First synchronise the enrolment.
$cohorttrace = new \null_progress_trace();
$result = enrol_cohort_sync($cohorttrace, $cohortid);
if($result > 0){
//\mtrace("Error during 'enrol_cohort_sync': code {$result}");
if ($result > 0) {
//\mtrace("Error during 'enrol_cohort_sync': code {$result}");.
}
$cohorttrace->finished();
} else {
// Instance not added for some reason, so report an error somewhere
// (or not)
//\mtrace("Error - instance not added for some reason");
// Instance not added for some reason, so report an error somewhere.
// (or not).
//\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
// A: Check if there are cohort links that are no longer related to this studyplan
// 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.
// B: Check if these links are valid through another studyplan...
// 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
// since it is generally a good idea to keep student access to courses available
// 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 .
if(get_config("local_treestudyplan","csync_autoremove")){
// Only try the autoremove if the option is enabled
//\mtrace("Autoremove scan for course {$courseid} {$course->shortname}");
// find all cohort syncs for this course
if (get_config("local_treestudyplan", "csync_autoremove")) {
// Only try the autoremove if the option is enabled.
//\mtrace("Autoremove scan for course {$courseid} {$course->shortname}");.
// find all cohort syncs for this course.
$searchparams = [
'courseid' => $courseid,
'enrol' => self::METHOD,
'roleid' => get_config("local_treestudyplan","csync_roleid"),
'roleid' => get_config("local_treestudyplan", "csync_roleid"),
];
$records = $DB->get_records("enrol",$searchparams);
foreach($records as $instance){
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
if(!in_array($instance->customint1,$cohortids)){
//\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
$records = $DB->get_records("enrol", $searchparams);
foreach ($records as $instance) {
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.
if (!in_array($instance->customint1, $cohortids)) {
//\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.
$plans = json_decode($instance->customtext4);
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
// otherwise, check if we should remove it
if(in_array($this->studyplanid,$plans)){
//\mtrace("Found this studyplan in the id list - removing from list");
//if this plan was referenced before
// first remove the link
$fplans = self::array_remove_value($plans,$this->studyplanid);
if(count($fplans) == 0){
// delete the sync if there are no studyplan references left
//\mtrace("No references are left, removing instance");
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.
// otherwise, check if we should remove it.
if (in_array($this->studyplanid, $plans)) {
//\mtrace("Found this studyplan in the id list - removing from list");.
//if this plan was referenced before.
// first remove the link.
$fplans = self::array_remove_value($plans, $this->studyplanid);
if (count($fplans) == 0) {
// delete the sync if there are no studyplan references left.
//\mtrace("No references are left, removing instance");.
$enrol->delete_instance($instance);
} else {
// otherwise just update the references so this studyplan is no longer linked
//\mtrace("Still references left in the list, updating list...");
$enrol->update_instance($instance,(object)["customtext4"=>json_encode($fplans)]);
// otherwise just update the references so this studyplan is no longer linked.
//\mtrace("Still references left in the list, updating list...");.
$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
// 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;
require_once($CFG->libdir.'/externallib.php');
@ -8,57 +29,57 @@ class cascadeusersync {
private const METHOD = "manual";
private $studyplan;
function __construct(studyplan $studyplan){
function __construct(studyplan $studyplan) {
$this->studyplan = $studyplan;
}
function sync(){
function sync() {
global $DB;
/* Explainer:
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.
(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.)
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.
*/
$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();
// And find the users that are linked to this studyplan.
$userids = $this->studyplan->get_linked_user_ids();
// Get the roleid to use for synchronizations
$roleid = get_config("local_treestudyplan","csync_roleid");
// Get the roleid to use for synchronizations.
$roleid = get_config("local_treestudyplan", "csync_roleid");
// Next, for each course that is linked:
foreach($courseids as $courseid){
// Next, for each course that is linked:.
foreach ($courseids as $courseid) {
$course = \get_course($courseid);
if(count($userids) > 0){
// Get the manual enrol instance for this course
if (count($userids) > 0) {
// Get the manual enrol instance for this course.
$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)) {
$instance = $DB->get_record('enrol', array('id' => $instanceid));
}
else {
// Instance not added for some reason, so report an error somewhere
// (or not)
$instance = null;
// Instance not added for some reason, so report an error somewhere.
// (or not).
$instance = null;
}
}
if($instance !== null){
foreach($userids as $uid){
if ($instance !== null) {
foreach ($userids as $uid) {
// 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
// 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;
require_once($CFG->libdir.'/externallib.php');
@ -23,29 +43,29 @@ class completion {
];
public static function label($completion) {
if(array_key_exists($completion,self::LABELS)){
if (array_key_exists($completion, self::LABELS)) {
return self::LABELS[$completion];
}
else
else
{
return self::LABELS[self::INCOMPLETE];
}
}
public static function structure($value=VALUE_REQUIRED){
return new \external_value(PARAM_TEXT, 'completion state (failed|incomplete|pending|progress|completed|good|excellent)',$value);
public static function structure($value=VALUE_REQUIRED) {
return new \external_value(PARAM_TEXT, 'completion state (failed|incomplete|pending|progress|completed|good|excellent)', $value);
}
public static function count_states(array $states){
// initialize result array
public static function count_states(array $states) {
// initialize result array.
$statecount = [];
foreach(array_keys(self::LABELS) as $key) {
foreach (array_keys(self::LABELS) as $key) {
$statecount[$key] = 0;
}
// process all states in array and increment relevant counter for each one
foreach($states as $c){
if(array_key_exists($c,$statecount)){
// process all states in array and increment relevant counter for each one.
foreach ($states as $c) {
if (array_key_exists($c, $statecount)) {
$statecount[$c] += 1;
}
}

View File

@ -1,11 +1,31 @@
<?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;
require_once($CFG->libdir.'/externallib.php');
use \grade_item;
class completionscanner
class completionscanner
{
private static $mod_supported = [];
private static $course_students = [];
@ -15,19 +35,19 @@ class completionscanner
private $gi = null;
private $pending_cache = [];
public static function supported($mod){
if(!array_key_exists($mod,self::$mod_supported)){
public static function supported($mod) {
if (!array_key_exists($mod, self::$mod_supported)) {
self::$mod_supported[$mod] = class_exists("\local_treestudyplan\\local\\ungradedscanners\\{$mod}_scanner");
}
return self::$mod_supported[$mod];
}
public static function get_course_students($courseid){
public static function get_course_students($courseid) {
global $CFG;
if(!array_key_exists($courseid,self::$course_students)){
if (!array_key_exists($courseid, self::$course_students)) {
$students = [];
$context = \context_course::instance($courseid);
foreach (explode(',', $CFG->gradebookroles) as $roleid) {
foreach (explode(', ', $CFG->gradebookroles) as $roleid) {
$roleid = trim($roleid);
$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];
}
public function __construct(\completion_criteria $crit,$course){
public function __construct(\completion_criteria $crit, $course) {
$this->courseid = $course->id;
$this->course = $course;
$this->modinfo = get_fast_modinfo($course);
$this->crit = $crit;
$this->completioninfo = new \completion_info($course);
// Find a related scanner if the type is an activity type
if($crit->criteriatype == COMPLETION_CRITERIA_TYPE_ACTIVITY){
// First find the course module
// Find a related scanner if the type is an activity type.
if ($crit->criteriatype == COMPLETION_CRITERIA_TYPE_ACTIVITY) {
// First find the course module.
$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]);
if($gi !== false)
{
// 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
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(self::supported($gi->itemmodule)) {
if ($gi !== false) {
// 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.
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 (self::supported($gi->itemmodule)) {
$scannerclass = "\local_treestudyplan\\local\ungradedscanners\\{$gi->itemmodule}_scanner";
$this->scanner = new $scannerclass($gi);
}
@ -67,57 +85,57 @@ class completionscanner
}
public function pending($userid){
if(!array_key_exists($userid, $this->pending_cache)){
if($this->scanner === null) {
public function pending($userid) {
if (!array_key_exists($userid, $this->pending_cache)) {
if ($this->scanner === null) {
$this->pending_cache[$userid] = false;
}
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];
}
}
public static function structure($value=VALUE_OPTIONAL){
public static function structure($value=VALUE_OPTIONAL) {
return new \external_single_structure([
"ungraded" => new \external_value(PARAM_INT, 'number of ungraded submissions'),
"completed" => new \external_value(PARAM_INT, 'number of completed students'),
"completed_pass" => new \external_value(PARAM_INT, 'number of completed-pass 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'),
],"details about gradable submissions",$value);
], "details about gradable submissions", $value);
}
public function model(){
// get completion info
public function model() {
// get completion info.
$students = self::get_course_students($this->courseid);
$completed = 0;
$ungraded = 0;
$completed_pass = 0;
$completed_fail = 0;
foreach($students as $userid){
if($this->pending($userid)){
// First check if the completion needs grading
foreach ($students as $userid) {
if ($this->pending($userid)) {
// First check if the completion needs grading.
$ungraded++;
} else {
$completion = $this->completioninfo->get_user_completion($userid,$this->crit);
if($this->crit->criteriatype == COMPLETION_CRITERIA_TYPE_ACTIVITY){
// If it's an activity completion, add all the relevant activities as sub-items
$completion_status = $this->completioninfo->get_grade_completion($this->cm,$userid);
if($completion_status == COMPLETION_COMPLETE_PASS){
$completion = $this->completioninfo->get_user_completion($userid, $this->crit);
if ($this->crit->criteriatype == COMPLETION_CRITERIA_TYPE_ACTIVITY) {
// If it's an activity completion, add all the relevant activities as sub-items.
$completion_status = $this->completioninfo->get_grade_completion($this->cm, $userid);
if ($completion_status == COMPLETION_COMPLETE_PASS) {
$completed_pass++;
} else if ($completion_status == COMPLETION_COMPLETE_FAIL){
} else if ($completion_status == COMPLETION_COMPLETE_FAIL) {
$completed_fail++;
} else if ($completion_status == COMPLETION_COMPLETE){
} else if ($completion_status == COMPLETION_COMPLETE) {
$completed++;
}
}
else{
if($completion->is_complete()){
if ($completion->is_complete()) {
$completed++;
}
}

View File

@ -1,15 +1,35 @@
<?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;
class contextinfo {
public $context;
public function __construct($context){
public function __construct($context) {
$this->context = $context;
}
public static function structure($value=VALUE_REQUIRED){
public static function structure($value=VALUE_REQUIRED) {
return new \external_single_structure([
"name" => new \external_value(PARAM_TEXT, 'context name'),
"shortname" => new \external_value(PARAM_TEXT, 'context short name'),
@ -19,16 +39,16 @@ class contextinfo {
}
public function model() {
$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);
}
return [
"name" => $this->context->get_context_name(false,false),
"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),
"shortpath" => array_map(function($c){ return \context::instance_by_id($c)->get_context_name(false,true);},$ctxPath),
"name" => $this->context->get_context_name(false, false),
"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),
"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 {
if($contextid <= 1){
if ($contextid <= 1) {
$contextid = 1;
}
return \context::instance_by_id($contextid);

View File

@ -1,4 +1,24 @@
<?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;
require_once($CFG->libdir.'/externallib.php');
@ -17,142 +37,141 @@ class corecompletioninfo {
private $modinfo;
private static $COMPLETION_HANDLES = null;
public function id(){
public function id() {
return $this->course->id;
}
public function __construct($course){
public function __construct($course) {
global $DB;
$this->course = $course;
$this->completion = new \completion_info($this->course);
$this->modinfo = get_fast_modinfo($this->course);
}
static public function completiontypes(){
global $COMPLETION_CRITERIA_TYPES;
// Just return the keys of the global array COMPLETION_CRITERIA_TYPES, so we don't have to manually
static public function completiontypes() {
global $COMPLETION_CRITERIA_TYPES;
// Just return the keys of the global array COMPLETION_CRITERIA_TYPES, so we don't have to manually.
// add any completion 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
*/
static public function completion_handle($completion){
if(empty(self::$COMPLETION_HANDLES)){
// Cache the translation table, to avoid overhead
static public function completion_handle($completion) {
if (empty(self::$COMPLETION_HANDLES)) {
// Cache the translation table, to avoid overhead.
self::$COMPLETION_HANDLES = [
COMPLETION_INCOMPLETE => "incomplete",
COMPLETION_COMPLETE => "complete",
COMPLETION_COMPLETE => "complete",
COMPLETION_COMPLETE_PASS => "complete-pass",
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";
}
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([
"id" => new \external_value(PARAM_INT,'criteria id',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),
"id" => new \external_value(PARAM_INT, 'criteria id', 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),
"details" => new \external_single_structure([
"type" => new \external_value(PARAM_RAW, 'type',VALUE_OPTIONAL),
"criteria" => new \external_value(PARAM_RAW, 'criteria',VALUE_OPTIONAL),
"requirement" => new \external_value(PARAM_RAW, 'requirement',VALUE_OPTIONAL),
"status" => new \external_value(PARAM_RAW, 'status',VALUE_OPTIONAL),
"type" => new \external_value(PARAM_RAW, 'type', VALUE_OPTIONAL),
"criteria" => new \external_value(PARAM_RAW, 'criteria', VALUE_OPTIONAL),
"requirement" => new \external_value(PARAM_RAW, 'requirement', VALUE_OPTIONAL),
"status" => new \external_value(PARAM_RAW, 'status', VALUE_OPTIONAL),
]),
"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([
"items" => new \external_multiple_structure(self::completion_item_editor_structure(),'subitems',VALUE_OPTIONAL),
"title" => new \external_value(PARAM_TEXT,'optional title',VALUE_OPTIONAL),
"desc" => new \external_value(PARAM_TEXT, 'optional description',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),
"desc" => new \external_value(PARAM_TEXT, 'optional description', VALUE_OPTIONAL),
"type" => new \external_value(PARAM_TEXT, 'completion type name'),
"aggregation" => new \external_value(PARAM_TEXT, 'completion aggregation for this type ["all","any"]'),
], 'completion type',$value);
"aggregation" => new \external_value(PARAM_TEXT, 'completion aggregation for this type ["all", "any"]'),
], 'completion type', $value);
}
public static function editor_structure($value=VALUE_REQUIRED){
public static function editor_structure($value=VALUE_REQUIRED) {
return new \external_single_structure([
"conditions" => new \external_multiple_structure(self::completion_type_editor_structure(),'completion conditions'),
"aggregation" => new \external_value(PARAM_TEXT, 'completion aggregation ["all","any"]'),
"enabled" => new \external_value(PARAM_BOOL,"whether completion is enabled here"),
], 'course completion info',$value);
"conditions" => new \external_multiple_structure(self::completion_type_editor_structure(), 'completion conditions'),
"aggregation" => new \external_value(PARAM_TEXT, 'completion aggregation ["all", "any"]'),
"enabled" => new \external_value(PARAM_BOOL, "whether completion is enabled here"),
], '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([
"id" => new \external_value(PARAM_INT,'id of completion',VALUE_OPTIONAL),
"title" => new \external_value(PARAM_TEXT,'name of subitem',VALUE_OPTIONAL),
"id" => new \external_value(PARAM_INT, 'id of completion', VALUE_OPTIONAL),
"title" => new \external_value(PARAM_TEXT, 'name of subitem', VALUE_OPTIONAL),
"details" => new \external_single_structure([
"type" => new \external_value(PARAM_RAW, 'type',VALUE_OPTIONAL),
"criteria" => new \external_value(PARAM_RAW, 'criteria',VALUE_OPTIONAL),
"requirement" => new \external_value(PARAM_RAW, 'requirement',VALUE_OPTIONAL),
"status" => new \external_value(PARAM_RAW, 'status',VALUE_OPTIONAL),
"type" => new \external_value(PARAM_RAW, 'type', VALUE_OPTIONAL),
"criteria" => new \external_value(PARAM_RAW, 'criteria', VALUE_OPTIONAL),
"requirement" => new \external_value(PARAM_RAW, 'requirement', 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'),
"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),
"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),
], 'completion type',$value);
"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),
"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),
], '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([
"items" => new \external_multiple_structure(self::completion_item_user_structure(),'subitems',VALUE_OPTIONAL),
"title" => new \external_value(PARAM_TEXT,'optional title',VALUE_OPTIONAL),
"desc" => new \external_value(PARAM_TEXT, 'optional description',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),
"desc" => new \external_value(PARAM_TEXT, 'optional description', VALUE_OPTIONAL),
"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'),
"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'),
"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([
"progress" => new \external_value(PARAM_INT, 'completed sub-conditions'),
"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),
"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),
"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'),
"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),
], 'course completion info',$value);
"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),
], 'course completion info', $value);
}
private static function aggregation_handle($method){
private static function aggregation_handle($method) {
return ($method==COMPLETION_AGGREGATION_ALL)?"all":"any";
}
public function editor_model() {
global $DB, $CFG, $COMPLETION_CRITERIA_TYPES;
$conditions = [];
$aggregation = "all"; // default
$aggregation = "all"; // default.
$info = [
"conditions" => $conditions,
"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
if($this->completion->is_enabled())
{
// Check if completion tracking is enabled for this course - otherwise, revert to defaults .
if ($this->completion->is_enabled()) {
$aggregation = $this->completion->get_aggregation_method();
// Loop through all condition types to see if they are applicable
foreach(self::completiontypes() as $type){
$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
// Loop through all condition types to see if they are applicable.
foreach (self::completiontypes() as $type) {
$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.
{
$cinfo = [
"type" => $COMPLETION_CRITERIA_TYPES[$type],
@ -161,13 +180,13 @@ class corecompletioninfo {
"items" => [],
];
foreach($criterias as $criteria){
// 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
// See moodle/completion/criteria/completion_criteria_*.php::get_details() for the code that is
// in the code below is based on
if($type == COMPLETION_CRITERIA_TYPE_SELF){
foreach ($criterias as $criteria) {
// 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.
// See moodle/completion/criteria/completion_criteria_*.php::get_details() for the code that is.
// in the code below is based on.
if ($type == COMPLETION_CRITERIA_TYPE_SELF) {
$details = [
"type" => $criteria->get_title(),
"criteria" => $criteria->get_title(),
@ -175,15 +194,15 @@ class corecompletioninfo {
"status" => "",
];
}
else if ($type == COMPLETION_CRITERIA_TYPE_DATE){
else if ($type == COMPLETION_CRITERIA_TYPE_DATE) {
$details = [
"type" => get_string('datepassed', 'completion'),
"criteria" => get_string('remainingenroleduntildate', 'completion'),
"requirement" => date("Y-m-d",$criteria->timeend),
"requirement" => date("Y-m-d", $criteria->timeend),
"status" => "",
];
}
else if ($type == COMPLETION_CRITERIA_TYPE_UNENROL){
else if ($type == COMPLETION_CRITERIA_TYPE_UNENROL) {
$details = [
"type" => get_string('unenrolment', 'completion'),
"criteria" => get_string('unenrolment', 'completion'),
@ -191,12 +210,12 @@ class corecompletioninfo {
"status" => "",
];
}
else if ($type == COMPLETION_CRITERIA_TYPE_ACTIVITY){
else if ($type == COMPLETION_CRITERIA_TYPE_ACTIVITY) {
$cm = $this->modinfo->get_cm($criteria->moduleinstance);
$details = [
"type" => $criteria->get_title(),
"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
"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.
"status" => "",
];
if ($cm->has_view()) {
@ -204,12 +223,12 @@ class corecompletioninfo {
} else {
$details['criteria'] = $cm->get_formatted_name();
}
// Build requirements
// Build requirements.
$details['requirement'] = array();
if ($cm->completion == COMPLETION_TRACKING_MANUAL) {
$details['requirement'][] = get_string('markingyourselfcomplete', 'completion');
} elseif ($cm->completion == COMPLETION_TRACKING_AUTOMATIC) {
} else if ($cm->completion == COMPLETION_TRACKING_AUTOMATIC) {
if ($cm->completionview) {
$modulename = \core_text::strtolower(get_string('modulename', $criteria->module));
$details['requirement'][] = get_string('viewingactivity', 'completion', $modulename);
@ -227,7 +246,7 @@ class corecompletioninfo {
$details['requirement'] = implode(', ', $details['requirement']);
}
else if ($type == COMPLETION_CRITERIA_TYPE_DURATION){
else if ($type == COMPLETION_CRITERIA_TYPE_DURATION) {
$details = [
"type" => get_string('periodpostenrolment', 'completion'),
"criteria" => get_string('remainingenroledfortime', 'completion'),
@ -235,18 +254,18 @@ class corecompletioninfo {
"status" => "",
];
}
else if ($type == COMPLETION_CRITERIA_TYPE_GRADE){
else if ($type == COMPLETION_CRITERIA_TYPE_GRADE) {
$details = [
"type" => get_string('coursegrade', '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),
"status" => "",
];
}
else if ($type == COMPLETION_CRITERIA_TYPE_ROLE){
else if ($type == COMPLETION_CRITERIA_TYPE_ROLE) {
$criteria = $criteria->get_title();
$details = [
"type" => get_string('manualcompletionby', 'completion'),
"criteria" => $criteria,
@ -254,7 +273,7 @@ class corecompletioninfo {
"status" => "",
];
}
else if ($type == COMPLETION_CRITERIA_TYPE_COURSE){
else if ($type == COMPLETION_CRITERIA_TYPE_COURSE) {
$prereq = get_course($criteria->courseinstance);
$coursecontext = \context_course::instance($prereq->id, MUST_EXIST);
$fullname = format_string($prereq->fullname, true, array('context' => $coursecontext));
@ -265,7 +284,7 @@ class corecompletioninfo {
"status" => "",
];
} else {
// Moodle added a criteria type
// Moodle added a criteria type.
$details = [
"type" => "",
"criteria" => "",
@ -273,9 +292,9 @@ class corecompletioninfo {
"status" => "",
];
}
$scanner = new completionscanner($criteria,$this->course);
$scanner = new completionscanner($criteria, $this->course);
// only add the items list if we actually have items...
$cinfo["items"][] = [
"id" => $criteria->id,
@ -296,18 +315,18 @@ class corecompletioninfo {
return $info;
}
private function aggregate_completions($typeaggregation,$completions){
private function aggregate_completions($typeaggregation, $completions) {
$completed = 0;
$count = count($completions);
foreach($completions as $c){
if($c->is_complete()){
foreach ($completions as $c) {
if ($c->is_complete()) {
$completed++;
}
}
if($typeaggregation == COMPLETION_AGGREGATION_ALL){
if ($typeaggregation == COMPLETION_AGGREGATION_ALL) {
return $completed >= $count;
}
else { // COMPLETION_AGGREGATION_ANY
}
else { // COMPLETION_AGGREGATION_ANY.
return $completed > 1;
}
@ -323,21 +342,20 @@ class corecompletioninfo {
"conditions" => [],
"completed" => $this->completion->is_course_complete($userid),
"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),
];
// Check if completion tracking is enabled for this course - otherwise, revert to defaults
if($this->completion->is_enabled() && $this->completion->is_tracked_user($userid))
{
// Check if completion tracking is enabled for this course - otherwise, revert to defaults .
if ($this->completion->is_enabled() && $this->completion->is_tracked_user($userid)) {
$anypending = false;
// Loop through all conditions to see if they are applicable
foreach(self::completiontypes() as $type){
// Get the main completion for this type
$completions = $this->completion->get_completions($userid,$type);
if(count($completions) > 0){
// Loop through all conditions to see if they are applicable.
foreach (self::completiontypes() as $type) {
// Get the main completion for this type.
$completions = $this->completion->get_completions($userid, $type);
if (count($completions) > 0) {
$typeaggregation = $this->completion->get_aggregation_method($type);
$completed = $this->aggregate_completions($typeaggregation,$completions);
$completed = $this->aggregate_completions($typeaggregation, $completions);
$cinfo = [
"type" => $COMPLETION_CRITERIA_TYPES[$type],
"aggregation" => self::aggregation_handle($typeaggregation),
@ -348,67 +366,67 @@ class corecompletioninfo {
];
$progress = 0;
foreach($completions as $completion){
foreach ($completions as $completion) {
$criteria = $completion->get_criteria();
if($completion->is_complete()) {
$progress += 1; // Add a point to the progress counter
}
if ($completion->is_complete()) {
$progress += 1; // Add a point to the progress counter.
}
$iinfo = [
"id" => $criteria->id,
"title" => $criteria->get_title_detailed(),
"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),
];
if($type == COMPLETION_CRITERIA_TYPE_ACTIVITY){
if ($type == COMPLETION_CRITERIA_TYPE_ACTIVITY) {
$cm = $this->modinfo->get_cm($criteria->moduleinstance);
// If it's an activity completion, add all the relevant activities as sub-items
$completion_status = $this->completion->get_grade_completion($cm,$userid);
// If it's an activity completion, add all the relevant activities as sub-items.
$completion_status = $this->completion->get_grade_completion($cm, $userid);
$iinfo['status'] = self::completion_handle($completion_status);
// 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]);
// Determine the grade (retrieve from grade item, not from completion)
$grade = $this->get_grade($cm,$userid);
// 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]);
// Determine the grade (retrieve from grade item, not from completion).
$grade = $this->get_grade($cm, $userid);
$iinfo['grade'] = $grade->grade;
$iinfo['feedback'] = $grade->feedback;
$iinfo['pending'] = $grade->pending;
$anypending = $anypending || $grade->pending;
// Overwrite the status with progress if something has been graded, or is pending
if($completion_status != COMPLETION_INCOMPLETE || $anypending){
if($cinfo["status"] == "incomplete"){
// Overwrite the status with progress if something has been graded, or is pending.
if ($completion_status != COMPLETION_INCOMPLETE || $anypending) {
if ($cinfo["status"] == "incomplete") {
$cinfo["status"] = "progress";
}
}
}
else if ($type == COMPLETION_CRITERIA_TYPE_GRADE){
// Make sure we provide the current course grade
else if ($type == COMPLETION_CRITERIA_TYPE_GRADE) {
// Make sure we provide the current course grade.
$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["status"] = $completion->is_complete()?"complete-pass":"complete-fail";
if ($cinfo["status"] == "incomplete"){
if ($cinfo["status"] == "incomplete") {
$cinfo["status"] = "progress";
}
}
}
// finally add the item to the items list
// finally add the item to the items list.
$cinfo["items"][] = $iinfo;
}
// Set the count and progress stats based on the Type's aggregation style
if($typeaggregation == COMPLETION_AGGREGATION_ALL){
// Count and Progress amount to the sum of items
// Set the count and progress stats based on the Type's aggregation style.
if ($typeaggregation == COMPLETION_AGGREGATION_ALL) {
// Count and Progress amount to the sum of items.
$cinfo["count"] = count($cinfo["items"]);
$cinfo["progress"] = $progress;
}
else { //$typeaggregation == COMPLETION_AGGREGATION_ANY
// Count and progress are either 1 or 0, since any of the items
// complete's the type
else { //$typeaggregation == COMPLETION_AGGREGATION_ANY.
// Count and progress are either 1 or 0, since any of the items.
// complete's the type.
$cinfo["count"] = (count($cinfo["items"]) > 0)?1:0;
$cinfo["progress"] = ($progress>0)?1:0;
}
@ -426,41 +444,39 @@ class corecompletioninfo {
* Get the grade for a certain course module
* @return stdClass|null object containing 'grade' and optional 'feedback' attribute
*/
private function get_grade($cm,$userid){
// TODO: Display grade in the way described in the course setup (with letters if needed)
private function get_grade($cm, $userid) {
// TODO: Display grade in the way described in the course setup (with letters if needed).
$gi= grade_item::fetch(['itemtype' => 'mod',
'itemmodule' => $cm->modname,
'iteminstance' => $cm->instance,
'courseid' => $this->course->id]); // Make sure we only get results relevant to this course
$gi= grade_item::fetch(['itemtype' => 'mod',
'itemmodule' => $cm->modname,
'iteminstance' => $cm->instance,
'courseid' => $this->course->id]); // Make sure we only get results relevant to this course.
if($gi)
{
// Only the following types of grade yield a result
if(($gi->gradetype == GRADE_TYPE_VALUE || $gi->gradetype == GRADE_TYPE_SCALE))
{
if ($gi) {
// Only the following types of grade yield a result.
if (($gi->gradetype == GRADE_TYPE_VALUE || $gi->gradetype == GRADE_TYPE_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;
// Check if the final grade is available and numeric (safety check)
if(!empty($grade) && !empty($grade->finalgrade) && is_numeric($grade->finalgrade)){
// convert scale grades to corresponding scale name
if(isset($scale)){
// get scale value
// Check if the final grade is available and numeric (safety check).
if (!empty($grade) && !empty($grade->finalgrade) && is_numeric($grade->finalgrade)) {
// convert scale grades to corresponding scale name.
if (isset($scale)) {
// get scale value.
$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->pending = (new gradingscanner($gi))->pending($userid);
}
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->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
* @return stdClass|null object containing 'grade' and optional 'feedback' attribute
*/
private function get_course_grade($userid){
// TODO: Display grade in the way described in the course setup (with letters if needed)
$gi= grade_item::fetch(['itemtype' => 'course',
'iteminstance' => $this->course->id,
private function get_course_grade($userid) {
// TODO: Display grade in the way described in the course setup (with letters if needed).
$gi= grade_item::fetch(['itemtype' => 'course',
'iteminstance' => $this->course->id,
'courseid' => $this->course->id]);
if($gi)
{
// Only the following types of grade yield a result
if(($gi->gradetype == GRADE_TYPE_VALUE || $gi->gradetype == GRADE_TYPE_SCALE))
{
if ($gi) {
// Only the following types of grade yield a result.
if (($gi->gradetype == GRADE_TYPE_VALUE || $gi->gradetype == GRADE_TYPE_SCALE)) {
$scale = $gi->load_scale();
$grade = $gi->get_final($userid); // Get the grade for the specified user
// Check if the final grade is available and numeric (safety check)
if(!empty($grade) && !empty($grade->finalgrade) && is_numeric($grade->finalgrade)){
// convert scale grades to corresponding scale name
if(isset($scale)){
// get scale value
$grade = $gi->get_final($userid); // Get the grade for the specified user.
// Check if the final grade is available and numeric (safety check).
if (!empty($grade) && !empty($grade->finalgrade) && is_numeric($grade->finalgrade)) {
// convert scale grades to corresponding scale name.
if (isset($scale)) {
// get scale value.
return $scale->get_nearest_item($grade->finalgrade);
}
else
}
else
{
// round final grade to 1 decimal point
return round($grade->finalgrade,1);
// round final grade to 1 decimal point.
return round($grade->finalgrade, 1);
}
}
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
* @return null|float The percentage, or null if completion is not supported in the course,
* 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.
if (!$this->completion->is_enabled()) {
@ -534,38 +548,38 @@ class corecompletioninfo {
$count = count($completions);
$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)) {
$completed = $count;
}
else {
// count all completions, but treat
foreach($completions as $completion){
// count all completions, but treat .
foreach ($completions as $completion) {
$crit = $completion->get_criteria();
if($crit->criteriatype == COMPLETION_CRITERIA_TYPE_ACTIVITY) {
// get the cm data object
if ($crit->criteriatype == COMPLETION_CRITERIA_TYPE_ACTIVITY) {
// get the cm data object.
$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);
// Count complete, but failed as incomplete too...
if (($data->completionstate == COMPLETION_INCOMPLETE) || ($data->completionstate == COMPLETION_COMPLETE_FAIL)) {
$completed += 0;
} else {
$completed += 1;
}
}
}
else {
if($completion->is_complete()){
if ($completion->is_complete()) {
$completed += 1;
}
}
}
}
}
$result = new \stdClass;
$result->count = $count;
$result->completed = $completed;
$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
* @return null|\stdClass The percentage, or null if completion is not supported in the course,
* 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.
if (!$this->completion->is_enabled()) {
@ -591,74 +605,74 @@ class corecompletioninfo {
$aggregation = $this->completion->get_aggregation_method();
$critcount = [];
// Before we check how many modules have been completed see if the course has completed.
// count all completions, but treat
foreach($completions as $completion){
// Before we check how many modules have been completed see if the course has completed. .
// count all completions, but treat .
foreach ($completions as $completion) {
$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;
if(!array_key_exists($type,$critcount)){
if (!array_key_exists($type, $critcount)) {
$critcount[$type] = new \stdClass;
$critcount[$type]->count = 0;
$critcount[$type]->completed = 0;
$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->count += 1;
if($crit->criteriatype == COMPLETION_CRITERIA_TYPE_ACTIVITY) {
// get the cm data object
if ($crit->criteriatype == COMPLETION_CRITERIA_TYPE_ACTIVITY) {
// get the cm data object.
$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);
// Count complete, but failed as incomplete too...
if (($data->completionstate == COMPLETION_INCOMPLETE) || ($data->completionstate == COMPLETION_COMPLETE_FAIL)) {
$typecount->completed += 0;
} else {
$typecount->completed += 1;
}
}
}
else {
if($completion->is_complete()){
if ($completion->is_complete()) {
$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;
$completed = 0;
$completion_percentage = 0;
foreach($critcount as $c){
// Take only types that are actually present into account
if($c->count > 0){
// 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)
if($c->aggregation == COMPLETION_AGGREGATION_ALL){
foreach ($critcount as $c) {
// Take only types that are actually present into account.
if ($c->count > 0) {
// 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).
if ($c->aggregation == COMPLETION_AGGREGATION_ALL) {
$ct = $c->count;
$cmpl = $c->completed;
}
}
else {
$ct = 1;
$cmpl = ($c->completed > 0)?1:0;
}
// if ANY completion for the types, count only the criteria type with the highest completion percentage -
// Overwrite data if current type is more complete
if($aggregation == COMPLETION_AGGREGATION_ANY) {
// if ANY completion for the types, count only the criteria type with the highest completion percentage -.
// Overwrite data if current type is more complete.
if ($aggregation == COMPLETION_AGGREGATION_ANY) {
$pct = $cmpl/$ct;
if($pct > $completion_percentage){
if ($pct > $completion_percentage) {
$count = $ct;
$completed = $cmpl;
$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 {
$count += $ct;
$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->completed = $completed;
$result->percentage = ($count > 0)?(($completed / $count) * 100):0;
return $result;
}
return $result;
}

View File

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

View File

@ -1,4 +1,24 @@
<?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;
require_once($CFG->libdir.'/externallib.php');
@ -15,22 +35,22 @@ class coursemoduleinfo {
private $cm_info;
private $db_record;
public function __construct($id){
public function __construct($id) {
global $DB;
// Determine the icon for the associated activity
// Determine the icon for the associated activity.
$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->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;
}
public function setTitle($value){
public function setTitle($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
// 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;
require_once($CFG->libdir.'/externallib.php');
@ -8,7 +29,7 @@ use \local_treestudyplan\local\helpers\webservicehelper;
use \local_treestudyplan\completionscanner;
use \local_treestudyplan\gradingscanner;
class courseservice extends \external_api
class courseservice extends \external_api
{
const CAP_EDIT = "local/treestudyplan:editstudyplan";
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( [
"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));
}
protected static function map_category_structure($lazy=false,$value=VALUE_REQUIRED)
{
protected static function map_category_structure($lazy=false, $value=VALUE_REQUIRED) {
$s = [
"id" => new \external_value(PARAM_INT, 'course category id'),
"context_id" => new \external_value(PARAM_INT, 'course category context id'),
"category" => contextinfo::structure(VALUE_OPTIONAL),
"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'),
"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["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;
$root = \core_course_category::get($root_id);
$context = $root->get_context();
// Make sure the user has access to the context for editing purposes
webservicehelper::require_capabilities(self::CAP_EDIT,$context);
// Make sure the user has access to the context for editing purposes.
webservicehelper::require_capabilities(self::CAP_EDIT, $context);
// Determine top categories from provided context
// Determine top categories from provided context.
if($root->id == 0){
// on the system level, determine the user's topmost allowed catecories
if ($root->id == 0) {
// on the system level, determine the user's topmost allowed catecories.
$user_top = \core_course_category::user_top();
if($user_top->id == 0){ // top category..
$children = $root->get_children(); // returns a list of çore_course_category, let it overwrite $children
if ($user_top->id == 0) { // top category..
$children = $root->get_children(); // returns a list of çore_course_category, let it overwrite $children.
} else {
$children = [$user_top];
}
} else if ($root->is_uservisible()){
} else if ($root->is_uservisible()) {
$children = [$root];
}
}
foreach($children as $cat){
$list[] = static::map_category($cat,false);
foreach ($children as $cat) {
$list[] = static::map_category($cat, false);
}
return $list;
}
}
public static function get_category_parameters()
public static function get_category_parameters()
{
return new \external_function_parameters( [
"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);
}
public static function get_category($id){
public static function get_category($id) {
$cat = \core_course_category::get($id);
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;
$catcontext = $cat->get_context();
$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();
$model = [
"id" => $cat->id,
@ -108,40 +128,39 @@ class courseservice extends \external_api
"hascourses" => !empty($courses),
];
if(!$lazy)
{
if (!$lazy) {
$model["courses"] = [];
foreach($courses as $course){
foreach ($courses as $course) {
$courseinfo = new courseinfo($course->id);
$model["courses"][] = $courseinfo->editor_model();
}
$model["children"] = [];
foreach($children as $child){
$model["children"][] = static::map_category($child,true);
foreach ($children as $child) {
$model["children"][] = static::map_category($child, true);
}
}
return $model;
}
public static function list_accessible_categories_parameters()
public static function list_accessible_categories_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));
}
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;
} else { // $operation == "view" || default
} else { // $operation == "view" || default.
$capability = self::CAP_VIEW;
}
@ -149,35 +168,35 @@ class courseservice extends \external_api
$list = [];
/* @var $cat \core_course_category */
foreach($cats as $cat){
$list[] = static::map_category($cat,true);
foreach ($cats as $cat) {
$list[] = static::map_category($cat, true);
}
return $list;
}
public static function categories_by_capability($capability,\core_course_category $parent=null){
// List the categories in which the user has a specific capability
public static function categories_by_capability($capability, \core_course_category $parent=null) {
// List the categories in which the user has a specific capability.
$list = [];
// initialize parent if needed
if($parent == null){
// initialize parent if needed.
if ($parent == null) {
$parent = \core_course_category::user_top();
if(has_capability($capability,$parent->get_context())){
if (has_capability($capability, $parent->get_context())) {
$list[] = $parent;
}
}
$children = $parent->get_children();
foreach($children as $child){
// Check if we should add this category
if(has_capability($capability,$child->get_context())){
foreach ($children as $child) {
// Check if we should add this category.
if (has_capability($capability, $child->get_context())) {
$list[] = $child;
// For optimization purposes, we include all its children now, since they will have inherited the permission
// #PREMATURE_OPTIMIZATION ???
$list = array_merge($list,self::recursive_child_categories($child));
// For optimization purposes, we include all its children now, since they will have inherited the permission.
// #PREMATURE_OPTIMIZATION ???.
$list = array_merge($list, self::recursive_child_categories($child));
} else {
if($child->get_children_count() > 0){
$list = array_merge($list,self::categories_by_capability($capability,$child));
if ($child->get_children_count() > 0) {
$list = array_merge($list, self::categories_by_capability($capability, $child));
}
}
}
@ -185,91 +204,91 @@ class courseservice extends \external_api
return $list;
}
protected static function recursive_child_categories(\core_course_category $parent){
protected static function recursive_child_categories(\core_course_category $parent) {
$list = [];
$children = $parent->get_children();
foreach($children as $child){
foreach ($children as $child) {
$list[] = $child;
if($child->get_children_count() > 0){
$list = array_merge($list,self::recursive_child_categories($child));
if ($child->get_children_count() > 0) {
$list = array_merge($list, self::recursive_child_categories($child));
}
}
return $list;
}
public static function list_used_categories_parameters()
public static function list_used_categories_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));
}
public static function list_used_categories($operation='edit')
public static function list_used_categories($operation='edit')
{
global $DB;
if($operation == "edit"){
if ($operation == "edit") {
$capability = self::CAP_EDIT;
} else { // $operation == "view" || default
} else { // $operation == "view" || default.
$capability = self::CAP_VIEW;
}
}
$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");
foreach($rs as $r){
foreach ($rs as $r) {
$context_ids[$r->context_id] = $r->num;
}
$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);
$list = [];
foreach($cats as $cat){
foreach ($cats as $cat) {
$count = 0;
$ctxid = $cat->get_context()->id;
if(array_key_exists($ctxid,$context_ids)){
if (array_key_exists($ctxid, $context_ids)) {
$count = $context_ids[$ctxid];
}
$o = static::map_category($cat,true);
$o = static::map_category($cat, true);
$o["studyplancount"] = $count;
$list[] = $o;
}
return $list;
}
public static function list_accessible_categories_with_usage($operation='edit'){
public static function list_accessible_categories_with_usage($operation='edit') {
global $DB;
if($operation == "edit"){
if ($operation == "edit") {
$capability = self::CAP_EDIT;
} else { // $operation == "view" || default
} else { // $operation == "view" || default.
$capability = self::CAP_VIEW;
}
// retrieve context ids used
}
// retrieve context ids used.
$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");
foreach($rs as $r){
foreach ($rs as $r) {
$context_ids[$r->context_id] = $r->num;
}
$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);
$list = [];
foreach($cats as $cat){
foreach ($cats as $cat) {
$count = 0;
$ctxid = $cat->get_context()->id;
if(array_key_exists($ctxid,$context_ids)){
if (array_key_exists($ctxid, $context_ids)) {
$count = $context_ids[$ctxid];
}
$o = new \stdClass();
@ -283,38 +302,35 @@ class courseservice extends \external_api
/**************************************
*
*
* Progress scanners for teacherview
*
*
**************************************/
public static function scan_grade_progress_parameters()
{
public static function scan_grade_progress_parameters() {
return new \external_function_parameters( [
"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),
"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),
]);
}
public static function scan_grade_progress_returns()
{
public static function scan_grade_progress_returns() {
return gradingscanner::structure(VALUE_REQUIRED);
}
public static function scan_grade_progress($gradeitemid, $studyplanid)
{
public static function scan_grade_progress($gradeitemid, $studyplanid) {
global $DB;
// Verify access to the study plan
// Verify access to the study plan.
$o = studyplan::findById($studyplanid);
webservicehelper::require_capabilities(self::CAP_VIEW,$o->context());
// Retrieve grade item
webservicehelper::require_capabilities(self::CAP_VIEW, $o->context());
// Retrieve grade item.
$gi = \grade_item::fetch(["id" => $gradeitemid]);
// Validate course is linked to studyplan
// Validate course is linked to studyplan.
$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()}");
}
@ -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( [
"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),
"courseid" => new \external_value(PARAM_INT, 'Course id of criteria',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),
"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);
}
public static function scan_completion_progress($criteriaid, $studyplanid,$courseid)
public static function scan_completion_progress($criteriaid, $studyplanid, $courseid)
{
global $DB;
// Verify access to the study plan
// Verify access to the study plan.
$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]);
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()}");
}
$scanner = new completionscanner($crit,\get_course($courseid));
$scanner = new completionscanner($crit, \get_course($courseid));
return $scanner->model();
}
public static function scan_badge_progress_parameters()
public static function scan_badge_progress_parameters()
{
return new \external_function_parameters( [
"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),
"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),
]);
}
public static function scan_badge_progress_returns()
public static function scan_badge_progress_returns()
{
return new \external_single_structure([
"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;
// Check access to the study plan
// Check access to the study plan.
$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
if(!$o->badge_linked($badgeid)){
// Validate that badge is linked to studyplan.
if (!$o->badge_linked($badgeid)) {
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);
$badgeinfo = new badgeinfo($badge);
// get the connected users
// get the connected users.
$students = associationservice::all_associated($studyplanid);
// Just get the user ids
$studentids = array_map(function ($a){ return $a["id"];},$students);
// Just get the user ids.
$studentids = array_map(function ($a) { return $a["id"];}, $students);
return [
"total" => count($studentids),

View File

@ -1,4 +1,24 @@
<?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;
require_once($CFG->libdir.'/externallib.php');
@ -16,7 +36,7 @@ class gradeinfo {
private $studyitem = null;
private $id;
private $gradeitem;
private $icon;
private $link;
private $gradinglink;
@ -36,29 +56,29 @@ class gradeinfo {
private static $sections = [];
protected static function getSectionSequence($sectionid){
protected static function getSectionSequence($sectionid) {
global $DB;
if(!array_key_exists($sectionid,self::$sections)){
self::$sections[$sectionid] = explode(",",$DB->get_field("course_sections","sequence",["id"=>$sectionid]));
if (!array_key_exists($sectionid, self::$sections)) {
self::$sections[$sectionid] = explode(", ", $DB->get_field("course_sections", "sequence", ["id"=>$sectionid]));
}
return self::$sections[$sectionid];
}
public function getGradeitem(){
public function getGradeitem() {
return $this->gradeitem;
}
public function getGradingscanner(){
public function getGradingscanner() {
return $this->gradingscanner;
}
public function getScale(){
public function getScale() {
return $this->scale;
}
protected static function get_contentitems() {
global $PAGE;
if(empty(static::$contentitems)){
if (empty(static::$contentitems)) {
$PAGE->set_context(\context_system::instance());
static::$contentitems = (new content_item_readonly_repository())->find_all();
}
@ -67,60 +87,58 @@ class gradeinfo {
public static function get_contentitem($name) {
$contentitems = static::get_contentitems();
for($i = 0; $i < count($contentitems); $i++){
if($contentitems[$i]->get_name() == $name){
for($i = 0; $i < count($contentitems); $i++) {
if ($contentitems[$i]->get_name() == $name) {
return $contentitems[$i];
}
}
return null;
}
public static function getCourseContextById($id){
public static function getCourseContextById($id) {
$gi = grade_item::fetch(["id" => $id]);
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));
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));
}
return \context_course::instance($gi->courseid);;
}
public function __construct($id,studyitem $studyitem = null){
public function __construct($id, studyitem $studyitem = null) {
global $DB;
$this->studyitem = $studyitem;
$gi = grade_item::fetch(["id" => $id]);
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));
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));
}
$this->id = $id;
$this->gradeitem = $gi;
// Determine the icon for the associated activity
// Determine the icon for the associated activity.
$contentitem = static::get_contentitem($gi->itemmodule);
$this->icon = empty($contentitem)?"":$contentitem->get_icon();
// Determine a link to the associated activity
if($gi->itemtype != "mod" || empty($gi->itemmodule) || empty($gi->iteminstance)){
// Determine a link to the associated activity.
if ($gi->itemtype != "mod" || empty($gi->itemmodule) || empty($gi->iteminstance)) {
$this->link = "";
$this->cmid = 0;
$this->section = 0;
$this->sectionorder = 0;
}
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;
// sort by position in course
//
// sort by position in course.
// .
$this->section = $cminfo->sectionnum;
$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}";
if($gi->itemmodule == 'quiz'){
if ($gi->itemmodule == 'quiz') {
$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";
}
else {
@ -141,31 +159,31 @@ class gradeinfo {
}
public function is_selected(){
public function is_selected() {
global $DB;
if($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]);
if($r && $r->include) {
if ($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]);
if ($r && $r->include) {
return(true);
}
}
return(false);
}
public function is_required(){
public function is_required() {
global $DB;
if($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]);
if($r && $r->include && $r->required) {
if ($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]);
if ($r && $r->include && $r->required) {
return(true);
}
}
return(false);
}
public static function editor_structure($value=VALUE_REQUIRED){
public static function editor_structure($value=VALUE_REQUIRED) {
return new \external_single_structure([
"id" => new \external_value(PARAM_INT, 'grade_item 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'),
"grading" => gradingscanner::structure(),
"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) {
@ -195,15 +213,15 @@ class gradeinfo {
"required" => $this->is_required(),
];
// 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())
&& $this->gradingscanner->is_available()){
if ($studyitem !== null && $this->is_selected() && has_capability('local/treestudyplan:viewuserreports', $studyitem->studyline()->studyplan()->context())
&& $this->gradingscanner->is_available()) {
$model['grading'] = $this->gradingscanner->model();
}
return $model;
}
public static function user_structure($value=VALUE_REQUIRED){
public static function user_structure($value=VALUE_REQUIRED) {
return new \external_single_structure([
"id" => new \external_value(PARAM_INT, 'grade_item 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)'),
"icon" => new \external_value(PARAM_RAW, 'html for icon of 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'),
"selected" => new \external_value(PARAM_BOOL, 'is selected for current studyitem'),
], 'referenced course information',$value);
], 'referenced course information', $value);
}
public function user_model($userid) {
global $DB;
$grade = $this->gradeitem->get_final($userid);
// convert scale grades to corresponding scale name
if(!empty($grade)){
if(!is_numeric($grade->finalgrade) && empty($grade->finalgrade)){
// convert scale grades to corresponding scale name.
if (!empty($grade)) {
if (!is_numeric($grade->finalgrade) && empty($grade->finalgrade)) {
$finalgrade = "-";
}
else if(isset($this->scale)){
else if (isset($this->scale)) {
$finalgrade = $this->scale->get_nearest_item($grade->finalgrade);
}
else
}
else
{
$finalgrade = round($grade->finalgrade,1);
$finalgrade = round($grade->finalgrade, 1);
}
}
else
else
{
$finalgrade = "-";
}
// retrieve the aggregator and determine completion
if(!isset($this->studyitem)){
// retrieve the aggregator and determine completion.
if (!isset($this->studyitem)) {
throw new \UnexpectedValueException("Study item not set (null) for gradeinfo in report mode");
}
$aggregator = $this->studyitem->studyline()->studyplan()->aggregator();
$completion = $aggregator->grade_completion($this,$userid);
$completion = $aggregator->grade_completion($this, $userid);
$model = [
"id" => $this->id,
@ -268,7 +286,7 @@ class gradeinfo {
return $model;
}
public function export_model(){
public function export_model() {
return [
"name" => $this->name,
"type" => $this->gradeitem->itemmodule,
@ -276,113 +294,108 @@ class gradeinfo {
"required" => $this->is_required(),
];
}
public static function import(studyitem $item,array $model){
if($item->type() == studyitem::COURSE){
public static function import(studyitem $item, array $model) {
if ($item->type() == studyitem::COURSE) {
$course_id = $item->courseid();
$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_type = $gi->itemmodule;
if($gi_name == $model["name"] && $gi_type == $model["type"]){
// we have a match
if(!isset($model["selected"])){ $model["selected"] = true;}
if(!isset($model["required"])){ $model["required"] = false;}
if($model["selected"] || $model["required"]){
static::include_grade($gi->id,$item->id(),$model["selected"], $model["required"]);
}
if ($gi_name == $model["name"] && $gi_type == $model["type"]) {
// we have a match.
if (!isset($model["selected"])) { $model["selected"] = true;}
if (!isset($model["required"])) { $model["required"] = false;}
if ($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 = [];
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);
} 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);
}
foreach($activities as $act)
{
if($act->visible)
{
foreach ($activities as $act) {
if ($act->visible) {
$gradeitems= grade_item::fetch_all(['itemtype' => 'mod', 'itemmodule' => $act->mod, 'iteminstance' => $act->id, 'courseid' => $course->id]);
if(!empty($gradeitems))
{
foreach($gradeitems as $gi){
if(($gi->gradetype == GRADE_TYPE_VALUE || $gi->gradetype == GRADE_TYPE_SCALE))
{
if (!empty($gradeitems)) {
foreach ($gradeitems as $gi) {
if (($gi->gradetype == GRADE_TYPE_VALUE || $gi->gradetype == GRADE_TYPE_SCALE)) {
try {
$gradable = new static($gi->id,$studyitem);
$gradable = new static($gi->id, $studyitem);
$list[] = $gradable;
}
catch(\InvalidArgumentException $x){}
catch(\InvalidArgumentException $x) {}
}
}
}
}
}
usort($list, function($a,$b){
usort($list, function($a, $b) {
$course = $a->coursesort <=> $b->coursesort;
return ($course != 0)?$course:$a->gradeitem->sortorder <=> $b->gradeitem->sortorder;
});
return $list;
}
public static function list_studyitem_gradables(studyitem $studyitem)
{
public static function list_studyitem_gradables(studyitem $studyitem) {
global $DB;
$table = 'local_treestudyplan_gradeinc';
$list = [];
$records = $DB->get_records($table,['studyitem_id' => $studyitem->id()]);
foreach($records as $r){
if(isset($r->grade_item_id)){
$records = $DB->get_records($table, ['studyitem_id' => $studyitem->id()]);
foreach ($records as $r) {
if (isset($r->grade_item_id)) {
try {
if($r->include || $r->required){
$list[] = new static($r->grade_item_id,$studyitem);
if ($r->include || $r->required) {
$list[] = new static($r->grade_item_id, $studyitem);
}
}
catch(\InvalidArgumentException $x){
// on InvalidArgumentException, the grade_item id can no longer be found
// Remove the link to avoid database record hogging
catch(\InvalidArgumentException $x) {
// on InvalidArgumentException, the grade_item id can no longer be found.
// Remove the link to avoid database record hogging.
$DB->delete_records($table, ['id' => $r->id]);
}
}
}
usort($list, function($a,$b){
usort($list, function($a, $b) {
$course = $a->coursesort <=> $b->coursesort;
return ($course != 0)?$course:$a->gradeitem->sortorder <=> $b->gradeitem->sortorder;
});
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;
$table = 'local_treestudyplan_gradeinc';
if($include){
// make sure a record exits
$r = $DB->get_record($table,['studyitem_id' => $item_id, 'grade_item_id' => $grade_id]);
if($r){
if ($include) {
// make sure a record exits.
$r = $DB->get_record($table, ['studyitem_id' => $item_id, 'grade_item_id' => $grade_id]);
if ($r) {
$r->include = 1;
$r->required = boolval($required)?1:0;
$id = $DB->update_record($table, $r);
} else {
$DB->insert_record($table, [
'studyitem_id' => $item_id,
'grade_item_id' => $grade_id,
'include' => 1,
'studyitem_id' => $item_id,
'grade_item_id' => $grade_id,
'include' => 1,
'required' =>boolval($required)?1:0]
);
}
} else {
// remove if it should not be included
$r = $DB->get_record($table,['studyitem_id' => $item_id, 'grade_item_id' => $grade_id]);
if($r){
// remove if it should not be included.
$r = $DB->get_record($table, ['studyitem_id' => $item_id, 'grade_item_id' => $grade_id]);
if ($r) {
$DB->delete_records($table, ['id' => $r->id]);
}
}

View File

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

View File

@ -1,4 +1,25 @@
<?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;
@ -12,57 +33,55 @@ class bistate_aggregator extends \local_treestudyplan\aggregator {
public const DEPRECATED = false;
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_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 $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 $accept_pending_as_submitted = False; // Also count ungraded but submitted
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_completed = 0.66; // Minimum fraction that must be completed to aggregate as completed.
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 $accept_pending_as_submitted = False; // Also count ungraded but submitted .
public function __construct($configstr) {
// allow public constructor for testing purposes
// allow public constructor for testing purposes.
$this->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}"));
if($val >= 0 && $val <= 100)
{
if ($val >= 0 && $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}"));
}
// Next, decode json
$config = \json_decode($configstr,true);
// Next, decode json.
$config = \json_decode($configstr, true);
if(is_array($config)){
// copy all valid config settings to this item
foreach(["thresh_excellent", "thresh_good", "thresh_completed","thresh_progress",] as $key){
if(array_key_exists($key,$config)){
if (is_array($config)) {
// copy all valid config settings to this item.
foreach (["thresh_excellent", "thresh_good", "thresh_completed", "thresh_progress", ] as $key) {
if (array_key_exists($key, $config)) {
$val = $config[$key];
if($val >= 0 && $val <= 100)
{
if ($val >= 0 && $val <= 100) {
$this->$key = floatval($val)/100;
}
}
}
}
foreach(["use_failed", "accept_pending_as_submitted"] as $key){
if(array_key_exists($key,$config)){
foreach (["use_failed", "accept_pending_as_submitted"] as $key) {
if (array_key_exists($key, $config)) {
$this->$key = boolval($config[$key]);
}
}
} else {
}
}
// Return active configuration model
// Return active configuration model.
public function config_string() {
return json_encode([
"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 useRequiredGrades() { return True;}
public function useItemConditions() { return False;}
public function aggregate_binary_goals(array $completions, array $required = []){
// function is public to allow access for the testing code
public function aggregate_binary_goals(array $completions, array $required = []) {
// function is public to allow access for the testing code.
// return te following conditions
// Possible states:
// - 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::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::INCOMPLETE - No goals have been started
// - completion::PROGRESS - All other states
// return te following conditions.
// Possible states:.
// - 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::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::INCOMPLETE - No goals have been started.
// - completion::PROGRESS - All other states.
$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;
foreach($completions as $index => $c) {
foreach ($completions as $index => $c) {
$completed += ($c >= completion::COMPLETED)?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_started = ($total >0)?(floatval($started)/floatval($total)):0.0;
if($total == 0){
if ($total == 0) {
return completion::INCOMPLETE;
}
if($fraction_completed >= $this->thresh_excellent && $allrequiredmet){
if ($fraction_completed >= $this->thresh_excellent && $allrequiredmet) {
return completion::EXCELLENT;
}
else if($fraction_completed >= $this->thresh_good && $allrequiredmet){
else if ($fraction_completed >= $this->thresh_good && $allrequiredmet) {
return completion::GOOD;
}
else if($fraction_completed >= $this->thresh_completed && $allrequiredmet){
else if ($fraction_completed >= $this->thresh_completed && $allrequiredmet) {
return completion::COMPLETED;
}
else if($started == 0){
else if ($started == 0) {
return completion::INCOMPLETE;
}
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();
$coursefinished = ($course->enddate)?($course->enddate < time()):false;
// Note: studyitem condition config is not used in this aggregator.
// loop through all associated gradables and count the totals, completed, etc..
$completions = [];
$required = [];
foreach(gradeinfo::list_studyitem_gradables($studyitem) as $gi){
$completions[] = $this->grade_completion($gi,$userid);
if($gi->is_required()){
// if it's a required grade
// also add it's index in the completion list to the list of required grades
foreach (gradeinfo::list_studyitem_gradables($studyitem) as $gi) {
$completions[] = $this->grade_completion($gi, $userid);
if ($gi->is_required()) {
// if it's a required grade .
// also add it's index in the completion list to the list of required grades .
$required[] = count($completions) - 1;
}
}
// Combine the aquired completions into one
$result = self::aggregate_binary_goals($completions,$required);
if($this->use_failed && $result == completion::PROGRESS && $coursefinished){
// Combine the aquired completions into one.
$result = self::aggregate_binary_goals($completions, $required);
if ($this->use_failed && $result == completion::PROGRESS && $coursefinished) {
return completion::FAILED;
} else {
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.
// Possible states:
// - completion::EXCELLENT - All incoming states are excellent
// - completion::GOOD - All incoming states are at least good
// - completion::COMPLETED - All incoming states are at least completed
// - completion::FAILED - All incoming states are failed
// - completion::INCOMPLETE - All incoming states are incomplete
// - completion::PROGRESS - All other states
// Possible states:.
// - completion::EXCELLENT - All incoming states are excellent.
// - completion::GOOD - All incoming states are at least good.
// - completion::COMPLETED - All incoming states are at least completed.
// - completion::FAILED - All incoming states are failed.
// - completion::INCOMPLETE - All incoming states are incomplete.
// - completion::PROGRESS - All other states.
// First count all states
// First count all states.
$statecount = completion::count_states($completion);
$total = count($completion);
if( $total == $statecount[completion::EXCELLENT]){
if ( $total == $statecount[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;
}
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;
}
else if( $statecount[completion::FAILED]){
}
else if ( $statecount[completion::FAILED]) {
return completion::FAILED;
}
else if( $total == $statecount[completion::INCOMPLETE]){
}
else if ( $total == $statecount[completion::INCOMPLETE]) {
return completion::INCOMPLETE;
}
}
else {
return completion::PROGRESS;
}
@ -203,21 +222,20 @@ class bistate_aggregator extends \local_treestudyplan\aggregator {
$table = "local_treestudyplan_gradecfg";
$gradeitem = $gradeinfo->getGradeitem();
$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;
if(empty($grade)){
if (empty($grade)) {
return completion::INCOMPLETE;
}
else if($grade->finalgrade === NULL)
{
// 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
// Therefor, we treat a NULL result as a reason to check the relevant gradingscanner for presence of pending items
else if ($grade->finalgrade === NULL) {
// 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.
// 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.
// Pending items are otherwise expressly indicated by the "pendingsubmission" field in the user model
if($gradeinfo->getGradingscanner()->pending($userid)){
// Pending items are otherwise expressly indicated by the "pendingsubmission" field in the user model.
if ($gradeinfo->getGradingscanner()->pending($userid)) {
return completion::PENDING;
} else {
return completion::INCOMPLETE;
@ -225,49 +243,44 @@ class bistate_aggregator extends \local_treestudyplan\aggregator {
}
else {
$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;
$scale = $gradeinfo->getScale();
if( isset($scale)){
$gradecfg = $DB->get_record($table,["scale_id"=>$scale->id]);
if ( isset($scale)) {
$gradecfg = $DB->get_record($table, ["scale_id"=>$scale->id]);
}
else if($gradeitem->grademin == 0)
{
$gradecfg = $DB->get_record($table,["grade_points"=>$gradeitem->grademax]);
else if ($gradeitem->grademin == 0) {
$gradecfg = $DB->get_record($table, ["grade_points"=>$gradeitem->grademax]);
}
else
else
{
$gradecfg = null;
}
// for point grades, a provided grade pass overrides the defaults in the gradeconfig
// for scales, the configuration in the gradeconfig is leading
// for point grades, a provided grade pass overrides the defaults in the gradeconfig.
// for scales, the configuration in the gradeconfig is leading.
if($gradecfg && (isset($scale) || $gradeitem->gradepass == 0))
{
// if so, we need to know if the grade is
if($finalgrade >= $gradecfg->min_completed){
// return completed if completed
if ($gradecfg && (isset($scale) || $gradeitem->gradepass == 0)) {
// if so, we need to know if the grade is .
if ($finalgrade >= $gradecfg->min_completed) {
// return completed if completed.
return completion::COMPLETED;
}
else if($this->use_failed && $coursefinished)
{
// return failed if failed is enabled and the grade is less than the minimum grade for progress
else if ($this->use_failed && $coursefinished) {
// return failed if failed is enabled and the grade is less than the minimum grade for progress.
return completion::FAILED;
}
else {
return completion::PROGRESS;
}
}
else if($gradeitem->gradepass > 0)
{
else if ($gradeitem->gradepass > 0) {
$range = floatval($gradeitem->grademax - $gradeitem->grademin);
// if no gradeconfig and gradepass is set, use that one to determine config.
if($finalgrade >= $gradeitem->gradepass){
if ($finalgrade >= $gradeitem->gradepass) {
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 completion::FAILED;
}
@ -276,18 +289,17 @@ class bistate_aggregator extends \local_treestudyplan\aggregator {
}
}
else {
// Blind assumptions if nothing is provided
// over 55% of range is completed
// if range >= 3 and failed is enabled, assume that this means failed
// Blind assumptions if nothing is provided.
// over 55% of range is completed.
// if range >= 3 and failed is enabled, assume that this means failed.
$g = floatval($finalgrade - $gradeitem->grademin);
$range = floatval($gradeitem->grademax - $gradeitem->grademin);
$score = $g / $range;
if($score > 0.55){
if ($score > 0.55) {
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 completion::FAILED;
}

View File

@ -1,4 +1,25 @@
<?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;
@ -11,42 +32,42 @@ use \local_treestudyplan\debug;
class core_aggregator extends \local_treestudyplan\aggregator {
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) {
// allow public constructor for testing purposes
// allow public constructor for testing purposes.
$this->initialize($configstr);
}
protected function initialize($configstr) {
// First initialize with the defaults
foreach(["accept_pending_as_submitted"] as $key){
// First initialize with the defaults.
foreach (["accept_pending_as_submitted"] as $key) {
$this->$key = boolval(get_config('local_treestudyplan', "bistate_{$key}"));
}
// Next, decode json
$config = \json_decode($configstr,true);
// Next, decode json.
$config = \json_decode($configstr, true);
if(is_array($config)){
// copy all valid config settings to this item
foreach(["accept_pending_as_submitted"] as $key){
if(array_key_exists($key,$config)){
if (is_array($config)) {
// copy all valid config settings to this item.
foreach (["accept_pending_as_submitted"] as $key) {
if (array_key_exists($key, $config)) {
$this->$key = boolval($config[$key]);
}
}
} else {
}
}
// Return active configuration model
// Return active configuration model.
public function config_string() {
return json_encode([
"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 useRequiredGrades() { return True;}
public function useItemConditions() { return False;}
@ -68,37 +89,37 @@ class core_aggregator extends \local_treestudyplan\aggregator {
* @return void
*/
public function aggregate_course(courseinfo $courseinfo, studyitem $studyitem, $userid){
// Retrieve the core completion info from the core
public function aggregate_course(courseinfo $courseinfo, studyitem $studyitem, $userid) {
// Retrieve the core completion info from the core.
$course = $courseinfo->course();
$completion = new \completion_info($course);
if ($completion->is_enabled() && $completion->is_tracked_user($userid)){
if($completion->is_course_complete($userid)){
if ($completion->is_enabled() && $completion->is_tracked_user($userid)) {
if ($completion->is_course_complete($userid)) {
// 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...
// Probably needs a config value in the aggregator....
return completion::COMPLETED;
} else {
// Check if the course is over or not, if it is over, display failed
// Else, return PROGRESS
// Check if the course is over or not, if it is over, display failed.
// Else, return PROGRESS.
// Retrieve timing through courseinfo
// Retrieve timing through courseinfo .
$timing = courseinfo::coursetiming($course);
// Not met and time is passed, means FAILED
if($timing == "past"){
// Not met and time is passed, means FAILED.
if ($timing == "past") {
return completion::FAILED;
}
}
else {
// Check if any of the requirements are being met?
// Check if any of the requirements are being met?.
$completions = $completion->get_completions($userid);
foreach($completions as $c){
if($c->is_complete()){
// If so, return progress
foreach ($completions as $c) {
if ($c->is_complete()) {
// If so, return 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.
// Possible states:
// - completion::EXCELLENT - All incoming states are excellent
// - completion::GOOD - All incoming states are at least good
// - completion::COMPLETED - All incoming states are at least completed
// - completion::FAILED - All incoming states are failed
// - completion::INCOMPLETE - All incoming states are incomplete
// - completion::PROGRESS - All other states
// Possible states:.
// - completion::EXCELLENT - All incoming states are excellent.
// - completion::GOOD - All incoming states are at least good.
// - completion::COMPLETED - All incoming states are at least completed.
// - completion::FAILED - All incoming states are failed.
// - completion::INCOMPLETE - All incoming states are incomplete.
// - completion::PROGRESS - All other states.
// First count all states
// First count all states.
$statecount = completion::count_states($completion);
$total = count($completion);
if( $total == $statecount[completion::EXCELLENT]){
if ( $total == $statecount[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;
}
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;
}
else if( $statecount[completion::FAILED]){
}
else if ( $statecount[completion::FAILED]) {
return completion::FAILED;
}
else if( $total == $statecount[completion::INCOMPLETE]){
}
else if ( $total == $statecount[completion::INCOMPLETE]) {
return completion::INCOMPLETE;
}
}
else {
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.
public function grade_completion(gradeinfo $gradeinfo, $userid) {
@ -155,18 +176,17 @@ class core_aggregator extends \local_treestudyplan\aggregator {
$gradeitem = $gradeinfo->getGradeitem();
$grade = $gradeitem->get_final($userid);
if(empty($grade)){
if (empty($grade)) {
return completion::INCOMPLETE;
}
else if($grade->finalgrade === NULL)
{
// 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
// Therefor, we treat a NULL result as a reason to check the relevant gradingscanner for presence of pending items
else if ($grade->finalgrade === NULL) {
// 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.
// 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.
// Pending items are otherwise expressly indicated by the "pendingsubmission" field in the user model
if($gradeinfo->getGradingscanner()->pending($userid)){
// Pending items are otherwise expressly indicated by the "pendingsubmission" field in the user model.
if ($gradeinfo->getGradingscanner()->pending($userid)) {
return completion::PENDING;
} else {
return completion::INCOMPLETE;
@ -174,49 +194,44 @@ class core_aggregator extends \local_treestudyplan\aggregator {
}
else {
$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;
$scale = $gradeinfo->getScale();
if( isset($scale)){
$gradecfg = $DB->get_record($table,["scale_id"=>$scale->id]);
if ( isset($scale)) {
$gradecfg = $DB->get_record($table, ["scale_id"=>$scale->id]);
}
else if($gradeitem->grademin == 0)
{
$gradecfg = $DB->get_record($table,["grade_points"=>$gradeitem->grademax]);
else if ($gradeitem->grademin == 0) {
$gradecfg = $DB->get_record($table, ["grade_points"=>$gradeitem->grademax]);
}
else
else
{
$gradecfg = null;
}
// for point grades, a provided grade pass overrides the defaults in the gradeconfig
// for scales, the configuration in the gradeconfig is leading
// for point grades, a provided grade pass overrides the defaults in the gradeconfig.
// for scales, the configuration in the gradeconfig is leading.
if($gradecfg && (isset($scale) || $gradeitem->gradepass == 0))
{
// if so, we need to know if the grade is
if($finalgrade >= $gradecfg->min_completed){
// return completed if completed
if ($gradecfg && (isset($scale) || $gradeitem->gradepass == 0)) {
// if so, we need to know if the grade is .
if ($finalgrade >= $gradecfg->min_completed) {
// return completed if completed.
return completion::COMPLETED;
}
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
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 completion::FAILED;
}
else {
return completion::PROGRESS;
}
}
else if($gradeitem->gradepass > 0)
{
else if ($gradeitem->gradepass > 0) {
$range = floatval($gradeitem->grademax - $gradeitem->grademin);
// if no gradeconfig and gradepass is set, use that one to determine config.
if($finalgrade >= $gradeitem->gradepass){
if ($finalgrade >= $gradeitem->gradepass) {
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 completion::FAILED;
}
@ -225,18 +240,17 @@ class core_aggregator extends \local_treestudyplan\aggregator {
}
}
else {
// Blind assumptions if nothing is provided
// over 55% of range is completed
// if range >= 3 and failed is enabled, assume that this means failed
// Blind assumptions if nothing is provided.
// over 55% of range is completed.
// if range >= 3 and failed is enabled, assume that this means failed.
$g = floatval($finalgrade - $gradeitem->grademin);
$range = floatval($gradeitem->grademax - $gradeitem->grademin);
$score = $g / $range;
if($score > 0.55){
if ($score > 0.55) {
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 completion::FAILED;
}

View File

@ -1,4 +1,25 @@
<?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;
@ -11,24 +32,23 @@ class tristate_aggregator extends \local_treestudyplan\aggregator {
public const DEPRECATED = true;
private const DEFAULT_CONDITION = "50";
public function needSelectGradables(){ return True;}
public function needSelectGradables() { return True;}
public function isDeprecated() { return self::DEPRECATED;}
public function useRequiredGrades() { return False;}
public function useItemConditions() { return True;}
protected function aggregate_completion(array $a, $condition = "50") {
if(in_array($condition, ['ALL','67','50','ANY'])){
// condition is one of the valid conditions
if (in_array($condition, ['ALL', '67', '50', 'ANY'])) {
// condition is one of the valid conditions.
$c_completed = 0;
$c_excellent = 0;
$c_progress = 0;
$c_pending = 0;
$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_completed += ($c>=completion::COMPLETED)?1:0;
$c_excellent += ($c>=completion::EXCELLENT)?1:0;
@ -42,17 +62,17 @@ class tristate_aggregator extends \local_treestudyplan\aggregator {
'ANY' => 1,
][$condition];
if($c_excellent >= $required) {
if ($c_excellent >= $required) {
return completion::EXCELLENT;
} else if ($c_completed >= $required) {
return completion::COMPLETED;
} else {
// 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
if($c_progress > 0){
// 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.
if ($c_progress > 0) {
return completion::PROGRESS;
}
else if($c_pending > 0){
}
else if ($c_pending > 0) {
return completion::PENDING;
}
else {
@ -64,30 +84,30 @@ class tristate_aggregator extends \local_treestudyplan\aggregator {
return completion::INCOMPLETE;
}
}
else
else
{
// indeterminable, return null
// indeterminable, 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();
if(empty($condition)){
if (empty($condition)) {
$condition = self::DEFAULT_CONDITION;
}
$list = [];
foreach(gradeinfo::list_studyitem_gradables($studyitem) as $gi){
$list[] = $this->grade_completion($gi,$userid);
foreach (gradeinfo::list_studyitem_gradables($studyitem) as $gi) {
$list[] = $this->grade_completion($gi, $userid);
}
$completion = self::aggregate_completion($list,$condition);
$completion = self::aggregate_completion($list, $condition);
return $completion;
}
public function aggregate_junction(array $completion, studyitem $studyitem, $userid){
$completed = self::aggregate_completion($completion,$studyitem->conditions());
// if null result (conditions are unknown/null) - default to ALL
return isset($completed)?$completed:(self::aggregate_completion($completion,'ALL'));
public function aggregate_junction(array $completion, studyitem $studyitem, $userid) {
$completed = self::aggregate_completion($completion, $studyitem->conditions());
// if null result (conditions are unknown/null) - default to ALL.
return isset($completed)?$completed:(self::aggregate_completion($completion, 'ALL'));
}
public function grade_completion(gradeinfo $gradeinfo, $userid) {
@ -95,36 +115,34 @@ class tristate_aggregator extends \local_treestudyplan\aggregator {
$gradeitem = $gradeinfo->getGradeitem();
$grade = $gradeitem->get_final($userid);
if(empty($grade)){
if (empty($grade)) {
return completion::INCOMPLETE;
}
else if($grade->finalgrade === NULL)
{
// 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
// Therefor, we treat a NULL result as a reason to check the relevant gradingscanner for presence of pending items
else if ($grade->finalgrade === NULL) {
// 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.
// 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.
// Pending items are otherwise expressly indicated by the "pendingsubmission" field in the user model
if($gradeinfo->getGradingscanner()->pending($userid)){
// Pending items are otherwise expressly indicated by the "pendingsubmission" field in the user model.
if ($gradeinfo->getGradingscanner()->pending($userid)) {
return completion::PENDING;
} else {
return completion::INCOMPLETE;
}
}
else {
$finalgrade = $grade->finalgrade;
$scale = $gradeinfo->getScale();
if($gradeitem->gradepass > 0)
{
// Base completion off of gradepass (if set)
if($gradeitem->grademax > $gradeitem->gradepass && $finalgrade >= $gradeitem->grademax){
// If gradepass is configured
if ($gradeitem->gradepass > 0) {
// Base completion off of gradepass (if set).
if ($gradeitem->grademax > $gradeitem->gradepass && $finalgrade >= $gradeitem->grademax) {
// If gradepass is configured .
return completion::EXCELLENT;
}
else if($finalgrade >= $gradeitem->gradepass){
else if ($finalgrade >= $gradeitem->gradepass) {
return completion::COMPLETED;
}
else {
@ -132,17 +150,17 @@ class tristate_aggregator extends \local_treestudyplan\aggregator {
}
}
else {
// Blind assumptions:
// over 55% of range is completed
// over 85% of range is excellent
// Blind assumptions:.
// over 55% of range is completed.
// over 85% of range is excellent.
$g = floatval($finalgrade - $gradeitem->grademin);
$range = floatval($gradeitem->grademax - $gradeitem->grademin);
$score = $g / $range;
if($score > 0.85){
if ($score > 0.85) {
return completion::EXCELLENT;
}
else if($score > 0.55){
else if ($score > 0.55) {
return completion::COMPLETED;
}
else {

View File

@ -1,4 +1,26 @@
<?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;
use Exception;
@ -61,180 +83,176 @@ class gradegenerator {
"Praesent nec risus vestibulum quam venenatis tempor.",
"Nullam rhoncus ex a quam egestas, eu auctor enim lobortis.",
"Nam luctus ante id lacus scelerisque, quis blandit ante elementum.",
];
private function generatedfeedback(){
if(file_exists("/usr/games/fortune")){
// get a fortune if it is available
private function generatedfeedback() {
if (file_exists("/usr/games/fortune")) {
// get a fortune if it is available.
return shell_exec("/usr/games/fortune -n 160 -e disclaimer literature science pratchett wisdom education");
} else {
// get a random loremipsum string
return self::$loremipsum[rand(0,count(self::$loremipsum)-1)];
// get a random loremipsum string.
return self::$loremipsum[rand(0, count(self::$loremipsum)-1)];
}
}
public function __construct(){
public function __construct() {
}
public function addstudent(string $student){
if(!array_key_exists($student,$this->table)){
$this->table[$student] = [
"intelligence" => rand(70,100),
"endurance" => rand(70,100),
public function addstudent(string $student) {
if (!array_key_exists($student, $this->table)) {
$this->table[$student] = [
"intelligence" => rand(70, 100),
"endurance" => rand(70, 100),
"skills" => [],
];
}
}
public function addskill(string $student, string $skill){
public function addskill(string $student, string $skill) {
$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"];
$end = $this->table[$student]["endurance"];
$this->table[$student]["skills"][$skill] = [
"intelligence" => min(100, $int + rand(-30,30)),
"endurance" => min(100, $end + rand(-10,10)),
$this->table[$student]["skills"][$skill] = [
"intelligence" => min(100, $int + rand(-30, 30)),
"endurance" => min(100, $end + rand(-10, 10)),
];
}
}
// Below is mostly just for reference in the json file
public function addUserNameInfo(string $student, $firstname,$lastname){
// Below is mostly just for reference in the json file.
public function addUserNameInfo(string $student, $firstname, $lastname) {
$this->addstudent($student);
$this->table[$student]["firstname"] = $firstname;
$this->table[$student]["lastname"] = $lastname;
}
public function generateraw($student,$skill, $count )
{
$this->addskill($student,$skill);
public function generateraw($student, $skill, $count ) {
$this->addskill($student, $skill);
$int = $this->table[$student]["skills"][$skill]["intelligence"];
$end = $this->table[$student]["skills"][$skill]["endurance"];
$results = [];
$gaveup = false;
for($i=0; $i < $count; $i++){
for($i=0; $i < $count; $i++) {
$r = new \stdClass;
if($gaveup) {
if ($gaveup) {
$r->done = !$gaveup;
} 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){
$score = rand(0,$int) ;
$r->result = ($score > 20); // determine if the assignment was successful
if(!$r->result){
$r->failed = !($score > 10);
if ($r->done) {
$score = rand(0, $int) ;
$r->result = ($score > 20); // determine if the assignment was successful.
if (!$r->result) {
$r->failed = !($score > 10);
}
} 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;
}
// Aways generate a little feedback
// Aways generate a little feedback.
$r->fb = $this->generatedfeedback();
$results[] = $r;
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
$gaveup = (rand(0,$end) < 15);
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.
$gaveup = (rand(0, $end) < 15);
}
}
return $results;
}
public function generate($student,$skill, array $gradeinfos ){
public function generate($student, $skill, array $gradeinfos ) {
global $DB;
$table ="local_treestudyplan_gradecfg";
$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];
$gi = $g->getGradeitem();
$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();
if( isset($scale)){
$gradecfg = $DB->get_record($table,["scale_id"=>$scale->id]);
if ( isset($scale)) {
$gradecfg = $DB->get_record($table, ["scale_id"=>$scale->id]);
}
else if($gi->grademin == 0)
{
$gradecfg = $DB->get_record($table,["grade_points"=>$gi->grademax]);
else if ($gi->grademin == 0) {
$gradecfg = $DB->get_record($table, ["grade_points"=>$gi->grademax]);
}
else
else
{
$gradecfg = null;
}
// next generate the grade
if($gradecfg)
{
if(!$gr->done){
// INCOMPLETE
// 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";
// next generate the grade.
if ($gradecfg) {
if (!$gr->done) {
// INCOMPLETE.
// 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";.
$r = (object)["gi" => $g, "grade" => $grade, "fb" =>"" ];
}
else if(!$gr->result){
else if (!$gr->result) {
$grade = rand($gradecfg->min_progress, $gradecfg->min_completed -1 );
$r = (object)["gi" => $g, "grade" => $grade, "fb" =>$gr->fb ];
}
else{
// COMPLETED
// COMPLETED.
$r = (object)["gi" => $g, "grade" => rand( $gradecfg->min_completed, $gi->grademax ), "fb" =>$gr->fb ];
}
$r->gradetext = $r->grade;
if( isset($scale)){
if ( isset($scale)) {
$scaleitems = $scale->load_items();
if($r->grade > 0){
if ($r->grade > 0) {
$r->gradetext = trim($scale->get_nearest_item($r->grade));
} else {
$r->gradetext = "-";
}
}
}
else if($gi->gradepass > 0)
{
if(!$gr->done){
// INCOMPLETe or FAILED
else if ($gi->gradepass > 0) {
if (!$gr->done) {
// INCOMPLETe or FAILED.
$grade = rand(0, $gi->gradepass/2);
$r = (object)["gi" => $g, "grade" => $grade, "fb" =>($grade > 0)?$gr->fb:"" ];
}
else if(!$gr->result){
//PROGRESS
else if (!$gr->result) {
//PROGRESS.
$r = (object)["gi" => $g, "grade" => rand( round($gi->gradepass/2), $gi->gradepass -1 ), "fb" =>$gr->fb ];
}
else{
// COMPLETED
// COMPLETED.
$r = (object)["gi" => $g, "grade" => rand( $gi->gradepass, $gi->grademax ), "fb" =>$gr->fb ];
}
$r->gradetext = $r->grade;
}
else {
// Blind assumptions if nothing is provided
// over 55% of range is completed
// under 35% is not done
// Blind assumptions if nothing is provided.
// over 55% of range is completed.
// under 35% is not done.
$range = floatval($gi->grademax - $gi->grademin);
if(!$gr->done){
// INCOMPLETe or FAILED
if (!$gr->done) {
// INCOMPLETe or FAILED.
$grade = rand(0, round($range * 0.35) - 1);
$r = (object)["gi" => $g, "grade" => $gi->grademin+$grade, "fb" =>($grade > 0)?$gr->fb:"" ];
}
else if(!$gr->result){
//PROGRESS
$r = (object)["gi" => $g, "grade" => $gi->grademin+rand(round($range * 0.35),round($range * 0.55) - 1 ), "fb" =>$gr->fb ];
else if (!$gr->result) {
//PROGRESS.
$r = (object)["gi" => $g, "grade" => $gi->grademin+rand(round($range * 0.35), round($range * 0.55) - 1 ), "fb" =>$gr->fb ];
}
else{
// COMPLETED
// COMPLETED.
$r = (object)["gi" => $g, "grade" => $gi->grademin+rand(round($range * 0.55) , $range ), "fb" =>$gr->fb ];
}
@ -242,50 +260,49 @@ class gradegenerator {
}
$rlist[] = $r;
}
return $rlist;
}
public function getstats($student){
public function getstats($student) {
return $this->table[$student];
}
public function serialize(): ?string{
return json_encode([
"table" => $this->table],JSON_PRETTY_PRINT);
"table" => $this->table], JSON_PRETTY_PRINT);
}
public function unserialize(string $data): void {
$o = json_decode($data,true);
$o = json_decode($data, true);
$this->table = $o["table"];
}
public function toFile(string $filename){
public function toFile(string $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);
if(file_exists($filename)){
if (file_exists($filename)) {
try{
$json = file_get_contents($filename);
$this->unserialize($json);
} catch(Exception $x){
} catch(Exception $x) {
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) {
$info = posix_getpwuid(posix_getuid());
$path = str_replace('~', $info['dir'], $path);
}
return $path;
}

View File

@ -1,49 +1,67 @@
<?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;
use DateTime;
class debugger {
private $fname;
private $tag;
private $enabled = false;
public function __construct($filename,$tag)
{
public function __construct($filename, $tag) {
global $CFG;
$this->fname = $filename;
$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);
}
public function dump($object,string $tag="")
{
if(strlen($tag) > 0){
public function dump($object, string $tag="") {
if (strlen($tag) > 0) {
$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);
}
private function writeblock($str){
if($this->enabled){
private function writeblock($str) {
if ($this->enabled) {
$now = new DateTime();
$tagline = "[ {$this->tag} - ".$now->format("c")." ]";
$fp = fopen($this->fname,"a");
fwrite($fp,$tagline . ":\n".$str."\n");
$fp = fopen($this->fname, "a");
fwrite($fp, $tagline . ":\n".$str."\n");
fclose($fp);
}
}

View File

@ -1,4 +1,25 @@
<?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;
require_once($CFG->dirroot.'/webservice/lib.php');
@ -10,28 +31,28 @@ class webservicehelper {
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
* @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.
* @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();
}
if(is_array($capability)){
//TODO: replace this by accesslib function \has_any_capability()
foreach($capability as $cap){
if(has_capability($cap,$context)){
if (is_array($capability)) {
//TODO: replace this by accesslib function \has_any_capability().
foreach ($capability as $cap) {
if (has_capability($cap, $context)) {
return true;
}
}
}
elseif(has_capability($capability,$context)){
else if (has_capability($capability, $context)) {
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
* @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
* @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 = [];
// initialize parent if needed
if($parent == null){
// initialize parent if needed.
if ($parent == null) {
$parent = \core_course_category::user_top();
if(has_capability($capability,$parent->get_context())){
if (has_capability($capability, $parent->get_context())) {
$list[] = $parent;
}
}
$children = $parent->get_children();
// 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
// Stage one (surface check): check all children for the capability
foreach($children as $child){
// Check if we should add this category
if(has_capability($capability,$child->get_context())){
// 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.
// Stage one (surface check): check all children for the capability.
foreach ($children as $child) {
// Check if we should add this category.
if (has_capability($capability, $child->get_context())) {
return true;
}
}
}
// Stage two (deep dive): recurse into the child categories
foreach($children as $child){
if($child->get_children_count() > 0){
if(self::has_capability_in_any_category($capability,$child)){
// Stage two (deep dive): recurse into the child categories.
foreach ($children as $child) {
if ($child->get_children_count() > 0) {
if (self::has_capability_in_any_category($capability, $child)) {
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 \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
* @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){
if($validate) {
\external_api::validate_context($context);
public static function require_capabilities($capability, $context=null, $validate=true) {
if ($validate) {
\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()})");
}
}
@ -98,32 +119,32 @@ class webservicehelper {
* @throws \InvalidArgumentException When the context is not found
*/
public static function find_context($contextid): \context{
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 (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...
try{
$context = \context::instance_by_id($contextid);
}
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
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.
}
// Validate the found context
// Validate the found context.
\external_api::validate_context($context);
self::$validated_contexts[$contextid] = $context;
}
return self::$validated_contexts[$contextid];
}
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 \context_system The system context, validated to use as this context
*/
public static function system_context(): \context_system {
if(!isset(static::$systemcontext)){
if (!isset(static::$systemcontext)) {
static::$systemcontext = \context_system::instance();
\external_api::validate_context(static::$systemcontext);
}

View File

@ -1,66 +1,86 @@
<?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;
class assign_scanner extends scanner_base {
protected function get_ungraded_submissions(){
protected function get_ungraded_submissions() {
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
FROM {assign_submission} asgn_sub
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
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.userid > 0
AND asgn_sub.userid > 0
AND a.grade <> 0 AND (ag.id IS NULL OR asgn_sub.timemodified >= ag.timemodified)
";
return $DB->get_fieldset_sql($sql);
}
protected function get_graded_users(){
protected function get_graded_users() {
global $DB;
$sql = "SELECT DISTINCT g.userid
FROM {grade_grades} g
LEFT JOIN {grade_items} gi on g.itemid = gi.id
$sql = "SELECT DISTINCT g.userid
FROM {grade_grades} g
LEFT JOIN {grade_items} gi on g.itemid = gi.id
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);
}
public function count_ungraded($course_userids=[]){
public function count_ungraded($course_userids=[]) {
$ungraded = $this->get_ungraded_submissions();
if(count($course_userids) > 0){
$ungraded = array_intersect($ungraded,$course_userids);
if (count($course_userids) > 0) {
$ungraded = array_intersect($ungraded, $course_userids);
}
return count($ungraded);
}
public function count_graded($course_userids=[]){
public function count_graded($course_userids=[]) {
$ungraded = $this->get_ungraded_submissions();
$graded = $this->get_graded_users();
if(count($course_userids) > 0){
$ungraded = array_intersect($ungraded,$course_userids);
$graded = array_intersect($graded,$course_userids);
if (count($course_userids) > 0) {
$ungraded = array_intersect($ungraded, $course_userids);
$graded = array_intersect($graded, $course_userids);
}
// determine how many id's have a grade, but also an ungraded submission
$dual = array_intersect($ungraded,$graded);
// subtract those from the graded count
// determine how many id's have a grade, but also an ungraded submission.
$dual = array_intersect($ungraded, $graded);
// subtract those from the graded count.
return count($graded) - count($dual);
}
public function has_ungraded_submission($userid)
{
public function has_ungraded_submission($userid) {
$ungraded = $this->get_ungraded_submissions();
return in_array($userid,$ungraded);
return in_array($userid, $ungraded);
}
}

View File

@ -1,17 +1,38 @@
<?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;
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 {
protected function get_ungraded_submissions(){
// count all users who have one or more questions that still need grading
protected function get_ungraded_submissions() {
// count all users who have one or more questions that still need grading.
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
FROM {question_attempt_steps} qas
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);
$submissions = [];
foreach($rs as $r){
// Now, check if
foreach ($rs as $r) {
// Now, check if .
$maxstate_sql = "SELECT MAX(qas.sequencenumber) FROM {question_attempt_steps} qas WHERE qas.questionattemptid = {$r->attempt_id}";
$max = $DB->get_field_sql($maxstate_sql);
if($r->sequencenumber == $max){
$submissions[$r->userid] = true; // set array index based on user id, to avoid checking if value is in array
if ($r->sequencenumber == $max) {
$submissions[$r->userid] = true; // set array index based on user id, to avoid checking if value is in array.
}
}
$rs->close();
return array_keys($submissions);
}
public function count_ungraded($course_userids=[]){
public function count_ungraded($course_userids=[]) {
$ungraded = $this->get_ungraded_submissions();
if(count($course_userids) > 0){
$ungraded = array_intersect($ungraded,$course_userids);
if (count($course_userids) > 0) {
$ungraded = array_intersect($ungraded, $course_userids);
}
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.
global $DB;
$sql = "SELECT DISTINCT g.userid
FROM {grade_grades} g
LEFT JOIN {grade_items} gi on g.itemid = gi.id
$sql = "SELECT DISTINCT g.userid
FROM {grade_grades} g
LEFT JOIN {grade_items} gi on g.itemid = gi.id
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);
if(count($course_userids) > 0){
$graded = array_intersect($graded,$course_userids);
if (count($course_userids) > 0) {
$graded = array_intersect($graded, $course_userids);
}
return count($graded);
}
public function has_ungraded_submission($userid)
{
public function has_ungraded_submission($userid) {
$ungraded = $this->get_ungraded_submissions();
return in_array($userid,$ungraded);
return in_array($userid, $ungraded);
}
}

View File

@ -1,4 +1,25 @@
<?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;
use \grade_item;
@ -6,7 +27,7 @@ use \grade_item;
abstract class scanner_base {
protected $gi;
public function __construct(grade_item $gi){
public function __construct(grade_item $gi) {
$this->gi = $gi;
}

View File

@ -1,4 +1,25 @@
<?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;
require_once($CFG->libdir.'/externallib.php');
@ -9,71 +30,71 @@ class period {
private static $PAGECACHE = [];
private $r; // Holds database record
private $r; // Holds database record.
private $id;
private $page;
public function aggregator(){
public function aggregator() {
return $this->studyplan->aggregator();
}
// Cache constructors to avoid multiple creation events in one session.
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);
}
}
return self::$CACHE[$id];
}
/**
* 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;
if($periodnr < 1){
// Clamp period index
if ($periodnr < 1) {
// Clamp period index .
$periodnr = 1;
}
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);
} catch(\dml_missing_record_exception $x){
} catch(\dml_missing_record_exception $x) {
// Period does not exist - create one ...
// 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
// 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 .
$pcount = $page->periods();
$ystart = $page->startdate()->getTimestamp();
$yend = $page->enddate()->getTimestamp();
// Estimate the period's timing to make a reasonable first guess
$ydelta = $yend - $ystart;
// Estimate the period's timing to make a reasonable first guess.
$ydelta = $yend - $ystart;
$ptime = $ydelta / $pcount;
try{
// 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);
$pstart = strtotime($startdate)+(24*60*60); // Add one day
} catch(\dml_missing_record_exception $x2){
// If not, do a fair guess
// 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);
$pstart = strtotime($startdate)+(24*60*60); // Add one day.
} catch(\dml_missing_record_exception $x2) {
// If not, do a fair guess.
$pstart = $ystart + (($periodnr-1)*$ptime);
}
try{
// 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);
$pstart = strtotime($enddate)-(24*60*60); // subtract one day
} catch(\dml_missing_record_exception $x2){
// If not, do a fair guess
// 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);
$pstart = strtotime($enddate)-(24*60*60); // subtract one day.
} catch(\dml_missing_record_exception $x2) {
// If not, do a fair guess.
$pend = $pstart + $ptime;
}
// And create the period
// And create the period.
$period = self::add([
'page_id' => $page->id(),
'period' => $periodnr,
'fullname' => \get_string("period_default_fullname","local_treestudyplan",$periodnr),
'shortname' => \get_string("period_default_shortname","local_treestudyplan",$periodnr),
'startdate' => date("Y-m-d",$pstart),
'enddate' => date("Y-m-d",$pend),
'fullname' => \get_string("period_default_fullname", "local_treestudyplan", $periodnr),
'shortname' => \get_string("period_default_shortname", "local_treestudyplan", $periodnr),
'startdate' => date("Y-m-d", $pstart),
'enddate' => date("Y-m-d", $pend),
]);
}
return $period;
@ -81,26 +102,26 @@ class period {
// Cache constructors to avoid multiple creation events in one session.
public static function findForPage(studyplanpage $page): array {
if(!array_key_exists($page->id(),self::$PAGECACHE)){
if (!array_key_exists($page->id(), self::$PAGECACHE)) {
$periods = [];
// find and add the periods to an array with the period sequence as a key
for($i=1; $i <= $page->periods(); $i++){
$period = self::find($page,$i);
// find and add the periods to an array with the period sequence as a key.
for($i=1; $i <= $page->periods(); $i++) {
$period = self::find($page, $i);
$periods[$i] = $period;
}
self::$PAGECACHE[$page->id()] = $periods;
}
}
return self::$PAGECACHE[$page->id()];
}
private function __construct($id) {
global $DB;
$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);
}
public function id(){
public function id() {
return $this->id;
}
@ -108,37 +129,37 @@ class period {
return $this->page->studyplan();
}
public function page(){
public function page() {
return $this->page;
}
public function shortname(){
public function shortname() {
return $this->r->shortname;
}
public function fullname(){
public function fullname() {
return $this->r->fullname;
}
public function period(){
public function period() {
return $this->r->period;
}
public function startdate(){
public function startdate() {
return new \DateTime($this->r->startdate);
}
public function enddate(){
if($this->r->enddate && strlen($this->r->enddate) > 0){
public function enddate() {
if ($this->r->enddate && strlen($this->r->enddate) > 0) {
return new \DateTime($this->r->enddate);
}
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"));
}
}
public static function structure($value=VALUE_REQUIRED){
public static function structure($value=VALUE_REQUIRED) {
return new \external_single_structure([
"id" => new \external_value(PARAM_INT, 'id 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'),
"startdate" => new \external_value(PARAM_TEXT, 'start 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 [
'id' => $this->r->id,
'fullname' => $this->r->fullname,
@ -164,62 +185,62 @@ class period {
* Do not use directly to add periods, unless performing import
* The static find() and findForPage() functions create the period if needed
*/
public static function add($fields){
public static function add($fields) {
global $DB;
if(!isset($fields['page_id'])){
if (!isset($fields['page_id'])) {
throw new \InvalidArgumentException("parameter 'page_id' missing");
}
if(!isset($fields['period'])){
if (!isset($fields['period'])) {
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");
}
$addable = ['page_id','fullname','shortname','period','startdate','enddate'];
$addable = ['page_id', 'fullname', 'shortname', 'period', 'startdate', 'enddate'];
$info = [ ];
foreach($addable as $f){
if(array_key_exists($f,$fields)){
foreach ($addable as $f) {
if (array_key_exists($f, $fields)) {
$info[$f] = $fields[$f];
}
}
$id = $DB->insert_record(self::TABLE, $info);
unset(self::$PAGECACHE[$fields['page_id']]); // invalidate the cache for this page
return self::findById($id); // make sure the new page is immediately cached
unset(self::$PAGECACHE[$fields['page_id']]); // invalidate the cache for this page.
return self::findById($id); // make sure the new page is immediately cached.
}
public function edit($fields){
public function edit($fields) {
global $DB;
$editable = ['fullname','shortname','startdate','enddate'];
$info = ['id' => $this->id,];
foreach($editable as $f){
if(array_key_exists($f,$fields)){
$editable = ['fullname', 'shortname', 'startdate', 'enddate'];
$info = ['id' => $this->id, ];
foreach ($editable as $f) {
if (array_key_exists($f, $fields)) {
$info[$f] = $fields[$f];
}
}
$DB->update_record(self::TABLE, $info);
//reload record after edit
$this->r = $DB->get_record(self::TABLE,['id' => $this->id],"*",MUST_EXIST);
unset(self::$PAGECACHE[$this->r->page_id]); // invalidate the cache for this page
//reload record after edit.
$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.
return $this;
}
public function delete(){
public function delete() {
global $DB;
$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();
}
public static function page_structure($value=VALUE_REQUIRED){
return new \external_multiple_structure(self::structure(),"The periods in the page",$value);
public static function page_structure($value=VALUE_REQUIRED) {
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 = [];
foreach(self::findForPage($page) as $p){
foreach (self::findForPage($page) as $p) {
$model[] = $p->model();
}
return $model;

View File

@ -1,4 +1,25 @@
<?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;
use core_privacy\local\metadata\collection;
@ -12,7 +33,7 @@ use \core_privacy\local\request\helper;
use \core_privacy\local\request\transform;
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\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 {
$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
INNER JOIN {local_treestudyplan_user} a ON a.studyplan_id = s.id
WHERE ( a.user_id = :userid )
";
$contextlist->add_from_sql($sql, ['userid' => $userid]);
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.
*/
public static function export_user_data(approved_contextlist $contextlist){
public static function export_user_data(approved_contextlist $contextlist) {
global $DB;
foreach ($contextlist->get_contexts() as $context) {
$user = $contextlist->get_user();
if($context instanceof \context_system){
// Export invitations
if ($context instanceof \context_system) {
// Export invitations.
$sql = "SELECT * FROM {local_treestudyplan_invit} i
WHERE ( aiuser_id = :userid )
";
$records = $DB->get_records_sql($sql,["userid" => $user->id]);
foreach($records as $r) {
$records = $DB->get_records_sql($sql, ["userid" => $user->id]);
foreach ($records as $r) {
static::export_invitation_data_for_user($r);
}
// Export empty associations
// Export empty associations.
$sql = "SELECT * FROM {local_treestudyplan} s
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)
";
$records = $DB->get_records_sql($sql,["userid" => $user->id, "contextid" => $context->id]);
foreach($records as $r) {
$records = $DB->get_records_sql($sql, ["userid" => $user->id, "contextid" => $context->id]);
foreach ($records as $r) {
static::export_studyplan_data_for_user($r);
}
} else if ($context->contextlevel == CONTEXT_COURSECAT){
// Export studyplan associations
}
} else if ($context->contextlevel == CONTEXT_COURSECAT) {
// Export studyplan associations.
$sql = "SELECT * FROM {local_treestudyplan} s
INNER JOIN {local_treestudyplan_user} a ON a.studyplan_id = s.id
WHERE ( a.user_id = :userid AND s.context_id = :contextid)
";
$records = $DB->get_records_sql($sql,["userid" => $user->id, "contextid" => $context->id]);
foreach($records as $r) {
$records = $DB->get_records_sql($sql, ["userid" => $user->id, "contextid" => $context->id]);
foreach ($records as $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.
*/
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;
// find studyplans in context
if($context->contextlevel == CONTEXT_COURSECAT){
// find studyplans in context.
if ($context->contextlevel == CONTEXT_COURSECAT) {
$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]);
foreach($plan_ids as $plan_id){
$DB->delete_records("local_treestudyplan_user",["studyplan_id" => $plan_id]);
$plan_ids = $DB->get_fieldset_sql($sql, ["contextid"=>$context->id]);
foreach ($plan_ids as $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.
*/
public static function delete_data_for_user(approved_contextlist $contextlist){
public static function delete_data_for_user(approved_contextlist $contextlist) {
global $DB;
$user = $contextlist->get_user();
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
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]);
foreach($plan_ids as $plan_id){
$DB->delete_records("local_treestudyplan_user",["studyplan_id" => $plan_id,"user_id" => $user->id]);
$plan_ids = $DB->get_fieldset_sql($sql, ["contextid"=>$context->id, "userid" => $user->id]);
foreach ($plan_ids as $plan_id) {
$DB->delete_records("local_treestudyplan_user", ["studyplan_id" => $plan_id, "user_id" => $user->id]);
}
// Also delete all invitations for this user
$DB->delete_records("local_treestudyplan_invit",["user_id" => $user->id]);
// Also delete all invitations for this user.
$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
WHERE ( a.user_id = :userid AND s.context_id = :contextid)";
$plan_ids = $DB->get_fieldset_sql($sql,["contextid"=>$context->id, "userid" => $user->id]);
foreach($plan_ids as $plan_id){
$DB->delete_records("local_treestudyplan_user",["studyplan_id" => $plan_id,"user_id" => $user->id]);
$plan_ids = $DB->get_fieldset_sql($sql, ["contextid"=>$context->id, "userid" => $user->id]);
foreach ($plan_ids as $plan_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.
*/
public static function get_users_in_context(userlist $userlist){
public static function get_users_in_context(userlist $userlist) {
$context = $userlist->get_context();
if ($context instanceof \context_system) {
// Add all invitations
// Add all invitations.
$sql = "SELECT i.user_id as userid
FROM {local_treestudyplan_invit} i;";
$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
INNER JOIN {local_treestudyplan} s ON a.studyplan_id = s.id
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
INNER JOIN {local_treestudyplan} s ON a.studyplan_id = s.id
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.
*/
public static function delete_data_for_users(approved_userlist $userlist){
public static function delete_data_for_users(approved_userlist $userlist) {
global $DB;
$context = $userlist->get_context();
$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 = [];
if($context->contextlevel == CONTEXT_SYSTEM){
// Determine the relevant plan_ids for this context
$sql = "SELECT s.id FROM {local_treestudyplan}
if ($context->contextlevel == CONTEXT_SYSTEM) {
// Determine the relevant plan_ids for this context.
$sql = "SELECT s.id FROM {local_treestudyplan}
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,]);
// 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);
$plan_ids = $DB->get_fieldset_sql($sql, ["contextid"=>$context->id, ]);
// If plan ids not empty, they will be processed later.
} else if ($context->contextlevel == CONTEXT_COURSECAT){
$sql = "SELECT s.id FROM {local_treestudyplan}
// 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) {
$sql = "SELECT s.id FROM {local_treestudyplan}
WHERE (s.context_id = :contextid)";
$plan_ids = $DB->get_fieldset_sql($sql,["contextid"=>$context->id,]);
// If plan ids not empty, they will be processed later
$plan_ids = $DB->get_fieldset_sql($sql, ["contextid"=>$context->id, ]);
// If plan ids not empty, they will be processed later.
}
// Now delete the studyplan associations if relevant
if(count($plan_ids) >0 && count($users) >0){
// Now delete the studyplan associations if relevant.
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;
$sql = "user_id {$userinsql} and studyplan_id {$planinsql}";
$DB->delete_records_select('local_treestudyplan_user', $sql, $params);

View File

@ -1,16 +1,37 @@
<?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->dirroot/local/treestudyplan/lib.php");
class reportinvite_form extends moodleform {
//Add elements to form
const GOALS_EDITOR_OPTIONS = array('trusttext'=>true, 'subdirs'=>true, 'maxfiles'=>0,'maxbytes'=>5*1024*1025);
//Add elements to form.
const GOALS_EDITOR_OPTIONS = array('trusttext'=>true, 'subdirs'=>true, 'maxfiles'=>0, 'maxbytes'=>5*1024*1025);
public function definition() {
global $CFG;
// 'code', 'revision', 'description', 'goals', 'complexity', 'points', 'studyhours'
$mform = $this->_form; // Don't forget the underscore!
// 'code', 'revision', 'description', 'goals', 'complexity', 'points', 'studyhours'.
$mform = $this->_form; // Don't forget the underscore! .
$mform->addElement('hidden', 'add', 0);
$mform->setType('add', PARAM_ALPHANUM);
@ -18,25 +39,25 @@ class reportinvite_form extends moodleform {
$mform->addElement('hidden', 'update', 0);
$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_edit', get_string('invite_desc_edit','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('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->setDefault('name', ''); //Default value
$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->setDefault('name', ''); //Default value.
$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->setType('email', PARAM_NOTAGS); //Set type of element
$mform->setDefault('email', ''); //Default value
$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->setDefault('email', ''); //Default value.
$mform->addRule('email', get_string('required'), 'required', 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();
}
//Custom validation should be added here
//Custom validation should be added here.
function validation($data, $files) {
return array();
}
@ -48,17 +69,17 @@ class reportinvite_form extends moodleform {
function get_data()
{
global $DB,$USER;
global $DB, $USER;
$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;
}
if(empty($data->update))
if (empty($data->update))
{
$date = new DateTime("now", core_date::get_user_timezone_object());
$date->setTime(0, 0, 0);
@ -66,9 +87,9 @@ class reportinvite_form extends moodleform {
$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 {
$length = 20;
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
@ -78,7 +99,7 @@ class reportinvite_form extends moodleform {
$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]));
$data->invitekey = $randomkey;

View File

@ -1,4 +1,25 @@
<?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;
require_once($CFG->libdir.'/externallib.php');
@ -6,7 +27,7 @@ use \local_treestudyplan\local\helpers\webservicehelper;
require_once($CFG->libdir.'/badgeslib.php');
class studentstudyplanservice extends \external_api
class studentstudyplanservice extends \external_api
{
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([
"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(
studyplan::simple_structure()
);
}
private static function list_user_studyplans($userid){
private static function list_user_studyplans($userid) {
global $CFG, $DB;
$list = [];
$studyplans = studyplan::find_for_user($userid);
foreach($studyplans as $studyplan)
{
// only include studyplans in the context the user has permissions for
if(webservicehelper::has_capabilities(self::CAP_VIEWOTHER,$studyplan->context(),false)){
foreach ($studyplans as $studyplan) {
// only include studyplans in the context the user has permissions for.
if (webservicehelper::has_capabilities(self::CAP_VIEWOTHER, $studyplan->context(), false)) {
$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( [
"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(
studyplan::user_structure()
);
}
public static function get_user_studyplans($userid)
{
public static function get_user_studyplans($userid) {
global $CFG, $DB;
$studyplans = studyplan::find_for_user($userid);
$map = [];
foreach($studyplans as $studyplan)
{
// only include studyplans in the context the user has permissions for
if(webservicehelper::has_capabilities(self::CAP_VIEWOTHER,$studyplan->context(),false)){
$map = [];
foreach ($studyplans as $studyplan) {
// only include studyplans in the context the user has permissions for.
if (webservicehelper::has_capabilities(self::CAP_VIEWOTHER, $studyplan->context(), false)) {
$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( [
"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();
}
public static function get_user_studyplan($userid,$studyplanid)
{
public static function get_user_studyplan($userid, $studyplanid) {
global $CFG, $DB;
$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);
}
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( [
"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(
studyplan::user_structure()
);
}
public static function get_invited_studyplan($invitekey)
{
public static function get_invited_studyplan($invitekey) {
global $CFG, $DB;
$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 [];
}
// Validate context now
// Validate context now.
\external_api::validate_context(\context_system::instance());
$userid = $invite->user_id;
$map = [];
$studyplans = studyplan::find_for_user($userid);
foreach($studyplans as $studyplan)
{
foreach ($studyplans as $studyplan) {
$map[] = $studyplan->user_model($userid);
}
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([]);
}
public static function list_own_studyplans_returns()
public static function list_own_studyplans_returns()
{
return new \external_multiple_structure(
studyplan::simple_structure()
);
}
private static function list_own_studyplans(){
private static function list_own_studyplans() {
global $CFG, $DB, $USER;
$userid = $USER->id;
$list = [];
$studyplans = studyplan::find_for_user($userid);
foreach($studyplans as $studyplan)
{
foreach ($studyplans as $studyplan) {
$list[] =$studyplan->simple_model();
}
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( [
"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(
studyplan::user_structure()
);
}
public static function get_own_studyplan($id=null)
{
public static function get_own_studyplan($id=null) {
global $USER;
// Validate this call in the system context.
@ -219,8 +232,8 @@ class studentstudyplanservice extends \external_api
$studyplans = studyplan::find_for_user($userid);
if(isset($id) && $id > 0){
if(isset($studyplans[$id])){
if (isset($id) && $id > 0) {
if (isset($studyplans[$id])) {
$studyplan = $studyplans[$id];
return [$studyplan->user_model($userid)];
} else {
@ -229,7 +242,7 @@ class studentstudyplanservice extends \external_api
}
else {
$map = [];
foreach($studyplans as $studyplan){
foreach ($studyplans as $studyplan) {
$map[] = $studyplan->user_model($userid);
}
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( [
"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(
studyplan::editor_structure()
);
}
public static function get_teaching_studyplans($id=null)
{
public static function get_teaching_studyplans($id=null) {
global $CFG, $DB, $USER;
$userid = $USER->id;
\external_api::validate_context(\context_system::instance());
\external_api::validate_context(\context_system::instance());
$studyplans = teachingfinder::list_my_plans();
if(isset($id) && $id > 0){
if(isset($studyplans[$id])){
if (isset($id) && $id > 0) {
if (isset($studyplans[$id])) {
$studyplan = $studyplans[$id];
return [$studyplan->editor_model($userid)];
} else {
@ -274,7 +286,7 @@ class studentstudyplanservice extends \external_api
}
else {
$map = [];
foreach($studyplans as $studyplan){
foreach ($studyplans as $studyplan) {
$map[] = $studyplan->editor_model($userid);
}
return $map;

View File

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

View File

@ -1,4 +1,25 @@
<?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;
require_once($CFG->libdir.'/externallib.php');
@ -10,96 +31,92 @@ class studyitemconnection {
private $id;
protected function __construct($r){
protected function __construct($r) {
$this->r = $r;
$this->id = $r->id;
}
public static function structure($value=VALUE_REQUIRED){
public static function structure($value=VALUE_REQUIRED) {
return new \external_single_structure([
'id' => new \external_value(PARAM_INT, 'id of connection'),
'from_id' => new \external_value(PARAM_INT, 'id of start 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];
}
public function from_item(){
public function from_item() {
return studyitem::findById($this->r->from_id);
}
public function to_item(){
public function to_item() {
return studyitem::findById($this->r->to_id);
}
public function from_id(){
public function from_id() {
return $this->r->from_id;
}
public function to_id(){
public function to_id() {
return $this->r->to_id;
}
public static function find_outgoing($item_id){
public static function find_outgoing($item_id) {
global $DB;
$list = [];
$conn_out = $DB->get_records(self::TABLE,['from_id' => $item_id]);
foreach($conn_out as $c) {
$conn_out = $DB->get_records(self::TABLE, ['from_id' => $item_id]);
foreach ($conn_out as $c) {
$list[] = new self($c);
}
return $list;
}
public static function find_incoming($item_id){
public static function find_incoming($item_id) {
global $DB;
$list = [];
$conn_in = $DB->get_records(self::TABLE,['to_id' => $item_id]);
foreach($conn_in as $c) {
$conn_in = $DB->get_records(self::TABLE, ['to_id' => $item_id]);
foreach ($conn_in as $c) {
$list[] = new self($c);
}
return $list;
}
public static function connect($from_id,$to_id)
{
public static function connect($from_id, $to_id) {
global $DB;
//check if link already exists
if(!$DB->record_exists(self::TABLE, ['from_id' => $from_id, 'to_id' => $to_id]))
{
//check if link already exists.
if (!$DB->record_exists(self::TABLE, ['from_id' => $from_id, 'to_id' => $to_id])) {
$id = $DB->insert_record(self::TABLE, [
'from_id' => $from_id,
'from_id' => $from_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 {
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;
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, [
'from_id' => $from_id,
'from_id' => $from_id,
'to_id' => $to_id,
]);
return success::success('Items Disconnected');
} else {
return success::success('Connection does not exist');
}
}
}
public static function clear($id) {
global $DB;

View File

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

View File

@ -1,4 +1,25 @@
<?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;
require_once($CFG->libdir.'/externallib.php');
@ -10,50 +31,50 @@ class studyplan {
private static $STUDYPLAN_CACHE = [];
private $r; // Holds database record
private $r; // Holds database record.
private $id;
private $aggregator;
private $context = null; // Hold context object once retrieved
private $linked_userids = null; // cache lookup of linked users (saves queries)
private $context = null; // Hold context object once retrieved.
private $linked_userids = null; // cache lookup of linked users (saves queries).
private $page_cache = null;
public function aggregator(){
public function aggregator() {
return $this->aggregator;
}
// Cache constructors to avoid multiple creation events in one session.
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);
}
}
return self::$STUDYPLAN_CACHE[$id];
}
private function __construct($id) {
global $DB;
$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);
}
public function id(){
public function id() {
return $this->id;
}
public function shortname(){
public function shortname() {
return $this->r->shortname;
}
public function name(){
public function name() {
return $this->r->name;
}
public function pages(){
public function pages() {
// cached version of find_studyplan_children.
// (may be premature optimization, since
// find_studyplan_children also does some caching)
if(empty($this->page_cache)){
// (may be premature optimization, since .
// find_studyplan_children also does some caching).
if (empty($this->page_cache)) {
$this->page_cache = studyplanpage::find_studyplan_children($this);
}
return $this->page_cache;
@ -63,18 +84,18 @@ class studyplan {
* Return the context this studyplan is associated to
*/
public function context(): \context{
if(!isset($this->context)){
if (!isset($this->context)) {
try{
$this->context = contextinfo::by_id($this->r->context_id)->context;
}
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
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.
}
}
return $this->context;
}
public static function simple_structure($value=VALUE_REQUIRED){
public static function simple_structure($value=VALUE_REQUIRED) {
return new \external_single_structure([
"id" => new \external_value(PARAM_INT, 'id 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_config" => new \external_value(PARAM_TEXT, 'config string for aggregator'),
"aggregation_info" => aggregator::basic_structure(),
"pages" => new \external_multiple_structure(studyplanpage::simple_structure(),'pages'),
],'Basic studyplan info',$value);
"pages" => new \external_multiple_structure(studyplanpage::simple_structure(), 'pages'),
], 'Basic studyplan info', $value);
}
public function simple_model(){
public function simple_model() {
$pages = [];
foreach($this->pages() as $p){
foreach ($this->pages() as $p) {
$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([
"id" => new \external_value(PARAM_INT, 'id 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'),
"name" => new \external_value(PARAM_TEXT, 'name of scale'),
])),
],"Scale forcing on stuff", VALUE_OPTIONAL),
],"Advanced features available", VALUE_OPTIONAL),
],'Studyplan full structure',$value);
], "Scale forcing on stuff", VALUE_OPTIONAL),
], "Advanced features available", VALUE_OPTIONAL),
], 'Studyplan full structure', $value);
}
public function editor_model(){
public function editor_model() {
global $DB;
$model = [
@ -144,25 +165,24 @@ class studyplan {
'context_id' => $this->context()->id,
"aggregation" => $this->r->aggregation,
"aggregation_config" => $this->aggregator->config_string(),
'aggregation_info' => $this->aggregator->basic_model(),
'aggregation_info' => $this->aggregator->basic_model(),
'pages' => [],
];
foreach($this->pages() as $p)
{
foreach ($this->pages() as $p) {
$model['pages'][] = $p->editor_model();
}
if(has_capability('local/treestudyplan:forcescales', \context_system::instance())){
if(!array_key_exists('advanced',$model)){
// Create advanced node if it does not exist
if (has_capability('local/treestudyplan:forcescales', \context_system::instance())) {
if (!array_key_exists('advanced', $model)) {
// Create advanced node if it does not exist.
$model['advanced'] = [];
}
// get a list of available scales
$scales = array_map( function($scale){
return [ "id" => $scale->id, "name" => $scale->name,];
// get a list of available scales.
$scales = array_map( function($scale) {
return [ "id" => $scale->id, "name" => $scale->name, ];
}, \grade_scale::fetch_all(array('courseid'=>0)) ) ;
$model['advanced']['force_scales'] = [
@ -174,32 +194,32 @@ class studyplan {
return $model;
}
public static function add($fields,$bare=false){
public static function add($fields, $bare=false) {
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 ];
foreach($addable as $f){
if(array_key_exists($f,$fields)){
foreach ($addable as $f) {
if (array_key_exists($f, $fields)) {
$info[$f] = $fields[$f];
}
}
$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
// Add a single page and copy the names.This keeps the data sane until the upgrade to
// real page management is done
// Start temporary skräpp code.
// Add a single page and copy the names.This keeps the data sane until the upgrade to .
// real page management is done.
// 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
if(!$bare){
// TODO: Remove this when proper page management is implemented.
if (!$bare) {
$pageaddable = ['name','shortname','description','periods','startdate','enddate'];
$pageaddable = ['name', 'shortname', 'description', 'periods', 'startdate', 'enddate'];
$pageinfo = ['studyplan_id' => $id];
foreach($pageaddable as $f){
if(array_key_exists($f,$fields)){
if($f == "name"){
foreach ($pageaddable as $f) {
if (array_key_exists($f, $fields)) {
if ($f == "name") {
$pageinfo["fullname"] = $fields[$f];
} else {
$pageinfo[$f] = $fields[$f];
@ -210,41 +230,41 @@ class studyplan {
$page = studyplanpage::add($pageinfo);
$plan->page_cache = [$page];
}
// End temporary skräpp code
// End temporary skräpp code.
return $plan;
}
public function edit($fields){
public function edit($fields) {
global $DB;
$editable = ['name','shortname','description','idnumber','context_id','aggregation','aggregation_config'];
$info = ['id' => $this->id,];
foreach($editable as $f){
if(array_key_exists($f,$fields)){
$editable = ['name', 'shortname', 'description', 'idnumber', 'context_id', 'aggregation', 'aggregation_config'];
$info = ['id' => $this->id, ];
foreach ($editable as $f) {
if (array_key_exists($f, $fields)) {
$info[$f] = $fields[$f];
}
}
$DB->update_record(self::TABLE, $info);
//reload record after edit
$this->r = $DB->get_record(self::TABLE,['id' => $this->id],"*",MUST_EXIST);
//reload the context...
$this->context = null;
$this->context();
// reload aggregator
$this->aggregator = aggregator::createOrDefault($this->r->aggregation, $this->r->aggregation_config);
//reload record after edit.
$this->r = $DB->get_record(self::TABLE, ['id' => $this->id], "*", MUST_EXIST);
// Start temporary skräpp code
// TODO: Until proper page editing is implemented, copy data from studyplan to it's first page
//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.
// 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.
if(count($this->pages()) == 1){
// update the info to the page as well
if (count($this->pages()) == 1) {
// update the info to the page as well.
$page = $this->pages()[0];
$pageeditable = ['name','shortname','description','periods','startdate','enddate'];
$pageeditable = ['name', 'shortname', 'description', 'periods', 'startdate', 'enddate'];
$pageinfo = [];
foreach($pageeditable as $f){
if(array_key_exists($f,$fields)){
if($f == "name"){
foreach ($pageeditable as $f) {
if (array_key_exists($f, $fields)) {
if ($f == "name") {
$pageinfo["fullname"] = $fields[$f];
}else {
$pageinfo[$f] = $fields[$f];
@ -253,22 +273,22 @@ class studyplan {
}
$page->edit($pageinfo);
}
// End temporary skräpp code
// End temporary skräpp code.
return $this;
}
public function delete($force=false){
public function delete($force=false) {
global $DB;
if($force){
if ($force) {
$children = studyplanpage::find_studyplan_children($this);
foreach($children as $c){
foreach ($children as $c) {
$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');
}
else
@ -278,25 +298,24 @@ class studyplan {
}
}
public static function find_all($contextid=-1){
public static function find_all($contextid=-1) {
global $DB, $USER;
$list = [];
if($contextid <= 0){
$ids = $DB->get_fieldset_select(self::TABLE,"id","");
if ($contextid <= 0) {
$ids = $DB->get_fieldset_select(self::TABLE, "id", "");
}
else{
if($contextid == 1){
if ($contextid == 1) {
$contextid = 1;
$where = "context_id <= :contextid OR context_id IS NULL";
} else {
$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);
}
return $list;
@ -307,38 +326,36 @@ class studyplan {
$list = [];
$where = "shortname = :shortname AND context_id = :contextid";
if($contextid == 0){
if ($contextid == 0) {
$where .= "OR context_id IS NULL";
}
$ids = $DB->get_fieldset_select(self::TABLE,"id",$where,["shortname"=>$shortname, "contextid" => $contextid]);
foreach($ids as $id)
{
$ids = $DB->get_fieldset_select(self::TABLE, "id", $where, ["shortname"=>$shortname, "contextid" => $contextid]);
foreach ($ids as $id) {
$list[] = studyplan::findById($id);
}
return $list;
}
public static function find_for_user($userid)
{
public static function find_for_user($userid) {
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 {cohort_members} cm ON j.cohort_id = cm.cohortid
WHERE cm.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
WHERE j.user_id = :userid";
$user_plan_ids = $DB->get_fieldset_sql($sql, ['userid' => $userid]);
$plans = [];
foreach($cohort_plan_ids as $id) {
foreach ($cohort_plan_ids as $id) {
$plans[$id] = self::findById($id);
}
foreach($user_plan_ids as $id) {
if(!array_key_exists($id,$plans)){
foreach ($user_plan_ids as $id) {
if (!array_key_exists($id, $plans)) {
$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;
$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 {cohort_members} cm ON j.cohort_id = cm.cohortid
WHERE cm.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
WHERE j.user_id = :userid";
$count += $DB->count_records_sql($sql, ['userid' => $userid]);
@ -365,50 +381,50 @@ class studyplan {
return ($count > 0);
}
/**
/**
* Retrieve the users linked to this studyplan.
* @return array of User objects
*/
public function find_linked_users(){
public function find_linked_users() {
global $DB;
$users = [];
$uids = $this->find_linked_userids();
foreach($uids as $uid){
$users[] = $DB->get_record("user",["id"=>$uid]);
foreach ($uids as $uid) {
$users[] = $DB->get_record("user", ["id"=>$uid]);
}
return $users;
}
/**
/**
* Retrieve the user id's of the users linked to this studyplan.
* @return array of int (User Id)
*/
public function find_linked_userids(): array {
global $DB;
if($this->linked_userids === null){
if ($this->linked_userids === null) {
$uids = [];
// First get directly linked userids
// First get directly linked userids.
$sql = "SELECT j.user_id FROM {local_treestudyplan_user} j
WHERE j.studyplan_id = :planid";
$ulist = $DB->get_fieldset_sql($sql, ['planid' => $this->id]);
$uids = array_merge($uids,$ulist);
foreach($ulist as $uid){
$users[] = $DB->get_record("user",["id"=>$uid]);
$uids = array_merge($uids, $ulist);
foreach ($ulist as $uid) {
$users[] = $DB->get_record("user", ["id"=>$uid]);
}
// Next het users linked though cohort
$sql = "SELECT cm.userid FROM {local_treestudyplan_cohort} j
// Next het users linked though cohort.
$sql = "SELECT cm.userid FROM {local_treestudyplan_cohort} j
INNER JOIN {cohort_members} cm ON j.cohort_id = cm.cohortid
WHERE j.studyplan_id = :planid";
$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);
}
@ -416,23 +432,23 @@ class studyplan {
}
/** 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){
if(is_int($user)){
public function has_linked_user($user) {
if (is_int($user)) {
$userid = $user;
} else {
$userid = $user->id;
}
$uids = $this->find_linked_userids();
if(in_array($userid,$uids)){
if (in_array($userid, $uids)) {
return true;
} else {
return false;
}
}
public static function user_structure($value=VALUE_REQUIRED){
public static function user_structure($value=VALUE_REQUIRED) {
return new \external_single_structure([
"id" => new \external_value(PARAM_INT, 'id 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'),
"pages" => new \external_multiple_structure(studyplanpage::user_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 = [
'id' => $this->r->id,
@ -457,59 +473,53 @@ class studyplan {
'aggregation_info' => $this->aggregator->basic_model(),
];
foreach($this->pages() as $p)
{
foreach ($this->pages() as $p) {
$model['pages'][] = $p->user_model($userid);
}
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);
$new = $ori->duplicate($name,$shortname);
$new = $ori->duplicate($name, $shortname);
return $new->simple_model();
}
public function duplicate($name,$shortname)
{
// First duplicate the studyplan structure
public function duplicate($name, $shortname) {
// First duplicate the studyplan structure.
$newplan =studyplan::add([
'name' => $name,
'shortname' => $shortname,
'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);
}
return $newplan;
}
}
public static function export_structure()
{
public static function export_structure() {
return new \external_single_structure([
"format" => new \external_value(PARAM_TEXT, 'format of studyplan export'),
"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();
$json = json_encode([
"type"=>"studyplan",
"version"=>2.0,
"studyplan"=>$model
],\JSON_PRETTY_PRINT);
], \JSON_PRETTY_PRINT);
return [ "format" => "application/json", "content" => $json];
}
public function export_model()
{
public function export_model() {
$model = [
'name' => $this->r->name,
'shortname' => $this->r->shortname,
@ -522,32 +532,29 @@ class studyplan {
return $model;
}
public function export_pages_model()
{
public function export_pages_model() {
$pages = [];
foreach($this->pages() as $p)
{
foreach ($this->pages() as $p) {
$pages[] = $p->export_model();
}
return $pages;
}
public static function import_studyplan($content,$format="application/json",$context_id=1)
{
if($format != "application/json") { return false;}
$content = json_decode($content,true);
if($content["type"] == "studyplan" && $content["version"] >= 2.0){
// Make sure the aggregation_config is re-encoded as json text
public static function import_studyplan($content, $format="application/json", $context_id=1) {
if ($format != "application/json") { return false;}
$content = json_decode($content, true);
if ($content["type"] == "studyplan" && $content["version"] >= 2.0) {
// Make sure the aggregation_config is re-encoded as json text.
$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;
// Create a new plan, based on the given parameters - this is the import studyplan part
$plan = self::add($content["studyplan"],true);
// Now import each page
// Create a new plan, based on the given parameters - this is the import studyplan part.
$plan = self::add($content["studyplan"], true);
// Now import each page.
return $plan->import_pages_model($content["studyplan"]["pages"]);
}
@ -557,17 +564,16 @@ class studyplan {
}
}
public function import_pages($content,$format="application/json")
{
if($format != "application/json") { return false;}
$content = json_decode($content,true);
if($content["version"] >= 2.0){
if($content["type"] == "studyplanpage"){
// import single page from a studyplanpage (wrapped in array of one page)
public function import_pages($content, $format="application/json") {
if ($format != "application/json") { return false;}
$content = json_decode($content, true);
if ($content["version"] >= 2.0) {
if ($content["type"] == "studyplanpage") {
// import single page from a studyplanpage (wrapped in array of one page).
return $this->import_pages_model([$content["page"]]);
}
else if($content["type"] == "studyplan"){
// Import all pages from the studyplan
else if ($content["type"] == "studyplan") {
// Import all pages from the studyplan.
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.
foreach($model as $p){
foreach ($model as $p) {
$p["studyplan_id"] = $this->id();
$page = studyplanpage::add($p);
$this->page_cache[] = $page;
@ -592,38 +597,38 @@ class studyplan {
/**
* Mark the studyplan as changed regarding courses and associated cohorts
*/
public function mark_csync_changed(){
public function mark_csync_changed() {
global $DB;
$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
$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.
}
/**
* Clear the csync mark
*/
public function clear_csync_changed(){
public function clear_csync_changed() {
global $DB;
$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
$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.
}
public function has_csync_changed(){
public function has_csync_changed() {
return ($this->r->csync_flag > 0)?true:false;
}
/**
* See if the specified course id is linked in this studyplan
*/
public function course_linked($courseid){
public function course_linked($courseid) {
global $DB;
$sql = "SELECT COUNT(i.id)
FROM {local_treestudyplan}
INNER JOIN {local_treestudyplan_line} l ON p.id = l.studyplan_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";
$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;
}
@ -632,7 +637,7 @@ class studyplan {
* List the course id is linked in this studyplan
* Used for cohort enrolment cascading
*/
public function get_linked_course_ids(){
public function get_linked_course_ids() {
global $DB;
$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_item} i ON l.id = i.line_id
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;
}
@ -649,7 +654,7 @@ class 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;
$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
*/
public function get_linked_user_ids(){
public function get_linked_user_ids() {
global $CFG, $DB;
$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
*/
public function badge_linked($badgeid){
public function badge_linked($badgeid) {
global $DB;
$sql = "SELECT COUNT(i.id)
FROM {local_treestudyplan}
INNER JOIN {local_treestudyplan_line} l ON p.id = l.studyplan_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";
$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;
}

View File

@ -1,4 +1,25 @@
<?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;
require_once($CFG->libdir.'/externallib.php');
@ -10,30 +31,30 @@ class studyplanpage {
private static $CACHE = [];
private $r; // Holds database record
private $r; // Holds database record.
private $id;
private $studyplan;
public function aggregator(){
public function aggregator() {
return $this->studyplan->aggregator();
}
// Cache constructors to avoid multiple creation events in one session.
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);
}
}
return self::$CACHE[$id];
}
private function __construct($id) {
global $DB;
$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);
}
public function id(){
public function id() {
return $this->id;
}
@ -41,34 +62,34 @@ class studyplanpage {
return $this->studyplan;
}
public function shortname(){
public function shortname() {
return $this->r->shortname;
}
public function periods(){
public function periods() {
return $this->r->periods;
}
public function fullname(){
public function fullname() {
return $this->r->fullname;
}
public function startdate(){
public function startdate() {
return new \DateTime($this->r->startdate);
}
public function enddate(){
if($this->r->enddate && strlen($this->r->enddate) > 0){
public function enddate() {
if ($this->r->enddate && strlen($this->r->enddate) > 0) {
return new \DateTime($this->r->enddate);
}
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"));
}
}
public static function simple_structure($value=VALUE_REQUIRED){
public static function simple_structure($value=VALUE_REQUIRED) {
return new \external_single_structure([
"id" => new \external_value(PARAM_INT, 'id 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'),
"enddate" => new \external_value(PARAM_TEXT, 'end date of studyplan'),
"perioddesc" => period::page_structure(),
],'Studyplan page basic info',$value);
], 'Studyplan page basic info', $value);
}
public function simple_model(){
public function simple_model() {
return [
'id' => $this->r->id,
'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([
"id" => new \external_value(PARAM_INT, 'id of studyplan'),
"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'),
"studylines" => new \external_multiple_structure(studyline::editor_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;
$model = [
@ -124,68 +145,67 @@ class studyplanpage {
];
$children = studyline::find_page_children($this);
foreach($children as $c)
{
foreach ($children as $c) {
$model['studylines'][] = $c->editor_model();
}
return $model;
}
public static function add($fields){
public static function add($fields) {
global $CFG, $DB;
if(!isset($fields['studyplan_id'])){
if (!isset($fields['studyplan_id'])) {
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 ];
foreach($addable as $f){
if(array_key_exists($f,$fields)){
foreach ($addable as $f) {
if (array_key_exists($f, $fields)) {
$info[$f] = $fields[$f];
}
}
if(!isset($addable['periods'])){
if (!isset($addable['periods'])) {
$addable['periods'] = 4;
} else if($addable['periods'] < 1){
} else if ($addable['periods'] < 1) {
$addable['periods'] = 1;
}
$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;
$editable = ['fullname','shortname','description','periods','startdate','enddate'];
$info = ['id' => $this->id,];
foreach($editable as $f){
if(array_key_exists($f,$fields)){
$editable = ['fullname', 'shortname', 'description', 'periods', 'startdate', 'enddate'];
$info = ['id' => $this->id, ];
foreach ($editable as $f) {
if (array_key_exists($f, $fields)) {
$info[$f] = $fields[$f];
}
}
if(isset($info['periods']) && $info['periods'] < 1){
if (isset($info['periods']) && $info['periods'] < 1) {
$info['periods'] = 1;
}
$DB->update_record(self::TABLE, $info);
//reload record after edit
$this->r = $DB->get_record(self::TABLE,['id' => $this->id],"*",MUST_EXIST);
//reload record after edit.
$this->r = $DB->get_record(self::TABLE, ['id' => $this->id], "*", MUST_EXIST);
return $this;
}
public function delete($force=false){
public function delete($force=false) {
global $DB;
if($force){
if ($force) {
$children = studyline::find_page_children($this);
foreach($children as $c){
foreach ($children as $c) {
$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');
}
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([
"id" => new \external_value(PARAM_INT, 'id 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'),
"studylines" => new \external_multiple_structure(studyline::user_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 = [
'id' => $this->r->id,
@ -224,35 +244,31 @@ class studyplanpage {
];
$children = studyline::find_page_children($this);
foreach($children as $c)
{
foreach ($children as $c) {
$model['studylines'][] = $c->user_model($userid);
}
return $model;
}
public static function find_studyplan_children(studyplan $plan)
{
public static function find_studyplan_children(studyplan $plan) {
global $DB;
$list = [];
$ids = $DB->get_fieldset_select(self::TABLE,"id","studyplan_id = :plan_id ORDER BY startdate",
$ids = $DB->get_fieldset_select(self::TABLE, "id", "studyplan_id = :plan_id ORDER BY startdate",
['plan_id' => $plan->id()]);
foreach($ids as $id) {
foreach ($ids as $id) {
$list[] = self::findById($id);
}
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);
$new = $ori->duplicate($name,$shortname);
$new = $ori->duplicate($name, $shortname);
return $new->simple_model();
}
public function duplicate($new_studyplan)
{
// First duplicate the studyplan structure
public function duplicate($new_studyplan) {
// First duplicate the studyplan structure.
$new = studyplanpage::add([
'studyplan_id' => $new_studyplan->id(),
'fullname' => $this->r->fullname,
@ -262,101 +278,98 @@ class studyplanpage {
'startdate' => $this->r->startdate,
'enddate' => empty($this->r->enddate)?null:$this->r->enddate,
]);
// next, copy the studylines
// next, copy the studylines.
$children = studyline::find_page_children($this);
$itemtranslation = [];
$linetranslation = [];
foreach($children as $c){
$newchild = $c->duplicate($this,$itemtranslation);
foreach ($children as $c) {
$newchild = $c->duplicate($this, $itemtranslation);
$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
// (feature of the studyline::duplicate function)
// use this to recreate the lines in the new plan
foreach(array_keys($itemtranslation) as $item_id){
// copy based on the outgoing connections of each item, to avoid duplicates
// 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).
// use this to recreate the lines in the new plan.
foreach (array_keys($itemtranslation) as $item_id) {
// copy based on the outgoing connections of each item, to avoid duplicates.
$connections = studyitemconnection::find_outgoing($item_id);
foreach($connections as $conn){
studyitemconnection::connect($itemtranslation[$conn->from_id],$itemtranslation[$conn->to_id]);
foreach ($connections as $conn) {
studyitemconnection::connect($itemtranslation[$conn->from_id], $itemtranslation[$conn->to_id]);
}
}
return $new;
}
}
public static function export_structure()
{
public static function export_structure() {
return new \external_single_structure([
"format" => new \external_value(PARAM_TEXT, 'format of studyplan export'),
"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();
$json = json_encode([
"type"=>"studyplanpage",
"version"=>2.0,
"page"=>$model
],\JSON_PRETTY_PRINT);
], \JSON_PRETTY_PRINT);
return [ "format" => "application/json", "content" => $json];
}
public function export_page_csv()
{
public function export_page_csv() {
$plist = period::findForPage($this);
$model = $this->editor_model();
$periods = intval($model["periods"]);
// First line
// First line.
$csv = "\"\"";
for($i = 1; $i <= $periods; $i++){
for($i = 1; $i <= $periods; $i++) {
$name = $plist[$i]->shortname();
$csv .= ",\"{$name}\"";
$csv .= ", \"{$name}\"";
}
$csv .= "\r\n";
// next, make one line per studyline
foreach($model["studylines"] as $line){
// determine how many fields are simultaneous in the line at maximum
// next, make one line per studyline.
foreach ($model["studylines"] as $line) {
// determine how many fields are simultaneous in the line at maximum.
$maxlines = 1;
for($i = 1; $i <= $periods; $i++){
if(count($line["slots"]) > $i){
for($i = 1; $i <= $periods; $i++) {
if (count($line["slots"]) > $i) {
$ct = 0;
foreach($line["slots"][$i][studyline::SLOTSET_COMPETENCY] as $itm){
if($itm["type"] == "course"){
foreach ($line["slots"][$i][studyline::SLOTSET_COMPETENCY] as $itm) {
if ($itm["type"] == "course") {
$ct += 1;
}
}
if($ct > $maxlines){
if ($ct > $maxlines) {
$maxlines = $ct;
}
}
}
for($lct = 0; $lct < $maxlines; $lct++){
for($lct = 0; $lct < $maxlines; $lct++) {
$csv .= "\"{$line["name"]}\"";
for($i = 1; $i <= $periods; $i++){
for($i = 1; $i <= $periods; $i++) {
$filled = false;
if(count($line["slots"]) > $i){
if (count($line["slots"]) > $i) {
$ct = 0;
foreach($line["slots"][$i][studyline::SLOTSET_COMPETENCY] as $itm){
if($itm["type"] == "course"){
if($ct == $lct){
$csv .= ",\"";
foreach ($line["slots"][$i][studyline::SLOTSET_COMPETENCY] as $itm) {
if ($itm["type"] == "course") {
if ($ct == $lct) {
$csv .= ", \"";
$csv .= $itm["course"]["fullname"];
$csv .= "\r\n";
$first = true;
foreach($itm["course"]["grades"] as $g){
if($g["selected"]){
if($first){
foreach ($itm["course"]["grades"] as $g) {
if ($g["selected"]) {
if ($first) {
$first = false;
}
else{
else{
$csv .= "\r\n";
}
$csv .= "- ".str_replace('"', '\'', $g["name"]);
@ -369,9 +382,9 @@ class studyplanpage {
$ct++;
}
}
}
if(!$filled) {
$csv .= ",\"\"";
}
if (!$filled) {
$csv .= ", \"\"";
}
}
$csv .= "\r\n";
@ -381,29 +394,28 @@ class studyplanpage {
return [ "format" => "text/csv", "content" => $csv];
}
public function export_studylines(){
public function export_studylines() {
$model = $this->export_studylines_model();
$json = json_encode([
"type"=>"studylines",
"version"=>2.0,
"studylines"=>$model,
],\JSON_PRETTY_PRINT);
], \JSON_PRETTY_PRINT);
return [ "format" => "application/json", "content" => $json];
}
public function export_periods(){
public function export_periods() {
$model = period::page_model($this);
$json = json_encode([
"type"=>"periods",
"version"=>2.0,
"perioddesc"=>$model,
],\JSON_PRETTY_PRINT);
], \JSON_PRETTY_PRINT);
return [ "format" => "application/json", "content" => $json];
}
public function export_model()
{
public function export_model() {
$model = [
'fullname' => $this->r->fullname,
'shortname' => $this->r->shortname,
@ -417,26 +429,23 @@ class studyplanpage {
return $model;
}
public function export_studylines_model()
{
public function export_studylines_model() {
$children = studyline::find_page_children($this);
$lines = [];
foreach($children as $c)
{
foreach ($children as $c) {
$lines[] = $c->export_model();
}
return $lines;
}
public function import_periods($content,$format="application/json")
{
if($format != "application/json") { return false;}
$content = json_decode($content,true);
if($content["type"] == "periods" && $content["version"] >= 2.0){
public function import_periods($content, $format="application/json") {
if ($format != "application/json") { return false;}
$content = json_decode($content, true);
if ($content["type"] == "periods" && $content["version"] >= 2.0) {
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"]);
}
else {
@ -444,17 +453,16 @@ class studyplanpage {
}
}
public function import_studylines($content,$format="application/json")
{
if($format != "application/json") { return false;}
$content = json_decode($content,true);
if($content["type"] == "studylines" && $content["version"] >= 2.0){
public function import_studylines($content, $format="application/json") {
if ($format != "application/json") { return false;}
$content = json_decode($content, true);
if ($content["type"] == "studylines" && $content["version"] >= 2.0) {
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"]);
}
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"]);
}
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);
foreach($children as $l){
if($shortname == $l->shortname()){
foreach ($children as $l) {
if ($shortname == $l->shortname()) {
return $l;
}
}
return null;
}
public function import_periods_model($model){
public function import_periods_model($model) {
$periods = period::findForPage($this);
foreach($model as $pmodel){
foreach ($model as $pmodel) {
$pi = $pmodel["period"];
if(array_key_exists($pi,$periods)){
if (array_key_exists($pi, $periods)) {
$periods[$pi]->edit($pmodel);
}
}
}
public function import_studylines_model($model)
{
// First attempt to map each studyline model to an existing or new line
public function import_studylines_model($model) {
// First attempt to map each studyline model to an existing or new line.
$line_map = [];
foreach($model as $ix => $linemodel){
foreach ($model as $ix => $linemodel) {
$line = $this->find_studyline_by_shortname($linemodel["shortname"]);
if(empty($line)){
if (empty($line)) {
$linemodel["page_id"] = $this->id;
$line = studyline::add($linemodel);
} 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;
}
// next, let each study line import the study items
$itemtranslation = [];
// next, let each study line import the study items.
$itemtranslation = [];
$connections = [];
foreach($model as $ix => $linemodel){
$line_map[$ix]->import_studyitems($linemodel["slots"],$itemtranslation,$connections);
foreach ($model as $ix => $linemodel) {
$line_map[$ix]->import_studyitems($linemodel["slots"], $itemtranslation, $connections);
}
// Finally, create the links between the study items
foreach($connections as $from => $dests){
foreach($dests as $to){
studyitemconnection::connect($from,$itemtranslation[$to]);
// Finally, create the links between the study items.
foreach ($connections as $from => $dests) {
foreach ($dests as $to) {
studyitemconnection::connect($from, $itemtranslation[$to]);
}
}
return true;

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,24 @@
<?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;
@ -7,21 +27,20 @@ class success {
private $success;
private $msg;
public static function success($msg=""){
return new self(true,$msg);
public static function success($msg="") {
return new self(true, $msg);
}
public static function fail($msg=""){
return new self(false,$msg);
public static function fail($msg="") {
return new self(false, $msg);
}
public function __construct($success,$msg){
public function __construct($success, $msg) {
$this->success = ($success)?true:false;
$this->msg = $msg;
}
public static function structure()
{
public static function structure() {
return new \external_single_structure([
"success" => new \external_value(PARAM_BOOL, 'operation completed succesfully'),
"msg" => new \external_value(PARAM_TEXT, 'message'),
@ -32,11 +51,11 @@ class success {
return ["success" => $this->success, "msg"=> $this->msg];
}
public function successful(){
public function successful() {
return $this->success;
}
public function msg(){
public function msg() {
return $this->msg;
}

View File

@ -1,4 +1,25 @@
<?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;
require_once($CFG->dirroot.'/course/externallib.php');
use local_treestudyplan\studyplan;
@ -20,24 +41,24 @@ class autocohortsync extends \core\task\scheduled_task {
* Execute the task.
*/
public function execute() {
if(get_config("local_treestudyplan","csync_enable")){
if (get_config("local_treestudyplan", "csync_enable")) {
\mtrace("Automatic csync cascading enabled");
$studyplans = studyplan::find_all();
foreach($studyplans as $studyplan) {
// Only process studyplans that have been marked for change because
foreach ($studyplans as $studyplan) {
// Only process studyplans that have been marked for change because .
// 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");
$enroller = new cascadecohortsync($studyplan);
$enroller->sync();
if(get_config("local_treestudyplan","csync_users")){
if (get_config("local_treestudyplan", "csync_users")) {
$userenroller = new cascadeusersync($studyplan);
$userenroller->sync();
}
$studyplan->clear_csync_changed();
}
}
}
\mtrace("Task done");
} else {

View File

@ -1,11 +1,32 @@
<?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;
require_once($CFG->dirroot.'/course/externallib.php');
use local_treestudyplan\teachingfinder;
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.
*
@ -21,9 +42,9 @@ class refreshteacherlist extends \core\task\scheduled_task {
public function execute() {
\mtrace("Ververs lijst met leraren");
$teacher_ids = teachingfinder::list_teacher_ids();
foreach($teacher_ids as $tid){
foreach ($teacher_ids as $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");
teachingfinder::update_teaching_cache($tid);
}

View File

@ -1,22 +1,43 @@
<?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;
class teachingfinder {
const TABLE = "local_treestudyplan_teachers";
public static function list_my_plans(){
global $USER,$DB;
public static function list_my_plans() {
global $USER, $DB;
$userid = $USER->id;
$records = $DB->get_records(self::TABLE,['teacher_id' => $userid]);
if(count($records) == 0){
// initiate a search if the cache is empty
$records = $DB->get_records(self::TABLE, ['teacher_id' => $userid]);
if (count($records) == 0) {
// initiate a search if the cache is empty.
self::update_teaching_cache($userid);
$DB->get_records(self::TABLE,['teacher_id' => $userid]);
$DB->get_records(self::TABLE, ['teacher_id' => $userid]);
}
$list = [];
foreach($records as $r){
foreach ($records as $r) {
$list[] = studyplan::findById($r->studyplan_id);
}
return $list;
@ -28,56 +49,55 @@ class teachingfinder {
* (Has the mod/assign::grade capability in one of the linked courses)
* TODO: OPTIMIZE THIS CHECK!!!
*/
public static function update_teaching_cache($userid){
public static function update_teaching_cache($userid) {
global $DB;
$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()";
$page_ids = $DB->get_fieldset_sql($sql, []);
// 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)
// 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).
foreach($page_ids as $page_id) {
$sql = "SELECT i.course_id FROM {local_treestudyplan_item} i
INNER JOIN {local_treestudyplan_line} l ON i.line_id = l.id
foreach ($page_ids as $page_id) {
$sql = "SELECT i.course_id FROM {local_treestudyplan_item} i
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";
$course_ids = $DB->get_fieldset_sql($sql, ["page_id" => $page_id]);
$linked = false;
foreach($course_ids as $cid){
foreach ($course_ids as $cid) {
$coursecontext = \context_course::instance($cid);
if (is_enrolled($coursecontext, $userid, 'mod/assign:grade')){
$linked = true;
break; // No need to search further
if (is_enrolled($coursecontext, $userid, 'mod/assign:grade')) {
$linked = true;
break; // No need to search further.
}
}
if($linked)
{
if ($linked) {
$list[] = $page_id;
}
}
// Now, clear the database of all records for this user
$DB->delete_records(self::TABLE,["teacher_id"=>$userid]);
// And add new records for the found studyplans
// Now, clear the database of all records for this user.
$DB->delete_records(self::TABLE, ["teacher_id"=>$userid]);
// And add new records for the found studyplans.
$now = time();
foreach($list as $page_id){
// Retrieve the studyplan id from the page
//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]);
$DB->insert_record(self::TABLE,["teacher_id"=>$userid,"studyplan_id"=>$planid,"update_time"=>$now]);
foreach ($list as $page_id) {
// Retrieve the studyplan id from the page.
//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]);
$DB->insert_record(self::TABLE, ["teacher_id"=>$userid, "studyplan_id"=>$planid, "update_time"=>$now]);
}
return $list;
}
public static function list_teacher_ids(){
public static function list_teacher_ids() {
global $DB;
return $DB->get_fieldset_sql("SELECT DISTINCT teacher_id FROM {".self::TABLE."}");
}

View File

@ -1,4 +1,25 @@
<?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;
use \local_treestudyplan\local\gradegenerator;
@ -39,13 +60,13 @@ if ($options['help']) {
cli_writeln($usage);
exit(2);
}
//cli_writeln(print_r($options,true));
//cli_writeln(print_r($options, true));.
if (empty($options['studyplan']) && empty($options["all"])) {
cli_error('Missing mandatory argument studyplan.', 2);
}
if(!empty($options["all"])){
if (!empty($options["all"])) {
$plans = studyplan::find_all();
} else {
$plans = studyplan::find_by_shortname($options["studyplan"]);
@ -56,22 +77,22 @@ $generator = new gradegenerator();
$generator->fromFile($options["file"]);
cli_writeln(count($plans)." studyplans found:");
foreach($plans as $plan){
foreach ($plans as $plan) {
cli_heading($plan->name());
$users = $plan->find_linked_users();
foreach($users as $u){
foreach ($users as $u) {
$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}");
}
foreach($plan->pages() as $page){
foreach ($plan->pages() as $page) {
$lines = studyline::find_page_children($page);
foreach($lines as $line){
foreach ($lines as $line) {
cli_writeln(" ** {$line->name()} **");
$items = studyitem::find_studyline_children($line);
foreach($users as $u){
$generator->addskill($u->username,$line->shortname());
foreach ($users as $u) {
$generator->addskill($u->username, $line->shortname());
}
}
}

View File

@ -1,4 +1,25 @@
<?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;
use \local_treestudyplan\local\gradegenerator;
@ -46,7 +67,7 @@ if ($options['help']) {
exit(2);
}
/////////////////////////////////
/////////////////////////////////.
$user = get_admin();
if (!$user) {
@ -65,14 +86,14 @@ login_attempt_valid($user);
complete_user_login($user);
////////////////////////////////
////////////////////////////////.
if (empty($options['studyplan']) && empty($options["all"])) {
cli_error('Missing mandatory argument studyplan.', 2);
}
if(!empty($options["all"])){
if (!empty($options["all"])) {
$plans = studyplan::find_all();
} else {
$plans = studyplan::find_by_shortname($options["studyplan"]);
@ -85,64 +106,64 @@ $generator->fromFile($options["file"]);
$assignments = [];
cli_writeln(count($plans)." studyplans found:");
foreach($plans as $plan){
foreach ($plans as $plan) {
cli_heading($plan->name());
$users = $plan->find_linked_users();
foreach($plan->pages() as $page){
foreach ($plan->pages() as $page) {
$lines = studyline::find_page_children($page);
foreach($lines as $line){
foreach ($lines as $line) {
cli_writeln(" ** {$line->name()} **");
$items = studyitem::find_studyline_children($line);
foreach($items as $item){
if($item->type() == studyitem::COURSE) { // only handle courses for now
foreach ($items as $item) {
if ($item->type() == studyitem::COURSE) { // only handle courses for now.
$courseinfo = $item->getcourseinfo();
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} <-");
$gradables = gradeinfo::list_studyitem_gradables($item);
$gen = $generator->generate($u->username,$line->shortname(),$gradables);
foreach($gen as $gg){
$gen = $generator->generate($u->username, $line->shortname(), $gradables);
foreach ($gen as $gg) {
$g = $gg->gi;
$gi = $g->getGradeitem();
$name = $gi->itemname;
$grade = $gg->gradetext;
cli_write (" - {$name} = {$grade}");
// 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',
// 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',
['gradeitemid' => $gi->id, 'userid' => $u->id]);
if(!$existing){
if($gg->grade > 0){
if($gi->itemmodule == "assign"){
// If it is an assignment, submit though that interface
list($c,$cminfo) = get_course_and_cm_from_instance($gi->iteminstance,$gi->itemmodule);
if (!$existing) {
if ($gg->grade > 0) {
if ($gi->itemmodule == "assign") {
// If it is an assignment, submit though that interface .
list($c, $cminfo) = get_course_and_cm_from_instance($gi->iteminstance, $gi->itemmodule);
$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->grader = $USER->id;
$ug->feedbacktext = nl2br( htmlspecialchars($gg->fb));
$ug->feedbackformat = FORMAT_HTML;
//print_r($ug);
if(!$options["dryrun"]){
//print_r($ug);.
if (!$options["dryrun"]) {
$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");
} else {
cli_writeln(" ... (Dry Run)");
}
} else {
// Otherwise, set the grade through the manual grading override
// Otherwise, set the grade through the manual grading override.
cli_writeln(" ... Cannot store");
}
@ -155,7 +176,7 @@ foreach($plans as $plan){
}
}
}
else
else
{
cli_writeln(" Skipping since it has not started yet");
}

View File

@ -1,4 +1,24 @@
<?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 = [

View File

@ -1,4 +1,24 @@
<?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 = [
"Competency listing" => [
@ -34,530 +54,530 @@ $services = [
$functions = [
/***************************
* Studyplan functions
* Studyplan functions
***************************/
'local_treestudyplan_list_studyplans' => [ //web service function name
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function
'methodname' => 'list_studyplans', //external function name
'description' => 'List available studyplans', //human readable description of the web service function
'type' => 'read', //database rights of the web service function (read, write)
'local_treestudyplan_list_studyplans' => [ //web service function name.
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function.
'methodname' => 'list_studyplans', //external function name.
'description' => 'List available studyplans', //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_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
'capabilities' => 'local/treestudyplan:editstudyplan, local/treestudyplan:viewuserreports', // 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)
'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', // 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_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)
'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
'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)
],
'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
'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)
],
'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
'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,
],
'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
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function
'methodname' => 'list_badges', //external function name
'description' => 'List availabel site badges', //human readable description of the web service function
'type' => 'read', //database rights of the web service function (read, write)
'local_treestudyplan_list_badges' => [ //web service function name.
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function.
'methodname' => 'list_badges', //external function name.
'description' => 'List availabel site badges', //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
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required.
'loginrequired' => true,
],
],
/***************************
* Association functions
* Association functions
***************************/
'local_treestudyplan_list_cohort' => [ //web service function name
'classname' => '\local_treestudyplan\associationservice', //class containing the external function
'methodname' => 'list_cohort', //external function name
'description' => 'List available cohorts', //human readable description of the web service function
'type' => 'read', //database rights of the web service function (read, write)
'local_treestudyplan_list_cohort' => [ //web service function name.
'classname' => '\local_treestudyplan\associationservice', //class containing the external function.
'methodname' => 'list_cohort', //external function name.
'description' => 'List available cohorts', //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
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required.
'loginrequired' => true,
],
'local_treestudyplan_find_user' => [ //web service function name
'classname' => '\local_treestudyplan\associationservice', //class containing the external function
'methodname' => 'find_user', //external function name
'description' => 'Find user', //human readable description of the web service function
'type' => 'read', //database rights of the web service function (read, write)
'local_treestudyplan_find_user' => [ //web service function name.
'classname' => '\local_treestudyplan\associationservice', //class containing the external function.
'methodname' => 'find_user', //external function name.
'description' => 'Find user', //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
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required.
'loginrequired' => true,
],
'local_treestudyplan_connect_cohort' => [ //web service function name
'classname' => '\local_treestudyplan\associationservice', //class containing the external function
'methodname' => 'connect_cohort', //external function name
'description' => 'Connect cohort to studyplan', //human readable description of the web service function
'type' => 'read', //database rights of the web service function (read, write)
'local_treestudyplan_connect_cohort' => [ //web service function name.
'classname' => '\local_treestudyplan\associationservice', //class containing the external function.
'methodname' => 'connect_cohort', //external function name.
'description' => 'Connect cohort to studyplan', //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
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required.
'loginrequired' => true,
],
'local_treestudyplan_disconnect_cohort' => [ //web service function name
'classname' => '\local_treestudyplan\associationservice', //class containing the external function
'methodname' => 'disconnect_cohort', //external function name
'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)
'local_treestudyplan_disconnect_cohort' => [ //web service function name.
'classname' => '\local_treestudyplan\associationservice', //class containing the external function.
'methodname' => 'disconnect_cohort', //external function name.
'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).
'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_connect_user' => [ //web service function name
'classname' => '\local_treestudyplan\associationservice', //class containing the external function
'methodname' => 'connect_user', //external function name
'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)
'local_treestudyplan_connect_user' => [ //web service function name.
'classname' => '\local_treestudyplan\associationservice', //class containing the external function.
'methodname' => 'connect_user', //external function name.
'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).
'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_disconnect_user' => [ //web service function name
'classname' => '\local_treestudyplan\associationservice', //class containing the external function
'methodname' => 'disconnect_user', //external function name
'description' => 'Disconnect user from studyplan', //human readable description of the web service function
'type' => 'read', //database rights of the web service function (read, write)
'local_treestudyplan_disconnect_user' => [ //web service function name.
'classname' => '\local_treestudyplan\associationservice', //class containing the external function.
'methodname' => 'disconnect_user', //external function name.
'description' => 'Disconnect user from studyplan', //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
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required.
'loginrequired' => true,
],
'local_treestudyplan_associated_users' => [ //web service function name
'classname' => '\local_treestudyplan\associationservice', //class containing the external function
'methodname' => 'associated_users', //external function name
'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)
'local_treestudyplan_associated_users' => [ //web service function name.
'classname' => '\local_treestudyplan\associationservice', //class containing the external function.
'methodname' => 'associated_users', //external function name.
'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).
'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_associated_cohorts' => [ //web service function name
'classname' => '\local_treestudyplan\associationservice', //class containing the external function
'methodname' => 'associated_cohorts', //external function name
'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)
'local_treestudyplan_associated_cohorts' => [ //web service function name.
'classname' => '\local_treestudyplan\associationservice', //class containing the external function.
'methodname' => 'associated_cohorts', //external function name.
'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).
'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_list_user_studyplans' => [ //web service function name
'classname' => '\local_treestudyplan\studentstudyplanservice', //class containing the external function
'methodname' => 'list_user_studyplans', //external function name
'description' => 'List user studyplans', //human readable description of the web service function
'type' => 'read', //database rights of the web service function (read, write)
'local_treestudyplan_list_user_studyplans' => [ //web service function name.
'classname' => '\local_treestudyplan\studentstudyplanservice', //class containing the external function.
'methodname' => 'list_user_studyplans', //external function name.
'description' => 'List user studyplans', //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
'capabilities' => 'local/treestudyplan:viewuserreports', // Advises the admin which capabilities are required.
'loginrequired' => true,
],
'local_treestudyplan_get_user_studyplans' => [ //web service function name
'classname' => '\local_treestudyplan\studentstudyplanservice', //class containing the external function
'methodname' => 'get_user_studyplans', //external function name
'description' => 'Retrieve user studyplan', //human readable description of the web service function
'type' => 'read', //database rights of the web service function (read, write)
],
'local_treestudyplan_get_user_studyplans' => [ //web service function name.
'classname' => '\local_treestudyplan\studentstudyplanservice', //class containing the external function.
'methodname' => 'get_user_studyplans', //external function name.
'description' => 'Retrieve user studyplan', //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
'capabilities' => 'local/treestudyplan:viewuserreports', // Advises the admin which capabilities are required.
'loginrequired' => true,
],
'local_treestudyplan_get_user_studyplan' => [ //web service function name
'classname' => '\local_treestudyplan\studentstudyplanservice', //class containing the external function
'methodname' => 'get_user_studyplan', //external function name
'description' => 'Retrieve user studyplan', //human readable description of the web service function
'type' => 'read', //database rights of the web service function (read, write)
],
'local_treestudyplan_get_user_studyplan' => [ //web service function name.
'classname' => '\local_treestudyplan\studentstudyplanservice', //class containing the external function.
'methodname' => 'get_user_studyplan', //external function name.
'description' => 'Retrieve user studyplan', //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
'capabilities' => 'local/treestudyplan:viewuserreports', // Advises the admin which capabilities are required.
'loginrequired' => true,
],
'local_treestudyplan_get_invited_studyplan' => [ //web service function name
'classname' => '\local_treestudyplan\studentstudyplanservice', //class containing the external function
'methodname' => 'get_invited_studyplan', //external function name
'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)
],
'local_treestudyplan_get_invited_studyplan' => [ //web service function name.
'classname' => '\local_treestudyplan\studentstudyplanservice', //class containing the external function.
'methodname' => 'get_invited_studyplan', //external function name.
'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).
'ajax' => true,
'capabilities' => '', // Advises the admin which capabilities are required
'capabilities' => '', // Advises the admin which capabilities are required.
'loginrequired' => false,
],
'local_treestudyplan_list_own_studyplans' => [ //web service function name
'classname' => '\local_treestudyplan\studentstudyplanservice', //class containing the external function
'methodname' => 'list_own_studyplans', //external function name
'description' => 'List own studyplans', //human readable description of the web service function
'type' => 'read', //database rights of the web service function (read, write)
],
'local_treestudyplan_list_own_studyplans' => [ //web service function name.
'classname' => '\local_treestudyplan\studentstudyplanservice', //class containing the external function.
'methodname' => 'list_own_studyplans', //external function name.
'description' => 'List own studyplans', //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_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
'capabilities' => '', // Advises the admin which capabilities are required.
'loginrequired' => true,
],
'local_treestudyplan_all_associated' => [ //web service function name
'classname' => '\local_treestudyplan\associationservice', //class containing the external function
'methodname' => 'all_associated', //external function name
'description' => 'List associated users', //human readable description of the web service function
'type' => 'read', //database rights of the web service function (read, write)
'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' => 'local/treestudyplan:viewuserreports', // 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
'capabilities' => '', // 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)
'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
'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)
'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
'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)
'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', // 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
'capabilities' => 'local/treestudyplan:editstudyplan, local/treestudyplan:selectowngradables', // 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)
'local_treestudyplan_all_associated' => [ //web service function name.
'classname' => '\local_treestudyplan\associationservice', //class containing the external function.
'methodname' => 'all_associated', //external function name.
'description' => 'List associated users', //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
'capabilities' => 'local/treestudyplan:viewuserreports', // 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)
'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_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
'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)
'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:viewuserreports', // Advises the admin which capabilities are required
'capabilities' => 'local/treestudyplan:forcescales', // 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)
'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,
],
'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,
'loginrequired' => true,
],
'local_treestudyplan_list_used_categories' => [ //web service function name
'classname' => '\local_treestudyplan\courseservice', //class containing the external function
'methodname' => 'list_used_categories', //external function name
'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)
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required
'local_treestudyplan_list_used_categories' => [ //web service function name.
'classname' => '\local_treestudyplan\courseservice', //class containing the external function.
'methodname' => 'list_used_categories', //external function name.
'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).
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required.
'ajax' => true,
'loginrequired' => true,
],
'local_treestudyplan_scan_badge_progress' => [ //web service function name
'classname' => '\local_treestudyplan\courseservice', //class containing the external function
'methodname' => 'scan_badge_progress', //external function name
'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)
'capabilities' => 'local/treestudyplan:viewuserreports', // Advises the admin which capabilities are required
'local_treestudyplan_scan_badge_progress' => [ //web service function name.
'classname' => '\local_treestudyplan\courseservice', //class containing the external function.
'methodname' => 'scan_badge_progress', //external function name.
'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).
'capabilities' => 'local/treestudyplan:viewuserreports', // Advises the admin which capabilities are required.
'ajax' => true,
'loginrequired' => true,
],
'local_treestudyplan_scan_completion_progress' => [ //web service function name
'classname' => '\local_treestudyplan\courseservice', //class containing the external function
'methodname' => 'scan_completion_progress', //external function name
'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)
'capabilities' => 'local/treestudyplan:viewuserreports', // Advises the admin which capabilities are required
'local_treestudyplan_scan_completion_progress' => [ //web service function name.
'classname' => '\local_treestudyplan\courseservice', //class containing the external function.
'methodname' => 'scan_completion_progress', //external function name.
'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).
'capabilities' => 'local/treestudyplan:viewuserreports', // Advises the admin which capabilities are required.
'ajax' => true,
'loginrequired' => true,
],
'local_treestudyplan_scan_grade_progress' => [ //web service function name
'classname' => '\local_treestudyplan\courseservice', //class containing the external function
'methodname' => 'scan_grade_progress', //external function name
'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)
'capabilities' => 'local/treestudyplan:viewuserreports', // Advises the admin which capabilities are required
'local_treestudyplan_scan_grade_progress' => [ //web service function name.
'classname' => '\local_treestudyplan\courseservice', //class containing the external function.
'methodname' => 'scan_grade_progress', //external function name.
'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).
'capabilities' => 'local/treestudyplan:viewuserreports', // Advises the admin which capabilities are required.
'ajax' => true,
'loginrequired' => true,
],
'local_treestudyplan_cascade_cohortsync' => [ //web service function name
'classname' => '\local_treestudyplan\associationservice', //class containing the external function
'methodname' => 'cascade_cohortsync', //external function name
'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)
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required
'local_treestudyplan_cascade_cohortsync' => [ //web service function name.
'classname' => '\local_treestudyplan\associationservice', //class containing the external function.
'methodname' => 'cascade_cohortsync', //external function name.
'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).
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required.
'ajax' => true,
'loginrequired' => true,
],
'local_treestudyplan_course_period_timing' => [ //web service function name
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function
'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
'type' => 'read', //database rights of the web service function (read, write)
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required
],
'local_treestudyplan_course_period_timing' => [ //web service function name.
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function.
'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.
'type' => 'read', //database rights of the web service function (read, write).
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required.
'ajax' => true,
'loginrequired' => true,
],
'local_treestudyplan_set_studyitem_span' => [ //web service function name
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function
'methodname' => 'set_studyitem_span', //external function name
'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)
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required
],
'local_treestudyplan_set_studyitem_span' => [ //web service function name.
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function.
'methodname' => 'set_studyitem_span', //external function name.
'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).
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required.
'ajax' => true,
'loginrequired' => true,
],
],
];

View File

@ -1,4 +1,25 @@
<?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 = [
[
'classname' => 'local_treestudyplan\task\autocohortsync',

View File

@ -1,4 +1,25 @@
<?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) {
global $DB;
$dbman = $DB->get_manager();
@ -266,9 +287,9 @@ function xmldb_local_treestudyplan_upgrade($oldversion) {
if (!$dbman->table_exists($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");
foreach($plans as $p){
foreach ($plans as $p) {
$o = [ "studyplan_id" => $p->id,
"periods" => $p->slots,
"fullname" => $p->name,
@ -277,17 +298,17 @@ function xmldb_local_treestudyplan_upgrade($oldversion) {
"startdate" => $p->startdate,
"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
// And update their record to reference the page instead of the plan
$lines = $DB->get_recordset("local_treestudyplan_line",["studyplan_id" => $p->id]);
foreach($lines as $l){
// Now find all studyline references to the studyplan .
// And update their record to reference the page instead of the plan.
$lines = $DB->get_recordset("local_treestudyplan_line", ["studyplan_id" => $p->id]);
foreach ($lines as $l) {
$lo = [
"id" => $l->id,
"page_id"=> $pageid,
];
$DB->update_record("local_treestudyplan_line",$lo);
$DB->update_record("local_treestudyplan_line", $lo);
}
$lines->close();
}
@ -347,16 +368,16 @@ function xmldb_local_treestudyplan_upgrade($oldversion) {
if (!$dbman->table_exists($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");
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;
$ystart = strtotime($r->startdate)+0;
$yend = strtotime($r->enddate)+0;
if($yend < $ystart){
// If no end time is given, assume a year duration for period calculations
if ($yend < $ystart) {
// If no end time is given, assume a year duration for period calculations.
$ydelta = (365*24*60*60)/$pcount;
}
else{
@ -364,19 +385,19 @@ function xmldb_local_treestudyplan_upgrade($oldversion) {
}
$ptime = $ydelta / $pcount;
for($i=0; $i < $pcount; $i++){
for($i=0; $i < $pcount; $i++) {
$pnum = $i+1;
$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,
"period" => $pnum,
"fullname" => "Period {$pnum}",
"shortname" => "P{$pnum}",
"startdate" => date("Y-m-d",$pstart),
"enddate" => date("Y-m-d",$pend)
"startdate" => date("Y-m-d", $pstart),
"enddate" => date("Y-m-d", $pend)
];
$DB->insert_record("local_treestudyplan_period",$o);
$DB->insert_record("local_treestudyplan_period", $o);
}
}
$recordset->close();
@ -458,8 +479,8 @@ function xmldb_local_treestudyplan_upgrade($oldversion) {
if ($oldversion < 2023082100) {
// 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
// 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.
// Define field studyplan_id to be dropped from local_treestudyplan_line.
$table = new xmldb_table('local_treestudyplan_line');

32
doc.php
View File

@ -1,11 +1,32 @@
<?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.'/weblib.php');
$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->set_pagelayout('base');
$PAGE->set_context($systemcontext);
@ -14,16 +35,15 @@ require_login();
$pi = $_SERVER['PATH_INFO'];
$file = $CFG->dirroot."/local/treestudyplan/doc".$pi;
// Fallback to index
if(!file_exists($file)){
// Fallback to index.
if (!file_exists($file)) {
$file = $CFG->dirroot."/local/treestudyplan/doc/index.htm";
}
$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 file_get_contents($file);
print $OUTPUT->footer();

View File

@ -1,23 +1,32 @@
<?php
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'])));
error_log("Using {$root}/config.php");
require_once($root."/config.php");
}
else
{
// If not, assume the cwd is not symlinked and proceed as we are used to
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("./lib.php");
require_once($CFG->libdir.'/weblib.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);
$resend = optional_param('resend', 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_context($systemcontext);
if($update > 0)
{
if ($update > 0) {
$PAGE->set_title(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();
print $OUTPUT->header();
if(!empty($add))
{
if (!empty($add)) {
$data = array(
'add' => 1,
);
}
else if(!empty($update))
{
else if (!empty($update)) {
$data = $DB->get_record("local_treestudyplan_invit", array('id' => $update));
$data->update = $update;
if(empty($data) || $data->user_id != $USER->id)
if (empty($data) || $data->user_id != $USER->id)
{
print_error('invalidaction');
exit;
}
}
else if(!empty($resend))
{
else if (!empty($resend)) {
$data = $DB->get_record("local_treestudyplan_invit", array('id' => $resend));
$data->resend = $resend;
if(empty($data) || $data->user_id != $USER->id)
if (empty($data) || $data->user_id != $USER->id)
{
print_error('invalidaction');
exit;
}
// Do some resending of an invitation
// Do some resending of an invitation.
local_treestudyplan_send_invite($data->id);
@ -82,11 +87,10 @@ else if(!empty($resend))
print $OUTPUT->footer();
exit;
}
else if(!empty($delete))
{
else if (!empty($delete)) {
$data = $DB->get_record("local_treestudyplan_invit", array('id' => $delete));
$data->delete = $delete;
if(empty($data) || $data->user_id != $USER->id)
if (empty($data) || $data->user_id != $USER->id)
{
print_error('invalidaction');
exit;
@ -105,13 +109,11 @@ else {
$mform = new reportinvite_form();
$mform->set_data($data);
if($mform->is_cancelled())
{
if ($mform->is_cancelled()) {
redirect("$CFG->wwwroot/local/treestudyplan/invitations.php");
}
else if ($data = $mform->get_data())
{
if(!empty($data->update))
else if ($data = $mform->get_data()) {
if (!empty($data->update))
{
$id = $data->update;
@ -123,25 +125,25 @@ else if ($data = $mform->get_data())
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);
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");
}
@ -152,7 +154,7 @@ else if ($data = $mform->get_data())
else
{
$data = null;
if($unitid > 0)
if ($unitid > 0)
{
}

View File

@ -1,4 +1,25 @@
<?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.'/weblib.php');
@ -7,71 +28,70 @@ use \local_treestudyplan\courseservice;
$systemcontext = context_system::instance();
$PAGE->set_url("/local/treestudyplan/edit-plan.php",array());
$PAGE->set_url("/local/treestudyplan/edit-plan.php", array());
require_login();
// Figure out the context (category or system, based on either category or context parameter)
$categoryid = optional_param('categoryid', 0, PARAM_INT); // Category id
$contextid = optional_param('contextid', 0, PARAM_INT); // Context id
if($categoryid > 0){
// Figure out the context (category or system, based on either category or context parameter).
$categoryid = optional_param('categoryid', 0, PARAM_INT); // Category id.
$contextid = optional_param('contextid', 0, PARAM_INT); // Context id.
if ($categoryid > 0) {
$studyplancontext = context_coursecat::instance($categoryid);
}
elseif($contextid > 0)
{
else if ($contextid > 0) {
$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;
}
else
else
{
$studyplancontext = $systemcontext;
}
}
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");
$contextid=1; // fallback to system context
foreach($available_contexts as $ctx){
if($ctx->count > 0){
$contextid=1; // fallback to system context.
foreach ($available_contexts as $ctx) {
if ($ctx->count > 0) {
$contextid = $ctx->ctxid;
break;
}
}
// reload page with selected category
$url = new \moodle_url('/local/treestudyplan/edit-plan.php',["contextid" => $contextid]);
// reload page with selected category.
$url = new \moodle_url('/local/treestudyplan/edit-plan.php', ["contextid" => $contextid]);
header('Location: '.$url->out(false), true, 302);
exit;
}
require_capability('local/treestudyplan:editstudyplan',$studyplancontext);
$contextname = $studyplancontext->get_context_name(false,false);
require_capability('local/treestudyplan:editstudyplan', $studyplancontext);
$contextname = $studyplancontext->get_context_name(false, false);
$PAGE->set_pagelayout('coursecategory');
$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);
if($studyplancontext->id > 1){
if ($studyplancontext->id > 1) {
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/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");
//Local translate function
function t($str, $param=null, $plugin='local_treestudyplan'){
print get_string($str,$plugin,$param);
//Local translate function.
function t($str, $param=null, $plugin='local_treestudyplan') {
print get_string($str, $plugin, $param);
}
print $OUTPUT->header();
@ -86,11 +106,11 @@ print $OUTPUT->header();
</div>
</div>
<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-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':''"
><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>
</div>
<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>
<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-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;
<t-studyplan-edit
@creating=""
@ -122,9 +142,9 @@ print $OUTPUT->header();
<div v-else class='t-studyplan-notselected'>
<p><?php t("studyplan_noneselected"); ?></p>
<b-card-group deck>
<s-studyplan-card
v-for='(studyplan,planindex) in studyplans'
:key='studyplan.id'
<s-studyplan-card
v-for='(studyplan, planindex) in studyplans'
:key='studyplan.id'
v-model='studyplans[planindex]'
open
@open='selectStudyplan(studyplan)'
@ -151,7 +171,7 @@ print $OUTPUT->header();
<div class='t-toolbox-preface'>
<b-form-checkbox v-model="toolbox.right" switch><?php t("toolbar-right");?></b-form-checkbox>
</div>
<b-tabs content-class='mt-3'>
<b-tabs content-class='mt-3'>
<b-tab title="<?php t('courses')?>">
<t-coursecat-list v-model="courses"></t-coursecat-list>
</b-tab>

View File

@ -1,14 +1,34 @@
<?php
if(isset($_SERVER['SCRIPT_FILENAME']))
{
// If SCRIPT_FILENAME is set, use that so the symlinked directories the developmen environment uses are handled correctly
// 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
*/
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'])));
error_log("Using {$root}/config.php");
require_once($root."/config.php");
}
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");
}
@ -19,10 +39,10 @@ use local_treestudyplan;
$INVITED_URL = "/local/treestudyplan/invited.php";
//admin_externalpage_setup('major');
//admin_externalpage_setup('major');.
$systemcontext = context_system::instance();
$PAGE->set_url("/local/treestudyplan/invitations.php",array());
$PAGE->set_url("/local/treestudyplan/invitations.php", array());
require_login();
$PAGE->set_pagelayout('base');
@ -30,16 +50,15 @@ $PAGE->set_context($systemcontext);
$PAGE->set_title(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/buttonlinks', 'init');
// retrieve list of courses that the student is enrolled in
$sent = optional_param('sent','',PARAM_INT);
if(!empty($sent))
{
// retrieve list of courses that the student is enrolled in.
$sent = optional_param('sent', '', PARAM_INT);
if (!empty($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));
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 "<thead>";
print "<th>".get_string('invite_name','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_name', 'local_treestudyplan')."</th>";
print "<th>".get_string('invite_email', 'local_treestudyplan')."</th>";
print "<th>".get_string('invite_date', 'local_treestudyplan')."</th>";
print "<th>&nbsp;</th>";
print "</thead>";
print "<tbody>";
if(count($invites) > 0)
{
foreach($invites as $invite)
if (count($invites) > 0) {
foreach ($invites as $invite)
{
$testlink = $INVITED_URL."?key={$invite->invitekey}";
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='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 " data-confirmtext='".get_string('invite_confirm_resend','local_treestudyplan',$invite->name)."'";
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 " data-confirmtext='".get_string('invite_confirm_resend', 'local_treestudyplan', $invite->name)."'";
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 " ><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 " 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 " 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 "</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>";

View File

@ -1,24 +1,43 @@
<?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");
//Local translate function
function t($str, $param=null, $plugin='local_treestudyplan'){
print get_string($str,$plugin,$param);
//Local translate function.
function t($str, $param=null, $plugin='local_treestudyplan') {
print get_string($str, $plugin, $param);
}
$systemcontext = context_system::instance();
$PAGE->set_pagelayout('base');
$PAGE->set_context($systemcontext);
// See if we can get a valid user for this invited
$invitekey = optional_param('key', '', PARAM_ALPHANUM); // module name
$PAGE->set_url("/local/treestudyplan/invited.php",array('key' => $invitekey));
// See if we can get a valid user for this invited.
$invitekey = optional_param('key', '', PARAM_ALPHANUM); // module name.
$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]);
if(empty($invite))
{
if (empty($invite)) {
$PAGE->set_title(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();
// render page for skill level 0 (global)
// render page for skill level 0 (global).
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 $OUTPUT->footer();
@ -38,10 +57,10 @@ if(empty($invite))
}
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/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));
$PAGE->set_title(get_string('report_invited', 'local_treestudyplan', "{$student->firstname} {$student->lastname}" ));

View File

@ -1,4 +1,25 @@
<?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['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_edit'] = "Edit an existing invitation";
$string['invite_name'] = "Name";
$string['invite_email'] = "Email";
$string['invite_date'] = "Date";
$string['invite_email'] = "Email";
$string['invite_date'] = "Date";
$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_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>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>
<a href="{$a->link}">{$a->link}</a></p>
<p>Kind regards,<br>
<p>Kind regards, <br>
{$a->sender}</p>
';

View File

@ -1,4 +1,24 @@
<?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';
@ -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_edit'] = "Uitnodiging bewerken";
$string['invite_name'] = "Naam";
$string['invite_email'] = "Email";
$string['invite_date'] = "Datum";
$string['invite_email'] = "Email";
$string['invite_date'] = "Datum";
$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_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>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>
<a href="{$a->link}">{$a->link}</a></p>
<p>Met vriendelijke groet,<br>
<p>Met vriendelijke groet, <br>
{$a->sender}</p>
';

185
lib.php
View File

@ -1,4 +1,25 @@
<?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');
use local_treestudyplan\local\helpers\webservicehelper;
@ -15,47 +36,47 @@ function local_treestudyplan_extend_navigation(global_navigation $navigation) {
$systemcontext = context_system::instance();
// 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"
// [your name for my studyplan]|/local/treestudyplan/myreport.php
// [your name for studyplan viewing]|/local/treestudyplan/view-plan.php
// [your name for studyplan managing]|/local/treestudyplan/edit-plan.php
// For example:
// Mijn studieplan|/local/treestudyplan/myreport.php
// Studieplannen|/local/treestudyplan/view-plan.php
// Studieplannen beheren|/local/treestudyplan/edit-plan.php
// 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".
// [your name for my studyplan]|/local/treestudyplan/myreport.php.
// [your name for studyplan viewing]|/local/treestudyplan/view-plan.php.
// [your name for studyplan managing]|/local/treestudyplan/edit-plan.php.
// For example:.
// Mijn studieplan|/local/treestudyplan/myreport.php.
// Studieplannen|/local/treestudyplan/view-plan.php.
// Studieplannen beheren|/local/treestudyplan/edit-plan.php.
// 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,
// 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
// 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,.
// 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.
$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);
if(!empty($userstudyplans))
if (!empty($userstudyplans))
{
// create studyplan node
// create studyplan node.
$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()),
global_navigation::TYPE_SYSTEM,
null,
global_navigation::TYPE_SYSTEM,
null,
"local_treestudyplan_myreport",
new pix_icon("myreport", '', 'local_treestudyplan')
);
$node->showinflatnavigation = true;
$node->showinsecondarynavigation=true;
// create invitenode node
// create invitenode node.
$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()),
global_navigation::TYPE_CUSTOM ,
null,
global_navigation::TYPE_CUSTOM ,
null,
"local_treestudyplan_invitemgmt",
new pix_icon("invitemgmt", '', 'local_treestudyplan')
);
@ -63,68 +84,68 @@ function local_treestudyplan_extend_navigation(global_navigation $navigation) {
$node->add_node($invitenode);
$navigation->add_node($node,'mycourses');
$navigation->add_node($node, 'mycourses');
}
else {
$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'))
{
$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()),
global_navigation::TYPE_SYSTEM ,
null,
global_navigation::TYPE_SYSTEM ,
null,
"local_treestudyplan_viewplan",
new pix_icon("viewplans", '', 'local_treestudyplan')
);
$node->showinflatnavigation = true;
$node->showinsecondarynavigation=true;
$navigation->add_node($node,'mycourses');
$navigation->add_node($node, 'mycourses');
}
else {
$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')
)
{
$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()),
global_navigation::TYPE_SYSTEM ,
null,
global_navigation::TYPE_SYSTEM ,
null,
"local_treestudyplan_editplan",
new pix_icon("viewplans", '', 'local_treestudyplan')
);
$node->showinflatnavigation = true;
$node->showinsecondarynavigation=true;
$navigation->add_node($node,'mycourses');
$navigation->add_node($node, 'mycourses');
}
else {
$hide_primary_hrefs[] = "/local/treestudyplan/edit-plan.php";
}
}
}
else {
$hide_primary_hrefs[] = "/local/treestudyplan/myreport.php";
$hide_primary_hrefs[] = "/local/treestudyplan/edit-plan.php";
$hide_primary_hrefs[] = "/local/treestudyplan/view-plan.php";
}
// create invitenode node
// create invitenode node.
$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()),
global_navigation::TYPE_USER ,
null,
global_navigation::TYPE_USER ,
null,
"local_treestudyplan_invitemgmt",
new pix_icon("nav_invited", '', 'local_treestudyplan')
);
$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]);
@ -134,27 +155,27 @@ function local_treestudyplan_extend_navigation(global_navigation $navigation) {
function local_treestudyplan_extend_navigation_category_settings($navigation, context_coursecat $coursecategorycontext) {
global $CFG, $PAGE;
$categoryid = $coursecategorycontext->instanceid;
if(has_capability('local/treestudyplan:editstudyplan',$coursecategorycontext)){
if (has_capability('local/treestudyplan:editstudyplan', $coursecategorycontext)) {
$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]),
global_navigation::TYPE_CATEGORY,
null,
global_navigation::TYPE_CATEGORY,
null,
"local_treestudyplan_editplan",
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(
get_string('link_viewplan',"local_treestudyplan"),
get_string('link_viewplan', "local_treestudyplan"),
new moodle_url($CFG->wwwroot . "/local/treestudyplan/view-plan.php", ["categoryid"=>$categoryid]),
global_navigation::TYPE_CATEGORY,
null,
global_navigation::TYPE_CATEGORY,
null,
"local_treestudyplan_viewplan",
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();
}
function local_treestudyplan_send_invite($inviteid)
{
global $DB,$USER,$CFG;
function local_treestudyplan_send_invite($inviteid) {
global $DB, $USER, $CFG;
$invite = $DB->get_record("local_treestudyplan_invit", array('id' => $inviteid));
$noreply = 'noreply@' . get_host_from_url($CFG->wwwroot);
$mailer = get_mailer();
$mailer->setFrom($noreply,"{$USER->firstname} {$USER->lastname}");
$mailer->addAddress($invite->email,$invite->name);
$mailer->addReplyTo($USER->email,"{$USER->firstname} {$USER->lastname}");
$mailer->setFrom($noreply, "{$USER->firstname} {$USER->lastname}");
$mailer->addAddress($invite->email, $invite->name);
$mailer->addReplyTo($USER->email, "{$USER->firstname} {$USER->lastname}");
$invitehref = $CFG->wwwroot."/local/treestudyplan/invited.php?key={$invite->invitekey}";
$data = [ 'permissions'=> '',
$data = [ 'permissions'=> '',
'invitee' => $invite->name,
'sender' => "{$USER->firstname} {$USER->lastname}",
'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";
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";
}
$body = get_string('invite_mail_text','local_treestudyplan',$data);
$subject = get_string('invite_mail_subject','local_treestudyplan', $data);
$body = get_string('invite_mail_text', 'local_treestudyplan', $data);
$subject = get_string('invite_mail_subject', 'local_treestudyplan', $data);
$html = "
<!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'>
<!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'>.
<head>
<meta http-equiv='Content-Type' content='text/html; charset=UTF-8' />
<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;
// By default wherecondition retrieves all users except the deleted, not confirmed and guest.
$params = ['cohortid' => $cohortid];
@ -257,24 +276,22 @@ function local_treestudyplan_find_cohortmembers($cohortid)
WHERE u.suspended = 0 AND u.id > 1
ORDER BY u.lastname
";
$availableusers = $DB->get_records_sql($sql,$params);
$availableusers = $DB->get_records_sql($sql, $params);
return $availableusers;
}
function local_treestudyplan_get_cohort_path($cohort)
{
function local_treestudyplan_get_cohort_path($cohort) {
$cohortcontext = context::instance_by_id($cohort->contextid);
if($cohortcontext && $cohortcontext->id != SYSCONTEXTID)
{
if ($cohortcontext && $cohortcontext->id != SYSCONTEXTID) {
$ctxpath = array_map(
function($ctx){ return $ctx->get_context_name(false);},
function($ctx) { return $ctx->get_context_name(false);},
$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[] = $cohort->name;
return implode(" / ",$ctxpath);
return implode(" / ", $ctxpath);
}
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 $DB;
$args = (object)$args;
$context = $args->context;
if(empty($args->cmid)){
if (empty($args->cmid)) {
return "RANDOM!";
}
@ -299,8 +316,8 @@ function local_treestudyplan_output_fragment_mod_edit_form($args){
// Check the course exists.
$course = \get_course($cm->course);
// require_login
require_login($course, false, $cm); // needed to setup proper $COURSE
// require_login.
require_login($course, false, $cm); // needed to setup proper $COURSE.
list($cm, $context, $module, $data, $cw) = \get_moduleinfo_data($cm, $course);

View File

@ -1,4 +1,25 @@
<?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.'/weblib.php');
@ -7,22 +28,22 @@ use local_treestudyplan;
$systemcontext = context_system::instance();
$PAGE->set_url("/local/treestudyplan/myreport.php",array());
$PAGE->set_url("/local/treestudyplan/myreport.php", array());
require_login();
$PAGE->set_pagelayout('embedded');
$PAGE->set_context($systemcontext);
$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_title(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/devstyles.css'));
$PAGE->requires->js_call_amd('local_treestudyplan/page-myreport', 'init');
//Local translate function
function t($str, $param=null, $plugin='local_treestudyplan'){
print get_string($str,$plugin,$param);
//Local translate function.
function t($str, $param=null, $plugin='local_treestudyplan') {
print get_string($str, $plugin, $param);
}
print $OUTPUT->header();

View File

@ -1,4 +1,25 @@
<?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.'/weblib.php');
@ -7,36 +28,36 @@ use local_treestudyplan;
$systemcontext = context_system::instance();
$PAGE->set_url("/local/treestudyplan/myreport.php",array());
$PAGE->set_url("/local/treestudyplan/myreport.php", array());
require_login();
$PAGE->set_pagelayout('base');
$PAGE->set_context($systemcontext);
$teachermode = has_capability("local/treestudyplan:viewuserreports",$systemcontext);
$teachermode = has_capability("local/treestudyplan:viewuserreports", $systemcontext);
if($teachermode){
$PAGE->set_title(get_string('myreport_teachermode','local_treestudyplan'));
$PAGE->set_heading(get_string('myreport_teachermode','local_treestudyplan'));
if ($teachermode) {
$PAGE->set_title(get_string('myreport_teachermode', 'local_treestudyplan'));
$PAGE->set_heading(get_string('myreport_teachermode', 'local_treestudyplan'));
} else {
$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_title(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/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
function t($str, $param=null, $plugin='local_treestudyplan'){
print get_string($str,$plugin,$param);
//Local translate function.
function t($str, $param=null, $plugin='local_treestudyplan') {
print get_string($str, $plugin, $param);
}
print $OUTPUT->header();
?>
<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>
<?php } ?>
</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
// 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
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// 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
// 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
// You should have received a copy of the GNU General Public License.
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
@ -19,7 +19,7 @@
*
* @package local_chronotable
* @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();
@ -29,10 +29,10 @@ use local_treestudyplan\aggregator;
if ($hassiteconfig) {
/**************************************
*
*
* Main studyplan settings
*
*************************************/
*
*************************************/
// Create admin settings category.
$ADMIN->add('courses', new admin_category('local_treestudyplan',
@ -43,15 +43,15 @@ if ($hassiteconfig) {
get_string('settingspage', 'local_treestudyplan', null, true));
// GOAL AGGREGATION SETTINGS
// GOAL AGGREGATION SETTINGS.
$page->add(new admin_setting_heading('local_treestudyplan/aggregation_heading',
get_string('setting_aggregation_heading', 'local_treestudyplan'),
get_string('settingdesc_aggregation_heading', 'local_treestudyplan')
));
$aggregators = [];
foreach(aggregator::list() as $a){
$aggregators[$a] = get_string("{$a}_aggregator_title",'local_treestudyplan', null, true);
foreach (aggregator::list() as $a) {
$aggregators[$a] = get_string("{$a}_aggregator_title", 'local_treestudyplan', null, true);
}
$page->add(new admin_setting_configselect('local_treestudyplan/aggregation_mode',
@ -61,7 +61,7 @@ if ($hassiteconfig) {
$aggregators
));
// DISPLAY COURSE INFO SETTINGS
// DISPLAY COURSE INFO SETTINGS.
$page->add(new admin_setting_heading('local_treestudyplan/display_heading',
get_string('setting_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")];
$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();
foreach($cat->get_fields() as $field) {
foreach ($cat->get_fields() as $field) {
$fieldname = $field->get_formatted_name();
$fieldid = $field->get("shortname");
$displayfields["customfield_".$fieldid] = $catname.": ".$fieldname;
@ -86,7 +86,7 @@ if ($hassiteconfig) {
$displayfields
));
// BISTATE AGGREGATON DEFAULTS
// BISTATE AGGREGATON DEFAULTS.
$page->add(new admin_setting_heading('local_treestudyplan/bistate_aggregation_heading',
get_string('setting_bistate_heading', 'local_treestudyplan'),
get_string('settingdesc_bistate_heading', 'local_treestudyplan')
@ -129,10 +129,10 @@ if ($hassiteconfig) {
$ADMIN->add('local_treestudyplan', $page);
/**************************************
*
*
* Manage plans link (systemwide)
*
*************************************/
*
*************************************/
$ADMIN->add('local_treestudyplan', new admin_externalpage(
'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',
get_string('settingspage_csync', 'local_treestudyplan', null, true));
// Description heading
// Description heading.
$page_csync->add(new admin_setting_heading('local_treestudyplan/csync_heading',
get_string('setting_csync_heading', 'local_treestudyplan'),
get_string('settingdesc_csync_heading', 'local_treestudyplan')
));
// Enable/disable cohort sync
$page_csync->add(new admin_setting_configcheckbox('local_treestudyplan/csync_enable',
// Enable/disable cohort sync.
$page_csync->add(new admin_setting_configcheckbox('local_treestudyplan/csync_enable',
get_string('setting_csync_enable_field', 'local_treestudyplan'),
get_string('settingdesc_csync_enable_field', 'local_treestudyplan'),
false
));
// Enable/disable autoremove
// Enable/disable autoremove.
$page_csync->add(new admin_setting_configcheckbox('local_treestudyplan/csync_autoremove',
get_string('setting_csync_autoremove_field', 'local_treestudyplan'),
get_string('settingdesc_csync_autoremove_field', 'local_treestudyplan'),
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',
get_string('setting_csync_remember_manual_csync_field', 'local_treestudyplan'),
get_string('settingdesc_csync_remember_manual_csync_field', 'local_treestudyplan'),
true
));
// Enable/disable group creation
// Enable/disable group creation.
$page_csync->add(new admin_setting_configcheckbox('local_treestudyplan/csync_creategroup',
get_string('setting_csync_creategroup_field', 'local_treestudyplan'),
get_string('settingdesc_csync_creategroup_field', 'local_treestudyplan'),
true
));
// Sync users too yes/no?
// Sync users too yes/no?.
$page_csync->add(new admin_setting_configcheckbox('local_treestudyplan/csync_users',
get_string('setting_csync_users_field', 'local_treestudyplan'),
get_string('settingdesc_csync_users_field', 'local_treestudyplan'),
true
));
// Select csync enrol role
// Select csync enrol role.
if (!during_initial_install()) {
$options = get_default_enrol_roles(context_system::instance());
$student = get_archetype_roles('student');
@ -206,19 +206,19 @@ if ($hassiteconfig) {
$ADMIN->add('local_treestudyplan', $page_csync);
/**************************************
*
*
* Grade and scale interpretation
*
*************************************/
*
*************************************/
$ADMIN->add('local_treestudyplan', new admin_externalpage(
'local_treestudyplan_gradeconfig',
get_string('cfg_grades', 'local_treestudyplan', null, true),
$CFG->wwwroot . '/local/treestudyplan/cfg_grades.php'));
/**************************************
*
*
* Add the help link (Temporary until a better place is found)
*
*
**************************************/
$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
$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)
// 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
*/
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 = [
'theme_boost' => 2019052000,

View File

@ -1,4 +1,25 @@
<?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");
use \local_treestudyplan\courseservice;
@ -7,66 +28,65 @@ require_once($CFG->libdir.'/weblib.php');
$systemcontext = context_system::instance();
$PAGE->set_url("/local/treestudyplan/view-plan.php",array());
$PAGE->set_url("/local/treestudyplan/view-plan.php", array());
require_login();
// Figure out the context (category or system, based on either category or context parameter)
$categoryid = optional_param('categoryid', 0, PARAM_INT); // Category id
$contextid = optional_param('contextid', 0, PARAM_INT); // Context id
if($categoryid > 0){
// Figure out the context (category or system, based on either category or context parameter).
$categoryid = optional_param('categoryid', 0, PARAM_INT); // Category id.
$contextid = optional_param('contextid', 0, PARAM_INT); // Context id.
if ($categoryid > 0) {
$studyplancontext = context_coursecat::instance($categoryid);
}
elseif($contextid > 0)
{
else if ($contextid > 0) {
$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;
}
else
else
{
$studyplancontext = $systemcontext;
}
}
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");
$contextid=1; // fallback to system context
foreach($available_contexts as $ctx){
if($ctx->count > 0){
$contextid=1; // fallback to system context.
foreach ($available_contexts as $ctx) {
if ($ctx->count > 0) {
$contextid = $ctx->ctxid;
break;
}
}
// reload page with selected category
$url = new \moodle_url('/local/treestudyplan/view-plan.php',["contextid" => $contextid]);
// reload page with selected category.
$url = new \moodle_url('/local/treestudyplan/view-plan.php', ["contextid" => $contextid]);
header('Location: '.$url->out(false), true, 302);
exit;
}
require_capability('local/treestudyplan:viewuserreports',$studyplancontext);
$contextname = $studyplancontext->get_context_name(false,false);
require_capability('local/treestudyplan:viewuserreports', $studyplancontext);
$contextname = $studyplancontext->get_context_name(false, false);
$PAGE->set_pagelayout('base');
$PAGE->set_context($studyplancontext);
$PAGE->set_title(get_string('view_plan','local_treestudyplan')." - ".$contextname);
$PAGE->set_heading(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);
if($studyplancontext->id > 1){
if ($studyplancontext->id > 1) {
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/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
function t($str, $param=null, $plugin='local_treestudyplan'){
print get_string($str,$plugin,$param);
//Local translate function.
function t($str, $param=null, $plugin='local_treestudyplan') {
print get_string($str, $plugin, $param);
}
print $OUTPUT->header();
@ -78,11 +98,11 @@ print $OUTPUT->header();
</div>
</div>
<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-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':''"
><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>
</div>
<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>
<span v-if='displayedstudyplan'><?php t("studyplan_select"); ?></span>&nbsp;
<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-button variant='primary' v-if='associatedstudents && associatedstudents.length > 0' v-b-toggle.toolbox-sidebar><?php t('selectstudent_btn') ?></b-button>
</div>
@ -104,9 +124,9 @@ print $OUTPUT->header();
<div v-else class='t-studyplan-notselected'>
<p><?php t("studyplan_noneselected"); ?></p>
<b-card-group deck>
<s-studyplan-card
v-for='(studyplan,planindex) in studyplans'
:key='studyplan.id'
<s-studyplan-card
v-for='(studyplan, planindex) in studyplans'
:key='studyplan.id'
v-model='studyplans[planindex]'
open
@open='selectStudyplan(studyplan)'