Completed code guidelines rework

This commit is contained in:
PMKuipers 2024-06-02 23:23:32 +02:00
parent e72be595aa
commit 64c821bbe9
59 changed files with 939 additions and 818 deletions

View File

@ -23,7 +23,6 @@
namespace local_treestudyplan; namespace local_treestudyplan;
use moodle_exception; use moodle_exception;
use ValueError;
defined('MOODLE_INTERNAL') || die(); defined('MOODLE_INTERNAL') || die();
@ -78,7 +77,7 @@ abstract class aggregator {
* Create a new aggregatior object based on the specified method * Create a new aggregatior object based on the specified method
* @param mixed $method Aggregation method * @param mixed $method Aggregation method
* @param mixed $configstr Configuration string for aggregator * @param mixed $configstr Configuration string for aggregator
* @throws ValueError If method is not found * @throws moodle_exception If method is not found
*/ */
public static function create($method, $configstr): self { public static function create($method, $configstr): self {
if (self::supported($method)) { if (self::supported($method)) {
@ -98,7 +97,7 @@ abstract class aggregator {
public static function create_or_default($method, $configstr): self { public static function create_or_default($method, $configstr): self {
try { try {
return self::create($method, $configstr); return self::create($method, $configstr);
} catch (\ValueError $x) { } catch (\moodle_exception $x) {
return self::create(self::FALLBACK, ""); return self::create(self::FALLBACK, "");
} }
} }

View File

@ -58,7 +58,8 @@ class associationservice extends \external_api {
"lastname" => new \external_value(PARAM_TEXT, 'last name'), "lastname" => new \external_value(PARAM_TEXT, 'last name'),
"idnumber" => new \external_value(PARAM_TEXT, 'id number'), "idnumber" => new \external_value(PARAM_TEXT, 'id number'),
"email" => new \external_value(PARAM_TEXT, 'email address'), "email" => new \external_value(PARAM_TEXT, 'email address'),
"lastaccess" => new \external_value(PARAM_INT, 'id of last access this user had to any course in the studyplan', VALUE_OPTIONAL), "lastaccess" => new \external_value(PARAM_INT,
'id of last access this user had to any course in the studyplan', VALUE_OPTIONAL),
]); ]);
} }
@ -131,6 +132,11 @@ class associationservice extends \external_api {
} }
/**
* Get last access time of user to any of the courses in the studyplan
* @param $userid ID of user
* @param $studyplanid ID of studyplan
*/
public static function user_lastaccess($userid, $studyplanid=null) { public static function user_lastaccess($userid, $studyplanid=null) {
global $DB; global $DB;
if (!empty($studyplanid)) { if (!empty($studyplanid)) {
@ -735,7 +741,7 @@ class associationservice extends \external_api {
webservicehelper::require_capabilities(self::CAP_EDIT, $studyplan->context()); webservicehelper::require_capabilities(self::CAP_EDIT, $studyplan->context());
$user = $DB->get_record("user", ["id" => $userid]); $user = $DB->get_record("user", ["id" => $userid]);
if ( has_capability(self::CAP_COACH, $studyplan->context(), $user)) { if (has_capability(self::CAP_COACH, $studyplan->context(), $user)) {
if (!$DB->record_exists('local_treestudyplan_coach', ['studyplan_id' => $studyplanid, 'user_id' => $userid])) { if (!$DB->record_exists('local_treestudyplan_coach', ['studyplan_id' => $studyplanid, 'user_id' => $userid])) {
$id = $DB->insert_record('local_treestudyplan_coach', [ $id = $DB->insert_record('local_treestudyplan_coach', [
'studyplan_id' => $studyplanid, 'studyplan_id' => $studyplanid,

View File

@ -60,7 +60,7 @@ class badgeinfo {
BADGE_STATUS_ACTIVE => 0, BADGE_STATUS_ACTIVE => 0,
BADGE_STATUS_INACTIVE_LOCKED => 1, BADGE_STATUS_INACTIVE_LOCKED => 1,
BADGE_STATUS_ACTIVE_LOCKED => 1, BADGE_STATUS_ACTIVE_LOCKED => 1,
BADGE_STATUS_ARCHIVED => 1, // We don't want to edit archived badges anyway.... . BADGE_STATUS_ARCHIVED => 1, // We don't want to edit archived badges anyway.....
]; ];
/** /**
@ -270,13 +270,17 @@ class badgeinfo {
return $badge; return $badge;
} }
/**
* Define structure for badge completion parts
* @param object $value VALUE_OPTIONAL or VALUE_REQUIRED
*/
protected static function badge_completion_structure($value) { protected static function badge_completion_structure($value) {
return new \external_single_structure([ return new \external_single_structure([
"types" => new \external_multiple_structure(new \external_single_structure([ "types" => new \external_multiple_structure(new \external_single_structure([
'criteria' => new \external_multiple_structure(new \external_single_structure([ 'criteria' => new \external_multiple_structure(new \external_single_structure([
"title" => new \external_value(PARAM_RAW, 'criterion title'), "title" => new \external_value(PARAM_RAW, 'criterion title'),
"description" => new \external_value(PARAM_RAW, 'criterion description'), "description" => new \external_value(PARAM_RAW, 'criterion description'),
"link"=> new \external_value(PARAM_RAW, 'link to criterion resource', VALUE_OPTIONAL), "link" => new \external_value(PARAM_RAW, 'link to criterion resource', VALUE_OPTIONAL),
"requirements" => new \external_multiple_structure(new \external_single_structure([ "requirements" => new \external_multiple_structure(new \external_single_structure([
"title" => new \external_value(PARAM_RAW, 'requirment title'), "title" => new \external_value(PARAM_RAW, 'requirment title'),
"completed" => new \external_value(PARAM_BOOL, 'criterion is completed or not', VALUE_OPTIONAL), "completed" => new \external_value(PARAM_BOOL, 'criterion is completed or not', VALUE_OPTIONAL),
@ -297,6 +301,10 @@ class badgeinfo {
], 'badge completion information', $value); ], 'badge completion information', $value);
} }
/**
* Generate completion model of this badge for a given user
* @param int $userid id of user to check for
*/
protected function badge_completion_data($userid): array { protected function badge_completion_data($userid): array {
$count = 0; $count = 0;
@ -323,14 +331,14 @@ class badgeinfo {
// Determine how to process the progress data, depending on the TYPE's aggregation. // Determine how to process the progress data, depending on the TYPE's aggregation.
if ($typeagg == BADGE_CRITERIA_AGGREGATION_ANY) { if ($typeagg == BADGE_CRITERIA_AGGREGATION_ANY) {
$typecount = 1; $typecount = 1;
$typeprogress = ($typeprogress > 0)?1:0; $typeprogress = ($typeprogress > 0) ? 1 : 0;
} }
// Determine how to patch this data into the overall progress numbers, depending on the OVERALL aggregation. // Determine how to patch this data into the overall progress numbers, depending on the OVERALL aggregation.
if ($badgeagg == BADGE_CRITERIA_AGGREGATION_ANY) { if ($badgeagg == BADGE_CRITERIA_AGGREGATION_ANY) {
/* If ANY completion overall, count only the criteria type with the highest completion percentage -. /* If ANY completion overall, count only the criteria type with the highest completion percentage -.
Overwrite data if current type is more complete */ Overwrite data if current type is more complete */
$typefraction = ($typecount > 0) ? ($typeprogress / $typecount):0; $typefraction = ($typecount > 0) ? ($typeprogress / $typecount) : ($typeprogress / $typecount);
if ($typefraction > $fraction || ($fraction == 0 && $typecount > $count)) { if ($typefraction > $fraction || ($fraction == 0 && $typecount > $count)) {
$fraction = $typefraction; $fraction = $typefraction;
$count = $typecount; $count = $typecount;
@ -342,7 +350,7 @@ class badgeinfo {
$progress += $typeprogress; $progress += $typeprogress;
} }
$aggrgationhandle = ($typeagg == BADGE_CRITERIA_AGGREGATION_ALL)?"all":"any"; $aggrgationhandle = ($typeagg == BADGE_CRITERIA_AGGREGATION_ALL) ? "all" : "any";
$typeinfo = [ $typeinfo = [
'title' => ucfirst(get_string("criteria_descr_$type", "badges", get_string($aggrgationhandle, "core"))), 'title' => ucfirst(get_string("criteria_descr_$type", "badges", get_string($aggrgationhandle, "core"))),
'aggregation' => $aggrgationhandle, 'aggregation' => $aggrgationhandle,
@ -355,7 +363,7 @@ class badgeinfo {
} }
} }
$aggrgationhandle = ($badgeagg == BADGE_CRITERIA_AGGREGATION_ALL)?"all":"any"; $aggrgationhandle = ($badgeagg == BADGE_CRITERIA_AGGREGATION_ALL) ? "all" : "any";
return [ return [
"types" => $types, "types" => $types,
"title" => ucfirst(get_string("criteria_descr_0", "badges", mb_strtolower(get_string($aggrgationhandle, "core")))), "title" => ucfirst(get_string("criteria_descr_0", "badges", mb_strtolower(get_string($aggrgationhandle, "core")))),
@ -441,10 +449,11 @@ class badgeinfo {
$title = get_string('error:nosuchmod', 'badges'); $title = get_string('error:nosuchmod', 'badges');
$description = get_string('error:nosuchmod', 'badges'); $description = get_string('error:nosuchmod', 'badges');
} else { } else {
$title = \html_writer::tag('b', '"' . get_string('modulename', $mod->modname) . ' - ' . $mod->name . '"');; $title = \html_writer::tag('b', '"' . get_string('modulename', $mod->modname) . ' - ' . $mod->name . '"');;
$description = \html_writer::tag('b', '"' . get_string('modulename', $mod->modname) . ' - ' . $mod->name . '"'); $description = \html_writer::tag('b', '"' . get_string('modulename', $mod->modname) . ' - ' . $mod->name . '"');
if (isset($p['bydate'])) { if (isset($p['bydate'])) {
$description .= get_string('criteria_descr_bydate', 'badges', userdate($p['bydate'], get_string('strftimedate', 'core_langconfig'))); $description .= get_string('criteria_descr_bydate', 'badges',
userdate($p['bydate'], get_string('strftimedate', 'core_langconfig')));
} }
} }
@ -453,15 +462,16 @@ class badgeinfo {
"description" => $description, "description" => $description,
"requirements" => [ "requirements" => [
'completion' => [ 'completion' => [
'title' => get_string('completeactivity', 'core'). 'title' => get_string('completeactivity', 'core').
get_string('modulename', $mod->modname) . ' - ' . $mod->name, get_string('modulename', $mod->modname) . ' - ' . $mod->name,
], ],
] ],
]; ];
if (isset($p["bydate"])) { if (isset($p["bydate"])) {
$subcrit["requirements"]["bydate"] = [ $subcrit["requirements"]["bydate"] = [
'title' => get_string('criteria_descr_bydate', 'badges', userdate($p['bydate'], get_string('strftimedate', 'core_langconfig'))), 'title' => get_string('criteria_descr_bydate', 'badges',
userdate($p['bydate'], get_string('strftimedate', 'core_langconfig'))),
]; ];
} }
@ -513,16 +523,15 @@ class badgeinfo {
]; ];
if (isset($userid)) { if (isset($userid)) {
$crit = $DB->get_record('badge_manual_award', ['issuerrole' => $p['role'], 'recipientid' => $userid, 'badgeid' => $crit->badgeid]); $crit = $DB->get_record('badge_manual_award',
['issuerrole' => $p['role'], 'recipientid' => $userid, 'badgeid' => $crit->badgeid]);
$subcrit["completed"] = $crit !== false; $subcrit["completed"] = $crit !== false;
} }
$list[] = $subcrit; $list[] = $subcrit;
} }
} else if ($crit->criteriatype == BADGE_CRITERIA_TYPE_SOCIAL) {
/* Unused in moodle - probably deprecated */
} else if ($crit->criteriatype == BADGE_CRITERIA_TYPE_COURSE || $crit->criteriatype == BADGE_CRITERIA_TYPE_COURSESET) { } else if ($crit->criteriatype == BADGE_CRITERIA_TYPE_COURSE || $crit->criteriatype == BADGE_CRITERIA_TYPE_COURSESET) {
if ($crit->criteriatype == BADGE_CRITERIA_TYPE_COURSE) { if ($crit->criteriatype == BADGE_CRITERIA_TYPE_COURSE) {
$params = [reset($crit->params)]; // Only use the first parameter $params = [reset($crit->params)]; // Only use the first parameter.
} else { } else {
$params = $crit->params; $params = $crit->params;
} }
@ -534,7 +543,8 @@ class badgeinfo {
} else { } else {
$description = \html_writer::tag('b', '"' . $course->fullname . '"'); $description = \html_writer::tag('b', '"' . $course->fullname . '"');
if (isset($p['bydate'])) { if (isset($p['bydate'])) {
$description .= get_string('criteria_descr_bydate', 'badges', userdate($p['bydate'], get_string('strftimedate', 'core_langconfig'))); $description .= get_string( 'criteria_descr_bydate', 'badges',
userdate($p['bydate'], get_string('strftimedate', 'core_langconfig')));
} }
if (isset($p['grade'])) { if (isset($p['grade'])) {
$description .= get_string('criteria_descr_grade', 'badges', $p['grade']); $description .= get_string('criteria_descr_grade', 'badges', $p['grade']);
@ -549,18 +559,18 @@ class badgeinfo {
'completion' => [ 'completion' => [
'title' => get_string('coursecompleted', 'completion'), 'title' => get_string('coursecompleted', 'completion'),
], ],
] ],
]; ];
if (isset($p["grade"])) { if (isset($p["grade"])) {
$subcrit["requirements"]["grade"] = [ $subcrit["requirements"]["grade"] = [
'title' => get_string('criteria_descr_grade', 'badges', $p["grade"]), 'title' => get_string('criteria_descr_grade', 'badges', $p["grade"]),
]; ];
} }
if (isset($p["bydate"])) { if (isset($p["bydate"])) {
$subcrit["requirements"]["bydate"] = [ $subcrit["requirements"]["bydate"] = [
'title' => get_string('criteria_descr_bydate', 'badges', userdate($p['bydate'], get_string('strftimedate', 'core_langconfig'))), 'title' => get_string('criteria_descr_bydate', 'badges',
userdate($p['bydate'], get_string('strftimedate', 'core_langconfig'))),
]; ];
} }
@ -577,7 +587,8 @@ class badgeinfo {
} }
$checkdate = true; $checkdate = true;
if (isset($p["bydate"])) { if (isset($p["bydate"])) {
$checkdate = ((bool) $coursecompletion->timecompleted) && ($coursecompletion->timecompleted <= $p["bydate"]); $checkdate = ((bool) $coursecompletion->timecompleted) &&
($coursecompletion->timecompleted <= $p["bydate"]);
$subcrit["requirements"]["bydate"]["completed"] = (bool) $checkdate; $subcrit["requirements"]["bydate"]["completed"] = (bool) $checkdate;
} }
@ -592,7 +603,7 @@ class badgeinfo {
$fields = profile_get_custom_fields(); $fields = profile_get_custom_fields();
// Get formatted field name if such field exists. // Get formatted field name if such field exists.
$fieldname = isset($fields[$p['field']]->name) ? $fieldname = isset($fields[$p['field']]->name) ?
format_string($fields[$p['field']]->name): null; format_string($fields[$p['field']]->name) : null;
} else { } else {
$fieldname = \core_user\fields::get_display_name($p['field']); $fieldname = \core_user\fields::get_display_name($p['field']);
} }
@ -614,7 +625,7 @@ class badgeinfo {
if (isset($userid)) { if (isset($userid)) {
$join = ''; $join = '';
$where = ''; $where = '';
$sqlparams = [ 'userid'=> $userid]; $sqlparams = [ 'userid' => $userid];
if (is_numeric($p['field'])) { if (is_numeric($p['field'])) {
// This is a custom field. // This is a custom field.
@ -773,19 +784,8 @@ class badgeinfo {
throw new \moodle_exception("\$course argument must be course id or course object", "local_treestudyplan"); throw new \moodle_exception("\$course argument must be course id or course object", "local_treestudyplan");
} }
/*
$crit->citeriatype = BADGE_CRITERIA_TYPE_COURSE
$badge->params[0] = ["course"=> $courseid]
$crit->citeriatype = BADGE_CRITERIA_TYPE_COURSESET
$badge->params[] = [["course"=> $courseid]...]
$crit->citeriatype = BADGE_CRITERIA_TYPE_ACTIVITY
$badge->params[] = [["module"=> $cmid]...]
$crit->citeriatype = BADGE_CRITERIA_TYPE_COMPETENCY
$badge->params[] = [["competency"=> $competencyid]...]
*/
$search = trim($search); $search = trim($search);
$conditions = ""; $conditions = "";
$basesqlparams = ['courseid' => $courseid]; $basesqlparams = ['courseid' => $courseid];
if (strlen($search) > 0) { if (strlen($search) > 0) {
@ -799,7 +799,8 @@ class badgeinfo {
} }
[$ctypesql, $ctypeinparams] = $DB->get_in_or_equal([BADGE_CRITERIA_TYPE_COURSESET, BADGE_CRITERIA_TYPE_COURSE], SQL_PARAMS_NAMED); [$ctypesql, $ctypeinparams] = $DB->get_in_or_equal([BADGE_CRITERIA_TYPE_COURSESET, BADGE_CRITERIA_TYPE_COURSE],
SQL_PARAMS_NAMED);
$sqlparams = array_merge($basesqlparams, $ctypeinparams); $sqlparams = array_merge($basesqlparams, $ctypeinparams);
$sql = "SELECT DISTINCT b.id from {badge} b $sql = "SELECT DISTINCT b.id from {badge} b
@ -847,7 +848,7 @@ class badgeinfo {
} }
$search = trim($search); $search = trim($search);
$conditions = ""; $conditions = "";
$basesqlparams = ['courseid' => $courseid]; $basesqlparams = ['courseid' => $courseid];
if (strlen($search) > 0) { if (strlen($search) > 0) {
@ -860,7 +861,7 @@ class badgeinfo {
$basesqlparams = array_merge($basesqlparams, $inparams); $basesqlparams = array_merge($basesqlparams, $inparams);
} }
// Get all course badges for this course // Get all course badges for this course.
$badgesids = []; $badgesids = [];
$sql = "SELECT DISTINCT b.id from {badge} b $sql = "SELECT DISTINCT b.id from {badge} b
WHERE b.courseid = :courseid AND $conditions"; WHERE b.courseid = :courseid AND $conditions";
@ -886,14 +887,14 @@ class badgeinfo {
$badgeids = []; $badgeids = [];
foreach (studyline::find_page_children($page) as $line) { foreach (studyline::find_page_children($page) as $line) {
foreach (studyitem::find_studyline_children($line) as $item) { foreach (studyitem::find_studyline_children($line) as $item) {
if ($item->type() == studyitem::COURSE && $item->courseid() ) { if ($item->type() == studyitem::COURSE && $item->courseid()) {
$courseid = $item->courseid(); $courseid = $item->courseid();
$relatedbadges = badgeinfo::find_badges_by_course_relation($courseid, $search, $active); $relatedbadges = self::find_badges_by_course_relation($courseid, $search, $active);
foreach ($relatedbadges as $id) { foreach ($relatedbadges as $id) {
$badgeids[] = $id; $badgeids[] = $id;
} }
if ($includecoursebadges) { if ($includecoursebadges) {
$coursebadges = badgeinfo::find_course_badges($courseid, $search, $active); $coursebadges = self::find_course_badges($courseid, $search, $active);
foreach ($coursebadges as $id) { foreach ($coursebadges as $id) {
$badgeids[] = $id; $badgeids[] = $id;
} }
@ -923,7 +924,7 @@ class badgeinfo {
global $DB; global $DB;
$search = trim($search); $search = trim($search);
$conditions = "TRUE"; $conditions = "TRUE";
$basesqlparams = ['type' => \BADGE_TYPE_SITE]; $basesqlparams = ['type' => \BADGE_TYPE_SITE];
if (strlen($search) > 0) { if (strlen($search) > 0) {
@ -936,7 +937,7 @@ class badgeinfo {
$basesqlparams = array_merge($basesqlparams, $inparams); $basesqlparams = array_merge($basesqlparams, $inparams);
} }
// Get all course badges for this course // Get all course badges for this course.
$sql = "SELECT DISTINCT b.id from {badge} b $sql = "SELECT DISTINCT b.id from {badge} b
WHERE $conditions"; WHERE $conditions";

View File

@ -122,7 +122,7 @@ class cascadecohortsync {
$records = $DB->get_records("enrol", $searchparams); $records = $DB->get_records("enrol", $searchparams);
foreach ($records as $instance) { foreach ($records as $instance) {
if (!empty($instance->customtext4)) { if (!empty($instance->customtext4)) {
// Only add to the list if customtext4 is not empty // Only add to the list if customtext4 is not empty.
$list[] = $instance; $list[] = $instance;
} }
} }
@ -175,7 +175,7 @@ class cascadecohortsync {
// Find the study lines associated to this studyplan. // Find the study lines associated to this studyplan.
$lines = $this->studyplan->get_all_studylines(); $lines = $this->studyplan->get_all_studylines();
foreach($lines as $line) { foreach ($lines as $line) {
$this->syncline($line); $this->syncline($line);
} }
@ -192,7 +192,7 @@ class cascadecohortsync {
if ($line->enrollable()) { if ($line->enrollable()) {
// Since the studyline is enrollable, we need to cascade by student. // Since the studyline is enrollable, we need to cascade by student.
foreach ($courseids as $courseid) { foreach ($courseids as $courseid) {
/* 1: Associate the users by individual association */ /* 1: Associate the users by individual association */
$course = \get_course($courseid); $course = \get_course($courseid);
$userids = $line->get_enrolled_userids(); $userids = $line->get_enrolled_userids();
if (count($userids) > 0) { if (count($userids) > 0) {
@ -215,7 +215,8 @@ class cascadecohortsync {
} }
} }
/* 2: Remove the cohort sync for this studyplan for this line if it exists, since this course should not have cohort sync form this studyplan /* 2: Remove the cohort sync for this studyplan for this line if it exists,
since this course should not have cohort sync form this studyplan
Then - if no one uses the link anymore, deactivate it... Then - if no one uses the link anymore, deactivate it...
This does not remove the sync from courses that are unlinked from a studplan. 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 But maybe we do not want that anyway, since it is generally a good idea to keep student
@ -224,7 +225,7 @@ class cascadecohortsync {
if (get_config("local_treestudyplan", "csync_autoremove")) { if (get_config("local_treestudyplan", "csync_autoremove")) {
// Only try the autoremove if the option is enabled. // Only try the autoremove if the option is enabled.
foreach($this->list_cohortsyncs($courseid) as $instance) { foreach ($this->list_cohortsyncs($courseid) as $instance) {
$this->unlink_cohortsync($instance); $this->unlink_cohortsync($instance);
} }
@ -255,14 +256,13 @@ class cascadecohortsync {
// Create group: . // Create group: .
// 1: check if a link exists. // 1: check if a link exists.
// If not, make it (maybe use some of the custom text to list the studyplans involved). // If not, make it (maybe use some of the custom text to list the studyplans involved).
if ($instance = $DB->get_record('enrol', $instanceparams)) { if ($instance = $DB->get_record('enrol', $instanceparams)) {
// It already exists. // It already exists.
// Check if this studyplan is already referenced in customtext4 in json format. // Check if this studyplan is already referenced in customtext4 in json format.
// TODO: Check this code - Maybe add option to not remember manually added stuff .
$plans = json_decode($instance->customtext4); $plans = json_decode($instance->customtext4);
if ($plans == false || !is_array(($plans))) { if ($plans == false || !is_array(($plans))) {
// If the data was not an array (null or garbled), count it as manually added. // If the data was not an array (null or garbled), count it as manually added.
@ -297,7 +297,8 @@ class cascadecohortsync {
// In the customtext4 field in json format. // In the customtext4 field in json format.
$instance = $DB->get_record('enrol', ['id' => $instanceid]); $instance = $DB->get_record('enrol', ['id' => $instanceid]);
$this->enrol->update_instance($instance, (object)["customtext4" => json_encode([(int)($this->studyplanid)])]); $this->enrol->update_instance($instance, (object)[
"customtext4" => json_encode([(int)($this->studyplanid)])]);
// Successfully added a valid new instance, so now instantiate it. // Successfully added a valid new instance, so now instantiate it.
// First synchronise the enrolment. // First synchronise the enrolment.
@ -309,7 +310,7 @@ class cascadecohortsync {
} }
} }
/* 2: Check if there are cohort links for this studyplan in this course that should be removed. /* 2: Check if there are cohort links for this studyplan in this course that should be removed.
A: Check if there are cohort links that are no longer related to this studyplan. A: Check if there are cohort links that are no longer related to this studyplan.
B: Check if these links are valid through another studyplan... B: Check if these links are valid through another studyplan...
If no one uses the link anymore, deactivate it... If no one uses the link anymore, deactivate it...
@ -322,7 +323,7 @@ class cascadecohortsync {
if (get_config("local_treestudyplan", "csync_autoremove")) { if (get_config("local_treestudyplan", "csync_autoremove")) {
// Only try the autoremove if the option is enabled. // Only try the autoremove if the option is enabled.
foreach($this->list_cohortsyncs($courseid) as $instance) { foreach ($this->list_cohortsyncs($courseid) as $instance) {
// Only check the records that have studyplan information in the customtext4 field. // Only check the records that have studyplan information in the customtext4 field.
// First check if the cohort is not one of the cohort id's we have associated. // First check if the cohort is not one of the cohort id's we have associated.
if (!in_array($instance->customint1, $this->cohortids)) { if (!in_array($instance->customint1, $this->cohortids)) {

View File

@ -78,8 +78,8 @@ class cascadeusersync {
// And find the users that are linked to this studyplan. // And find the users that are linked to this studyplan.
$userids = $this->studyplan->get_linked_user_ids(); $userids = $this->studyplan->get_linked_user_ids();
foreach($lines as $line) { foreach ($lines as $line) {
$this->syncline($line); $this->syncline($line);
} }
} }

View File

@ -252,7 +252,7 @@ class corecompletioninfo {
$info = [ $info = [
"conditions" => $conditions, "conditions" => $conditions,
"aggregation" => self::aggregation_handle($this->completion->get_aggregation_method()), "aggregation" => self::aggregation_handle($this->completion->get_aggregation_method()),
"enabled" => $this->completion->is_enabled() "enabled" => $this->completion->is_enabled(),
]; ];
// Check if completion tracking is enabled for this course - otherwise, revert to defaults . // Check if completion tracking is enabled for this course - otherwise, revert to defaults .
@ -261,7 +261,7 @@ class corecompletioninfo {
// Loop through all condition types to see if they are applicable. // Loop through all condition types to see if they are applicable.
foreach (self::completiontypes() as $type => $handle) { foreach (self::completiontypes() as $type => $handle) {
$criterias = $this->completion->get_criteria($type); // Returns array of relevant criteria items. $criterias = $this->completion->get_criteria($type); // Returns array of relevant criteria items.
if (count($criterias) > 0 ) { if (count($criterias) > 0) {
// Only take it into account if the criteria count is > 0. // Only take it into account if the criteria count is > 0.
$cinfo = [ $cinfo = [
"type" => $handle, "type" => $handle,
@ -314,7 +314,7 @@ class corecompletioninfo {
} else { } else {
$details['criteria'] = $cm->get_formatted_name(); $details['criteria'] = $cm->get_formatted_name();
} }
// Set title based on cm formatted name // Set title based on cm formatted name.
$title = $cm->get_formatted_name(); $title = $cm->get_formatted_name();
// Build requirements. // Build requirements.
$details['requirement'] = []; $details['requirement'] = [];
@ -349,12 +349,11 @@ class corecompletioninfo {
$displaytype = \grade_get_setting($this->course->id, 'displaytype', $CFG->grade_displaytype); $displaytype = \grade_get_setting($this->course->id, 'displaytype', $CFG->grade_displaytype);
$gradepass = $criteria->gradepass; $gradepass = $criteria->gradepass;
// Find grade item for course result. // Find grade item for course result.
$gi = new \grade_item(['courseid' => $this->course->id, 'itemtype' => 'course']); $gi = new \grade_item(['courseid' => $this->course->id, 'itemtype' => 'course']);
$displaygrade = \grade_format_gradevalue($gradepass, $gi, true, $displaytype, 1); $displaygrade = \grade_format_gradevalue($gradepass, $gi, true, $displaytype, 1);
$details = [ $details = [
"type" => get_string('coursegrade', 'completion'), "type" => get_string('coursegrade', 'completion'),
"criteria" => get_string('graderequired', 'completion'), "criteria" => get_string('graderequired', 'completion'),
// TODO: convert to selected representation (letter, percentage, etc).
"requirement" => get_string('graderequired', 'completion') "requirement" => get_string('graderequired', 'completion')
.": ".$displaygrade, .": ".$displaygrade,
"status" => "", "status" => "",
@ -479,7 +478,7 @@ class corecompletioninfo {
"details" => $criteria->get_details($completion), "details" => $criteria->get_details($completion),
"completed" => $completion->is_complete(), // Make sure to override for activi. "completed" => $completion->is_complete(), // Make sure to override for activi.
"status" => self::completion_handle( "status" => self::completion_handle(
$completion->is_complete() ? COMPLETION_COMPLETE : COMPLETION_INCOMPLETE), $completion->is_complete() ? COMPLETION_COMPLETE : COMPLETION_INCOMPLETE),
]; ];
if ($type == COMPLETION_CRITERIA_TYPE_ACTIVITY) { if ($type == COMPLETION_CRITERIA_TYPE_ACTIVITY) {
@ -496,14 +495,14 @@ class corecompletioninfo {
and we want to show similar behaviour. This happens when completion data is reset and we want to show similar behaviour. This happens when completion data is reset
in a module in a module
*/ */
if ( !$completion->is_complete() if (!$completion->is_complete()
&& &&
in_array($gradecompletion, [COMPLETION_COMPLETE, COMPLETION_COMPLETE_PASS])) { in_array($gradecompletion, [COMPLETION_COMPLETE, COMPLETION_COMPLETE_PASS])) {
/* If a passing grade was provided, but the activity was not completed, /* If a passing grade was provided, but the activity was not completed,
* most likely the completion data was erased. * most likely the completion data was erased.
*/ */
if ( !is_null($cm->completiongradeitemnumber) || ($cm->completionpassgrade) ) { if (!is_null($cm->completiongradeitemnumber) || ($cm->completionpassgrade)) {
// Show a warning if this activity has grade completions to help make sense of the completion. // Show a warning if this activity has grade completions to help make sense of the completion.
$iinfo["warning"] = get_string("warning_incomplete_pass", "local_treestudyplan"); $iinfo["warning"] = get_string("warning_incomplete_pass", "local_treestudyplan");
} else { } else {
@ -543,8 +542,8 @@ class corecompletioninfo {
$iinfo['grade'] = $this->format_course_grade($rawgrade); $iinfo['grade'] = $this->format_course_grade($rawgrade);
$rq = floatval($iinfo['details']['requirement']); $rq = floatval($iinfo['details']['requirement']);
$iinfo['details']['requirement'] = $this->format_course_grade($rq); $iinfo['details']['requirement'] = $this->format_course_grade($rq);
; ;
$iinfo["status"] = $completion->is_complete() ? "complete-pass" : "complete-fail"; $iinfo["status"] = $completion->is_complete() ? "complete-pass" : "complete-fail";
if ($cinfo["status"] == "incomplete") { if ($cinfo["status"] == "incomplete") {
$cinfo["status"] = "progress"; $cinfo["status"] = "progress";
} }
@ -589,8 +588,6 @@ class corecompletioninfo {
* @return stdClass|null object containing 'grade' and optional 'feedback' attribute * @return stdClass|null object containing 'grade' and optional 'feedback' attribute
*/ */
private function get_grade($cm, $userid) { private function get_grade($cm, $userid) {
// TODO: Display grade in the way described in the course setup (with letters if needed).
$gi = grade_item::fetch(['itemtype' => 'mod', $gi = grade_item::fetch(['itemtype' => 'mod',
'itemmodule' => $cm->modname, 'itemmodule' => $cm->modname,
'iteminstance' => $cm->instance, 'iteminstance' => $cm->instance,
@ -624,7 +621,6 @@ class corecompletioninfo {
* @return stdClass|null object containing 'grade' and optional 'feedback' attribute * @return stdClass|null object containing 'grade' and optional 'feedback' attribute
*/ */
private function format_course_grade($grade) { private function format_course_grade($grade) {
// TODO: Display grade in the way described in the course setup (with letters if needed).
$gi = new \grade_item(['itemtype' => 'course', $gi = new \grade_item(['itemtype' => 'course',
'courseid' => $this->course->id]); 'courseid' => $this->course->id]);
@ -740,7 +736,7 @@ class corecompletioninfo {
$result = new \stdClass; $result = new \stdClass;
$result->count = $count; $result->count = $count;
$result->completed = $completed; $result->completed = $completed;
$result->percentage = ($count > 0) ? (($completed / $count) * 100): 0; $result->percentage = ($count > 0) ? (($completed / $count) * 100) : 0;
return $result; return $result;
} }

View File

@ -79,10 +79,14 @@ class coursecompetencyinfo {
], "details about gradable submissions", $value); ], "details about gradable submissions", $value);
} }
/**
* Convert completion stats to web service model.
* @param object $stats Stats object
*/
protected static function completionstats($stats) { protected static function completionstats($stats) {
return [ return [
"students" => $stats->count, "students" => $stats->count,
"completed" => 0, "completed" => 0,
"ungraded" => $stats->nneedreview, "ungraded" => $stats->nneedreview,
"completed_pass" => $stats->nproficient, "completed_pass" => $stats->nproficient,
"completed_fail" => $stats->nfailed, "completed_fail" => $stats->nfailed,
@ -113,7 +117,6 @@ class coursecompetencyinfo {
"courseproficient" => new \external_value(PARAM_BOOL, 'course competency proficiency', VALUE_OPTIONAL), "courseproficient" => new \external_value(PARAM_BOOL, 'course competency proficiency', VALUE_OPTIONAL),
"needreview" => new \external_value(PARAM_BOOL, 'waiting for review or review in progress', VALUE_OPTIONAL), "needreview" => new \external_value(PARAM_BOOL, 'waiting for review or review in progress', VALUE_OPTIONAL),
"completionstats" => static::completionstats_structure(VALUE_OPTIONAL), "completionstats" => static::completionstats_structure(VALUE_OPTIONAL),
"count" => new \external_value(PARAM_INT, 'number of students in stats', VALUE_OPTIONAL),
"required" => new \external_value(PARAM_BOOL, 'if required in parent competency rule', VALUE_OPTIONAL), "required" => new \external_value(PARAM_BOOL, 'if required in parent competency rule', VALUE_OPTIONAL),
"points" => new \external_value(PARAM_INT, 'number of points in parent competency rule', VALUE_OPTIONAL), "points" => new \external_value(PARAM_INT, 'number of points in parent competency rule', VALUE_OPTIONAL),
"progress" => new \external_value(PARAM_INT, 'number completed child competencies/points', VALUE_OPTIONAL), "progress" => new \external_value(PARAM_INT, 'number completed child competencies/points', VALUE_OPTIONAL),
@ -121,7 +124,8 @@ class coursecompetencyinfo {
"feedback" => new \external_value(PARAM_RAW, 'feedback provided with this competency', VALUE_OPTIONAL), "feedback" => new \external_value(PARAM_RAW, 'feedback provided with this competency', VALUE_OPTIONAL),
]; ];
if ($recurse) { if ($recurse) {
$struct["children"] = new \external_multiple_structure(self::competencyinfo_structure(false), 'child competencies', VALUE_OPTIONAL); $struct["children"] = new \external_multiple_structure(
self::competencyinfo_structure(false), 'child competencies', VALUE_OPTIONAL);
} }
return new \external_single_structure($struct, 'course completion info'); return new \external_single_structure($struct, 'course completion info');
} }
@ -157,12 +161,12 @@ class coursecompetencyinfo {
private function competencyinfo_model($competency, $userid=null): array { private function competencyinfo_model($competency, $userid=null): array {
$displayfield = get_config("local_treestudyplan", "competency_displayname"); $displayfield = get_config("local_treestudyplan", "competency_displayname");
$detailfield = get_config("local_treestudyplan", "competency_detailfield"); $detailfield = get_config("local_treestudyplan", "competency_detailfield");
$headingfield = ($displayfield != 'description')?$displayfield:"shortname"; $headingfield = ($displayfield != 'description') ? $displayfield : "shortname";
$framework = $competency->get_framework(); $framework = $competency->get_framework();
$heading = $framework->get($headingfield); $heading = $framework->get($headingfield);
if (empty(trim($heading))) { if (empty(trim($heading))) {
$heading = $framework->get('shortname'); // Fall back to shortname if heading field is empty $heading = $framework->get('shortname'); // Fall back to shortname if heading field is empty.
} }
$path = [[ $path = [[
'id' => $framework->get('id'), 'id' => $framework->get('id'),
@ -174,7 +178,7 @@ class coursecompetencyinfo {
$competencypath[] = $c->get('shortname'); $competencypath[] = $c->get('shortname');
$heading = $c->get($headingfield); $heading = $c->get($headingfield);
if (empty(trim($heading))) { if (empty(trim($heading))) {
$heading = $c->get('shortname'); // Fall back to shortname if heading field is empty $heading = $c->get('shortname'); // Fall back to shortname if heading field is empty.
} }
$path[] = [ $path[] = [
'id' => $c->get('id'), 'id' => $c->get('id'),
@ -186,7 +190,7 @@ class coursecompetencyinfo {
$heading = $competency->get($headingfield); $heading = $competency->get($headingfield);
if (empty(trim($heading))) { if (empty(trim($heading))) {
$heading = $competency->get('shortname'); // Fall back to shortname if heading field is empty $heading = $competency->get('shortname'); // Fall back to shortname if heading field is empty.
} }
$path[] = [ $path[] = [
'id' => $competency->get('id'), 'id' => $competency->get('id'),
@ -197,7 +201,7 @@ class coursecompetencyinfo {
$title = $competency->get($displayfield); $title = $competency->get($displayfield);
if (empty(trim($title))) { if (empty(trim($title))) {
$title = $competency->get('shortname'); // Fall back to shortname if heading field is empty $title = $competency->get('shortname'); // Fall back to shortname if heading field is empty.
} }
$model = [ $model = [
'id' => $competency->get('id'), 'id' => $competency->get('id'),
@ -226,7 +230,7 @@ class coursecompetencyinfo {
$nproficient = 0; $nproficient = 0;
$cis = []; $cis = [];
foreach($coursecompetencies as $c) { foreach ($coursecompetencies as $c) {
$ci = $this->competencyinfo_model($c); $ci = $this->competencyinfo_model($c);
if (!empty($studentlist)) { if (!empty($studentlist)) {
$stats = $this->proficiency_stats($c, $studentlist); $stats = $this->proficiency_stats($c, $studentlist);
@ -262,9 +266,9 @@ class coursecompetencyinfo {
$ruleconfig = json_decode($ruleconfig); $ruleconfig = json_decode($ruleconfig);
$points = $ruleconfig->base->points; $points = $ruleconfig->base->points;
// Make a nice map of the competency rule config // Make a nice map of the competency rule config.
$crlist = []; $crlist = [];
foreach($ruleconfig->competencies as $cr) { foreach ($ruleconfig->competencies as $cr) {
$crlist[$cr->id] = $cr; $crlist[$cr->id] = $cr;
} }
$ci["rule"] = $ruletext . " ({$points} ".get_string("points", "core_grades").")"; $ci["rule"] = $ruletext . " ({$points} ".get_string("points", "core_grades").")";
@ -272,11 +276,11 @@ class coursecompetencyinfo {
$ci["rule"] = $ruletext; $ci["rule"] = $ruletext;
} }
// get one level of children // Get one level of children.
$dids = competency::get_descendants_ids($c); $dids = competency::get_descendants_ids($c);
if (count($dids) > 0) { if (count($dids) > 0) {
$children = []; $children = [];
foreach($dids as $did) { foreach ($dids as $did) {
$cc = new competency($did); $cc = new competency($did);
$cci = $this->competencyinfo_model($cc); $cci = $this->competencyinfo_model($cc);
if (!empty($studentlist)) { if (!empty($studentlist)) {
@ -355,21 +359,21 @@ class coursecompetencyinfo {
$ruleconfig = json_decode($ruleconfig); $ruleconfig = json_decode($ruleconfig);
$pointsreq = $ruleconfig->base->points; $pointsreq = $ruleconfig->base->points;
$points = 0; $points = 0;
// Make a nice map of the competency rule config // Make a nice map of the competency rule config.
$crlist = []; $crlist = [];
foreach($ruleconfig->competencies as $cr) { foreach ($ruleconfig->competencies as $cr) {
$crlist[$cr->id] = $cr; $crlist[$cr->id] = $cr;
} }
} }
// get one level of children // Get one level of children.
$dids = competency::get_descendants_ids($c); $dids = competency::get_descendants_ids($c);
if (count($dids) > 0) { if (count($dids) > 0) {
$dcount = 0; $dcount = 0;
$dprogress = 0; $dprogress = 0;
$children = []; $children = [];
foreach($dids as $did) { foreach ($dids as $did) {
$cc = new competency($did); $cc = new competency($did);
$cci = $this->competencyinfo_model($cc, $userid); $cci = $this->competencyinfo_model($cc, $userid);
$cp = $p = $this->proficiency($cc, $userid); $cp = $p = $this->proficiency($cc, $userid);
@ -442,6 +446,11 @@ class coursecompetencyinfo {
return $list; return $list;
} }
/**
* Determine proficiency stats
* @param object $competency
* @param array $studentlist
*/
protected function proficiency_stats($competency, $studentlist) { protected function proficiency_stats($competency, $studentlist) {
$r = new \stdClass(); $r = new \stdClass();
$r->count = 0; $r->count = 0;
@ -453,8 +462,8 @@ class coursecompetencyinfo {
foreach ($studentlist as $sid) { foreach ($studentlist as $sid) {
$p = $this->proficiency($competency, $sid); $p = $this->proficiency($competency, $sid);
$r->count += 1; $r->count += 1;
$r->nproficient += ($p->proficient === true)?1:0; $r->nproficient += ($p->proficient === true) ? 1 : 0;
$r->nfailed += ($p->proficient === false)?1:0; $r->nfailed += ($p->proficient === false) ? 1 : 0;
$r->ncourseproficient += ($p->courseproficient) ? 1 : 0; $r->ncourseproficient += ($p->courseproficient) ? 1 : 0;
$r->nneedreview += ($p->needreview) ? 1 : 0; $r->nneedreview += ($p->needreview) ? 1 : 0;
} }
@ -535,7 +544,9 @@ class coursecompetencyinfo {
$ucc = self::get_user_competency_in_course($this->course->id, $userid, $competencyid); $ucc = self::get_user_competency_in_course($this->course->id, $userid, $competencyid);
$r->courseproficient = $ucc->get('proficiency'); $r->courseproficient = $ucc->get('proficiency');
$r->coursegrade = $scale->get_nearest_item($ucc->get('grade')); $r->coursegrade = $scale->get_nearest_item($ucc->get('grade'));
} catch (\Exception $x) {} } catch (\Exception $x) {
$ucc = null;
}
return $r; return $r;
} }
@ -552,12 +563,12 @@ class coursecompetencyinfo {
$competencyid = $competency->get('id'); $competencyid = $competency->get('id');
$uc = self::get_user_competency($userid, $competencyid); $uc = self::get_user_competency($userid, $competencyid);
// Get evidences and sort by creation date (newest first) // Get evidences and sort by creation date (newest first).
$evidence = evidence::get_records_for_usercompetency($uc->get('id'), \context_system::instance(), 'timecreated', "DESC"); $evidence = evidence::get_records_for_usercompetency($uc->get('id'), \context_system::instance(), 'timecreated', "DESC");
// Get the first valid note and return it; // Get the first valid note and return it.
foreach($evidence as $e) { foreach ($evidence as $e) {
if ( in_array($e->get('action'), [evidence::ACTION_OVERRIDE, evidence::ACTION_COMPLETE] )) { if (in_array($e->get('action'), [evidence::ACTION_OVERRIDE, evidence::ACTION_COMPLETE])) {
return $e->get('note'); return $e->get('note');
break; break;
} }
@ -575,13 +586,15 @@ class coursecompetencyinfo {
public static function require_competency(int $competencyid, int $itemid, bool $required) { public static function require_competency(int $competencyid, int $itemid, bool $required) {
global $DB; global $DB;
$item = studyitem::find_by_id($itemid); $item = studyitem::find_by_id($itemid);
// Make sure conditions are properly configured; // Make sure conditions are properly configured.
$conditions = []; $conditions = [];
try { try {
$conditions = json_decode($item->conditions(), true); $conditions = json_decode($item->conditions(), true);
} catch (\Exception $x) {} } catch (\Exception $x) {
$conditions = [];
}
// Make sure the competencied field exists // Make sure the competencied field exists.
if (!isset($conditions["competencies"]) || !is_array($conditions["competencies"])) { if (!isset($conditions["competencies"]) || !is_array($conditions["competencies"])) {
$conditions["competencies"] = []; $conditions["competencies"] = [];
} }
@ -595,7 +608,7 @@ class coursecompetencyinfo {
$conditions["competencies"][$competencyid]["required"] = boolval($required); $conditions["competencies"][$competencyid]["required"] = boolval($required);
} }
// Store conditions; // Store conditions.
$item->edit(["conditions" => json_encode($conditions)]); $item->edit(["conditions" => json_encode($conditions)]);
return success::success(); return success::success();
} }
@ -609,10 +622,12 @@ class coursecompetencyinfo {
$conditions = []; $conditions = [];
try { try {
$conditions = json_decode($this->studyitem->conditions(), true); $conditions = json_decode($this->studyitem->conditions(), true);
} catch (\Exception $x) {} } catch (\Exception $x) {
$conditions = [];
}
// Make sure the competencied field exists // Make sure the competencied field exists.
if ( isset($conditions["competencies"]) if (isset($conditions["competencies"])
&& is_array($conditions["competencies"]) && is_array($conditions["competencies"])
&& isset($conditions["competencies"][$competency->get("id")]) && isset($conditions["competencies"][$competency->get("id")])
&& isset($conditions["competencies"][$competency->get("id")]["required"]) && isset($conditions["competencies"][$competency->get("id")]["required"])

View File

@ -267,7 +267,7 @@ class courseinfo {
'fullname' => $this->course->fullname, 'fullname' => $this->course->fullname,
'shortname' => $this->course->shortname, 'shortname' => $this->course->shortname,
'displayname' => $this->displayname(), 'displayname' => $this->displayname(),
'context' => $contextinfo->model() 'context' => $contextinfo->model(),
]; ];
return $info; return $info;
@ -344,12 +344,12 @@ class courseinfo {
$info['grades'][] = $gradable->editor_model($this->studyitem); $info['grades'][] = $gradable->editor_model($this->studyitem);
} }
} }
if ($aggregator->use_corecompletioninfo()) { if ($aggregator->use_corecompletioninfo()) {
$cc = new corecompletioninfo($this->course, $this->studyitem); $cc = new corecompletioninfo($this->course, $this->studyitem);
$studentlist = $this->studyitem->studyline()->studyplan()->find_linked_userids(); $studentlist = $this->studyitem->studyline()->studyplan()->find_linked_userids();
$info['completion'] = $cc->editor_model($studentlist); $info['completion'] = $cc->editor_model($studentlist);
} }
if ($aggregator->use_coursecompetencies()) { if ($aggregator->use_coursecompetencies()) {
$ci = new coursecompetencyinfo($this->course, $this->studyitem); $ci = new coursecompetencyinfo($this->course, $this->studyitem);
$studentlist = $this->studyitem->studyline()->studyplan()->find_linked_userids(); $studentlist = $this->studyitem->studyline()->studyplan()->find_linked_userids();
$info['competency'] = $ci->editor_model($studentlist); $info['competency'] = $ci->editor_model($studentlist);
@ -467,11 +467,11 @@ class courseinfo {
$info['grades'][] = $gi->user_model($userid); $info['grades'][] = $gi->user_model($userid);
} }
} }
if ($aggregator->use_corecompletioninfo()) { if ($aggregator->use_corecompletioninfo()) {
$cc = new corecompletioninfo($this->course, $this->studyitem); $cc = new corecompletioninfo($this->course, $this->studyitem);
$info['completion'] = $cc->user_model($userid); $info['completion'] = $cc->user_model($userid);
} }
if ($aggregator->use_coursecompetencies()) { if ($aggregator->use_coursecompetencies()) {
$ci = new coursecompetencyinfo($this->course, $this->studyitem); $ci = new coursecompetencyinfo($this->course, $this->studyitem);
$info['competency'] = $ci->user_model($userid); $info['competency'] = $ci->user_model($userid);
} }
@ -502,7 +502,7 @@ class courseinfo {
*/ */
public function extrafields_model($includeteachervisible=false) { public function extrafields_model($includeteachervisible=false) {
$list = []; $list = [];
for ($i=1; $i <= 5; $i++) { for ($i = 1; $i <= 5; $i++) {
$field = get_config('local_treestudyplan', 'courseinfo'.$i.'_field'); $field = get_config('local_treestudyplan', 'courseinfo'.$i.'_field');
if ($field) { if ($field) {
$title = self::extrafields_localize_title(get_config('local_treestudyplan', 'courseinfo'.$i.'_title')); $title = self::extrafields_localize_title(get_config('local_treestudyplan', 'courseinfo'.$i.'_title'));
@ -516,7 +516,7 @@ class courseinfo {
"type" => $type, "type" => $type,
"fieldname" => $field, "fieldname" => $field,
"courseid" => $this->course->id, "courseid" => $this->course->id,
"checked" => ($type=="checkbox") ? ($raw ? true : false):null, "checked" => ($type == "checkbox") ? ($raw ? true : false) : null,
]; ];
} }
} }
@ -524,11 +524,15 @@ class courseinfo {
return $list; return $list;
} }
/**
* Localize title for extra field
* @param string $field
*/
protected static function extrafields_localize_title($field) { protected static function extrafields_localize_title($field) {
$lang = trim(current_language()); $lang = trim(current_language());
$lines = explode("\n", $field); $lines = explode("\n", $field);
$title = ""; $title = "";
$fallback = ""; // Fallback to first title $fallback = ""; // Fallback to first title.
foreach ($lines as $l) { foreach ($lines as $l) {
$parts = explode("|", $l, 2); $parts = explode("|", $l, 2);
if (count($parts) > 0) { if (count($parts) > 0) {
@ -537,7 +541,7 @@ class courseinfo {
$fallback = $parts[0]; $fallback = $parts[0];
} }
if (count($parts) == 1 && empty($title)) { if (count($parts) == 1 && empty($title)) {
// Set line without language as default if no localized line found // Set line without language as default if no localized line found.
$title = trim($parts[0]); $title = trim($parts[0]);
} else if (trim($parts[1]) == $lang) { } else if (trim($parts[1]) == $lang) {
return trim($parts[0]); return trim($parts[0]);
@ -546,7 +550,7 @@ class courseinfo {
} }
// Return default title or fall back to first localizef title. // Return default title or fall back to first localizef title.
return (strlen($title) > 0)?$title:$fallback; return (strlen($title) > 0) ? $title : $fallback;
} }
/** /**
@ -558,13 +562,13 @@ class courseinfo {
if ($fieldname == "description") { if ($fieldname == "description") {
// Process embedded files. // Process embedded files.
$value = \file_rewrite_pluginfile_urls( $value = \file_rewrite_pluginfile_urls(
// The description content // The description content.
$this->course()->summary, $this->course()->summary,
// The pluginfile URL which will serve the request. // The pluginfile URL which will serve the request.
'pluginfile.php', 'pluginfile.php',
// The combination of contextid / component / filearea / itemid // The combination of contextid / component / filearea / itemid.
// form the virtual bucket that file are stored in. // Form the virtual bucket that file are stored in.
$this->coursecontext->id, // System instance is always used for this $this->coursecontext->id, // System instance is always used for this.
'course', 'course',
'summary', 'summary',
'' ''
@ -573,7 +577,7 @@ class courseinfo {
} else if ($fieldname == "idnumber") { } else if ($fieldname == "idnumber") {
$idnumber = trim(preg_replace("/\s+/u", " ", $this->course->idnumber)); $idnumber = trim(preg_replace("/\s+/u", " ", $this->course->idnumber));
return [$idnumber, "text", $this->course->idnumber]; return [$idnumber, "text", $this->course->idnumber];
} else if ($fieldname == "contacts") { } else if ($fieldname == "contacts") {
$cle = new \core_course_list_element($this->course()); $cle = new \core_course_list_element($this->course());
$contacts = $cle->get_course_contacts(); $contacts = $cle->get_course_contacts();
$value = ""; $value = "";
@ -587,7 +591,7 @@ class courseinfo {
$value = get_string("none"); $value = get_string("none");
} }
return [$value, "text", $value]; return [$value, "text", $value];
} else if (strpos( $fieldname , "customfield_") === 0) { } else if (strpos( $fieldname , "customfield_") === 0) {
$fieldshortname = substr($fieldname, strlen("customfield_")); $fieldshortname = substr($fieldname, strlen("customfield_"));
$handler = \core_customfield\handler::get_handler('core_course', 'course'); $handler = \core_customfield\handler::get_handler('core_course', 'course');
@ -600,7 +604,7 @@ class courseinfo {
$raw = $data->get_value(); $raw = $data->get_value();
$type = $field->get('type'); $type = $field->get('type');
// Only show if visibility is "Everyone" or ("Teachers" and in teacher view ) // Only show if visibility is "Everyone" or ("Teachers" and in teacher view ).
if ($visibility > 0 && ($visibility == 2 || $includeteachervisible)) { if ($visibility > 0 && ($visibility == 2 || $includeteachervisible)) {
if ($type == "date") { if ($type == "date") {
// Date should be converted to YYYY-MM-DD so the javascript can properly format it. // Date should be converted to YYYY-MM-DD so the javascript can properly format it.

View File

@ -51,7 +51,8 @@ class courseservice extends \external_api {
/** /**
* Get the topmost categories for the specicied user. * Get the topmost categories for the specicied user.
* Most of the work is offloaded to an SQL query in the interest of speed, but moodle functions are used to double check access permissions. * Most of the work is offloaded to an SQL query in the interest of speed,
* but moodle functions are used to double check access permissions.
* @param int $userid Id of the user * @param int $userid Id of the user
* @return array of core_course_category * @return array of core_course_category
*/ */
@ -65,9 +66,9 @@ class courseservice extends \external_api {
if (has_capability($capability, \context_system::instance(), $userid)) { if (has_capability($capability, \context_system::instance(), $userid)) {
if ($capability == 'moodle/category:viewcourselist') { if ($capability == 'moodle/category:viewcourselist') {
// We are now just looking for the visible main level categories. // We are now just looking for the visible main level categories.
// Add all categories of depth = 1; // Add all categories of depth = 1.
$rs = $DB->get_recordset("course_categories", ["depth" => 1], 'sortorder'); $rs = $DB->get_recordset("course_categories", ["depth" => 1], 'sortorder');
foreach( $rs as $rcat) { foreach ($rs as $rcat) {
// Get the category, and double check if the category is visible to the current user. // Get the category, and double check if the category is visible to the current user.
// Just in case it is a hidden category and the user does not have the viewhidden permission. // Just in case it is a hidden category and the user does not have the viewhidden permission.
$cat = \core_course_category::get($rcat->id, \IGNORE_MISSING, false, $userid); $cat = \core_course_category::get($rcat->id, \IGNORE_MISSING, false, $userid);
@ -79,7 +80,7 @@ class courseservice extends \external_api {
$rs->close(); $rs->close();
} else { } else {
// Context is system, but system may not be visible // Context is system, but system may not be visible.
// Return the top visible categories for this user. // Return the top visible categories for this user.
// Recurses only once. // Recurses only once.
return self::user_tops($userid); return self::user_tops($userid);
@ -88,10 +89,10 @@ class courseservice extends \external_api {
// We need to search for the permissions on an individial context level. // We need to search for the permissions on an individial context level.
// This part finds all top categories with a certain permission that are also visible for the user. // This part finds all top categories with a certain permission that are also visible for the user.
$sql = "SELECT UNIQUE ctx.* FROM {context} AS ctx $sql = "SELECT UNIQUE ctx.* FROM {context} ctx
INNER JOIN {role_assignments} AS ra ON ra.contextid = ctx.id INNER JOIN {role_assignments} ra ON ra.contextid = ctx.id
INNER JOIN {role_capabilities} AS rc ON ra.roleid = rc.roleid INNER JOIN {role_capabilities} rc ON ra.roleid = rc.roleid
LEFT JOIN {course_categories} AS cat ON ctx.instanceid = cat.id LEFT JOIN {course_categories} cat ON ctx.instanceid = cat.id
WHERE ( ctx.contextlevel = :ctxl_coursecat ) WHERE ( ctx.contextlevel = :ctxl_coursecat )
AND ra.userid = :userid AND rc.capability = :capability AND ra.userid = :userid AND rc.capability = :capability
ORDER BY ctx.depth ASC, cat.sortorder ASC"; ORDER BY ctx.depth ASC, cat.sortorder ASC";
@ -133,7 +134,6 @@ class courseservice extends \external_api {
} }
} }
} }
//$recordset->close();
} }
return $tops; return $tops;
@ -152,10 +152,10 @@ class courseservice extends \external_api {
$tops = []; $tops = [];
$pathlike = $DB->sql_like('ctx.path', ':pathsearch'); $pathlike = $DB->sql_like('ctx.path', ':pathsearch');
$sql = "SELECT UNIQUE ctx.* FROM {context} AS ctx $sql = "SELECT UNIQUE ctx.* FROM {context} ctx
INNER JOIN {role_assignments} AS ra ON ra.contextid = ctx.id INNER JOIN {role_assignments} ra ON ra.contextid = ctx.id
INNER JOIN {role_capabilities} AS rc ON ra.roleid = rc.roleid INNER JOIN {role_capabilities} rc ON ra.roleid = rc.roleid
LEFT JOIN {course_categories} AS cat ON ctx.instanceid = cat.id LEFT JOIN {course_categories} cat ON ctx.instanceid = cat.id
WHERE ( ctx.contextlevel = :ctxl_coursecat ) WHERE ( ctx.contextlevel = :ctxl_coursecat )
AND ra.userid = :userid AND rc.capability = :capability AND ra.userid = :userid AND rc.capability = :capability
AND {$pathlike} AND {$pathlike}
@ -255,7 +255,7 @@ class courseservice extends \external_api {
throw new moodle_exception("error:nocategoriesvisible", "local_treestudyplan"); throw new moodle_exception("error:nocategoriesvisible", "local_treestudyplan");
} }
} else { } else {
if (\get_config("local_treestudyplan", "limitcourselist")) { // TODO: Insert config setting here if (\get_config("local_treestudyplan", "limitcourselist")) {
$studyplan = studyplan::find_by_id($studyplanid); $studyplan = studyplan::find_by_id($studyplanid);
$context = $studyplan->context(); $context = $studyplan->context();
if ($context->contextlevel == \CONTEXT_SYSTEM) { if ($context->contextlevel == \CONTEXT_SYSTEM) {
@ -446,7 +446,7 @@ class courseservice extends \external_api {
$cats = []; $cats = [];
// If the reference context id is not in the list, push it there if the user has proper permissions in that context // If the reference context id is not in the list, push it there if the user has proper permissions in that context.
if ($refctxid > 1 && !in_array($refctxid, $contextids)) { if ($refctxid > 1 && !in_array($refctxid, $contextids)) {
try { try {
// Get the context. // Get the context.
@ -455,8 +455,8 @@ class courseservice extends \external_api {
if (has_capability($capability, $refctx)) { if (has_capability($capability, $refctx)) {
$insertctxs[] = $refctx; $insertctxs[] = $refctx;
} }
} catch(\dml_missing_record_exception $x) { } catch (\dml_missing_record_exception $x) {
// ignore context $refctx = null;
} }
} }
@ -469,10 +469,10 @@ class courseservice extends \external_api {
if ($idx !== false) { if ($idx !== false) {
$contextids = array_merge( $contextids = array_merge(
array_slice($contextids, 0, $idx+1), array_slice($contextids, 0, $idx + 1),
array_reverse(array_slice($ipath, 0, $i)), array_reverse(array_slice($ipath, 0, $i)),
array_slice($contextids, $idx+1, count($contextids) - 1) array_slice($contextids, $idx + 1, count($contextids) - 1)
) ; );
$found = true; $found = true;
break; break;
@ -484,7 +484,7 @@ class courseservice extends \external_api {
} }
// Now translate this to the list of categories. // Now translate this to the list of categories.
foreach ($contextids as $ctxid ) { foreach ($contextids as $ctxid) {
try { try {
$ctx = \context::instance_by_id($ctxid); $ctx = \context::instance_by_id($ctxid);
if ($ctx->contextlevel == CONTEXT_SYSTEM) { if ($ctx->contextlevel == CONTEXT_SYSTEM) {
@ -504,7 +504,7 @@ class courseservice extends \external_api {
} }
} }
} catch (\dml_missing_record_exception $x) { } catch (\dml_missing_record_exception $x) {
// ignore context $ctx = null;
} }
} }
@ -539,7 +539,6 @@ class courseservice extends \external_api {
return new \external_function_parameters( [ return new \external_function_parameters( [
"gradeitemid" => new \external_value(PARAM_INT, 'Grade item ID to scan progress for', VALUE_DEFAULT), "gradeitemid" => new \external_value(PARAM_INT, 'Grade item ID to scan progress for', VALUE_DEFAULT),
"studyplanid" => new \external_value(PARAM_INT, 'Study plan id to check progress in', VALUE_DEFAULT), "studyplanid" => new \external_value(PARAM_INT, 'Study plan id to check progress in', VALUE_DEFAULT),
]); ]);
} }

View File

@ -19,16 +19,16 @@
* @copyright 2023 P.M. Kuipers * @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/ */
namespace local_treestudyplan; namespace local_treestudyplan;
use Exception;
use JsonException; use JsonException;
defined('MOODLE_INTERNAL') || die(); /**
* Crude tool to aid in debugging
*/
class debug { class debug {
/** /**
* Dump an object in json format to debug log
* @param $object Object to dump * @param $object Object to dump
* @param $filename File to write to * @param $filename File to write to
* @return any The object * @return any The object
@ -42,9 +42,9 @@ class debug {
fwrite($f, $json."\n"); fwrite($f, $json."\n");
} catch (JsonException $x) { } catch (JsonException $x) {
fwrite($f, "Error processing json: ". $x->getMessage()."\n"); fwrite($f, "Error processing json: ". $x->getMessage()."\n");
fwrite($f, "Print_r dump: \n".print_r($object, true)."\n");
} }
} catch (\Exception $x) { } catch (\Exception $x) {
$f = null;
} finally { } finally {
fclose($f); fclose($f);
@ -52,22 +52,10 @@ class debug {
return $object; return $object;
} }
/**
* @param $object Object to dump
* @param $filename File to write to
* @return any The object
*/
public static function &print_r(&$object, $filename="/tmp/debug.log") {
try {
$f = fopen($filename, "a+");
fwrite($f, "Print_r dump: \n".print_r($object, true)."\n");
} catch (\Exception $x) {
} finally {
fclose($f);
}
}
/** /**
* Write text to debug log
* @param $object Object to dump * @param $object Object to dump
* @param $filename File to write to * @param $filename File to write to
* @return any The object * @return any The object
@ -77,8 +65,23 @@ class debug {
$f = fopen($filename, "a+"); $f = fopen($filename, "a+");
fwrite($f, $text."\n"); fwrite($f, $text."\n");
} catch (\Exception $x) { } catch (\Exception $x) {
$f = null;
} finally { } finally {
fclose($f); fclose($f);
} }
} }
} /**
* Write nothing to the file. (Used to satisfy the code checker)
* @param $filename File to write to
* @return any The object
*/
public static function void($filename="/tmp/debug.log") {
try {
$f = fopen($filename, "a+");
} catch (\Exception $x) {
$f = null;
} finally {
fclose($f);
}
}
}

View File

@ -1,8 +1,31 @@
<?php <?php
// This file is part of the Studyplan plugin for Moodle
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <https://www.gnu.org/licenses/>.
/**
* No file description
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace local_treestudyplan\form; namespace local_treestudyplan\form;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot."/lib/formslib.php"); require_once($CFG->dirroot."/lib/formslib.php");
/**
* Base class for moodle forms used in this plugin.
*/
abstract class formbase extends \moodleform { abstract class formbase extends \moodleform {
/** /**
@ -13,9 +36,9 @@ abstract class formbase extends \moodleform {
*/ */
public function __construct($params, $ajaxformdata=null) { public function __construct($params, $ajaxformdata=null) {
$customdata = static::init_customdata($params); $customdata = static::init_customdata($params);
if (static::check_security($customdata) !== false) { if (static::check_security($customdata) ) {
parent::__construct(null, (array)$customdata, 'post', '', null, true, $ajaxformdata); parent::__construct(null, (array)$customdata, 'post', '', null, true, $ajaxformdata);
// Initialize the initial data based on the parameter validation // Initialize the initial data based on the parameter validation.
$this->set_data($this->init_formdata((object)$this->_customdata)); $this->set_data($this->init_formdata((object)$this->_customdata));
} else { } else {
throw new \moodle_exception('accessexception', 'core'); throw new \moodle_exception('accessexception', 'core');
@ -48,7 +71,9 @@ abstract class formbase extends \moodleform {
* @return bool True if security validation passes. * @return bool True if security validation passes.
* @throws \moodle_exception if access denied for a specific reason. * @throws \moodle_exception if access denied for a specific reason.
*/ */
abstract public static function check_security(object $customdata); public static function check_security(object $customdata) {
return true;
}
/** /**
* Process the submission and perform necessary actions * Process the submission and perform necessary actions
@ -69,8 +94,7 @@ abstract class formbase extends \moodleform {
if ($data) { if ($data) {
return $this->process_submitted_data($data); return $this->process_submitted_data($data);
} else { } else {
throw new \moodle_exception('no_form_data', 'local_treestudyplan', '', null, $data); throw new \moodle_exception('no_form_data', 'local_treestudyplan', '', null, $data);
} }
} }
}
}

View File

@ -1,4 +1,24 @@
<?php <?php
// This file is part of the Studyplan plugin for Moodle
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <https://www.gnu.org/licenses/>.
/**
* No file description
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace local_treestudyplan\form; namespace local_treestudyplan\form;
defined('MOODLE_INTERNAL') || die(); defined('MOODLE_INTERNAL') || die();
@ -33,7 +53,7 @@ class studyplan_editform extends formbase {
*/ */
public static function init_customdata(object $params) { public static function init_customdata(object $params) {
$customdata = new stdClass; $customdata = new stdClass;
$customdata->create = $params->mode=='create' ? true : false; $customdata->create = $params->mode == 'create' ? true : false;
if ($customdata->create) { if ($customdata->create) {
$customdata->context = \context::instance_by_id($params->contextid); $customdata->context = \context::instance_by_id($params->contextid);
} else { } else {
@ -47,32 +67,20 @@ class studyplan_editform extends formbase {
'trusttext' => true, 'trusttext' => true,
'subdirs' => true, 'subdirs' => true,
'maxfiles' => 20, 'maxfiles' => 20,
'maxbytes' => 20*1024*1024, 'maxbytes' => 20 * 1024 * 1024,
'context' => \context_system::instance(), // Keep the files in system context 'context' => \context_system::instance(), // Keep the files in system context.
]; ];
$customdata->fileoptions = [ $customdata->fileoptions = [
'subdirs' => 0, 'subdirs' => 0,
'maxbytes' => 10*1024*1024, // Max 10MiB should be sufficient for a picture. 'maxbytes' => 10 * 1024 * 1024, // Max 10MiB should be sufficient for a picture.
'areamaxbytes' => 10485760, 'areamaxbytes' => 10485760,
'maxfiles' => 1, // Just one file 'maxfiles' => 1, // Just one file.
'accepted_types' => ['.jpg', '.png', '.jpeg', '.svg', '.svgz'], 'accepted_types' => ['.jpg', '.png', '.jpeg', '.svg', '.svgz'],
'return_types' => \FILE_INTERNAL | \FILE_EXTERNAL, 'return_types' => \FILE_INTERNAL | \FILE_EXTERNAL,
]; ];
return $customdata; return $customdata;
} }
/**
* Validate security access for this form based on the customdata generated by init_customdata
* Return true if validation passes, false or throw an exception if it does not.
*
* @param object $customdata The customdata for this form
* @return bool True if security validation passes.
* @throws \moodle_exception if access denied for a specific reason.
*/
public static function check_security(object $customdata) {
/*webservicehelper::require_capabilities(self::CAP_EDIT, $customdata->context); */
}
/** /**
* Generate form data from parameters * Generate form data from parameters
* Also validate parameters and access permissions here * Also validate parameters and access permissions here
@ -98,7 +106,7 @@ class studyplan_editform extends formbase {
$august = strtotime("first day of august next year"); $august = strtotime("first day of august next year");
} }
$entry->startdate = $august; $entry->startdate = $august;
$entry->enddate = $august + (364*24*60*60); // Not bothering about leap years here. $entry->enddate = $august + (364 * 24 * 60 * 60); // Not bothering about leap years here.
$entry->periods = 4; $entry->periods = 4;
} else { } else {
$entry = $DB->get_record(studyplan::TABLE, ['id' => $customdata->plan->id()]); $entry = $DB->get_record(studyplan::TABLE, ['id' => $customdata->plan->id()]);
@ -109,7 +117,7 @@ class studyplan_editform extends formbase {
$agcfg = json_decode($customdata->plan->aggregator()->config_string(), true); $agcfg = json_decode($customdata->plan->aggregator()->config_string(), true);
} }
// Prepare the editor // Prepare the editor.
$entry = file_prepare_standard_editor( $entry, $entry = file_prepare_standard_editor( $entry,
'description', 'description',
$customdata->editoroptions, $customdata->editoroptions,
@ -119,15 +127,15 @@ class studyplan_editform extends formbase {
($customdata->create) ? null : $customdata->plan->id() ($customdata->create) ? null : $customdata->plan->id()
); );
// Prepare file area for the icon // Prepare file area for the icon.
// Get an unused draft itemid which will be used for this form. // Get an unused draft itemid which will be used for this form.
$draftitemid = file_get_submitted_draft_itemid('icon'); $draftitemid = file_get_submitted_draft_itemid('icon');
file_prepare_draft_area( file_prepare_draft_area(
// The $draftitemid is the target location. // The $draftitemid is the target location.
$draftitemid, $draftitemid,
// The combination of contextid / component / filearea / itemid // The combination of contextid / component / filearea / itemid.
// form the virtual bucket that files are currently stored in // Form the virtual bucket that files are currently stored in.
// and will be copied from. // And will be copied from.
\context_system::instance()->id, \context_system::instance()->id,
'local_treestudyplan', 'local_treestudyplan',
'icon', 'icon',
@ -154,10 +162,10 @@ class studyplan_editform extends formbase {
$mform = $this->_form; $mform = $this->_form;
$customdata = (object)$this->_customdata; $customdata = (object)$this->_customdata;
// Register integer type // Register integer type.
text_integer::Register(); text_integer::register();
// Define the form // Define the form.
$field = 'name'; $field = 'name';
$mform->addElement('text', $field, $mform->addElement('text', $field,
get_string('studyplan_name', 'local_treestudyplan'), get_string('studyplan_name', 'local_treestudyplan'),
@ -176,7 +184,7 @@ class studyplan_editform extends formbase {
[]); []);
$contextlist = []; $contextlist = [];
foreach(courseservice::list_available_categories('edit') as $c) { foreach (courseservice::list_available_categories('edit') as $c) {
$contextlist[$c['context_id']] = implode(" / ", $c['category']['path']); $contextlist[$c['context_id']] = implode(" / ", $c['category']['path']);
} }
@ -196,8 +204,8 @@ class studyplan_editform extends formbase {
if ($customdata->create) { if ($customdata->create) {
$timeless = \get_config("local_treestudyplan", "timelessperiods"); $timeless = \get_config("local_treestudyplan", "timelessperiods");
if ( !$timeless) { if (!$timeless) {
// Only add these fields if a new studyplan is created, to easily initialize the first page // Only add these fields if a new studyplan is created, to easily initialize the first page.
$field = 'startdate'; $field = 'startdate';
$mform->addElement('date_selector', $field, $mform->addElement('date_selector', $field,
get_string('studyplan_startdate', 'local_treestudyplan'), get_string('studyplan_startdate', 'local_treestudyplan'),
@ -219,7 +227,7 @@ class studyplan_editform extends formbase {
$mform->addRule($field, null, 'required', null, 'client'); $mform->addRule($field, null, 'required', null, 'client');
} else { } else {
// These fields are only relevant if the studyplan is edited // These fields are only relevant if the studyplan is edited.
$field = 'suspended'; $field = 'suspended';
$mform->addElement('advcheckbox', $field, $mform->addElement('advcheckbox', $field,
get_string('studyplan_suspend', 'local_treestudyplan'), get_string('studyplan_suspend', 'local_treestudyplan'),
@ -238,9 +246,9 @@ class studyplan_editform extends formbase {
} }
$aggregators = []; $aggregators = [];
foreach(aggregator::list_model() as $a) { foreach (aggregator::list_model() as $a) {
// Add method only if not deprecated or currently used // Add method only if not deprecated or currently used.
if ( $customdata->simplemodel['aggregation'] == $a['id'] || !($a['deprecated']) ) { if ($customdata->simplemodel['aggregation'] == $a['id'] || !($a['deprecated'])) {
$aggregators[$a['id']] = $a['name']; $aggregators[$a['id']] = $a['name'];
} }
} }
@ -307,7 +315,8 @@ class studyplan_editform extends formbase {
$customdata = (object)$this->_customdata; $customdata = (object)$this->_customdata;
// Add aggregation configs to entry. // Add aggregation configs to entry.
$agcfg = json_decode(aggregator::create($entry->aggregation, "")->config_string(), true); // Retrieve default config string from selected aggregation method // Retrieve default config string from selected aggregation method.
$agcfg = json_decode(aggregator::create($entry->aggregation, "")->config_string(), true);
foreach ($agcfg as $key => $val) { foreach ($agcfg as $key => $val) {
$entrykey = $entry->aggregation."_".$key; $entrykey = $entry->aggregation."_".$key;
$agcfg[$key] = $entry->$entrykey; $agcfg[$key] = $entry->$entrykey;
@ -316,7 +325,7 @@ class studyplan_editform extends formbase {
if ($customdata->create) { if ($customdata->create) {
// Use our own abstraction to create the record, so caches are maintained // Use our own abstraction to create the record, so caches are maintained.
$plan = studyplan::add(['name' => $entry->name, $plan = studyplan::add(['name' => $entry->name,
'shortname' => $entry->shortname, 'shortname' => $entry->shortname,
'idnumber' => $entry->idnumber, 'idnumber' => $entry->idnumber,
@ -327,7 +336,7 @@ class studyplan_editform extends formbase {
'enddate' => date("Y-m-d", $entry->enddate), 'enddate' => date("Y-m-d", $entry->enddate),
'periods' => $entry->periods, 'periods' => $entry->periods,
]); ]);
// Process the provided files in the description editor // Process the provided files in the description editor.
$entry = file_postupdate_standard_editor($entry, $entry = file_postupdate_standard_editor($entry,
'description', 'description',
$customdata->editoroptions, $customdata->editoroptions,
@ -335,7 +344,7 @@ class studyplan_editform extends formbase {
'local_treestudyplan', 'local_treestudyplan',
'studyplan', 'studyplan',
$plan->id()); $plan->id());
// Update the description // Update the description.
$plan->edit([ $plan->edit([
'description' => $entry->description, 'description' => $entry->description,
'descriptionformat' => $entry->descriptionformat, 'descriptionformat' => $entry->descriptionformat,
@ -343,7 +352,7 @@ class studyplan_editform extends formbase {
} else { } else {
$plan = $customdata->plan; $plan = $customdata->plan;
// Process the provided files in the description editor // Process the provided files in the description editor.
$entry = file_postupdate_standard_editor($entry, $entry = file_postupdate_standard_editor($entry,
'description', 'description',
$customdata->editoroptions, $customdata->editoroptions,
@ -352,7 +361,7 @@ class studyplan_editform extends formbase {
'studyplan', 'studyplan',
$plan->id()); $plan->id());
// Use our own abstraction to update the record, so caches are maintained // Use our own abstraction to update the record, so caches are maintained.
$plan->edit(['name' => $entry->name, $plan->edit(['name' => $entry->name,
'shortname' => $entry->shortname, 'shortname' => $entry->shortname,
'idnumber' => $entry->idnumber, 'idnumber' => $entry->idnumber,
@ -370,8 +379,8 @@ class studyplan_editform extends formbase {
file_save_draft_area_files( file_save_draft_area_files(
// The $entry->icon property contains the itemid of the draft file area. // The $entry->icon property contains the itemid of the draft file area.
$entry->icon, $entry->icon,
// The combination of contextid / component / filearea / itemid // The combination of contextid / component / filearea / itemid.
// form the virtual bucket that file are stored in. // Form the virtual bucket that file are stored in.
\context_system::instance()->id, \context_system::instance()->id,
'local_treestudyplan', 'local_treestudyplan',
'icon', 'icon',
@ -387,5 +396,4 @@ class studyplan_editform extends formbase {
debug::dump($response); debug::dump($response);
return $response; return $response;
} }
}
}

View File

@ -1,12 +1,30 @@
<?php <?php
// This file is part of the Studyplan plugin for Moodle
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <https://www.gnu.org/licenses/>.
/**
* No file description
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace local_treestudyplan\form; namespace local_treestudyplan\form;
defined('MOODLE_INTERNAL') || die(); defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot.'/repository/lib.php'); require_once($CFG->dirroot.'/repository/lib.php');
use local_treestudyplan\aggregator;
use local_treestudyplan\studyplan; use local_treestudyplan\studyplan;
use local_treestudyplan\premium;
use local_treestudyplan\contextinfo; use local_treestudyplan\contextinfo;
use local_treestudyplan\studyplanservice; use local_treestudyplan\studyplanservice;
use local_treestudyplan\courseservice; use local_treestudyplan\courseservice;
@ -38,18 +56,6 @@ class studyplan_fromtemplateform extends formbase {
return $customdata; return $customdata;
} }
/**
* Validate security access for this form based on the customdata generated by init_customdata
* Return true if validation passes, false or throw an exception if it does not.
*
* @param object $customdata The customdata for this form
* @return bool True if security validation passes.
* @throws \moodle_exception if access denied for a specific reason.
*/
public static function check_security(object $customdata) {
/*webservicehelper::require_capabilities(self::CAP_EDIT, $customdata->context); */
}
/** /**
* Generate form data from parameters * Generate form data from parameters
* Also validate parameters and access permissions here * Also validate parameters and access permissions here
@ -72,7 +78,7 @@ class studyplan_fromtemplateform extends formbase {
$august = strtotime("first day of august next year"); $august = strtotime("first day of august next year");
} }
$entry->startdate = $august; $entry->startdate = $august;
$entry->enddate = $august + (364*24*60*60); // Not bothering about leap years here. $entry->enddate = $august + (364 * 24 * 60 * 60); // Not bothering about leap years here.
$entry->periods = 4; $entry->periods = 4;
$entry->template_id = 0; $entry->template_id = 0;
@ -87,13 +93,13 @@ class studyplan_fromtemplateform extends formbase {
$mform = $this->_form; $mform = $this->_form;
$customdata = (object)$this->_customdata; $customdata = (object)$this->_customdata;
// Register integer type // Register integer type.
text_integer::Register(); text_integer::register();
// Define the form // Define the form.
$templatelist = []; $templatelist = [];
foreach(studyplan::find_template() as $s) { foreach (studyplan::find_template() as $s) {
$c = (new contextinfo($s->context()))->model(); $c = (new contextinfo($s->context()))->model();
$templatelist[$s->id()] = implode(" / ", $c['path']) . " / " . $s->name(); $templatelist[$s->id()] = implode(" / ", $c['path']) . " / " . $s->name();
} }
@ -125,7 +131,7 @@ class studyplan_fromtemplateform extends formbase {
[]); []);
$contextlist = []; $contextlist = [];
foreach(courseservice::list_available_categories('edit') as $c) { foreach (courseservice::list_available_categories('edit') as $c) {
$contextlist[$c['context_id']] = implode(" / ", $c['category']['path']); $contextlist[$c['context_id']] = implode(" / ", $c['category']['path']);
} }
@ -136,8 +142,8 @@ class studyplan_fromtemplateform extends formbase {
$mform->addRule($field, null, 'required', null, 'client'); $mform->addRule($field, null, 'required', null, 'client');
$timeless = \get_config("local_treestudyplan", "timelessperiods"); $timeless = \get_config("local_treestudyplan", "timelessperiods");
if ( !$timeless) { if (!$timeless) {
// Only add these fields if the studyplans are timed // Only add these fields if the studyplans are timed.
$field = 'startdate'; $field = 'startdate';
$mform->addElement('date_selector', $field, $mform->addElement('date_selector', $field,
get_string('studyplan_startdate', 'local_treestudyplan'), get_string('studyplan_startdate', 'local_treestudyplan'),
@ -180,5 +186,4 @@ class studyplan_fromtemplateform extends formbase {
return null; return null;
} }
} }
}
}

View File

@ -1,15 +1,32 @@
<?php <?php
// This file is part of the Studyplan plugin for Moodle
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <https://www.gnu.org/licenses/>.
/**
* No file description
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace local_treestudyplan\form; namespace local_treestudyplan\form;
defined('MOODLE_INTERNAL') || die(); defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot.'/repository/lib.php'); require_once($CFG->dirroot.'/repository/lib.php');
use local_treestudyplan\aggregator;
use local_treestudyplan\studyplan; use local_treestudyplan\studyplan;
use local_treestudyplan\studyplanpage; use local_treestudyplan\studyplanpage;
use local_treestudyplan\courseservice;
use local_treestudyplan\form\text_integer; use local_treestudyplan\form\text_integer;
use local_treestudyplan\local\helpers\webservicehelper;
use local_treestudyplan\studyplanservice; use local_treestudyplan\studyplanservice;
use moodle_exception; use moodle_exception;
use stdClass; use stdClass;
@ -33,7 +50,7 @@ class studyplanpage_editform extends formbase {
*/ */
public static function init_customdata(object $params) { public static function init_customdata(object $params) {
$customdata = new stdClass; $customdata = new stdClass;
$customdata->create = $params->mode=='create' ? true : false; $customdata->create = $params->mode == 'create' ? true : false;
if ($customdata->create) { if ($customdata->create) {
$customdata->plan = studyplan::find_by_id($params->studyplan_id); $customdata->plan = studyplan::find_by_id($params->studyplan_id);
} else { } else {
@ -47,32 +64,20 @@ class studyplanpage_editform extends formbase {
'trusttext' => true, 'trusttext' => true,
'subdirs' => true, 'subdirs' => true,
'maxfiles' => 20, 'maxfiles' => 20,
'maxbytes' => 20*1024*1024, 'maxbytes' => 20 * 1024 * 1024,
'context' => \context_system::instance(), // Keep the files in system context 'context' => \context_system::instance(), // Keep the files in system context.
]; ];
$customdata->fileoptions = [ $customdata->fileoptions = [
'subdirs' => 0, 'subdirs' => 0,
'maxbytes' => 10*1024*1024, // Max 10MiB should be sufficient for a picture. 'maxbytes' => 10 * 1024 * 1024, // Max 10MiB should be sufficient for a picture.
'areamaxbytes' => 10485760, 'areamaxbytes' => 10485760,
'maxfiles' => 1, // Just one file 'maxfiles' => 1, // Just one file.
'accepted_types' => ['.jpg', '.png'], 'accepted_types' => ['.jpg', '.png'],
'return_types' => \FILE_INTERNAL | \FILE_EXTERNAL, 'return_types' => \FILE_INTERNAL | \FILE_EXTERNAL,
]; ];
return $customdata; return $customdata;
} }
/**
* Validate security access for this form based on the customdata generated by init_customdata
* Return true if validation passes, false or throw an exception if it does not.
*
* @param object $customdata The customdata for this form
* @return bool True if security validation passes.
* @throws \moodle_exception if access denied for a specific reason.
*/
public static function check_security(object $customdata) {
/*webservicehelper::require_capabilities(self::CAP_EDIT, $customdata->context); */
}
/** /**
* Generate form data from parameters * Generate form data from parameters
* Also validate parameters and access permissions here * Also validate parameters and access permissions here
@ -91,21 +96,21 @@ class studyplanpage_editform extends formbase {
$entry = new stdClass; $entry = new stdClass;
$entry->studyplan_id = $plan->id(); $entry->studyplan_id = $plan->id();
// By default, make the start date of a new page, continue 1 day after the last page's start date; // By default, make the start date of a new page, continue 1 day after the last page's start date.
$otherpages = $plan->pages(); $otherpages = $plan->pages();
if (count($otherpages) > 0) { if (count($otherpages) > 0) {
$lastpage = $otherpages[count($otherpages) -1]; $lastpage = $otherpages[count($otherpages) - 1];
// Propose 1 year after the last page's start date, if no end date is set. // Propose 1 year after the last page's start date, if no end date is set.
if ($lastpage->enddate(false) == null) { if ($lastpage->enddate(false) == null) {
$entry->startdate = $lastpage->startdate()->add(new \DateInterval("P1Y"))->format("U"); $entry->startdate = $lastpage->startdate()->add(new \DateInterval("P1Y"))->format("U");
} else { } else {
// Otherwise, propose 1 day after the last page's end date // Otherwise, propose 1 day after the last page's end date.
$entry->startdate = $lastpage->enddate()->add(new \DateInterval("P1D"))->format("U"); $entry->startdate = $lastpage->enddate()->add(new \DateInterval("P1D"))->format("U");
} }
} else { } else {
// Determine the next august 1st for default value purposes. Only if no other page is available // Determine the next august 1st for default value purposes. Only if no other page is available.
$august = strtotime("first day of august this year"); $august = strtotime("first day of august this year");
if ($august < time()) { if ($august < time()) {
$august = strtotime("first day of august next year"); $august = strtotime("first day of august next year");
@ -114,7 +119,7 @@ class studyplanpage_editform extends formbase {
$entry->startdate = $august; $entry->startdate = $august;
} }
$entry->enddate = $entry->startdate + (364*24*60*60); // Not bothering about leap years here. $entry->enddate = $entry->startdate + (364 * 24 * 60 * 60); // Not bothering with leap years here.
$entry->periods = 4; $entry->periods = 4;
} else { } else {
$entry = $DB->get_record(studyplanpage::TABLE, ['id' => $customdata->page->id()]); $entry = $DB->get_record(studyplanpage::TABLE, ['id' => $customdata->page->id()]);
@ -122,7 +127,7 @@ class studyplanpage_editform extends formbase {
$entry->enddate = strtotime($entry->enddate); $entry->enddate = strtotime($entry->enddate);
} }
// Prepare the editor // Prepare the editor.
$entry = file_prepare_standard_editor( $entry, $entry = file_prepare_standard_editor( $entry,
'description', 'description',
$customdata->editoroptions, $customdata->editoroptions,
@ -143,10 +148,10 @@ class studyplanpage_editform extends formbase {
$mform = $this->_form; $mform = $this->_form;
$customdata = (object)$this->_customdata; $customdata = (object)$this->_customdata;
// Register integer type // Register integer type.
text_integer::Register(); text_integer::register();
// Define the form // Define the form.
$field = 'fullname'; $field = 'fullname';
$mform->addElement('text', $field, $mform->addElement('text', $field,
get_string('studyplan_name', 'local_treestudyplan'), get_string('studyplan_name', 'local_treestudyplan'),
@ -160,7 +165,7 @@ class studyplanpage_editform extends formbase {
$mform->addRule($field, null, 'required', null, 'client'); $mform->addRule($field, null, 'required', null, 'client');
$timeless = \get_config("local_treestudyplan", "timelessperiods"); $timeless = \get_config("local_treestudyplan", "timelessperiods");
if ( !$timeless) { if (!$timeless) {
$field = 'startdate'; $field = 'startdate';
$mform->addElement('date_selector', $field, $mform->addElement('date_selector', $field,
get_string('studyplan_startdate', 'local_treestudyplan'), get_string('studyplan_startdate', 'local_treestudyplan'),
@ -201,7 +206,7 @@ class studyplanpage_editform extends formbase {
if ($customdata->create) { if ($customdata->create) {
// Use our own abstraction to update the record, so caches are maintained // Use our own abstraction to update the record, so caches are maintained.
$page = studyplanpage::add(['fullname' => $entry->fullname, $page = studyplanpage::add(['fullname' => $entry->fullname,
'shortname' => $entry->shortname, 'shortname' => $entry->shortname,
'startdate' => date("Y-m-d", $entry->startdate), 'startdate' => date("Y-m-d", $entry->startdate),
@ -209,7 +214,7 @@ class studyplanpage_editform extends formbase {
'periods' => $entry->periods, 'periods' => $entry->periods,
'studyplan_id' => $customdata->plan->id(), 'studyplan_id' => $customdata->plan->id(),
]); ]);
// Process the provided files in the description editor // Process the provided files in the description editor.
$entry = file_postupdate_standard_editor($entry, $entry = file_postupdate_standard_editor($entry,
'description', 'description',
$customdata->editoroptions, $customdata->editoroptions,
@ -217,7 +222,7 @@ class studyplanpage_editform extends formbase {
'local_treestudyplan', 'local_treestudyplan',
'studyplanpage', 'studyplanpage',
$page->id()); $page->id());
// Update the description // Update the description.
$page->edit([ $page->edit([
'description' => $entry->description, 'description' => $entry->description,
'descriptionformat' => $entry->descriptionformat, 'descriptionformat' => $entry->descriptionformat,
@ -225,7 +230,7 @@ class studyplanpage_editform extends formbase {
} else { } else {
$page = $customdata->page; $page = $customdata->page;
// Process the provided files in the description editor // Process the provided files in the description editor.
$entry = file_postupdate_standard_editor($entry, $entry = file_postupdate_standard_editor($entry,
'description', 'description',
$customdata->editoroptions, $customdata->editoroptions,
@ -234,7 +239,7 @@ class studyplanpage_editform extends formbase {
'studyplanpage', 'studyplanpage',
$page->id()); $page->id());
// Use our own abstraction to update the record, so caches are maintained // Use our own abstraction to update the record, so caches are maintained.
$page->edit(['fullname' => $entry->fullname, $page->edit(['fullname' => $entry->fullname,
'shortname' => $entry->shortname, 'shortname' => $entry->shortname,
'description' => $entry->description, 'description' => $entry->description,
@ -251,5 +256,4 @@ class studyplanpage_editform extends formbase {
*/ */
return studyplanservice::clean_returnvalue(studyplanpage::editor_structure(), $page->editor_model()); return studyplanservice::clean_returnvalue(studyplanpage::editor_structure(), $page->editor_model());
} }
}
}

View File

@ -1,4 +1,25 @@
<?php <?php
// This file is part of the Studyplan plugin for Moodle
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <https://www.gnu.org/licenses/>.
/**
* No file description
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace local_treestudyplan\form; namespace local_treestudyplan\form;
use MoodleQuickForm_text; use MoodleQuickForm_text;
use MoodleQuickForm; use MoodleQuickForm;
@ -7,8 +28,11 @@ defined('MOODLE_INTERNAL') || die();
global $CFG; global $CFG;
require_once($CFG->libdir . "/form/text.php"); require_once($CFG->libdir . "/form/text.php");
class text_integer extends MoodleQuickForm_text {
/** /**
* Class to handle integer only form field.
*/
class text_integer extends MoodleQuickForm_text {
/**
* Accepts a renderer * Accepts a renderer
* *
* @param object $renderer An HTML_QuickForm_Renderer object * @param object $renderer An HTML_QuickForm_Renderer object
@ -32,7 +56,7 @@ class text_integer extends MoodleQuickForm_text {
$unsigned = (isset($this->_attributes['unsigned']) && $this->_attributes['unsigned']); $unsigned = (isset($this->_attributes['unsigned']) && $this->_attributes['unsigned']);
$nonzero = (isset($this->_attributes['nonzero']) && $this->_attributes['nonzero']); $nonzero = (isset($this->_attributes['nonzero']) && $this->_attributes['nonzero']);
$context = array( $context = [
'element' => $elementcontext, 'element' => $elementcontext,
'label' => $label, 'label' => $label,
'unsigned' => ($unsigned) ? true : false , 'unsigned' => ($unsigned) ? true : false ,
@ -41,7 +65,7 @@ class text_integer extends MoodleQuickForm_text {
'advanced' => $advanced, 'advanced' => $advanced,
'helpbutton' => $helpbutton, 'helpbutton' => $helpbutton,
'error' => $error, 'error' => $error,
); ];
$html = $OUTPUT->render_from_template('local_treestudyplan/form/element_text_integer', $context); $html = $OUTPUT->render_from_template('local_treestudyplan/form/element_text_integer', $context);
if ($renderer->_inGroup) { if ($renderer->_inGroup) {
$this->_groupElementTemplate = $html; $this->_groupElementTemplate = $html;
@ -59,7 +83,10 @@ class text_integer extends MoodleQuickForm_text {
$renderer->_html .= $html; $renderer->_html .= $html;
} }
public static function Register() { /**
* Register the element
*/
public static function register() {
global $CFG; global $CFG;
MoodleQuickForm::registerElementType( MoodleQuickForm::registerElementType(
// The custom element is named `course_competency_rule`. // The custom element is named `course_competency_rule`.
@ -74,4 +101,4 @@ class text_integer extends MoodleQuickForm_text {
'local_treestudyplan\form\text_integer' 'local_treestudyplan\form\text_integer'
); );
} }
} }

View File

@ -174,7 +174,7 @@ class gradeinfo {
// Determine the icon for the associated activity. // Determine the icon for the associated activity.
$contentitem = static::get_contentitem($gi->itemmodule); $contentitem = static::get_contentitem($gi->itemmodule);
$this->icon = empty($contentitem) ? "" : $contentitem->get_icon(); $this->icon = empty($contentitem) ? "" : $contentitem->get_icon();
$this->scale = $gi->load_scale(); $this->scale = $gi->load_scale();
$this->outcome = $gi->load_outcome(); $this->outcome = $gi->load_outcome();
@ -337,7 +337,7 @@ class gradeinfo {
public function user_model($userid) { public function user_model($userid) {
global $DB; global $DB;
$grade = $this->gradeitem->get_final($userid); $grade = $this->gradeitem->get_final($userid);
// Format grade for proper display // Format grade for proper display.
if (is_object($grade)) { if (is_object($grade)) {
$finalgrade = \grade_format_gradevalue($grade->finalgrade, $this->gradeitem, true, null, 1); $finalgrade = \grade_format_gradevalue($grade->finalgrade, $this->gradeitem, true, null, 1);
} else { } else {
@ -357,8 +357,8 @@ class gradeinfo {
"name" => $this->name, "name" => $this->name,
"typename" => $this->typename, "typename" => $this->typename,
"grade" => $finalgrade, "grade" => $finalgrade,
"gradetype" => isset($this->scale) ? "completion" : "grade", "gradetype" => isset($this->scale) ? "completion" : "grade",
"feedback" => empty($grade) ? null : $grade->feedback, "feedback" => empty($grade) ? null : $grade->feedback,
"completion" => completion::label($completion), "completion" => completion::label($completion),
"icon" => $this->icon, "icon" => $this->icon,
"link" => $this->link, "link" => $this->link,
@ -504,14 +504,14 @@ class gradeinfo {
$r = $DB->get_record($table, ['studyitem_id' => $itemid, 'grade_item_id' => $gradeid]); $r = $DB->get_record($table, ['studyitem_id' => $itemid, 'grade_item_id' => $gradeid]);
if ($r) { if ($r) {
$r->include = 1; $r->include = 1;
$r->required = boolval($required) ? 1 : 0; $r->required = boolval($required) ? 1 : 0;
$id = $DB->update_record($table, $r); $id = $DB->update_record($table, $r);
} else { } else {
$DB->insert_record($table, [ $DB->insert_record($table, [
'studyitem_id' => $itemid, 'studyitem_id' => $itemid,
'grade_item_id' => $gradeid, 'grade_item_id' => $gradeid,
'include' => 1, 'include' => 1,
'required' => boolval($required) ? 1 : 0 ] 'required' => boolval($required) ? 1 : 0 ]
); );
} }
} else { } else {

View File

@ -148,7 +148,7 @@ class gradingscanner {
$ungraded++; $ungraded++;
} else { } else {
$grade = $this->gi->get_final($userid); $grade = $this->gi->get_final($userid);
if ( (!empty($grade->finalgrade)) && is_numeric($grade->finalgrade)) { if ((!empty($grade->finalgrade)) && is_numeric($grade->finalgrade)) {
// Compare grade to minimum grade. // Compare grade to minimum grade.
if ($this->grade_passed($grade)) { if ($this->grade_passed($grade)) {
$completedpass++; $completedpass++;
@ -180,7 +180,7 @@ class gradingscanner {
// First determine if we have a grade_config for this scale or this maximum grade. // First determine if we have a grade_config for this scale or this maximum grade.
$finalgrade = $grade->finalgrade; $finalgrade = $grade->finalgrade;
$scale = $this->gi->load_scale(); $scale = $this->gi->load_scale();
if ( isset($scale)) { if (isset($scale)) {
$gradecfg = $DB->get_record($table, ["scale_id" => $scale->id]); $gradecfg = $DB->get_record($table, ["scale_id" => $scale->id]);
} else if ($this->gi->grademin == 0) { } else if ($this->gi->grademin == 0) {
$gradecfg = $DB->get_record($table, ["grade_points" => $this->gi->grademax]); $gradecfg = $DB->get_record($table, ["grade_points" => $this->gi->grademax]);

View File

@ -173,7 +173,7 @@ class bistate_aggregator extends \local_treestudyplan\aggregator {
$totalrequired = 0; $totalrequired = 0;
$requiredmet = 0; $requiredmet = 0;
$minprogress = ($this->cfg()->accept_pending_as_submitted) ? completion : :PENDING : completion::PROGRESS; $minprogress = ($this->cfg()->accept_pending_as_submitted) ? completion::PENDING : completion::PROGRESS;
foreach ($completions as $index => $c) { foreach ($completions as $index => $c) {
@ -193,10 +193,10 @@ class bistate_aggregator extends \local_treestudyplan\aggregator {
$started = $progress + $failed; $started = $progress + $failed;
$allrequiredmet = ($requiredmet >= $totalrequired); $allrequiredmet = ($requiredmet >= $totalrequired);
$fractioncompleted = ($total > 0) ? (floatval($completed) / floatval($total)): 0.0; $fractioncompleted = ($total > 0) ? (floatval($completed) / floatval($total)) : 0.0;
$fractionprogress = ($total > 0) ? (floatval($progress) / floatval($total)): 0.0; $fractionprogress = ($total > 0) ? (floatval($progress) / floatval($total)) : 0.0;
$fractionfailed = ($total > 0) ? (floatval($failed) / floatval($total)): 0.0; $fractionfailed = ($total > 0) ? (floatval($failed) / floatval($total)) : 0.0;
$fractionstarted = ($total > 0) ? (floatval($started) / floatval($total)): 0.0; $fractionstarted = ($total > 0) ? (floatval($started) / floatval($total)) : 0.0;
if ($total == 0) { if ($total == 0) {
return completion::INCOMPLETE; return completion::INCOMPLETE;
@ -230,7 +230,7 @@ class bistate_aggregator extends \local_treestudyplan\aggregator {
*/ */
public function aggregate_course(courseinfo $courseinfo, studyitem $studyitem, $userid) { public function aggregate_course(courseinfo $courseinfo, studyitem $studyitem, $userid) {
$course = $courseinfo->course(); $course = $courseinfo->course();
$coursefinished = ($course->enddate) ? ($course->enddate < time()): false; $coursefinished = ($course->enddate) ? ($course->enddate < time()) : false;
// Note: studyitem condition config is not used in this aggregator. // Note: studyitem condition config is not used in this aggregator.
// Loop through all associated gradables and count the totals, completed, etc.. // Loop through all associated gradables and count the totals, completed, etc..
$completions = []; $completions = [];
@ -278,32 +278,32 @@ class bistate_aggregator extends \local_treestudyplan\aggregator {
$total = count($completion); $total = count($completion);
if ($method == "ANY") { if ($method == "ANY") {
if ( $statecount[completion::EXCELLENT] >= 1 ) { if ($statecount[completion::EXCELLENT] >= 1) {
return completion::EXCELLENT; return completion::EXCELLENT;
} else if ( $statecount[completion::GOOD] >= 1 ) { } else if ($statecount[completion::GOOD] >= 1) {
return completion::GOOD; return completion::GOOD;
} else if ( $statecount[completion::COMPLETED] >= 1 ) { } else if ($statecount[completion::COMPLETED] >= 1) {
return completion::COMPLETED; return completion::COMPLETED;
} else if ( $statecount[completion::PROGRESS] >= 1 ) { } else if ($statecount[completion::PROGRESS] >= 1) {
return completion::PROGRESS; return completion::PROGRESS;
} else if ( $statecount[completion::FAILED] >= 1) { } else if ($statecount[completion::FAILED] >= 1) {
return completion::FAILED; return completion::FAILED;
} else { } else {
return completion::INCOMPLETE; return completion::INCOMPLETE;
} }
} else { /* default value of ALL */ } else { /* default value of ALL */
if ( $total == $statecount[completion::EXCELLENT]) { if ($total == $statecount[completion::EXCELLENT]) {
return completion::EXCELLENT; return completion::EXCELLENT;
} else if ( $total == ( $statecount[completion::EXCELLENT] } else if ($total == ( $statecount[completion::EXCELLENT]
+ $statecount[completion::GOOD]) ) { + $statecount[completion::GOOD]) ) {
return completion::GOOD; return completion::GOOD;
} else if ( $total == ( $statecount[completion::EXCELLENT] } else if ($total == ( $statecount[completion::EXCELLENT]
+ $statecount[completion::GOOD] + $statecount[completion::GOOD]
+ $statecount[completion::COMPLETED]) ) { + $statecount[completion::COMPLETED]) ) {
return completion::COMPLETED; return completion::COMPLETED;
} else if ( $statecount[completion::FAILED]) { } else if ($statecount[completion::FAILED]) {
return completion::FAILED; return completion::FAILED;
} else if ( $total == $statecount[completion::INCOMPLETE]) { } else if ($total == $statecount[completion::INCOMPLETE]) {
return completion::INCOMPLETE; return completion::INCOMPLETE;
} else { } else {
return completion::PROGRESS; return completion::PROGRESS;
@ -323,7 +323,7 @@ class bistate_aggregator extends \local_treestudyplan\aggregator {
$gradeitem = $gradeinfo->get_gradeitem(); $gradeitem = $gradeinfo->get_gradeitem();
$grade = $gradeitem->get_final($userid); $grade = $gradeitem->get_final($userid);
$course = \get_course($gradeitem->courseid); // Fetch course from cache. $course = \get_course($gradeitem->courseid); // Fetch course from cache.
$coursefinished = ($course->enddate) ? ($course->enddate < time()): false; $coursefinished = ($course->enddate) ? ($course->enddate < time()) : false;
if (empty($grade)) { if (empty($grade)) {
return completion::INCOMPLETE; return completion::INCOMPLETE;
@ -344,7 +344,7 @@ class bistate_aggregator extends \local_treestudyplan\aggregator {
// First determine if we have a grade_config for this scale or this maximum grade. // First determine if we have a grade_config for this scale or this maximum grade.
$finalgrade = $grade->finalgrade; $finalgrade = $grade->finalgrade;
$scale = $gradeinfo->get_scale(); $scale = $gradeinfo->get_scale();
if ( isset($scale)) { if (isset($scale)) {
$gradecfg = $DB->get_record($table, ["scale_id" => $scale->id]); $gradecfg = $DB->get_record($table, ["scale_id" => $scale->id]);
} else if ($gradeitem->grademin == 0) { } else if ($gradeitem->grademin == 0) {
$gradecfg = $DB->get_record($table, ["grade_points" => $gradeitem->grademax]); $gradecfg = $DB->get_record($table, ["grade_points" => $gradeitem->grademax]);

View File

@ -67,8 +67,8 @@ class competency_aggregator extends \local_treestudyplan\aggregator {
* @param string $configstr Aggregation configuration string * @param string $configstr Aggregation configuration string
*/ */
protected function initialize($configstr) { protected function initialize($configstr) {
// First initialize with the defaults. // First initialize with the defaults.
foreach (["thresh_completed" ] as $key) { foreach (["thresh_completed" ] as $key) {
$val = intval(get_config('local_treestudyplan', "competency_{$key}")); $val = intval(get_config('local_treestudyplan', "competency_{$key}"));
if ($val >= 0 && $val <= 100) { if ($val >= 0 && $val <= 100) {
$this->cfg()->$key = floatval($val) / 100; $this->cfg()->$key = floatval($val) / 100;
@ -165,7 +165,7 @@ class competency_aggregator extends \local_treestudyplan\aggregator {
* @return int Aggregated completion as completion class constant * @return int Aggregated completion as completion class constant
*/ */
public function aggregate_course(courseinfo $courseinfo, studyitem $studyitem, $userid) { public function aggregate_course(courseinfo $courseinfo, studyitem $studyitem, $userid) {
// Retrieve the course competencies // Retrieve the course competencies.
$course = $courseinfo->course(); $course = $courseinfo->course();
$cci = new coursecompetencyinfo($course, $studyitem); $cci = new coursecompetencyinfo($course, $studyitem);
@ -196,9 +196,9 @@ class competency_aggregator extends \local_treestudyplan\aggregator {
} }
// Determine minimum for // Determine minimum for.
$limit = $this->cfg()->thresh_completed * $count; $limit = $this->cfg()->thresh_completed * $count;
$coursefinished = ($course->enddate) ? ($course->enddate < time()): false; $coursefinished = ($course->enddate) ? ($course->enddate < time()) : false;
if ($proficient >= $count && $requiredmet >= $requiredcount) { if ($proficient >= $count && $requiredmet >= $requiredcount) {
if ($limit < $count) { if ($limit < $count) {
@ -209,13 +209,13 @@ class competency_aggregator extends \local_treestudyplan\aggregator {
} else if ($proficient > $limit && $requiredmet >= $requiredcount) { } else if ($proficient > $limit && $requiredmet >= $requiredcount) {
return completion::COMPLETED; return completion::COMPLETED;
} else if ($proficient > 0) { } else if ($proficient > 0) {
if ( $this->cfg()->use_failed && $coursefinished) { if ($this->cfg()->use_failed && $coursefinished) {
return completion::FAILED; return completion::FAILED;
} else { } else {
return completion::PROGRESS; return completion::PROGRESS;
} }
} else { } else {
if ( $this->cfg()->use_failed && $coursefinished) { if ($this->cfg()->use_failed && $coursefinished) {
return completion::FAILED; return completion::FAILED;
} else { } else {
return completion::INCOMPLETE; return completion::INCOMPLETE;
@ -247,32 +247,32 @@ class competency_aggregator extends \local_treestudyplan\aggregator {
$total = count($completion); $total = count($completion);
if ($method == "ANY") { if ($method == "ANY") {
if ( $statecount[completion::EXCELLENT] >= 1 ) { if ($statecount[completion::EXCELLENT] >= 1) {
return completion::EXCELLENT; return completion::EXCELLENT;
} else if ( $statecount[completion::GOOD] >= 1 ) { } else if ($statecount[completion::GOOD] >= 1) {
return completion::GOOD; return completion::GOOD;
} else if ( $statecount[completion::COMPLETED] >= 1 ) { } else if ($statecount[completion::COMPLETED] >= 1) {
return completion::COMPLETED; return completion::COMPLETED;
} else if ( $statecount[completion::PROGRESS] >= 1 ) { } else if ($statecount[completion::PROGRESS] >= 1) {
return completion::PROGRESS; return completion::PROGRESS;
} else if ( $statecount[completion::FAILED] >= 1) { } else if ($statecount[completion::FAILED] >= 1) {
return completion::FAILED; return completion::FAILED;
} else { } else {
return completion::INCOMPLETE; return completion::INCOMPLETE;
} }
} else { /* default value of ALL */ } else { /* default value of ALL */
if ( $total == $statecount[completion::EXCELLENT]) { if ($total == $statecount[completion::EXCELLENT]) {
return completion::EXCELLENT; return completion::EXCELLENT;
} else if ( $total == ( $statecount[completion::EXCELLENT] } else if ($total == ( $statecount[completion::EXCELLENT]
+ $statecount[completion::GOOD]) ) { + $statecount[completion::GOOD]) ) {
return completion::GOOD; return completion::GOOD;
} else if ( $total == ( $statecount[completion::EXCELLENT] } else if ($total == ( $statecount[completion::EXCELLENT]
+ $statecount[completion::GOOD] + $statecount[completion::GOOD]
+ $statecount[completion::COMPLETED]) ) { + $statecount[completion::COMPLETED]) ) {
return completion::COMPLETED; return completion::COMPLETED;
} else if ( $statecount[completion::FAILED]) { } else if ($statecount[completion::FAILED]) {
return completion::FAILED; return completion::FAILED;
} else if ( $total == $statecount[completion::INCOMPLETE]) { } else if ($total == $statecount[completion::INCOMPLETE]) {
return completion::INCOMPLETE; return completion::INCOMPLETE;
} else { } else {
return completion::PROGRESS; return completion::PROGRESS;

View File

@ -121,7 +121,7 @@ class core_aggregator extends \local_treestudyplan\aggregator {
if ($completion->is_enabled() && $completion->is_tracked_user($userid)) { if ($completion->is_enabled() && $completion->is_tracked_user($userid)) {
if ($completion->is_course_complete($userid)) { if ($completion->is_course_complete($userid)) {
// Completed is completed // Completed is completed.
return completion::COMPLETED; return completion::COMPLETED;
} else { } else {
// Check if the course is over or not, if it is over, display failed. // Check if the course is over or not, if it is over, display failed.
@ -174,32 +174,32 @@ class core_aggregator extends \local_treestudyplan\aggregator {
$total = count($completion); $total = count($completion);
if ($method == "ANY") { if ($method == "ANY") {
if ( $statecount[completion::EXCELLENT] >= 1 ) { if ($statecount[completion::EXCELLENT] >= 1) {
return completion::EXCELLENT; return completion::EXCELLENT;
} else if ( $statecount[completion::GOOD] >= 1 ) { } else if ($statecount[completion::GOOD] >= 1) {
return completion::GOOD; return completion::GOOD;
} else if ( $statecount[completion::COMPLETED] >= 1 ) { } else if ($statecount[completion::COMPLETED] >= 1) {
return completion::COMPLETED; return completion::COMPLETED;
} else if ( $statecount[completion::PROGRESS] >= 1 ) { } else if ($statecount[completion::PROGRESS] >= 1) {
return completion::PROGRESS; return completion::PROGRESS;
} else if ( $statecount[completion::FAILED] >= 1) { } else if ($statecount[completion::FAILED] >= 1) {
return completion::FAILED; return completion::FAILED;
} else { } else {
return completion::INCOMPLETE; return completion::INCOMPLETE;
} }
} else { /* default value of ALL */ } else { /* default value of ALL */
if ( $total == $statecount[completion::EXCELLENT]) { if ($total == $statecount[completion::EXCELLENT]) {
return completion::EXCELLENT; return completion::EXCELLENT;
} else if ( $total == ( $statecount[completion::EXCELLENT] } else if ($total == ( $statecount[completion::EXCELLENT]
+ $statecount[completion::GOOD]) ) { + $statecount[completion::GOOD]) ) {
return completion::GOOD; return completion::GOOD;
} else if ( $total == ( $statecount[completion::EXCELLENT] } else if ($total == ( $statecount[completion::EXCELLENT]
+ $statecount[completion::GOOD] + $statecount[completion::GOOD]
+ $statecount[completion::COMPLETED]) ) { + $statecount[completion::COMPLETED]) ) {
return completion::COMPLETED; return completion::COMPLETED;
} else if ( $statecount[completion::FAILED]) { } else if ($statecount[completion::FAILED]) {
return completion::FAILED; return completion::FAILED;
} else if ( $total == $statecount[completion::INCOMPLETE]) { } else if ($total == $statecount[completion::INCOMPLETE]) {
return completion::INCOMPLETE; return completion::INCOMPLETE;
} else { } else {
return completion::PROGRESS; return completion::PROGRESS;

View File

@ -152,7 +152,7 @@ class tristate_aggregator extends \local_treestudyplan\aggregator {
public function aggregate_junction(array $completion, studyitem $studyitem, $userid) { public function aggregate_junction(array $completion, studyitem $studyitem, $userid) {
$completed = self::aggregate_completion($completion, $studyitem->conditions()); $completed = self::aggregate_completion($completion, $studyitem->conditions());
// If null result (conditions are unknown/null) - default to ALL. // If null result (conditions are unknown/null) - default to ALL.
return isset($completed) ? $completed : (self::aggregate_completion($completion, 'ALL')); return isset($completed) ? $completed : (self::aggregate_completion($completion, 'ALL'));
} }
/** /**

View File

@ -93,7 +93,6 @@ class gradegenerator {
"Praesent nec risus vestibulum quam venenatis tempor.", "Praesent nec risus vestibulum quam venenatis tempor.",
"Nullam rhoncus ex a quam egestas, eu auctor enim lobortis.", "Nullam rhoncus ex a quam egestas, eu auctor enim lobortis.",
"Nam luctus ante id lacus scelerisque, quis blandit ante elementum.", "Nam luctus ante id lacus scelerisque, quis blandit ante elementum.",
]; ];
/** /**
@ -230,7 +229,7 @@ class gradegenerator {
// First get the configured interpretation for this scale or grade. // First get the configured interpretation for this scale or grade.
$scale = $gi->load_scale(); $scale = $gi->load_scale();
if ( isset($scale)) { if (isset($scale)) {
$gradecfg = $DB->get_record($table, ["scale_id" => $scale->id]); $gradecfg = $DB->get_record($table, ["scale_id" => $scale->id]);
} else if ($gi->grademin == 0) { } else if ($gi->grademin == 0) {
$gradecfg = $DB->get_record($table, ["grade_points" => $gi->grademax]); $gradecfg = $DB->get_record($table, ["grade_points" => $gi->grademax]);
@ -254,7 +253,7 @@ class gradegenerator {
} }
$r->gradetext = $r->grade; $r->gradetext = $r->grade;
if ( isset($scale)) { if (isset($scale)) {
$scaleitems = $scale->load_items(); $scaleitems = $scale->load_items();
if ($r->grade > 0) { if ($r->grade > 0) {
$r->gradetext = trim($scale->get_nearest_item($r->grade)); $r->gradetext = trim($scale->get_nearest_item($r->grade));
@ -289,21 +288,21 @@ class gradegenerator {
$r = (object)[ $r = (object)[
"gi" => $g, "gi" => $g,
"grade" => $gi->grademin + $grade, "grade" => $gi->grademin + $grade,
"fb" => ($grade > 0) ? $gr->fb : "" "fb" => ($grade > 0) ? $gr->fb : "",
]; ];
} else if (!$gr->result) { } else if (!$gr->result) {
// PROGRESS. // PROGRESS.
$r = (object)[ $r = (object)[
"gi" => $g, "gi" => $g,
"grade" => $gi->grademin + rand(round($range * 0.35), round($range * 0.55) - 1 ), "grade" => $gi->grademin + rand(round($range * 0.35), round($range * 0.55) - 1 ),
"fb" => $gr->fb "fb" => $gr->fb,
]; ];
} else { } else {
// COMPLETED. // COMPLETED.
$r = (object)[ $r = (object)[
"gi" => $g, "gi" => $g,
"grade" => $gi->grademin + rand(round($range * 0.55) , $range ), "grade" => $gi->grademin + rand(round($range * 0.55) , $range ),
"fb" => $gr->fb "fb" => $gr->fb,
]; ];
} }

View File

@ -52,14 +52,9 @@ class webservicehelper {
} }
if (is_array($capability)) { if (is_array($capability)) {
// TODO: replace this by accesslib function \has_any_capability(). return \has_any_capability($capability, $context);
foreach ($capability as $cap) { } else {
if (has_capability($cap, $context)) { return has_capability($capability, $context);
return true;
}
}
} else if (has_capability($capability, $context)) {
return true;
} }
} }

View File

@ -30,19 +30,19 @@ class randomimage {
/** /**
* Image width * Image width
* @var integer * @var int
*/ */
private $width; private $width;
/** /**
* Image height * Image height
* @var integer * @var int
*/ */
private $height ; private $height;
/** /**
* Number of shapes to draw * Number of shapes to draw
* @var integer * @var int
*/ */
private $shapes; private $shapes;
@ -66,7 +66,7 @@ class randomimage {
IMG_ARC_PIE, IMG_ARC_PIE,
IMG_ARC_CHORD, IMG_ARC_CHORD,
IMG_ARC_EDGED, IMG_ARC_EDGED,
IMG_ARC_NOFILL IMG_ARC_NOFILL,
]; ];
/** /**
@ -75,11 +75,12 @@ class randomimage {
* @param integer $maxpts Max number of point to use * @param integer $maxpts Max number of point to use
* @return void * @return void
*/ */
private function random_polygon($im, Int $maxpts = 20) private function random_polygon($im, Int $maxpts = 20) {
{
$color = imagecolorallocatealpha($im, ...$this->random_color_alpha()); $color = imagecolorallocatealpha($im, ...$this->random_color_alpha());
$pts = $this->random_pts(\random_int(3, $maxpts)); $numpoints = \random_int(3, $maxpts);
imagefilledpolygon($im, $pts, $color); $pts = $this->random_pts($numpoints);
// Remove $numpoints parameter when php7.4 is no longer supported (param is deprecated in >8.1).
imagefilledpolygon($im, $pts, $numpoints, $color);
} }
/** /**
@ -87,8 +88,7 @@ class randomimage {
* @param \GdImage $im The image resource * @param \GdImage $im The image resource
* @return void * @return void
*/ */
private function random_arc($im) private function random_arc($im) {
{
$cx = \random_int(0, $this->width); $cx = \random_int(0, $this->width);
$cy = \random_int(0, $this->height); $cy = \random_int(0, $this->height);
$w = \random_int(0, $this->width); $w = \random_int(0, $this->width);
@ -104,13 +104,12 @@ class randomimage {
* Generates an array of random alpha color values. * Generates an array of random alpha color values.
* @return Array [r, g, b, a] * @return Array [r, g, b, a]
*/ */
private function random_color_alpha(): Array private function random_color_alpha(): Array {
{
return [ return [
\random_int(0, 255), \random_int(0, 255),
\random_int(0, 255), \random_int(0, 255),
\random_int(0, 255), \random_int(0, 255),
\random_int(0, 127) \random_int(0, 127),
]; ];
} }
@ -119,16 +118,21 @@ class randomimage {
* @param integer $length Number of sets of points to generate * @param integer $length Number of sets of points to generate
* @return Array The resulting array of points * @return Array The resulting array of points
*/ */
private function random_pts($length): Array private function random_pts($length): Array {
{
$pts = []; $pts = [];
for($n = 0; $n < $length; $n++) { for ($n = 0; $n < $length; $n++) {
$pts[] = \random_int(0, $this->width); $pts[] = \random_int(0, $this->width);
$pts[] = \random_int(0, $this->height); $pts[] = \random_int(0, $this->height);
} }
return $pts; return $pts;
} }
/**
* Create new random image
* @param int $shapes Number of shapes
* @param int $width Width of the images generated
* @param int $height Height of the images generated
*/
public function __construct($shapes=3, $width=256, $height=256) { public function __construct($shapes=3, $width=256, $height=256) {
$this->shapes = $shapes; $this->shapes = $shapes;
$this->width = $width; $this->width = $width;
@ -159,15 +163,20 @@ class randomimage {
imagedestroy($im); imagedestroy($im);
} }
/**
* Get image content as string
* @return string
*/
public function content() { public function content() {
return $this->content = \file_get_contents($this->tempfile); return $this->content = \file_get_contents($this->tempfile);
} }
public function __destruct() /**
{ * Destructor for random image object
*/
public function __destruct() {
if (isset($this->tempfile) && file_exists($this->tempfile)) { if (isset($this->tempfile) && file_exists($this->tempfile)) {
unlink($this->tempfile); unlink($this->tempfile);
} }
} }
}
}

View File

@ -117,7 +117,7 @@ class period {
$pend = $pstart + $ptime; $pend = $pstart + $ptime;
} }
// Continue period numbers if so configured // Continue period numbers if so configured.
if (get_config("local_treestudyplan", "continueperiodnumberingnewpage")) { if (get_config("local_treestudyplan", "continueperiodnumberingnewpage")) {
$offset = 0; $offset = 0;
foreach (studyplanpage::find_studyplan_children($page->studyplan()) as $p) { foreach (studyplanpage::find_studyplan_children($page->studyplan()) as $p) {

View File

@ -25,19 +25,22 @@ defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir.'/externallib.php'); require_once($CFG->libdir.'/externallib.php');
use DateTime;
use moodle_url;
use stdClass;
/** /**
* Handle badge information in the same style as the other classes * Handle badge information in the same style as the other classes
*/ */
class premium extends \external_api { class premium extends \external_api {
// Toggle the variable below to enable support for premium stuff. /**
// If set to false, all premium features will be enabled and no premium settings panel will be visible. * Toggle the variable below to enable support for premium stuff.
* If set to false, all premium features will be enabled and no premium settings panel will be visible.
* @var bool
*/
private static $premiumsupported = false; private static $premiumsupported = false;
/**
* Certficate code
* @var string
*/
private static $premiumcrt = "-----BEGIN CERTIFICATE----- private static $premiumcrt = "-----BEGIN CERTIFICATE-----
MIIDSzCCAjMCFFlyhmKf1fN7U5lQL/dtlsyP24AQMA0GCSqGSIb3DQEBCwUAMGEx MIIDSzCCAjMCFFlyhmKf1fN7U5lQL/dtlsyP24AQMA0GCSqGSIb3DQEBCwUAMGEx
CzAJBgNVBAYTAk5MMRYwFAYDVQQIDA1Ob29yZC1Ib2xsYW5kMRowGAYDVQQKDBFN CzAJBgNVBAYTAk5MMRYwFAYDVQQIDA1Ob29yZC1Ib2xsYW5kMRowGAYDVQQKDBFN
@ -59,78 +62,94 @@ n8FqBInMBhGu1uz0Qeo09ke0RHRnghP9EXfig/veMegASZeEhFqmS2Bdiy6gqeZ5
Klc5I28bGbvxIV5pnL6ZSjHEDp2WreM8HB0XFJwU+Q== Klc5I28bGbvxIV5pnL6ZSjHEDp2WreM8HB0XFJwU+Q==
-----END CERTIFICATE-----"; -----END CERTIFICATE-----";
/**
* Cached status
* @var object
*/
private static $cachedpremiumstatus = null; private static $cachedpremiumstatus = null;
/**
* Check if premium is supported
* @return bool
*/
public static function supported() { public static function supported() {
return self::$premiumsupported; return self::$premiumsupported;
} }
/**
* Decrypt encrypted data
* @param string $encrypted Encrypted data
*/
private static function decrypt($encrypted) { private static function decrypt($encrypted) {
// Get the public key. // Get the public key.
$key = \openssl_get_publickey(self::$premiumcrt); $key = \openssl_get_publickey(self::$premiumcrt);
if ($key === false ) { if ($key === false) {
throw new \ValueError("Error parsing public key data)"); throw new \ParseError("Error parsing public key data)");
} }
// Determine the key size. // Determine the key size.
$keysize = \openssl_pkey_get_details($key)["bits"]; $keysize = \openssl_pkey_get_details($key)["bits"];
//
$blocksize = ($keysize / 8); // Bits / 8. Whether padded or not. $blocksize = ($keysize / 8); // Bits / 8. Whether padded or not.
// Decode data in // Decode data in.
$b64 = \base64_decode($encrypted); $b64 = \base64_decode($encrypted);
if ($b64 === false) { if ($b64 === false) {
throw new \ValueError("Error in base64 decoding"); throw new \ParseError("Error in base64 decoding");
} }
$data = \str_split($b64, $blocksize); $data = \str_split($b64, $blocksize);
$decrypted = ""; $decrypted = "";
$i = 0; $i = 0;
foreach($data as $chunk) { foreach ($data as $chunk) {
if (\openssl_public_decrypt($chunk, $dchunk, $key, \OPENSSL_PKCS1_PADDING)) { if (\openssl_public_decrypt($chunk, $dchunk, $key, \OPENSSL_PKCS1_PADDING)) {
$decrypted .= $dchunk; $decrypted .= $dchunk;
} else { } else {
throw new \ValueError("Error decrypting chunk $i ({$blocksize} bytes)"); throw new \ParseError("Error decrypting chunk $i ({$blocksize} bytes)");
} }
$i++; $i++;
} }
// Deprecated in PHP 8.0 and up, but included to be compatible with 7.4. // Deprecated in PHP 8.0 and up, but included to be compatible with 7.4.
// Wrap in a try/catch in case the function is removed in a later version. if (\PHP_MAJOR_VERSION < 8) {
try {
\openssl_pkey_free($key); \openssl_pkey_free($key);
} catch (\Exception $x) {} }
return $decrypted; return $decrypted;
} }
/**
* Trim headers from key data
* @param string $data the key dat with headers
* @return string Key without headers
*/
private static function trim_headers($data) { private static function trim_headers($data) {
// Headers are repeated in this function for easier testing and copy-pasting into other projects. // Headers are repeated in this function for easier testing and copy-pasting into other projects.
$STARTHEADER = "----- BEGIN ACTIVATION KEY -----"; $startheader = "----- BEGIN ACTIVATION KEY -----";
$ENDHEADER = "----- END ACTIVATION KEY -----"; $endheader = "----- END ACTIVATION KEY -----";
$parts = preg_split("/\r?\n/", \trim($data)); $parts = preg_split("/\r?\n/", \trim($data));
if (count($parts) > 2) { if (count($parts) > 2) {
$start = -1; $start = -1;
$end = -1; $end = -1;
for($i = 0; $i < count($parts); $i++) { for ($i = 0; $i < count($parts); $i++) {
$p = trim(preg_replace('/\s+/u', ' ', $parts[$i])); // Make sure all unicode spaces are converted to normal spaces before comparing... // Make sure all unicode spaces are converted to normal spaces before comparing.
$p = trim(preg_replace('/\s+/u', ' ', $parts[$i]));
if ( $p == $STARTHEADER ) { if ($p == $startheader) {
$start = $i+1; $start = $i + 1;
} }
if ($start > 0 && $p == $ENDHEADER) { if ($start > 0 && $p == $endheader) {
$end = $i; $end = $i;
} }
} }
if ($start < 0 || $end < 0 || $end - $start <= 0) { if ($start < 0 || $end < 0 || $end - $start <= 0) {
throw new \ValueError("Invalid activation key wrappers"); throw new \ParseError("Invalid activation key wrappers");
} else { } else {
$keyslice = array_slice($parts, $start, $end - $start); $keyslice = array_slice($parts, $start, $end - $start);
return implode("\n", $keyslice); return implode("\n", $keyslice);
} }
} else { } else {
throw new \ValueError("Invalid activation key"); throw new \ParseError("Invalid activation key");
} }
} }
@ -140,7 +159,7 @@ Klc5I28bGbvxIV5pnL6ZSjHEDp2WreM8HB0XFJwU+Q==
*/ */
public static function enabled() { public static function enabled() {
if (self::$premiumsupported) { if (self::$premiumsupported) {
$status = self::premiumStatus(); $status = self::premiumstatus();
return $status->enabled; return $status->enabled;
} else { } else {
return true; return true;
@ -153,7 +172,7 @@ Klc5I28bGbvxIV5pnL6ZSjHEDp2WreM8HB0XFJwU+Q==
* @return bool * @return bool
*/ */
public static function has_intent($intent) { public static function has_intent($intent) {
$status = self::premiumStatus(); $status = self::premiumstatus();
if ($status->enabled) { if ($status->enabled) {
return \in_array(\strtolower($intent), $status->intents); return \in_array(\strtolower($intent), $status->intents);
} else { } else {
@ -161,6 +180,9 @@ Klc5I28bGbvxIV5pnL6ZSjHEDp2WreM8HB0XFJwU+Q==
} }
} }
/**
* Generate debug info.
*/
public static function debuginfo() { public static function debuginfo() {
$s = "<pre>"; $s = "<pre>";
try { try {
@ -172,10 +194,10 @@ Klc5I28bGbvxIV5pnL6ZSjHEDp2WreM8HB0XFJwU+Q==
$s .= $json . "\n----\n"; $s .= $json . "\n----\n";
$decoded = \json_decode($json, false); $decoded = \json_decode($json, false);
$s .= "Read as object:\n----\n"; $s .= "Read as object:\n----\n";
$s .= print_r($decoded, true) ."\n----\n"; $s .= \json_encode($decoded, \JSON_PRETTY_PRINT) ."\n----\n";
$status = self::premiumStatus(); $status = self::premiumstatus();
$s .= "Parsed premium status block:\n----\n"; $s .= "Parsed premium status block:\n----\n";
$s .= print_r($status, true) ."\n----\n"; $s .= \json_encode($status, \JSON_PRETTY_PRINT) ."\n----\n";
$s .= "Message: " . self::statusdescription() . "\n"; $s .= "Message: " . self::statusdescription() . "\n";
} else { } else {
$s .= "Premium key empty"; $s .= "Premium key empty";
@ -188,14 +210,15 @@ Klc5I28bGbvxIV5pnL6ZSjHEDp2WreM8HB0XFJwU+Q==
} }
} }
$s.="</pre>"; $s .= "</pre>";
return $s; return $s;
} }
/** /**
* Determine, cache and retrieve premium status * Determine, cache and retrieve premium status
* @return object * @return object
*/ */
protected static function premiumStatus() { protected static function premiumstatus() {
if (!isset(self::$cachedpremiumstatus)) { if (!isset(self::$cachedpremiumstatus)) {
// Initialize default object. // Initialize default object.
@ -217,9 +240,9 @@ Klc5I28bGbvxIV5pnL6ZSjHEDp2WreM8HB0XFJwU+Q==
if (is_object($decoded)) { if (is_object($decoded)) {
// Copy basic data/ // Copy basic data/.
$keys = ["name", "website", "expires", "issued"]; $keys = ["name", "website", "expires", "issued"];
foreach ( $keys as $k) { foreach ($keys as $k) {
if (isset($decoded->$k)) { if (isset($decoded->$k)) {
$o->$k = $decoded->$k; $o->$k = $decoded->$k;
} }
@ -229,24 +252,26 @@ Klc5I28bGbvxIV5pnL6ZSjHEDp2WreM8HB0XFJwU+Q==
$o->intents = explode(", ", $decoded->intent); $o->intents = explode(", ", $decoded->intent);
} }
// Convert dates to DateTime for // Convert dates to DateTime for.
$now = new \DateTime(); $now = new \DateTime();
$issuedate = new \DateTime($o->issued); $issuedate = new \DateTime($o->issued);
$expirydate = new \DateTime(); // Default to now
if ($o->expires == 'never') { if ($o->expires == 'never') {
// If expiry date == never // If expiry date == never.
$expirydate = new \DateTime(); // Default to now and add a year.
$expirydate->add(new \DateInterval("P1Y")); $expirydate->add(new \DateInterval("P1Y"));
} else { } else {
try { try {
$expirydate = new \DateTime($o->expires); $expirydate = new \DateTime($o->expires);
} catch (\Exception $x) {} } catch (\Exception $x) {
$expirydate = new \DateTime(); // Default to now.
}
} }
if ( \in_array('treestudyplan', $o->intents) if (\in_array('treestudyplan', $o->intents)
&& !empty($o->issued) && !empty($o->issued)
&& self::website_match($o->website) && self::website_match($o->website)
) { ) {
if ($expirydate > $now ) { if ($expirydate > $now) {
$o->enabled = true; $o->enabled = true;
$o->expired = false; $o->expired = false;
} else { } else {
@ -263,7 +288,7 @@ Klc5I28bGbvxIV5pnL6ZSjHEDp2WreM8HB0XFJwU+Q==
} }
} }
} }
} catch (\ValueError $x) { } catch (\ParseError $x) {
$o->status = \get_string("premium:invalidactivationcontent", "local_treestudyplan"); $o->status = \get_string("premium:invalidactivationcontent", "local_treestudyplan");
} }
self::$cachedpremiumstatus = $o; self::$cachedpremiumstatus = $o;
@ -281,7 +306,7 @@ Klc5I28bGbvxIV5pnL6ZSjHEDp2WreM8HB0XFJwU+Q==
// Add double slashes to key and site if no scheme is set. // Add double slashes to key and site if no scheme is set.
// Basically: if no double slashes present before any dots, slashes or @s. // Basically: if no double slashes present before any dots, slashes or @s.
if (!\preg_match_all('#^[^./@]*?//#', $key )) { if (!\preg_match_all('#^[^./@]*?//#', $key)) {
$key = "//".$key; $key = "//".$key;
} }
if (!\preg_match_all('#^[^./@]*?//#', $site)) { if (!\preg_match_all('#^[^./@]*?//#', $site)) {
@ -291,13 +316,13 @@ Klc5I28bGbvxIV5pnL6ZSjHEDp2WreM8HB0XFJwU+Q==
$keyurl = (object)\parse_url($key); $keyurl = (object)\parse_url($key);
$siteurl = (object)\parse_url($site); $siteurl = (object)\parse_url($site);
// No match if host is empty on key or site // No match if host is empty on key or site.
if (empty($keyurl->host) || empty($siteurl->host)) { if (empty($keyurl->host) || empty($siteurl->host)) {
return false; return false;
} }
if ($keyurl->host == "*") { if ($keyurl->host == "*") {
// * matches all // Value * matches all.
return true; return true;
} }
@ -306,37 +331,41 @@ Klc5I28bGbvxIV5pnL6ZSjHEDp2WreM8HB0XFJwU+Q==
$siteparts = \array_reverse(\explode(".", $siteurl->host)); $siteparts = \array_reverse(\explode(".", $siteurl->host));
// Trim starting www from both parts, since site.domain and www.site.domain should be treated as the same. // Trim starting www from both parts, since site.domain and www.site.domain should be treated as the same.
if (($x = \array_pop($keyparts)) != "www") {\array_push($keyparts, $x);} if (($x = \array_pop($keyparts)) != "www") {
if (($x = \array_pop($siteparts)) != "www") {\array_push($siteparts, $x);} \array_push($keyparts, $x);
}
if (($x = \array_pop($siteparts)) != "www") {
\array_push($siteparts, $x);
}
for ($i = 0; $i < count($keyparts); $i++) { for ($i = 0; $i < count($keyparts); $i++) {
// No match if the site does not have a part, but the key does. Unless the key part is * // No match if the site does not have a part, but the key does. Unless the key part is .
if (!isset($siteparts[$i]) ) { if (!isset($siteparts[$i])) {
if ($keyparts[$i] != "*") { if ($keyparts[$i] != "*") {
return false; return false;
} else { } else {
$i++; //increment $i by one before break, to make sure the comparison following this loop holds. $i++; // Increment $i by one before break, to make sure the comparison following this loop holds.
break; // Stop comparison. Host part matches. break; // Stop comparison. Host part matches.
} }
} }
// Now do a proper case insensitive check for matching. // Now do a proper case insensitive check for matching.
// Uses fnmatch to easily handle shell type wildcards. // Uses fnmatch to easily handle shell type wildcards.
if ( ! \fnmatch($keyparts[$i], $siteparts[$i], \FNM_CASEFOLD)) { if (! \fnmatch($keyparts[$i], $siteparts[$i], \FNM_CASEFOLD)) {
return false; return false;
} }
} }
// Fail if the site has a deeper subdomain than the key, unless the deepest key subdomain is * // Fail if the site has a deeper subdomain than the key, unless the deepest key subdomain is .
if ($keyparts[$i-1] != '*' && count($siteparts) > ($i)) { if ($keyparts[$i - 1] != '*' && count($siteparts) > ($i)) {
return false; return false;
} }
// If we made it here then the host part matches. Now check the path. // If we made it here then the host part matches. Now check the path.
// If path is /*, matches all subpaths including / // If path is /*, matches all subpaths including /.
$keypath = empty($keyurl->path) ? "/" : $keyurl->path; $keypath = empty($keyurl->path) ? "/" : $keyurl->path;
$sitepath = empty($siteurl->path) ? "/" : $siteurl->path; $sitepath = empty($siteurl->path) ? "/" : $siteurl->path;
// Trim trailing / from both paths before comparison // Trim trailing / from both paths before comparison.
if (\strlen($sitepath) > 1) { if (\strlen($sitepath) > 1) {
$sitepath = \rtrim($sitepath, "/"); $sitepath = \rtrim($sitepath, "/");
} }
@ -365,7 +394,9 @@ Klc5I28bGbvxIV5pnL6ZSjHEDp2WreM8HB0XFJwU+Q==
"name" => new \external_value(PARAM_TEXT, 'premium registration name'), "name" => new \external_value(PARAM_TEXT, 'premium registration name'),
"expires" => new \external_value(PARAM_TEXT, 'premium registration expiry date'), "expires" => new \external_value(PARAM_TEXT, 'premium registration expiry date'),
"expired" => new \external_value(PARAM_BOOL, 'premium status expired'), "expired" => new \external_value(PARAM_BOOL, 'premium status expired'),
"intents" => new \external_multiple_structure(new \external_value(PARAM_TEXT), 'additional intents included in the license '), "intents" => new \external_multiple_structure(
new \external_value(PARAM_TEXT), 'additional intents included in the license'
),
]); ]);
} }
@ -375,7 +406,7 @@ Klc5I28bGbvxIV5pnL6ZSjHEDp2WreM8HB0XFJwU+Q==
*/ */
public static function get_premiumstatus() { public static function get_premiumstatus() {
if (self::$premiumsupported) { if (self::$premiumsupported) {
$status = self::premiumStatus(); $status = self::premiumstatus();
$keys = [ $keys = [
"enabled", "enabled",
"website", "website",
@ -386,7 +417,7 @@ Klc5I28bGbvxIV5pnL6ZSjHEDp2WreM8HB0XFJwU+Q==
]; ];
$result = []; $result = [];
foreach ( $keys as $param) { foreach ($keys as $param) {
$result[$param] = $status->$param; $result[$param] = $status->$param;
} }
return $result; return $result;
@ -407,7 +438,7 @@ Klc5I28bGbvxIV5pnL6ZSjHEDp2WreM8HB0XFJwU+Q==
* @return string HTML description of premium status. * @return string HTML description of premium status.
*/ */
public static function statusdescription() { public static function statusdescription() {
$status = self::premiumStatus(); $status = self::premiumstatus();
$msg = new \stdClass; $msg = new \stdClass;
$msg->name = $status->name; $msg->name = $status->name;
@ -439,4 +470,4 @@ Klc5I28bGbvxIV5pnL6ZSjHEDp2WreM8HB0XFJwU+Q==
} }
} }
} }

View File

@ -40,6 +40,9 @@ class reportservice extends \external_api {
*/ */
const CAP_VIEW = "local/treestudyplan:viewuserreports"; const CAP_VIEW = "local/treestudyplan:viewuserreports";
/**
* Parameter description for webservice function
*/
public static function get_report_structure_parameters(): \external_function_parameters { public static function get_report_structure_parameters(): \external_function_parameters {
return new \external_function_parameters([ return new \external_function_parameters([
"pageid" => new \external_value(PARAM_INT, 'id of studyplan page'), "pageid" => new \external_value(PARAM_INT, 'id of studyplan page'),
@ -83,7 +86,7 @@ class reportservice extends \external_api {
$firstperiod = 1; $firstperiod = 1;
} }
if (empty($lastperiod)) { if (empty($lastperiod)) {
// Index for periods starts at 1, so the period count is same as length // Index for periods starts at 1, so the period count is same as length.
$lastperiod = $page->periods(); $lastperiod = $page->periods();
} }
@ -95,9 +98,9 @@ class reportservice extends \external_api {
"periods" => [], "periods" => [],
]; ];
// Find all relevant parts in this order // Find all relevant parts in this order.
for($i = $firstperiod; $i <= $lastperiod; $i++) { for ($i = $firstperiod; $i <= $lastperiod; $i++) {
$period = period::find($page, $i); $period = period::find($page, $i);
$pmodel = [ $pmodel = [
'period' => $period->model(), 'period' => $period->model(),
@ -127,13 +130,15 @@ class reportservice extends \external_api {
return $model; return $model;
} }
/**
* Parameter description for webservice function
*/
public static function get_report_data_parameters(): \external_function_parameters { public static function get_report_data_parameters(): \external_function_parameters {
return new \external_function_parameters([ return new \external_function_parameters([
"pageid" => new \external_value(PARAM_INT, 'id of studyplan page'), "pageid" => new \external_value(PARAM_INT, 'id of studyplan page'),
"userid" => new \external_value(PARAM_INT, 'id of user'), "userid" => new \external_value(PARAM_INT, 'id of user'),
"firstperiod" => new \external_value(PARAM_INT, 'first period to include in report', VALUE_DEFAULT), "firstperiod" => new \external_value(PARAM_INT, 'first period to include in report', VALUE_DEFAULT),
"lastperiod" => new \external_value(PARAM_INT, 'last period to include in report', VALUE_DEFAULT), "lastperiod" => new \external_value(PARAM_INT, 'last period to include in report', VALUE_DEFAULT),
]); ]);
} }
@ -144,6 +149,13 @@ class reportservice extends \external_api {
return new \external_multiple_structure(studyitem::user_structure(), "Information per studyitem"); return new \external_multiple_structure(studyitem::user_structure(), "Information per studyitem");
} }
/**
* Webservice function get_report data
* @param int $pageid
* @param int $userid
* @param int|null $firsteperiod
* @param int|null $lastperiod
*/
public static function get_report_data($pageid, $userid, $firstperiod=null, $lastperiod=null) { public static function get_report_data($pageid, $userid, $firstperiod=null, $lastperiod=null) {
$page = studyplanpage::find_by_id($pageid); $page = studyplanpage::find_by_id($pageid);
webservicehelper::require_capabilities(self::CAP_VIEW, $page->studyplan()->context()); webservicehelper::require_capabilities(self::CAP_VIEW, $page->studyplan()->context());
@ -156,15 +168,12 @@ class reportservice extends \external_api {
$firstperiod = 1; $firstperiod = 1;
} }
if (empty($lastperiod)) { if (empty($lastperiod)) {
// Index for periods starts at 1, so the period count is same as length // Index for periods starts at 1, so the period count is same as length.
$lastperiod = $page->periods(); $lastperiod = $page->periods();
} }
$model = []; $model = [];
for ($i = $firstperiod; $i <= $lastperiod; $i++) {
//TODO: Build report structure
for($i = $firstperiod; $i <= $lastperiod; $i++) {
$lines = studyline::find_page_children($page); $lines = studyline::find_page_children($page);
foreach ($lines as $line) { foreach ($lines as $line) {
$items = studyitem::search_studyline_children($line, $i, studyitem::COURSE); $items = studyitem::search_studyline_children($line, $i, studyitem::COURSE);
@ -175,11 +184,12 @@ class reportservice extends \external_api {
} }
} }
} }
return $model; return $model;
} }
/**
* Parameter description for webservice function
*/
public static function get_report_details_parameters(): \external_function_parameters { public static function get_report_details_parameters(): \external_function_parameters {
return new \external_function_parameters([ return new \external_function_parameters([
"itemid" => new \external_value(PARAM_INT, 'id of studyitem'), "itemid" => new \external_value(PARAM_INT, 'id of studyitem'),

View File

@ -127,7 +127,7 @@ class studentstudyplanservice extends \external_api {
if ($studyplan->exist_for_user($userid)) { if ($studyplan->exist_for_user($userid)) {
$model = $studyplan->user_model($userid); $model = $studyplan->user_model($userid);
return $model; return $model;
} else { } else {
return null; return null;
@ -222,7 +222,7 @@ class studentstudyplanservice extends \external_api {
return []; return [];
} }
// Do not validate the context in this instance to avoid requiring logins when this is not wanted // Do not validate the context in this instance to avoid requiring logins when this is not wanted.
$userid = $invite->user_id; $userid = $invite->user_id;
@ -279,7 +279,7 @@ class studentstudyplanservice extends \external_api {
return []; return [];
} }
// Do not validate the context in this instance to avoid requiring logins when this is not wanted // Do not validate the context in this instance to avoid requiring logins when this is not wanted.
$studyplan = studyplan::find_by_id($studyplanid); $studyplan = studyplan::find_by_id($studyplanid);
$userid = $invite->user_id; $userid = $invite->user_id;
@ -341,7 +341,7 @@ class studentstudyplanservice extends \external_api {
return []; return [];
} }
// Do not validate the context in this instance to avoid requiring logins when this is not wanted // Do not validate the context in this instance to avoid requiring logins when this is not wanted.
$page = studyplanpage::find_by_id($pageid); $page = studyplanpage::find_by_id($pageid);
$studyplan = $page->studyplan(); $studyplan = $page->studyplan();
@ -430,8 +430,8 @@ class studentstudyplanservice extends \external_api {
$studyplan = studyplan::find_by_id($studyplanid); $studyplan = studyplan::find_by_id($studyplanid);
if ($studyplan->exist_for_user($userid) && !$studyplan->suspended()) { if ($studyplan->exist_for_user($userid) && !$studyplan->suspended()) {
$model = $studyplan->user_model($userid); $model = $studyplan->user_model($userid);
return $model; return $model;
} else { } else {
throw new \moodle_exception("You do not have access to this studyplan"); throw new \moodle_exception("You do not have access to this studyplan");
} }
@ -556,8 +556,8 @@ class studentstudyplanservice extends \external_api {
\external_api::validate_context(\context_system::instance()); \external_api::validate_context(\context_system::instance());
// Return empty list if coach role not enabled // Return empty list if coach role not enabled.
if ( ! (premium::enabled() && \get_config("local_treestudyplan", "enablecoach")) ) { if (! (premium::enabled() && \get_config("local_treestudyplan", "enablecoach"))) {
return []; return [];
} }
@ -570,7 +570,9 @@ class studentstudyplanservice extends \external_api {
if (has_capability(self::CAP_COACH, $studyplan->context(), $USER)) { if (has_capability(self::CAP_COACH, $studyplan->context(), $USER)) {
$list[] = $studyplan->simple_model_coach(); $list[] = $studyplan->simple_model_coach();
} }
} catch(\Exception $x) {} } catch (\Exception $x) {
$studyplan = null;
}
} }
return $list; return $list;
} }
@ -694,7 +696,7 @@ class studentstudyplanservice extends \external_api {
$o->enrol($userid); $o->enrol($userid);
} }
// Trigger immediate cohort synchronization for this line only // Trigger immediate cohort synchronization for this line only.
autocohortsync::syncline($o); autocohortsync::syncline($o);
return $o->enrol_info_model($userid); return $o->enrol_info_model($userid);

View File

@ -422,7 +422,7 @@ class studyitem {
foreach ($resequence as $sq) { foreach ($resequence as $sq) {
// Only change line_id if new line is within the same studyplan page. // Only change line_id if new line is within the same studyplan page.
if ( self::find_by_id($sq['id'])->studyline()->page()->id() == if (self::find_by_id($sq['id'])->studyline()->page()->id() ==
studyline::find_by_id($sq['line_id'])->page()->id() ) { studyline::find_by_id($sq['line_id'])->page()->id() ) {
$DB->update_record(self::TABLE, [ $DB->update_record(self::TABLE, [
'id' => $sq['id'], 'id' => $sq['id'],
@ -646,7 +646,7 @@ class studyitem {
break; break;
} }
} }
return ($notexpired) ? completion : :COMPLETED : completion::INCOMPLETE; return ($notexpired) ? completion::COMPLETED : completion::INCOMPLETE;
} else { } else {
return completion::COMPLETED; return completion::COMPLETED;
} }

View File

@ -96,7 +96,7 @@ class studyitemconnection {
/** /**
* Get target studyitem id for this connection * Get target studyitem id for this connection
*/ */
public function to_id() :int { public function to_id(): int {
return $this->r->to_id; return $this->r->to_id;
} }
@ -167,9 +167,9 @@ class studyitemconnection {
'to_id' => $toid, 'to_id' => $toid,
]); ]);
return success::success(['msg'=>'Items Disconnected']); return success::success(['msg' => 'Items Disconnected']);
} else { } else {
return success::success(['msg'=>'Connection does not exist']); return success::success(['msg' => 'Connection does not exist']);
} }
} }

View File

@ -179,14 +179,14 @@ class studyline {
* Whether this line is enrollable by the student * Whether this line is enrollable by the student
*/ */
public function self_enrollable(): bool { public function self_enrollable(): bool {
return ($this->r->enrollable == self::ENROLLABLE_SELF || $this->r->enrollable == self::ENROLLABLE_SELF_ROLE); return ($this->r->enrollable == self::ENROLLABLE_SELF || $this->r->enrollable == self::ENROLLABLE_SELF_ROLE);
} }
/** /**
* Whether this line is enrollable by a role * Whether this line is enrollable by a role
*/ */
public function role_enrollable(): bool { public function role_enrollable(): bool {
return ($this->r->enrollable == self::ENROLLABLE_ROLE || $this->r->enrollable == self::ENROLLABLE_SELF_ROLE); return ($this->r->enrollable == self::ENROLLABLE_ROLE || $this->r->enrollable == self::ENROLLABLE_SELF_ROLE);
} }
/** /**
@ -198,13 +198,13 @@ class studyline {
$plan = $this->studyplan(); $plan = $this->studyplan();
if (!empty($userid)) { if (!empty($userid)) {
if ( $plan->has_linked_user(intval($userid))) { if ($plan->has_linked_user(intval($userid))) {
if ( $this->self_enrollable() && $userid == $USER->id ) { if ($this->self_enrollable() && $userid == $USER->id) {
return true; return true;
} }
if ( $this->role_enrollable()) { if ($this->role_enrollable()) {
$context = $plan->context(); $context = $plan->context();
foreach ( $this->enrol_roleids() as $rid) { foreach ($this->enrol_roleids() as $rid) {
if (\user_has_role_assignment($USER->id, $rid, $context->id)) { if (\user_has_role_assignment($USER->id, $rid, $context->id)) {
return true; return true;
} }
@ -212,9 +212,9 @@ class studyline {
} }
} }
} else { } else {
if ( $this->role_enrollable()) { if ($this->role_enrollable()) {
$context = $plan->context(); $context = $plan->context();
foreach ( $this->enrol_roleids() as $rid) { foreach ($this->enrol_roleids() as $rid) {
if (\user_has_role_assignment($USER->id, $rid, $context->id)) { if (\user_has_role_assignment($USER->id, $rid, $context->id)) {
return true; return true;
} }
@ -239,13 +239,15 @@ class studyline {
global $DB; global $DB;
$list = []; $list = [];
$roles = explode(", ", $this->r->enrolrole); $roles = explode(", ", $this->r->enrolrole);
foreach($roles as $r) { foreach ($roles as $r) {
$roleid = intval($r); $roleid = intval($r);
if ($roleid > 0) { if ($roleid > 0) {
try { try {
$role = $DB->get_record('role', ["id" => $roleid], "*", MUST_EXIST); $role = $DB->get_record('role', ["id" => $roleid], "*", MUST_EXIST);
$list[] = $role; $list[] = $role;
} catch(\Exception $x) {} } catch (\Exception $x) {
$role = null;
}
} }
} }
@ -259,7 +261,7 @@ class studyline {
global $DB; global $DB;
$list = []; $list = [];
$roles = explode(", ", $this->r->enrolrole); $roles = explode(", ", $this->r->enrolrole);
foreach($roles as $r) { foreach ($roles as $r) {
$roleid = intval($r); $roleid = intval($r);
if ($roleid > 0) { if ($roleid > 0) {
$list[] = $roleid; $list[] = $roleid;
@ -274,7 +276,7 @@ class studyline {
*/ */
public function enrol_roles_model(): array { public function enrol_roles_model(): array {
$list = []; $list = [];
foreach($this->enrol_roles() as $role) { foreach ($this->enrol_roles() as $role) {
$name = role_get_name($role, $this->context()); // Get localized role name. $name = role_get_name($role, $this->context()); // Get localized role name.
$list[] = [ $list[] = [
"id" => $role->id, "id" => $role->id,
@ -304,7 +306,7 @@ class studyline {
self::SLOTSET_FILTER => new \external_multiple_structure( self::SLOTSET_FILTER => new \external_multiple_structure(
studyitem::editor_structure(), 'filter items'), studyitem::editor_structure(), 'filter items'),
]) ])
) ),
], "Study line editor structure", $value); ], "Study line editor structure", $value);
} }
@ -552,7 +554,7 @@ class studyline {
self::SLOTSET_FILTER => new \external_multiple_structure( self::SLOTSET_FILTER => new \external_multiple_structure(
studyitem::user_structure(), 'filter items'), studyitem::user_structure(), 'filter items'),
]) ])
) ),
], 'Studyline with user info', $value); ], 'Studyline with user info', $value);
} }
@ -571,7 +573,7 @@ class studyline {
]; ];
if (!empty($userid)) { if (!empty($userid)) {
$r = $DB->get_record('local_treestudyplan_lineuser', [ $r = $DB->get_record('local_treestudyplan_lineuser', [
'line_id' => $this->id(), 'line_id' => $this->id(),
'user_id' => $userid, 'user_id' => $userid,
]); ]);
@ -606,15 +608,16 @@ class studyline {
return $model; return $model;
} }
/** Get the users enrolled in this studyline if relevant /**
* @return array of Userids linked * Get the users enrolled in this studyline if relevant
* @return array of Userids linked
*/ */
public function get_enrolled_userids() { public function get_enrolled_userids() {
$userids = $this->studyplan()->find_linked_userids(); $userids = $this->studyplan()->find_linked_userids();
if ($this->enrollable()) { if ($this->enrollable()) {
$list = []; $list = [];
foreach($userids as $uid) { foreach ($userids as $uid) {
if ( $this->isenrolled($uid)) { if ($this->isenrolled($uid)) {
$list[] = $uid; $list[] = $uid;
} }
} }
@ -632,7 +635,7 @@ class studyline {
public function isenrolled($userid) { public function isenrolled($userid) {
global $DB; global $DB;
if ($this->r->enrollable == self::ENROLLABLE_NONE) { if ($this->r->enrollable == self::ENROLLABLE_NONE) {
return true; // If student cannot enrol, the student always is enrolled return true; // If student cannot enrol, the student always is enrolled.
} else { } else {
$r = $DB->get_record('local_treestudyplan_lineuser', [ $r = $DB->get_record('local_treestudyplan_lineuser', [
'line_id' => $this->id(), 'line_id' => $this->id(),
@ -721,7 +724,7 @@ class studyline {
// Registration already exists, check if enrolled is true and update accordingly. // Registration already exists, check if enrolled is true and update accordingly.
if (boolval($r->enrolled)) { if (boolval($r->enrolled)) {
$r->enrolled = 0; $r->enrolled = 0;
$r->timeenrolled = time(); // Regi $r->timeenrolled = time(); // Regi.
$r->enrolledby = $USER->id; $r->enrolledby = $USER->id;
$DB->update_record("local_treestudyplan_lineuser", $r); $DB->update_record("local_treestudyplan_lineuser", $r);

View File

@ -146,7 +146,7 @@ class studyplan {
*/ */
public function startdate() { public function startdate() {
$date = null; $date = null;
foreach($this->pages() as $p) { foreach ($this->pages() as $p) {
if (!isset($date) || $p->startdate() < $date) { if (!isset($date) || $p->startdate() < $date) {
$date = $p->startdate(); $date = $p->startdate();
} }
@ -154,13 +154,17 @@ class studyplan {
return $date; return $date;
} }
/**
* Determine studyplan icon
* @return string Url of icon
*/
private function icon() { private function icon() {
global $CFG; global $CFG;
$fs = \get_file_storage(); $fs = \get_file_storage();
// Returns an array of `stored_file` instances. // Returns an array of `stored_file` instances.
$files = $fs->get_area_files(\context_system::instance()->id, 'local_treestudyplan', 'icon', $this->id); $files = $fs->get_area_files(\context_system::instance()->id, 'local_treestudyplan', 'icon', $this->id);
if (count($files) > 0 ) { if (count($files) > 0) {
$file = array_shift($files); $file = array_shift($files);
if ($file->get_filename() == ".") { if ($file->get_filename() == ".") {
// Get next file if the first is the directory itself. // Get next file if the first is the directory itself.
@ -183,8 +187,14 @@ class studyplan {
// Fall back to the standard (ugly) default image. // Fall back to the standard (ugly) default image.
$url = new \moodle_url($CFG->wwwroot . "/local/treestudyplan/pix/default_icon.png"); $url = new \moodle_url($CFG->wwwroot . "/local/treestudyplan/pix/default_icon.png");
} else { } else {
$url = \moodle_url::make_pluginfile_url(\context_system::instance()->id, 'local_treestudyplan', 'defaulticon', 0, "/", $url = \moodle_url::make_pluginfile_url(
$defaulticon); \context_system::instance()->id,
'local_treestudyplan',
'defaulticon',
0,
"/",
$defaulticon
);
} }
} }
@ -201,9 +211,9 @@ class studyplan {
// The pluginfile URL which will serve the request. // The pluginfile URL which will serve the request.
'pluginfile.php', 'pluginfile.php',
// The combination of contextid / component / filearea / itemid // The combination of contextid / component / filearea / itemid.
// form the virtual bucket that file are stored in. // Form the virtual bucket that file are stored in.
\context_system::instance()->id, // System instance is always used for this \context_system::instance()->id, // System instance is always used for this.
'local_treestudyplan', 'local_treestudyplan',
'studyplan', 'studyplan',
$this->id $this->id
@ -216,7 +226,7 @@ class studyplan {
* @return studyplanpage[] * @return studyplanpage[]
*/ */
public function pages($refresh=false): array { public function pages($refresh=false): array {
if ( ((bool)$refresh) || empty($this->pagecache)) { if (((bool)$refresh) || empty($this->pagecache)) {
$this->pagecache = studyplanpage::find_studyplan_children($this); $this->pagecache = studyplanpage::find_studyplan_children($this);
} }
return $this->pagecache; return $this->pagecache;
@ -266,7 +276,11 @@ class studyplan {
"aggregation_info" => aggregator::basic_structure(), "aggregation_info" => aggregator::basic_structure(),
"pages" => new \external_multiple_structure(studyplanpage::simple_structure(), 'pages'), "pages" => new \external_multiple_structure(studyplanpage::simple_structure(), 'pages'),
"progress" => new \external_value(PARAM_FLOAT, "fraction of completed modules", VALUE_OPTIONAL), "progress" => new \external_value(PARAM_FLOAT, "fraction of completed modules", VALUE_OPTIONAL),
"amteaching" => new \external_value(PARAM_BOOL, "Current user is teaching one or more courses in this studyplan", VALUE_OPTIONAL), "amteaching" => new \external_value(
PARAM_BOOL,
"Current user is teaching one or more courses in this studyplan",
VALUE_OPTIONAL
),
"suspended" => new \external_value(PARAM_BOOL, 'if studyplan is suspended', VALUE_OPTIONAL), "suspended" => new \external_value(PARAM_BOOL, 'if studyplan is suspended', VALUE_OPTIONAL),
], 'Basic studyplan info', $value); ], 'Basic studyplan info', $value);
} }
@ -313,7 +327,7 @@ class studyplan {
$model = $this->simple_model(); $model = $this->simple_model();
$users = $this->find_linked_userids(); $users = $this->find_linked_userids();
$sum = 0; $sum = 0;
foreach ( $users as $uid ) { foreach ($users as $uid) {
$sum += $this->scanuserprogress($uid); $sum += $this->scanuserprogress($uid);
} }
@ -408,7 +422,8 @@ class studyplan {
public static function add($fields, $bare = false): self { public static function add($fields, $bare = false): self {
global $CFG, $DB; global $CFG, $DB;
$addable = ['name', 'shortname', 'description', 'descriptionformat', 'idnumber', 'context_id', 'aggregation', 'aggregation_config']; $addable = ['name', 'shortname', 'description', 'descriptionformat',
'idnumber', 'context_id', 'aggregation', 'aggregation_config'];
$info = ['enddate' => null, "template" => 0, "suspended" => 0]; $info = ['enddate' => null, "template" => 0, "suspended" => 0];
foreach ($addable as $f) { foreach ($addable as $f) {
if (array_key_exists($f, $fields)) { if (array_key_exists($f, $fields)) {
@ -418,8 +433,8 @@ class studyplan {
$id = $DB->insert_record(self::TABLE, $info); $id = $DB->insert_record(self::TABLE, $info);
$plan = self::find_by_id($id); // Make sure the new studyplan is immediately cached. $plan = self::find_by_id($id); // Make sure the new studyplan is immediately cached.
// Add a single page and initialize it with placeholder data // Add a single page and initialize it with placeholder data.
// This makes it easier to create a new study plan // This makes it easier to create a new study plan.
// On import, adding an empty page messes things up , so we have an option to skip this.... // On import, adding an empty page messes things up , so we have an option to skip this....
if (!$bare) { if (!$bare) {
@ -459,7 +474,7 @@ class studyplan {
'aggregation', 'aggregation',
'aggregation_config', 'aggregation_config',
'suspended', 'suspended',
'template' 'template',
]; ];
$info = ['id' => $this->id ]; $info = ['id' => $this->id ];
foreach ($editable as $f) { foreach ($editable as $f) {
@ -501,7 +516,7 @@ class studyplan {
$DB->delete_records("local_treestudyplan_coach", ["studyplan_id" => $this->id]); $DB->delete_records("local_treestudyplan_coach", ["studyplan_id" => $this->id]);
$DB->delete_records("local_treestudyplan_cohort", ["studyplan_id" => $this->id]); $DB->delete_records("local_treestudyplan_cohort", ["studyplan_id" => $this->id]);
$DB->delete_records("local_treestudyplan_user", ["studyplan_id" => $this->id]); $DB->delete_records("local_treestudyplan_user", ["studyplan_id" => $this->id]);
//
$DB->delete_records('local_treestudyplan', ['id' => $this->id]); $DB->delete_records('local_treestudyplan', ['id' => $this->id]);
return success::success(); return success::success();
} }
@ -768,7 +783,8 @@ class studyplan {
return $this->linkeduserids; return $this->linkeduserids;
} }
/** Check if this studyplan is linked to a particular user /**
* Check if this studyplan is linked to a particular user
* @param bool|stdClass $user The userid or user record of the user * @param bool|stdClass $user The userid or user record of the user
*/ */
public function has_linked_user($user) { public function has_linked_user($user) {
@ -785,13 +801,14 @@ class studyplan {
} }
} }
/** Check if this studyplan is linked to a particular user /**
* Check if this studyplan is linked to a particular user
* @param bool|stdClass|null $user The userid or user record of the user Leave empty to check current user. * @param bool|stdClass|null $user The userid or user record of the user Leave empty to check current user.
*/ */
public function is_coach($user=null) { public function is_coach($user=null) {
global $DB, $USER global $DB, $USER;
;
if ( ! (premium::enabled() && \get_config("local_treestudyplan", "enablecoach")) ) { if (! (premium::enabled() && \get_config("local_treestudyplan", "enablecoach"))) {
// If coach role is not available, return false immediately. // If coach role is not available, return false immediately.
return false; return false;
} }
@ -803,7 +820,7 @@ class studyplan {
} else { } else {
$userid = $user->id; $userid = $user->id;
} }
$r = $DB->get_record(self::TABLE_COACH, ["studyplan_id" => $this->id, "user_id"=> $userid]); $r = $DB->get_record(self::TABLE_COACH, ["studyplan_id" => $this->id, "user_id" => $userid]);
if ($r && has_capability(associationservice::CAP_COACH, $this->context(), $userid)) { if ($r && has_capability(associationservice::CAP_COACH, $this->context(), $userid)) {
return true; return true;
@ -845,8 +862,8 @@ class studyplan {
$prg = $p->scanuserprogress($userid); $prg = $p->scanuserprogress($userid);
$progress += $prg; $progress += $prg;
} }
// Now average it out over the amount of pages // Now average it out over the amount of pages.
if (count($pages) > 0 ) { if (count($pages) > 0) {
return $progress / count($pages); return $progress / count($pages);
} else { } else {
return 0; return 0;
@ -912,7 +929,7 @@ class studyplan {
'description' => $this->r->description, 'description' => $this->r->description,
'descriptionformat' => $this->r->descriptionformat, 'descriptionformat' => $this->r->descriptionformat,
'aggregation' => $this->r->aggregation, 'aggregation' => $this->r->aggregation,
'aggregation_config' => $this->r->aggregation_config 'aggregation_config' => $this->r->aggregation_config,
], true); ], true);
// Copy any files related to this studyplan. // Copy any files related to this studyplan.
@ -981,7 +998,7 @@ class studyplan {
$json = json_encode([ $json = json_encode([
"type" => "studyplan", "type" => "studyplan",
"version" => 2.0, "version" => 2.0,
"studyplan" => $model "studyplan" => $model,
], \JSON_PRETTY_PRINT); ], \JSON_PRETTY_PRINT);
return [ "format" => "application/json", "content" => $json]; return [ "format" => "application/json", "content" => $json];
} }
@ -1042,10 +1059,10 @@ class studyplan {
*/ */
public function import_files($importfiles, $area) { public function import_files($importfiles, $area) {
$fs = get_file_storage(); $fs = get_file_storage();
foreach($importfiles as $file) { foreach ($importfiles as $file) {
if ($file['name'] != ".") { if ($file['name'] != ".") {
$fileinfo = [ $fileinfo = [
'contextid' =>\context_system::instance()->id, // ID of the system context. 'contextid' => \context_system::instance()->id, // ID of the system context.
'component' => 'local_treestudyplan', // Your component name. 'component' => 'local_treestudyplan', // Your component name.
'filearea' => $area, // Usually = table name. 'filearea' => $area, // Usually = table name.
'itemid' => $this->id, // ID of the studyplanpage. 'itemid' => $this->id, // ID of the studyplanpage.
@ -1094,11 +1111,11 @@ class studyplan {
// Create a new plan, based on the given parameters - this is the import studyplan part. // Create a new plan, based on the given parameters - this is the import studyplan part.
$plan = self::add( $planmodel, true); $plan = self::add( $planmodel, true);
// Import the files // Import the files.
if (isset( $planmodel['files']) && is_array( $planmodel['files'])) { if (isset( $planmodel['files']) && is_array( $planmodel['files'])) {
$plan->import_files( $planmodel['files'], "studyplan"); $plan->import_files( $planmodel['files'], "studyplan");
} }
// Import the icon // Import the icon.
if (isset( $planmodel['iconfiles']) && is_array( $planmodel['iconfiles'])) { if (isset( $planmodel['iconfiles']) && is_array( $planmodel['iconfiles'])) {
$plan->import_files( $planmodel['iconfiles'], "icon"); $plan->import_files( $planmodel['iconfiles'], "icon");
} }
@ -1215,7 +1232,7 @@ class studyplan {
$fields = $DB->get_fieldset_sql($sql, ["studyplan_id" => $this->id]); $fields = $DB->get_fieldset_sql($sql, ["studyplan_id" => $this->id]);
$list = []; $list = [];
foreach($fields as $id) { foreach ($fields as $id) {
$list[] = studyline::find_by_id($id); $list[] = studyline::find_by_id($id);
} }
return $list; return $list;

View File

@ -154,9 +154,9 @@ class studyplanpage {
// The pluginfile URL which will serve the request. // The pluginfile URL which will serve the request.
'pluginfile.php', 'pluginfile.php',
// The combination of contextid / component / filearea / itemid // The combination of contextid / component / filearea / itemid.
// form the virtual bucket that file are stored in. // Form the virtual bucket that file are stored in.
\context_system::instance()->id, // System instance is always used for this \context_system::instance()->id, // System instance is always used for this.
'local_treestudyplan', 'local_treestudyplan',
'studyplanpage', 'studyplanpage',
$this->id $this->id
@ -164,6 +164,10 @@ class studyplanpage {
return $text; return $text;
} }
/**
* Determine studyplan timing
* @return string
*/
public function timing() { public function timing() {
$now = new \DateTime(); $now = new \DateTime();
if ($now > $this->startdate()) { if ($now > $this->startdate()) {
@ -292,7 +296,7 @@ class studyplanpage {
} }
$id = $DB->insert_record(self::TABLE, $info); $id = $DB->insert_record(self::TABLE, $info);
// Refresh the page cache for the studyplan // Refresh the page cache for the studyplan.
$plan->pages(true); $plan->pages(true);
$page = self::find_by_id($id); // Make sure the new page is immediately cached. $page = self::find_by_id($id); // Make sure the new page is immediately cached.
@ -301,7 +305,7 @@ class studyplanpage {
if (get_config("local_treestudyplan", "copystudylinesnewpage")) { if (get_config("local_treestudyplan", "copystudylinesnewpage")) {
$templatepage = null; $templatepage = null;
$templatelines = []; $templatelines = [];
// find the latest a page with lines in the plan // Find the latest a page with lines in the plan.
$pages = $plan->pages(true); $pages = $plan->pages(true);
foreach ($pages as $p) { foreach ($pages as $p) {
if ($p->id() != $id) { if ($p->id() != $id) {
@ -322,7 +326,7 @@ class studyplanpage {
} }
if (count(studyline::find_page_children($page)) == 0) { if (count(studyline::find_page_children($page)) == 0) {
// Add an empty study line if there are currently no study lines // Add an empty study line if there are currently no study lines.
$lineinfo = ['page_id' => $id, $lineinfo = ['page_id' => $id,
'name' => get_string("default_line_name", "local_treestudyplan"), 'name' => get_string("default_line_name", "local_treestudyplan"),
'shortname' => get_string("default_line_shortname", "local_treestudyplan"), 'shortname' => get_string("default_line_shortname", "local_treestudyplan"),
@ -450,8 +454,8 @@ class studyplanpage {
} }
} }
} }
if ( $courses > 0 ) { if ($courses > 0) {
return ($completed/$courses); return ($completed / $courses);
} else { } else {
return 0; return 0;
} }
@ -502,7 +506,7 @@ class studyplanpage {
'description' => $this->r->description, 'description' => $this->r->description,
'pages' => $this->r->pages, 'pages' => $this->r->pages,
'startdate' => $this->startdate()->add($timeoffset)->format("Y-m-d"), 'startdate' => $this->startdate()->add($timeoffset)->format("Y-m-d"),
'enddate' => empty($this->r->enddate) ? null : ($this->enddate()->add($timeoffset)->format("Y-m-d")), 'enddate' => empty($this->r->enddate) ? null : ($this->enddate()->add($timeoffset)->format("Y-m-d")),
]); ]);
// Copy any files related to this page. // Copy any files related to this page.
@ -585,7 +589,7 @@ class studyplanpage {
$json = json_encode([ $json = json_encode([
"type" => "studyplanpage", "type" => "studyplanpage",
"version" => 2.0, "version" => 2.0,
"page" => $model "page" => $model,
], \JSON_PRETTY_PRINT); ], \JSON_PRETTY_PRINT);
return [ "format" => "application/json", "content" => $json]; return [ "format" => "application/json", "content" => $json];
} }
@ -752,10 +756,10 @@ class studyplanpage {
*/ */
public function import_files($importfiles, $area) { public function import_files($importfiles, $area) {
$fs = get_file_storage(); $fs = get_file_storage();
foreach($importfiles as $file) { foreach ($importfiles as $file) {
if ($file['name'] != ".") { if ($file['name'] != ".") {
$fileinfo = [ $fileinfo = [
'contextid' =>\context_system::instance()->id, // ID of the system context. 'contextid' => \context_system::instance()->id, // ID of the system context.
'component' => 'local_treestudyplan', // Your component name. 'component' => 'local_treestudyplan', // Your component name.
'filearea' => $area, // Usually = table name. 'filearea' => $area, // Usually = table name.
'itemid' => $this->id, // ID of the studyplanpage. 'itemid' => $this->id, // ID of the studyplanpage.

View File

@ -133,7 +133,7 @@ class studyplanservice extends \external_api {
webservicehelper::require_capabilities([self::CAP_EDIT, self::CAP_VIEW], $studyplan->context()); webservicehelper::require_capabilities([self::CAP_EDIT, self::CAP_VIEW], $studyplan->context());
} }
// If suspended, only allow the mapping if the user has edit rights // If suspended, only allow the mapping if the user has edit rights.
if (!has_capability(self::CAP_EDIT, $studyplan->context()) && $studyplan->suspended()) { if (!has_capability(self::CAP_EDIT, $studyplan->context()) && $studyplan->suspended()) {
return null; return null;
} else { } else {
@ -237,7 +237,7 @@ class studyplanservice extends \external_api {
'description' => $description, 'description' => $description,
'periods' => $periods, 'periods' => $periods,
'startdate' => $startdate, 'startdate' => $startdate,
'enddate' => empty($enddate) ? null : $enddate, 'enddate' => empty($enddate) ? null : $enddate,
'aggregation' => $aggregation, 'aggregation' => $aggregation,
'aggregation_config' => $aggregationconfig, 'aggregation_config' => $aggregationconfig,
'context_id' => $contextid, 'context_id' => $contextid,
@ -650,10 +650,10 @@ class studyplanservice extends \external_api {
'type' => $type, 'type' => $type,
'slot' => $slot, 'slot' => $slot,
'layer' => $layer, 'layer' => $layer,
'competency_id' => isset($details['competency_id']) ? $details['competency_id'] : null, 'competency_id' => isset($details['competency_id']) ? $details['competency_id'] : null,
'course_id' => isset($details['course_id']) ? $details['course_id'] : null, 'course_id' => isset($details['course_id']) ? $details['course_id'] : null,
'badge_id' => isset($details['badge_id']) ? $details['badge_id'] : null, 'badge_id' => isset($details['badge_id']) ? $details['badge_id'] : null,
'continuation_id' => isset($details['continuation_id']) ? $details['continuation_id'] : null, 'continuation_id' => isset($details['continuation_id']) ? $details['continuation_id'] : null,
]); ]);
return $o->editor_model(); return $o->editor_model();
} }
@ -902,7 +902,6 @@ class studyplanservice extends \external_api {
*/ */
public static function list_badges_parameters(): \external_function_parameters { public static function list_badges_parameters(): \external_function_parameters {
return new \external_function_parameters( [ return new \external_function_parameters( [
] ); ] );
} }
@ -922,12 +921,9 @@ class studyplanservice extends \external_api {
$result = []; $result = [];
$badges = badges_get_badges(BADGE_TYPE_SITE, "timemodified"); $badges = badges_get_badges(BADGE_TYPE_SITE, "timemodified");
foreach ($badges as $badge) { foreach ($badges as $badge) {
// TODO: Add config option to list only active badges.
$result[] = (new badgeinfo($badge))->editor_model(); $result[] = (new badgeinfo($badge))->editor_model();
// TODO: Include course badges somehow... Just site badges is not enough.
} }
return $result; return $result;
@ -962,7 +958,7 @@ class studyplanservice extends \external_api {
*/ */
public static function search_badges($search, $active=false) { public static function search_badges($search, $active=false) {
$systemcontext = webservicehelper::system_context(); $systemcontext = webservicehelper::system_context();
// Check system permission to // Check system permission to.
webservicehelper::require_capabilities("moodle/badges:viewbadges", $systemcontext); webservicehelper::require_capabilities("moodle/badges:viewbadges", $systemcontext);
$results = []; $results = [];
@ -1115,8 +1111,9 @@ class studyplanservice extends \external_api {
$studyplan = $item->studyline()->studyplan(); $studyplan = $item->studyline()->studyplan();
if (($studyplan->is_coach() && !$studyplan->suspended()) if (($studyplan->is_coach() && !$studyplan->suspended())
|| has_capability('local/treestudyplan:editstudyplan', $studyplan->context()) || has_capability('local/treestudyplan:editstudyplan', $studyplan->context())
|| ($coursecontext && is_enrolled($coursecontext, $USER, 'local/treestudyplan:selectowngradables'))) { || ($coursecontext && is_enrolled($coursecontext, $USER, 'local/treestudyplan:selectowngradables'))
return coursecompetencyinfo::require_competency($competencyid, $itemid, $required)->model(); ) {
return coursecompetencyinfo::require_competency($competencyid, $itemid, $required)->model();
} else { } else {
return success::fail("Access denied")->model(); return success::fail("Access denied")->model();
} }
@ -1518,7 +1515,7 @@ class studyplanservice extends \external_api {
// Validate import context. // Validate import context.
webservicehelper::require_capabilities(self::CAP_EDIT, $plan->context()); webservicehelper::require_capabilities(self::CAP_EDIT, $plan->context());
$result = $plan->import_pages($content, $format); $result = $plan->import_pages($content, $format);
return ($result ? success : :success(): success::fail())->model(); return ($result ? success::success() : success::fail())->model();
} catch (\webservice_access_exception $x) { } catch (\webservice_access_exception $x) {
return success::fail("Access denied")->model(); return success::fail("Access denied")->model();
} }
@ -1556,7 +1553,7 @@ class studyplanservice extends \external_api {
webservicehelper::require_capabilities(self::CAP_EDIT, $page->studyplan()->context()); webservicehelper::require_capabilities(self::CAP_EDIT, $page->studyplan()->context());
$result = $page->import_studylines($content, $format); $result = $page->import_studylines($content, $format);
return ($result ? success : :success(): success::fail())->model(); return ($result ? success::success() : success::fail())->model();
} catch (\webservice_access_exception $x) { } catch (\webservice_access_exception $x) {
return success::fail("Access denied")->model(); return success::fail("Access denied")->model();
} }
@ -1622,7 +1619,7 @@ class studyplanservice extends \external_api {
foreach (explode("&", $formdata) as $pair) { foreach (explode("&", $formdata) as $pair) {
$p = explode("=", $pair, 2); $p = explode("=", $pair, 2);
$k = urldecode($p[0]); $k = urldecode($p[0]);
$v = (count($p) > 1) ? urldecode($p[1]): ""; $v = (count($p) > 1) ? urldecode($p[1]) : "";
if (strpos($k, "[") > 0 && strpos($k, "]") == strlen($k) - 1) { if (strpos($k, "[") > 0 && strpos($k, "]") == strlen($k) - 1) {
// Its a bracketet field, like filename[text] which should be separated and put into a named array. // Its a bracketet field, like filename[text] which should be separated and put into a named array.
@ -1892,7 +1889,7 @@ class studyplanservice extends \external_api {
$studyitems = studyitem::find_studyline_children($line); $studyitems = studyitem::find_studyline_children($line);
foreach ($studyitems as $item) { foreach ($studyitems as $item) {
if ($item->type() == studyitem::COURSE) { if ($item->type() == studyitem::COURSE) {
if ( $item->slot() <= $page->periods() ) { if ($item->slot() <= $page->periods()) {
$period = $periods[$item->slot()]; $period = $periods[$item->slot()];
try { try {
self::course_period_timing($period->id(), $item->courseid(), $item->span()); self::course_period_timing($period->id(), $item->courseid(), $item->span());
@ -1991,11 +1988,11 @@ class studyplanservice extends \external_api {
$contextlevels = [CONTEXT_SYSTEM, CONTEXT_COURSECAT]; $contextlevels = [CONTEXT_SYSTEM, CONTEXT_COURSECAT];
$list = []; $list = [];
$roles = \get_all_roles(); $roles = \get_all_roles();
foreach($roles as $role) { foreach ($roles as $role) {
$name = \role_get_name($role, $p->context()); // Get localized role name. $name = \role_get_name($role, $p->context()); // Get localized role name.
$rctxtlevels = \get_role_contextlevels($role->id); $rctxtlevels = \get_role_contextlevels($role->id);
$intersect = array_intersect($rctxtlevels, $contextlevels); $intersect = array_intersect($rctxtlevels, $contextlevels);
if ( count($intersect) > 0 ) { if (count($intersect) > 0) {
$list[] = [ $list[] = [
'id' => $role->id, 'id' => $role->id,
'name' => $name, 'name' => $name,
@ -2081,7 +2078,7 @@ class studyplanservice extends \external_api {
} }
} }
// Trigger immediate cohort synchronization for this line only // Trigger immediate cohort synchronization for this line only.
autocohortsync::syncline($o); autocohortsync::syncline($o);
return $list; return $list;
} }
@ -2132,7 +2129,7 @@ class studyplanservice extends \external_api {
$list[] = self::student_enrol_status_model($userid, $o); $list[] = self::student_enrol_status_model($userid, $o);
} }
// Trigger immediate cohort synchronization for this line only // Trigger immediate cohort synchronization for this line only.
autocohortsync::syncline($o); autocohortsync::syncline($o);
return $list; return $list;
} }
@ -2174,7 +2171,7 @@ class studyplanservice extends \external_api {
$list = []; $list = [];
$p = $o->studyplan(); $p = $o->studyplan();
foreach( $p->find_linked_userids() as $userid) { foreach ($p->find_linked_userids() as $userid) {
$list[] = self::student_enrol_status_model($userid, $o); $list[] = self::student_enrol_status_model($userid, $o);
} }
@ -2195,7 +2192,6 @@ class studyplanservice extends \external_api {
*/ */
public static function count_templates_parameters(): \external_function_parameters { public static function count_templates_parameters(): \external_function_parameters {
return new \external_function_parameters( [ return new \external_function_parameters( [
] ); ] );
} }

View File

@ -63,7 +63,7 @@ class success {
* @param array|object $data Custom data to pass to receiver * @param array|object $data Custom data to pass to receiver
*/ */
public function __construct($success, $msg, $data=[]) { public function __construct($success, $msg, $data=[]) {
$this->success = ($success) ? true : false; $this->success = ($success) ? true : false;
$this->msg = $msg; $this->msg = $msg;
$this->data = json_encode($data); $this->data = json_encode($data);
} }

View File

@ -98,7 +98,7 @@ class autocohortsync extends \core\task\scheduled_task {
$userenroller = new cascadeusersync($plan); $userenroller = new cascadeusersync($plan);
$userenroller->sync($line); $userenroller->sync($line);
} }
// Leave the csync required flag for the next auto update // Leave the csync required flag for the next auto update.
} }
} }

View File

@ -64,7 +64,7 @@ class teachingfinder {
public static function is_teaching_studyplan(studyplan $plan, $userid) { public static function is_teaching_studyplan(studyplan $plan, $userid) {
global $DB; global $DB;
$count = $DB->count_records(self::TABLE, ['teacher_id' => $userid, "studyplan_id" => $plan->id()]); $count = $DB->count_records(self::TABLE, ['teacher_id' => $userid, "studyplan_id" => $plan->id()]);
return ($count > 0)?true:false; return ($count > 0) ? true : false;
} }
/** /**
@ -76,7 +76,7 @@ class teachingfinder {
public static function is_teaching($userid) { public static function is_teaching($userid) {
global $DB; global $DB;
$count = $DB->count_records(self::TABLE, ['teacher_id' => $userid]); $count = $DB->count_records(self::TABLE, ['teacher_id' => $userid]);
return ($count > 0)?true:false; return ($count > 0) ? true : false;
} }
/** /**
@ -167,7 +167,6 @@ class teachingfinder {
$now = time(); $now = time();
foreach ($list as $pageid) { foreach ($list as $pageid) {
// Retrieve the studyplan id from the page. // Retrieve the studyplan id from the page.
// TODO: Change this when page management is implemented to return the page instead of the plan.
$planid = $DB->get_field("local_treestudyplan_page", "studyplan_id", ["id" => $pageid]); $planid = $DB->get_field("local_treestudyplan_page", "studyplan_id", ["id" => $pageid]);
$DB->insert_record(self::TABLE, ["teacher_id" => $userid, "studyplan_id" => $planid, "update_time" => $now]); $DB->insert_record(self::TABLE, ["teacher_id" => $userid, "studyplan_id" => $planid, "update_time" => $now]);
} }

View File

@ -45,6 +45,12 @@ class utilityservice extends \external_api {
*/ */
const CAP_VIEW = "local/treestudyplan:viewuserreports"; const CAP_VIEW = "local/treestudyplan:viewuserreports";
/**
* Load moodle form
* @param string $formname
* @param string $params
* @param string $ajaxformdata
*/
protected static function load_mform($formname, $params, $ajaxformdata = null) { protected static function load_mform($formname, $params, $ajaxformdata = null) {
global $CFG; global $CFG;
/* We don't need to load the form php file (class autoloading will handle that) /* We don't need to load the form php file (class autoloading will handle that)
@ -58,7 +64,7 @@ class utilityservice extends \external_api {
$mformclassname = "\\local_treestudyplan\\form\\{$formname}"; $mformclassname = "\\local_treestudyplan\\form\\{$formname}";
// Check if the form is a subclass of formbase. // Check if the form is a subclass of formbase.
if ( is_a($mformclassname, "\\local_treestudyplan\\form\\formbase", true)) { if (is_a($mformclassname, "\\local_treestudyplan\\form\\formbase", true)) {
$jsonparams = json_decode($params); $jsonparams = json_decode($params);
$mform = new $mformclassname( $jsonparams, $ajaxformdata); $mform = new $mformclassname( $jsonparams, $ajaxformdata);
return $mform; return $mform;
@ -121,7 +127,7 @@ class utilityservice extends \external_api {
return new \external_single_structure( return new \external_single_structure(
[ [
'html' => new \external_value(PARAM_RAW, 'HTML fragment.'), 'html' => new \external_value(PARAM_RAW, 'HTML fragment.'),
'javascript' => new \external_value(PARAM_RAW, 'JavaScript fragment') 'javascript' => new \external_value(PARAM_RAW, 'JavaScript fragment'),
] ]
); );
} }
@ -157,7 +163,7 @@ class utilityservice extends \external_api {
$ajaxformdata = []; $ajaxformdata = [];
parse_str($formdata, $ajaxformdata); parse_str($formdata, $ajaxformdata);
// Load the form, provide submitted form data and perform security checks // Load the form, provide submitted form data and perform security checks.
$mform = self::load_mform($formname, $params, $ajaxformdata); $mform = self::load_mform($formname, $params, $ajaxformdata);
$return = $mform->process_submission(); $return = $mform->process_submission();
@ -165,7 +171,7 @@ class utilityservice extends \external_api {
return success::success($return)->model(); return success::success($return)->model();
} }
/** /**
* Parameter description for webservice function submit_cm_editform * Parameter description for webservice function submit_cm_editform
*/ */
public static function getsettings_parameters(): \external_function_parameters { public static function getsettings_parameters(): \external_function_parameters {
@ -193,7 +199,7 @@ class utilityservice extends \external_api {
return [ return [
"hivizdropslots" => get_config("local_treestudyplan", "hivizdropslots"), "hivizdropslots" => get_config("local_treestudyplan", "hivizdropslots"),
"toolboxleft" => get_config("local_treestudyplan", "toolboxleft") "toolboxleft" => get_config("local_treestudyplan", "toolboxleft"),
]; ];
} }

View File

@ -41,7 +41,7 @@ Options:
list($options, $unrecognised) = cli_get_params([ list($options, $unrecognised) = cli_get_params([
'help' => false, 'help' => false,
'amount' => null 'amount' => null,
], [ ], [
'h' => 'help', 'h' => 'help',
'a' => 'amount', 'a' => 'amount',
@ -56,7 +56,7 @@ if ($options['help']) {
cli_writeln($usage); cli_writeln($usage);
exit(2); exit(2);
} }
print_r($options);
if (empty($options['amount']) || !is_numeric($options['amount'])) { if (empty($options['amount']) || !is_numeric($options['amount'])) {
cli_error('Missing mandatory integer argument "amount"', 2); cli_error('Missing mandatory integer argument "amount"', 2);
} }
@ -76,7 +76,7 @@ if (!$user) {
cli_error("Unable to find admin user in DB."); cli_error("Unable to find admin user in DB.");
} }
$auth = empty($user->auth) ? 'manual' : $user->auth; $auth = empty($user->auth) ? 'manual' : $user->auth;
if ($auth == 'nologin' || !is_enabled_auth($auth)) { if ($auth == 'nologin' || !is_enabled_auth($auth)) {
cli_error(sprintf("User authentication is either 'nologin' or disabled. Check Moodle authentication method for '%s'", cli_error(sprintf("User authentication is either 'nologin' or disabled. Check Moodle authentication method for '%s'",
$user->username)); $user->username));
@ -87,8 +87,8 @@ $authplugin->sync_roles($user);
login_attempt_valid($user); login_attempt_valid($user);
complete_user_login($user); complete_user_login($user);
for ($i=0; $i<$amount; $i++) { for ($i = 0; $i < $amount; $i++) {
$fortune = shell_exec("{$fortunepath} -n 160 "); $fortune = shell_exec("{$fortunepath} -n 160 ");
$pieces = explode(" ", $fortune); $pieces = explode(" ", $fortune);
$name = implode(" ", array_splice($pieces, 0, 4)); $name = implode(" ", array_splice($pieces, 0, 4));
@ -118,7 +118,7 @@ for ($i=0; $i<$amount; $i++) {
$fordb->issuercontact = $CFG->badges_defaultissuercontact; $fordb->issuercontact = $CFG->badges_defaultissuercontact;
$fordb->expiredate = null; $fordb->expiredate = null;
$fordb->expireperiod = null; $fordb->expireperiod = null;
$fordb->type = \BADGE_TYPE_SITE; // Site badge. $fordb->type = \BADGE_TYPE_SITE; // Site badge.
$fordb->courseid = null; $fordb->courseid = null;
$fordb->messagesubject = get_string('messagesubject', 'badges'); $fordb->messagesubject = get_string('messagesubject', 'badges');

View File

@ -43,7 +43,7 @@ list($options, $unrecognised) = cli_get_params([
'help' => false, 'help' => false,
'studyplan' => null, 'studyplan' => null,
'all' => false, 'all' => false,
'file' => "/tmp/generategrades.json" 'file' => "/tmp/generategrades.json",
], [ ], [
'h' => 'help', 'h' => 'help',
's' => 'studyplan', 's' => 'studyplan',

View File

@ -47,13 +47,13 @@ list($options, $unrecognised) = cli_get_params([
'dryrun' => false, 'dryrun' => false,
'studyplan' => null, 'studyplan' => null,
'all' => false, 'all' => false,
'file' => "/tmp/generategrades.json" 'file' => "/tmp/generategrades.json",
], [ ], [
'h' => 'help', 'h' => 'help',
's' => 'studyplan', 's' => 'studyplan',
'a' => 'all', 'a' => 'all',
'f' => 'file', 'f' => 'file',
'd' => 'dryrun' 'd' => 'dryrun',
]); ]);
if ($unrecognised) { if ($unrecognised) {
@ -73,7 +73,7 @@ if (!$user) {
cli_error("Unable to find admin user in DB."); cli_error("Unable to find admin user in DB.");
} }
$auth = empty($user->auth) ? 'manual' : $user->auth; $auth = empty($user->auth) ? 'manual' : $user->auth;
if ($auth == 'nologin' || !is_enabled_auth($auth)) { if ($auth == 'nologin' || !is_enabled_auth($auth)) {
cli_error(sprintf("User authentication is either 'nologin' or disabled. Check Moodle authentication method for '%s'", cli_error(sprintf("User authentication is either 'nologin' or disabled. Check Moodle authentication method for '%s'",
$user->username)); $user->username));
@ -150,7 +150,10 @@ foreach ($plans as $plan) {
$ug = $a->get_user_grade($u->id, true); $ug = $a->get_user_grade($u->id, true);
$ug->grade = grade_floatval($gg->grade); $ug->grade = grade_floatval($gg->grade);
$ug->grader = $USER->id; $ug->grader = $USER->id;
$ug->feedbacktext = nl2br( htmlspecialchars($gg->fb)); $ug->feedbacktext = nl2br(htmlspecialchars(
$gg->fb,
ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401
));
$ug->feedbackformat = FORMAT_HTML; $ug->feedbackformat = FORMAT_HTML;
if (!$options["dryrun"]) { if (!$options["dryrun"]) {

View File

@ -14,7 +14,7 @@
// 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 <https://www.gnu.org/licenses/>. // along with Moodle. If not, see <https://www.gnu.org/licenses/>.
/** /**
* View study plans - teacher view and student view * Entry point for Coach
* @package local_treestudyplan * @package local_treestudyplan
* @copyright 2023 P.M. Kuipers * @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@ -37,7 +37,7 @@ $PAGE->set_title(get_string('coaching_plans', 'local_treestudyplan'));
$PAGE->set_heading(get_string('coaching_plans', 'local_treestudyplan')); $PAGE->set_heading(get_string('coaching_plans', 'local_treestudyplan'));
premium::require_premium(); premium::require_premium();
if ( ! (\get_config("local_treestudyplan", "enablecoach")) ) { if (! (\get_config("local_treestudyplan", "enablecoach"))) {
throw new \moodle_exception("error:coachdisabled", "local_treestudyplan"); throw new \moodle_exception("error:coachdisabled", "local_treestudyplan");
} }
@ -50,7 +50,6 @@ $PAGE->requires->js_call_amd('local_treestudyplan/page-coach', 'init', []);
/** /**
* Shortcut function to provide translations * Shortcut function to provide translations
*
* @param mixed $str Translation key * @param mixed $str Translation key
* @param null|string[] $param Parameters to pass to translation * @param null|string[] $param Parameters to pass to translation
* @param string $plugin Location to search for translation strings * @param string $plugin Location to search for translation strings
@ -61,7 +60,14 @@ function t($str, $param = null, $plugin = 'local_treestudyplan') {
} }
print $OUTPUT->header(); print $OUTPUT->header();
?> $text = (object)[
'studyplan_select' => t("studyplan_select"),
'coacheditmode' => t("coacheditmode"),
'showoverview' => t("showoverview"),
'studyplan_noneselected' => t("studyplan_noneselected"),
];
print <<<END
<div id='root' class="t-studyplan-limit-width"> <div id='root' class="t-studyplan-limit-width">
<div class='vue-loader' v-show='false'> <div class='vue-loader' v-show='false'>
<div class="spinner-border text-primary" role="status"> <div class="spinner-border text-primary" role="status">
@ -73,7 +79,7 @@ print $OUTPUT->header();
<template v-if="displayedstudyplan"> <template v-if="displayedstudyplan">
<a href='#' @click.prevent='closeStudyplan' <a href='#' @click.prevent='closeStudyplan'
><i style='font-size: 150%;' class='fa fa-chevron-left'></i> <?php t('back');?></a> ><i style='font-size: 150%;' class='fa fa-chevron-left'></i> <?php t('back');?></a>
<span><?php t("studyplan_select"); ?></span>&nbsp; <span>{$text->studyplan_select}</span>&nbsp;
<s-studyplan-details <s-studyplan-details
v-model="displayedstudyplan" v-model="displayedstudyplan"
v-if="displayedstudyplan.description" v-if="displayedstudyplan.description"
@ -93,14 +99,14 @@ print $OUTPUT->header();
variant="primary" variant="primary"
> >
<template v-slot="{value}">{{value.firstname}} {{value.lastname}}</template> <template v-slot="{value}">{{value.firstname}} {{value.lastname}}</template>
<template #defaultlabel><span class='text-primary'><?php t("coacheditmode"); ?></span></template> <template #defaultlabel><span class='text-primary'>{$text->coacheditmode}</span></template>
</s-prevnext-selector> </s-prevnext-selector>
</template> </template>
</div> </div>
<div class='t-studyplan-container'> <div class='t-studyplan-container'>
<h2 v-if='displayedstudyplan&& selectedstudent' <h2 v-if='displayedstudyplan&& selectedstudent'
>{{selectedstudent.firstname}} {{selectedstudent.lastname}} - {{displayedstudyplan.name}}</h2> >{{selectedstudent.firstname}} {{selectedstudent.lastname}} - {{displayedstudyplan.name}}</h2>
<h2 v-else-if='displayedstudyplan'><?php t("showoverview"); ?> - {{displayedstudyplan.name}}</h2> <h2 v-else-if='displayedstudyplan'>{$text->showoverview} - {{displayedstudyplan.name}}</h2>
<template v-if="!loadingstudyplan && displayedstudyplan"> <template v-if="!loadingstudyplan && displayedstudyplan">
<r-studyplan v-if="selectedstudent" <r-studyplan v-if="selectedstudent"
v-model='displayedstudyplan' v-model='displayedstudyplan'
@ -116,7 +122,7 @@ print $OUTPUT->header();
<span class="sr-only">Loading...</span> <span class="sr-only">Loading...</span>
</div> </div>
<div v-else class='t-studyplan-notselected'> <div v-else class='t-studyplan-notselected'>
<p><?php t("studyplan_noneselected"); ?></p> <p>{$text->studyplan_noneselected}</p>
<b-card-group deck> <b-card-group deck>
<s-studyplan-card <s-studyplan-card
v-for='(studyplan, planindex) in studyplans' v-for='(studyplan, planindex) in studyplans'
@ -130,6 +136,6 @@ print $OUTPUT->header();
</div> </div>
</div> </div>
</div> </div>
<?php END;
print $OUTPUT->footer(); print $OUTPUT->footer();

View File

@ -27,63 +27,62 @@ $capabilities = [
'riskbitmask' => RISK_DATALOSS , 'riskbitmask' => RISK_DATALOSS ,
'captype' => 'write', 'captype' => 'write',
'contextlevel' => CONTEXT_SYSTEM, 'contextlevel' => CONTEXT_SYSTEM,
'archetypes' => array( 'archetypes' => [
'manager' => CAP_ALLOW 'manager' => CAP_ALLOW,
), ],
], ],
'local/treestudyplan:forcescales' => [ 'local/treestudyplan:forcescales' => [
'riskbitmask' => RISK_DATALOSS , 'riskbitmask' => RISK_DATALOSS ,
'captype' => 'write', 'captype' => 'write',
'contextlevel' => CONTEXT_SYSTEM, 'contextlevel' => CONTEXT_SYSTEM,
'archetypes' => array( 'archetypes' => [
'manager' => CAP_ALLOW 'manager' => CAP_ALLOW,
), ],
], ],
'local/treestudyplan:selectowngradables' => [ 'local/treestudyplan:selectowngradables' => [
'riskbitmask' => RISK_DATALOSS , 'riskbitmask' => RISK_DATALOSS ,
'captype' => 'write', 'captype' => 'write',
'contextlevel' => CONTEXT_SYSTEM, 'contextlevel' => CONTEXT_SYSTEM,
'archetypes' => array( 'archetypes' => [
'teacher' => CAP_ALLOW 'teacher' => CAP_ALLOW,
), ],
], ],
'local/treestudyplan:configure' => [ 'local/treestudyplan:configure' => [
'riskbitmask' => RISK_DATALOSS , 'riskbitmask' => RISK_DATALOSS ,
'captype' => 'write', 'captype' => 'write',
'contextlevel' => CONTEXT_SYSTEM, 'contextlevel' => CONTEXT_SYSTEM,
'archetypes' => array( 'archetypes' => [
'manager' => CAP_ALLOW 'manager' => CAP_ALLOW,
), ],
], ],
'local/treestudyplan:viewuserreports' => [ 'local/treestudyplan:viewuserreports' => [
'riskbitmask' => RISK_PERSONAL , 'riskbitmask' => RISK_PERSONAL ,
'captype' => 'write', 'captype' => 'write',
'contextlevel' => CONTEXT_SYSTEM, 'contextlevel' => CONTEXT_SYSTEM,
'archetypes' => array( 'archetypes' => [
'manager' => CAP_ALLOW 'manager' => CAP_ALLOW,
), ],
], ],
'local/treestudyplan:lineunenrol' => [ 'local/treestudyplan:lineunenrol' => [
'riskbitmask' => RISK_PERSONAL , 'riskbitmask' => RISK_PERSONAL ,
'captype' => 'write', 'captype' => 'write',
'contextlevel' => CONTEXT_SYSTEM, 'contextlevel' => CONTEXT_SYSTEM,
'archetypes' => array( 'archetypes' => [
'manager' => CAP_ALLOW 'manager' => CAP_ALLOW,
), ],
], ],
'local/treestudyplan:coach' => [ 'local/treestudyplan:coach' => [
'riskbitmask' => RISK_PERSONAL , 'riskbitmask' => RISK_PERSONAL ,
'captype' => 'write', 'captype' => 'write',
'contextlevel' => CONTEXT_SYSTEM, 'contextlevel' => CONTEXT_SYSTEM,
'archetypes' => array( 'archetypes' => [
'manager' => CAP_ALLOW 'manager' => CAP_ALLOW,
), ],
], ],
]; ];

View File

@ -23,7 +23,6 @@
defined('MOODLE_INTERNAL') || die(); defined('MOODLE_INTERNAL') || die();
$services = [ $services = [
]; ];
$functions = [ $functions = [
@ -832,5 +831,4 @@ $functions = [
'capabilities' => '', 'capabilities' => '',
'loginrequired' => true, 'loginrequired' => true,
], ],
]; ];

View File

@ -49,5 +49,4 @@ $tasks = [
'month' => '*', 'month' => '*',
'dayofweek' => '*', 'dayofweek' => '*',
], ],
]; ];

View File

@ -394,7 +394,7 @@ function xmldb_local_treestudyplan_upgrade($oldversion) {
"fullname" => "Period {$pnum}", "fullname" => "Period {$pnum}",
"shortname" => "P{$pnum}", "shortname" => "P{$pnum}",
"startdate" => date("Y-m-d", $pstart), "startdate" => date("Y-m-d", $pstart),
"enddate" => date("Y-m-d", $pend) "enddate" => date("Y-m-d", $pend),
]; ];
$DB->insert_record("local_treestudyplan_period", $o); $DB->insert_record("local_treestudyplan_period", $o);
} }
@ -532,7 +532,6 @@ function xmldb_local_treestudyplan_upgrade($oldversion) {
$dbman->add_field($table, $field); $dbman->add_field($table, $field);
} }
// Treestudyplan savepoint reached. // Treestudyplan savepoint reached.
upgrade_plugin_savepoint(true, 2024022502, 'local', 'treestudyplan'); upgrade_plugin_savepoint(true, 2024022502, 'local', 'treestudyplan');
} }
@ -552,27 +551,27 @@ function xmldb_local_treestudyplan_upgrade($oldversion) {
} }
if ($oldversion < 2024022504) { if ($oldversion < 2024022504) {
// Define table local_treestudyplan_lineuser to be created. // Define table local_treestudyplan_lineuser to be created.
$table = new xmldb_table('local_treestudyplan_lineuser'); $table = new xmldb_table('local_treestudyplan_lineuser');
// Adding fields to table local_treestudyplan_lineuser. // Adding fields to table local_treestudyplan_lineuser.
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
$table->add_field('line_id', XMLDB_TYPE_INTEGER, '18', null, XMLDB_NOTNULL, null, null); $table->add_field('line_id', XMLDB_TYPE_INTEGER, '18', null, XMLDB_NOTNULL, null, null);
$table->add_field('user_id', XMLDB_TYPE_INTEGER, '18', null, XMLDB_NOTNULL, null, null); $table->add_field('user_id', XMLDB_TYPE_INTEGER, '18', null, XMLDB_NOTNULL, null, null);
$table->add_field('timeenrolled', XMLDB_TYPE_INTEGER, '12', null, null, null, null); $table->add_field('timeenrolled', XMLDB_TYPE_INTEGER, '12', null, null, null, null);
$table->add_field('enrolled', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '0'); $table->add_field('enrolled', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '0');
$table->add_field('enrolledby', XMLDB_TYPE_INTEGER, '18', null, null, null, null); $table->add_field('enrolledby', XMLDB_TYPE_INTEGER, '18', null, null, null, null);
// Adding keys to table local_treestudyplan_lineuser. // Adding keys to table local_treestudyplan_lineuser.
$table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']); $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
$table->add_key('line_id-id', XMLDB_KEY_FOREIGN, ['line_id'], 'local_treestudyplan_line', ['id']); $table->add_key('line_id-id', XMLDB_KEY_FOREIGN, ['line_id'], 'local_treestudyplan_line', ['id']);
$table->add_key('user_id-id', XMLDB_KEY_FOREIGN, ['user_id'], 'user', ['id']); $table->add_key('user_id-id', XMLDB_KEY_FOREIGN, ['user_id'], 'user', ['id']);
$table->add_key('enrolledby-id', XMLDB_KEY_FOREIGN, ['enrolledby'], 'user', ['id']); $table->add_key('enrolledby-id', XMLDB_KEY_FOREIGN, ['enrolledby'], 'user', ['id']);
// Conditionally launch create table for local_treestudyplan_lineuser. // Conditionally launch create table for local_treestudyplan_lineuser.
if (!$dbman->table_exists($table)) { if (!$dbman->table_exists($table)) {
$dbman->create_table($table); $dbman->create_table($table);
} }
// Treestudyplan savepoint reached. // Treestudyplan savepoint reached.
upgrade_plugin_savepoint(true, 2024022504, 'local', 'treestudyplan'); upgrade_plugin_savepoint(true, 2024022504, 'local', 'treestudyplan');

View File

@ -45,7 +45,7 @@ if (!file_exists($file)) {
$mime = mime_content_type($file); $mime = mime_content_type($file);
$texttypes = ["text/html", "text/plain"]; $texttypes = ["text/html", "text/plain"];
if ( in_array($mime, $texttypes)) { if (in_array($mime, $texttypes)) {
print $OUTPUT->header(); print $OUTPUT->header();
print file_get_contents($file); print file_get_contents($file);
print $OUTPUT->footer(); print $OUTPUT->footer();

View File

@ -51,9 +51,9 @@ require_login();
print $OUTPUT->header(); print $OUTPUT->header();
if (!empty($add)) { if (!empty($add)) {
$data = array( $data = [
'add' => 1, 'add' => 1,
); ];
} else if (!empty($update)) { } else if (!empty($update)) {
$data = $DB->get_record("local_treestudyplan_invit", ['id' => $update]); $data = $DB->get_record("local_treestudyplan_invit", ['id' => $update]);

View File

@ -64,7 +64,6 @@ $ci = new contextinfo($studyplancontext);
$contextname = $ci->pathstr(); $contextname = $ci->pathstr();
$PAGE->set_pagelayout('base'); $PAGE->set_pagelayout('base');
//$PAGE->set_context($studyplancontext);
$PAGE->set_title(get_string('cfg_plans', 'local_treestudyplan')." - ".$contextname); $PAGE->set_title(get_string('cfg_plans', 'local_treestudyplan')." - ".$contextname);
$PAGE->set_heading($contextname); $PAGE->set_heading($contextname);
@ -72,20 +71,30 @@ if ($studyplancontext->id > 1) {
navigation_node::override_active_url(new moodle_url('/course/index.php', ['categoryid' => $categoryid ])); navigation_node::override_active_url(new moodle_url('/course/index.php', ['categoryid' => $categoryid ]));
$PAGE->navbar->add(get_string('cfg_plans', 'local_treestudyplan')); $PAGE->navbar->add(get_string('cfg_plans', 'local_treestudyplan'));
// Coursecat context // Coursecat context.
$cat = \core_course_category::get($studyplancontext->instanceid, IGNORE_MISSING, true); // We checck visibility later $cat = \core_course_category::get($studyplancontext->instanceid, IGNORE_MISSING, true); // We checck visibility later.
} else { } else {
// System context // System context.
$cat = \core_course_category::top(); $cat = \core_course_category::top();
} }
if (!$cat->is_uservisible()) { if (!$cat->is_uservisible()) {
throw new \moodle_exception("error:cannotviewcategory", "local_treestudyplan", "/local/treestudyplan/edit_plan.php", $contextname); throw new \moodle_exception(
"error:cannotviewcategory",
"local_treestudyplan",
"/local/treestudyplan/edit_plan.php",
$contextname
);
} }
if (!has_capability('local/treestudyplan:editstudyplan', $studyplancontext)) { if (!has_capability('local/treestudyplan:editstudyplan', $studyplancontext)) {
throw new \moodle_exception("error:nostudyplaneditaccess", "local_treestudyplan", "/local/treestudyplan/edit_plan.php", $contextname); throw new \moodle_exception(
"error:nostudyplaneditaccess",
"local_treestudyplan",
"/local/treestudyplan/edit_plan.php",
$contextname
);
} }
// Load javascripts and specific css. // Load javascripts and specific css.
@ -110,8 +119,13 @@ function t($str, $param = null, $plugin = 'local_treestudyplan') {
} }
print $OUTPUT->header(); print $OUTPUT->header();
$text = (object)[
'loading' => t("loading", null, "core"),
'studyplan_select' => t("studyplan_select"),
'studyplan_noneselected' => t("studyplan_noneselected"),
];
?> print <<<END
<div id='root' class="t-studyplan-limit-width"> <div id='root' class="t-studyplan-limit-width">
<div class='vue-loader' v-show='false'> <div class='vue-loader' v-show='false'>
<div class="spinner-border text-primary" role="status"> <div class="spinner-border text-primary" role="status">
@ -120,26 +134,27 @@ print $OUTPUT->header();
</div> </div>
<div v-cloak> <div v-cloak>
<div v-if='!activestudyplan && usedcontexts && !loadingstudyplan' class='ml-3 mb-3 s-context-selector'> <div v-if='!activestudyplan && usedcontexts && !loadingstudyplan' class='ml-3 mb-3 s-context-selector'>
<b-form-select text='<?php print($contextname);?>' :value="contextid" @change='switchContext' <b-form-select text='{$contextname}' :value="contextid" @change='switchContext'
:class="(!(usedcontexts.length)) ? 'text-primary' : ''"> :class="(!(usedcontexts.length)) ? 'text-primary' : ''">
<b-form-select-option v-if='!(usedcontexts.length)' :value="contextid" <b-form-select-option v-if='!(usedcontexts.length)' :value="contextid"
:class="'text-primary'"> :class="'text-primary'">
<span><?php t("loading", null, "core"); ?>...</span></b-form-select-option> <span>{$text->loading}...</span></b-form-select-option>
<b-form-select-option v-for='ctx in usedcontexts' :key='ctx.id' :value="ctx.context_id" <b-form-select-option v-for='ctx in usedcontexts' :key='ctx.id' :value="ctx.context_id"
:class="(ctx.studyplancount > 0) ? 'font-weight-bold' : ''" :class="(ctx.studyplancount > 0) ? 'font-weight-bold' : ''"
><span v-for="(p, i) in ctx.category.path" ><span v-for="(p, i) in ctx.category.path"
><span v-if="i>0"> / </span>{{ p }}</span> <span>({{ ctx.studyplancount }})</span> ><span v-if="i>0"> / </span>{{ p }}</span> <span>({{ ctx.studyplancount }})</span>
</b-form-select-option> </b-form-select-option>
</b-form-select> </b-form-select>
<div v-if="!(usedcontexts.length)" style="position: relative; top: 0.3rem; width: 1.2rem; height: 1.2rem; font-size: 0.7rem;" <div v-if="!(usedcontexts.length)"
style="position: relative; top: 0.3rem; width: 1.2rem; height: 1.2rem; font-size: 0.7rem;"
class="spinner-border text-primary" role="status"></div> class="spinner-border text-primary" role="status"></div>
</div> </div>
<h3 v-else><?php print $contextname; ?></h3> <h3 v-else>{$contextname}</h3>
<div class="m-buttonbar" style="margin-bottom: 1em;"> <div class="m-buttonbar" style="margin-bottom: 1em;">
<a href='#' v-if='activestudyplan' @click.prevent='closeStudyplan' <a href='#' v-if='activestudyplan' @click.prevent='closeStudyplan'
><i style='font-size: 150%;' class='fa fa-chevron-left'></i> <?php t('back');?></a> ><i style='font-size: 150%;' class='fa fa-chevron-left'></i> <?php t('back');?></a>
<span v-if='activestudyplan'><?php t("studyplan_select"); ?></span>&nbsp; <span v-if='activestudyplan'>{$text->studyplan_select}</span>&nbsp;
<b-form-select v-if='activestudyplan' lazy :text='dropdown_title' :value='activestudyplan.id' @change="selectStudyplan"> <b-form-select v-if='activestudyplan' lazy :text='dropdown_title' :value='activestudyplan.id' @change="selectStudyplan">
<b-form-select-option <b-form-select-option
v-for='(studyplan, planindex) in studyplans' v-for='(studyplan, planindex) in studyplans'
@ -181,7 +196,7 @@ print $OUTPUT->header();
<span class="sr-only">Loading...</span> <span class="sr-only">Loading...</span>
</div> </div>
<div v-else class='t-studyplan-notselected'> <div v-else class='t-studyplan-notselected'>
<p><?php t("studyplan_noneselected"); ?></p> <p>{$text->studyplan_noneselected}</p>
<b-card-group deck> <b-card-group deck>
<s-studyplan-card <s-studyplan-card
v-for='(studyplan, planindex) in studyplans' v-for='(studyplan, planindex) in studyplans'
@ -193,7 +208,11 @@ print $OUTPUT->header();
> >
<template #title> <template #title>
<span class='s-studyplan-card-title-buttons'> <span class='s-studyplan-card-title-buttons'>
<t-studyplan-edit v-model="studyplans[planindex]" @moved="movedStudyplan" @input="refreshTemplateCount"></t-studyplan-edit> <t-studyplan-edit
v-model="studyplans[planindex]"
@moved="movedStudyplan"
@input="refreshTemplateCount"
></t-studyplan-edit>
<t-studyplan-associate v-model="studyplans[planindex]"></t-studyplan-associate> <t-studyplan-associate v-model="studyplans[planindex]"></t-studyplan-associate>
</span> </span>
</template> </template>
@ -203,6 +222,5 @@ print $OUTPUT->header();
</div> </div>
</div> </div>
</div> </div>
<?php END;
print $OUTPUT->footer(); print $OUTPUT->footer();

View File

@ -122,4 +122,4 @@ if (!get_config("local_treestudyplan", "enableplansharing")) {
print "<i class='fa fa-plus'></i> ".get_string('invite_button_new', 'local_treestudyplan')."</a>"; print "<i class='fa fa-plus'></i> ".get_string('invite_button_new', 'local_treestudyplan')."</a>";
print $OUTPUT->footer(); print $OUTPUT->footer();
} }

View File

@ -90,19 +90,18 @@ if (!get_config("local_treestudyplan", "enableplansharing")) {
$PAGE->set_heading(get_string('report_invited', 'local_treestudyplan', "{$student->firstname} {$student->lastname}")); $PAGE->set_heading(get_string('report_invited', 'local_treestudyplan', "{$student->firstname} {$student->lastname}"));
print $OUTPUT->header(); print $OUTPUT->header();
?> print "
<div id='root'> <div id='root'>
<div class='vue-loader' v-show='false'> <div class='vue-loader' v-show='false'>
<div class="spinner-border text-primary" role="status"> <div class='spinner-border text-primary' role='status'>
<span class="sr-only">Loading...</span> <span class='sr-only'>Loading...</span>
</div> </div>
</div> </div>
<div v-cloak> <div v-cloak>
<r-report type="invited" :invitekey="invitekey"></r-report> <r-report type='invited' :invitekey='invitekey'></r-report>
</div> </div>
</div> </div>
";
<?php
print $OUTPUT->footer(); print $OUTPUT->footer();
} }

View File

@ -137,7 +137,7 @@ $string["infofield_position_below"] = 'Below course results';
$string["infofield_position_header"] = 'Header '; $string["infofield_position_header"] = 'Header ';
$string["infofield_position_footer"] = 'Footer'; $string["infofield_position_footer"] = 'Footer';
for ($i=1;$i<=5;$i++) { for ($i = 1; $i <= 5; $i++) {
$string["setting_infofield".$i."_field"] = 'Add course details from field ('.$i.')'; $string["setting_infofield".$i."_field"] = 'Add course details from field ('.$i.')';
$string["settingdesc_infofield".$i."_field"] = 'Information field to show'; $string["settingdesc_infofield".$i."_field"] = 'Information field to show';
$string["setting_infofield".$i."_title"] = 'Title for course details from field ('.$i.').'; $string["setting_infofield".$i."_title"] = 'Title for course details from field ('.$i.').';
@ -523,4 +523,4 @@ $string["line_is_enrolled"] = 'You are registered for this line';
$string["line_enrolled_in"] = 'Registered in {$a}'; $string["line_enrolled_in"] = 'Registered in {$a}';
$string["switch_coach_editmode"] = "Edit studyplan"; $string["switch_coach_editmode"] = "Edit studyplan";
$string["suspended"] = "Suspended"; $string["suspended"] = "Suspended";
$string["no_templates"] = "There are no templates available. Mark a study plan as template to enable template based creation."; $string["no_templates"] = "There are no templates available. Mark a study plan as template to enable template based creation.";

View File

@ -137,7 +137,7 @@ $string["infofield_position_below"] = 'Onder cursusresultaten';
$string["infofield_position_header"] = 'Header '; $string["infofield_position_header"] = 'Header ';
$string["infofield_position_footer"] = 'Footer'; $string["infofield_position_footer"] = 'Footer';
for ($i=1;$i<=5;$i++) { for ($i = 1; $i <= 5; $i++) {
$string["setting_infofield".$i."_field"] = 'Laat gegevens zien uit cursusveld ('.$i.')'; $string["setting_infofield".$i."_field"] = 'Laat gegevens zien uit cursusveld ('.$i.')';
$string["settingdesc_infofield".$i."_field"] = 'Kies cursusveld om informatie uit te tonen'; $string["settingdesc_infofield".$i."_field"] = 'Kies cursusveld om informatie uit te tonen';
$string["setting_infofield".$i."_title"] = 'Titel voor gegevens uit cursusveld ('.$i.')'; $string["setting_infofield".$i."_title"] = 'Titel voor gegevens uit cursusveld ('.$i.')';
@ -524,4 +524,4 @@ $string["line_is_enrolled"] = 'Je bent ingeschreven voor deze leerlijn';
$string["line_enrolled_in"] = 'Ingeschreven in {$a}'; $string["line_enrolled_in"] = 'Ingeschreven in {$a}';
$string["switch_coach_editmode"] = "Studieplan bewerken"; $string["switch_coach_editmode"] = "Studieplan bewerken";
$string["suspended"] = "Tijdelijk uitgeschakeld"; $string["suspended"] = "Tijdelijk uitgeschakeld";
$string["no_templates"] = "Er zijn geen templates beschikbaar. Zet het vinkje 'Bruikbaar als template' aan bij een studieplan om deze als template te kunnen gebruiken."; $string["no_templates"] = "Er zijn geen templates beschikbaar. Zet het vinkje 'Bruikbaar als template' aan bij een studieplan om deze als template te kunnen gebruiken.";

34
lib.php
View File

@ -56,7 +56,7 @@ function local_treestudyplan_autofill_customusermenuitems() {
"/local/treestudyplan/edit-plan.php" => ["included" => false, "strkey" => "link_editplan"], "/local/treestudyplan/edit-plan.php" => ["included" => false, "strkey" => "link_editplan"],
]; ];
if ((premium::enabled() && \get_config("local_treestudyplan", "enablecoach"))) { if ((premium::enabled() && \get_config("local_treestudyplan", "enablecoach"))) {
// Also include the coach role if enabled // Also include the coach role if enabled.
$navitems["/local/treestudyplan/coach.php"] = ["included" => false, "strkey" => "link_coach"]; $navitems["/local/treestudyplan/coach.php"] = ["included" => false, "strkey" => "link_coach"];
} }
@ -86,7 +86,7 @@ function local_treestudyplan_autofill_customusermenuitems() {
$link, // Link. $link, // Link.
'', // Tooltip. '', // Tooltip.
$lang, // Language code. $lang, // Language code.
" #Automatically added by studyplan plugin. See setting 'primary_nav_autofill' to disable this" " #Automatically added by studyplan plugin. See setting 'primary_nav_autofill' to disable this",
]); ]);
$custommenuitems = trim($custommenuitems)."\n".$line; $custommenuitems = trim($custommenuitems)."\n".$line;
} }
@ -164,7 +164,7 @@ function local_treestudyplan_extend_navigation(global_navigation $navigation) {
} else { } else {
$hideprimaryhrefs[] = "/local/treestudyplan/myreport.php"; $hideprimaryhrefs[] = "/local/treestudyplan/myreport.php";
} }
if ( has_capability('local/treestudyplan:viewuserreports', context_system::instance()) if (has_capability('local/treestudyplan:viewuserreports', context_system::instance())
|| webservicehelper::has_capability_in_any_category('local/treestudyplan:viewuserreports')) { || webservicehelper::has_capability_in_any_category('local/treestudyplan:viewuserreports')) {
$node = navigation_node::create( $node = navigation_node::create(
get_string("link_viewplan", "local_treestudyplan"), get_string("link_viewplan", "local_treestudyplan"),
@ -180,7 +180,7 @@ function local_treestudyplan_extend_navigation(global_navigation $navigation) {
} else { } else {
$hideprimaryhrefs[] = "/local/treestudyplan/view-plan.php"; $hideprimaryhrefs[] = "/local/treestudyplan/view-plan.php";
} }
if ( has_capability('local/treestudyplan:editstudyplan', context_system::instance()) if (has_capability('local/treestudyplan:editstudyplan', context_system::instance())
|| webservicehelper::has_capability_in_any_category('local/treestudyplan:editstudyplan') || webservicehelper::has_capability_in_any_category('local/treestudyplan:editstudyplan')
) { ) {
$node = navigation_node::create( $node = navigation_node::create(
@ -198,11 +198,11 @@ function local_treestudyplan_extend_navigation(global_navigation $navigation) {
$hideprimaryhrefs[] = "/local/treestudyplan/edit-plan.php"; $hideprimaryhrefs[] = "/local/treestudyplan/edit-plan.php";
} }
$coachsql = "SELECT COUNT('id') FROM {local_treestudyplan_coach} as c $coachsql = "SELECT COUNT('id') FROM {local_treestudyplan_coach} c
INNER JOIN {local_treestudyplan} AS t ON c.studyplan_id = t.id INNER JOIN {local_treestudyplan} t ON c.studyplan_id = t.id
WHERE c.user_id = :user_id"; WHERE c.user_id = :user_id";
if ( (premium::enabled() && \get_config("local_treestudyplan", "enablecoach")) && if ((premium::enabled() && \get_config("local_treestudyplan", "enablecoach")) &&
(has_capability('local/treestudyplan:coach', context_system::instance()) (has_capability('local/treestudyplan:coach', context_system::instance())
|| webservicehelper::has_capability_in_any_category('local/treestudyplan:coach') || webservicehelper::has_capability_in_any_category('local/treestudyplan:coach')
) && $DB->count_records_sql($coachsql, ["user_id" => $USER->id]) > 0 ) && $DB->count_records_sql($coachsql, ["user_id" => $USER->id]) > 0
@ -332,7 +332,7 @@ function local_treestudyplan_send_invite($inviteid) {
if ($invite->allow_details || $invite->allow_calendar || $invite->allow_badges) { if ($invite->allow_details || $invite->allow_calendar || $invite->allow_badges) {
$data['permissions'] = get_string('invite_mail_permissions', 'local_treestudyplan'); $data['permissions'] = get_string('invite_mail_permissions', 'local_treestudyplan');
$data['permissions'] .= "<ul>\n"; $data['permissions'] .= "<ul>\n";
if ($invite->allow_details ) { if ($invite->allow_details) {
$data['permissions'] .= "<li>".get_string('invite_allow_details', 'local_treestudyplan')."</li>\n"; $data['permissions'] .= "<li>".get_string('invite_allow_details', 'local_treestudyplan')."</li>\n";
} }
if ($invite->allow_calendar) { if ($invite->allow_calendar) {
@ -449,14 +449,14 @@ function local_treestudyplan_pluginfile(
$itemid = array_shift($args); $itemid = array_shift($args);
// Studyplan icons and description images are not secret, so don't overdo it on access control... // Studyplan icons and description images are not secret, so don't overdo it on access control...
if ( true ) { if (true) {
// Extract the filename / filepath from the $args array // Extract the filename / filepath from the $args array.
$filename = array_pop($args); // The last item in the $args array. $filename = array_pop($args); // The last item in the $args array.
if (empty($args)) { if (empty($args)) {
// $args is empty => the path is '/'. // Var $args is empty => the path is '/'.
$filepath = '/'; $filepath = '/';
} else { } else {
// $args contains the remaining elements of the filepath. // Var $args contains the remaining elements of the filepath.
$filepath = '/' . implode('/', $args) . '/'; $filepath = '/' . implode('/', $args) . '/';
} }
@ -468,7 +468,7 @@ function local_treestudyplan_pluginfile(
return false; return false;
} }
// We can now send the file back to the browser - in this case with a cache lifetime of 1 day and no filtering. // We can now send the file back to the browser - in this case with a cache lifetime of 1 day and no filtering.
send_stored_file($file, 24*60*60, 0, $forcedownload, $options); send_stored_file($file, 24 * 60 * 60, 0, $forcedownload, $options);
} else { } else {
return false; return false;
} }
@ -478,13 +478,13 @@ function local_treestudyplan_pluginfile(
// Fetch the itemid from the path. // Fetch the itemid from the path.
$itemid = array_shift($args); $itemid = array_shift($args);
// Extract the filename / filepath from the $args array // Extract the filename / filepath from the $args array.
$filename = array_pop($args); // The last item in the $args array. $filename = array_pop($args); // The last item in the $args array.
if (empty($args)) { if (empty($args)) {
// $args is empty => the path is '/'. // Var $args is empty => the path is '/'.
$filepath = '/'; $filepath = '/';
} else { } else {
// $args contains the remaining elements of the filepath. // Var $args contains the remaining elements of the filepath.
$filepath = '/' . implode('/', $args) . '/'; $filepath = '/' . implode('/', $args) . '/';
} }
@ -496,7 +496,7 @@ function local_treestudyplan_pluginfile(
return false; return false;
} }
// We can now send the file back to the browser - in this case with a cache lifetime of 1 day and no filtering. // We can now send the file back to the browser - in this case with a cache lifetime of 1 day and no filtering.
send_stored_file($file, 24*60*60, 0, $forcedownload, $options); send_stored_file($file, 24 * 60 * 60, 0, $forcedownload, $options);
} else { } else {
return false; return false;
} }

View File

@ -35,7 +35,6 @@ require_login();
$PAGE->set_pagelayout('base'); $PAGE->set_pagelayout('base');
$PAGE->set_context($systemcontext); $PAGE->set_context($systemcontext);
//$teachermode = has_capability("local/treestudyplan:viewuserreports", $systemcontext);
$amteaching = teachingfinder::is_teaching($USER->id); $amteaching = teachingfinder::is_teaching($USER->id);
$haveplans = studyplan::exist_for_user($USER->id); $haveplans = studyplan::exist_for_user($USER->id);

View File

@ -23,8 +23,6 @@
require_once("../../config.php"); require_once("../../config.php");
use local_treestudyplan\contextinfo; use local_treestudyplan\contextinfo;
use local_treestudyplan\courseservice;
use local_treestudyplan\studyplan;
use local_treestudyplan\studyplanpage; use local_treestudyplan\studyplanpage;
use local_treestudyplan\premium; use local_treestudyplan\premium;
@ -42,8 +40,8 @@ $context = $studyplan->context();
$ci = new contextinfo($context); $ci = new contextinfo($context);
$contextname = $ci->pathstr(); $contextname = $ci->pathstr();
$firstperiod = optional_param('firstperiod', 0, PARAM_INT); // First period to show $firstperiod = optional_param('firstperiod', 0, PARAM_INT); // First period to show.
$lastperiod = optional_param('lastperiod', 0, PARAM_INT); // Last periode to show $lastperiod = optional_param('lastperiod', 0, PARAM_INT); // Last periode to show.
$PAGE->set_pagelayout('report'); $PAGE->set_pagelayout('report');
$PAGE->set_title(get_string('studyplan_report', 'local_treestudyplan')); $PAGE->set_title(get_string('studyplan_report', 'local_treestudyplan'));
@ -62,7 +60,11 @@ $PAGE->requires->css(new moodle_url($CFG->wwwroot.'/local/treestudyplan/css/boot
if ($CFG->debugdeveloper) { if ($CFG->debugdeveloper) {
$PAGE->requires->css(new moodle_url($CFG->wwwroot.'/local/treestudyplan/css/devstyles.css')); $PAGE->requires->css(new moodle_url($CFG->wwwroot.'/local/treestudyplan/css/devstyles.css'));
} }
$PAGE->requires->js_call_amd('local_treestudyplan/page-result-overview', 'init', [$studyplan->id(), $page->id(), $firstperiod, $lastperiod]); $PAGE->requires->js_call_amd(
'local_treestudyplan/page-result-overview',
'init',
[$studyplan->id(), $page->id(), $firstperiod, $lastperiod]
);
/** /**
* Shortcut function to provide translations * Shortcut function to provide translations
@ -81,30 +83,30 @@ print "<h3><b>{$contextname}</b> / {$studyplan->name()}</h3>";
if ($studyplan->name() != $page->fullname()) { if ($studyplan->name() != $page->fullname()) {
print "<h4>{$page->fullname()}</h4>"; print "<h4>{$page->fullname()}</h4>";
} }
?> print <<<END
<div id='root'> <div id='root'>
<div class='vue-loader' v-show='false'> <div class='vue-loader' v-show='false'>
<div class="spinner-border text-primary" role="status"> <div class='spinner-border text-primary' role='status'>
<span class="sr-only">Loading...</span> <span class='sr-only'>Loading...</span>
</div> </div>
</div> </div>
<div v-cloak> <div v-cloak>
<div class='vue-loader' v-if="!structure"> <div class='vue-loader' v-if='!structure'>
<div class="spinner-border text-primary" role="status"> <div class='spinner-border text-primary' role='status'>
<span class="sr-only">Loading...</span> <span class='sr-only'>Loading...</span>
</div> </div>
</div> </div>
<template v-else> <template v-else>
<div class='container q-pageperiodselection'> <div class='container q-pageperiodselection'>
<div class='row'> <div class='row'>
<div class="col-sm-2 q-label">{{text.page}}</div> <div class='col-sm-2 q-label'>{{text.page}}</div>
<div class="col-sm-6"> <div class='col-sm-6'>
<select class="custom-select" :value="page.id" @change="selectedPage"> <select class='custom-select' :value='page.id' @change='selectedPage'>
<template v-if="studyplan"> <template v-if='studyplan'>
<option v-for="p in studyplan.pages" <option v-for='p in studyplan.pages'
:key="p.id" :key='p.id'
:value="p.id" :value='p.id'
:selected="(page.id == p.id)?true:false" :selected='(page.id == p.id) ? true : false'
>{{p.fullname}}</option> >{{p.fullname}}</option>
</template> </template>
<option v-else disabled>{{text.loading}}</option> <option v-else disabled>{{text.loading}}</option>
@ -112,27 +114,27 @@ if ($studyplan->name() != $page->fullname()) {
</div> </div>
</div> </div>
<div class='row'> <div class='row'>
<div class="col-sm-2 q-label" >{{text.period}} ({{text.from}} / {{text.to}})</div> <div class='col-sm-2 q-label' >{{text.period}} ({{text.from}} / {{text.to}})</div>
<div class="col-sm-2"> <div class='col-sm-2'>
<select class="custom-select" @change="selectedFirstPeriod"> <select class='custom-select' @change='selectedFirstPeriod'>
<template v-if="page"> <template v-if='page'>
<option v-for="p in page.perioddesc" <option v-for='p in page.perioddesc'
:key="p.id" :key='p.id'
:value="p.period" :value='p.period'
:selected="(structure.firstperiod == p.period)?true:false" :selected='(structure.firstperiod == p.period) ? true : false'
>{{p.fullname}}</option> >{{p.fullname}}</option>
</template> </template>
<option v-else disabled>{{text.loading}}</option> <option v-else disabled>{{text.loading}}</option>
</select> </select>
</div> </div>
<div class="col-sm-2 "> <div class='col-sm-2 '>
<select class="custom-select" @change="selectedLastPeriod"> <select class='custom-select' @change='selectedLastPeriod'>
<template v-if="page"> <template v-if='page'>
<template v-for="p in page.perioddesc"> <template v-for='p in page.perioddesc'>
<option v-if="p.period >= structure.firstperiod" <option v-if='p.period >= structure.firstperiod'
:key="p.id" :key='p.id'
:value="p.period" :value='p.period'
:selected="(structure.lastperiod == p.period)?true:false" :selected='(structure.lastperiod == p.period) ? true : false'
>{{p.fullname}}</option> >{{p.fullname}}</option>
</template> </template>
</template> </template>
@ -141,10 +143,10 @@ if ($studyplan->name() != $page->fullname()) {
</div> </div>
</div> </div>
</div> </div>
<q-studyplanreport :structure="structure" ></s-studyplanreport> <q-studyplanreport :structure='structure' ></s-studyplanreport>
</template> </template>
</div> </div>
</div> </div>
<?php END;
print $OUTPUT->footer(); print $OUTPUT->footer();

View File

@ -55,7 +55,7 @@ if ($hassiteconfig) {
true, true,
)); ));
// Default image for study plans // Default image for study plans.
$page->add(new admin_setting_configstoredfile('local_treestudyplan/defaulticon', $page->add(new admin_setting_configstoredfile('local_treestudyplan/defaulticon',
get_string('setting_defaulticon', 'local_treestudyplan'), get_string('setting_defaulticon', 'local_treestudyplan'),
get_string('settingdesc_defaulticon', 'local_treestudyplan'), get_string('settingdesc_defaulticon', 'local_treestudyplan'),
@ -89,8 +89,17 @@ if ($hassiteconfig) {
get_string('settingdesc_display_heading', 'local_treestudyplan') get_string('settingdesc_display_heading', 'local_treestudyplan')
)); ));
$displayfields = ["shortname" => get_string("shortname"), "idnumber" => get_string("idnumber"), "fullname" => get_string("fullname") ]; $displayfields = [
$infofields = ["" => get_string('none'), "description" => get_string("description"), "contacts" => get_string("teachers"), "idnumber" => get_string("idnumber")]; "shortname" => get_string("shortname"),
"idnumber" => get_string("idnumber"),
"fullname" => get_string("fullname"),
];
$infofields = [
"" => get_string('none'),
"description" => get_string("description"),
"contacts" => get_string("teachers"),
"idnumber" => get_string("idnumber"),
];
$handler = \core_customfield\handler::get_handler('core_course', 'course'); $handler = \core_customfield\handler::get_handler('core_course', 'course');
foreach ($handler->get_categories_with_fields() as $cat) { foreach ($handler->get_categories_with_fields() as $cat) {
@ -106,49 +115,43 @@ if ($hassiteconfig) {
} }
} }
} }
//get_config("local_treestudyplan", "enableplansharing")
$page->add(new admin_setting_configcheckbox('local_treestudyplan/enableplansharing', $page->add(new admin_setting_configcheckbox('local_treestudyplan/enableplansharing',
get_string('setting_enableplansharing', 'local_treestudyplan'), get_string('setting_enableplansharing', 'local_treestudyplan'),
get_string('settingdesc_enableplansharing', 'local_treestudyplan'), get_string('settingdesc_enableplansharing', 'local_treestudyplan'),
true, true,
)); ));
//get_config("local_treestudyplan", "timelessperiods")
$page->add(new admin_setting_configcheckbox('local_treestudyplan/timelessperiods', $page->add(new admin_setting_configcheckbox('local_treestudyplan/timelessperiods',
get_string('setting_timelessperiods', 'local_treestudyplan'), get_string('setting_timelessperiods', 'local_treestudyplan'),
get_string('settingdesc_timelessperiods', 'local_treestudyplan'), get_string('settingdesc_timelessperiods', 'local_treestudyplan'),
false, false,
)); ));
//get_config("local_treestudyplan", "limitcourselist")
$page->add(new admin_setting_configcheckbox('local_treestudyplan/limitcourselist', $page->add(new admin_setting_configcheckbox('local_treestudyplan/limitcourselist',
get_string('setting_limitcourselist', 'local_treestudyplan'), get_string('setting_limitcourselist', 'local_treestudyplan'),
get_string('settingdesc_limitcourselist', 'local_treestudyplan'), get_string('settingdesc_limitcourselist', 'local_treestudyplan'),
false, false,
)); ));
//get_config("local_treestudyplan", "hivizdropslots")
$page->add(new admin_setting_configcheckbox('local_treestudyplan/hivizdropslots', $page->add(new admin_setting_configcheckbox('local_treestudyplan/hivizdropslots',
get_string('setting_hivizdropslots', 'local_treestudyplan'), get_string('setting_hivizdropslots', 'local_treestudyplan'),
get_string('settingdesc_hivizdropslots', 'local_treestudyplan'), get_string('settingdesc_hivizdropslots', 'local_treestudyplan'),
false, false,
)); ));
//get_config("local_treestudyplan", "toolboxleft")
$page->add(new admin_setting_configcheckbox('local_treestudyplan/toolboxleft', $page->add(new admin_setting_configcheckbox('local_treestudyplan/toolboxleft',
get_string('setting_toolboxleft', 'local_treestudyplan'), get_string('setting_toolboxleft', 'local_treestudyplan'),
get_string('settingdesc_toolboxleft', 'local_treestudyplan'), get_string('settingdesc_toolboxleft', 'local_treestudyplan'),
true, true,
)); ));
//get_config("local_treestudyplan", "copystudylinesnewpage")
$page->add(new admin_setting_configcheckbox('local_treestudyplan/copystudylinesnewpage', $page->add(new admin_setting_configcheckbox('local_treestudyplan/copystudylinesnewpage',
get_string('setting_copystudylinesnewpage', 'local_treestudyplan'), get_string('setting_copystudylinesnewpage', 'local_treestudyplan'),
get_string('settingdesc_copystudylinesnewpage', 'local_treestudyplan'), get_string('settingdesc_copystudylinesnewpage', 'local_treestudyplan'),
false, false,
)); ));
//get_config("local_treestudyplan", "continueperiodnumberingnewpage")
$page->add(new admin_setting_configcheckbox('local_treestudyplan/continueperiodnumberingnewpage', $page->add(new admin_setting_configcheckbox('local_treestudyplan/continueperiodnumberingnewpage',
get_string('setting_continueperiodnumberingnewpage', 'local_treestudyplan'), get_string('setting_continueperiodnumberingnewpage', 'local_treestudyplan'),
get_string('settingdesc_continueperiodnumberingnewpage', 'local_treestudyplan'), get_string('settingdesc_continueperiodnumberingnewpage', 'local_treestudyplan'),
@ -156,7 +159,6 @@ if ($hassiteconfig) {
)); ));
if (premium::enabled()) { if (premium::enabled()) {
//get_config("local_treestudyplan", "enablecoach")
$page->add(new admin_setting_configcheckbox('local_treestudyplan/enablecoach', $page->add(new admin_setting_configcheckbox('local_treestudyplan/enablecoach',
get_string('setting_enablecoach', 'local_treestudyplan'), get_string('setting_enablecoach', 'local_treestudyplan'),
get_string('settingdesc_enablecoach', 'local_treestudyplan'), get_string('settingdesc_enablecoach', 'local_treestudyplan'),
@ -186,7 +188,7 @@ if ($hassiteconfig) {
"below" => get_string("infofield_position_below", 'local_treestudyplan'), "below" => get_string("infofield_position_below", 'local_treestudyplan'),
]; ];
for ($i=1;$i<=5;$i++) { for ($i = 1; $i <= 5; $i++) {
$page->add(new admin_setting_configselect('local_treestudyplan/courseinfo'.$i.'_field', $page->add(new admin_setting_configselect('local_treestudyplan/courseinfo'.$i.'_field',
get_string('setting_infofield'.$i.'_field', 'local_treestudyplan'), get_string('setting_infofield'.$i.'_field', 'local_treestudyplan'),
get_string('settingdesc_infofield'.$i.'_field', 'local_treestudyplan'), get_string('settingdesc_infofield'.$i.'_field', 'local_treestudyplan'),
@ -220,7 +222,7 @@ if ($hassiteconfig) {
get_string('setting_competency_detailfield', 'local_treestudyplan'), get_string('setting_competency_detailfield', 'local_treestudyplan'),
get_string('settingdesc_competency_detailfield', 'local_treestudyplan'), get_string('settingdesc_competency_detailfield', 'local_treestudyplan'),
"shortname", "shortname",
["" => get_string("none", "core", ), ["" => get_string("none", "core", ),
"shortname" => get_string("name", "core", ), "shortname" => get_string("name", "core", ),
"idnumber" => get_string("idnumber", "core", ), "idnumber" => get_string("idnumber", "core", ),
"description" => get_string("description", "core", )] "description" => get_string("description", "core", )]
@ -410,7 +412,7 @@ if ($hassiteconfig) {
)); ));
if ($CFG->debugdeveloper) { if ($CFG->debugdeveloper) {
// show decrypted key data // Show decrypted key data.
$pagepremium->add(new admin_setting_description('local_treestudyplan/premium_debug', $pagepremium->add(new admin_setting_description('local_treestudyplan/premium_debug',
get_string('setting_premium_debug', 'local_treestudyplan'), get_string('setting_premium_debug', 'local_treestudyplan'),
premium::debuginfo() . "<br>&nbsp;<br>" // Add empty row at end. premium::debuginfo() . "<br>&nbsp;<br>" // Add empty row at end.
@ -420,5 +422,4 @@ if ($hassiteconfig) {
// Add settings page2 to the admin settings category. // Add settings page2 to the admin settings category.
$ADMIN->add('local_treestudyplan', $pagepremium); $ADMIN->add('local_treestudyplan', $pagepremium);
} }
} }

View File

@ -1,83 +0,0 @@
<?php
function sitematch($key, $site) {
// Add double slashes to key and site if no scheme is set.
// Basically: if no double slashes present before any dots, shashes or @s.
if (!\preg_match_all('#^[^./@]*?//#', $key )) {
$key = "//".$key;
}
if (!\preg_match_all('#^[^./@]*?//#', $site)) {
$site = "//".$site;
}
// Use parse_url() to split path and host.
$keyurl = (object)\parse_url($key);
$siteurl = (object)\parse_url($site);
// No match if host is empty on key or site
if (empty($keyurl->host) || empty($siteurl->host)) {
return false;
}
if ($keyurl->host == "*") {
// * matches all
return true;
}
// First match the host part.
$keyparts = \array_reverse(\explode(".", $keyurl->host));
$siteparts = \array_reverse(\explode(".", $siteurl->host));
// Trim starting www from both parts, since site.domain and www.site.domain should be treated as the same.
if (($x = \array_pop($keyparts)) != "www") {\array_push($keyparts, $x);}
if (($x = \array_pop($siteparts)) != "www") {\array_push($siteparts, $x);}
for ($i = 0; $i < count($keyparts); $i++) {
// No match if the site does not have a part, but the key does. Unless the key part is *
if (!isset($siteparts[$i]) ) {
if ($keyparts[$i] != "*") {
return false;
} else {
$i++; //increment $i by one before break, to make sure the comparison following this loop holds.
break; // Stop comparison. Host part matches.
}
}
// Now do a proper case insensitive check for matching.
// Uses fnmatch to easily handle shell type wildcards.
if ( ! \fnmatch($keyparts[$i], $siteparts[$i], \FNM_CASEFOLD)) {
return false;
}
}
// Fail if the site has a deeper subdomain than the key, unless the deepest key subdomain is *
if ($keyparts[$i-1] != '*' && count($siteparts) > ($i)) {
return false;
}
// If we made it here then the host part matches. Now check the path.
// If path is /*, matches all subpaths including /
$keypath = empty($keyurl->path) ? "/" : $keyurl->path;
$sitepath = empty($siteurl->path) ? "/" : $siteurl->path;
// Trim trailing / from both paths before comparison
if (\strlen($sitepath) > 1) {
$sitepath = \rtrim($sitepath, "/");
}
if (\strlen($keypath) > 1) {
$keypath = \rtrim($keypath, "/");
}
// Do a case insensitive fnmatch on the site so wildcards are matched too.
return \fnmatch($keypath, $sitepath, \FNM_CASEFOLD);
}
$tests = [
["*", "https://www.miqra.nl"],
["*/*", "https://www.miqra.nl"],
["*", "https://clients.openedu.nl/fith"],
["clients.openedu.nl/fith", "https://clients.openedu.nl/fith/"],
["clients.openedu.nl/fith/", "https://clients.openedu.nl/fith"],
];
foreach($tests as $test) {
[$key, $site] = $test;
print("Checking key '{$key}' on site '{$site}': " . (sitematch($key, $site)?"MATCH":"FAIL") . "\n");
}

View File

@ -65,7 +65,6 @@ $ci = new contextinfo($studyplancontext);
$contextname = $ci->pathstr(); $contextname = $ci->pathstr();
$PAGE->set_pagelayout('base'); $PAGE->set_pagelayout('base');
//$PAGE->set_context($studyplancontext);
$PAGE->set_title(get_string('view_plan', 'local_treestudyplan')." - ".$contextname); $PAGE->set_title(get_string('view_plan', 'local_treestudyplan')." - ".$contextname);
$PAGE->set_heading(get_string('view_plan', 'local_treestudyplan')." - ".$contextname); $PAGE->set_heading(get_string('view_plan', 'local_treestudyplan')." - ".$contextname);
@ -73,19 +72,29 @@ if ($studyplancontext->id > 1) {
navigation_node::override_active_url(new moodle_url('/course/index.php', ['categoryid' => $categoryid ])); navigation_node::override_active_url(new moodle_url('/course/index.php', ['categoryid' => $categoryid ]));
$PAGE->navbar->add(get_string('view_plan', 'local_treestudyplan')); $PAGE->navbar->add(get_string('view_plan', 'local_treestudyplan'));
// Coursecat context // Coursecat context.
$cat = \core_course_category::get($studyplancontext->instanceid, IGNORE_MISSING, true); // We checck visibility later $cat = \core_course_category::get($studyplancontext->instanceid, IGNORE_MISSING, true); // We checck visibility later.
} else { } else {
// System context // System context.
$cat = \core_course_category::top(); $cat = \core_course_category::top();
} }
if (!$cat->is_uservisible()) { if (!$cat->is_uservisible()) {
throw new \moodle_exception("error:cannotviewcategory", "local_treestudyplan", "/local/treestudyplan/view_plan.php", $contextname); throw new \moodle_exception(
"error:cannotviewcategory",
"local_treestudyplan",
"/local/treestudyplan/view_plan.php",
$contextname
);
} }
if (!has_capability('local/treestudyplan:viewuserreports', $studyplancontext)) { if (!has_capability('local/treestudyplan:viewuserreports', $studyplancontext)) {
throw new \moodle_exception("error:nostudyplanviewaccess", "local_treestudyplan", "/local/treestudyplan/view_plan.php", $contextname); throw new \moodle_exception(
"error:nostudyplanviewaccess",
"local_treestudyplan",
"/local/treestudyplan/view_plan.php",
$contextname
);
} }
// Load javascripts and specific css. // Load javascripts and specific css.
@ -108,7 +117,15 @@ function t($str, $param = null, $plugin = 'local_treestudyplan') {
} }
print $OUTPUT->header(); print $OUTPUT->header();
?> $text = (object)[
'loading' => t("loading", null, "core"),
'back' => t('back'),
'studyplan_select' => t("studyplan_select"),
'selectstudent_btn' => t('selectstudent_btn'),
'showoverview' => t("showoverview"),
'studyplan_noneselected' => t("studyplan_noneselected"),
];
print <<<END
<div id='root'> <div id='root'>
<div class='vue-loader' v-show='false'> <div class='vue-loader' v-show='false'>
<div class="spinner-border text-primary" role="status"> <div class="spinner-border text-primary" role="status">
@ -117,25 +134,26 @@ print $OUTPUT->header();
</div> </div>
<div v-cloak> <div v-cloak>
<div v-if='!activestudyplan && usedcontexts' class='ml-3 mb-3 s-context-selector'> <div v-if='!activestudyplan && usedcontexts' class='ml-3 mb-3 s-context-selector'>
<b-form-select text='<?php print($contextname);?>' :value="contextid" @change='switchContext' <b-form-select text='{$contextname}' :value="contextid" @change='switchContext'
:class="(!(usedcontexts.length)) ? 'text-primary' : ''"> :class="(!(usedcontexts.length)) ? 'text-primary' : ''">
<b-form-select-option v-if='!(usedcontexts.length)' :value="contextid" <b-form-select-option v-if='!(usedcontexts.length)' :value="contextid"
:class="'text-primary'"> :class="'text-primary'">
<span><?php t("loading", null, "core"); ?>...</span></b-form-select-option> <span>{$text->loading}...</span></b-form-select-option>
<b-form-select-option v-for='ctx in usedcontexts' :key='ctx.id' :value="ctx.context_id" <b-form-select-option v-for='ctx in usedcontexts' :key='ctx.id' :value="ctx.context_id"
:class="(ctx.studyplancount > 0) ? 'font-weight-bold' : ''" :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 v-for="(p, i) in ctx.category.path"><span v-if="i>0"> / </span>{{ p }}</span>
<span>({{ ctx.studyplancount }})</span></b-form-select-option> <span>({{ ctx.studyplancount }})</span></b-form-select-option>
</b-form-select> </b-form-select>
<div v-if="!(usedcontexts.length)" style="position: relative; top: 0.3rem; width: 1.2rem; height: 1.2rem; font-size: 0.7rem;" <div v-if="!(usedcontexts.length)"
style="position: relative; top: 0.3rem; width: 1.2rem; height: 1.2rem; font-size: 0.7rem;"
class="spinner-border text-primary" role="status"></div> class="spinner-border text-primary" role="status"></div>
</div> </div>
<h3 v-else><?php print $contextname; ?></h3> <h3 v-else>{$contextname}</h3>
<div class="m-buttonbar" style="margin-bottom: 1em;"> <div class="m-buttonbar" style="margin-bottom: 1em;">
<template v-if="displayedstudyplan"> <template v-if="displayedstudyplan">
<a href='#' @click.prevent='closeStudyplan' <a href='#' @click.prevent='closeStudyplan'
><i style='font-size: 150%;' class='fa fa-chevron-left'></i> <?php t('back');?></a> ><i style='font-size: 150%;' class='fa fa-chevron-left'></i> {$text->back}</a>
<span><?php t("studyplan_select"); ?></span>&nbsp; <span>{$text->studyplan_select}</span>&nbsp;
<b-form-select lazy :text='dropdown_title' :value='displayedstudyplan.id'> <b-form-select lazy :text='dropdown_title' :value='displayedstudyplan.id'>
<b-form-select-option <b-form-select-option
v-for='(studyplan, planindex) in studyplans' v-for='(studyplan, planindex) in studyplans'
@ -164,7 +182,7 @@ print $OUTPUT->header();
variant="primary" variant="primary"
> >
<template v-slot="{value}">{{value.firstname}} {{value.lastname}}</template> <template v-slot="{value}">{{value.firstname}} {{value.lastname}}</template>
<template #defaultlabel><span class='text-primary'><?php t("showoverview"); ?></span></template> <template #defaultlabel><span class='text-primary'>{$text->showoverview}</span></template>
</s-prevnext-selector> </s-prevnext-selector>
</div> </div>
</template> </template>
@ -172,7 +190,7 @@ print $OUTPUT->header();
<div class='t-studyplan-container'> <div class='t-studyplan-container'>
<h2 v-if='displayedstudyplan&& selectedstudent' <h2 v-if='displayedstudyplan&& selectedstudent'
>{{selectedstudent.firstname}} {{selectedstudent.lastname}} - {{displayedstudyplan.name}}</h2> >{{selectedstudent.firstname}} {{selectedstudent.lastname}} - {{displayedstudyplan.name}}</h2>
<h2 v-else-if='displayedstudyplan'><?php t("showoverview"); ?> - {{displayedstudyplan.name}}</h2> <h2 v-else-if='displayedstudyplan'>{$text->showoverview} - {{displayedstudyplan.name}}</h2>
<r-studyplan v-if='!loadingstudyplan && displayedstudyplan' <r-studyplan v-if='!loadingstudyplan && displayedstudyplan'
v-model='displayedstudyplan' :teachermode='!selectedstudent' v-model='displayedstudyplan' :teachermode='!selectedstudent'
></r-studyplan> ></r-studyplan>
@ -180,7 +198,7 @@ print $OUTPUT->header();
<span class="sr-only">Loading...</span> <span class="sr-only">Loading...</span>
</div> </div>
<div v-else class='t-studyplan-notselected'> <div v-else class='t-studyplan-notselected'>
<p><?php t("studyplan_noneselected"); ?></p> <p>{$text->studyplan_noneselected}</p>
<b-card-group deck> <b-card-group deck>
<s-studyplan-card <s-studyplan-card
v-for='(studyplan, planindex) in studyplans' v-for='(studyplan, planindex) in studyplans'
@ -194,6 +212,6 @@ print $OUTPUT->header();
</div> </div>
</div> </div>
</div> </div>
<?php END;
print $OUTPUT->footer(); print $OUTPUT->footer();