diff --git a/classes/aggregator.php b/classes/aggregator.php index ed7e34f..d1cd31e 100644 --- a/classes/aggregator.php +++ b/classes/aggregator.php @@ -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, ""); } } diff --git a/classes/associationservice.php b/classes/associationservice.php index c9fa46e..7ed9568 100644 --- a/classes/associationservice.php +++ b/classes/associationservice.php @@ -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, diff --git a/classes/badgeinfo.php b/classes/badgeinfo.php index ff728cf..190e8e8 100644 --- a/classes/badgeinfo.php +++ b/classes/badgeinfo.php @@ -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"; diff --git a/classes/cascadecohortsync.php b/classes/cascadecohortsync.php index 998de4e..1b5d09e 100644 --- a/classes/cascadecohortsync.php +++ b/classes/cascadecohortsync.php @@ -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)) { diff --git a/classes/cascadeusersync.php b/classes/cascadeusersync.php index 8a373d7..47a157b 100644 --- a/classes/cascadeusersync.php +++ b/classes/cascadeusersync.php @@ -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); } } diff --git a/classes/corecompletioninfo.php b/classes/corecompletioninfo.php index b7b2b80..69679ee 100644 --- a/classes/corecompletioninfo.php +++ b/classes/corecompletioninfo.php @@ -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; } diff --git a/classes/coursecompetencyinfo.php b/classes/coursecompetencyinfo.php index 2699c67..4ba74d6 100644 --- a/classes/coursecompetencyinfo.php +++ b/classes/coursecompetencyinfo.php @@ -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"]) diff --git a/classes/courseinfo.php b/classes/courseinfo.php index df65592..2d75ab6 100644 --- a/classes/courseinfo.php +++ b/classes/courseinfo.php @@ -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. diff --git a/classes/courseservice.php b/classes/courseservice.php index 3da90db..53e1efa 100644 --- a/classes/courseservice.php +++ b/classes/courseservice.php @@ -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), - ]); } diff --git a/classes/debug.php b/classes/debug.php index 781fab3..927f843 100644 --- a/classes/debug.php +++ b/classes/debug.php @@ -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,8 +65,23 @@ class debug { $f = fopen($filename, "a+"); fwrite($f, $text."\n"); } catch (\Exception $x) { + $f = null; } finally { fclose($f); } } -} \ No newline at end of file + /** + * 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); + } + } +} diff --git a/classes/form/formbase.php b/classes/form/formbase.php index 460d939..44517fe 100644 --- a/classes/form/formbase.php +++ b/classes/form/formbase.php @@ -1,8 +1,31 @@ . +/** + * 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); } } - -} \ No newline at end of file +} diff --git a/classes/form/studyplan_editform.php b/classes/form/studyplan_editform.php index 3035001..8b030c4 100644 --- a/classes/form/studyplan_editform.php +++ b/classes/form/studyplan_editform.php @@ -1,4 +1,24 @@ . +/** + * 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; } - -} \ No newline at end of file +} diff --git a/classes/form/studyplan_fromtemplateform.php b/classes/form/studyplan_fromtemplateform.php index 6140e76..b7830e8 100644 --- a/classes/form/studyplan_fromtemplateform.php +++ b/classes/form/studyplan_fromtemplateform.php @@ -1,12 +1,30 @@ . +/** + * 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; } } - -} \ No newline at end of file +} diff --git a/classes/form/studyplanpage_editform.php b/classes/form/studyplanpage_editform.php index 689e519..34450c0 100644 --- a/classes/form/studyplanpage_editform.php +++ b/classes/form/studyplanpage_editform.php @@ -1,15 +1,32 @@ . +/** + * 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()); } - -} \ No newline at end of file +} diff --git a/classes/form/text_integer.php b/classes/form/text_integer.php index 9b3598f..761fb6e 100644 --- a/classes/form/text_integer.php +++ b/classes/form/text_integer.php @@ -1,4 +1,25 @@ . +/** + * 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`. @@ -74,4 +101,4 @@ class text_integer extends MoodleQuickForm_text { 'local_treestudyplan\form\text_integer' ); } -} \ No newline at end of file +} diff --git a/classes/gradeinfo.php b/classes/gradeinfo.php index 311b548..434af20 100644 --- a/classes/gradeinfo.php +++ b/classes/gradeinfo.php @@ -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 { diff --git a/classes/gradingscanner.php b/classes/gradingscanner.php index 63a4598..67315d7 100644 --- a/classes/gradingscanner.php +++ b/classes/gradingscanner.php @@ -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]); diff --git a/classes/local/aggregators/bistate_aggregator.php b/classes/local/aggregators/bistate_aggregator.php index 88d57ef..835bb85 100644 --- a/classes/local/aggregators/bistate_aggregator.php +++ b/classes/local/aggregators/bistate_aggregator.php @@ -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]); diff --git a/classes/local/aggregators/competency_aggregator.php b/classes/local/aggregators/competency_aggregator.php index 1e7f01c..9454801 100644 --- a/classes/local/aggregators/competency_aggregator.php +++ b/classes/local/aggregators/competency_aggregator.php @@ -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; diff --git a/classes/local/aggregators/core_aggregator.php b/classes/local/aggregators/core_aggregator.php index 3d949bf..1ece1f9 100644 --- a/classes/local/aggregators/core_aggregator.php +++ b/classes/local/aggregators/core_aggregator.php @@ -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; diff --git a/classes/local/aggregators/tristate_aggregator.php b/classes/local/aggregators/tristate_aggregator.php index c462b3a..5e04005 100644 --- a/classes/local/aggregators/tristate_aggregator.php +++ b/classes/local/aggregators/tristate_aggregator.php @@ -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')); } /** diff --git a/classes/local/gradegenerator.php b/classes/local/gradegenerator.php index 79f0e9b..d0d8d1d 100644 --- a/classes/local/gradegenerator.php +++ b/classes/local/gradegenerator.php @@ -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, ]; } diff --git a/classes/local/helpers/webservicehelper.php b/classes/local/helpers/webservicehelper.php index 87eae8d..8fdf072 100644 --- a/classes/local/helpers/webservicehelper.php +++ b/classes/local/helpers/webservicehelper.php @@ -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); } } diff --git a/classes/local/randomimage.php b/classes/local/randomimage.php index 8e7384b..4736107 100644 --- a/classes/local/randomimage.php +++ b/classes/local/randomimage.php @@ -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); } } - -} \ No newline at end of file +} diff --git a/classes/period.php b/classes/period.php index f503aea..e341a75 100644 --- a/classes/period.php +++ b/classes/period.php @@ -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) { diff --git a/classes/premium.php b/classes/premium.php index 6c3f5ea..ebe00a2 100644 --- a/classes/premium.php +++ b/classes/premium.php @@ -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 = "
";
         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.="
"; + $s .= ""; 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; @@ -439,4 +470,4 @@ Klc5I28bGbvxIV5pnL6ZSjHEDp2WreM8HB0XFJwU+Q== } } -} \ No newline at end of file +} diff --git a/classes/reportservice.php b/classes/reportservice.php index d4d73a7..786b2fb 100644 --- a/classes/reportservice.php +++ b/classes/reportservice.php @@ -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'), diff --git a/classes/studentstudyplanservice.php b/classes/studentstudyplanservice.php index 054bb4d..c84fa5f 100644 --- a/classes/studentstudyplanservice.php +++ b/classes/studentstudyplanservice.php @@ -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); diff --git a/classes/studyitem.php b/classes/studyitem.php index 985eea5..629d52b 100644 --- a/classes/studyitem.php +++ b/classes/studyitem.php @@ -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; } diff --git a/classes/studyitemconnection.php b/classes/studyitemconnection.php index 1c7df50..4d13f9d 100644 --- a/classes/studyitemconnection.php +++ b/classes/studyitemconnection.php @@ -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']); } } diff --git a/classes/studyline.php b/classes/studyline.php index f504666..9db0896 100644 --- a/classes/studyline.php +++ b/classes/studyline.php @@ -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); diff --git a/classes/studyplan.php b/classes/studyplan.php index bb6e881..925360d 100644 --- a/classes/studyplan.php +++ b/classes/studyplan.php @@ -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; diff --git a/classes/studyplanpage.php b/classes/studyplanpage.php index 1de3e5f..9b700cf 100644 --- a/classes/studyplanpage.php +++ b/classes/studyplanpage.php @@ -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. diff --git a/classes/studyplanservice.php b/classes/studyplanservice.php index 081d798..7c6c19e 100644 --- a/classes/studyplanservice.php +++ b/classes/studyplanservice.php @@ -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( [ - ] ); } diff --git a/classes/success.php b/classes/success.php index 9581d96..9f2c72b 100644 --- a/classes/success.php +++ b/classes/success.php @@ -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); } diff --git a/classes/task/autocohortsync.php b/classes/task/autocohortsync.php index eefb49d..adc342f 100644 --- a/classes/task/autocohortsync.php +++ b/classes/task/autocohortsync.php @@ -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. } } diff --git a/classes/teachingfinder.php b/classes/teachingfinder.php index bcf1329..8e0fe34 100644 --- a/classes/teachingfinder.php +++ b/classes/teachingfinder.php @@ -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]); } diff --git a/classes/utilityservice.php b/classes/utilityservice.php index 8fb5b49..1ed463c 100644 --- a/classes/utilityservice.php +++ b/classes/utilityservice.php @@ -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"), ]; } diff --git a/cli/generate_badges.php b/cli/generate_badges.php index 1f0f65f..501376d 100644 --- a/cli/generate_badges.php +++ b/cli/generate_badges.php @@ -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'); diff --git a/cli/prime_students.php b/cli/prime_students.php index 98b0604..bcf2749 100644 --- a/cli/prime_students.php +++ b/cli/prime_students.php @@ -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', diff --git a/cli/randomize_grades.php b/cli/randomize_grades.php index 1e2f07b..85ac580 100644 --- a/cli/randomize_grades.php +++ b/cli/randomize_grades.php @@ -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"]) { diff --git a/coach.php b/coach.php index 603cdcb..d7d5bd8 100644 --- a/coach.php +++ b/coach.php @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . /** - * 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 <<
@@ -73,7 +79,7 @@ print $OUTPUT->header();

{{selectedstudent.firstname}} {{selectedstudent.lastname}} - {{displayedstudyplan.name}}

-

- {{displayedstudyplan.name}}

+

{$text->showoverview} - {{displayedstudyplan.name}}