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;
use moodle_exception;
use ValueError;
defined('MOODLE_INTERNAL') || die();
@ -78,7 +77,7 @@ abstract class aggregator {
* Create a new aggregatior object based on the specified method
* @param mixed $method Aggregation method
* @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 {
if (self::supported($method)) {
@ -98,7 +97,7 @@ abstract class aggregator {
public static function create_or_default($method, $configstr): self {
try {
return self::create($method, $configstr);
} catch (\ValueError $x) {
} catch (\moodle_exception $x) {
return self::create(self::FALLBACK, "");
}
}

View File

@ -58,7 +58,8 @@ class associationservice extends \external_api {
"lastname" => new \external_value(PARAM_TEXT, 'last name'),
"idnumber" => new \external_value(PARAM_TEXT, 'id number'),
"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) {
global $DB;
if (!empty($studyplanid)) {
@ -735,7 +741,7 @@ class associationservice extends \external_api {
webservicehelper::require_capabilities(self::CAP_EDIT, $studyplan->context());
$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])) {
$id = $DB->insert_record('local_treestudyplan_coach', [
'studyplan_id' => $studyplanid,

View File

@ -60,7 +60,7 @@ class badgeinfo {
BADGE_STATUS_ACTIVE => 0,
BADGE_STATUS_INACTIVE_LOCKED => 1,
BADGE_STATUS_ACTIVE_LOCKED => 1,
BADGE_STATUS_ARCHIVED => 1, // We don't want to edit archived badges anyway.... .
BADGE_STATUS_ARCHIVED => 1, // We don't want to edit archived badges anyway.....
];
/**
@ -270,13 +270,17 @@ class badgeinfo {
return $badge;
}
/**
* Define structure for badge completion parts
* @param object $value VALUE_OPTIONAL or VALUE_REQUIRED
*/
protected static function badge_completion_structure($value) {
return new \external_single_structure([
"types" => 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'),
"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([
"title" => new \external_value(PARAM_RAW, 'requirment title'),
"completed" => new \external_value(PARAM_BOOL, 'criterion is completed or not', VALUE_OPTIONAL),
@ -297,6 +301,10 @@ class badgeinfo {
], '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 {
$count = 0;
@ -323,14 +331,14 @@ class badgeinfo {
// Determine how to process the progress data, depending on the TYPE's aggregation.
if ($typeagg == BADGE_CRITERIA_AGGREGATION_ANY) {
$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.
if ($badgeagg == BADGE_CRITERIA_AGGREGATION_ANY) {
/* If ANY completion overall, count only the criteria type with the highest completion percentage -.
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)) {
$fraction = $typefraction;
$count = $typecount;
@ -342,7 +350,7 @@ class badgeinfo {
$progress += $typeprogress;
}
$aggrgationhandle = ($typeagg == BADGE_CRITERIA_AGGREGATION_ALL)?"all":"any";
$aggrgationhandle = ($typeagg == BADGE_CRITERIA_AGGREGATION_ALL) ? "all" : "any";
$typeinfo = [
'title' => ucfirst(get_string("criteria_descr_$type", "badges", get_string($aggrgationhandle, "core"))),
'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 [
"types" => $types,
"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');
$description = get_string('error:nosuchmod', 'badges');
} 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 . '"');
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,
"requirements" => [
'completion' => [
'title' => get_string('completeactivity', 'core').
'title' => get_string('completeactivity', 'core').
get_string('modulename', $mod->modname) . ' - ' . $mod->name,
],
]
],
];
if (isset($p["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)) {
$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;
}
$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) {
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 {
$params = $crit->params;
}
@ -534,7 +543,8 @@ class badgeinfo {
} else {
$description = \html_writer::tag('b', '"' . $course->fullname . '"');
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'])) {
$description .= get_string('criteria_descr_grade', 'badges', $p['grade']);
@ -549,18 +559,18 @@ class badgeinfo {
'completion' => [
'title' => get_string('coursecompleted', 'completion'),
],
]
],
];
if (isset($p["grade"])) {
$subcrit["requirements"]["grade"] = [
'title' => get_string('criteria_descr_grade', 'badges', $p["grade"]),
];
}
if (isset($p["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;
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;
}
@ -592,7 +603,7 @@ class badgeinfo {
$fields = profile_get_custom_fields();
// Get formatted field name if such field exists.
$fieldname = isset($fields[$p['field']]->name) ?
format_string($fields[$p['field']]->name): null;
format_string($fields[$p['field']]->name) : null;
} else {
$fieldname = \core_user\fields::get_display_name($p['field']);
}
@ -614,7 +625,7 @@ class badgeinfo {
if (isset($userid)) {
$join = '';
$where = '';
$sqlparams = [ 'userid'=> $userid];
$sqlparams = [ 'userid' => $userid];
if (is_numeric($p['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");
}
/*
$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);
$conditions = "";
$conditions = "";
$basesqlparams = ['courseid' => $courseid];
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);
$sql = "SELECT DISTINCT b.id from {badge} b
@ -847,7 +848,7 @@ class badgeinfo {
}
$search = trim($search);
$conditions = "";
$conditions = "";
$basesqlparams = ['courseid' => $courseid];
if (strlen($search) > 0) {
@ -860,7 +861,7 @@ class badgeinfo {
$basesqlparams = array_merge($basesqlparams, $inparams);
}
// Get all course badges for this course
// Get all course badges for this course.
$badgesids = [];
$sql = "SELECT DISTINCT b.id from {badge} b
WHERE b.courseid = :courseid AND $conditions";
@ -886,14 +887,14 @@ class badgeinfo {
$badgeids = [];
foreach (studyline::find_page_children($page) as $line) {
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();
$relatedbadges = badgeinfo::find_badges_by_course_relation($courseid, $search, $active);
$relatedbadges = self::find_badges_by_course_relation($courseid, $search, $active);
foreach ($relatedbadges as $id) {
$badgeids[] = $id;
}
if ($includecoursebadges) {
$coursebadges = badgeinfo::find_course_badges($courseid, $search, $active);
$coursebadges = self::find_course_badges($courseid, $search, $active);
foreach ($coursebadges as $id) {
$badgeids[] = $id;
}
@ -923,7 +924,7 @@ class badgeinfo {
global $DB;
$search = trim($search);
$conditions = "TRUE";
$conditions = "TRUE";
$basesqlparams = ['type' => \BADGE_TYPE_SITE];
if (strlen($search) > 0) {
@ -936,7 +937,7 @@ class badgeinfo {
$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
WHERE $conditions";

View File

@ -122,7 +122,7 @@ class cascadecohortsync {
$records = $DB->get_records("enrol", $searchparams);
foreach ($records as $instance) {
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;
}
}
@ -175,7 +175,7 @@ class cascadecohortsync {
// Find the study lines associated to this studyplan.
$lines = $this->studyplan->get_all_studylines();
foreach($lines as $line) {
foreach ($lines as $line) {
$this->syncline($line);
}
@ -192,7 +192,7 @@ class cascadecohortsync {
if ($line->enrollable()) {
// Since the studyline is enrollable, we need to cascade by student.
foreach ($courseids as $courseid) {
/* 1: Associate the users by individual association */
/* 1: Associate the users by individual association */
$course = \get_course($courseid);
$userids = $line->get_enrolled_userids();
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...
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
@ -224,7 +225,7 @@ class cascadecohortsync {
if (get_config("local_treestudyplan", "csync_autoremove")) {
// 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);
}
@ -255,14 +256,13 @@ class cascadecohortsync {
// 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 ($instance = $DB->get_record('enrol', $instanceparams)) {
// It already exists.
// Check if this studyplan is already referenced in customtext4 in json format.
// TODO: Check this code - Maybe add option to not remember manually added stuff .
$plans = json_decode($instance->customtext4);
if ($plans == false || !is_array(($plans))) {
// If the data was not an array (null or garbled), count it as manually added.
@ -297,7 +297,8 @@ class cascadecohortsync {
// In the customtext4 field in json format.
$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.
// 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.
B: Check if these links are valid through another studyplan...
If no one uses the link anymore, deactivate it...
@ -322,7 +323,7 @@ class cascadecohortsync {
if (get_config("local_treestudyplan", "csync_autoremove")) {
// 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.
// First check if the cohort is not one of the cohort id's we have associated.
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.
$userids = $this->studyplan->get_linked_user_ids();
foreach($lines as $line) {
$this->syncline($line);
foreach ($lines as $line) {
$this->syncline($line);
}
}

View File

@ -252,7 +252,7 @@ class corecompletioninfo {
$info = [
"conditions" => $conditions,
"aggregation" => self::aggregation_handle($this->completion->get_aggregation_method()),
"enabled" => $this->completion->is_enabled()
"enabled" => $this->completion->is_enabled(),
];
// Check if completion tracking is enabled for this course - otherwise, revert to defaults .
@ -261,7 +261,7 @@ class corecompletioninfo {
// Loop through all condition types to see if they are applicable.
foreach (self::completiontypes() as $type => $handle) {
$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.
$cinfo = [
"type" => $handle,
@ -314,7 +314,7 @@ class corecompletioninfo {
} else {
$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();
// Build requirements.
$details['requirement'] = [];
@ -349,12 +349,11 @@ class corecompletioninfo {
$displaytype = \grade_get_setting($this->course->id, 'displaytype', $CFG->grade_displaytype);
$gradepass = $criteria->gradepass;
// 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);
$details = [
"type" => get_string('coursegrade', 'completion'),
"criteria" => get_string('graderequired', 'completion'),
// TODO: convert to selected representation (letter, percentage, etc).
"requirement" => get_string('graderequired', 'completion')
.": ".$displaygrade,
"status" => "",
@ -479,7 +478,7 @@ class corecompletioninfo {
"details" => $criteria->get_details($completion),
"completed" => $completion->is_complete(), // Make sure to override for activi.
"status" => self::completion_handle(
$completion->is_complete() ? COMPLETION_COMPLETE : COMPLETION_INCOMPLETE),
$completion->is_complete() ? COMPLETION_COMPLETE : COMPLETION_INCOMPLETE),
];
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
in a module
*/
if ( !$completion->is_complete()
if (!$completion->is_complete()
&&
in_array($gradecompletion, [COMPLETION_COMPLETE, COMPLETION_COMPLETE_PASS])) {
/* If a passing grade was provided, but the activity was not completed,
* 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.
$iinfo["warning"] = get_string("warning_incomplete_pass", "local_treestudyplan");
} else {
@ -543,8 +542,8 @@ class corecompletioninfo {
$iinfo['grade'] = $this->format_course_grade($rawgrade);
$rq = floatval($iinfo['details']['requirement']);
$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") {
$cinfo["status"] = "progress";
}
@ -589,8 +588,6 @@ class corecompletioninfo {
* @return stdClass|null object containing 'grade' and optional 'feedback' attribute
*/
private function get_grade($cm, $userid) {
// TODO: Display grade in the way described in the course setup (with letters if needed).
$gi = grade_item::fetch(['itemtype' => 'mod',
'itemmodule' => $cm->modname,
'iteminstance' => $cm->instance,
@ -624,7 +621,6 @@ class corecompletioninfo {
* @return stdClass|null object containing 'grade' and optional 'feedback' attribute
*/
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',
'courseid' => $this->course->id]);
@ -740,7 +736,7 @@ class corecompletioninfo {
$result = new \stdClass;
$result->count = $count;
$result->completed = $completed;
$result->percentage = ($count > 0) ? (($completed / $count) * 100): 0;
$result->percentage = ($count > 0) ? (($completed / $count) * 100) : 0;
return $result;
}

View File

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

View File

@ -267,7 +267,7 @@ class courseinfo {
'fullname' => $this->course->fullname,
'shortname' => $this->course->shortname,
'displayname' => $this->displayname(),
'context' => $contextinfo->model()
'context' => $contextinfo->model(),
];
return $info;
@ -344,12 +344,12 @@ class courseinfo {
$info['grades'][] = $gradable->editor_model($this->studyitem);
}
}
if ($aggregator->use_corecompletioninfo()) {
if ($aggregator->use_corecompletioninfo()) {
$cc = new corecompletioninfo($this->course, $this->studyitem);
$studentlist = $this->studyitem->studyline()->studyplan()->find_linked_userids();
$info['completion'] = $cc->editor_model($studentlist);
}
if ($aggregator->use_coursecompetencies()) {
if ($aggregator->use_coursecompetencies()) {
$ci = new coursecompetencyinfo($this->course, $this->studyitem);
$studentlist = $this->studyitem->studyline()->studyplan()->find_linked_userids();
$info['competency'] = $ci->editor_model($studentlist);
@ -467,11 +467,11 @@ class courseinfo {
$info['grades'][] = $gi->user_model($userid);
}
}
if ($aggregator->use_corecompletioninfo()) {
if ($aggregator->use_corecompletioninfo()) {
$cc = new corecompletioninfo($this->course, $this->studyitem);
$info['completion'] = $cc->user_model($userid);
}
if ($aggregator->use_coursecompetencies()) {
if ($aggregator->use_coursecompetencies()) {
$ci = new coursecompetencyinfo($this->course, $this->studyitem);
$info['competency'] = $ci->user_model($userid);
}
@ -502,7 +502,7 @@ class courseinfo {
*/
public function extrafields_model($includeteachervisible=false) {
$list = [];
for ($i=1; $i <= 5; $i++) {
for ($i = 1; $i <= 5; $i++) {
$field = get_config('local_treestudyplan', 'courseinfo'.$i.'_field');
if ($field) {
$title = self::extrafields_localize_title(get_config('local_treestudyplan', 'courseinfo'.$i.'_title'));
@ -516,7 +516,7 @@ class courseinfo {
"type" => $type,
"fieldname" => $field,
"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;
}
/**
* Localize title for extra field
* @param string $field
*/
protected static function extrafields_localize_title($field) {
$lang = trim(current_language());
$lines = explode("\n", $field);
$title = "";
$fallback = ""; // Fallback to first title
$fallback = ""; // Fallback to first title.
foreach ($lines as $l) {
$parts = explode("|", $l, 2);
if (count($parts) > 0) {
@ -537,7 +541,7 @@ class courseinfo {
$fallback = $parts[0];
}
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]);
} else if (trim($parts[1]) == $lang) {
return trim($parts[0]);
@ -546,7 +550,7 @@ class courseinfo {
}
// 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") {
// Process embedded files.
$value = \file_rewrite_pluginfile_urls(
// The description content
// The description content.
$this->course()->summary,
// The pluginfile URL which will serve the request.
'pluginfile.php',
// The combination of contextid / component / filearea / itemid
// form the virtual bucket that file are stored in.
$this->coursecontext->id, // System instance is always used for this
// The combination of contextid / component / filearea / itemid.
// Form the virtual bucket that file are stored in.
$this->coursecontext->id, // System instance is always used for this.
'course',
'summary',
''
@ -573,7 +577,7 @@ class courseinfo {
} else if ($fieldname == "idnumber") {
$idnumber = trim(preg_replace("/\s+/u", " ", $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());
$contacts = $cle->get_course_contacts();
$value = "";
@ -587,7 +591,7 @@ class courseinfo {
$value = get_string("none");
}
return [$value, "text", $value];
} else if (strpos( $fieldname , "customfield_") === 0) {
} else if (strpos( $fieldname , "customfield_") === 0) {
$fieldshortname = substr($fieldname, strlen("customfield_"));
$handler = \core_customfield\handler::get_handler('core_course', 'course');
@ -600,7 +604,7 @@ class courseinfo {
$raw = $data->get_value();
$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 ($type == "date") {
// 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.
* 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
* @return array of core_course_category
*/
@ -65,9 +66,9 @@ class courseservice extends \external_api {
if (has_capability($capability, \context_system::instance(), $userid)) {
if ($capability == 'moodle/category:viewcourselist') {
// 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');
foreach( $rs as $rcat) {
foreach ($rs as $rcat) {
// 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.
$cat = \core_course_category::get($rcat->id, \IGNORE_MISSING, false, $userid);
@ -79,7 +80,7 @@ class courseservice extends \external_api {
$rs->close();
} 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.
// Recurses only once.
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.
// 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
INNER JOIN {role_assignments} AS ra ON ra.contextid = ctx.id
INNER JOIN {role_capabilities} AS rc ON ra.roleid = rc.roleid
LEFT JOIN {course_categories} AS cat ON ctx.instanceid = cat.id
$sql = "SELECT UNIQUE ctx.* FROM {context} ctx
INNER JOIN {role_assignments} ra ON ra.contextid = ctx.id
INNER JOIN {role_capabilities} rc ON ra.roleid = rc.roleid
LEFT JOIN {course_categories} cat ON ctx.instanceid = cat.id
WHERE ( ctx.contextlevel = :ctxl_coursecat )
AND ra.userid = :userid AND rc.capability = :capability
ORDER BY ctx.depth ASC, cat.sortorder ASC";
@ -133,7 +134,6 @@ class courseservice extends \external_api {
}
}
}
//$recordset->close();
}
return $tops;
@ -152,10 +152,10 @@ class courseservice extends \external_api {
$tops = [];
$pathlike = $DB->sql_like('ctx.path', ':pathsearch');
$sql = "SELECT UNIQUE ctx.* FROM {context} AS ctx
INNER JOIN {role_assignments} AS ra ON ra.contextid = ctx.id
INNER JOIN {role_capabilities} AS rc ON ra.roleid = rc.roleid
LEFT JOIN {course_categories} AS cat ON ctx.instanceid = cat.id
$sql = "SELECT UNIQUE ctx.* FROM {context} ctx
INNER JOIN {role_assignments} ra ON ra.contextid = ctx.id
INNER JOIN {role_capabilities} rc ON ra.roleid = rc.roleid
LEFT JOIN {course_categories} cat ON ctx.instanceid = cat.id
WHERE ( ctx.contextlevel = :ctxl_coursecat )
AND ra.userid = :userid AND rc.capability = :capability
AND {$pathlike}
@ -255,7 +255,7 @@ class courseservice extends \external_api {
throw new moodle_exception("error:nocategoriesvisible", "local_treestudyplan");
}
} else {
if (\get_config("local_treestudyplan", "limitcourselist")) { // TODO: Insert config setting here
if (\get_config("local_treestudyplan", "limitcourselist")) {
$studyplan = studyplan::find_by_id($studyplanid);
$context = $studyplan->context();
if ($context->contextlevel == \CONTEXT_SYSTEM) {
@ -446,7 +446,7 @@ class courseservice extends \external_api {
$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)) {
try {
// Get the context.
@ -455,8 +455,8 @@ class courseservice extends \external_api {
if (has_capability($capability, $refctx)) {
$insertctxs[] = $refctx;
}
} catch(\dml_missing_record_exception $x) {
// ignore context
} catch (\dml_missing_record_exception $x) {
$refctx = null;
}
}
@ -469,10 +469,10 @@ class courseservice extends \external_api {
if ($idx !== false) {
$contextids = array_merge(
array_slice($contextids, 0, $idx+1),
array_slice($contextids, 0, $idx + 1),
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;
break;
@ -484,7 +484,7 @@ class courseservice extends \external_api {
}
// Now translate this to the list of categories.
foreach ($contextids as $ctxid ) {
foreach ($contextids as $ctxid) {
try {
$ctx = \context::instance_by_id($ctxid);
if ($ctx->contextlevel == CONTEXT_SYSTEM) {
@ -504,7 +504,7 @@ class courseservice extends \external_api {
}
}
} catch (\dml_missing_record_exception $x) {
// ignore context
$ctx = null;
}
}
@ -539,7 +539,6 @@ class courseservice extends \external_api {
return new \external_function_parameters( [
"gradeitemid" => new \external_value(PARAM_INT, 'Grade item ID to scan progress for', VALUE_DEFAULT),
"studyplanid" => new \external_value(PARAM_INT, 'Study plan id to check progress in', VALUE_DEFAULT),
]);
}

View File

@ -19,16 +19,16 @@
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace local_treestudyplan;
use Exception;
use JsonException;
defined('MOODLE_INTERNAL') || die();
/**
* Crude tool to aid in debugging
*/
class debug {
/**
* Dump an object in json format to debug log
* @param $object Object to dump
* @param $filename File to write to
* @return any The object
@ -42,9 +42,9 @@ class debug {
fwrite($f, $json."\n");
} catch (JsonException $x) {
fwrite($f, "Error processing json: ". $x->getMessage()."\n");
fwrite($f, "Print_r dump: \n".print_r($object, true)."\n");
}
} catch (\Exception $x) {
$f = null;
} finally {
fclose($f);
@ -52,22 +52,10 @@ class debug {
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 $filename File to write to
* @return any The object
@ -77,6 +65,21 @@ class debug {
$f = fopen($filename, "a+");
fwrite($f, $text."\n");
} catch (\Exception $x) {
$f = null;
} finally {
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
// 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;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot."/lib/formslib.php");
/**
* Base class for moodle forms used in this plugin.
*/
abstract class formbase extends \moodleform {
/**
@ -13,9 +36,9 @@ abstract class formbase extends \moodleform {
*/
public function __construct($params, $ajaxformdata=null) {
$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);
// 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));
} else {
throw new \moodle_exception('accessexception', 'core');
@ -48,7 +71,9 @@ abstract class formbase extends \moodleform {
* @return bool True if security validation passes.
* @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
@ -69,8 +94,7 @@ abstract class formbase extends \moodleform {
if ($data) {
return $this->process_submitted_data($data);
} 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
// 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;
defined('MOODLE_INTERNAL') || die();
@ -33,7 +53,7 @@ class studyplan_editform extends formbase {
*/
public static function init_customdata(object $params) {
$customdata = new stdClass;
$customdata->create = $params->mode=='create' ? true : false;
$customdata->create = $params->mode == 'create' ? true : false;
if ($customdata->create) {
$customdata->context = \context::instance_by_id($params->contextid);
} else {
@ -47,32 +67,20 @@ class studyplan_editform extends formbase {
'trusttext' => true,
'subdirs' => true,
'maxfiles' => 20,
'maxbytes' => 20*1024*1024,
'context' => \context_system::instance(), // Keep the files in system context
'maxbytes' => 20 * 1024 * 1024,
'context' => \context_system::instance(), // Keep the files in system context.
];
$customdata->fileoptions = [
'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,
'maxfiles' => 1, // Just one file
'maxfiles' => 1, // Just one file.
'accepted_types' => ['.jpg', '.png', '.jpeg', '.svg', '.svgz'],
'return_types' => \FILE_INTERNAL | \FILE_EXTERNAL,
];
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
* Also validate parameters and access permissions here
@ -98,7 +106,7 @@ class studyplan_editform extends formbase {
$august = strtotime("first day of august next year");
}
$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;
} else {
$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);
}
// Prepare the editor
// Prepare the editor.
$entry = file_prepare_standard_editor( $entry,
'description',
$customdata->editoroptions,
@ -119,15 +127,15 @@ class studyplan_editform extends formbase {
($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.
$draftitemid = file_get_submitted_draft_itemid('icon');
file_prepare_draft_area(
// The $draftitemid is the target location.
$draftitemid,
// The combination of contextid / component / filearea / itemid
// form the virtual bucket that files are currently stored in
// and will be copied from.
// The combination of contextid / component / filearea / itemid.
// Form the virtual bucket that files are currently stored in.
// And will be copied from.
\context_system::instance()->id,
'local_treestudyplan',
'icon',
@ -154,10 +162,10 @@ class studyplan_editform extends formbase {
$mform = $this->_form;
$customdata = (object)$this->_customdata;
// Register integer type
text_integer::Register();
// Register integer type.
text_integer::register();
// Define the form
// Define the form.
$field = 'name';
$mform->addElement('text', $field,
get_string('studyplan_name', 'local_treestudyplan'),
@ -176,7 +184,7 @@ class studyplan_editform extends formbase {
[]);
$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']);
}
@ -196,8 +204,8 @@ class studyplan_editform extends formbase {
if ($customdata->create) {
$timeless = \get_config("local_treestudyplan", "timelessperiods");
if ( !$timeless) {
// Only add these fields if a new studyplan is created, to easily initialize the first page
if (!$timeless) {
// Only add these fields if a new studyplan is created, to easily initialize the first page.
$field = 'startdate';
$mform->addElement('date_selector', $field,
get_string('studyplan_startdate', 'local_treestudyplan'),
@ -219,7 +227,7 @@ class studyplan_editform extends formbase {
$mform->addRule($field, null, 'required', null, 'client');
} else {
// These fields are only relevant if the studyplan is edited
// These fields are only relevant if the studyplan is edited.
$field = 'suspended';
$mform->addElement('advcheckbox', $field,
get_string('studyplan_suspend', 'local_treestudyplan'),
@ -238,9 +246,9 @@ class studyplan_editform extends formbase {
}
$aggregators = [];
foreach(aggregator::list_model() as $a) {
// Add method only if not deprecated or currently used
if ( $customdata->simplemodel['aggregation'] == $a['id'] || !($a['deprecated']) ) {
foreach (aggregator::list_model() as $a) {
// Add method only if not deprecated or currently used.
if ($customdata->simplemodel['aggregation'] == $a['id'] || !($a['deprecated'])) {
$aggregators[$a['id']] = $a['name'];
}
}
@ -307,7 +315,8 @@ class studyplan_editform extends formbase {
$customdata = (object)$this->_customdata;
// 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) {
$entrykey = $entry->aggregation."_".$key;
$agcfg[$key] = $entry->$entrykey;
@ -316,7 +325,7 @@ class studyplan_editform extends formbase {
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,
'shortname' => $entry->shortname,
'idnumber' => $entry->idnumber,
@ -327,7 +336,7 @@ class studyplan_editform extends formbase {
'enddate' => date("Y-m-d", $entry->enddate),
'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,
'description',
$customdata->editoroptions,
@ -335,7 +344,7 @@ class studyplan_editform extends formbase {
'local_treestudyplan',
'studyplan',
$plan->id());
// Update the description
// Update the description.
$plan->edit([
'description' => $entry->description,
'descriptionformat' => $entry->descriptionformat,
@ -343,7 +352,7 @@ class studyplan_editform extends formbase {
} else {
$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,
'description',
$customdata->editoroptions,
@ -352,7 +361,7 @@ class studyplan_editform extends formbase {
'studyplan',
$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,
'shortname' => $entry->shortname,
'idnumber' => $entry->idnumber,
@ -370,8 +379,8 @@ class studyplan_editform extends formbase {
file_save_draft_area_files(
// The $entry->icon property contains the itemid of the draft file area.
$entry->icon,
// The combination of contextid / component / filearea / itemid
// form the virtual bucket that file are stored in.
// The combination of contextid / component / filearea / itemid.
// Form the virtual bucket that file are stored in.
\context_system::instance()->id,
'local_treestudyplan',
'icon',
@ -387,5 +396,4 @@ class studyplan_editform extends formbase {
debug::dump($response);
return $response;
}
}

View File

@ -1,12 +1,30 @@
<?php
// This file is part of the Studyplan plugin for Moodle
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <https://www.gnu.org/licenses/>.
/**
* 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;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot.'/repository/lib.php');
use local_treestudyplan\aggregator;
use local_treestudyplan\studyplan;
use local_treestudyplan\premium;
use local_treestudyplan\contextinfo;
use local_treestudyplan\studyplanservice;
use local_treestudyplan\courseservice;
@ -38,18 +56,6 @@ class studyplan_fromtemplateform extends formbase {
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
* Also validate parameters and access permissions here
@ -72,7 +78,7 @@ class studyplan_fromtemplateform extends formbase {
$august = strtotime("first day of august next year");
}
$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->template_id = 0;
@ -87,13 +93,13 @@ class studyplan_fromtemplateform extends formbase {
$mform = $this->_form;
$customdata = (object)$this->_customdata;
// Register integer type
text_integer::Register();
// Register integer type.
text_integer::register();
// Define the form
// Define the form.
$templatelist = [];
foreach(studyplan::find_template() as $s) {
foreach (studyplan::find_template() as $s) {
$c = (new contextinfo($s->context()))->model();
$templatelist[$s->id()] = implode(" / ", $c['path']) . " / " . $s->name();
}
@ -125,7 +131,7 @@ class studyplan_fromtemplateform extends formbase {
[]);
$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']);
}
@ -136,8 +142,8 @@ class studyplan_fromtemplateform extends formbase {
$mform->addRule($field, null, 'required', null, 'client');
$timeless = \get_config("local_treestudyplan", "timelessperiods");
if ( !$timeless) {
// Only add these fields if the studyplans are timed
if (!$timeless) {
// Only add these fields if the studyplans are timed.
$field = 'startdate';
$mform->addElement('date_selector', $field,
get_string('studyplan_startdate', 'local_treestudyplan'),
@ -180,5 +186,4 @@ class studyplan_fromtemplateform extends formbase {
return null;
}
}
}

View File

@ -1,15 +1,32 @@
<?php
// This file is part of the Studyplan plugin for Moodle
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <https://www.gnu.org/licenses/>.
/**
* 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;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot.'/repository/lib.php');
use local_treestudyplan\aggregator;
use local_treestudyplan\studyplan;
use local_treestudyplan\studyplanpage;
use local_treestudyplan\courseservice;
use local_treestudyplan\form\text_integer;
use local_treestudyplan\local\helpers\webservicehelper;
use local_treestudyplan\studyplanservice;
use moodle_exception;
use stdClass;
@ -33,7 +50,7 @@ class studyplanpage_editform extends formbase {
*/
public static function init_customdata(object $params) {
$customdata = new stdClass;
$customdata->create = $params->mode=='create' ? true : false;
$customdata->create = $params->mode == 'create' ? true : false;
if ($customdata->create) {
$customdata->plan = studyplan::find_by_id($params->studyplan_id);
} else {
@ -47,32 +64,20 @@ class studyplanpage_editform extends formbase {
'trusttext' => true,
'subdirs' => true,
'maxfiles' => 20,
'maxbytes' => 20*1024*1024,
'context' => \context_system::instance(), // Keep the files in system context
'maxbytes' => 20 * 1024 * 1024,
'context' => \context_system::instance(), // Keep the files in system context.
];
$customdata->fileoptions = [
'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,
'maxfiles' => 1, // Just one file
'maxfiles' => 1, // Just one file.
'accepted_types' => ['.jpg', '.png'],
'return_types' => \FILE_INTERNAL | \FILE_EXTERNAL,
];
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
* Also validate parameters and access permissions here
@ -91,21 +96,21 @@ class studyplanpage_editform extends formbase {
$entry = new stdClass;
$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();
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.
if ($lastpage->enddate(false) == null) {
$entry->startdate = $lastpage->startdate()->add(new \DateInterval("P1Y"))->format("U");
} 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");
}
} 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");
if ($august < time()) {
$august = strtotime("first day of august next year");
@ -114,7 +119,7 @@ class studyplanpage_editform extends formbase {
$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;
} else {
$entry = $DB->get_record(studyplanpage::TABLE, ['id' => $customdata->page->id()]);
@ -122,7 +127,7 @@ class studyplanpage_editform extends formbase {
$entry->enddate = strtotime($entry->enddate);
}
// Prepare the editor
// Prepare the editor.
$entry = file_prepare_standard_editor( $entry,
'description',
$customdata->editoroptions,
@ -143,10 +148,10 @@ class studyplanpage_editform extends formbase {
$mform = $this->_form;
$customdata = (object)$this->_customdata;
// Register integer type
text_integer::Register();
// Register integer type.
text_integer::register();
// Define the form
// Define the form.
$field = 'fullname';
$mform->addElement('text', $field,
get_string('studyplan_name', 'local_treestudyplan'),
@ -160,7 +165,7 @@ class studyplanpage_editform extends formbase {
$mform->addRule($field, null, 'required', null, 'client');
$timeless = \get_config("local_treestudyplan", "timelessperiods");
if ( !$timeless) {
if (!$timeless) {
$field = 'startdate';
$mform->addElement('date_selector', $field,
get_string('studyplan_startdate', 'local_treestudyplan'),
@ -201,7 +206,7 @@ class studyplanpage_editform extends formbase {
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,
'shortname' => $entry->shortname,
'startdate' => date("Y-m-d", $entry->startdate),
@ -209,7 +214,7 @@ class studyplanpage_editform extends formbase {
'periods' => $entry->periods,
'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,
'description',
$customdata->editoroptions,
@ -217,7 +222,7 @@ class studyplanpage_editform extends formbase {
'local_treestudyplan',
'studyplanpage',
$page->id());
// Update the description
// Update the description.
$page->edit([
'description' => $entry->description,
'descriptionformat' => $entry->descriptionformat,
@ -225,7 +230,7 @@ class studyplanpage_editform extends formbase {
} else {
$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,
'description',
$customdata->editoroptions,
@ -234,7 +239,7 @@ class studyplanpage_editform extends formbase {
'studyplanpage',
$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,
'shortname' => $entry->shortname,
'description' => $entry->description,
@ -251,5 +256,4 @@ class studyplanpage_editform extends formbase {
*/
return studyplanservice::clean_returnvalue(studyplanpage::editor_structure(), $page->editor_model());
}
}

View File

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

View File

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

View File

@ -148,7 +148,7 @@ class gradingscanner {
$ungraded++;
} else {
$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.
if ($this->grade_passed($grade)) {
$completedpass++;
@ -180,7 +180,7 @@ class gradingscanner {
// First determine if we have a grade_config for this scale or this maximum grade.
$finalgrade = $grade->finalgrade;
$scale = $this->gi->load_scale();
if ( isset($scale)) {
if (isset($scale)) {
$gradecfg = $DB->get_record($table, ["scale_id" => $scale->id]);
} else if ($this->gi->grademin == 0) {
$gradecfg = $DB->get_record($table, ["grade_points" => $this->gi->grademax]);

View File

@ -173,7 +173,7 @@ class bistate_aggregator extends \local_treestudyplan\aggregator {
$totalrequired = 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) {
@ -193,10 +193,10 @@ class bistate_aggregator extends \local_treestudyplan\aggregator {
$started = $progress + $failed;
$allrequiredmet = ($requiredmet >= $totalrequired);
$fractioncompleted = ($total > 0) ? (floatval($completed) / floatval($total)): 0.0;
$fractionprogress = ($total > 0) ? (floatval($progress) / floatval($total)): 0.0;
$fractionfailed = ($total > 0) ? (floatval($failed) / floatval($total)): 0.0;
$fractionstarted = ($total > 0) ? (floatval($started) / floatval($total)): 0.0;
$fractioncompleted = ($total > 0) ? (floatval($completed) / floatval($total)) : 0.0;
$fractionprogress = ($total > 0) ? (floatval($progress) / floatval($total)) : 0.0;
$fractionfailed = ($total > 0) ? (floatval($failed) / floatval($total)) : 0.0;
$fractionstarted = ($total > 0) ? (floatval($started) / floatval($total)) : 0.0;
if ($total == 0) {
return completion::INCOMPLETE;
@ -230,7 +230,7 @@ class bistate_aggregator extends \local_treestudyplan\aggregator {
*/
public function aggregate_course(courseinfo $courseinfo, studyitem $studyitem, $userid) {
$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.
// Loop through all associated gradables and count the totals, completed, etc..
$completions = [];
@ -278,32 +278,32 @@ class bistate_aggregator extends \local_treestudyplan\aggregator {
$total = count($completion);
if ($method == "ANY") {
if ( $statecount[completion::EXCELLENT] >= 1 ) {
if ($statecount[completion::EXCELLENT] >= 1) {
return completion::EXCELLENT;
} else if ( $statecount[completion::GOOD] >= 1 ) {
} else if ($statecount[completion::GOOD] >= 1) {
return completion::GOOD;
} else if ( $statecount[completion::COMPLETED] >= 1 ) {
} else if ($statecount[completion::COMPLETED] >= 1) {
return completion::COMPLETED;
} else if ( $statecount[completion::PROGRESS] >= 1 ) {
} else if ($statecount[completion::PROGRESS] >= 1) {
return completion::PROGRESS;
} else if ( $statecount[completion::FAILED] >= 1) {
} else if ($statecount[completion::FAILED] >= 1) {
return completion::FAILED;
} else {
return completion::INCOMPLETE;
}
} else { /* default value of ALL */
if ( $total == $statecount[completion::EXCELLENT]) {
if ($total == $statecount[completion::EXCELLENT]) {
return completion::EXCELLENT;
} else if ( $total == ( $statecount[completion::EXCELLENT]
} else if ($total == ( $statecount[completion::EXCELLENT]
+ $statecount[completion::GOOD]) ) {
return completion::GOOD;
} else if ( $total == ( $statecount[completion::EXCELLENT]
} else if ($total == ( $statecount[completion::EXCELLENT]
+ $statecount[completion::GOOD]
+ $statecount[completion::COMPLETED]) ) {
return completion::COMPLETED;
} else if ( $statecount[completion::FAILED]) {
} else if ($statecount[completion::FAILED]) {
return completion::FAILED;
} else if ( $total == $statecount[completion::INCOMPLETE]) {
} else if ($total == $statecount[completion::INCOMPLETE]) {
return completion::INCOMPLETE;
} else {
return completion::PROGRESS;
@ -323,7 +323,7 @@ class bistate_aggregator extends \local_treestudyplan\aggregator {
$gradeitem = $gradeinfo->get_gradeitem();
$grade = $gradeitem->get_final($userid);
$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)) {
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.
$finalgrade = $grade->finalgrade;
$scale = $gradeinfo->get_scale();
if ( isset($scale)) {
if (isset($scale)) {
$gradecfg = $DB->get_record($table, ["scale_id" => $scale->id]);
} else if ($gradeitem->grademin == 0) {
$gradecfg = $DB->get_record($table, ["grade_points" => $gradeitem->grademax]);

View File

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

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_course_complete($userid)) {
// Completed is completed
// Completed is completed.
return completion::COMPLETED;
} else {
// 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);
if ($method == "ANY") {
if ( $statecount[completion::EXCELLENT] >= 1 ) {
if ($statecount[completion::EXCELLENT] >= 1) {
return completion::EXCELLENT;
} else if ( $statecount[completion::GOOD] >= 1 ) {
} else if ($statecount[completion::GOOD] >= 1) {
return completion::GOOD;
} else if ( $statecount[completion::COMPLETED] >= 1 ) {
} else if ($statecount[completion::COMPLETED] >= 1) {
return completion::COMPLETED;
} else if ( $statecount[completion::PROGRESS] >= 1 ) {
} else if ($statecount[completion::PROGRESS] >= 1) {
return completion::PROGRESS;
} else if ( $statecount[completion::FAILED] >= 1) {
} else if ($statecount[completion::FAILED] >= 1) {
return completion::FAILED;
} else {
return completion::INCOMPLETE;
}
} else { /* default value of ALL */
if ( $total == $statecount[completion::EXCELLENT]) {
if ($total == $statecount[completion::EXCELLENT]) {
return completion::EXCELLENT;
} else if ( $total == ( $statecount[completion::EXCELLENT]
} else if ($total == ( $statecount[completion::EXCELLENT]
+ $statecount[completion::GOOD]) ) {
return completion::GOOD;
} else if ( $total == ( $statecount[completion::EXCELLENT]
} else if ($total == ( $statecount[completion::EXCELLENT]
+ $statecount[completion::GOOD]
+ $statecount[completion::COMPLETED]) ) {
return completion::COMPLETED;
} else if ( $statecount[completion::FAILED]) {
} else if ($statecount[completion::FAILED]) {
return completion::FAILED;
} else if ( $total == $statecount[completion::INCOMPLETE]) {
} else if ($total == $statecount[completion::INCOMPLETE]) {
return completion::INCOMPLETE;
} else {
return completion::PROGRESS;

View File

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

View File

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

View File

@ -30,19 +30,19 @@ class randomimage {
/**
* Image width
* @var integer
* @var int
*/
private $width;
/**
* Image height
* @var integer
* @var int
*/
private $height ;
private $height;
/**
* Number of shapes to draw
* @var integer
* @var int
*/
private $shapes;
@ -66,7 +66,7 @@ class randomimage {
IMG_ARC_PIE,
IMG_ARC_CHORD,
IMG_ARC_EDGED,
IMG_ARC_NOFILL
IMG_ARC_NOFILL,
];
/**
@ -75,11 +75,12 @@ class randomimage {
* @param integer $maxpts Max number of point to use
* @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());
$pts = $this->random_pts(\random_int(3, $maxpts));
imagefilledpolygon($im, $pts, $color);
$numpoints = \random_int(3, $maxpts);
$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
* @return void
*/
private function random_arc($im)
{
private function random_arc($im) {
$cx = \random_int(0, $this->width);
$cy = \random_int(0, $this->height);
$w = \random_int(0, $this->width);
@ -104,13 +104,12 @@ class randomimage {
* Generates an array of random alpha color values.
* @return Array [r, g, b, a]
*/
private function random_color_alpha(): Array
{
private function random_color_alpha(): Array {
return [
\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
* @return Array The resulting array of points
*/
private function random_pts($length): Array
{
private function random_pts($length): Array {
$pts = [];
for($n = 0; $n < $length; $n++) {
for ($n = 0; $n < $length; $n++) {
$pts[] = \random_int(0, $this->width);
$pts[] = \random_int(0, $this->height);
}
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) {
$this->shapes = $shapes;
$this->width = $width;
@ -159,15 +163,20 @@ class randomimage {
imagedestroy($im);
}
/**
* Get image content as string
* @return string
*/
public function content() {
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)) {
unlink($this->tempfile);
}
}
}

View File

@ -117,7 +117,7 @@ class period {
$pend = $pstart + $ptime;
}
// Continue period numbers if so configured
// Continue period numbers if so configured.
if (get_config("local_treestudyplan", "continueperiodnumberingnewpage")) {
$offset = 0;
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');
use DateTime;
use moodle_url;
use stdClass;
/**
* Handle badge information in the same style as the other classes
*/
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;
/**
* Certficate code
* @var string
*/
private static $premiumcrt = "-----BEGIN CERTIFICATE-----
MIIDSzCCAjMCFFlyhmKf1fN7U5lQL/dtlsyP24AQMA0GCSqGSIb3DQEBCwUAMGEx
CzAJBgNVBAYTAk5MMRYwFAYDVQQIDA1Ob29yZC1Ib2xsYW5kMRowGAYDVQQKDBFN
@ -59,78 +62,94 @@ n8FqBInMBhGu1uz0Qeo09ke0RHRnghP9EXfig/veMegASZeEhFqmS2Bdiy6gqeZ5
Klc5I28bGbvxIV5pnL6ZSjHEDp2WreM8HB0XFJwU+Q==
-----END CERTIFICATE-----";
/**
* Cached status
* @var object
*/
private static $cachedpremiumstatus = null;
/**
* Check if premium is supported
* @return bool
*/
public static function supported() {
return self::$premiumsupported;
}
/**
* Decrypt encrypted data
* @param string $encrypted Encrypted data
*/
private static function decrypt($encrypted) {
// Get the public key.
$key = \openssl_get_publickey(self::$premiumcrt);
if ($key === false ) {
throw new \ValueError("Error parsing public key data)");
if ($key === false) {
throw new \ParseError("Error parsing public key data)");
}
// Determine the key size.
$keysize = \openssl_pkey_get_details($key)["bits"];
//
$blocksize = ($keysize / 8); // Bits / 8. Whether padded or not.
// Decode data in
// Decode data in.
$b64 = \base64_decode($encrypted);
if ($b64 === false) {
throw new \ValueError("Error in base64 decoding");
throw new \ParseError("Error in base64 decoding");
}
$data = \str_split($b64, $blocksize);
$decrypted = "";
$i = 0;
foreach($data as $chunk) {
foreach ($data as $chunk) {
if (\openssl_public_decrypt($chunk, $dchunk, $key, \OPENSSL_PKCS1_PADDING)) {
$decrypted .= $dchunk;
} else {
throw new \ValueError("Error decrypting chunk $i ({$blocksize} bytes)");
throw new \ParseError("Error decrypting chunk $i ({$blocksize} bytes)");
}
$i++;
}
// 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.
try {
if (\PHP_MAJOR_VERSION < 8) {
\openssl_pkey_free($key);
} catch (\Exception $x) {}
}
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) {
// Headers are repeated in this function for easier testing and copy-pasting into other projects.
$STARTHEADER = "----- BEGIN ACTIVATION KEY -----";
$ENDHEADER = "----- END ACTIVATION KEY -----";
$startheader = "----- BEGIN ACTIVATION KEY -----";
$endheader = "----- END ACTIVATION KEY -----";
$parts = preg_split("/\r?\n/", \trim($data));
if (count($parts) > 2) {
$start = -1;
$end = -1;
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...
for ($i = 0; $i < count($parts); $i++) {
// Make sure all unicode spaces are converted to normal spaces before comparing.
$p = trim(preg_replace('/\s+/u', ' ', $parts[$i]));
if ( $p == $STARTHEADER ) {
$start = $i+1;
if ($p == $startheader) {
$start = $i + 1;
}
if ($start > 0 && $p == $ENDHEADER) {
if ($start > 0 && $p == $endheader) {
$end = $i;
}
}
if ($start < 0 || $end < 0 || $end - $start <= 0) {
throw new \ValueError("Invalid activation key wrappers");
throw new \ParseError("Invalid activation key wrappers");
} else {
$keyslice = array_slice($parts, $start, $end - $start);
return implode("\n", $keyslice);
}
} else {
throw new \ValueError("Invalid activation key");
throw new \ParseError("Invalid activation key");
}
}
@ -140,7 +159,7 @@ Klc5I28bGbvxIV5pnL6ZSjHEDp2WreM8HB0XFJwU+Q==
*/
public static function enabled() {
if (self::$premiumsupported) {
$status = self::premiumStatus();
$status = self::premiumstatus();
return $status->enabled;
} else {
return true;
@ -153,7 +172,7 @@ Klc5I28bGbvxIV5pnL6ZSjHEDp2WreM8HB0XFJwU+Q==
* @return bool
*/
public static function has_intent($intent) {
$status = self::premiumStatus();
$status = self::premiumstatus();
if ($status->enabled) {
return \in_array(\strtolower($intent), $status->intents);
} else {
@ -161,6 +180,9 @@ Klc5I28bGbvxIV5pnL6ZSjHEDp2WreM8HB0XFJwU+Q==
}
}
/**
* Generate debug info.
*/
public static function debuginfo() {
$s = "<pre>";
try {
@ -172,10 +194,10 @@ Klc5I28bGbvxIV5pnL6ZSjHEDp2WreM8HB0XFJwU+Q==
$s .= $json . "\n----\n";
$decoded = \json_decode($json, false);
$s .= "Read as object:\n----\n";
$s .= print_r($decoded, true) ."\n----\n";
$status = self::premiumStatus();
$s .= \json_encode($decoded, \JSON_PRETTY_PRINT) ."\n----\n";
$status = self::premiumstatus();
$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";
} else {
$s .= "Premium key empty";
@ -188,14 +210,15 @@ Klc5I28bGbvxIV5pnL6ZSjHEDp2WreM8HB0XFJwU+Q==
}
}
$s.="</pre>";
$s .= "</pre>";
return $s;
}
/**
* Determine, cache and retrieve premium status
* @return object
*/
protected static function premiumStatus() {
protected static function premiumstatus() {
if (!isset(self::$cachedpremiumstatus)) {
// Initialize default object.
@ -217,9 +240,9 @@ Klc5I28bGbvxIV5pnL6ZSjHEDp2WreM8HB0XFJwU+Q==
if (is_object($decoded)) {
// Copy basic data/
// Copy basic data/.
$keys = ["name", "website", "expires", "issued"];
foreach ( $keys as $k) {
foreach ($keys as $k) {
if (isset($decoded->$k)) {
$o->$k = $decoded->$k;
}
@ -229,24 +252,26 @@ Klc5I28bGbvxIV5pnL6ZSjHEDp2WreM8HB0XFJwU+Q==
$o->intents = explode(", ", $decoded->intent);
}
// Convert dates to DateTime for
// Convert dates to DateTime for.
$now = new \DateTime();
$issuedate = new \DateTime($o->issued);
$expirydate = new \DateTime(); // Default to now
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"));
} else {
try {
$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)
&& self::website_match($o->website)
) {
if ($expirydate > $now ) {
if ($expirydate > $now) {
$o->enabled = true;
$o->expired = false;
} else {
@ -263,7 +288,7 @@ Klc5I28bGbvxIV5pnL6ZSjHEDp2WreM8HB0XFJwU+Q==
}
}
}
} catch (\ValueError $x) {
} catch (\ParseError $x) {
$o->status = \get_string("premium:invalidactivationcontent", "local_treestudyplan");
}
self::$cachedpremiumstatus = $o;
@ -281,7 +306,7 @@ Klc5I28bGbvxIV5pnL6ZSjHEDp2WreM8HB0XFJwU+Q==
// Add double slashes to key and site if no scheme is set.
// Basically: if no double slashes present before any dots, slashes or @s.
if (!\preg_match_all('#^[^./@]*?//#', $key )) {
if (!\preg_match_all('#^[^./@]*?//#', $key)) {
$key = "//".$key;
}
if (!\preg_match_all('#^[^./@]*?//#', $site)) {
@ -291,13 +316,13 @@ Klc5I28bGbvxIV5pnL6ZSjHEDp2WreM8HB0XFJwU+Q==
$keyurl = (object)\parse_url($key);
$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)) {
return false;
}
if ($keyurl->host == "*") {
// * matches all
// Value * matches all.
return true;
}
@ -306,37 +331,41 @@ Klc5I28bGbvxIV5pnL6ZSjHEDp2WreM8HB0XFJwU+Q==
$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);}
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]) ) {
// 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.
$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)) {
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)) {
// 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 /
// 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
// Trim trailing / from both paths before comparison.
if (\strlen($sitepath) > 1) {
$sitepath = \rtrim($sitepath, "/");
}
@ -365,7 +394,9 @@ Klc5I28bGbvxIV5pnL6ZSjHEDp2WreM8HB0XFJwU+Q==
"name" => new \external_value(PARAM_TEXT, 'premium registration name'),
"expires" => new \external_value(PARAM_TEXT, 'premium registration expiry date'),
"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() {
if (self::$premiumsupported) {
$status = self::premiumStatus();
$status = self::premiumstatus();
$keys = [
"enabled",
"website",
@ -386,7 +417,7 @@ Klc5I28bGbvxIV5pnL6ZSjHEDp2WreM8HB0XFJwU+Q==
];
$result = [];
foreach ( $keys as $param) {
foreach ($keys as $param) {
$result[$param] = $status->$param;
}
return $result;
@ -407,7 +438,7 @@ Klc5I28bGbvxIV5pnL6ZSjHEDp2WreM8HB0XFJwU+Q==
* @return string HTML description of premium status.
*/
public static function statusdescription() {
$status = self::premiumStatus();
$status = self::premiumstatus();
$msg = new \stdClass;
$msg->name = $status->name;

View File

@ -40,6 +40,9 @@ class reportservice extends \external_api {
*/
const CAP_VIEW = "local/treestudyplan:viewuserreports";
/**
* Parameter description for webservice function
*/
public static function get_report_structure_parameters(): \external_function_parameters {
return new \external_function_parameters([
"pageid" => new \external_value(PARAM_INT, 'id of studyplan page'),
@ -83,7 +86,7 @@ class reportservice extends \external_api {
$firstperiod = 1;
}
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();
}
@ -95,9 +98,9 @@ class reportservice extends \external_api {
"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);
$pmodel = [
'period' => $period->model(),
@ -127,13 +130,15 @@ class reportservice extends \external_api {
return $model;
}
/**
* Parameter description for webservice function
*/
public static function get_report_data_parameters(): \external_function_parameters {
return new \external_function_parameters([
"pageid" => new \external_value(PARAM_INT, 'id of studyplan page'),
"userid" => new \external_value(PARAM_INT, 'id of user'),
"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),
]);
}
@ -144,6 +149,13 @@ class reportservice extends \external_api {
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) {
$page = studyplanpage::find_by_id($pageid);
webservicehelper::require_capabilities(self::CAP_VIEW, $page->studyplan()->context());
@ -156,15 +168,12 @@ class reportservice extends \external_api {
$firstperiod = 1;
}
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();
}
$model = [];
//TODO: Build report structure
for($i = $firstperiod; $i <= $lastperiod; $i++) {
for ($i = $firstperiod; $i <= $lastperiod; $i++) {
$lines = studyline::find_page_children($page);
foreach ($lines as $line) {
$items = studyitem::search_studyline_children($line, $i, studyitem::COURSE);
@ -175,11 +184,12 @@ class reportservice extends \external_api {
}
}
}
return $model;
}
/**
* Parameter description for webservice function
*/
public static function get_report_details_parameters(): \external_function_parameters {
return new \external_function_parameters([
"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)) {
$model = $studyplan->user_model($userid);
$model = $studyplan->user_model($userid);
return $model;
} else {
return null;
@ -222,7 +222,7 @@ class studentstudyplanservice extends \external_api {
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;
@ -279,7 +279,7 @@ class studentstudyplanservice extends \external_api {
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);
$userid = $invite->user_id;
@ -341,7 +341,7 @@ class studentstudyplanservice extends \external_api {
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);
$studyplan = $page->studyplan();
@ -430,8 +430,8 @@ class studentstudyplanservice extends \external_api {
$studyplan = studyplan::find_by_id($studyplanid);
if ($studyplan->exist_for_user($userid) && !$studyplan->suspended()) {
$model = $studyplan->user_model($userid);
return $model;
$model = $studyplan->user_model($userid);
return $model;
} else {
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());
// Return empty list if coach role not enabled
if ( ! (premium::enabled() && \get_config("local_treestudyplan", "enablecoach")) ) {
// Return empty list if coach role not enabled.
if (! (premium::enabled() && \get_config("local_treestudyplan", "enablecoach"))) {
return [];
}
@ -570,7 +570,9 @@ class studentstudyplanservice extends \external_api {
if (has_capability(self::CAP_COACH, $studyplan->context(), $USER)) {
$list[] = $studyplan->simple_model_coach();
}
} catch(\Exception $x) {}
} catch (\Exception $x) {
$studyplan = null;
}
}
return $list;
}
@ -694,7 +696,7 @@ class studentstudyplanservice extends \external_api {
$o->enrol($userid);
}
// Trigger immediate cohort synchronization for this line only
// Trigger immediate cohort synchronization for this line only.
autocohortsync::syncline($o);
return $o->enrol_info_model($userid);

View File

@ -422,7 +422,7 @@ class studyitem {
foreach ($resequence as $sq) {
// 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() ) {
$DB->update_record(self::TABLE, [
'id' => $sq['id'],
@ -646,7 +646,7 @@ class studyitem {
break;
}
}
return ($notexpired) ? completion : :COMPLETED : completion::INCOMPLETE;
return ($notexpired) ? completion::COMPLETED : completion::INCOMPLETE;
} else {
return completion::COMPLETED;
}

View File

@ -96,7 +96,7 @@ class studyitemconnection {
/**
* Get target studyitem id for this connection
*/
public function to_id() :int {
public function to_id(): int {
return $this->r->to_id;
}
@ -167,9 +167,9 @@ class studyitemconnection {
'to_id' => $toid,
]);
return success::success(['msg'=>'Items Disconnected']);
return success::success(['msg' => 'Items Disconnected']);
} 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
*/
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
*/
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();
if (!empty($userid)) {
if ( $plan->has_linked_user(intval($userid))) {
if ( $this->self_enrollable() && $userid == $USER->id ) {
if ($plan->has_linked_user(intval($userid))) {
if ($this->self_enrollable() && $userid == $USER->id) {
return true;
}
if ( $this->role_enrollable()) {
if ($this->role_enrollable()) {
$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)) {
return true;
}
@ -212,9 +212,9 @@ class studyline {
}
}
} else {
if ( $this->role_enrollable()) {
if ($this->role_enrollable()) {
$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)) {
return true;
}
@ -239,13 +239,15 @@ class studyline {
global $DB;
$list = [];
$roles = explode(", ", $this->r->enrolrole);
foreach($roles as $r) {
foreach ($roles as $r) {
$roleid = intval($r);
if ($roleid > 0) {
try {
$role = $DB->get_record('role', ["id" => $roleid], "*", MUST_EXIST);
$list[] = $role;
} catch(\Exception $x) {}
} catch (\Exception $x) {
$role = null;
}
}
}
@ -259,7 +261,7 @@ class studyline {
global $DB;
$list = [];
$roles = explode(", ", $this->r->enrolrole);
foreach($roles as $r) {
foreach ($roles as $r) {
$roleid = intval($r);
if ($roleid > 0) {
$list[] = $roleid;
@ -274,7 +276,7 @@ class studyline {
*/
public function enrol_roles_model(): array {
$list = [];
foreach($this->enrol_roles() as $role) {
foreach ($this->enrol_roles() as $role) {
$name = role_get_name($role, $this->context()); // Get localized role name.
$list[] = [
"id" => $role->id,
@ -304,7 +306,7 @@ class studyline {
self::SLOTSET_FILTER => new \external_multiple_structure(
studyitem::editor_structure(), 'filter items'),
])
)
),
], "Study line editor structure", $value);
}
@ -552,7 +554,7 @@ class studyline {
self::SLOTSET_FILTER => new \external_multiple_structure(
studyitem::user_structure(), 'filter items'),
])
)
),
], 'Studyline with user info', $value);
}
@ -571,7 +573,7 @@ class studyline {
];
if (!empty($userid)) {
$r = $DB->get_record('local_treestudyplan_lineuser', [
$r = $DB->get_record('local_treestudyplan_lineuser', [
'line_id' => $this->id(),
'user_id' => $userid,
]);
@ -606,15 +608,16 @@ class studyline {
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() {
$userids = $this->studyplan()->find_linked_userids();
if ($this->enrollable()) {
$list = [];
foreach($userids as $uid) {
if ( $this->isenrolled($uid)) {
foreach ($userids as $uid) {
if ($this->isenrolled($uid)) {
$list[] = $uid;
}
}
@ -632,7 +635,7 @@ class studyline {
public function isenrolled($userid) {
global $DB;
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 {
$r = $DB->get_record('local_treestudyplan_lineuser', [
'line_id' => $this->id(),
@ -721,7 +724,7 @@ class studyline {
// Registration already exists, check if enrolled is true and update accordingly.
if (boolval($r->enrolled)) {
$r->enrolled = 0;
$r->timeenrolled = time(); // Regi
$r->timeenrolled = time(); // Regi.
$r->enrolledby = $USER->id;
$DB->update_record("local_treestudyplan_lineuser", $r);

View File

@ -146,7 +146,7 @@ class studyplan {
*/
public function startdate() {
$date = null;
foreach($this->pages() as $p) {
foreach ($this->pages() as $p) {
if (!isset($date) || $p->startdate() < $date) {
$date = $p->startdate();
}
@ -154,13 +154,17 @@ class studyplan {
return $date;
}
/**
* Determine studyplan icon
* @return string Url of icon
*/
private function icon() {
global $CFG;
$fs = \get_file_storage();
// Returns an array of `stored_file` instances.
$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);
if ($file->get_filename() == ".") {
// Get next file if the first is the directory itself.
@ -183,8 +187,14 @@ class studyplan {
// Fall back to the standard (ugly) default image.
$url = new \moodle_url($CFG->wwwroot . "/local/treestudyplan/pix/default_icon.png");
} else {
$url = \moodle_url::make_pluginfile_url(\context_system::instance()->id, 'local_treestudyplan', 'defaulticon', 0, "/",
$defaulticon);
$url = \moodle_url::make_pluginfile_url(
\context_system::instance()->id,
'local_treestudyplan',
'defaulticon',
0,
"/",
$defaulticon
);
}
}
@ -201,9 +211,9 @@ class studyplan {
// The pluginfile URL which will serve the request.
'pluginfile.php',
// The combination of contextid / component / filearea / itemid
// form the virtual bucket that file are stored in.
\context_system::instance()->id, // System instance is always used for this
// The combination of contextid / component / filearea / itemid.
// Form the virtual bucket that file are stored in.
\context_system::instance()->id, // System instance is always used for this.
'local_treestudyplan',
'studyplan',
$this->id
@ -216,7 +226,7 @@ class studyplan {
* @return studyplanpage[]
*/
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);
}
return $this->pagecache;
@ -266,7 +276,11 @@ class studyplan {
"aggregation_info" => aggregator::basic_structure(),
"pages" => new \external_multiple_structure(studyplanpage::simple_structure(), 'pages'),
"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),
], 'Basic studyplan info', $value);
}
@ -313,7 +327,7 @@ class studyplan {
$model = $this->simple_model();
$users = $this->find_linked_userids();
$sum = 0;
foreach ( $users as $uid ) {
foreach ($users as $uid) {
$sum += $this->scanuserprogress($uid);
}
@ -408,7 +422,8 @@ class studyplan {
public static function add($fields, $bare = false): self {
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];
foreach ($addable as $f) {
if (array_key_exists($f, $fields)) {
@ -418,8 +433,8 @@ class studyplan {
$id = $DB->insert_record(self::TABLE, $info);
$plan = self::find_by_id($id); // Make sure the new studyplan is immediately cached.
// Add a single page and initialize it with placeholder data
// This makes it easier to create a new study plan
// Add a single page and initialize it with placeholder data.
// 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....
if (!$bare) {
@ -459,7 +474,7 @@ class studyplan {
'aggregation',
'aggregation_config',
'suspended',
'template'
'template',
];
$info = ['id' => $this->id ];
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_cohort", ["studyplan_id" => $this->id]);
$DB->delete_records("local_treestudyplan_user", ["studyplan_id" => $this->id]);
//
$DB->delete_records('local_treestudyplan', ['id' => $this->id]);
return success::success();
}
@ -768,7 +783,8 @@ class studyplan {
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
*/
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.
*/
public function is_coach($user=null) {
global $DB, $USER
;
if ( ! (premium::enabled() && \get_config("local_treestudyplan", "enablecoach")) ) {
global $DB, $USER;
if (! (premium::enabled() && \get_config("local_treestudyplan", "enablecoach"))) {
// If coach role is not available, return false immediately.
return false;
}
@ -803,7 +820,7 @@ class studyplan {
} else {
$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)) {
return true;
@ -845,8 +862,8 @@ class studyplan {
$prg = $p->scanuserprogress($userid);
$progress += $prg;
}
// Now average it out over the amount of pages
if (count($pages) > 0 ) {
// Now average it out over the amount of pages.
if (count($pages) > 0) {
return $progress / count($pages);
} else {
return 0;
@ -912,7 +929,7 @@ class studyplan {
'description' => $this->r->description,
'descriptionformat' => $this->r->descriptionformat,
'aggregation' => $this->r->aggregation,
'aggregation_config' => $this->r->aggregation_config
'aggregation_config' => $this->r->aggregation_config,
], true);
// Copy any files related to this studyplan.
@ -981,7 +998,7 @@ class studyplan {
$json = json_encode([
"type" => "studyplan",
"version" => 2.0,
"studyplan" => $model
"studyplan" => $model,
], \JSON_PRETTY_PRINT);
return [ "format" => "application/json", "content" => $json];
}
@ -1042,10 +1059,10 @@ class studyplan {
*/
public function import_files($importfiles, $area) {
$fs = get_file_storage();
foreach($importfiles as $file) {
foreach ($importfiles as $file) {
if ($file['name'] != ".") {
$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.
'filearea' => $area, // Usually = table name.
'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.
$plan = self::add( $planmodel, true);
// Import the files
// Import the files.
if (isset( $planmodel['files']) && is_array( $planmodel['files'])) {
$plan->import_files( $planmodel['files'], "studyplan");
}
// Import the icon
// Import the icon.
if (isset( $planmodel['iconfiles']) && is_array( $planmodel['iconfiles'])) {
$plan->import_files( $planmodel['iconfiles'], "icon");
}
@ -1215,7 +1232,7 @@ class studyplan {
$fields = $DB->get_fieldset_sql($sql, ["studyplan_id" => $this->id]);
$list = [];
foreach($fields as $id) {
foreach ($fields as $id) {
$list[] = studyline::find_by_id($id);
}
return $list;

View File

@ -154,9 +154,9 @@ class studyplanpage {
// The pluginfile URL which will serve the request.
'pluginfile.php',
// The combination of contextid / component / filearea / itemid
// form the virtual bucket that file are stored in.
\context_system::instance()->id, // System instance is always used for this
// The combination of contextid / component / filearea / itemid.
// Form the virtual bucket that file are stored in.
\context_system::instance()->id, // System instance is always used for this.
'local_treestudyplan',
'studyplanpage',
$this->id
@ -164,6 +164,10 @@ class studyplanpage {
return $text;
}
/**
* Determine studyplan timing
* @return string
*/
public function timing() {
$now = new \DateTime();
if ($now > $this->startdate()) {
@ -292,7 +296,7 @@ class studyplanpage {
}
$id = $DB->insert_record(self::TABLE, $info);
// Refresh the page cache for the studyplan
// Refresh the page cache for the studyplan.
$plan->pages(true);
$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")) {
$templatepage = null;
$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);
foreach ($pages as $p) {
if ($p->id() != $id) {
@ -322,7 +326,7 @@ class studyplanpage {
}
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,
'name' => get_string("default_line_name", "local_treestudyplan"),
'shortname' => get_string("default_line_shortname", "local_treestudyplan"),
@ -450,8 +454,8 @@ class studyplanpage {
}
}
}
if ( $courses > 0 ) {
return ($completed/$courses);
if ($courses > 0) {
return ($completed / $courses);
} else {
return 0;
}
@ -502,7 +506,7 @@ class studyplanpage {
'description' => $this->r->description,
'pages' => $this->r->pages,
'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.
@ -585,7 +589,7 @@ class studyplanpage {
$json = json_encode([
"type" => "studyplanpage",
"version" => 2.0,
"page" => $model
"page" => $model,
], \JSON_PRETTY_PRINT);
return [ "format" => "application/json", "content" => $json];
}
@ -752,10 +756,10 @@ class studyplanpage {
*/
public function import_files($importfiles, $area) {
$fs = get_file_storage();
foreach($importfiles as $file) {
foreach ($importfiles as $file) {
if ($file['name'] != ".") {
$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.
'filearea' => $area, // Usually = table name.
'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());
}
// 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()) {
return null;
} else {
@ -237,7 +237,7 @@ class studyplanservice extends \external_api {
'description' => $description,
'periods' => $periods,
'startdate' => $startdate,
'enddate' => empty($enddate) ? null : $enddate,
'enddate' => empty($enddate) ? null : $enddate,
'aggregation' => $aggregation,
'aggregation_config' => $aggregationconfig,
'context_id' => $contextid,
@ -650,10 +650,10 @@ class studyplanservice extends \external_api {
'type' => $type,
'slot' => $slot,
'layer' => $layer,
'competency_id' => isset($details['competency_id']) ? $details['competency_id'] : null,
'course_id' => isset($details['course_id']) ? $details['course_id'] : null,
'badge_id' => isset($details['badge_id']) ? $details['badge_id'] : null,
'continuation_id' => isset($details['continuation_id']) ? $details['continuation_id'] : null,
'competency_id' => isset($details['competency_id']) ? $details['competency_id'] : null,
'course_id' => isset($details['course_id']) ? $details['course_id'] : null,
'badge_id' => isset($details['badge_id']) ? $details['badge_id'] : null,
'continuation_id' => isset($details['continuation_id']) ? $details['continuation_id'] : null,
]);
return $o->editor_model();
}
@ -902,7 +902,6 @@ class studyplanservice extends \external_api {
*/
public static function list_badges_parameters(): \external_function_parameters {
return new \external_function_parameters( [
] );
}
@ -922,12 +921,9 @@ class studyplanservice extends \external_api {
$result = [];
$badges = badges_get_badges(BADGE_TYPE_SITE, "timemodified");
foreach ($badges as $badge) {
// TODO: Add config option to list only active badges.
$result[] = (new badgeinfo($badge))->editor_model();
// TODO: Include course badges somehow... Just site badges is not enough.
}
return $result;
@ -962,7 +958,7 @@ class studyplanservice extends \external_api {
*/
public static function search_badges($search, $active=false) {
$systemcontext = webservicehelper::system_context();
// Check system permission to
// Check system permission to.
webservicehelper::require_capabilities("moodle/badges:viewbadges", $systemcontext);
$results = [];
@ -1115,8 +1111,9 @@ class studyplanservice extends \external_api {
$studyplan = $item->studyline()->studyplan();
if (($studyplan->is_coach() && !$studyplan->suspended())
|| has_capability('local/treestudyplan:editstudyplan', $studyplan->context())
|| ($coursecontext && is_enrolled($coursecontext, $USER, 'local/treestudyplan:selectowngradables'))) {
return coursecompetencyinfo::require_competency($competencyid, $itemid, $required)->model();
|| ($coursecontext && is_enrolled($coursecontext, $USER, 'local/treestudyplan:selectowngradables'))
) {
return coursecompetencyinfo::require_competency($competencyid, $itemid, $required)->model();
} else {
return success::fail("Access denied")->model();
}
@ -1518,7 +1515,7 @@ class studyplanservice extends \external_api {
// Validate import context.
webservicehelper::require_capabilities(self::CAP_EDIT, $plan->context());
$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) {
return success::fail("Access denied")->model();
}
@ -1556,7 +1553,7 @@ class studyplanservice extends \external_api {
webservicehelper::require_capabilities(self::CAP_EDIT, $page->studyplan()->context());
$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) {
return success::fail("Access denied")->model();
}
@ -1622,7 +1619,7 @@ class studyplanservice extends \external_api {
foreach (explode("&", $formdata) as $pair) {
$p = explode("=", $pair, 2);
$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) {
// 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);
foreach ($studyitems as $item) {
if ($item->type() == studyitem::COURSE) {
if ( $item->slot() <= $page->periods() ) {
if ($item->slot() <= $page->periods()) {
$period = $periods[$item->slot()];
try {
self::course_period_timing($period->id(), $item->courseid(), $item->span());
@ -1991,11 +1988,11 @@ class studyplanservice extends \external_api {
$contextlevels = [CONTEXT_SYSTEM, CONTEXT_COURSECAT];
$list = [];
$roles = \get_all_roles();
foreach($roles as $role) {
foreach ($roles as $role) {
$name = \role_get_name($role, $p->context()); // Get localized role name.
$rctxtlevels = \get_role_contextlevels($role->id);
$intersect = array_intersect($rctxtlevels, $contextlevels);
if ( count($intersect) > 0 ) {
if (count($intersect) > 0) {
$list[] = [
'id' => $role->id,
'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);
return $list;
}
@ -2132,7 +2129,7 @@ class studyplanservice extends \external_api {
$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);
return $list;
}
@ -2174,7 +2171,7 @@ class studyplanservice extends \external_api {
$list = [];
$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);
}
@ -2195,7 +2192,6 @@ class studyplanservice extends \external_api {
*/
public static function count_templates_parameters(): \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
*/
public function __construct($success, $msg, $data=[]) {
$this->success = ($success) ? true : false;
$this->success = ($success) ? true : false;
$this->msg = $msg;
$this->data = json_encode($data);
}

View File

@ -98,7 +98,7 @@ class autocohortsync extends \core\task\scheduled_task {
$userenroller = new cascadeusersync($plan);
$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) {
global $DB;
$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) {
global $DB;
$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();
foreach ($list as $pageid) {
// 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]);
$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";
/**
* Load moodle form
* @param string $formname
* @param string $params
* @param string $ajaxformdata
*/
protected static function load_mform($formname, $params, $ajaxformdata = null) {
global $CFG;
/* 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}";
// 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);
$mform = new $mformclassname( $jsonparams, $ajaxformdata);
return $mform;
@ -121,7 +127,7 @@ class utilityservice extends \external_api {
return new \external_single_structure(
[
'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 = [];
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);
$return = $mform->process_submission();
@ -165,7 +171,7 @@ class utilityservice extends \external_api {
return success::success($return)->model();
}
/**
/**
* Parameter description for webservice function submit_cm_editform
*/
public static function getsettings_parameters(): \external_function_parameters {
@ -193,7 +199,7 @@ class utilityservice extends \external_api {
return [
"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([
'help' => false,
'amount' => null
'amount' => null,
], [
'h' => 'help',
'a' => 'amount',
@ -56,7 +56,7 @@ if ($options['help']) {
cli_writeln($usage);
exit(2);
}
print_r($options);
if (empty($options['amount']) || !is_numeric($options['amount'])) {
cli_error('Missing mandatory integer argument "amount"', 2);
}
@ -76,7 +76,7 @@ if (!$user) {
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)) {
cli_error(sprintf("User authentication is either 'nologin' or disabled. Check Moodle authentication method for '%s'",
$user->username));
@ -87,8 +87,8 @@ $authplugin->sync_roles($user);
login_attempt_valid($user);
complete_user_login($user);
for ($i=0; $i<$amount; $i++) {
$fortune = shell_exec("{$fortunepath} -n 160 ");
for ($i = 0; $i < $amount; $i++) {
$fortune = shell_exec("{$fortunepath} -n 160 ");
$pieces = explode(" ", $fortune);
$name = implode(" ", array_splice($pieces, 0, 4));
@ -118,7 +118,7 @@ for ($i=0; $i<$amount; $i++) {
$fordb->issuercontact = $CFG->badges_defaultissuercontact;
$fordb->expiredate = null;
$fordb->expireperiod = null;
$fordb->expireperiod = null;
$fordb->type = \BADGE_TYPE_SITE; // Site badge.
$fordb->courseid = null;
$fordb->messagesubject = get_string('messagesubject', 'badges');

View File

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

View File

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

View File

@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// 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
* @copyright 2023 P.M. Kuipers
* @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'));
premium::require_premium();
if ( ! (\get_config("local_treestudyplan", "enablecoach")) ) {
if (! (\get_config("local_treestudyplan", "enablecoach"))) {
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
*
* @param mixed $str Translation key
* @param null|string[] $param Parameters to pass to translation
* @param string $plugin Location to search for translation strings
@ -61,7 +60,14 @@ function t($str, $param = null, $plugin = 'local_treestudyplan') {
}
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 class='vue-loader' v-show='false'>
<div class="spinner-border text-primary" role="status">
@ -73,7 +79,7 @@ print $OUTPUT->header();
<template v-if="displayedstudyplan">
<a href='#' @click.prevent='closeStudyplan'
><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
v-model="displayedstudyplan"
v-if="displayedstudyplan.description"
@ -93,14 +99,14 @@ print $OUTPUT->header();
variant="primary"
>
<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>
</template>
</div>
<div class='t-studyplan-container'>
<h2 v-if='displayedstudyplan&& selectedstudent'
>{{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">
<r-studyplan v-if="selectedstudent"
v-model='displayedstudyplan'
@ -116,7 +122,7 @@ print $OUTPUT->header();
<span class="sr-only">Loading...</span>
</div>
<div v-else class='t-studyplan-notselected'>
<p><?php t("studyplan_noneselected"); ?></p>
<p>{$text->studyplan_noneselected}</p>
<b-card-group deck>
<s-studyplan-card
v-for='(studyplan, planindex) in studyplans'
@ -130,6 +136,6 @@ print $OUTPUT->header();
</div>
</div>
</div>
<?php
END;
print $OUTPUT->footer();

View File

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

View File

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

View File

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

View File

@ -394,7 +394,7 @@ function xmldb_local_treestudyplan_upgrade($oldversion) {
"fullname" => "Period {$pnum}",
"shortname" => "P{$pnum}",
"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);
}
@ -532,7 +532,6 @@ function xmldb_local_treestudyplan_upgrade($oldversion) {
$dbman->add_field($table, $field);
}
// Treestudyplan savepoint reached.
upgrade_plugin_savepoint(true, 2024022502, 'local', 'treestudyplan');
}
@ -552,27 +551,27 @@ function xmldb_local_treestudyplan_upgrade($oldversion) {
}
if ($oldversion < 2024022504) {
// Define table local_treestudyplan_lineuser to be created.
$table = new xmldb_table('local_treestudyplan_lineuser');
// Define table local_treestudyplan_lineuser to be created.
$table = new xmldb_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('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('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('enrolledby', XMLDB_TYPE_INTEGER, '18', null, null, null, null);
// Adding fields to table local_treestudyplan_lineuser.
$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('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('enrolled', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '0');
$table->add_field('enrolledby', XMLDB_TYPE_INTEGER, '18', null, null, null, null);
// Adding keys to table local_treestudyplan_lineuser.
$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('user_id-id', XMLDB_KEY_FOREIGN, ['user_id'], 'user', ['id']);
$table->add_key('enrolledby-id', XMLDB_KEY_FOREIGN, ['enrolledby'], 'user', ['id']);
// Adding keys to table local_treestudyplan_lineuser.
$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('user_id-id', XMLDB_KEY_FOREIGN, ['user_id'], 'user', ['id']);
$table->add_key('enrolledby-id', XMLDB_KEY_FOREIGN, ['enrolledby'], 'user', ['id']);
// Conditionally launch create table for local_treestudyplan_lineuser.
if (!$dbman->table_exists($table)) {
$dbman->create_table($table);
}
// Conditionally launch create table for local_treestudyplan_lineuser.
if (!$dbman->table_exists($table)) {
$dbman->create_table($table);
}
// Treestudyplan savepoint reached.
upgrade_plugin_savepoint(true, 2024022504, 'local', 'treestudyplan');

View File

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

View File

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

View File

@ -64,7 +64,6 @@ $ci = new contextinfo($studyplancontext);
$contextname = $ci->pathstr();
$PAGE->set_pagelayout('base');
//$PAGE->set_context($studyplancontext);
$PAGE->set_title(get_string('cfg_plans', 'local_treestudyplan')." - ".$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 ]));
$PAGE->navbar->add(get_string('cfg_plans', 'local_treestudyplan'));
// Coursecat context
$cat = \core_course_category::get($studyplancontext->instanceid, IGNORE_MISSING, true); // We checck visibility later
// Coursecat context.
$cat = \core_course_category::get($studyplancontext->instanceid, IGNORE_MISSING, true); // We checck visibility later.
} else {
// System context
// System context.
$cat = \core_course_category::top();
}
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)) {
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.
@ -110,8 +119,13 @@ function t($str, $param = null, $plugin = 'local_treestudyplan') {
}
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 class='vue-loader' v-show='false'>
<div class="spinner-border text-primary" role="status">
@ -120,26 +134,27 @@ print $OUTPUT->header();
</div>
<div v-cloak>
<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' : ''">
<b-form-select-option v-if='!(usedcontexts.length)' :value="contextid"
: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"
:class="(ctx.studyplancount > 0) ? 'font-weight-bold' : ''"
><span v-for="(p, i) in ctx.category.path"
><span v-if="i>0"> / </span>{{ p }}</span> <span>({{ ctx.studyplancount }})</span>
</b-form-select-option>
</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>
</div>
<h3 v-else><?php print $contextname; ?></h3>
<h3 v-else>{$contextname}</h3>
<div class="m-buttonbar" style="margin-bottom: 1em;">
<a href='#' v-if='activestudyplan' @click.prevent='closeStudyplan'
><i style='font-size: 150%;' class='fa fa-chevron-left'></i> <?php t('back');?></a>
<span v-if='activestudyplan'><?php t("studyplan_select"); ?></span>&nbsp;
<span v-if='activestudyplan'>{$text->studyplan_select}</span>&nbsp;
<b-form-select v-if='activestudyplan' lazy :text='dropdown_title' :value='activestudyplan.id' @change="selectStudyplan">
<b-form-select-option
v-for='(studyplan, planindex) in studyplans'
@ -181,7 +196,7 @@ print $OUTPUT->header();
<span class="sr-only">Loading...</span>
</div>
<div v-else class='t-studyplan-notselected'>
<p><?php t("studyplan_noneselected"); ?></p>
<p>{$text->studyplan_noneselected}</p>
<b-card-group deck>
<s-studyplan-card
v-for='(studyplan, planindex) in studyplans'
@ -193,7 +208,11 @@ print $OUTPUT->header();
>
<template #title>
<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>
</span>
</template>
@ -203,6 +222,5 @@ print $OUTPUT->header();
</div>
</div>
</div>
<?php
END;
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}"));
print $OUTPUT->header();
?>
print "
<div id='root'>
<div class='vue-loader' v-show='false'>
<div class="spinner-border text-primary" role="status">
<span class="sr-only">Loading...</span>
<div class='spinner-border text-primary' role='status'>
<span class='sr-only'>Loading...</span>
</div>
</div>
<div v-cloak>
<r-report type="invited" :invitekey="invitekey"></r-report>
<r-report type='invited' :invitekey='invitekey'></r-report>
</div>
</div>
<?php
";
print $OUTPUT->footer();
}

View File

@ -137,7 +137,7 @@ $string["infofield_position_below"] = 'Below course results';
$string["infofield_position_header"] = 'Header ';
$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["settingdesc_infofield".$i."_field"] = 'Information field to show';
$string["setting_infofield".$i."_title"] = 'Title for course details from field ('.$i.').';

View File

@ -137,7 +137,7 @@ $string["infofield_position_below"] = 'Onder cursusresultaten';
$string["infofield_position_header"] = 'Header ';
$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["settingdesc_infofield".$i."_field"] = 'Kies cursusveld om informatie uit te tonen';
$string["setting_infofield".$i."_title"] = 'Titel voor gegevens uit cursusveld ('.$i.')';

34
lib.php
View File

@ -56,7 +56,7 @@ function local_treestudyplan_autofill_customusermenuitems() {
"/local/treestudyplan/edit-plan.php" => ["included" => false, "strkey" => "link_editplan"],
];
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"];
}
@ -86,7 +86,7 @@ function local_treestudyplan_autofill_customusermenuitems() {
$link, // Link.
'', // Tooltip.
$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;
}
@ -164,7 +164,7 @@ function local_treestudyplan_extend_navigation(global_navigation $navigation) {
} else {
$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')) {
$node = navigation_node::create(
get_string("link_viewplan", "local_treestudyplan"),
@ -180,7 +180,7 @@ function local_treestudyplan_extend_navigation(global_navigation $navigation) {
} else {
$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')
) {
$node = navigation_node::create(
@ -198,11 +198,11 @@ function local_treestudyplan_extend_navigation(global_navigation $navigation) {
$hideprimaryhrefs[] = "/local/treestudyplan/edit-plan.php";
}
$coachsql = "SELECT COUNT('id') FROM {local_treestudyplan_coach} as c
INNER JOIN {local_treestudyplan} AS t ON c.studyplan_id = t.id
$coachsql = "SELECT COUNT('id') FROM {local_treestudyplan_coach} c
INNER JOIN {local_treestudyplan} t ON c.studyplan_id = t.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())
|| webservicehelper::has_capability_in_any_category('local/treestudyplan:coach')
) && $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) {
$data['permissions'] = get_string('invite_mail_permissions', 'local_treestudyplan');
$data['permissions'] .= "<ul>\n";
if ($invite->allow_details ) {
if ($invite->allow_details) {
$data['permissions'] .= "<li>".get_string('invite_allow_details', 'local_treestudyplan')."</li>\n";
}
if ($invite->allow_calendar) {
@ -449,14 +449,14 @@ function local_treestudyplan_pluginfile(
$itemid = array_shift($args);
// Studyplan icons and description images are not secret, so don't overdo it on access control...
if ( true ) {
// Extract the filename / filepath from the $args array
if (true) {
// Extract the filename / filepath from the $args array.
$filename = array_pop($args); // The last item in the $args array.
if (empty($args)) {
// $args is empty => the path is '/'.
// Var $args is empty => the path is '/'.
$filepath = '/';
} else {
// $args contains the remaining elements of the filepath.
// Var $args contains the remaining elements of the filepath.
$filepath = '/' . implode('/', $args) . '/';
}
@ -468,7 +468,7 @@ function local_treestudyplan_pluginfile(
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.
send_stored_file($file, 24*60*60, 0, $forcedownload, $options);
send_stored_file($file, 24 * 60 * 60, 0, $forcedownload, $options);
} else {
return false;
}
@ -478,13 +478,13 @@ function local_treestudyplan_pluginfile(
// Fetch the itemid from the path.
$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.
if (empty($args)) {
// $args is empty => the path is '/'.
// Var $args is empty => the path is '/'.
$filepath = '/';
} else {
// $args contains the remaining elements of the filepath.
// Var $args contains the remaining elements of the filepath.
$filepath = '/' . implode('/', $args) . '/';
}
@ -496,7 +496,7 @@ function local_treestudyplan_pluginfile(
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.
send_stored_file($file, 24*60*60, 0, $forcedownload, $options);
send_stored_file($file, 24 * 60 * 60, 0, $forcedownload, $options);
} else {
return false;
}

View File

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

View File

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

View File

@ -55,7 +55,7 @@ if ($hassiteconfig) {
true,
));
// Default image for study plans
// Default image for study plans.
$page->add(new admin_setting_configstoredfile('local_treestudyplan/defaulticon',
get_string('setting_defaulticon', 'local_treestudyplan'),
get_string('settingdesc_defaulticon', 'local_treestudyplan'),
@ -89,8 +89,17 @@ if ($hassiteconfig) {
get_string('settingdesc_display_heading', 'local_treestudyplan')
));
$displayfields = ["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")];
$displayfields = [
"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');
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',
get_string('setting_enableplansharing', 'local_treestudyplan'),
get_string('settingdesc_enableplansharing', 'local_treestudyplan'),
true,
));
//get_config("local_treestudyplan", "timelessperiods")
$page->add(new admin_setting_configcheckbox('local_treestudyplan/timelessperiods',
get_string('setting_timelessperiods', 'local_treestudyplan'),
get_string('settingdesc_timelessperiods', 'local_treestudyplan'),
false,
));
//get_config("local_treestudyplan", "limitcourselist")
$page->add(new admin_setting_configcheckbox('local_treestudyplan/limitcourselist',
get_string('setting_limitcourselist', 'local_treestudyplan'),
get_string('settingdesc_limitcourselist', 'local_treestudyplan'),
false,
));
//get_config("local_treestudyplan", "hivizdropslots")
$page->add(new admin_setting_configcheckbox('local_treestudyplan/hivizdropslots',
get_string('setting_hivizdropslots', 'local_treestudyplan'),
get_string('settingdesc_hivizdropslots', 'local_treestudyplan'),
false,
));
//get_config("local_treestudyplan", "toolboxleft")
$page->add(new admin_setting_configcheckbox('local_treestudyplan/toolboxleft',
get_string('setting_toolboxleft', 'local_treestudyplan'),
get_string('settingdesc_toolboxleft', 'local_treestudyplan'),
true,
));
//get_config("local_treestudyplan", "copystudylinesnewpage")
$page->add(new admin_setting_configcheckbox('local_treestudyplan/copystudylinesnewpage',
get_string('setting_copystudylinesnewpage', 'local_treestudyplan'),
get_string('settingdesc_copystudylinesnewpage', 'local_treestudyplan'),
false,
));
//get_config("local_treestudyplan", "continueperiodnumberingnewpage")
$page->add(new admin_setting_configcheckbox('local_treestudyplan/continueperiodnumberingnewpage',
get_string('setting_continueperiodnumberingnewpage', 'local_treestudyplan'),
get_string('settingdesc_continueperiodnumberingnewpage', 'local_treestudyplan'),
@ -156,7 +159,6 @@ if ($hassiteconfig) {
));
if (premium::enabled()) {
//get_config("local_treestudyplan", "enablecoach")
$page->add(new admin_setting_configcheckbox('local_treestudyplan/enablecoach',
get_string('setting_enablecoach', 'local_treestudyplan'),
get_string('settingdesc_enablecoach', 'local_treestudyplan'),
@ -186,7 +188,7 @@ if ($hassiteconfig) {
"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',
get_string('setting_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('settingdesc_competency_detailfield', 'local_treestudyplan'),
"shortname",
["" => get_string("none", "core", ),
["" => get_string("none", "core", ),
"shortname" => get_string("name", "core", ),
"idnumber" => get_string("idnumber", "core", ),
"description" => get_string("description", "core", )]
@ -410,7 +412,7 @@ if ($hassiteconfig) {
));
if ($CFG->debugdeveloper) {
// show decrypted key data
// Show decrypted key data.
$pagepremium->add(new admin_setting_description('local_treestudyplan/premium_debug',
get_string('setting_premium_debug', 'local_treestudyplan'),
premium::debuginfo() . "<br>&nbsp;<br>" // Add empty row at end.
@ -420,5 +422,4 @@ if ($hassiteconfig) {
// Add settings page2 to the admin settings category.
$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();
$PAGE->set_pagelayout('base');
//$PAGE->set_context($studyplancontext);
$PAGE->set_title(get_string('view_plan', 'local_treestudyplan')." - ".$contextname);
$PAGE->set_heading(get_string('view_plan', 'local_treestudyplan')." - ".$contextname);
@ -73,19 +72,29 @@ if ($studyplancontext->id > 1) {
navigation_node::override_active_url(new moodle_url('/course/index.php', ['categoryid' => $categoryid ]));
$PAGE->navbar->add(get_string('view_plan', 'local_treestudyplan'));
// Coursecat context
$cat = \core_course_category::get($studyplancontext->instanceid, IGNORE_MISSING, true); // We checck visibility later
// Coursecat context.
$cat = \core_course_category::get($studyplancontext->instanceid, IGNORE_MISSING, true); // We checck visibility later.
} else {
// System context
// System context.
$cat = \core_course_category::top();
}
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)) {
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.
@ -108,7 +117,15 @@ function t($str, $param = null, $plugin = 'local_treestudyplan') {
}
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 class='vue-loader' v-show='false'>
<div class="spinner-border text-primary" role="status">
@ -117,25 +134,26 @@ print $OUTPUT->header();
</div>
<div v-cloak>
<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' : ''">
<b-form-select-option v-if='!(usedcontexts.length)' :value="contextid"
: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"
:class="(ctx.studyplancount > 0) ? 'font-weight-bold' : ''"
><span v-for="(p, i) in ctx.category.path"><span v-if="i>0"> / </span>{{ p }}</span>
<span>({{ ctx.studyplancount }})</span></b-form-select-option>
</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>
</div>
<h3 v-else><?php print $contextname; ?></h3>
<h3 v-else>{$contextname}</h3>
<div class="m-buttonbar" style="margin-bottom: 1em;">
<template v-if="displayedstudyplan">
<a href='#' @click.prevent='closeStudyplan'
><i style='font-size: 150%;' class='fa fa-chevron-left'></i> <?php t('back');?></a>
<span><?php t("studyplan_select"); ?></span>&nbsp;
><i style='font-size: 150%;' class='fa fa-chevron-left'></i> {$text->back}</a>
<span>{$text->studyplan_select}</span>&nbsp;
<b-form-select lazy :text='dropdown_title' :value='displayedstudyplan.id'>
<b-form-select-option
v-for='(studyplan, planindex) in studyplans'
@ -164,7 +182,7 @@ print $OUTPUT->header();
variant="primary"
>
<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>
</div>
</template>
@ -172,7 +190,7 @@ print $OUTPUT->header();
<div class='t-studyplan-container'>
<h2 v-if='displayedstudyplan&& selectedstudent'
>{{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'
v-model='displayedstudyplan' :teachermode='!selectedstudent'
></r-studyplan>
@ -180,7 +198,7 @@ print $OUTPUT->header();
<span class="sr-only">Loading...</span>
</div>
<div v-else class='t-studyplan-notselected'>
<p><?php t("studyplan_noneselected"); ?></p>
<p>{$text->studyplan_noneselected}</p>
<b-card-group deck>
<s-studyplan-card
v-for='(studyplan, planindex) in studyplans'
@ -194,6 +212,6 @@ print $OUTPUT->header();
</div>
</div>
</div>
<?php
END;
print $OUTPUT->footer();