You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

5308 lines
202 KiB

<?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.id
FROM {scale} s
LEFT JOIN {" . competency_framework::TABLE ."} f
ON f.scaleid = :scaleid1
LEFT JOIN {" . competency::TABLE ."} c
ON c.scaleid = :scaleid2
WHERE f.id IS NOT NULL
OR 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 stdClass
*/
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;
}
/**
* 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_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 . "} uc
JOIN {" . competency::TABLE . "} c
ON c.id = uc.competencyid
JOIN {user} u
ON u.id = uc.userid
WHERE (uc.status = :waitingforreview
OR (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.
* @return bool True on success.
*/
public static function set_course_module_competency_ruleoutcome($coursemodulecompetencyorid, $ruleoutcome) {
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);
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 . "} p
JOIN {user} u
ON u.id = p.userid
LEFT JOIN {" . template::TABLE . "} t
ON t.id = p.templateid
WHERE (p.status = :waitingforreview
OR (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 (array_key_exists('userid', $data) && $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 competency[] $tree - array of competencies object
* @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 competency[] $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 int $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 int $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) {
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.
if ($usercompetencycourse->get('grade') === null) {
// 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.
if ($usercompetency->get('grade') !== null) {
$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. Complete is an action to use with automated systems.
if ($usercompetency->get('grade') === null) {
$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);
}
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) {
// 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
);
}
/**
* 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';
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
);
}
}
}
/**
* 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_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));
}
/**
* 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} ctx
WHERE ctx.contextlevel = :level
AND 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);
}
}