Moodle code style fixes part 1
This commit is contained in:
		
							parent
							
								
									ab8a3c6f84
								
							
						
					
					
						commit
						1a3df05195
					
				
					 63 changed files with 4467 additions and 3885 deletions
				
			
		|  | @ -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> | ||||
|     */ | ||||
|     */ | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
							
								
								
									
										42
									
								
								build.php
									
									
									
									
									
								
							
							
						
						
									
										42
									
								
								build.php
									
									
									
									
									
								
							|  | @ -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); | ||||
| } | ||||
|  |  | |||
							
								
								
									
										168
									
								
								cfg_grades.php
									
									
									
									
									
								
							
							
						
						
									
										168
									
								
								cfg_grades.php
									
									
									
									
									
								
							|  | @ -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')); | ||||
|  |  | |||
|  | @ -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; | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -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(); | ||||
| 
 | ||||
|  |  | |||
|  | @ -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++; | ||||
|             } | ||||
|         } | ||||
|  |  | |||
|  | @ -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");.
 | ||||
|     } | ||||
| } | ||||
|  | @ -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.
 | ||||
| 
 | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -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; | ||||
|             } | ||||
|         } | ||||
|  |  | |||
|  | @ -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++; | ||||
|                     } | ||||
|                 } | ||||
|  |  | |||
|  | @ -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); | ||||
|  |  | |||
|  | @ -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; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -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); | ||||
|             } | ||||
|         } | ||||
|  |  | |||
|  | @ -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.
 | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -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), | ||||
|  |  | |||
|  | @ -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]); | ||||
|             } | ||||
|         } | ||||
|  |  | |||
|  | @ -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 { | ||||
|  |  | |||
|  | @ -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; | ||||
|                 } | ||||
|  |  | |||
|  | @ -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; | ||||
|                 } | ||||
|  |  | |||
|  | @ -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 { | ||||
|  |  | |||
|  | @ -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; | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -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); | ||||
|         } | ||||
|     } | ||||
|  |  | |||
|  | @ -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); | ||||
|         } | ||||
|  |  | |||
|  | @ -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); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -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); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -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; | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -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; | ||||
|  |  | |||
|  | @ -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); | ||||
|  |  | |||
|  | @ -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; | ||||
|  |  | |||
|  | @ -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; | ||||
|  |  | |||
|  | @ -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); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|  |  | |||
|  | @ -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; | ||||
|  |  | |||
|  | @ -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; | ||||
|                         } | ||||
|                     } | ||||
|  |  | |||
|  | @ -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; | ||||
|     } | ||||
|  |  | |||
|  | @ -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
											
										
									
								
							|  | @ -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; | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -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 { | ||||
|  |  | |||
|  | @ -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); | ||||
|             } | ||||
|  |  | |||
|  | @ -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."}"); | ||||
|     } | ||||
|  |  | |||
|  | @ -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()); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  |  | |||
|  | @ -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"); | ||||
|                     } | ||||
|  |  | |||
|  | @ -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 = [ | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										878
									
								
								db/services.php
									
									
									
									
									
								
							
							
						
						
									
										878
									
								
								db/services.php
									
									
									
									
									
								
							|  | @ -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, | ||||
|     ],     | ||||
|     ], | ||||
| ]; | ||||
|  |  | |||
							
								
								
									
										21
									
								
								db/tasks.php
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								db/tasks.php
									
									
									
									
									
								
							|  | @ -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', | ||||
|  |  | |||
|  | @ -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
									
									
									
									
									
								
							
							
						
						
									
										32
									
								
								doc.php
									
									
									
									
									
								
							|  | @ -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(); | ||||
|  |  | |||
|  | @ -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) | ||||
| 	{ | ||||
| 		 | ||||
| 	} | ||||
|  |  | |||
|  | @ -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> 
 | ||||
| 			<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>  | ||||
| 			<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> | ||||
|  |  | |||
|  | @ -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> </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>"; | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										45
									
								
								invited.php
									
									
									
									
									
								
							
							
						
						
									
										45
									
								
								invited.php
									
									
									
									
									
								
							|  | @ -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}" )); | ||||
|  |  | |||
|  | @ -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> | ||||
| '; | ||||
| 
 | ||||
|  |  | |||
|  | @ -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
									
									
									
									
									
								
							
							
						
						
									
										185
									
								
								lib.php
									
									
									
									
									
								
							|  | @ -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); | ||||
| 
 | ||||
|  |  | |||
|  | @ -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(); | ||||
|  |  | |||
							
								
								
									
										47
									
								
								myreport.php
									
									
									
									
									
								
							
							
						
						
									
										47
									
								
								myreport.php
									
									
									
									
									
								
							|  | @ -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> | ||||
|  |  | |||
							
								
								
									
										168
									
								
								reports.php
									
									
									
									
									
								
							
							
						
						
									
										168
									
								
								reports.php
									
									
									
									
									
								
							|  | @ -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(); | ||||
| } | ||||
							
								
								
									
										78
									
								
								settings.php
									
									
									
									
									
								
							
							
						
						
									
										78
									
								
								settings.php
									
									
									
									
									
								
							|  | @ -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( | ||||
|  |  | |||
|  | @ -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, | ||||
|             ], | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										28
									
								
								version.php
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								version.php
									
									
									
									
									
								
							|  | @ -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, | ||||
|  |  | |||
|  | @ -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> 
 | ||||
| 			<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>  | ||||
| 			<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)' | ||||
|  |  | |||
		Reference in a new issue
	
	 PMKuipers
						PMKuipers