Ir a la última revisión | Autoría | Comparar con el anterior | Ultima modificación | Ver Log |
<?php// This file is part of Moodle - http://moodle.org///// Moodle is free software: you can redistribute it and/or modify// it under the terms of the GNU General Public License as published by// the Free Software Foundation, either version 3 of the License, or// (at your option) any later version.//// Moodle is distributed in the hope that it will be useful,// but WITHOUT ANY WARRANTY; without even the implied warranty of// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the// GNU General Public License for more details.//// You should have received a copy of the GNU General Public License// along with Moodle. If not, see <http://www.gnu.org/licenses/>./*** Class for loading/storing competency frameworks from the DB.** @package core_competency* @copyright 2015 Damyon Wiese* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later*/namespace core_competency;defined('MOODLE_INTERNAL') || die();use stdClass;use cm_info;use context;use context_helper;use context_system;use context_course;use context_module;use context_user;use coding_exception;use require_login_exception;use moodle_exception;use moodle_url;use required_capability_exception;/*** Class for doing things with competency frameworks.** @copyright 2015 Damyon Wiese* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later*/class api {/** @var boolean Allow api functions even if competencies are not enabled for the site. */private static $skipenabled = false;/*** Returns whether competencies are enabled.** This method should never do more than checking the config setting, the reason* being that some other code could be checking the config value directly* to avoid having to load this entire file into memory.** @return boolean True when enabled.*/public static function is_enabled() {return self::$skipenabled || get_config('core_competency', 'enabled');}/*** When competencies used to be enabled, we can show the text but do not include links.** @return boolean True means show links.*/public static function show_links() {return isloggedin() && !isguestuser() && get_config('core_competency', 'enabled');}/*** Allow calls to competency api functions even if competencies are not currently enabled.*/public static function skip_enabled() {self::$skipenabled = true;}/*** Restore the checking that competencies are enabled with any api function.*/public static function check_enabled() {self::$skipenabled = false;}/*** Throws an exception if competencies are not enabled.** @return void* @throws moodle_exception*/public static function require_enabled() {if (!static::is_enabled()) {throw new moodle_exception('competenciesarenotenabled', 'core_competency');}}/*** Checks whether a scale is used anywhere in the plugin.** This public API has two exceptions:* - It MUST NOT perform any capability checks.* - It MUST ignore whether competencies are enabled or not ({@link self::is_enabled()}).** @param int $scaleid The scale ID.* @return bool*/public static function is_scale_used_anywhere($scaleid) {global $DB;$sql = "SELECT s.idFROM {scale} sLEFT JOIN {" . competency_framework::TABLE ."} fON f.scaleid = :scaleid1LEFT JOIN {" . competency::TABLE ."} cON c.scaleid = :scaleid2WHERE f.id IS NOT NULLOR c.id IS NOT NULL";return $DB->record_exists_sql($sql, ['scaleid1' => $scaleid, 'scaleid2' => $scaleid]);}/*** Validate if current user have acces to the course_module if hidden.** @param mixed $cmmixed The cm_info class, course module record or its ID.* @param bool $throwexception Throw an exception or not.* @return bool*/protected static function validate_course_module($cmmixed, $throwexception = true) {$cm = $cmmixed;if (!is_object($cm)) {$cmrecord = get_coursemodule_from_id(null, $cmmixed);$modinfo = get_fast_modinfo($cmrecord->course);$cm = $modinfo->get_cm($cmmixed);} else if (!$cm instanceof cm_info) {// Assume we got a course module record.$modinfo = get_fast_modinfo($cm->course);$cm = $modinfo->get_cm($cm->id);}if (!$cm->uservisible) {if ($throwexception) {throw new require_login_exception('Course module is hidden');} else {return false;}}return true;}/*** Validate if current user have acces to the course if hidden.** @param mixed $courseorid The course or it ID.* @param bool $throwexception Throw an exception or not.* @return bool*/protected static function validate_course($courseorid, $throwexception = true) {$course = $courseorid;if (!is_object($course)) {$course = get_course($course);}$coursecontext = context_course::instance($course->id);if (!$course->visible and !has_capability('moodle/course:viewhiddencourses', $coursecontext)) {if ($throwexception) {throw new require_login_exception('Course is hidden');} else {return false;}}return true;}/*** Create a competency from a record containing all the data for the class.** Requires moodle/competency:competencymanage capability at the system context.** @param stdClass $record Record containing all the data for an instance of the class.* @return competency*/public static function create_competency(stdClass $record) {static::require_enabled();$competency = new competency(0, $record);// First we do a permissions check.require_capability('moodle/competency:competencymanage', $competency->get_context());// Reset the sortorder, use reorder instead.$competency->set('sortorder', 0);$competency->create();\core\event\competency_created::create_from_competency($competency)->trigger();// Reset the rule of the parent.$parent = $competency->get_parent();if ($parent) {$parent->reset_rule();$parent->update();}return $competency;}/*** Delete a competency by id.** Requires moodle/competency:competencymanage capability at the system context.** @param int $id The record to delete. This will delete alot of related data - you better be sure.* @return boolean*/public static function delete_competency($id) {global $DB;static::require_enabled();$competency = new competency($id);// First we do a permissions check.require_capability('moodle/competency:competencymanage', $competency->get_context());$events = array();$competencyids = array(intval($competency->get('id')));$contextid = $competency->get_context()->id;$competencyids = array_merge(competency::get_descendants_ids($competency), $competencyids);if (!competency::can_all_be_deleted($competencyids)) {return false;}$transaction = $DB->start_delegated_transaction();try {// Reset the rule of the parent.$parent = $competency->get_parent();if ($parent) {$parent->reset_rule();$parent->update();}// Delete the competency separately so the after_delete event can be triggered.$competency->delete();// Delete the competencies.competency::delete_multiple($competencyids);// Delete the competencies relation.related_competency::delete_multiple_relations($competencyids);// Delete competency evidences.user_evidence_competency::delete_by_competencyids($competencyids);// Register the competencies deleted events.$events = \core\event\competency_deleted::create_multiple_from_competencyids($competencyids, $contextid);} catch (\Exception $e) {$transaction->rollback($e);}$transaction->allow_commit();// Trigger events.foreach ($events as $event) {$event->trigger();}return true;}/*** Reorder this competency.** Requires moodle/competency:competencymanage capability at the system context.** @param int $id The id of the competency to move.* @return boolean*/public static function move_down_competency($id) {static::require_enabled();$current = new competency($id);// First we do a permissions check.require_capability('moodle/competency:competencymanage', $current->get_context());$max = self::count_competencies(array('parentid' => $current->get('parentid'),'competencyframeworkid' => $current->get('competencyframeworkid')));if ($max > 0) {$max--;}$sortorder = $current->get('sortorder');if ($sortorder >= $max) {return false;}$sortorder = $sortorder + 1;$current->set('sortorder', $sortorder);$filters = array('parentid' => $current->get('parentid'),'competencyframeworkid' => $current->get('competencyframeworkid'),'sortorder' => $sortorder);$children = self::list_competencies($filters, 'id');foreach ($children as $needtoswap) {$needtoswap->set('sortorder', $sortorder - 1);$needtoswap->update();}// OK - all set.$result = $current->update();return $result;}/*** Reorder this competency.** Requires moodle/competency:competencymanage capability at the system context.** @param int $id The id of the competency to move.* @return boolean*/public static function move_up_competency($id) {static::require_enabled();$current = new competency($id);// First we do a permissions check.require_capability('moodle/competency:competencymanage', $current->get_context());$sortorder = $current->get('sortorder');if ($sortorder == 0) {return false;}$sortorder = $sortorder - 1;$current->set('sortorder', $sortorder);$filters = array('parentid' => $current->get('parentid'),'competencyframeworkid' => $current->get('competencyframeworkid'),'sortorder' => $sortorder);$children = self::list_competencies($filters, 'id');foreach ($children as $needtoswap) {$needtoswap->set('sortorder', $sortorder + 1);$needtoswap->update();}// OK - all set.$result = $current->update();return $result;}/*** Move this competency so it sits in a new parent.** Requires moodle/competency:competencymanage capability at the system context.** @param int $id The id of the competency to move.* @param int $newparentid The new parent id for the competency.* @return boolean*/public static function set_parent_competency($id, $newparentid) {global $DB;static::require_enabled();$current = new competency($id);// First we do a permissions check.require_capability('moodle/competency:competencymanage', $current->get_context());if ($id == $newparentid) {throw new coding_exception('Can not set a competency as a parent of itself.');} if ($newparentid == $current->get('parentid')) {throw new coding_exception('Can not move a competency to the same location.');}// Some great variable assignment right here.$currentparent = $current->get_parent();$parent = !empty($newparentid) ? new competency($newparentid) : null;$parentpath = !empty($parent) ? $parent->get('path') : '/0/';// We're going to change quite a few things.$transaction = $DB->start_delegated_transaction();// If we are moving a node to a child of itself:// - promote all the child nodes by one level.// - remove the rule on self.// - re-read the parent.$newparents = explode('/', $parentpath);if (in_array($current->get('id'), $newparents)) {$children = competency::get_records(array('parentid' => $current->get('id')), 'id');foreach ($children as $child) {$child->set('parentid', $current->get('parentid'));$child->update();}// Reset the rule on self as our children have changed.$current->reset_rule();// The destination parent is one of our descendants, we need to re-fetch its values (path, parentid).$parent->read();}// Reset the rules of initial parent and destination.if (!empty($currentparent)) {$currentparent->reset_rule();$currentparent->update();}if (!empty($parent)) {$parent->reset_rule();$parent->update();}// Do the actual move.$current->set('parentid', $newparentid);$result = $current->update();// All right, let's commit this.$transaction->allow_commit();return $result;}/*** Update the details for a competency.** Requires moodle/competency:competencymanage capability at the system context.** @param stdClass $record The new details for the competency.* Note - must contain an id that points to the competency to update.** @return boolean*/public static function update_competency($record) {static::require_enabled();$competency = new competency($record->id);// First we do a permissions check.require_capability('moodle/competency:competencymanage', $competency->get_context());// Some things should not be changed in an update - they should use a more specific method.$record->sortorder = $competency->get('sortorder');$record->parentid = $competency->get('parentid');$record->competencyframeworkid = $competency->get('competencyframeworkid');$competency->from_record($record);require_capability('moodle/competency:competencymanage', $competency->get_context());// OK - all set.$result = $competency->update();// Trigger the update event.\core\event\competency_updated::create_from_competency($competency)->trigger();return $result;}/*** Read a the details for a single competency and return a record.** Requires moodle/competency:competencyview capability at the system context.** @param int $id The id of the competency to read.* @param bool $includerelated Include related tags or not.* @return competency*/public static function read_competency($id, $includerelated = false) {static::require_enabled();$competency = new competency($id);// First we do a permissions check.$context = $competency->get_context();if (!has_any_capability(array('moodle/competency:competencyview', 'moodle/competency:competencymanage'), $context)) {throw new required_capability_exception($context, 'moodle/competency:competencyview', 'nopermissions', '');}// OK - all set.if ($includerelated) {$relatedcompetency = new related_competency();if ($related = $relatedcompetency->list_relations($id)) {$competency->relatedcompetencies = $related;}}return $competency;}/*** Perform a text search based and return all results and their parents.** Requires moodle/competency:competencyview capability at the framework context.** @param string $textsearch A string to search for.* @param int $competencyframeworkid The id of the framework to limit the search.* @return array of competencies*/public static function search_competencies($textsearch, $competencyframeworkid) {static::require_enabled();$framework = new competency_framework($competencyframeworkid);// First we do a permissions check.$context = $framework->get_context();if (!has_any_capability(array('moodle/competency:competencyview', 'moodle/competency:competencymanage'), $context)) {throw new required_capability_exception($context, 'moodle/competency:competencyview', 'nopermissions', '');}// OK - all set.$competencies = competency::search($textsearch, $competencyframeworkid);return $competencies;}/*** Perform a search based on the provided filters and return a paginated list of records.** Requires moodle/competency:competencyview capability at some context.** @param array $filters A list of filters to apply to the list.* @param string $sort The column to sort on* @param string $order ('ASC' or 'DESC')* @param int $skip Number of records to skip (pagination)* @param int $limit Max of records to return (pagination)* @return array of competencies*/public static function list_competencies($filters, $sort = '', $order = 'ASC', $skip = 0, $limit = 0) {static::require_enabled();if (!isset($filters['competencyframeworkid'])) {$context = context_system::instance();} else {$framework = new competency_framework($filters['competencyframeworkid']);$context = $framework->get_context();}// First we do a permissions check.if (!has_any_capability(array('moodle/competency:competencyview', 'moodle/competency:competencymanage'), $context)) {throw new required_capability_exception($context, 'moodle/competency:competencyview', 'nopermissions', '');}// OK - all set.return competency::get_records($filters, $sort, $order, $skip, $limit);}/*** Perform a search based on the provided filters and return a paginated list of records.** Requires moodle/competency:competencyview capability at some context.** @param array $filters A list of filters to apply to the list.* @return int*/public static function count_competencies($filters) {static::require_enabled();if (!isset($filters['competencyframeworkid'])) {$context = context_system::instance();} else {$framework = new competency_framework($filters['competencyframeworkid']);$context = $framework->get_context();}// First we do a permissions check.if (!has_any_capability(array('moodle/competency:competencyview', 'moodle/competency:competencymanage'), $context)) {throw new required_capability_exception($context, 'moodle/competency:competencyview', 'nopermissions', '');}// OK - all set.return competency::count_records($filters);}/*** Create a competency framework from a record containing all the data for the class.** Requires moodle/competency:competencymanage capability at the system context.** @param stdClass $record Record containing all the data for an instance of the class.* @return competency_framework*/public static function create_framework(stdClass $record) {static::require_enabled();$framework = new competency_framework(0, $record);require_capability('moodle/competency:competencymanage', $framework->get_context());// Account for different formats of taxonomies.if (isset($record->taxonomies)) {$framework->set('taxonomies', $record->taxonomies);}$framework = $framework->create();// Trigger a competency framework created event.\core\event\competency_framework_created::create_from_framework($framework)->trigger();return $framework;}/*** Duplicate a competency framework by id.** Requires moodle/competency:competencymanage capability at the system context.** @param int $id The record to duplicate. All competencies associated and related will be duplicated.* @return competency_framework the framework duplicated*/public static function duplicate_framework($id) {global $DB;static::require_enabled();$framework = new competency_framework($id);require_capability('moodle/competency:competencymanage', $framework->get_context());// Starting transaction.$transaction = $DB->start_delegated_transaction();try {// Get a uniq idnumber based on the origin framework.$idnumber = competency_framework::get_unused_idnumber($framework->get('idnumber'));$framework->set('idnumber', $idnumber);// Adding the suffix copy to the shortname.$framework->set('shortname', get_string('duplicateditemname', 'core_competency', $framework->get('shortname')));$framework->set('id', 0);$framework = $framework->create();// Array that match the old competencies ids with the new one to use when copying related competencies.$frameworkcompetency = competency::get_framework_tree($id);$matchids = self::duplicate_competency_tree($framework->get('id'), $frameworkcompetency, 0, 0);// Copy the related competencies.$relcomps = related_competency::get_multiple_relations(array_keys($matchids));foreach ($relcomps as $relcomp) {$compid = $relcomp->get('competencyid');$relcompid = $relcomp->get('relatedcompetencyid');if (isset($matchids[$compid]) && isset($matchids[$relcompid])) {$newcompid = $matchids[$compid]->get('id');$newrelcompid = $matchids[$relcompid]->get('id');if ($newcompid < $newrelcompid) {$relcomp->set('competencyid', $newcompid);$relcomp->set('relatedcompetencyid', $newrelcompid);} else {$relcomp->set('competencyid', $newrelcompid);$relcomp->set('relatedcompetencyid', $newcompid);}$relcomp->set('id', 0);$relcomp->create();} else {// Debugging message when there is no match found.debugging('related competency id not found');}}// Setting rules on duplicated competencies.self::migrate_competency_tree_rules($frameworkcompetency, $matchids);$transaction->allow_commit();} catch (\Exception $e) {$transaction->rollback($e);}// Trigger a competency framework created event.\core\event\competency_framework_created::create_from_framework($framework)->trigger();return $framework;}/*** Delete a competency framework by id.** Requires moodle/competency:competencymanage capability at the system context.** @param int $id The record to delete. This will delete alot of related data - you better be sure.* @return boolean*/public static function delete_framework($id) {global $DB;static::require_enabled();$framework = new competency_framework($id);require_capability('moodle/competency:competencymanage', $framework->get_context());$events = array();$competenciesid = competency::get_ids_by_frameworkid($id);$contextid = $framework->get('contextid');if (!competency::can_all_be_deleted($competenciesid)) {return false;}$transaction = $DB->start_delegated_transaction();try {if (!empty($competenciesid)) {// Delete competencies.competency::delete_by_frameworkid($id);// Delete the related competencies.related_competency::delete_multiple_relations($competenciesid);// Delete the evidences for competencies.user_evidence_competency::delete_by_competencyids($competenciesid);}// Create a competency framework deleted event.$event = \core\event\competency_framework_deleted::create_from_framework($framework);$result = $framework->delete();// Register the deleted events competencies.$events = \core\event\competency_deleted::create_multiple_from_competencyids($competenciesid, $contextid);} catch (\Exception $e) {$transaction->rollback($e);}// Commit the transaction.$transaction->allow_commit();// If all operations are successfull then trigger the delete event.$event->trigger();// Trigger deleted event competencies.foreach ($events as $event) {$event->trigger();}return $result;}/*** Update the details for a competency framework.** Requires moodle/competency:competencymanage capability at the system context.** @param stdClass $record The new details for the framework. Note - must contain an id that points to the framework to update.* @return boolean*/public static function update_framework($record) {static::require_enabled();$framework = new competency_framework($record->id);// Check the permissions before update.require_capability('moodle/competency:competencymanage', $framework->get_context());// Account for different formats of taxonomies.$framework->from_record($record);if (isset($record->taxonomies)) {$framework->set('taxonomies', $record->taxonomies);}// Trigger a competency framework updated event.\core\event\competency_framework_updated::create_from_framework($framework)->trigger();return $framework->update();}/*** Read a the details for a single competency framework and return a record.** Requires moodle/competency:competencyview capability at the system context.** @param int $id The id of the framework to read.* @return competency_framework*/public static function read_framework($id) {static::require_enabled();$framework = new competency_framework($id);if (!has_any_capability(array('moodle/competency:competencyview', 'moodle/competency:competencymanage'),$framework->get_context())) {throw new required_capability_exception($framework->get_context(), 'moodle/competency:competencyview','nopermissions', '');}return $framework;}/*** Logg the competency framework viewed event.** @param competency_framework|int $frameworkorid The competency_framework object or competency framework id* @return bool*/public static function competency_framework_viewed($frameworkorid) {static::require_enabled();$framework = $frameworkorid;if (!is_object($framework)) {$framework = new competency_framework($framework);}if (!has_any_capability(array('moodle/competency:competencyview', 'moodle/competency:competencymanage'),$framework->get_context())) {throw new required_capability_exception($framework->get_context(), 'moodle/competency:competencyview','nopermissions', '');}\core\event\competency_framework_viewed::create_from_framework($framework)->trigger();return true;}/*** Logg the competency viewed event.** @param competency|int $competencyorid The competency object or competency id* @return bool*/public static function competency_viewed($competencyorid) {static::require_enabled();$competency = $competencyorid;if (!is_object($competency)) {$competency = new competency($competency);}if (!has_any_capability(array('moodle/competency:competencyview', 'moodle/competency:competencymanage'),$competency->get_context())) {throw new required_capability_exception($competency->get_context(), 'moodle/competency:competencyview','nopermissions', '');}\core\event\competency_viewed::create_from_competency($competency)->trigger();return true;}/*** Perform a search based on the provided filters and return a paginated list of records.** Requires moodle/competency:competencyview capability at the system context.** @param string $sort The column to sort on* @param string $order ('ASC' or 'DESC')* @param int $skip Number of records to skip (pagination)* @param int $limit Max of records to return (pagination)* @param context $context The parent context of the frameworks.* @param string $includes Defines what other contexts to fetch frameworks from.* Accepted values are:* - children: All descendants* - parents: All parents, grand parents, etc...* - self: Context passed only.* @param bool $onlyvisible If true return only visible frameworks* @param string $query A string to use to filter down the frameworks.* @return array of competency_framework*/public static function list_frameworks($sort, $order, $skip, $limit, $context, $includes = 'children',$onlyvisible = false, $query = '') {global $DB;static::require_enabled();// Get all the relevant contexts.$contexts = self::get_related_contexts($context, $includes,array('moodle/competency:competencyview', 'moodle/competency:competencymanage'));if (empty($contexts)) {throw new required_capability_exception($context, 'moodle/competency:competencyview', 'nopermissions', '');}// OK - all set.list($insql, $inparams) = $DB->get_in_or_equal(array_keys($contexts), SQL_PARAMS_NAMED);$select = "contextid $insql";if ($onlyvisible) {$select .= " AND visible = :visible";$inparams['visible'] = 1;}if (!empty($query) || is_numeric($query)) {$sqlnamelike = $DB->sql_like('shortname', ':namelike', false);$sqlidnlike = $DB->sql_like('idnumber', ':idnlike', false);$select .= " AND ($sqlnamelike OR $sqlidnlike) ";$inparams['namelike'] = '%' . $DB->sql_like_escape($query) . '%';$inparams['idnlike'] = '%' . $DB->sql_like_escape($query) . '%';}return competency_framework::get_records_select($select, $inparams, $sort . ' ' . $order, '*', $skip, $limit);}/*** Perform a search based on the provided filters and return a paginated list of records.** Requires moodle/competency:competencyview capability at the system context.** @param context $context The parent context of the frameworks.* @param string $includes Defines what other contexts to fetch frameworks from.* Accepted values are:* - children: All descendants* - parents: All parents, grand parents, etc...* - self: Context passed only.* @return int*/public static function count_frameworks($context, $includes) {global $DB;static::require_enabled();// Get all the relevant contexts.$contexts = self::get_related_contexts($context, $includes,array('moodle/competency:competencyview', 'moodle/competency:competencymanage'));if (empty($contexts)) {throw new required_capability_exception($context, 'moodle/competency:competencyview', 'nopermissions', '');}// OK - all set.list($insql, $inparams) = $DB->get_in_or_equal(array_keys($contexts), SQL_PARAMS_NAMED);return competency_framework::count_records_select("contextid $insql", $inparams);}/*** Fetches all the relevant contexts.** Note: This currently only supports system, category and user contexts. However user contexts* behave a bit differently and will fallback on the system context. This is what makes the most* sense because a user context does not have descendants, and only has system as a parent.** @param context $context The context to start from.* @param string $includes Defines what other contexts to find.* Accepted values are:* - children: All descendants* - parents: All parents, grand parents, etc...* - self: Context passed only.* @param array $hasanycapability Array of capabilities passed to {@link has_any_capability()} in each context.* @return context[] An array of contexts where keys are context IDs.*/public static function get_related_contexts($context, $includes, array $hasanycapability = null) {global $DB;static::require_enabled();if (!in_array($includes, array('children', 'parents', 'self'))) {throw new coding_exception('Invalid parameter value for \'includes\'.');}// If context user swap it for the context_system.if ($context->contextlevel == CONTEXT_USER) {$context = context_system::instance();}$contexts = array($context->id => $context);if ($includes == 'children') {$params = array('coursecatlevel' => CONTEXT_COURSECAT, 'path' => $context->path . '/%');$pathlike = $DB->sql_like('path', ':path');$sql = "contextlevel = :coursecatlevel AND $pathlike";$rs = $DB->get_recordset_select('context', $sql, $params);foreach ($rs as $record) {$ctxid = $record->id;context_helper::preload_from_record($record);$contexts[$ctxid] = context::instance_by_id($ctxid);}$rs->close();} else if ($includes == 'parents') {$children = $context->get_parent_contexts();foreach ($children as $ctx) {$contexts[$ctx->id] = $ctx;}}// Filter according to the capabilities required.if (!empty($hasanycapability)) {foreach ($contexts as $key => $ctx) {if (!has_any_capability($hasanycapability, $ctx)) {unset($contexts[$key]);}}}return $contexts;}/*** Count all the courses using a competency.** @param int $competencyid The id of the competency to check.* @return int*/public static function count_courses_using_competency($competencyid) {static::require_enabled();// OK - all set.$courses = course_competency::list_courses_min($competencyid);$count = 0;// Now check permissions on each course.foreach ($courses as $course) {if (!self::validate_course($course, false)) {continue;}$context = context_course::instance($course->id);$capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage');if (!has_any_capability($capabilities, $context)) {continue;}$count++;}return $count;}/*** List all the courses modules using a competency in a course.** @param int $competencyid The id of the competency to check.* @param int $courseid The id of the course to check.* @return array[int] Array of course modules ids.*/public static function list_course_modules_using_competency($competencyid, $courseid) {static::require_enabled();$result = array();self::validate_course($courseid);$coursecontext = context_course::instance($courseid);// We will not check each module - course permissions should be enough.$capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage');if (!has_any_capability($capabilities, $coursecontext)) {throw new required_capability_exception($coursecontext, 'moodle/competency:coursecompetencyview', 'nopermissions', '');}$cmlist = course_module_competency::list_course_modules($competencyid, $courseid);foreach ($cmlist as $cmid) {if (self::validate_course_module($cmid, false)) {array_push($result, $cmid);}}return $result;}/*** List all the competencies linked to a course module.** @param mixed $cmorid The course module, or its ID.* @return array[competency] Array of competency records.*/public static function list_course_module_competencies_in_course_module($cmorid) {static::require_enabled();$cm = $cmorid;if (!is_object($cmorid)) {$cm = get_coursemodule_from_id('', $cmorid, 0, true, MUST_EXIST);}// Check the user have access to the course module.self::validate_course_module($cm);$context = context_module::instance($cm->id);$capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage');if (!has_any_capability($capabilities, $context)) {throw new required_capability_exception($context, 'moodle/competency:coursecompetencyview', 'nopermissions', '');}$result = array();$cmclist = course_module_competency::list_course_module_competencies($cm->id);foreach ($cmclist as $id => $cmc) {array_push($result, $cmc);}return $result;}/*** List all the courses using a competency.** @param int $competencyid The id of the competency to check.* @return array[stdClass] Array of stdClass containing id and shortname.*/public static function list_courses_using_competency($competencyid) {static::require_enabled();// OK - all set.$courses = course_competency::list_courses($competencyid);$result = array();// Now check permissions on each course.foreach ($courses as $id => $course) {$context = context_course::instance($course->id);$capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage');if (!has_any_capability($capabilities, $context)) {unset($courses[$id]);continue;}if (!self::validate_course($course, false)) {unset($courses[$id]);continue;}array_push($result, $course);}return $result;}/*** Count the proficient competencies in a course for one user.** @param int $courseid The id of the course to check.* @param int $userid The id of the user to check.* @return int*/public static function count_proficient_competencies_in_course_for_user($courseid, $userid) {static::require_enabled();// Check the user have access to the course.self::validate_course($courseid);// First we do a permissions check.$context = context_course::instance($courseid);$capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage');if (!has_any_capability($capabilities, $context)) {throw new required_capability_exception($context, 'moodle/competency:coursecompetencyview', 'nopermissions', '');}// OK - all set.return user_competency_course::count_proficient_competencies($courseid, $userid);}/*** Count all the competencies in a course.** @param int $courseid The id of the course to check.* @return int*/public static function count_competencies_in_course($courseid) {static::require_enabled();// Check the user have access to the course.self::validate_course($courseid);// First we do a permissions check.$context = context_course::instance($courseid);$capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage');if (!has_any_capability($capabilities, $context)) {throw new required_capability_exception($context, 'moodle/competency:coursecompetencyview', 'nopermissions', '');}// OK - all set.return course_competency::count_competencies($courseid);}/*** List the competencies associated to a course.** @param mixed $courseorid The course, or its ID.* @return array( array(* 'competency' => \core_competency\competency,* 'coursecompetency' => \core_competency\course_competency* ))*/public static function list_course_competencies($courseorid) {static::require_enabled();$course = $courseorid;if (!is_object($courseorid)) {$course = get_course($courseorid);}// Check the user have access to the course.self::validate_course($course);$context = context_course::instance($course->id);$capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage');if (!has_any_capability($capabilities, $context)) {throw new required_capability_exception($context, 'moodle/competency:coursecompetencyview', 'nopermissions', '');}$result = array();// TODO We could improve the performance of this into one single query.$coursecompetencies = course_competency::list_course_competencies($course->id);$competencies = course_competency::list_competencies($course->id);// Build the return values.foreach ($coursecompetencies as $key => $coursecompetency) {$result[] = array('competency' => $competencies[$coursecompetency->get('competencyid')],'coursecompetency' => $coursecompetency);}return $result;}/*** Get a user competency.** @param int $userid The user ID.* @param int $competencyid The competency ID.* @return user_competency*/public static function get_user_competency($userid, $competencyid) {static::require_enabled();$existing = user_competency::get_multiple($userid, array($competencyid));$uc = array_pop($existing);if (!$uc) {$uc = user_competency::create_relation($userid, $competencyid);$uc->create();}if (!$uc->can_read()) {throw new required_capability_exception($uc->get_context(), 'moodle/competency:usercompetencyview','nopermissions', '');}return $uc;}/*** Get a user competency by ID.** @param int $usercompetencyid The user competency ID.* @return user_competency*/public static function get_user_competency_by_id($usercompetencyid) {static::require_enabled();$uc = new user_competency($usercompetencyid);if (!$uc->can_read()) {throw new required_capability_exception($uc->get_context(), 'moodle/competency:usercompetencyview','nopermissions', '');}return $uc;}/*** Count the competencies associated to a course module.** @param mixed $cmorid The course module, or its ID.* @return int*/public static function count_course_module_competencies($cmorid) {static::require_enabled();$cm = $cmorid;if (!is_object($cmorid)) {$cm = get_coursemodule_from_id('', $cmorid, 0, true, MUST_EXIST);}// Check the user have access to the course module.self::validate_course_module($cm);$context = context_module::instance($cm->id);$capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage');if (!has_any_capability($capabilities, $context)) {throw new required_capability_exception($context, 'moodle/competency:coursecompetencyview', 'nopermissions', '');}return course_module_competency::count_competencies($cm->id);}/*** List the competencies associated to a course module.** @param mixed $cmorid The course module, or its ID.* @return array( array(* 'competency' => \core_competency\competency,* 'coursemodulecompetency' => \core_competency\course_module_competency* ))*/public static function list_course_module_competencies($cmorid) {static::require_enabled();$cm = $cmorid;if (!is_object($cmorid)) {$cm = get_coursemodule_from_id('', $cmorid, 0, true, MUST_EXIST);}// Check the user have access to the course module.self::validate_course_module($cm);$context = context_module::instance($cm->id);$capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage');if (!has_any_capability($capabilities, $context)) {throw new required_capability_exception($context, 'moodle/competency:coursecompetencyview', 'nopermissions', '');}$result = array();// TODO We could improve the performance of this into one single query.$coursemodulecompetencies = course_module_competency::list_course_module_competencies($cm->id);$competencies = course_module_competency::list_competencies($cm->id);// Build the return values.foreach ($coursemodulecompetencies as $key => $coursemodulecompetency) {$result[] = array('competency' => $competencies[$coursemodulecompetency->get('competencyid')],'coursemodulecompetency' => $coursemodulecompetency);}return $result;}/*** Get a user competency in a course.** @param int $courseid The id of the course to check.* @param int $userid The id of the course to check.* @param int $competencyid The id of the competency.* @return user_competency_course*/public static function get_user_competency_in_course($courseid, $userid, $competencyid) {static::require_enabled();// First we do a permissions check.$context = context_course::instance($courseid);$capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage');if (!has_any_capability($capabilities, $context)) {throw new required_capability_exception($context, 'moodle/competency:coursecompetencyview', 'nopermissions', '');} else if (!user_competency::can_read_user_in_course($userid, $courseid)) {throw new required_capability_exception($context, 'moodle/competency:usercompetencyview', 'nopermissions', '');}// This will throw an exception if the competency does not belong to the course.$competency = course_competency::get_competency($courseid, $competencyid);$params = array('courseid' => $courseid, 'userid' => $userid, 'competencyid' => $competencyid);$exists = user_competency_course::get_record($params);// Create missing.if ($exists) {$ucc = $exists;} else {$ucc = user_competency_course::create_relation($userid, $competency->get('id'), $courseid);$ucc->create();}return $ucc;}/*** List all the user competencies in a course.** @param int $courseid The id of the course to check.* @param int $userid The id of the course to check.* @return array of user_competency_course objects*/public static function list_user_competencies_in_course($courseid, $userid) {static::require_enabled();// First we do a permissions check.$context = context_course::instance($courseid);$onlyvisible = 1;$capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage');if (!has_any_capability($capabilities, $context)) {throw new required_capability_exception($context, 'moodle/competency:coursecompetencyview', 'nopermissions', '');} else if (!user_competency::can_read_user_in_course($userid, $courseid)) {throw new required_capability_exception($context, 'moodle/competency:usercompetencyview', 'nopermissions', '');}// OK - all set.$competencylist = course_competency::list_competencies($courseid, false);$existing = user_competency_course::get_multiple($userid, $courseid, $competencylist);// Create missing.$orderedusercompetencycourses = array();$somemissing = false;foreach ($competencylist as $coursecompetency) {$found = false;foreach ($existing as $usercompetencycourse) {if ($usercompetencycourse->get('competencyid') == $coursecompetency->get('id')) {$found = true;$orderedusercompetencycourses[$usercompetencycourse->get('id')] = $usercompetencycourse;break;}}if (!$found) {$ucc = user_competency_course::create_relation($userid, $coursecompetency->get('id'), $courseid);$ucc->create();$orderedusercompetencycourses[$ucc->get('id')] = $ucc;}}return $orderedusercompetencycourses;}/*** List the user competencies to review.** The method returns values in this format:** array(* 'competencies' => array(* (stdClass)(* 'usercompetency' => (user_competency),* 'competency' => (competency),* 'user' => (user)* )* ),* 'count' => (int)* )** @param int $skip The number of records to skip.* @param int $limit The number of results to return.* @param int $userid The user we're getting the competencies to review for.* @return array Containing the keys 'count', and 'competencies'. The 'competencies' key contains an object* which contains 'competency', 'usercompetency' and 'user'.*/public static function list_user_competencies_to_review($skip = 0, $limit = 50, $userid = null) {global $DB, $USER;static::require_enabled();if ($userid === null) {$userid = $USER->id;}$capability = 'moodle/competency:usercompetencyreview';$ucfields = user_competency::get_sql_fields('uc', 'uc_');$compfields = competency::get_sql_fields('c', 'c_');$usercols = array('id') + get_user_fieldnames();$userfields = array();foreach ($usercols as $field) {$userfields[] = "u." . $field . " AS usr_" . $field;}$userfields = implode(',', $userfields);$select = "SELECT $ucfields, $compfields, $userfields";$countselect = "SELECT COUNT('x')";$sql = " FROM {" . user_competency::TABLE . "} ucJOIN {" . competency::TABLE . "} cON c.id = uc.competencyidJOIN {user} uON u.id = uc.useridWHERE (uc.status = :waitingforreviewOR (uc.status = :inreview AND uc.reviewerid = :reviewerid))AND u.deleted = 0";$ordersql = " ORDER BY c.shortname ASC";$params = array('inreview' => user_competency::STATUS_IN_REVIEW,'reviewerid' => $userid,'waitingforreview' => user_competency::STATUS_WAITING_FOR_REVIEW,);$countsql = $countselect . $sql;// Primary check to avoid the hard work of getting the users in which the user has permission.$count = $DB->count_records_sql($countselect . $sql, $params);if ($count < 1) {return array('count' => 0, 'competencies' => array());}// TODO MDL-52243 Use core function.list($insql, $inparams) = self::filter_users_with_capability_on_user_context_sql($capability, $userid, SQL_PARAMS_NAMED);$params += $inparams;$countsql = $countselect . $sql . " AND uc.userid $insql";$getsql = $select . $sql . " AND uc.userid $insql " . $ordersql;// Extracting the results.$competencies = array();$records = $DB->get_recordset_sql($getsql, $params, $skip, $limit);foreach ($records as $record) {$objects = (object) array('usercompetency' => new user_competency(0, user_competency::extract_record($record, 'uc_')),'competency' => new competency(0, competency::extract_record($record, 'c_')),'user' => persistent::extract_record($record, 'usr_'),);$competencies[] = $objects;}$records->close();return array('count' => $DB->count_records_sql($countsql, $params),'competencies' => $competencies);}/*** Add a competency to this course module.** @param mixed $cmorid The course module, or id of the course module* @param int $competencyid The id of the competency* @return bool*/public static function add_competency_to_course_module($cmorid, $competencyid) {static::require_enabled();$cm = $cmorid;if (!is_object($cmorid)) {$cm = get_coursemodule_from_id('', $cmorid, 0, true, MUST_EXIST);}// Check the user have access to the course module.self::validate_course_module($cm);// First we do a permissions check.$context = context_module::instance($cm->id);require_capability('moodle/competency:coursecompetencymanage', $context);// Check that the competency belongs to the course.$exists = course_competency::get_records(array('courseid' => $cm->course, 'competencyid' => $competencyid));if (!$exists) {throw new coding_exception('Cannot add a competency to a module if it does not belong to the course');}$record = new stdClass();$record->cmid = $cm->id;$record->competencyid = $competencyid;$coursemodulecompetency = new course_module_competency();$exists = $coursemodulecompetency->get_records(array('cmid' => $cm->id, 'competencyid' => $competencyid));if (!$exists) {$coursemodulecompetency->from_record($record);if ($coursemodulecompetency->create()) {return true;}}return false;}/*** Remove a competency from this course module.** @param mixed $cmorid The course module, or id of the course module* @param int $competencyid The id of the competency* @return bool*/public static function remove_competency_from_course_module($cmorid, $competencyid) {static::require_enabled();$cm = $cmorid;if (!is_object($cmorid)) {$cm = get_coursemodule_from_id('', $cmorid, 0, true, MUST_EXIST);}// Check the user have access to the course module.self::validate_course_module($cm);// First we do a permissions check.$context = context_module::instance($cm->id);require_capability('moodle/competency:coursecompetencymanage', $context);$record = new stdClass();$record->cmid = $cm->id;$record->competencyid = $competencyid;$competency = new competency($competencyid);$exists = course_module_competency::get_record(array('cmid' => $cm->id, 'competencyid' => $competencyid));if ($exists) {return $exists->delete();}return false;}/*** Move the course module competency up or down in the display list.** Requires moodle/competency:coursecompetencymanage capability at the course module context.** @param mixed $cmorid The course module, or id of the course module* @param int $competencyidfrom The id of the competency we are moving.* @param int $competencyidto The id of the competency we are moving to.* @return boolean*/public static function reorder_course_module_competency($cmorid, $competencyidfrom, $competencyidto) {static::require_enabled();$cm = $cmorid;if (!is_object($cmorid)) {$cm = get_coursemodule_from_id('', $cmorid, 0, true, MUST_EXIST);}// Check the user have access to the course module.self::validate_course_module($cm);// First we do a permissions check.$context = context_module::instance($cm->id);require_capability('moodle/competency:coursecompetencymanage', $context);$down = true;$matches = course_module_competency::get_records(array('cmid' => $cm->id, 'competencyid' => $competencyidfrom));if (count($matches) == 0) {throw new coding_exception('The link does not exist');}$competencyfrom = array_pop($matches);$matches = course_module_competency::get_records(array('cmid' => $cm->id, 'competencyid' => $competencyidto));if (count($matches) == 0) {throw new coding_exception('The link does not exist');}$competencyto = array_pop($matches);$all = course_module_competency::get_records(array('cmid' => $cm->id), 'sortorder', 'ASC', 0, 0);if ($competencyfrom->get('sortorder') > $competencyto->get('sortorder')) {// We are moving up, so put it before the "to" item.$down = false;}foreach ($all as $id => $coursemodulecompetency) {$sort = $coursemodulecompetency->get('sortorder');if ($down && $sort > $competencyfrom->get('sortorder') && $sort <= $competencyto->get('sortorder')) {$coursemodulecompetency->set('sortorder', $coursemodulecompetency->get('sortorder') - 1);$coursemodulecompetency->update();} else if (!$down && $sort >= $competencyto->get('sortorder') && $sort < $competencyfrom->get('sortorder')) {$coursemodulecompetency->set('sortorder', $coursemodulecompetency->get('sortorder') + 1);$coursemodulecompetency->update();}}$competencyfrom->set('sortorder', $competencyto->get('sortorder'));return $competencyfrom->update();}/*** Update ruleoutcome value for a course module competency.** @param int|course_module_competency $coursemodulecompetencyorid The course_module_competency, or its ID.* @param int $ruleoutcome The value of ruleoutcome.* @param bool $overridegrade If true, will override existing grades in related competencies.* @return bool True on success.*/public static function set_course_module_competency_ruleoutcome($coursemodulecompetencyorid, $ruleoutcome,$overridegrade = false) {static::require_enabled();$coursemodulecompetency = $coursemodulecompetencyorid;if (!is_object($coursemodulecompetency)) {$coursemodulecompetency = new course_module_competency($coursemodulecompetencyorid);}$cm = get_coursemodule_from_id('', $coursemodulecompetency->get('cmid'), 0, true, MUST_EXIST);self::validate_course_module($cm);$context = context_module::instance($cm->id);require_capability('moodle/competency:coursecompetencymanage', $context);$coursemodulecompetency->set('ruleoutcome', $ruleoutcome);$coursemodulecompetency->set('overridegrade', $overridegrade);return $coursemodulecompetency->update();}/*** Add a competency to this course.** @param int $courseid The id of the course* @param int $competencyid The id of the competency* @return bool*/public static function add_competency_to_course($courseid, $competencyid) {static::require_enabled();// Check the user have access to the course.self::validate_course($courseid);// First we do a permissions check.$context = context_course::instance($courseid);require_capability('moodle/competency:coursecompetencymanage', $context);$record = new stdClass();$record->courseid = $courseid;$record->competencyid = $competencyid;$competency = new competency($competencyid);// Can not add a competency that belong to a hidden framework.if ($competency->get_framework()->get('visible') == false) {throw new coding_exception('A competency belonging to hidden framework can not be linked to course');}$coursecompetency = new course_competency();$exists = $coursecompetency->get_records(array('courseid' => $courseid, 'competencyid' => $competencyid));if (!$exists) {$coursecompetency->from_record($record);if ($coursecompetency->create()) {return true;}}return false;}/*** Remove a competency from this course.** @param int $courseid The id of the course* @param int $competencyid The id of the competency* @return bool*/public static function remove_competency_from_course($courseid, $competencyid) {static::require_enabled();// Check the user have access to the course.self::validate_course($courseid);// First we do a permissions check.$context = context_course::instance($courseid);require_capability('moodle/competency:coursecompetencymanage', $context);$record = new stdClass();$record->courseid = $courseid;$record->competencyid = $competencyid;$coursecompetency = new course_competency();$exists = course_competency::get_record(array('courseid' => $courseid, 'competencyid' => $competencyid));if ($exists) {// Delete all course_module_competencies for this competency in this course.$cmcs = course_module_competency::get_records_by_competencyid_in_course($competencyid, $courseid);foreach ($cmcs as $cmc) {$cmc->delete();}return $exists->delete();}return false;}/*** Move the course competency up or down in the display list.** Requires moodle/competency:coursecompetencymanage capability at the course context.** @param int $courseid The course* @param int $competencyidfrom The id of the competency we are moving.* @param int $competencyidto The id of the competency we are moving to.* @return boolean*/public static function reorder_course_competency($courseid, $competencyidfrom, $competencyidto) {static::require_enabled();// Check the user have access to the course.self::validate_course($courseid);// First we do a permissions check.$context = context_course::instance($courseid);require_capability('moodle/competency:coursecompetencymanage', $context);$down = true;$coursecompetency = new course_competency();$matches = $coursecompetency->get_records(array('courseid' => $courseid, 'competencyid' => $competencyidfrom));if (count($matches) == 0) {throw new coding_exception('The link does not exist');}$competencyfrom = array_pop($matches);$matches = $coursecompetency->get_records(array('courseid' => $courseid, 'competencyid' => $competencyidto));if (count($matches) == 0) {throw new coding_exception('The link does not exist');}$competencyto = array_pop($matches);$all = $coursecompetency->get_records(array('courseid' => $courseid), 'sortorder', 'ASC', 0, 0);if ($competencyfrom->get('sortorder') > $competencyto->get('sortorder')) {// We are moving up, so put it before the "to" item.$down = false;}foreach ($all as $id => $coursecompetency) {$sort = $coursecompetency->get('sortorder');if ($down && $sort > $competencyfrom->get('sortorder') && $sort <= $competencyto->get('sortorder')) {$coursecompetency->set('sortorder', $coursecompetency->get('sortorder') - 1);$coursecompetency->update();} else if (!$down && $sort >= $competencyto->get('sortorder') && $sort < $competencyfrom->get('sortorder')) {$coursecompetency->set('sortorder', $coursecompetency->get('sortorder') + 1);$coursecompetency->update();}}$competencyfrom->set('sortorder', $competencyto->get('sortorder'));return $competencyfrom->update();}/*** Update ruleoutcome value for a course competency.** @param int|course_competency $coursecompetencyorid The course_competency, or its ID.* @param int $ruleoutcome The value of ruleoutcome.* @return bool True on success.*/public static function set_course_competency_ruleoutcome($coursecompetencyorid, $ruleoutcome) {static::require_enabled();$coursecompetency = $coursecompetencyorid;if (!is_object($coursecompetency)) {$coursecompetency = new course_competency($coursecompetencyorid);}$courseid = $coursecompetency->get('courseid');self::validate_course($courseid);$coursecontext = context_course::instance($courseid);require_capability('moodle/competency:coursecompetencymanage', $coursecontext);$coursecompetency->set('ruleoutcome', $ruleoutcome);return $coursecompetency->update();}/*** Create a learning plan template from a record containing all the data for the class.** Requires moodle/competency:templatemanage capability.** @param stdClass $record Record containing all the data for an instance of the class.* @return template*/public static function create_template(stdClass $record) {static::require_enabled();$template = new template(0, $record);// First we do a permissions check.if (!$template->can_manage()) {throw new required_capability_exception($template->get_context(), 'moodle/competency:templatemanage','nopermissions', '');}// OK - all set.$template = $template->create();// Trigger a template created event.\core\event\competency_template_created::create_from_template($template)->trigger();return $template;}/*** Duplicate a learning plan template.** Requires moodle/competency:templatemanage capability at the template context.** @param int $id the template id.* @return template*/public static function duplicate_template($id) {static::require_enabled();$template = new template($id);// First we do a permissions check.if (!$template->can_manage()) {throw new required_capability_exception($template->get_context(), 'moodle/competency:templatemanage','nopermissions', '');}// OK - all set.$competencies = template_competency::list_competencies($id, false);// Adding the suffix copy.$template->set('shortname', get_string('duplicateditemname', 'core_competency', $template->get('shortname')));$template->set('id', 0);$duplicatedtemplate = $template->create();// Associate each competency for the duplicated template.foreach ($competencies as $competency) {self::add_competency_to_template($duplicatedtemplate->get('id'), $competency->get('id'));}// Trigger a template created event.\core\event\competency_template_created::create_from_template($duplicatedtemplate)->trigger();return $duplicatedtemplate;}/*** Delete a learning plan template by id.* If the learning plan template has associated cohorts they will be deleted.** Requires moodle/competency:templatemanage capability.** @param int $id The record to delete.* @param boolean $deleteplans True to delete plans associaated to template, false to unlink them.* @return boolean*/public static function delete_template($id, $deleteplans = true) {global $DB;static::require_enabled();$template = new template($id);// First we do a permissions check.if (!$template->can_manage()) {throw new required_capability_exception($template->get_context(), 'moodle/competency:templatemanage','nopermissions', '');}$transaction = $DB->start_delegated_transaction();$success = true;// Check if there are cohorts associated.$templatecohorts = template_cohort::get_relations_by_templateid($template->get('id'));foreach ($templatecohorts as $templatecohort) {$success = $templatecohort->delete();if (!$success) {break;}}// Still OK, delete or unlink the plans from the template.if ($success) {$plans = plan::get_records(array('templateid' => $template->get('id')));foreach ($plans as $plan) {$success = $deleteplans ? self::delete_plan($plan->get('id')) : self::unlink_plan_from_template($plan);if (!$success) {break;}}}// Still OK, delete the template comptencies.if ($success) {$success = template_competency::delete_by_templateid($template->get('id'));}// OK - all set.if ($success) {// Create a template deleted event.$event = \core\event\competency_template_deleted::create_from_template($template);$success = $template->delete();}if ($success) {// Trigger a template deleted event.$event->trigger();// Commit the transaction.$transaction->allow_commit();} else {$transaction->rollback(new moodle_exception('Error while deleting the template.'));}return $success;}/*** Update the details for a learning plan template.** Requires moodle/competency:templatemanage capability.** @param stdClass $record The new details for the template. Note - must contain an id that points to the template to update.* @return boolean*/public static function update_template($record) {global $DB;static::require_enabled();$template = new template($record->id);// First we do a permissions check.if (!$template->can_manage()) {throw new required_capability_exception($template->get_context(), 'moodle/competency:templatemanage','nopermissions', '');} else if (isset($record->contextid) && $record->contextid != $template->get('contextid')) {// We can never change the context of a template.throw new coding_exception('Changing the context of an existing tempalte is forbidden.');}$updateplans = false;$before = $template->to_record();$template->from_record($record);$after = $template->to_record();// Should we update the related plans?if ($before->duedate != $after->duedate ||$before->shortname != $after->shortname ||$before->description != $after->description ||$before->descriptionformat != $after->descriptionformat) {$updateplans = true;}$transaction = $DB->start_delegated_transaction();$success = $template->update();if (!$success) {$transaction->rollback(new moodle_exception('Error while updating the template.'));return $success;}// Trigger a template updated event.\core\event\competency_template_updated::create_from_template($template)->trigger();if ($updateplans) {plan::update_multiple_from_template($template);}$transaction->allow_commit();return $success;}/*** Read a the details for a single learning plan template and return a record.** Requires moodle/competency:templateview capability at the system context.** @param int $id The id of the template to read.* @return template*/public static function read_template($id) {static::require_enabled();$template = new template($id);$context = $template->get_context();// First we do a permissions check.if (!$template->can_read()) {throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview','nopermissions', '');}// OK - all set.return $template;}/*** Perform a search based on the provided filters and return a paginated list of records.** Requires moodle/competency:templateview capability at the system context.** @param string $sort The column to sort on* @param string $order ('ASC' or 'DESC')* @param int $skip Number of records to skip (pagination)* @param int $limit Max of records to return (pagination)* @param context $context The parent context of the frameworks.* @param string $includes Defines what other contexts to fetch frameworks from.* Accepted values are:* - children: All descendants* - parents: All parents, grand parents, etc...* - self: Context passed only.* @param bool $onlyvisible If should list only visible templates* @return array of competency_framework*/public static function list_templates($sort, $order, $skip, $limit, $context, $includes = 'children', $onlyvisible = false) {global $DB;static::require_enabled();// Get all the relevant contexts.$contexts = self::get_related_contexts($context, $includes,array('moodle/competency:templateview', 'moodle/competency:templatemanage'));// First we do a permissions check.if (empty($contexts)) {throw new required_capability_exception($context, 'moodle/competency:templateview', 'nopermissions', '');}// Make the order by.$orderby = '';if (!empty($sort)) {$orderby = $sort . ' ' . $order;}// OK - all set.$template = new template();list($insql, $params) = $DB->get_in_or_equal(array_keys($contexts), SQL_PARAMS_NAMED);$select = "contextid $insql";if ($onlyvisible) {$select .= " AND visible = :visible";$params['visible'] = 1;}return $template->get_records_select($select, $params, $orderby, '*', $skip, $limit);}/*** Perform a search based on the provided filters and return how many results there are.** Requires moodle/competency:templateview capability at the system context.** @param context $context The parent context of the frameworks.* @param string $includes Defines what other contexts to fetch frameworks from.* Accepted values are:* - children: All descendants* - parents: All parents, grand parents, etc...* - self: Context passed only.* @return int*/public static function count_templates($context, $includes) {global $DB;static::require_enabled();// First we do a permissions check.$contexts = self::get_related_contexts($context, $includes,array('moodle/competency:templateview', 'moodle/competency:templatemanage'));if (empty($contexts)) {throw new required_capability_exception($context, 'moodle/competency:templateview', 'nopermissions', '');}// OK - all set.$template = new template();list($insql, $inparams) = $DB->get_in_or_equal(array_keys($contexts), SQL_PARAMS_NAMED);return $template->count_records_select("contextid $insql", $inparams);}/*** Count all the templates using a competency.** @param int $competencyid The id of the competency to check.* @return int*/public static function count_templates_using_competency($competencyid) {static::require_enabled();// First we do a permissions check.$context = context_system::instance();$onlyvisible = 1;$capabilities = array('moodle/competency:templateview', 'moodle/competency:templatemanage');if (!has_any_capability($capabilities, $context)) {throw new required_capability_exception($context, 'moodle/competency:templateview', 'nopermissions', '');}if (has_capability('moodle/competency:templatemanage', $context)) {$onlyvisible = 0;}// OK - all set.return template_competency::count_templates($competencyid, $onlyvisible);}/*** List all the learning plan templatesd using a competency.** @param int $competencyid The id of the competency to check.* @return array[stdClass] Array of stdClass containing id and shortname.*/public static function list_templates_using_competency($competencyid) {static::require_enabled();// First we do a permissions check.$context = context_system::instance();$onlyvisible = 1;$capabilities = array('moodle/competency:templateview', 'moodle/competency:templatemanage');if (!has_any_capability($capabilities, $context)) {throw new required_capability_exception($context, 'moodle/competency:templateview', 'nopermissions', '');}if (has_capability('moodle/competency:templatemanage', $context)) {$onlyvisible = 0;}// OK - all set.return template_competency::list_templates($competencyid, $onlyvisible);}/*** Count all the competencies in a learning plan template.** @param template|int $templateorid The template or its ID.* @return int*/public static function count_competencies_in_template($templateorid) {static::require_enabled();// First we do a permissions check.$template = $templateorid;if (!is_object($template)) {$template = new template($template);}if (!$template->can_read()) {throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview','nopermissions', '');}// OK - all set.return template_competency::count_competencies($template->get('id'));}/*** Count all the competencies in a learning plan template with no linked courses.** @param template|int $templateorid The template or its ID.* @return int*/public static function count_competencies_in_template_with_no_courses($templateorid) {// First we do a permissions check.$template = $templateorid;if (!is_object($template)) {$template = new template($template);}if (!$template->can_read()) {throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview','nopermissions', '');}// OK - all set.return template_competency::count_competencies_with_no_courses($template->get('id'));}/*** List all the competencies in a template.** @param template|int $templateorid The template or its ID.* @return array of competencies*/public static function list_competencies_in_template($templateorid) {static::require_enabled();// First we do a permissions check.$template = $templateorid;if (!is_object($template)) {$template = new template($template);}if (!$template->can_read()) {throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview','nopermissions', '');}// OK - all set.return template_competency::list_competencies($template->get('id'));}/*** Add a competency to this template.** @param int $templateid The id of the template* @param int $competencyid The id of the competency* @return bool*/public static function add_competency_to_template($templateid, $competencyid) {static::require_enabled();// First we do a permissions check.$template = new template($templateid);if (!$template->can_manage()) {throw new required_capability_exception($template->get_context(), 'moodle/competency:templatemanage','nopermissions', '');}$record = new stdClass();$record->templateid = $templateid;$record->competencyid = $competencyid;$competency = new competency($competencyid);// Can not add a competency that belong to a hidden framework.if ($competency->get_framework()->get('visible') == false) {throw new coding_exception('A competency belonging to hidden framework can not be added');}$exists = template_competency::get_records(array('templateid' => $templateid, 'competencyid' => $competencyid));if (!$exists) {$templatecompetency = new template_competency(0, $record);$templatecompetency->create();return true;}return false;}/*** Remove a competency from this template.** @param int $templateid The id of the template* @param int $competencyid The id of the competency* @return bool*/public static function remove_competency_from_template($templateid, $competencyid) {static::require_enabled();// First we do a permissions check.$template = new template($templateid);if (!$template->can_manage()) {throw new required_capability_exception($template->get_context(), 'moodle/competency:templatemanage','nopermissions', '');}$record = new stdClass();$record->templateid = $templateid;$record->competencyid = $competencyid;$competency = new competency($competencyid);$exists = template_competency::get_records(array('templateid' => $templateid, 'competencyid' => $competencyid));if ($exists) {$link = array_pop($exists);return $link->delete();}return false;}/*** Move the template competency up or down in the display list.** Requires moodle/competency:templatemanage capability at the system context.** @param int $templateid The template id* @param int $competencyidfrom The id of the competency we are moving.* @param int $competencyidto The id of the competency we are moving to.* @return boolean*/public static function reorder_template_competency($templateid, $competencyidfrom, $competencyidto) {static::require_enabled();$template = new template($templateid);// First we do a permissions check.if (!$template->can_manage()) {throw new required_capability_exception($template->get_context(), 'moodle/competency:templatemanage','nopermissions', '');}$down = true;$matches = template_competency::get_records(array('templateid' => $templateid, 'competencyid' => $competencyidfrom));if (count($matches) == 0) {throw new coding_exception('The link does not exist');}$competencyfrom = array_pop($matches);$matches = template_competency::get_records(array('templateid' => $templateid, 'competencyid' => $competencyidto));if (count($matches) == 0) {throw new coding_exception('The link does not exist');}$competencyto = array_pop($matches);$all = template_competency::get_records(array('templateid' => $templateid), 'sortorder', 'ASC', 0, 0);if ($competencyfrom->get('sortorder') > $competencyto->get('sortorder')) {// We are moving up, so put it before the "to" item.$down = false;}foreach ($all as $id => $templatecompetency) {$sort = $templatecompetency->get('sortorder');if ($down && $sort > $competencyfrom->get('sortorder') && $sort <= $competencyto->get('sortorder')) {$templatecompetency->set('sortorder', $templatecompetency->get('sortorder') - 1);$templatecompetency->update();} else if (!$down && $sort >= $competencyto->get('sortorder') && $sort < $competencyfrom->get('sortorder')) {$templatecompetency->set('sortorder', $templatecompetency->get('sortorder') + 1);$templatecompetency->update();}}$competencyfrom->set('sortorder', $competencyto->get('sortorder'));return $competencyfrom->update();}/*** Create a relation between a template and a cohort.** This silently ignores when the relation already existed.** @param template|int $templateorid The template or its ID.* @param stdClass|int $cohortorid The cohort ot its ID.* @return template_cohort*/public static function create_template_cohort($templateorid, $cohortorid) {global $DB;static::require_enabled();$template = $templateorid;if (!is_object($template)) {$template = new template($template);}require_capability('moodle/competency:templatemanage', $template->get_context());$cohort = $cohortorid;if (!is_object($cohort)) {$cohort = $DB->get_record('cohort', array('id' => $cohort), '*', MUST_EXIST);}// Replicate logic in cohort_can_view_cohort() because we can't use it directly as we don't have a course context.$cohortcontext = context::instance_by_id($cohort->contextid);if (!$cohort->visible && !has_capability('moodle/cohort:view', $cohortcontext)) {throw new required_capability_exception($cohortcontext, 'moodle/cohort:view', 'nopermissions', '');}$tplcohort = template_cohort::get_relation($template->get('id'), $cohort->id);if (!$tplcohort->get('id')) {$tplcohort->create();}return $tplcohort;}/*** Remove a relation between a template and a cohort.** @param template|int $templateorid The template or its ID.* @param stdClass|int $cohortorid The cohort ot its ID.* @return boolean True on success or when the relation did not exist.*/public static function delete_template_cohort($templateorid, $cohortorid) {global $DB;static::require_enabled();$template = $templateorid;if (!is_object($template)) {$template = new template($template);}require_capability('moodle/competency:templatemanage', $template->get_context());$cohort = $cohortorid;if (!is_object($cohort)) {$cohort = $DB->get_record('cohort', array('id' => $cohort), '*', MUST_EXIST);}$tplcohort = template_cohort::get_relation($template->get('id'), $cohort->id);if (!$tplcohort->get('id')) {return true;}return $tplcohort->delete();}/*** Lists user plans.** @param int $userid* @return \core_competency\plan[]*/public static function list_user_plans($userid) {global $DB, $USER;static::require_enabled();$select = 'userid = :userid';$params = array('userid' => $userid);$context = context_user::instance($userid);// Check that we can read something here.if (!plan::can_read_user($userid) && !plan::can_read_user_draft($userid)) {throw new required_capability_exception($context, 'moodle/competency:planview', 'nopermissions', '');}// The user cannot view the drafts.if (!plan::can_read_user_draft($userid)) {list($insql, $inparams) = $DB->get_in_or_equal(plan::get_draft_statuses(), SQL_PARAMS_NAMED, 'param', false);$select .= " AND status $insql";$params += $inparams;}// The user cannot view the non-drafts.if (!plan::can_read_user($userid)) {list($insql, $inparams) = $DB->get_in_or_equal(array(plan::STATUS_ACTIVE, plan::STATUS_COMPLETE),SQL_PARAMS_NAMED, 'param', false);$select .= " AND status $insql";$params += $inparams;}return plan::get_records_select($select, $params, 'name ASC');}/*** List the plans to review.** The method returns values in this format:** array(* 'plans' => array(* (stdClass)(* 'plan' => (plan),* 'template' => (template),* 'owner' => (stdClass)* )* ),* 'count' => (int)* )** @param int $skip The number of records to skip.* @param int $limit The number of results to return.* @param int $userid The user we're getting the plans to review for.* @return array Containing the keys 'count', and 'plans'. The 'plans' key contains an object* which contains 'plan', 'template' and 'owner'.*/public static function list_plans_to_review($skip = 0, $limit = 100, $userid = null) {global $DB, $USER;static::require_enabled();if ($userid === null) {$userid = $USER->id;}$planfields = plan::get_sql_fields('p', 'plan_');$tplfields = template::get_sql_fields('t', 'tpl_');$usercols = array('id') + get_user_fieldnames();$userfields = array();foreach ($usercols as $field) {$userfields[] = "u." . $field . " AS usr_" . $field;}$userfields = implode(',', $userfields);$select = "SELECT $planfields, $tplfields, $userfields";$countselect = "SELECT COUNT('x')";$sql = " FROM {" . plan::TABLE . "} pJOIN {user} uON u.id = p.useridLEFT JOIN {" . template::TABLE . "} tON t.id = p.templateidWHERE (p.status = :waitingforreviewOR (p.status = :inreview AND p.reviewerid = :reviewerid))AND p.userid != :userid";$params = array('waitingforreview' => plan::STATUS_WAITING_FOR_REVIEW,'inreview' => plan::STATUS_IN_REVIEW,'reviewerid' => $userid,'userid' => $userid);// Primary check to avoid the hard work of getting the users in which the user has permission.$count = $DB->count_records_sql($countselect . $sql, $params);if ($count < 1) {return array('count' => 0, 'plans' => array());}// TODO MDL-52243 Use core function.list($insql, $inparams) = self::filter_users_with_capability_on_user_context_sql('moodle/competency:planreview',$userid, SQL_PARAMS_NAMED);$sql .= " AND p.userid $insql";$params += $inparams;// Order by ID just to have some ordering in place.$ordersql = " ORDER BY p.id ASC";$plans = array();$records = $DB->get_recordset_sql($select . $sql . $ordersql, $params, $skip, $limit);foreach ($records as $record) {$plan = new plan(0, plan::extract_record($record, 'plan_'));$template = null;if ($plan->is_based_on_template()) {$template = new template(0, template::extract_record($record, 'tpl_'));}$plans[] = (object) array('plan' => $plan,'template' => $template,'owner' => persistent::extract_record($record, 'usr_'),);}$records->close();return array('count' => $DB->count_records_sql($countselect . $sql, $params),'plans' => $plans);}/*** Creates a learning plan based on the provided data.** @param stdClass $record* @return \core_competency\plan*/public static function create_plan(stdClass $record) {global $USER;static::require_enabled();$plan = new plan(0, $record);if ($plan->is_based_on_template()) {throw new coding_exception('To create a plan from a template use api::create_plan_from_template().');} else if ($plan->get('status') == plan::STATUS_COMPLETE) {throw new coding_exception('A plan cannot be created as complete.');}if (!$plan->can_manage()) {$context = context_user::instance($plan->get('userid'));throw new required_capability_exception($context, 'moodle/competency:planmanage', 'nopermissions', '');}$plan->create();// Trigger created event.\core\event\competency_plan_created::create_from_plan($plan)->trigger();return $plan;}/*** Create a learning plan from a template.** @param mixed $templateorid The template object or ID.* @param int $userid* @return false|\core_competency\plan Returns false when the plan already exists.*/public static function create_plan_from_template($templateorid, $userid) {static::require_enabled();$template = $templateorid;if (!is_object($template)) {$template = new template($template);}// The user must be able to view the template to use it as a base for a plan.if (!$template->can_read()) {throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview','nopermissions', '');}// Can not create plan from a hidden template.if ($template->get('visible') == false) {throw new coding_exception('A plan can not be created from a hidden template');}// Convert the template to a plan.$record = $template->to_record();$record->templateid = $record->id;$record->userid = $userid;$record->name = $record->shortname;$record->status = plan::STATUS_ACTIVE;unset($record->id);unset($record->timecreated);unset($record->timemodified);unset($record->usermodified);// Remove extra keys.$properties = plan::properties_definition();foreach ($record as $key => $value) {if (!array_key_exists($key, $properties)) {unset($record->$key);}}$plan = new plan(0, $record);if (!$plan->can_manage()) {throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage','nopermissions', '');}// We first apply the permission checks as we wouldn't want to leak information by returning early that// the plan already exists.if (plan::record_exists_select('templateid = :templateid AND userid = :userid', array('templateid' => $template->get('id'), 'userid' => $userid))) {return false;}$plan->create();// Trigger created event.\core\event\competency_plan_created::create_from_plan($plan)->trigger();return $plan;}/*** Create learning plans from a template and cohort.** @param mixed $templateorid The template object or ID.* @param int $cohortid The cohort ID.* @param bool $recreateunlinked When true the plans that were unlinked from this template will be re-created.* @return int The number of plans created.*/public static function create_plans_from_template_cohort($templateorid, $cohortid, $recreateunlinked = false) {global $DB, $CFG;static::require_enabled();require_once($CFG->dirroot . '/cohort/lib.php');$template = $templateorid;if (!is_object($template)) {$template = new template($template);}// The user must be able to view the template to use it as a base for a plan.if (!$template->can_read()) {throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview','nopermissions', '');}// Can not create plan from a hidden template.if ($template->get('visible') == false) {throw new coding_exception('A plan can not be created from a hidden template');}// Replicate logic in cohort_can_view_cohort() because we can't use it directly as we don't have a course context.$cohort = $DB->get_record('cohort', array('id' => $cohortid), '*', MUST_EXIST);$cohortcontext = context::instance_by_id($cohort->contextid);if (!$cohort->visible && !has_capability('moodle/cohort:view', $cohortcontext)) {throw new required_capability_exception($cohortcontext, 'moodle/cohort:view', 'nopermissions', '');}// Convert the template to a plan.$recordbase = $template->to_record();$recordbase->templateid = $recordbase->id;$recordbase->name = $recordbase->shortname;$recordbase->status = plan::STATUS_ACTIVE;unset($recordbase->id);unset($recordbase->timecreated);unset($recordbase->timemodified);unset($recordbase->usermodified);// Remove extra keys.$properties = plan::properties_definition();foreach ($recordbase as $key => $value) {if (!array_key_exists($key, $properties)) {unset($recordbase->$key);}}// Create the plans.$created = 0;$userids = template_cohort::get_missing_plans($template->get('id'), $cohortid, $recreateunlinked);foreach ($userids as $userid) {$record = (object) (array) $recordbase;$record->userid = $userid;$plan = new plan(0, $record);if (!$plan->can_manage()) {// Silently skip members where permissions are lacking.continue;}$plan->create();// Trigger created event.\core\event\competency_plan_created::create_from_plan($plan)->trigger();$created++;}return $created;}/*** Unlink a plan from its template.** @param \core_competency\plan|int $planorid The plan or its ID.* @return bool*/public static function unlink_plan_from_template($planorid) {global $DB;static::require_enabled();$plan = $planorid;if (!is_object($planorid)) {$plan = new plan($planorid);}// The user must be allowed to manage the plans of the user, nothing about the template.if (!$plan->can_manage()) {throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', '');}// Only plan with status DRAFT or ACTIVE can be unliked..if ($plan->get('status') == plan::STATUS_COMPLETE) {throw new coding_exception('Only draft or active plan can be unliked from a template');}// Early exit, it's already done...if (!$plan->is_based_on_template()) {return true;}// Fetch the template.$template = new template($plan->get('templateid'));// Now, proceed by copying all competencies to the plan, then update the plan.$transaction = $DB->start_delegated_transaction();$competencies = template_competency::list_competencies($template->get('id'), false);$i = 0;foreach ($competencies as $competency) {$record = (object) array('planid' => $plan->get('id'),'competencyid' => $competency->get('id'),'sortorder' => $i++);$pc = new plan_competency(null, $record);$pc->create();}$plan->set('origtemplateid', $template->get('id'));$plan->set('templateid', null);$success = $plan->update();$transaction->allow_commit();// Trigger unlinked event.\core\event\competency_plan_unlinked::create_from_plan($plan)->trigger();return $success;}/*** Updates a plan.** @param stdClass $record* @return \core_competency\plan*/public static function update_plan(stdClass $record) {static::require_enabled();$plan = new plan($record->id);// Validate that the plan as it is can be managed.if (!$plan->can_manage()) {throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', '');} else if ($plan->get('status') == plan::STATUS_COMPLETE) {// A completed plan cannot be edited.throw new coding_exception('Completed plan cannot be edited.');} else if ($plan->is_based_on_template()) {// Prevent a plan based on a template to be edited.throw new coding_exception('Cannot update a plan that is based on a template.');} else if (isset($record->templateid) && $plan->get('templateid') != $record->templateid) {// Prevent a plan to be based on a template.throw new coding_exception('Cannot base a plan on a template.');} else if (isset($record->userid) && $plan->get('userid') != $record->userid) {// Prevent change of ownership as the capabilities are checked against that.throw new coding_exception('A plan cannot be transfered to another user');} else if (isset($record->status) && $plan->get('status') != $record->status) {// Prevent change of status.throw new coding_exception('To change the status of a plan use the appropriate methods.');}$plan->from_record($record);$plan->update();// Trigger updated event.\core\event\competency_plan_updated::create_from_plan($plan)->trigger();return $plan;}/*** Returns a plan data.** @param int $id* @return \core_competency\plan*/public static function read_plan($id) {static::require_enabled();$plan = new plan($id);if (!$plan->can_read()) {$context = context_user::instance($plan->get('userid'));throw new required_capability_exception($context, 'moodle/competency:planview', 'nopermissions', '');}return $plan;}/*** Plan event viewed.** @param mixed $planorid The id or the plan.* @return boolean*/public static function plan_viewed($planorid) {static::require_enabled();$plan = $planorid;if (!is_object($plan)) {$plan = new plan($plan);}// First we do a permissions check.if (!$plan->can_read()) {$context = context_user::instance($plan->get('userid'));throw new required_capability_exception($context, 'moodle/competency:planview', 'nopermissions', '');}// Trigger a template viewed event.\core\event\competency_plan_viewed::create_from_plan($plan)->trigger();return true;}/*** Deletes a plan.** Plans based on a template can be removed just like any other one.** @param int $id* @return bool Success?*/public static function delete_plan($id) {global $DB;static::require_enabled();$plan = new plan($id);if (!$plan->can_manage()) {$context = context_user::instance($plan->get('userid'));throw new required_capability_exception($context, 'moodle/competency:planmanage', 'nopermissions', '');}// Wrap the suppression in a DB transaction.$transaction = $DB->start_delegated_transaction();// Delete plan competencies.$plancomps = plan_competency::get_records(array('planid' => $plan->get('id')));foreach ($plancomps as $plancomp) {$plancomp->delete();}// Delete archive user competencies if the status of the plan is complete.if ($plan->get('status') == plan::STATUS_COMPLETE) {self::remove_archived_user_competencies_in_plan($plan);}$event = \core\event\competency_plan_deleted::create_from_plan($plan);$success = $plan->delete();$transaction->allow_commit();// Trigger deleted event.$event->trigger();return $success;}/*** Cancel the review of a plan.** @param int|plan $planorid The plan, or its ID.* @return bool*/public static function plan_cancel_review_request($planorid) {static::require_enabled();$plan = $planorid;if (!is_object($plan)) {$plan = new plan($plan);}// We need to be able to view the plan at least.if (!$plan->can_read()) {throw new required_capability_exception($plan->get_context(), 'moodle/competency:planview', 'nopermissions', '');}if ($plan->is_based_on_template()) {throw new coding_exception('Template plans cannot be reviewed.'); // This should never happen.} else if ($plan->get('status') != plan::STATUS_WAITING_FOR_REVIEW) {throw new coding_exception('The plan review cannot be cancelled at this stage.');} else if (!$plan->can_request_review()) {throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', '');}$plan->set('status', plan::STATUS_DRAFT);$result = $plan->update();// Trigger review request cancelled event.\core\event\competency_plan_review_request_cancelled::create_from_plan($plan)->trigger();return $result;}/*** Request the review of a plan.** @param int|plan $planorid The plan, or its ID.* @return bool*/public static function plan_request_review($planorid) {static::require_enabled();$plan = $planorid;if (!is_object($plan)) {$plan = new plan($plan);}// We need to be able to view the plan at least.if (!$plan->can_read()) {throw new required_capability_exception($plan->get_context(), 'moodle/competency:planview', 'nopermissions', '');}if ($plan->is_based_on_template()) {throw new coding_exception('Template plans cannot be reviewed.'); // This should never happen.} else if ($plan->get('status') != plan::STATUS_DRAFT) {throw new coding_exception('The plan cannot be sent for review at this stage.');} else if (!$plan->can_request_review()) {throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', '');}$plan->set('status', plan::STATUS_WAITING_FOR_REVIEW);$result = $plan->update();// Trigger review requested event.\core\event\competency_plan_review_requested::create_from_plan($plan)->trigger();return $result;}/*** Start the review of a plan.** @param int|plan $planorid The plan, or its ID.* @return bool*/public static function plan_start_review($planorid) {global $USER;static::require_enabled();$plan = $planorid;if (!is_object($plan)) {$plan = new plan($plan);}// We need to be able to view the plan at least.if (!$plan->can_read()) {throw new required_capability_exception($plan->get_context(), 'moodle/competency:planview', 'nopermissions', '');}if ($plan->is_based_on_template()) {throw new coding_exception('Template plans cannot be reviewed.'); // This should never happen.} else if ($plan->get('status') != plan::STATUS_WAITING_FOR_REVIEW) {throw new coding_exception('The plan review cannot be started at this stage.');} else if (!$plan->can_review()) {throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', '');}$plan->set('status', plan::STATUS_IN_REVIEW);$plan->set('reviewerid', $USER->id);$result = $plan->update();// Trigger review started event.\core\event\competency_plan_review_started::create_from_plan($plan)->trigger();return $result;}/*** Stop reviewing a plan.** @param int|plan $planorid The plan, or its ID.* @return bool*/public static function plan_stop_review($planorid) {static::require_enabled();$plan = $planorid;if (!is_object($plan)) {$plan = new plan($plan);}// We need to be able to view the plan at least.if (!$plan->can_read()) {throw new required_capability_exception($plan->get_context(), 'moodle/competency:planview', 'nopermissions', '');}if ($plan->is_based_on_template()) {throw new coding_exception('Template plans cannot be reviewed.'); // This should never happen.} else if ($plan->get('status') != plan::STATUS_IN_REVIEW) {throw new coding_exception('The plan review cannot be stopped at this stage.');} else if (!$plan->can_review()) {throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', '');}$plan->set('status', plan::STATUS_DRAFT);$plan->set('reviewerid', null);$result = $plan->update();// Trigger review stopped event.\core\event\competency_plan_review_stopped::create_from_plan($plan)->trigger();return $result;}/*** Approve a plan.** This means making the plan active.** @param int|plan $planorid The plan, or its ID.* @return bool*/public static function approve_plan($planorid) {static::require_enabled();$plan = $planorid;if (!is_object($plan)) {$plan = new plan($plan);}// We need to be able to view the plan at least.if (!$plan->can_read()) {throw new required_capability_exception($plan->get_context(), 'moodle/competency:planview', 'nopermissions', '');}// We can approve a plan that is either a draft, in review, or waiting for review.if ($plan->is_based_on_template()) {throw new coding_exception('Template plans are already approved.'); // This should never happen.} else if (!$plan->is_draft()) {throw new coding_exception('The plan cannot be approved at this stage.');} else if (!$plan->can_review()) {throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', '');}$plan->set('status', plan::STATUS_ACTIVE);$plan->set('reviewerid', null);$result = $plan->update();// Trigger approved event.\core\event\competency_plan_approved::create_from_plan($plan)->trigger();return $result;}/*** Unapprove a plan.** This means making the plan draft.** @param int|plan $planorid The plan, or its ID.* @return bool*/public static function unapprove_plan($planorid) {static::require_enabled();$plan = $planorid;if (!is_object($plan)) {$plan = new plan($plan);}// We need to be able to view the plan at least.if (!$plan->can_read()) {throw new required_capability_exception($plan->get_context(), 'moodle/competency:planview', 'nopermissions', '');}if ($plan->is_based_on_template()) {throw new coding_exception('Template plans are always approved.'); // This should never happen.} else if ($plan->get('status') != plan::STATUS_ACTIVE) {throw new coding_exception('The plan cannot be sent back to draft at this stage.');} else if (!$plan->can_review()) {throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', '');}$plan->set('status', plan::STATUS_DRAFT);$result = $plan->update();// Trigger unapproved event.\core\event\competency_plan_unapproved::create_from_plan($plan)->trigger();return $result;}/*** Complete a plan.** @param int|plan $planorid The plan, or its ID.* @return bool*/public static function complete_plan($planorid) {global $DB;static::require_enabled();$plan = $planorid;if (!is_object($planorid)) {$plan = new plan($planorid);}// Validate that the plan can be managed.if (!$plan->can_manage()) {throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', '');}// Check if the plan was already completed.if ($plan->get('status') == plan::STATUS_COMPLETE) {throw new coding_exception('The plan is already completed.');}$originalstatus = $plan->get('status');$plan->set('status', plan::STATUS_COMPLETE);// The user should also be able to manage the plan when it's completed.if (!$plan->can_manage()) {throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', '');}// Put back original status because archive needs it to extract competencies from the right table.$plan->set('status', $originalstatus);// Do the things.$transaction = $DB->start_delegated_transaction();self::archive_user_competencies_in_plan($plan);$plan->set('status', plan::STATUS_COMPLETE);$success = $plan->update();if (!$success) {$transaction->rollback(new moodle_exception('The plan could not be updated.'));return $success;}$transaction->allow_commit();// Trigger updated event.\core\event\competency_plan_completed::create_from_plan($plan)->trigger();return $success;}/*** Reopen a plan.** @param int|plan $planorid The plan, or its ID.* @return bool*/public static function reopen_plan($planorid) {global $DB;static::require_enabled();$plan = $planorid;if (!is_object($planorid)) {$plan = new plan($planorid);}// Validate that the plan as it is can be managed.if (!$plan->can_manage()) {$context = context_user::instance($plan->get('userid'));throw new required_capability_exception($context, 'moodle/competency:planmanage', 'nopermissions', '');}$beforestatus = $plan->get('status');$plan->set('status', plan::STATUS_ACTIVE);// Validate if status can be changed.if (!$plan->can_manage()) {$context = context_user::instance($plan->get('userid'));throw new required_capability_exception($context, 'moodle/competency:planmanage', 'nopermissions', '');}// Wrap the updates in a DB transaction.$transaction = $DB->start_delegated_transaction();// Delete archived user competencies if the status of the plan is changed from complete to another status.$mustremovearchivedcompetencies = ($beforestatus == plan::STATUS_COMPLETE && $plan->get('status') != plan::STATUS_COMPLETE);if ($mustremovearchivedcompetencies) {self::remove_archived_user_competencies_in_plan($plan);}// If duedate less than or equal to duedate_threshold unset it.if ($plan->get('duedate') <= time() + plan::DUEDATE_THRESHOLD) {$plan->set('duedate', 0);}$success = $plan->update();if (!$success) {$transaction->rollback(new moodle_exception('The plan could not be updated.'));return $success;}$transaction->allow_commit();// Trigger reopened event.\core\event\competency_plan_reopened::create_from_plan($plan)->trigger();return $success;}/*** Get a single competency from the user plan.** @param int $planorid The plan, or its ID.* @param int $competencyid The competency id.* @return (object) array(* 'competency' => \core_competency\competency,* 'usercompetency' => \core_competency\user_competency* 'usercompetencyplan' => \core_competency\user_competency_plan* )* The values of of keys usercompetency and usercompetencyplan cannot be defined at the same time.*/public static function get_plan_competency($planorid, $competencyid) {static::require_enabled();$plan = $planorid;if (!is_object($planorid)) {$plan = new plan($planorid);}if (!user_competency::can_read_user($plan->get('userid'))) {throw new required_capability_exception($plan->get_context(), 'moodle/competency:usercompetencyview','nopermissions', '');}$competency = $plan->get_competency($competencyid);// Get user competencies from user_competency_plan if the plan status is set to complete.$iscompletedplan = $plan->get('status') == plan::STATUS_COMPLETE;if ($iscompletedplan) {$usercompetencies = user_competency_plan::get_multiple($plan->get('userid'), $plan->get('id'), array($competencyid));$ucresultkey = 'usercompetencyplan';} else {$usercompetencies = user_competency::get_multiple($plan->get('userid'), array($competencyid));$ucresultkey = 'usercompetency';}$found = count($usercompetencies);if ($found) {$uc = array_pop($usercompetencies);} else {if ($iscompletedplan) {throw new coding_exception('A user competency plan is missing');} else {$uc = user_competency::create_relation($plan->get('userid'), $competency->get('id'));$uc->create();}}$plancompetency = (object) array('competency' => $competency,'usercompetency' => null,'usercompetencyplan' => null);$plancompetency->$ucresultkey = $uc;return $plancompetency;}/*** List the plans with a competency.** @param int $userid The user id we want the plans for.* @param int $competencyorid The competency, or its ID.* @return array[plan] Array of learning plans.*/public static function list_plans_with_competency($userid, $competencyorid) {global $USER;static::require_enabled();$competencyid = $competencyorid;$competency = null;if (is_object($competencyid)) {$competency = $competencyid;$competencyid = $competency->get('id');}$plans = plan::get_by_user_and_competency($userid, $competencyid);foreach ($plans as $index => $plan) {// Filter plans we cannot read.if (!$plan->can_read()) {unset($plans[$index]);}}return $plans;}/*** List the competencies in a user plan.** @param int $planorid The plan, or its ID.* @return array((object) array(* 'competency' => \core_competency\competency,* 'usercompetency' => \core_competency\user_competency* 'usercompetencyplan' => \core_competency\user_competency_plan* ))* The values of of keys usercompetency and usercompetencyplan cannot be defined at the same time.*/public static function list_plan_competencies($planorid) {static::require_enabled();$plan = $planorid;if (!is_object($planorid)) {$plan = new plan($planorid);}if (!$plan->can_read()) {$context = context_user::instance($plan->get('userid'));throw new required_capability_exception($context, 'moodle/competency:planview', 'nopermissions', '');}$result = array();$competencies = $plan->get_competencies();// Get user competencies from user_competency_plan if the plan status is set to complete.$iscompletedplan = $plan->get('status') == plan::STATUS_COMPLETE;if ($iscompletedplan) {$usercompetencies = user_competency_plan::get_multiple($plan->get('userid'), $plan->get('id'), $competencies);$ucresultkey = 'usercompetencyplan';} else {$usercompetencies = user_competency::get_multiple($plan->get('userid'), $competencies);$ucresultkey = 'usercompetency';}// Build the return values.foreach ($competencies as $key => $competency) {$found = false;foreach ($usercompetencies as $uckey => $uc) {if ($uc->get('competencyid') == $competency->get('id')) {$found = true;unset($usercompetencies[$uckey]);break;}}if (!$found) {if ($iscompletedplan) {throw new coding_exception('A user competency plan is missing');} else {$uc = user_competency::create_relation($plan->get('userid'), $competency->get('id'));}}$plancompetency = (object) array('competency' => $competency,'usercompetency' => null,'usercompetencyplan' => null);$plancompetency->$ucresultkey = $uc;$result[] = $plancompetency;}return $result;}/*** Add a competency to a plan.** @param int $planid The id of the plan* @param int $competencyid The id of the competency* @return bool*/public static function add_competency_to_plan($planid, $competencyid) {static::require_enabled();$plan = new plan($planid);// First we do a permissions check.if (!$plan->can_manage()) {throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', '');} else if ($plan->is_based_on_template()) {throw new coding_exception('A competency can not be added to a learning plan based on a template');}if (!$plan->can_be_edited()) {throw new coding_exception('A competency can not be added to a learning plan completed');}$competency = new competency($competencyid);// Can not add a competency that belong to a hidden framework.if ($competency->get_framework()->get('visible') == false) {throw new coding_exception('A competency belonging to hidden framework can not be added');}$exists = plan_competency::get_record(array('planid' => $planid, 'competencyid' => $competencyid));if (!$exists) {$record = new stdClass();$record->planid = $planid;$record->competencyid = $competencyid;$plancompetency = new plan_competency(0, $record);$plancompetency->create();}return true;}/*** Remove a competency from a plan.** @param int $planid The plan id* @param int $competencyid The id of the competency* @return bool*/public static function remove_competency_from_plan($planid, $competencyid) {static::require_enabled();$plan = new plan($planid);// First we do a permissions check.if (!$plan->can_manage()) {$context = context_user::instance($plan->get('userid'));throw new required_capability_exception($context, 'moodle/competency:planmanage', 'nopermissions', '');} else if ($plan->is_based_on_template()) {throw new coding_exception('A competency can not be removed from a learning plan based on a template');}if (!$plan->can_be_edited()) {throw new coding_exception('A competency can not be removed from a learning plan completed');}$link = plan_competency::get_record(array('planid' => $planid, 'competencyid' => $competencyid));if ($link) {return $link->delete();}return false;}/*** Move the plan competency up or down in the display list.** Requires moodle/competency:planmanage capability at the system context.** @param int $planid The plan id* @param int $competencyidfrom The id of the competency we are moving.* @param int $competencyidto The id of the competency we are moving to.* @return boolean*/public static function reorder_plan_competency($planid, $competencyidfrom, $competencyidto) {static::require_enabled();$plan = new plan($planid);// First we do a permissions check.if (!$plan->can_manage()) {$context = context_user::instance($plan->get('userid'));throw new required_capability_exception($context, 'moodle/competency:planmanage', 'nopermissions', '');} else if ($plan->is_based_on_template()) {throw new coding_exception('A competency can not be reordered in a learning plan based on a template');}if (!$plan->can_be_edited()) {throw new coding_exception('A competency can not be reordered in a learning plan completed');}$down = true;$matches = plan_competency::get_records(array('planid' => $planid, 'competencyid' => $competencyidfrom));if (count($matches) == 0) {throw new coding_exception('The link does not exist');}$competencyfrom = array_pop($matches);$matches = plan_competency::get_records(array('planid' => $planid, 'competencyid' => $competencyidto));if (count($matches) == 0) {throw new coding_exception('The link does not exist');}$competencyto = array_pop($matches);$all = plan_competency::get_records(array('planid' => $planid), 'sortorder', 'ASC', 0, 0);if ($competencyfrom->get('sortorder') > $competencyto->get('sortorder')) {// We are moving up, so put it before the "to" item.$down = false;}foreach ($all as $id => $plancompetency) {$sort = $plancompetency->get('sortorder');if ($down && $sort > $competencyfrom->get('sortorder') && $sort <= $competencyto->get('sortorder')) {$plancompetency->set('sortorder', $plancompetency->get('sortorder') - 1);$plancompetency->update();} else if (!$down && $sort >= $competencyto->get('sortorder') && $sort < $competencyfrom->get('sortorder')) {$plancompetency->set('sortorder', $plancompetency->get('sortorder') + 1);$plancompetency->update();}}$competencyfrom->set('sortorder', $competencyto->get('sortorder'));return $competencyfrom->update();}/*** Cancel a user competency review request.** @param int $userid The user ID.* @param int $competencyid The competency ID.* @return bool*/public static function user_competency_cancel_review_request($userid, $competencyid) {static::require_enabled();$context = context_user::instance($userid);$uc = user_competency::get_record(array('userid' => $userid, 'competencyid' => $competencyid));if (!$uc || !$uc->can_read()) {throw new required_capability_exception($context, 'moodle/competency:usercompetencyview', 'nopermissions', '');} else if ($uc->get('status') != user_competency::STATUS_WAITING_FOR_REVIEW) {throw new coding_exception('The competency can not be cancel review request at this stage.');} else if (!$uc->can_request_review()) {throw new required_capability_exception($context, 'moodle/competency:usercompetencyrequestreview', 'nopermissions', '');}$uc->set('status', user_competency::STATUS_IDLE);$result = $uc->update();if ($result) {\core\event\competency_user_competency_review_request_cancelled::create_from_user_competency($uc)->trigger();}return $result;}/*** Request a user competency review.** @param int $userid The user ID.* @param int $competencyid The competency ID.* @return bool*/public static function user_competency_request_review($userid, $competencyid) {static::require_enabled();$uc = user_competency::get_record(array('userid' => $userid, 'competencyid' => $competencyid));if (!$uc) {$uc = user_competency::create_relation($userid, $competencyid);$uc->create();}if (!$uc->can_read()) {throw new required_capability_exception($uc->get_context(), 'moodle/competency:usercompetencyview','nopermissions', '');} else if ($uc->get('status') != user_competency::STATUS_IDLE) {throw new coding_exception('The competency can not be sent for review at this stage.');} else if (!$uc->can_request_review()) {throw new required_capability_exception($uc->get_context(), 'moodle/competency:usercompetencyrequestreview','nopermissions', '');}$uc->set('status', user_competency::STATUS_WAITING_FOR_REVIEW);$result = $uc->update();if ($result) {\core\event\competency_user_competency_review_requested::create_from_user_competency($uc)->trigger();}return $result;}/*** Start a user competency review.** @param int $userid The user ID.* @param int $competencyid The competency ID.* @return bool*/public static function user_competency_start_review($userid, $competencyid) {global $USER;static::require_enabled();$context = context_user::instance($userid);$uc = user_competency::get_record(array('userid' => $userid, 'competencyid' => $competencyid));if (!$uc || !$uc->can_read()) {throw new required_capability_exception($context, 'moodle/competency:usercompetencyview', 'nopermissions', '');} else if ($uc->get('status') != user_competency::STATUS_WAITING_FOR_REVIEW) {throw new coding_exception('The competency review can not be started at this stage.');} else if (!$uc->can_review()) {throw new required_capability_exception($context, 'moodle/competency:usercompetencyreview', 'nopermissions', '');}$uc->set('status', user_competency::STATUS_IN_REVIEW);$uc->set('reviewerid', $USER->id);$result = $uc->update();if ($result) {\core\event\competency_user_competency_review_started::create_from_user_competency($uc)->trigger();}return $result;}/*** Stop a user competency review.** @param int $userid The user ID.* @param int $competencyid The competency ID.* @return bool*/public static function user_competency_stop_review($userid, $competencyid) {static::require_enabled();$context = context_user::instance($userid);$uc = user_competency::get_record(array('userid' => $userid, 'competencyid' => $competencyid));if (!$uc || !$uc->can_read()) {throw new required_capability_exception($context, 'moodle/competency:usercompetencyview', 'nopermissions', '');} else if ($uc->get('status') != user_competency::STATUS_IN_REVIEW) {throw new coding_exception('The competency review can not be stopped at this stage.');} else if (!$uc->can_review()) {throw new required_capability_exception($context, 'moodle/competency:usercompetencyreview', 'nopermissions', '');}$uc->set('status', user_competency::STATUS_IDLE);$result = $uc->update();if ($result) {\core\event\competency_user_competency_review_stopped::create_from_user_competency($uc)->trigger();}return $result;}/*** Log user competency viewed event.** @param user_competency|int $usercompetencyorid The user competency object or user competency id* @return bool*/public static function user_competency_viewed($usercompetencyorid) {static::require_enabled();$uc = $usercompetencyorid;if (!is_object($uc)) {$uc = new user_competency($uc);}if (!$uc || !$uc->can_read()) {throw new required_capability_exception($uc->get_context(), 'moodle/competency:usercompetencyview','nopermissions', '');}\core\event\competency_user_competency_viewed::create_from_user_competency_viewed($uc)->trigger();return true;}/*** Log user competency viewed in plan event.** @param user_competency|int $usercompetencyorid The user competency object or user competency id* @param int $planid The plan ID* @return bool*/public static function user_competency_viewed_in_plan($usercompetencyorid, $planid) {static::require_enabled();$uc = $usercompetencyorid;if (!is_object($uc)) {$uc = new user_competency($uc);}if (!$uc || !$uc->can_read()) {throw new required_capability_exception($uc->get_context(), 'moodle/competency:usercompetencyview','nopermissions', '');}$plan = new plan($planid);if ($plan->get('status') == plan::STATUS_COMPLETE) {throw new coding_exception('To log the user competency in completed plan use user_competency_plan_viewed method.');}\core\event\competency_user_competency_viewed_in_plan::create_from_user_competency_viewed_in_plan($uc, $planid)->trigger();return true;}/*** Log user competency viewed in course event.** @param user_competency_course|int $usercoursecompetencyorid The user competency course object or its ID.* @param int $courseid The course ID* @return bool*/public static function user_competency_viewed_in_course($usercoursecompetencyorid) {static::require_enabled();$ucc = $usercoursecompetencyorid;if (!is_object($ucc)) {$ucc = new user_competency_course($ucc);}if (!$ucc || !user_competency::can_read_user_in_course($ucc->get('userid'), $ucc->get('courseid'))) {throw new required_capability_exception($ucc->get_context(), 'moodle/competency:usercompetencyview','nopermissions', '');}// Validate the course, this will throw an exception if not valid.self::validate_course($ucc->get('courseid'));\core\event\competency_user_competency_viewed_in_course::create_from_user_competency_viewed_in_course($ucc)->trigger();return true;}/*** Log user competency plan viewed event.** @param user_competency_plan|int $usercompetencyplanorid The user competency plan object or user competency plan id* @return bool*/public static function user_competency_plan_viewed($usercompetencyplanorid) {static::require_enabled();$ucp = $usercompetencyplanorid;if (!is_object($ucp)) {$ucp = new user_competency_plan($ucp);}if (!$ucp || !user_competency::can_read_user($ucp->get('userid'))) {throw new required_capability_exception($ucp->get_context(), 'moodle/competency:usercompetencyview','nopermissions', '');}$plan = new plan($ucp->get('planid'));if ($plan->get('status') != plan::STATUS_COMPLETE) {throw new coding_exception('To log the user competency in non-completed plan use '. 'user_competency_viewed_in_plan method.');}\core\event\competency_user_competency_plan_viewed::create_from_user_competency_plan($ucp)->trigger();return true;}/*** Check if template has related data.** @param int $templateid The id of the template to check.* @return boolean*/public static function template_has_related_data($templateid) {static::require_enabled();// First we do a permissions check.$template = new template($templateid);if (!$template->can_read()) {throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview','nopermissions', '');}// OK - all set.return $template->has_plans();}/*** List all the related competencies.** @param int $competencyid The id of the competency to check.* @return competency[]*/public static function list_related_competencies($competencyid) {static::require_enabled();$competency = new competency($competencyid);if (!has_any_capability(array('moodle/competency:competencyview', 'moodle/competency:competencymanage'),$competency->get_context())) {throw new required_capability_exception($competency->get_context(), 'moodle/competency:competencyview','nopermissions', '');}return $competency->get_related_competencies();}/*** Add a related competency.** @param int $competencyid The id of the competency* @param int $relatedcompetencyid The id of the related competency.* @return bool False when create failed, true on success, or if the relation already existed.*/public static function add_related_competency($competencyid, $relatedcompetencyid) {static::require_enabled();$competency1 = new competency($competencyid);$competency2 = new competency($relatedcompetencyid);require_capability('moodle/competency:competencymanage', $competency1->get_context());$relatedcompetency = related_competency::get_relation($competency1->get('id'), $competency2->get('id'));if (!$relatedcompetency->get('id')) {$relatedcompetency->create();return true;}return true;}/*** Remove a related competency.** @param int $competencyid The id of the competency.* @param int $relatedcompetencyid The id of the related competency.* @return bool True when it was deleted, false when it wasn't or the relation doesn't exist.*/public static function remove_related_competency($competencyid, $relatedcompetencyid) {static::require_enabled();$competency = new competency($competencyid);// This only check if we have the permission in either competency because both competencies// should belong to the same framework.require_capability('moodle/competency:competencymanage', $competency->get_context());$relatedcompetency = related_competency::get_relation($competencyid, $relatedcompetencyid);if ($relatedcompetency->get('id')) {return $relatedcompetency->delete();}return false;}/*** Read a user evidence.** @param int $id* @return user_evidence*/public static function read_user_evidence($id) {static::require_enabled();$userevidence = new user_evidence($id);if (!$userevidence->can_read()) {$context = $userevidence->get_context();throw new required_capability_exception($context, 'moodle/competency:userevidenceview', 'nopermissions', '');}return $userevidence;}/*** Create a new user evidence.** @param object $data The data.* @param int $draftitemid The draft ID in which files have been saved.* @return user_evidence*/public static function create_user_evidence($data, $draftitemid = null) {static::require_enabled();$userevidence = new user_evidence(null, $data);$context = $userevidence->get_context();if (!$userevidence->can_manage()) {throw new required_capability_exception($context, 'moodle/competency:userevidencemanage', 'nopermissions', '');}$userevidence->create();if (!empty($draftitemid)) {$fileareaoptions = array('subdirs' => true);$itemid = $userevidence->get('id');file_save_draft_area_files($draftitemid, $context->id, 'core_competency', 'userevidence', $itemid, $fileareaoptions);}// Trigger an evidence of prior learning created event.\core\event\competency_user_evidence_created::create_from_user_evidence($userevidence)->trigger();return $userevidence;}/*** Create a new user evidence.** @param object $data The data.* @param int $draftitemid The draft ID in which files have been saved.* @return user_evidence*/public static function update_user_evidence($data, $draftitemid = null) {static::require_enabled();$userevidence = new user_evidence($data->id);$context = $userevidence->get_context();if (!$userevidence->can_manage()) {throw new required_capability_exception($context, 'moodle/competency:userevidencemanage', 'nopermissions', '');} else if (property_exists($data, 'userid') && $data->userid != $userevidence->get('userid')) {throw new coding_exception('Can not change the userid of a user evidence.');}$userevidence->from_record($data);$userevidence->update();if (!empty($draftitemid)) {$fileareaoptions = array('subdirs' => true);$itemid = $userevidence->get('id');file_save_draft_area_files($draftitemid, $context->id, 'core_competency', 'userevidence', $itemid, $fileareaoptions);}// Trigger an evidence of prior learning updated event.\core\event\competency_user_evidence_updated::create_from_user_evidence($userevidence)->trigger();return $userevidence;}/*** Delete a user evidence.** @param int $id The user evidence ID.* @return bool*/public static function delete_user_evidence($id) {static::require_enabled();$userevidence = new user_evidence($id);$context = $userevidence->get_context();if (!$userevidence->can_manage()) {throw new required_capability_exception($context, 'moodle/competency:userevidencemanage', 'nopermissions', '');}// Delete the user evidence.$userevidence->delete();// Delete associated files.$fs = get_file_storage();$fs->delete_area_files($context->id, 'core_competency', 'userevidence', $id);// Delete relation between evidence and competencies.$userevidence->set('id', $id); // Restore the ID to fully mock the object.$competencies = user_evidence_competency::get_competencies_by_userevidenceid($id);foreach ($competencies as $competency) {static::delete_user_evidence_competency($userevidence, $competency->get('id'));}// Trigger an evidence of prior learning deleted event.\core\event\competency_user_evidence_deleted::create_from_user_evidence($userevidence)->trigger();$userevidence->set('id', 0); // Restore the object.return true;}/*** List the user evidence of a user.** @param int $userid The user ID.* @return user_evidence[]*/public static function list_user_evidence($userid) {static::require_enabled();if (!user_evidence::can_read_user($userid)) {$context = context_user::instance($userid);throw new required_capability_exception($context, 'moodle/competency:userevidenceview', 'nopermissions', '');}$evidence = user_evidence::get_records(array('userid' => $userid), 'name');return $evidence;}/*** Link a user evidence with a competency.** @param user_evidence|int $userevidenceorid User evidence or its ID.* @param int $competencyid Competency ID.* @return user_evidence_competency*/public static function create_user_evidence_competency($userevidenceorid, $competencyid) {global $USER;static::require_enabled();$userevidence = $userevidenceorid;if (!is_object($userevidence)) {$userevidence = self::read_user_evidence($userevidence);}// Perform user evidence capability checks.if (!$userevidence->can_manage()) {$context = $userevidence->get_context();throw new required_capability_exception($context, 'moodle/competency:userevidencemanage', 'nopermissions', '');}// Perform competency capability checks.$competency = self::read_competency($competencyid);// Get (and create) the relation.$relation = user_evidence_competency::get_relation($userevidence->get('id'), $competency->get('id'));if (!$relation->get('id')) {$relation->create();$link = url::user_evidence($userevidence->get('id'));self::add_evidence($userevidence->get('userid'),$competency,$userevidence->get_context(),evidence::ACTION_LOG,'evidence_evidenceofpriorlearninglinked','core_competency',$userevidence->get('name'),false,$link->out(false),null,$USER->id);}return $relation;}/*** Delete a relationship between a user evidence and a competency.** @param user_evidence|int $userevidenceorid User evidence or its ID.* @param int $competencyid Competency ID.* @return bool*/public static function delete_user_evidence_competency($userevidenceorid, $competencyid) {global $USER;static::require_enabled();$userevidence = $userevidenceorid;if (!is_object($userevidence)) {$userevidence = self::read_user_evidence($userevidence);}// Perform user evidence capability checks.if (!$userevidence->can_manage()) {$context = $userevidence->get_context();throw new required_capability_exception($context, 'moodle/competency:userevidencemanage', 'nopermissions', '');}// Get (and delete) the relation.$relation = user_evidence_competency::get_relation($userevidence->get('id'), $competencyid);if (!$relation->get('id')) {return true;}$success = $relation->delete();if ($success) {self::add_evidence($userevidence->get('userid'),$competencyid,$userevidence->get_context(),evidence::ACTION_LOG,'evidence_evidenceofpriorlearningunlinked','core_competency',$userevidence->get('name'),false,null,null,$USER->id);}return $success;}/*** Send request review for user evidence competencies.** @param int $id The user evidence ID.* @return bool*/public static function request_review_of_user_evidence_linked_competencies($id) {$userevidence = new user_evidence($id);$context = $userevidence->get_context();$userid = $userevidence->get('userid');if (!$userevidence->can_manage()) {throw new required_capability_exception($context, 'moodle/competency:userevidencemanage', 'nopermissions', '');}$usercompetencies = user_evidence_competency::get_user_competencies_by_userevidenceid($id);foreach ($usercompetencies as $usercompetency) {if ($usercompetency->get('status') == user_competency::STATUS_IDLE) {static::user_competency_request_review($userid, $usercompetency->get('competencyid'));}}return true;}/*** Recursively duplicate competencies from a tree, we start duplicating from parents to children to have a correct path.* This method does not copy the related competencies.** @param int $frameworkid - framework id* @param stdClass[] $tree - list of framework competency nodes* @param int $oldparent - old parent id* @param int $newparent - new parent id* @return competency[] $matchids - List of old competencies ids matched with new competencies object.*/protected static function duplicate_competency_tree($frameworkid, $tree, $oldparent = 0, $newparent = 0) {$matchids = array();foreach ($tree as $node) {if ($node->competency->get('parentid') == $oldparent) {$parentid = $node->competency->get('id');// Create the competency.$competency = new competency(0, $node->competency->to_record());$competency->set('competencyframeworkid', $frameworkid);$competency->set('parentid', $newparent);$competency->set('path', '');$competency->set('id', 0);$competency->reset_rule();$competency->create();// Trigger the created event competency.\core\event\competency_created::create_from_competency($competency)->trigger();// Match the old id with the new one.$matchids[$parentid] = $competency;if (!empty($node->children)) {// Duplicate children competency.$childrenids = self::duplicate_competency_tree($frameworkid, $node->children, $parentid, $competency->get('id'));// Array_merge does not keep keys when merging so we use the + operator.$matchids = $matchids + $childrenids;}}}return $matchids;}/*** Recursively migrate competency rules.** @param array $tree - array of competencies object* @param competency[] $matchids - List of old competencies ids matched with new competencies object*/protected static function migrate_competency_tree_rules($tree, $matchids) {foreach ($tree as $node) {$oldcompid = $node->competency->get('id');if ($node->competency->get('ruletype') && array_key_exists($oldcompid, $matchids)) {try {// Get the new competency.$competency = $matchids[$oldcompid];$class = $node->competency->get('ruletype');$newruleconfig = $class::migrate_config($node->competency->get('ruleconfig'), $matchids);$competency->set('ruleconfig', $newruleconfig);$competency->set('ruletype', $class);$competency->set('ruleoutcome', $node->competency->get('ruleoutcome'));$competency->update();} catch (\Exception $e) {debugging('Could not migrate competency rule from: ' . $oldcompid . ' to: ' . $competency->get('id') . '.' .' Exception: ' . $e->getMessage(), DEBUG_DEVELOPER);$competency->reset_rule();}}if (!empty($node->children)) {self::migrate_competency_tree_rules($node->children, $matchids);}}}/*** Archive user competencies in a plan.** @param plan $plan The plan object.* @return void*/protected static function archive_user_competencies_in_plan($plan) {// Check if the plan was already completed.if ($plan->get('status') == plan::STATUS_COMPLETE) {throw new coding_exception('The plan is already completed.');}$competencies = $plan->get_competencies();$usercompetencies = user_competency::get_multiple($plan->get('userid'), $competencies);$i = 0;foreach ($competencies as $competency) {$found = false;foreach ($usercompetencies as $uckey => $uc) {if ($uc->get('competencyid') == $competency->get('id')) {$found = true;$ucprecord = $uc->to_record();$ucprecord->planid = $plan->get('id');$ucprecord->sortorder = $i;unset($ucprecord->id);unset($ucprecord->status);unset($ucprecord->reviewerid);$usercompetencyplan = new user_competency_plan(0, $ucprecord);$usercompetencyplan->create();unset($usercompetencies[$uckey]);break;}}// If the user competency doesn't exist, we create a new relation in user_competency_plan.if (!$found) {$usercompetencyplan = user_competency_plan::create_relation($plan->get('userid'), $competency->get('id'),$plan->get('id'));$usercompetencyplan->set('sortorder', $i);$usercompetencyplan->create();}$i++;}}/*** Delete archived user competencies in a plan.** @param plan $plan The plan object.* @return void*/protected static function remove_archived_user_competencies_in_plan($plan) {$competencies = $plan->get_competencies();$usercompetenciesplan = user_competency_plan::get_multiple($plan->get('userid'), $plan->get('id'), $competencies);foreach ($usercompetenciesplan as $ucpkey => $ucp) {$ucp->delete();}}/*** List all the evidence for a user competency.** @param int $userid The user id - only used if usercompetencyid is 0.* @param int $competencyid The competency id - only used it usercompetencyid is 0.* @param int $planid The plan id - not used yet - but can be used to only list archived evidence if a plan is completed.* @param string $sort The field to sort the evidence by.* @param string $order The ordering of the sorting.* @param int $skip Number of records to skip.* @param int $limit Number of records to return.* @return \core_competency\evidence[]* @return array of \core_competency\evidence*/public static function list_evidence($userid = 0, $competencyid = 0, $planid = 0, $sort = 'timecreated',$order = 'DESC', $skip = 0, $limit = 0) {static::require_enabled();if (!user_competency::can_read_user($userid)) {$context = context_user::instance($userid);throw new required_capability_exception($context, 'moodle/competency:usercompetencyview', 'nopermissions', '');}$usercompetency = user_competency::get_record(array('userid' => $userid, 'competencyid' => $competencyid));if (!$usercompetency) {return array();}$plancompleted = false;if ($planid != 0) {$plan = new plan($planid);if ($plan->get('status') == plan::STATUS_COMPLETE) {$plancompleted = true;}}$select = 'usercompetencyid = :usercompetencyid';$params = array('usercompetencyid' => $usercompetency->get('id'));if ($plancompleted) {$select .= ' AND timecreated <= :timecompleted';$params['timecompleted'] = $plan->get('timemodified');}$orderby = $sort . ' ' . $order;$orderby .= !empty($orderby) ? ', id DESC' : 'id DESC'; // Prevent random ordering.$evidence = evidence::get_records_select($select, $params, $orderby, '*', $skip, $limit);return $evidence;}/*** List all the evidence for a user competency in a course.** @param int $userid The user ID.* @param int $courseid The course ID.* @param int $competencyid The competency ID.* @param string $sort The field to sort the evidence by.* @param string $order The ordering of the sorting.* @param int $skip Number of records to skip.* @param int $limit Number of records to return.* @return \core_competency\evidence[]*/public static function list_evidence_in_course($userid = 0, $courseid = 0, $competencyid = 0, $sort = 'timecreated',$order = 'DESC', $skip = 0, $limit = 0) {static::require_enabled();if (!user_competency::can_read_user_in_course($userid, $courseid)) {$context = context_user::instance($userid);throw new required_capability_exception($context, 'moodle/competency:usercompetencyview', 'nopermissions', '');}$usercompetency = user_competency::get_record(array('userid' => $userid, 'competencyid' => $competencyid));if (!$usercompetency) {return array();}$context = context_course::instance($courseid);return evidence::get_records_for_usercompetency($usercompetency->get('id'), $context, $sort, $order, $skip, $limit);}/*** Create an evidence from a list of parameters.** Requires no capability because evidence can be added in many situations under any user.** @param int $userid The user id for which evidence is added.* @param competency|int $competencyorid The competency, or its id for which evidence is added.* @param context|int $contextorid The context in which the evidence took place.* @param int $action The type of action to take on the competency. \core_competency\evidence::ACTION_*.* @param string $descidentifier The strings identifier.* @param string $desccomponent The strings component.* @param mixed $desca Any arguments the string requires.* @param bool $recommend When true, the user competency will be sent for review.* @param string $url The url the evidence may link to.* @param int $grade The grade, or scale ID item.* @param int $actionuserid The ID of the user who took the action of adding the evidence. Null when system.* This should be used when the action was taken by a real person, this will allow* to keep track of all the evidence given by a certain person.* @param string $note A note to attach to the evidence.* @return evidence* @throws coding_exception* @throws invalid_persistent_exception* @throws moodle_exception*/public static function add_evidence($userid, $competencyorid, $contextorid, $action, $descidentifier, $desccomponent,$desca = null, $recommend = false, $url = null, $grade = null, $actionuserid = null,$note = null, $overridegrade = false) {global $DB;static::require_enabled();// Some clearly important variable assignments right there.$competencyid = $competencyorid;$competency = null;if (is_object($competencyid)) {$competency = $competencyid;$competencyid = $competency->get('id');}$contextid = $contextorid;$context = $contextorid;if (is_object($contextorid)) {$contextid = $contextorid->id;} else {$context = context::instance_by_id($contextorid);}$setucgrade = false;$ucgrade = null;$ucproficiency = null;$usercompetencycourse = null;// Fetch or create the user competency.$usercompetency = user_competency::get_record(array('userid' => $userid, 'competencyid' => $competencyid));if (!$usercompetency) {$usercompetency = user_competency::create_relation($userid, $competencyid);$usercompetency->create();}// What should we be doing?switch ($action) {// Completing a competency.case evidence::ACTION_COMPLETE:// The logic here goes like this://// if rating outside a course// - set the default grade and proficiency ONLY if there is no current grade// else we are in a course// - set the defautl grade and proficiency in the course ONLY if there is no current grade in the course// - then check the course settings to see if we should push the rating outside the course// - if we should push it// --- push it only if the user_competency (outside the course) has no grade// Done.if ($grade !== null) {throw new coding_exception("The grade MUST NOT be set with a 'completing' evidence.");}// Fetch the default grade to attach to the evidence.if (empty($competency)) {$competency = new competency($competencyid);}list($grade, $proficiency) = $competency->get_default_grade();// Add user_competency_course record when in a course or module.if (in_array($context->contextlevel, array(CONTEXT_COURSE, CONTEXT_MODULE))) {$coursecontext = $context->get_course_context();$courseid = $coursecontext->instanceid;$filterparams = array('userid' => $userid,'competencyid' => $competencyid,'courseid' => $courseid);// Fetch or create user competency course.$usercompetencycourse = user_competency_course::get_record($filterparams);if (!$usercompetencycourse) {$usercompetencycourse = user_competency_course::create_relation($userid, $competencyid, $courseid);$usercompetencycourse->create();}// Only update the grade and proficiency if there is not already a grade or the override option is enabled.if ($usercompetencycourse->get('grade') === null || $overridegrade) {// Set grade.$usercompetencycourse->set('grade', $grade);// Set proficiency.$usercompetencycourse->set('proficiency', $proficiency);}// Check the course settings to see if we should push to user plans.$coursesettings = course_competency_settings::get_by_courseid($courseid);$setucgrade = $coursesettings->get('pushratingstouserplans');if ($setucgrade) {// Only push to user plans if there is not already a grade or the override option is enabled.if ($usercompetency->get('grade') !== null && !$overridegrade) {$setucgrade = false;} else {$ucgrade = $grade;$ucproficiency = $proficiency;}}} else {// When completing the competency we fetch the default grade from the competency. But we only mark// the user competency when a grade has not been set yet or if override option is enabled.// Complete is an action to use with automated systems.if ($usercompetency->get('grade') === null || $overridegrade) {$setucgrade = true;$ucgrade = $grade;$ucproficiency = $proficiency;}}break;// We override the grade, even overriding back to not set.case evidence::ACTION_OVERRIDE:$setucgrade = true;$ucgrade = $grade;if (empty($competency)) {$competency = new competency($competencyid);}if ($ucgrade !== null) {$ucproficiency = $competency->get_proficiency_of_grade($ucgrade);}// Add user_competency_course record when in a course or module.if (in_array($context->contextlevel, array(CONTEXT_COURSE, CONTEXT_MODULE))) {$coursecontext = $context->get_course_context();$courseid = $coursecontext->instanceid;$filterparams = array('userid' => $userid,'competencyid' => $competencyid,'courseid' => $courseid);// Fetch or create user competency course.$usercompetencycourse = user_competency_course::get_record($filterparams);if (!$usercompetencycourse) {$usercompetencycourse = user_competency_course::create_relation($userid, $competencyid, $courseid);$usercompetencycourse->create();}// Get proficiency.$proficiency = $ucproficiency;if ($proficiency === null) {if (empty($competency)) {$competency = new competency($competencyid);}$proficiency = $competency->get_proficiency_of_grade($grade);}// Set grade.$usercompetencycourse->set('grade', $grade);// Set proficiency.$usercompetencycourse->set('proficiency', $proficiency);$coursesettings = course_competency_settings::get_by_courseid($courseid);if (!$coursesettings->get('pushratingstouserplans')) {$setucgrade = false;}}break;// Simply logging an evidence.case evidence::ACTION_LOG:if ($grade !== null) {throw new coding_exception("The grade MUST NOT be set when 'logging' an evidence.");}break;// Whoops, this is not expected.default:throw new coding_exception('Unexpected action parameter when registering an evidence.');break;}// Should we recommend?if ($recommend && $usercompetency->get('status') == user_competency::STATUS_IDLE) {$usercompetency->set('status', user_competency::STATUS_WAITING_FOR_REVIEW);}// Setting the grade and proficiency for the user competency.$wascompleted = false;if ($setucgrade == true) {if (!$usercompetency->get('proficiency') && $ucproficiency) {$wascompleted = true;}$usercompetency->set('grade', $ucgrade);$usercompetency->set('proficiency', $ucproficiency);}// Prepare the evidence.$record = new stdClass();$record->usercompetencyid = $usercompetency->get('id');$record->contextid = $contextid;$record->action = $action;$record->descidentifier = $descidentifier;$record->desccomponent = $desccomponent;$record->grade = $grade;$record->actionuserid = $actionuserid;$record->note = $note;$evidence = new evidence(0, $record);$evidence->set('desca', $desca);$evidence->set('url', $url);// Validate both models, we should not operate on one if the other will not save.if (!$usercompetency->is_valid()) {throw new invalid_persistent_exception($usercompetency->get_errors());} else if (!$evidence->is_valid()) {throw new invalid_persistent_exception($evidence->get_errors());}// Save the user_competency_course record.if ($usercompetencycourse !== null) {// Validate and update.if (!$usercompetencycourse->is_valid()) {throw new invalid_persistent_exception($usercompetencycourse->get_errors());}$usercompetencycourse->update();}// Finally save. Pheww!$usercompetency->update();$evidence->create();// Trigger the evidence_created event.\core\event\competency_evidence_created::create_from_evidence($evidence, $usercompetency, $recommend)->trigger();// The competency was marked as completed, apply the rules.if ($wascompleted) {self::apply_competency_rules_from_usercompetency($usercompetency, $competency, $overridegrade);}return $evidence;}/*** Read an evidence.* @param int $evidenceid The evidence ID.* @return evidence*/public static function read_evidence($evidenceid) {static::require_enabled();$evidence = new evidence($evidenceid);$uc = new user_competency($evidence->get('usercompetencyid'));if (!$uc->can_read()) {throw new required_capability_exception($uc->get_context(), 'moodle/competency:usercompetencyview','nopermissions', '');}return $evidence;}/*** Delete an evidence.** @param evidence|int $evidenceorid The evidence, or its ID.* @return bool*/public static function delete_evidence($evidenceorid) {$evidence = $evidenceorid;if (!is_object($evidence)) {$evidence = new evidence($evidenceorid);}$uc = new user_competency($evidence->get('usercompetencyid'));if (!evidence::can_delete_user($uc->get('userid'))) {throw new required_capability_exception($uc->get_context(), 'moodle/competency:evidencedelete', 'nopermissions', '');}return $evidence->delete();}/*** Apply the competency rules from a user competency.** The user competency passed should be one that was recently marked as complete.* A user competency is considered 'complete' when it's proficiency value is true.** This method will check if the parent of this usercompetency's competency has any* rules and if so will see if they match. When matched it will take the required* step to add evidence and trigger completion, etc...** @param user_competency $usercompetency The user competency recently completed.* @param competency|null $competency The competency of the user competency, useful to avoid unnecessary read.* @return void*/protected static function apply_competency_rules_from_usercompetency(user_competency $usercompetency,competency $competency = null, $overridegrade = false) {// Perform some basic checks.if (!$usercompetency->get('proficiency')) {throw new coding_exception('The user competency passed is not completed.');}if ($competency === null) {$competency = $usercompetency->get_competency();}if ($competency->get('id') != $usercompetency->get('competencyid')) {throw new coding_exception('Mismatch between user competency and competency.');}// Fetch the parent.$parent = $competency->get_parent();if ($parent === null) {return;}// The parent should have a rule, and a meaningful outcome.$ruleoutcome = $parent->get('ruleoutcome');if ($ruleoutcome == competency::OUTCOME_NONE) {return;}$rule = $parent->get_rule_object();if ($rule === null) {return;}// Fetch or create the user competency for the parent.$userid = $usercompetency->get('userid');$parentuc = user_competency::get_record(array('userid' => $userid, 'competencyid' => $parent->get('id')));if (!$parentuc) {$parentuc = user_competency::create_relation($userid, $parent->get('id'));$parentuc->create();}// Does the rule match?if (!$rule->matches($parentuc)) {return;}// Figuring out what to do.$recommend = false;if ($ruleoutcome == competency::OUTCOME_EVIDENCE) {$action = evidence::ACTION_LOG;} else if ($ruleoutcome == competency::OUTCOME_RECOMMEND) {$action = evidence::ACTION_LOG;$recommend = true;} else if ($ruleoutcome == competency::OUTCOME_COMPLETE) {$action = evidence::ACTION_COMPLETE;} else {throw new moodle_exception('Unexpected rule outcome: ' + $ruleoutcome);}// Finally add an evidence.static::add_evidence($userid,$parent,$parent->get_context()->id,$action,'evidence_competencyrule','core_competency',null,$recommend,null,null,null,null,$overridegrade);}/*** Observe when a course module is marked as completed.** Note that the user being logged in while this happens may be anyone.* Do not rely on capability checks here!** @param \core\event\course_module_completion_updated $event* @return void*/public static function observe_course_module_completion_updated(\core\event\course_module_completion_updated $event) {if (!static::is_enabled()) {return;}$eventdata = $event->get_record_snapshot('course_modules_completion', $event->objectid);if ($eventdata->completionstate == COMPLETION_COMPLETE|| $eventdata->completionstate == COMPLETION_COMPLETE_PASS) {$coursemodulecompetencies = course_module_competency::list_course_module_competencies($eventdata->coursemoduleid);$cm = get_coursemodule_from_id(null, $eventdata->coursemoduleid);$fastmodinfo = get_fast_modinfo($cm->course)->cms[$cm->id];$cmname = $fastmodinfo->name;$url = $fastmodinfo->url;foreach ($coursemodulecompetencies as $coursemodulecompetency) {$outcome = $coursemodulecompetency->get('ruleoutcome');$action = null;$recommend = false;$strdesc = 'evidence_coursemodulecompleted';$overridegrade = $coursemodulecompetency->get('overridegrade');if ($outcome == course_module_competency::OUTCOME_NONE) {continue;}if ($outcome == course_module_competency::OUTCOME_EVIDENCE) {$action = evidence::ACTION_LOG;} else if ($outcome == course_module_competency::OUTCOME_RECOMMEND) {$action = evidence::ACTION_LOG;$recommend = true;} else if ($outcome == course_module_competency::OUTCOME_COMPLETE) {$action = evidence::ACTION_COMPLETE;} else {throw new moodle_exception('Unexpected rule outcome: ' + $outcome);}static::add_evidence($event->relateduserid,$coursemodulecompetency->get('competencyid'),$event->contextid,$action,$strdesc,'core_competency',$cmname,$recommend,$url,null,null,null,$overridegrade);}}}/*** Observe when a course is marked as completed.** Note that the user being logged in while this happens may be anyone.* Do not rely on capability checks here!** @param \core\event\course_completed $event* @return void*/public static function observe_course_completed(\core\event\course_completed $event) {if (!static::is_enabled()) {return;}$sql = 'courseid = :courseid AND ruleoutcome != :nooutcome';$params = array('courseid' => $event->courseid,'nooutcome' => course_competency::OUTCOME_NONE);$coursecompetencies = course_competency::get_records_select($sql, $params);$course = get_course($event->courseid);$courseshortname = format_string($course->shortname, null, array('context' => $event->contextid));foreach ($coursecompetencies as $coursecompetency) {$outcome = $coursecompetency->get('ruleoutcome');$action = null;$recommend = false;$strdesc = 'evidence_coursecompleted';if ($outcome == course_module_competency::OUTCOME_NONE) {continue;}if ($outcome == course_competency::OUTCOME_EVIDENCE) {$action = evidence::ACTION_LOG;} else if ($outcome == course_competency::OUTCOME_RECOMMEND) {$action = evidence::ACTION_LOG;$recommend = true;} else if ($outcome == course_competency::OUTCOME_COMPLETE) {$action = evidence::ACTION_COMPLETE;} else {throw new moodle_exception('Unexpected rule outcome: ' + $outcome);}static::add_evidence($event->relateduserid,$coursecompetency->get('competencyid'),$event->contextid,$action,$strdesc,'core_competency',$courseshortname,$recommend,$event->get_url());}}/*** Action to perform when a course module is deleted.** Do not call this directly, this is reserved for core use.** @param stdClass $cm The CM object.* @return void*/public static function hook_course_module_deleted(stdClass $cm) {global $DB;$DB->delete_records(course_module_competency::TABLE, array('cmid' => $cm->id));}/*** Action to perform when a course is deleted.** Do not call this directly, this is reserved for core use.** @param stdClass $course The course object.* @return void*/public static function hook_course_deleted(stdClass $course) {global $DB;$DB->delete_records(course_competency::TABLE, array('courseid' => $course->id));$DB->delete_records(course_competency_settings::TABLE, array('courseid' => $course->id));$DB->delete_records(user_competency_course::TABLE, array('courseid' => $course->id));}/*** Action to perform when a course is being reset.** Do not call this directly, this is reserved for core use.** @param int $courseid The course ID.* @return void*/public static function hook_course_reset_competency_ratings($courseid) {global $DB;$DB->delete_records(user_competency_course::TABLE, array('courseid' => $courseid));}/*** Action to perform when a cohort is deleted.** Do not call this directly, this is reserved for core use.** @param \stdClass $cohort The cohort object.* @return void*/public static function hook_cohort_deleted(\stdClass $cohort) {global $DB;$DB->delete_records(template_cohort::TABLE, array('cohortid' => $cohort->id));}/*** Action to perform when a user is deleted.** @param int $userid The user id.*/public static function hook_user_deleted($userid) {global $DB;$usercompetencies = $DB->get_records(user_competency::TABLE, ['userid' => $userid], '', 'id');foreach ($usercompetencies as $usercomp) {$DB->delete_records(evidence::TABLE, ['usercompetencyid' => $usercomp->id]);}$DB->delete_records(user_competency::TABLE, ['userid' => $userid]);$DB->delete_records(user_competency_course::TABLE, ['userid' => $userid]);$DB->delete_records(user_competency_plan::TABLE, ['userid' => $userid]);// Delete any associated files.$fs = get_file_storage();$context = context_user::instance($userid);$userevidences = $DB->get_records(user_evidence::TABLE, ['userid' => $userid], '', 'id');foreach ($userevidences as $userevidence) {$DB->delete_records(user_evidence_competency::TABLE, ['userevidenceid' => $userevidence->id]);$DB->delete_records(user_evidence::TABLE, ['id' => $userevidence->id]);$fs->delete_area_files($context->id, 'core_competency', 'userevidence', $userevidence->id);}$userplans = $DB->get_records(plan::TABLE, ['userid' => $userid], '', 'id');foreach ($userplans as $userplan) {$DB->delete_records(plan_competency::TABLE, ['planid' => $userplan->id]);$DB->delete_records(plan::TABLE, ['id' => $userplan->id]);}}/*** Manually grade a user competency.** @param int $userid* @param int $competencyid* @param int $grade* @param string $note A note to attach to the evidence* @return array of \core_competency\user_competency*/public static function grade_competency($userid, $competencyid, $grade, $note = null) {global $USER;static::require_enabled();$uc = static::get_user_competency($userid, $competencyid);$context = $uc->get_context();if (!user_competency::can_grade_user($uc->get('userid'))) {throw new required_capability_exception($context, 'moodle/competency:competencygrade', 'nopermissions', '');}// Throws exception if competency not in plan.$competency = $uc->get_competency();$competencycontext = $competency->get_context();if (!has_any_capability(array('moodle/competency:competencyview', 'moodle/competency:competencymanage'),$competencycontext)) {throw new required_capability_exception($competencycontext, 'moodle/competency:competencyview', 'nopermissions', '');}$action = evidence::ACTION_OVERRIDE;$desckey = 'evidence_manualoverride';$result = self::add_evidence($uc->get('userid'),$competency,$context->id,$action,$desckey,'core_competency',null,false,null,$grade,$USER->id,$note);if ($result) {$uc->read();$event = \core\event\competency_user_competency_rated::create_from_user_competency($uc);$event->trigger();}return $result;}/*** Manually grade a user competency from the plans page.** @param mixed $planorid* @param int $competencyid* @param int $grade* @param string $note A note to attach to the evidence* @return array of \core_competency\user_competency*/public static function grade_competency_in_plan($planorid, $competencyid, $grade, $note = null) {global $USER;static::require_enabled();$plan = $planorid;if (!is_object($planorid)) {$plan = new plan($planorid);}$context = $plan->get_context();if (!user_competency::can_grade_user($plan->get('userid'))) {throw new required_capability_exception($context, 'moodle/competency:competencygrade', 'nopermissions', '');}// Throws exception if competency not in plan.$competency = $plan->get_competency($competencyid);$competencycontext = $competency->get_context();if (!has_any_capability(array('moodle/competency:competencyview', 'moodle/competency:competencymanage'),$competencycontext)) {throw new required_capability_exception($competencycontext, 'moodle/competency:competencyview', 'nopermissions', '');}$action = evidence::ACTION_OVERRIDE;$desckey = 'evidence_manualoverrideinplan';$result = self::add_evidence($plan->get('userid'),$competency,$context->id,$action,$desckey,'core_competency',$plan->get('name'),false,null,$grade,$USER->id,$note);if ($result) {$uc = static::get_user_competency($plan->get('userid'), $competency->get('id'));$event = \core\event\competency_user_competency_rated_in_plan::create_from_user_competency($uc, $plan->get('id'));$event->trigger();}return $result;}/*** Manually grade a user course competency from the course page.** This may push the rating to the user competency* if the course is configured this way.** @param mixed $courseorid* @param int $userid* @param int $competencyid* @param int $grade* @param string $note A note to attach to the evidence* @return array of \core_competency\user_competency*/public static function grade_competency_in_course($courseorid, $userid, $competencyid, $grade, $note = null) {global $USER, $DB;static::require_enabled();$course = $courseorid;if (!is_object($courseorid)) {$course = $DB->get_record('course', array('id' => $courseorid));}$context = context_course::instance($course->id);// Check that we can view the user competency details in the course.if (!user_competency::can_read_user_in_course($userid, $course->id)) {throw new required_capability_exception($context, 'moodle/competency:usercompetencyview', 'nopermissions', '');}// Validate the permission to grade.if (!user_competency::can_grade_user_in_course($userid, $course->id)) {throw new required_capability_exception($context, 'moodle/competency:competencygrade', 'nopermissions', '');}// Check that competency is in course and visible to the current user.$competency = course_competency::get_competency($course->id, $competencyid);$competencycontext = $competency->get_context();if (!has_any_capability(array('moodle/competency:competencyview', 'moodle/competency:competencymanage'),$competencycontext)) {throw new required_capability_exception($competencycontext, 'moodle/competency:competencyview', 'nopermissions', '');}// Check that the user is enrolled in the course, and is "gradable".if (!is_enrolled($context, $userid, 'moodle/competency:coursecompetencygradable')) {throw new coding_exception('The competency may not be rated at this time.');}$action = evidence::ACTION_OVERRIDE;$desckey = 'evidence_manualoverrideincourse';$result = self::add_evidence($userid,$competency,$context->id,$action,$desckey,'core_competency',$context->get_context_name(),false,null,$grade,$USER->id,$note);if ($result) {$all = user_competency_course::get_multiple($userid, $course->id, array($competency->get('id')));$uc = reset($all);$event = \core\event\competency_user_competency_rated_in_course::create_from_user_competency_course($uc);$event->trigger();}return $result;}/*** Count the plans in the template, filtered by status.** Requires moodle/competency:templateview capability at the system context.** @param mixed $templateorid The id or the template.* @param int $status One of the plan status constants (or 0 for all plans).* @return int*/public static function count_plans_for_template($templateorid, $status = 0) {static::require_enabled();$template = $templateorid;if (!is_object($template)) {$template = new template($template);}// First we do a permissions check.if (!$template->can_read()) {throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview','nopermissions', '');}return plan::count_records_for_template($template->get('id'), $status);}/*** Count the user-completency-plans in the template, optionally filtered by proficiency.** Requires moodle/competency:templateview capability at the system context.** @param mixed $templateorid The id or the template.* @param mixed $proficiency If true, filter by proficiency, if false filter by not proficient, if null - no filter.* @return int*/public static function count_user_competency_plans_for_template($templateorid, $proficiency = null) {static::require_enabled();$template = $templateorid;if (!is_object($template)) {$template = new template($template);}// First we do a permissions check.if (!$template->can_read()) {throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview','nopermissions', '');}return user_competency_plan::count_records_for_template($template->get('id'), $proficiency);}/*** List the plans in the template, filtered by status.** Requires moodle/competency:templateview capability at the system context.** @param mixed $templateorid The id or the template.* @param int $status One of the plan status constants (or 0 for all plans).* @param int $skip The number of records to skip* @param int $limit The max number of records to return* @return plan[]*/public static function list_plans_for_template($templateorid, $status = 0, $skip = 0, $limit = 100) {$template = $templateorid;if (!is_object($template)) {$template = new template($template);}// First we do a permissions check.if (!$template->can_read()) {throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview','nopermissions', '');}return plan::get_records_for_template($template->get('id'), $status, $skip, $limit);}/*** Get the most often not completed competency for this course.** Requires moodle/competency:coursecompetencyview capability at the course context.** @param int $courseid The course id* @param int $skip The number of records to skip* @param int $limit The max number of records to return* @return competency[]*/public static function get_least_proficient_competencies_for_course($courseid, $skip = 0, $limit = 100) {static::require_enabled();$coursecontext = context_course::instance($courseid);if (!has_any_capability(array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage'),$coursecontext)) {throw new required_capability_exception($coursecontext, 'moodle/competency:coursecompetencyview', 'nopermissions', '');}return user_competency_course::get_least_proficient_competencies_for_course($courseid, $skip, $limit);}/*** Get the most often not completed competency for this template.** Requires moodle/competency:templateview capability at the system context.** @param mixed $templateorid The id or the template.* @param int $skip The number of records to skip* @param int $limit The max number of records to return* @return competency[]*/public static function get_least_proficient_competencies_for_template($templateorid, $skip = 0, $limit = 100) {static::require_enabled();$template = $templateorid;if (!is_object($template)) {$template = new template($template);}// First we do a permissions check.if (!$template->can_read()) {throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview','nopermissions', '');}return user_competency_plan::get_least_proficient_competencies_for_template($template->get('id'), $skip, $limit);}/*** Template event viewed.** Requires moodle/competency:templateview capability at the system context.** @param mixed $templateorid The id or the template.* @return boolean*/public static function template_viewed($templateorid) {static::require_enabled();$template = $templateorid;if (!is_object($template)) {$template = new template($template);}// First we do a permissions check.if (!$template->can_read()) {throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview','nopermissions', '');}// Trigger a template viewed event.\core\event\competency_template_viewed::create_from_template($template)->trigger();return true;}/*** Get the competency settings for a course.** Requires moodle/competency:coursecompetencyview capability at the course context.** @param int $courseid The course id* @return course_competency_settings*/public static function read_course_competency_settings($courseid) {static::require_enabled();// First we do a permissions check.if (!course_competency_settings::can_read($courseid)) {$context = context_course::instance($courseid);throw new required_capability_exception($context, 'moodle/competency:coursecompetencyview', 'nopermissions', '');}return course_competency_settings::get_by_courseid($courseid);}/*** Update the competency settings for a course.** Requires moodle/competency:coursecompetencyconfigure capability at the course context.** @param int $courseid The course id* @param stdClass $settings List of settings. The only valid setting ATM is pushratginstouserplans (boolean).* @return bool*/public static function update_course_competency_settings($courseid, $settings) {static::require_enabled();$settings = (object) $settings;// Get all the valid settings.$pushratingstouserplans = isset($settings->pushratingstouserplans) ? $settings->pushratingstouserplans : false;// First we do a permissions check.if (!course_competency_settings::can_manage_course($courseid)) {$context = context_course::instance($courseid);throw new required_capability_exception($context, 'moodle/competency:coursecompetencyconfigure', 'nopermissions', '');}$exists = course_competency_settings::get_record(array('courseid' => $courseid));// Now update or insert.if ($exists) {$settings = $exists;$settings->set('pushratingstouserplans', $pushratingstouserplans);return $settings->update();} else {$data = (object) array('courseid' => $courseid, 'pushratingstouserplans' => $pushratingstouserplans);$settings = new course_competency_settings(0, $data);$result = $settings->create();return !empty($result);}}/*** Function used to return a list of users where the given user has a particular capability.** This is used e.g. to find all the users where someone is able to manage their learning plans,* it also would be useful for mentees etc.** @param string $capability - The capability string we are filtering for. If '' is passed,* an always matching filter is returned.* @param int $userid - The user id we are using for the access checks. Defaults to current user.* @param int $type - The type of named params to return (passed to $DB->get_in_or_equal).* @param string $prefix - The type prefix for the db table (passed to $DB->get_in_or_equal).* @return list($sql, $params) Same as $DB->get_in_or_equal().* @todo MDL-52243 Move this function to lib/accesslib.php*/public static function filter_users_with_capability_on_user_context_sql($capability, $userid = 0, $type = SQL_PARAMS_QM,$prefix='param') {global $USER, $DB;$allresultsfilter = array('> 0', array());$noresultsfilter = array('= -1', array());if (empty($capability)) {return $allresultsfilter;}if (!$capinfo = get_capability_info($capability)) {throw new coding_exception('Capability does not exist: ' . $capability);}if (empty($userid)) {$userid = $USER->id;}// Make sure the guest account and not-logged-in users never get any risky caps no matter what the actual settings are.if (($capinfo->captype === 'write') or ($capinfo->riskbitmask & (RISK_XSS | RISK_CONFIG | RISK_DATALOSS))) {if (isguestuser($userid) or $userid == 0) {return $noresultsfilter;}}if (is_siteadmin($userid)) {// No filtering for site admins.return $allresultsfilter;}// Check capability on system level.$syscontext = context_system::instance();$hassystem = has_capability($capability, $syscontext, $userid);$access = get_user_roles_sitewide_accessdata($userid);// Build up a list of level 2 contexts (candidates to be user context).$filtercontexts = array();// Build list of roles to check overrides.$roles = array();foreach ($access['ra'] as $path => $role) {$parts = explode('/', $path);if (count($parts) == 3) {$filtercontexts[$parts[2]] = $parts[2];} else if (count($parts) > 3) {// We know this is not a user context because there is another path with more than 2 levels.unset($filtercontexts[$parts[2]]);}$roles = array_merge($roles, $role);}// Add all contexts in which a role may be overidden.$rdefs = get_role_definitions($roles);foreach ($rdefs as $roledef) {foreach ($roledef as $path => $caps) {if (!isset($caps[$capability])) {// The capability is not mentioned, we can ignore.continue;}$parts = explode('/', $path);if (count($parts) === 3) {// Only get potential user contexts, they only ever have 2 slashes /parentId/Id.$filtercontexts[$parts[2]] = $parts[2];}}}// No interesting contexts - return all or no results.if (empty($filtercontexts)) {if ($hassystem) {return $allresultsfilter;} else {return $noresultsfilter;}}// Fetch all interesting contexts for further examination.list($insql, $params) = $DB->get_in_or_equal($filtercontexts, SQL_PARAMS_NAMED);$params['level'] = CONTEXT_USER;$fields = context_helper::get_preload_record_columns_sql('ctx');$interestingcontexts = $DB->get_recordset_sql('SELECT ' . $fields . 'FROM {context} ctxWHERE ctx.contextlevel = :levelAND ctx.id ' . $insql . 'ORDER BY ctx.id', $params);if ($hassystem) {// If allowed at system, search for exceptions prohibiting the capability at user context.$excludeusers = array();foreach ($interestingcontexts as $contextrecord) {$candidateuserid = $contextrecord->ctxinstance;context_helper::preload_from_record($contextrecord);$usercontext = context_user::instance($candidateuserid);// Has capability should use the data already preloaded.if (!has_capability($capability, $usercontext, $userid)) {$excludeusers[$candidateuserid] = $candidateuserid;}}// Construct SQL excluding users with this role assigned for this user.if (empty($excludeusers)) {$interestingcontexts->close();return $allresultsfilter;}list($sql, $params) = $DB->get_in_or_equal($excludeusers, $type, $prefix, false);} else {// If not allowed at system, search for exceptions allowing the capability at user context.$allowusers = array();foreach ($interestingcontexts as $contextrecord) {$candidateuserid = $contextrecord->ctxinstance;context_helper::preload_from_record($contextrecord);$usercontext = context_user::instance($candidateuserid);// Has capability should use the data already preloaded.if (has_capability($capability, $usercontext, $userid)) {$allowusers[$candidateuserid] = $candidateuserid;}}// Construct SQL excluding users with this role assigned for this user.if (empty($allowusers)) {$interestingcontexts->close();return $noresultsfilter;}list($sql, $params) = $DB->get_in_or_equal($allowusers, $type, $prefix);}$interestingcontexts->close();// Return the goods!.return array($sql, $params);}}