moodle/competency/classes/api.php
Matthew Hilton cfb643293c MDL-56567 competency: Course module competency option to override grade
Previously, if a course module had already been graded, course module
completion linked to the course module would not update. This
commit adds the option to enable overriding the grade at the course
module competency level.

This ensures that if enabled and a user completes a module associated
with a competency, that competency will be graded appropriately.
2022-10-17 08:00:20 +10:00

5388 lines
205 KiB
PHP

<?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;
}
/**
* Count the competencies associated to a course module.
*
* @param mixed $cmorid The course module, or its ID.
* @return int
*/
public static function count_course_module_competencies($cmorid) {
static::require_enabled();
$cm = $cmorid;
if (!is_object($cmorid)) {
$cm = get_coursemodule_from_id('', $cmorid, 0, true, MUST_EXIST);
}
// Check the user have access to the course module.
self::validate_course_module($cm);
$context = context_module::instance($cm->id);
$capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage');
if (!has_any_capability($capabilities, $context)) {
throw new required_capability_exception($context, 'moodle/competency:coursecompetencyview', 'nopermissions', '');
}
return course_module_competency::count_competencies($cm->id);
}
/**
* List the competencies associated to a course module.
*
* @param mixed $cmorid The course module, or its ID.
* @return array( array(
* 'competency' => \core_competency\competency,
* 'coursemodulecompetency' => \core_competency\course_module_competency
* ))
*/
public static function list_course_module_competencies($cmorid) {
static::require_enabled();
$cm = $cmorid;
if (!is_object($cmorid)) {
$cm = get_coursemodule_from_id('', $cmorid, 0, true, MUST_EXIST);
}
// Check the user have access to the course module.
self::validate_course_module($cm);
$context = context_module::instance($cm->id);
$capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage');
if (!has_any_capability($capabilities, $context)) {
throw new required_capability_exception($context, 'moodle/competency:coursecompetencyview', 'nopermissions', '');
}
$result = array();
// TODO We could improve the performance of this into one single query.
$coursemodulecompetencies = course_module_competency::list_course_module_competencies($cm->id);
$competencies = course_module_competency::list_competencies($cm->id);
// Build the return values.
foreach ($coursemodulecompetencies as $key => $coursemodulecompetency) {
$result[] = array(
'competency' => $competencies[$coursemodulecompetency->get('competencyid')],
'coursemodulecompetency' => $coursemodulecompetency
);
}
return $result;
}
/**
* Get a user competency in a course.
*
* @param int $courseid The id of the course to check.
* @param int $userid The id of the course to check.
* @param int $competencyid The id of the competency.
* @return user_competency_course
*/
public static function get_user_competency_in_course($courseid, $userid, $competencyid) {
static::require_enabled();
// First we do a permissions check.
$context = context_course::instance($courseid);
$capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage');
if (!has_any_capability($capabilities, $context)) {
throw new required_capability_exception($context, 'moodle/competency:coursecompetencyview', 'nopermissions', '');
} else if (!user_competency::can_read_user_in_course($userid, $courseid)) {
throw new required_capability_exception($context, 'moodle/competency:usercompetencyview', 'nopermissions', '');
}
// This will throw an exception if the competency does not belong to the course.
$competency = course_competency::get_competency($courseid, $competencyid);
$params = array('courseid' => $courseid, 'userid' => $userid, 'competencyid' => $competencyid);
$exists = user_competency_course::get_record($params);
// Create missing.
if ($exists) {
$ucc = $exists;
} else {
$ucc = user_competency_course::create_relation($userid, $competency->get('id'), $courseid);
$ucc->create();
}
return $ucc;
}
/**
* List all the user competencies in a course.
*
* @param int $courseid The id of the course to check.
* @param int $userid The id of the course to check.
* @return array of user_competency_course objects
*/
public static function list_user_competencies_in_course($courseid, $userid) {
static::require_enabled();
// First we do a permissions check.
$context = context_course::instance($courseid);
$onlyvisible = 1;
$capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage');
if (!has_any_capability($capabilities, $context)) {
throw new required_capability_exception($context, 'moodle/competency:coursecompetencyview', 'nopermissions', '');
} else if (!user_competency::can_read_user_in_course($userid, $courseid)) {
throw new required_capability_exception($context, 'moodle/competency:usercompetencyview', 'nopermissions', '');
}
// OK - all set.
$competencylist = course_competency::list_competencies($courseid, false);
$existing = user_competency_course::get_multiple($userid, $courseid, $competencylist);
// Create missing.
$orderedusercompetencycourses = array();
$somemissing = false;
foreach ($competencylist as $coursecompetency) {
$found = false;
foreach ($existing as $usercompetencycourse) {
if ($usercompetencycourse->get('competencyid') == $coursecompetency->get('id')) {
$found = true;
$orderedusercompetencycourses[$usercompetencycourse->get('id')] = $usercompetencycourse;
break;
}
}
if (!$found) {
$ucc = user_competency_course::create_relation($userid, $coursecompetency->get('id'), $courseid);
$ucc->create();
$orderedusercompetencycourses[$ucc->get('id')] = $ucc;
}
}
return $orderedusercompetencycourses;
}
/**
* List the user competencies to review.
*
* The method returns values in this format:
*
* array(
* 'competencies' => array(
* (stdClass)(
* 'usercompetency' => (user_competency),
* 'competency' => (competency),
* 'user' => (user)
* )
* ),
* 'count' => (int)
* )
*
* @param int $skip The number of records to skip.
* @param int $limit The number of results to return.
* @param int $userid The user we're getting the competencies to review for.
* @return array Containing the keys 'count', and 'competencies'. The 'competencies' key contains an object
* which contains 'competency', 'usercompetency' and 'user'.
*/
public static function list_user_competencies_to_review($skip = 0, $limit = 50, $userid = null) {
global $DB, $USER;
static::require_enabled();
if ($userid === null) {
$userid = $USER->id;
}
$capability = 'moodle/competency:usercompetencyreview';
$ucfields = user_competency::get_sql_fields('uc', 'uc_');
$compfields = competency::get_sql_fields('c', 'c_');
$usercols = array('id') + get_user_fieldnames();
$userfields = array();
foreach ($usercols as $field) {
$userfields[] = "u." . $field . " AS usr_" . $field;
}
$userfields = implode(',', $userfields);
$select = "SELECT $ucfields, $compfields, $userfields";
$countselect = "SELECT COUNT('x')";
$sql = " FROM {" . user_competency::TABLE . "} 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.
* @param bool $overridegrade If true, will override existing grades in related competencies.
* @return bool True on success.
*/
public static function set_course_module_competency_ruleoutcome($coursemodulecompetencyorid, $ruleoutcome,
$overridegrade = false) {
static::require_enabled();
$coursemodulecompetency = $coursemodulecompetencyorid;
if (!is_object($coursemodulecompetency)) {
$coursemodulecompetency = new course_module_competency($coursemodulecompetencyorid);
}
$cm = get_coursemodule_from_id('', $coursemodulecompetency->get('cmid'), 0, true, MUST_EXIST);
self::validate_course_module($cm);
$context = context_module::instance($cm->id);
require_capability('moodle/competency:coursecompetencymanage', $context);
$coursemodulecompetency->set('ruleoutcome', $ruleoutcome);
$coursemodulecompetency->set('overridegrade', $overridegrade);
return $coursemodulecompetency->update();
}
/**
* Add a competency to this course.
*
* @param int $courseid The id of the course
* @param int $competencyid The id of the competency
* @return bool
*/
public static function add_competency_to_course($courseid, $competencyid) {
static::require_enabled();
// Check the user have access to the course.
self::validate_course($courseid);
// First we do a permissions check.
$context = context_course::instance($courseid);
require_capability('moodle/competency:coursecompetencymanage', $context);
$record = new stdClass();
$record->courseid = $courseid;
$record->competencyid = $competencyid;
$competency = new competency($competencyid);
// Can not add a competency that belong to a hidden framework.
if ($competency->get_framework()->get('visible') == false) {
throw new coding_exception('A competency belonging to hidden framework can not be linked to course');
}
$coursecompetency = new course_competency();
$exists = $coursecompetency->get_records(array('courseid' => $courseid, 'competencyid' => $competencyid));
if (!$exists) {
$coursecompetency->from_record($record);
if ($coursecompetency->create()) {
return true;
}
}
return false;
}
/**
* Remove a competency from this course.
*
* @param int $courseid The id of the course
* @param int $competencyid The id of the competency
* @return bool
*/
public static function remove_competency_from_course($courseid, $competencyid) {
static::require_enabled();
// Check the user have access to the course.
self::validate_course($courseid);
// First we do a permissions check.
$context = context_course::instance($courseid);
require_capability('moodle/competency:coursecompetencymanage', $context);
$record = new stdClass();
$record->courseid = $courseid;
$record->competencyid = $competencyid;
$coursecompetency = new course_competency();
$exists = course_competency::get_record(array('courseid' => $courseid, 'competencyid' => $competencyid));
if ($exists) {
// Delete all course_module_competencies for this competency in this course.
$cmcs = course_module_competency::get_records_by_competencyid_in_course($competencyid, $courseid);
foreach ($cmcs as $cmc) {
$cmc->delete();
}
return $exists->delete();
}
return false;
}
/**
* Move the course competency up or down in the display list.
*
* Requires moodle/competency:coursecompetencymanage capability at the course context.
*
* @param int $courseid The course
* @param int $competencyidfrom The id of the competency we are moving.
* @param int $competencyidto The id of the competency we are moving to.
* @return boolean
*/
public static function reorder_course_competency($courseid, $competencyidfrom, $competencyidto) {
static::require_enabled();
// Check the user have access to the course.
self::validate_course($courseid);
// First we do a permissions check.
$context = context_course::instance($courseid);
require_capability('moodle/competency:coursecompetencymanage', $context);
$down = true;
$coursecompetency = new course_competency();
$matches = $coursecompetency->get_records(array('courseid' => $courseid, 'competencyid' => $competencyidfrom));
if (count($matches) == 0) {
throw new coding_exception('The link does not exist');
}
$competencyfrom = array_pop($matches);
$matches = $coursecompetency->get_records(array('courseid' => $courseid, 'competencyid' => $competencyidto));
if (count($matches) == 0) {
throw new coding_exception('The link does not exist');
}
$competencyto = array_pop($matches);
$all = $coursecompetency->get_records(array('courseid' => $courseid), 'sortorder', 'ASC', 0, 0);
if ($competencyfrom->get('sortorder') > $competencyto->get('sortorder')) {
// We are moving up, so put it before the "to" item.
$down = false;
}
foreach ($all as $id => $coursecompetency) {
$sort = $coursecompetency->get('sortorder');
if ($down && $sort > $competencyfrom->get('sortorder') && $sort <= $competencyto->get('sortorder')) {
$coursecompetency->set('sortorder', $coursecompetency->get('sortorder') - 1);
$coursecompetency->update();
} else if (!$down && $sort >= $competencyto->get('sortorder') && $sort < $competencyfrom->get('sortorder')) {
$coursecompetency->set('sortorder', $coursecompetency->get('sortorder') + 1);
$coursecompetency->update();
}
}
$competencyfrom->set('sortorder', $competencyto->get('sortorder'));
return $competencyfrom->update();
}
/**
* Update ruleoutcome value for a course competency.
*
* @param int|course_competency $coursecompetencyorid The course_competency, or its ID.
* @param int $ruleoutcome The value of ruleoutcome.
* @return bool True on success.
*/
public static function set_course_competency_ruleoutcome($coursecompetencyorid, $ruleoutcome) {
static::require_enabled();
$coursecompetency = $coursecompetencyorid;
if (!is_object($coursecompetency)) {
$coursecompetency = new course_competency($coursecompetencyorid);
}
$courseid = $coursecompetency->get('courseid');
self::validate_course($courseid);
$coursecontext = context_course::instance($courseid);
require_capability('moodle/competency:coursecompetencymanage', $coursecontext);
$coursecompetency->set('ruleoutcome', $ruleoutcome);
return $coursecompetency->update();
}
/**
* Create a learning plan template from a record containing all the data for the class.
*
* Requires moodle/competency:templatemanage capability.
*
* @param stdClass $record Record containing all the data for an instance of the class.
* @return template
*/
public static function create_template(stdClass $record) {
static::require_enabled();
$template = new template(0, $record);
// First we do a permissions check.
if (!$template->can_manage()) {
throw new required_capability_exception($template->get_context(), 'moodle/competency:templatemanage',
'nopermissions', '');
}
// OK - all set.
$template = $template->create();
// Trigger a template created event.
\core\event\competency_template_created::create_from_template($template)->trigger();
return $template;
}
/**
* Duplicate a learning plan template.
*
* Requires moodle/competency:templatemanage capability at the template context.
*
* @param int $id the template id.
* @return template
*/
public static function duplicate_template($id) {
static::require_enabled();
$template = new template($id);
// First we do a permissions check.
if (!$template->can_manage()) {
throw new required_capability_exception($template->get_context(), 'moodle/competency:templatemanage',
'nopermissions', '');
}
// OK - all set.
$competencies = template_competency::list_competencies($id, false);
// Adding the suffix copy.
$template->set('shortname', get_string('duplicateditemname', 'core_competency', $template->get('shortname')));
$template->set('id', 0);
$duplicatedtemplate = $template->create();
// Associate each competency for the duplicated template.
foreach ($competencies as $competency) {
self::add_competency_to_template($duplicatedtemplate->get('id'), $competency->get('id'));
}
// Trigger a template created event.
\core\event\competency_template_created::create_from_template($duplicatedtemplate)->trigger();
return $duplicatedtemplate;
}
/**
* Delete a learning plan template by id.
* If the learning plan template has associated cohorts they will be deleted.
*
* Requires moodle/competency:templatemanage capability.
*
* @param int $id The record to delete.
* @param boolean $deleteplans True to delete plans associaated to template, false to unlink them.
* @return boolean
*/
public static function delete_template($id, $deleteplans = true) {
global $DB;
static::require_enabled();
$template = new template($id);
// First we do a permissions check.
if (!$template->can_manage()) {
throw new required_capability_exception($template->get_context(), 'moodle/competency:templatemanage',
'nopermissions', '');
}
$transaction = $DB->start_delegated_transaction();
$success = true;
// Check if there are cohorts associated.
$templatecohorts = template_cohort::get_relations_by_templateid($template->get('id'));
foreach ($templatecohorts as $templatecohort) {
$success = $templatecohort->delete();
if (!$success) {
break;
}
}
// Still OK, delete or unlink the plans from the template.
if ($success) {
$plans = plan::get_records(array('templateid' => $template->get('id')));
foreach ($plans as $plan) {
$success = $deleteplans ? self::delete_plan($plan->get('id')) : self::unlink_plan_from_template($plan);
if (!$success) {
break;
}
}
}
// Still OK, delete the template comptencies.
if ($success) {
$success = template_competency::delete_by_templateid($template->get('id'));
}
// OK - all set.
if ($success) {
// Create a template deleted event.
$event = \core\event\competency_template_deleted::create_from_template($template);
$success = $template->delete();
}
if ($success) {
// Trigger a template deleted event.
$event->trigger();
// Commit the transaction.
$transaction->allow_commit();
} else {
$transaction->rollback(new moodle_exception('Error while deleting the template.'));
}
return $success;
}
/**
* Update the details for a learning plan template.
*
* Requires moodle/competency:templatemanage capability.
*
* @param stdClass $record The new details for the template. Note - must contain an id that points to the template to update.
* @return boolean
*/
public static function update_template($record) {
global $DB;
static::require_enabled();
$template = new template($record->id);
// First we do a permissions check.
if (!$template->can_manage()) {
throw new required_capability_exception($template->get_context(), 'moodle/competency:templatemanage',
'nopermissions', '');
} else if (isset($record->contextid) && $record->contextid != $template->get('contextid')) {
// We can never change the context of a template.
throw new coding_exception('Changing the context of an existing tempalte is forbidden.');
}
$updateplans = false;
$before = $template->to_record();
$template->from_record($record);
$after = $template->to_record();
// Should we update the related plans?
if ($before->duedate != $after->duedate ||
$before->shortname != $after->shortname ||
$before->description != $after->description ||
$before->descriptionformat != $after->descriptionformat) {
$updateplans = true;
}
$transaction = $DB->start_delegated_transaction();
$success = $template->update();
if (!$success) {
$transaction->rollback(new moodle_exception('Error while updating the template.'));
return $success;
}
// Trigger a template updated event.
\core\event\competency_template_updated::create_from_template($template)->trigger();
if ($updateplans) {
plan::update_multiple_from_template($template);
}
$transaction->allow_commit();
return $success;
}
/**
* Read a the details for a single learning plan template and return a record.
*
* Requires moodle/competency:templateview capability at the system context.
*
* @param int $id The id of the template to read.
* @return template
*/
public static function read_template($id) {
static::require_enabled();
$template = new template($id);
$context = $template->get_context();
// First we do a permissions check.
if (!$template->can_read()) {
throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview',
'nopermissions', '');
}
// OK - all set.
return $template;
}
/**
* Perform a search based on the provided filters and return a paginated list of records.
*
* Requires moodle/competency:templateview capability at the system context.
*
* @param string $sort The column to sort on
* @param string $order ('ASC' or 'DESC')
* @param int $skip Number of records to skip (pagination)
* @param int $limit Max of records to return (pagination)
* @param context $context The parent context of the frameworks.
* @param string $includes Defines what other contexts to fetch frameworks from.
* Accepted values are:
* - children: All descendants
* - parents: All parents, grand parents, etc...
* - self: Context passed only.
* @param bool $onlyvisible If should list only visible templates
* @return array of competency_framework
*/
public static function list_templates($sort, $order, $skip, $limit, $context, $includes = 'children', $onlyvisible = false) {
global $DB;
static::require_enabled();
// Get all the relevant contexts.
$contexts = self::get_related_contexts($context, $includes,
array('moodle/competency:templateview', 'moodle/competency:templatemanage'));
// First we do a permissions check.
if (empty($contexts)) {
throw new required_capability_exception($context, 'moodle/competency:templateview', 'nopermissions', '');
}
// Make the order by.
$orderby = '';
if (!empty($sort)) {
$orderby = $sort . ' ' . $order;
}
// OK - all set.
$template = new template();
list($insql, $params) = $DB->get_in_or_equal(array_keys($contexts), SQL_PARAMS_NAMED);
$select = "contextid $insql";
if ($onlyvisible) {
$select .= " AND visible = :visible";
$params['visible'] = 1;
}
return $template->get_records_select($select, $params, $orderby, '*', $skip, $limit);
}
/**
* Perform a search based on the provided filters and return how many results there are.
*
* Requires moodle/competency:templateview capability at the system context.
*
* @param context $context The parent context of the frameworks.
* @param string $includes Defines what other contexts to fetch frameworks from.
* Accepted values are:
* - children: All descendants
* - parents: All parents, grand parents, etc...
* - self: Context passed only.
* @return int
*/
public static function count_templates($context, $includes) {
global $DB;
static::require_enabled();
// First we do a permissions check.
$contexts = self::get_related_contexts($context, $includes,
array('moodle/competency:templateview', 'moodle/competency:templatemanage'));
if (empty($contexts)) {
throw new required_capability_exception($context, 'moodle/competency:templateview', 'nopermissions', '');
}
// OK - all set.
$template = new template();
list($insql, $inparams) = $DB->get_in_or_equal(array_keys($contexts), SQL_PARAMS_NAMED);
return $template->count_records_select("contextid $insql", $inparams);
}
/**
* Count all the templates using a competency.
*
* @param int $competencyid The id of the competency to check.
* @return int
*/
public static function count_templates_using_competency($competencyid) {
static::require_enabled();
// First we do a permissions check.
$context = context_system::instance();
$onlyvisible = 1;
$capabilities = array('moodle/competency:templateview', 'moodle/competency:templatemanage');
if (!has_any_capability($capabilities, $context)) {
throw new required_capability_exception($context, 'moodle/competency:templateview', 'nopermissions', '');
}
if (has_capability('moodle/competency:templatemanage', $context)) {
$onlyvisible = 0;
}
// OK - all set.
return template_competency::count_templates($competencyid, $onlyvisible);
}
/**
* List all the learning plan templatesd using a competency.
*
* @param int $competencyid The id of the competency to check.
* @return array[stdClass] Array of stdClass containing id and shortname.
*/
public static function list_templates_using_competency($competencyid) {
static::require_enabled();
// First we do a permissions check.
$context = context_system::instance();
$onlyvisible = 1;
$capabilities = array('moodle/competency:templateview', 'moodle/competency:templatemanage');
if (!has_any_capability($capabilities, $context)) {
throw new required_capability_exception($context, 'moodle/competency:templateview', 'nopermissions', '');
}
if (has_capability('moodle/competency:templatemanage', $context)) {
$onlyvisible = 0;
}
// OK - all set.
return template_competency::list_templates($competencyid, $onlyvisible);
}
/**
* Count all the competencies in a learning plan template.
*
* @param template|int $templateorid The template or its ID.
* @return int
*/
public static function count_competencies_in_template($templateorid) {
static::require_enabled();
// First we do a permissions check.
$template = $templateorid;
if (!is_object($template)) {
$template = new template($template);
}
if (!$template->can_read()) {
throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview',
'nopermissions', '');
}
// OK - all set.
return template_competency::count_competencies($template->get('id'));
}
/**
* Count all the competencies in a learning plan template with no linked courses.
*
* @param template|int $templateorid The template or its ID.
* @return int
*/
public static function count_competencies_in_template_with_no_courses($templateorid) {
// First we do a permissions check.
$template = $templateorid;
if (!is_object($template)) {
$template = new template($template);
}
if (!$template->can_read()) {
throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview',
'nopermissions', '');
}
// OK - all set.
return template_competency::count_competencies_with_no_courses($template->get('id'));
}
/**
* List all the competencies in a template.
*
* @param template|int $templateorid The template or its ID.
* @return array of competencies
*/
public static function list_competencies_in_template($templateorid) {
static::require_enabled();
// First we do a permissions check.
$template = $templateorid;
if (!is_object($template)) {
$template = new template($template);
}
if (!$template->can_read()) {
throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview',
'nopermissions', '');
}
// OK - all set.
return template_competency::list_competencies($template->get('id'));
}
/**
* Add a competency to this template.
*
* @param int $templateid The id of the template
* @param int $competencyid The id of the competency
* @return bool
*/
public static function add_competency_to_template($templateid, $competencyid) {
static::require_enabled();
// First we do a permissions check.
$template = new template($templateid);
if (!$template->can_manage()) {
throw new required_capability_exception($template->get_context(), 'moodle/competency:templatemanage',
'nopermissions', '');
}
$record = new stdClass();
$record->templateid = $templateid;
$record->competencyid = $competencyid;
$competency = new competency($competencyid);
// Can not add a competency that belong to a hidden framework.
if ($competency->get_framework()->get('visible') == false) {
throw new coding_exception('A competency belonging to hidden framework can not be added');
}
$exists = template_competency::get_records(array('templateid' => $templateid, 'competencyid' => $competencyid));
if (!$exists) {
$templatecompetency = new template_competency(0, $record);
$templatecompetency->create();
return true;
}
return false;
}
/**
* Remove a competency from this template.
*
* @param int $templateid The id of the template
* @param int $competencyid The id of the competency
* @return bool
*/
public static function remove_competency_from_template($templateid, $competencyid) {
static::require_enabled();
// First we do a permissions check.
$template = new template($templateid);
if (!$template->can_manage()) {
throw new required_capability_exception($template->get_context(), 'moodle/competency:templatemanage',
'nopermissions', '');
}
$record = new stdClass();
$record->templateid = $templateid;
$record->competencyid = $competencyid;
$competency = new competency($competencyid);
$exists = template_competency::get_records(array('templateid' => $templateid, 'competencyid' => $competencyid));
if ($exists) {
$link = array_pop($exists);
return $link->delete();
}
return false;
}
/**
* Move the template competency up or down in the display list.
*
* Requires moodle/competency:templatemanage capability at the system context.
*
* @param int $templateid The template id
* @param int $competencyidfrom The id of the competency we are moving.
* @param int $competencyidto The id of the competency we are moving to.
* @return boolean
*/
public static function reorder_template_competency($templateid, $competencyidfrom, $competencyidto) {
static::require_enabled();
$template = new template($templateid);
// First we do a permissions check.
if (!$template->can_manage()) {
throw new required_capability_exception($template->get_context(), 'moodle/competency:templatemanage',
'nopermissions', '');
}
$down = true;
$matches = template_competency::get_records(array('templateid' => $templateid, 'competencyid' => $competencyidfrom));
if (count($matches) == 0) {
throw new coding_exception('The link does not exist');
}
$competencyfrom = array_pop($matches);
$matches = template_competency::get_records(array('templateid' => $templateid, 'competencyid' => $competencyidto));
if (count($matches) == 0) {
throw new coding_exception('The link does not exist');
}
$competencyto = array_pop($matches);
$all = template_competency::get_records(array('templateid' => $templateid), 'sortorder', 'ASC', 0, 0);
if ($competencyfrom->get('sortorder') > $competencyto->get('sortorder')) {
// We are moving up, so put it before the "to" item.
$down = false;
}
foreach ($all as $id => $templatecompetency) {
$sort = $templatecompetency->get('sortorder');
if ($down && $sort > $competencyfrom->get('sortorder') && $sort <= $competencyto->get('sortorder')) {
$templatecompetency->set('sortorder', $templatecompetency->get('sortorder') - 1);
$templatecompetency->update();
} else if (!$down && $sort >= $competencyto->get('sortorder') && $sort < $competencyfrom->get('sortorder')) {
$templatecompetency->set('sortorder', $templatecompetency->get('sortorder') + 1);
$templatecompetency->update();
}
}
$competencyfrom->set('sortorder', $competencyto->get('sortorder'));
return $competencyfrom->update();
}
/**
* Create a relation between a template and a cohort.
*
* This silently ignores when the relation already existed.
*
* @param template|int $templateorid The template or its ID.
* @param stdClass|int $cohortorid The cohort ot its ID.
* @return template_cohort
*/
public static function create_template_cohort($templateorid, $cohortorid) {
global $DB;
static::require_enabled();
$template = $templateorid;
if (!is_object($template)) {
$template = new template($template);
}
require_capability('moodle/competency:templatemanage', $template->get_context());
$cohort = $cohortorid;
if (!is_object($cohort)) {
$cohort = $DB->get_record('cohort', array('id' => $cohort), '*', MUST_EXIST);
}
// Replicate logic in cohort_can_view_cohort() because we can't use it directly as we don't have a course context.
$cohortcontext = context::instance_by_id($cohort->contextid);
if (!$cohort->visible && !has_capability('moodle/cohort:view', $cohortcontext)) {
throw new required_capability_exception($cohortcontext, 'moodle/cohort:view', 'nopermissions', '');
}
$tplcohort = template_cohort::get_relation($template->get('id'), $cohort->id);
if (!$tplcohort->get('id')) {
$tplcohort->create();
}
return $tplcohort;
}
/**
* Remove a relation between a template and a cohort.
*
* @param template|int $templateorid The template or its ID.
* @param stdClass|int $cohortorid The cohort ot its ID.
* @return boolean True on success or when the relation did not exist.
*/
public static function delete_template_cohort($templateorid, $cohortorid) {
global $DB;
static::require_enabled();
$template = $templateorid;
if (!is_object($template)) {
$template = new template($template);
}
require_capability('moodle/competency:templatemanage', $template->get_context());
$cohort = $cohortorid;
if (!is_object($cohort)) {
$cohort = $DB->get_record('cohort', array('id' => $cohort), '*', MUST_EXIST);
}
$tplcohort = template_cohort::get_relation($template->get('id'), $cohort->id);
if (!$tplcohort->get('id')) {
return true;
}
return $tplcohort->delete();
}
/**
* Lists user plans.
*
* @param int $userid
* @return \core_competency\plan[]
*/
public static function list_user_plans($userid) {
global $DB, $USER;
static::require_enabled();
$select = 'userid = :userid';
$params = array('userid' => $userid);
$context = context_user::instance($userid);
// Check that we can read something here.
if (!plan::can_read_user($userid) && !plan::can_read_user_draft($userid)) {
throw new required_capability_exception($context, 'moodle/competency:planview', 'nopermissions', '');
}
// The user cannot view the drafts.
if (!plan::can_read_user_draft($userid)) {
list($insql, $inparams) = $DB->get_in_or_equal(plan::get_draft_statuses(), SQL_PARAMS_NAMED, 'param', false);
$select .= " AND status $insql";
$params += $inparams;
}
// The user cannot view the non-drafts.
if (!plan::can_read_user($userid)) {
list($insql, $inparams) = $DB->get_in_or_equal(array(plan::STATUS_ACTIVE, plan::STATUS_COMPLETE),
SQL_PARAMS_NAMED, 'param', false);
$select .= " AND status $insql";
$params += $inparams;
}
return plan::get_records_select($select, $params, 'name ASC');
}
/**
* List the plans to review.
*
* The method returns values in this format:
*
* array(
* 'plans' => array(
* (stdClass)(
* 'plan' => (plan),
* 'template' => (template),
* 'owner' => (stdClass)
* )
* ),
* 'count' => (int)
* )
*
* @param int $skip The number of records to skip.
* @param int $limit The number of results to return.
* @param int $userid The user we're getting the plans to review for.
* @return array Containing the keys 'count', and 'plans'. The 'plans' key contains an object
* which contains 'plan', 'template' and 'owner'.
*/
public static function list_plans_to_review($skip = 0, $limit = 100, $userid = null) {
global $DB, $USER;
static::require_enabled();
if ($userid === null) {
$userid = $USER->id;
}
$planfields = plan::get_sql_fields('p', 'plan_');
$tplfields = template::get_sql_fields('t', 'tpl_');
$usercols = array('id') + get_user_fieldnames();
$userfields = array();
foreach ($usercols as $field) {
$userfields[] = "u." . $field . " AS usr_" . $field;
}
$userfields = implode(',', $userfields);
$select = "SELECT $planfields, $tplfields, $userfields";
$countselect = "SELECT COUNT('x')";
$sql = " FROM {" . plan::TABLE . "} 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 (property_exists($data, 'userid') && $data->userid != $userevidence->get('userid')) {
throw new coding_exception('Can not change the userid of a user evidence.');
}
$userevidence->from_record($data);
$userevidence->update();
if (!empty($draftitemid)) {
$fileareaoptions = array('subdirs' => true);
$itemid = $userevidence->get('id');
file_save_draft_area_files($draftitemid, $context->id, 'core_competency', 'userevidence', $itemid, $fileareaoptions);
}
// Trigger an evidence of prior learning updated event.
\core\event\competency_user_evidence_updated::create_from_user_evidence($userevidence)->trigger();
return $userevidence;
}
/**
* Delete a user evidence.
*
* @param int $id The user evidence ID.
* @return bool
*/
public static function delete_user_evidence($id) {
static::require_enabled();
$userevidence = new user_evidence($id);
$context = $userevidence->get_context();
if (!$userevidence->can_manage()) {
throw new required_capability_exception($context, 'moodle/competency:userevidencemanage', 'nopermissions', '');
}
// Delete the user evidence.
$userevidence->delete();
// Delete associated files.
$fs = get_file_storage();
$fs->delete_area_files($context->id, 'core_competency', 'userevidence', $id);
// Delete relation between evidence and competencies.
$userevidence->set('id', $id); // Restore the ID to fully mock the object.
$competencies = user_evidence_competency::get_competencies_by_userevidenceid($id);
foreach ($competencies as $competency) {
static::delete_user_evidence_competency($userevidence, $competency->get('id'));
}
// Trigger an evidence of prior learning deleted event.
\core\event\competency_user_evidence_deleted::create_from_user_evidence($userevidence)->trigger();
$userevidence->set('id', 0); // Restore the object.
return true;
}
/**
* List the user evidence of a user.
*
* @param int $userid The user ID.
* @return user_evidence[]
*/
public static function list_user_evidence($userid) {
static::require_enabled();
if (!user_evidence::can_read_user($userid)) {
$context = context_user::instance($userid);
throw new required_capability_exception($context, 'moodle/competency:userevidenceview', 'nopermissions', '');
}
$evidence = user_evidence::get_records(array('userid' => $userid), 'name');
return $evidence;
}
/**
* Link a user evidence with a competency.
*
* @param user_evidence|int $userevidenceorid User evidence or its ID.
* @param int $competencyid Competency ID.
* @return user_evidence_competency
*/
public static function create_user_evidence_competency($userevidenceorid, $competencyid) {
global $USER;
static::require_enabled();
$userevidence = $userevidenceorid;
if (!is_object($userevidence)) {
$userevidence = self::read_user_evidence($userevidence);
}
// Perform user evidence capability checks.
if (!$userevidence->can_manage()) {
$context = $userevidence->get_context();
throw new required_capability_exception($context, 'moodle/competency:userevidencemanage', 'nopermissions', '');
}
// Perform competency capability checks.
$competency = self::read_competency($competencyid);
// Get (and create) the relation.
$relation = user_evidence_competency::get_relation($userevidence->get('id'), $competency->get('id'));
if (!$relation->get('id')) {
$relation->create();
$link = url::user_evidence($userevidence->get('id'));
self::add_evidence(
$userevidence->get('userid'),
$competency,
$userevidence->get_context(),
evidence::ACTION_LOG,
'evidence_evidenceofpriorlearninglinked',
'core_competency',
$userevidence->get('name'),
false,
$link->out(false),
null,
$USER->id
);
}
return $relation;
}
/**
* Delete a relationship between a user evidence and a competency.
*
* @param user_evidence|int $userevidenceorid User evidence or its ID.
* @param int $competencyid Competency ID.
* @return bool
*/
public static function delete_user_evidence_competency($userevidenceorid, $competencyid) {
global $USER;
static::require_enabled();
$userevidence = $userevidenceorid;
if (!is_object($userevidence)) {
$userevidence = self::read_user_evidence($userevidence);
}
// Perform user evidence capability checks.
if (!$userevidence->can_manage()) {
$context = $userevidence->get_context();
throw new required_capability_exception($context, 'moodle/competency:userevidencemanage', 'nopermissions', '');
}
// Get (and delete) the relation.
$relation = user_evidence_competency::get_relation($userevidence->get('id'), $competencyid);
if (!$relation->get('id')) {
return true;
}
$success = $relation->delete();
if ($success) {
self::add_evidence(
$userevidence->get('userid'),
$competencyid,
$userevidence->get_context(),
evidence::ACTION_LOG,
'evidence_evidenceofpriorlearningunlinked',
'core_competency',
$userevidence->get('name'),
false,
null,
null,
$USER->id
);
}
return $success;
}
/**
* Send request review for user evidence competencies.
*
* @param int $id The user evidence ID.
* @return bool
*/
public static function request_review_of_user_evidence_linked_competencies($id) {
$userevidence = new user_evidence($id);
$context = $userevidence->get_context();
$userid = $userevidence->get('userid');
if (!$userevidence->can_manage()) {
throw new required_capability_exception($context, 'moodle/competency:userevidencemanage', 'nopermissions', '');
}
$usercompetencies = user_evidence_competency::get_user_competencies_by_userevidenceid($id);
foreach ($usercompetencies as $usercompetency) {
if ($usercompetency->get('status') == user_competency::STATUS_IDLE) {
static::user_competency_request_review($userid, $usercompetency->get('competencyid'));
}
}
return true;
}
/**
* Recursively duplicate competencies from a tree, we start duplicating from parents to children to have a correct path.
* This method does not copy the related competencies.
*
* @param int $frameworkid - framework id
* @param 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, $overridegrade = false) {
global $DB;
static::require_enabled();
// Some clearly important variable assignments right there.
$competencyid = $competencyorid;
$competency = null;
if (is_object($competencyid)) {
$competency = $competencyid;
$competencyid = $competency->get('id');
}
$contextid = $contextorid;
$context = $contextorid;
if (is_object($contextorid)) {
$contextid = $contextorid->id;
} else {
$context = context::instance_by_id($contextorid);
}
$setucgrade = false;
$ucgrade = null;
$ucproficiency = null;
$usercompetencycourse = null;
// Fetch or create the user competency.
$usercompetency = user_competency::get_record(array('userid' => $userid, 'competencyid' => $competencyid));
if (!$usercompetency) {
$usercompetency = user_competency::create_relation($userid, $competencyid);
$usercompetency->create();
}
// What should we be doing?
switch ($action) {
// Completing a competency.
case evidence::ACTION_COMPLETE:
// The logic here goes like this:
//
// if rating outside a course
// - set the default grade and proficiency ONLY if there is no current grade
// else we are in a course
// - set the defautl grade and proficiency in the course ONLY if there is no current grade in the course
// - then check the course settings to see if we should push the rating outside the course
// - if we should push it
// --- push it only if the user_competency (outside the course) has no grade
// Done.
if ($grade !== null) {
throw new coding_exception("The grade MUST NOT be set with a 'completing' evidence.");
}
// Fetch the default grade to attach to the evidence.
if (empty($competency)) {
$competency = new competency($competencyid);
}
list($grade, $proficiency) = $competency->get_default_grade();
// Add user_competency_course record when in a course or module.
if (in_array($context->contextlevel, array(CONTEXT_COURSE, CONTEXT_MODULE))) {
$coursecontext = $context->get_course_context();
$courseid = $coursecontext->instanceid;
$filterparams = array(
'userid' => $userid,
'competencyid' => $competencyid,
'courseid' => $courseid
);
// Fetch or create user competency course.
$usercompetencycourse = user_competency_course::get_record($filterparams);
if (!$usercompetencycourse) {
$usercompetencycourse = user_competency_course::create_relation($userid, $competencyid, $courseid);
$usercompetencycourse->create();
}
// Only update the grade and proficiency if there is not already a grade or the override option is enabled.
if ($usercompetencycourse->get('grade') === null || $overridegrade) {
// Set grade.
$usercompetencycourse->set('grade', $grade);
// Set proficiency.
$usercompetencycourse->set('proficiency', $proficiency);
}
// Check the course settings to see if we should push to user plans.
$coursesettings = course_competency_settings::get_by_courseid($courseid);
$setucgrade = $coursesettings->get('pushratingstouserplans');
if ($setucgrade) {
// Only push to user plans if there is not already a grade or the override option is enabled.
if ($usercompetency->get('grade') !== null && !$overridegrade) {
$setucgrade = false;
} else {
$ucgrade = $grade;
$ucproficiency = $proficiency;
}
}
} else {
// When completing the competency we fetch the default grade from the competency. But we only mark
// the user competency when a grade has not been set yet or if override option is enabled.
// Complete is an action to use with automated systems.
if ($usercompetency->get('grade') === null || $overridegrade) {
$setucgrade = true;
$ucgrade = $grade;
$ucproficiency = $proficiency;
}
}
break;
// We override the grade, even overriding back to not set.
case evidence::ACTION_OVERRIDE:
$setucgrade = true;
$ucgrade = $grade;
if (empty($competency)) {
$competency = new competency($competencyid);
}
if ($ucgrade !== null) {
$ucproficiency = $competency->get_proficiency_of_grade($ucgrade);
}
// Add user_competency_course record when in a course or module.
if (in_array($context->contextlevel, array(CONTEXT_COURSE, CONTEXT_MODULE))) {
$coursecontext = $context->get_course_context();
$courseid = $coursecontext->instanceid;
$filterparams = array(
'userid' => $userid,
'competencyid' => $competencyid,
'courseid' => $courseid
);
// Fetch or create user competency course.
$usercompetencycourse = user_competency_course::get_record($filterparams);
if (!$usercompetencycourse) {
$usercompetencycourse = user_competency_course::create_relation($userid, $competencyid, $courseid);
$usercompetencycourse->create();
}
// Get proficiency.
$proficiency = $ucproficiency;
if ($proficiency === null) {
if (empty($competency)) {
$competency = new competency($competencyid);
}
$proficiency = $competency->get_proficiency_of_grade($grade);
}
// Set grade.
$usercompetencycourse->set('grade', $grade);
// Set proficiency.
$usercompetencycourse->set('proficiency', $proficiency);
$coursesettings = course_competency_settings::get_by_courseid($courseid);
if (!$coursesettings->get('pushratingstouserplans')) {
$setucgrade = false;
}
}
break;
// Simply logging an evidence.
case evidence::ACTION_LOG:
if ($grade !== null) {
throw new coding_exception("The grade MUST NOT be set when 'logging' an evidence.");
}
break;
// Whoops, this is not expected.
default:
throw new coding_exception('Unexpected action parameter when registering an evidence.');
break;
}
// Should we recommend?
if ($recommend && $usercompetency->get('status') == user_competency::STATUS_IDLE) {
$usercompetency->set('status', user_competency::STATUS_WAITING_FOR_REVIEW);
}
// Setting the grade and proficiency for the user competency.
$wascompleted = false;
if ($setucgrade == true) {
if (!$usercompetency->get('proficiency') && $ucproficiency) {
$wascompleted = true;
}
$usercompetency->set('grade', $ucgrade);
$usercompetency->set('proficiency', $ucproficiency);
}
// Prepare the evidence.
$record = new stdClass();
$record->usercompetencyid = $usercompetency->get('id');
$record->contextid = $contextid;
$record->action = $action;
$record->descidentifier = $descidentifier;
$record->desccomponent = $desccomponent;
$record->grade = $grade;
$record->actionuserid = $actionuserid;
$record->note = $note;
$evidence = new evidence(0, $record);
$evidence->set('desca', $desca);
$evidence->set('url', $url);
// Validate both models, we should not operate on one if the other will not save.
if (!$usercompetency->is_valid()) {
throw new invalid_persistent_exception($usercompetency->get_errors());
} else if (!$evidence->is_valid()) {
throw new invalid_persistent_exception($evidence->get_errors());
}
// Save the user_competency_course record.
if ($usercompetencycourse !== null) {
// Validate and update.
if (!$usercompetencycourse->is_valid()) {
throw new invalid_persistent_exception($usercompetencycourse->get_errors());
}
$usercompetencycourse->update();
}
// Finally save. Pheww!
$usercompetency->update();
$evidence->create();
// Trigger the evidence_created event.
\core\event\competency_evidence_created::create_from_evidence($evidence, $usercompetency, $recommend)->trigger();
// The competency was marked as completed, apply the rules.
if ($wascompleted) {
self::apply_competency_rules_from_usercompetency($usercompetency, $competency, $overridegrade);
}
return $evidence;
}
/**
* Read an evidence.
* @param int $evidenceid The evidence ID.
* @return evidence
*/
public static function read_evidence($evidenceid) {
static::require_enabled();
$evidence = new evidence($evidenceid);
$uc = new user_competency($evidence->get('usercompetencyid'));
if (!$uc->can_read()) {
throw new required_capability_exception($uc->get_context(), 'moodle/competency:usercompetencyview',
'nopermissions', '');
}
return $evidence;
}
/**
* Delete an evidence.
*
* @param evidence|int $evidenceorid The evidence, or its ID.
* @return bool
*/
public static function delete_evidence($evidenceorid) {
$evidence = $evidenceorid;
if (!is_object($evidence)) {
$evidence = new evidence($evidenceorid);
}
$uc = new user_competency($evidence->get('usercompetencyid'));
if (!evidence::can_delete_user($uc->get('userid'))) {
throw new required_capability_exception($uc->get_context(), 'moodle/competency:evidencedelete', 'nopermissions', '');
}
return $evidence->delete();
}
/**
* Apply the competency rules from a user competency.
*
* The user competency passed should be one that was recently marked as complete.
* A user competency is considered 'complete' when it's proficiency value is true.
*
* This method will check if the parent of this usercompetency's competency has any
* rules and if so will see if they match. When matched it will take the required
* step to add evidence and trigger completion, etc...
*
* @param user_competency $usercompetency The user competency recently completed.
* @param competency|null $competency The competency of the user competency, useful to avoid unnecessary read.
* @return void
*/
protected static function apply_competency_rules_from_usercompetency(user_competency $usercompetency,
competency $competency = null, $overridegrade = false) {
// Perform some basic checks.
if (!$usercompetency->get('proficiency')) {
throw new coding_exception('The user competency passed is not completed.');
}
if ($competency === null) {
$competency = $usercompetency->get_competency();
}
if ($competency->get('id') != $usercompetency->get('competencyid')) {
throw new coding_exception('Mismatch between user competency and competency.');
}
// Fetch the parent.
$parent = $competency->get_parent();
if ($parent === null) {
return;
}
// The parent should have a rule, and a meaningful outcome.
$ruleoutcome = $parent->get('ruleoutcome');
if ($ruleoutcome == competency::OUTCOME_NONE) {
return;
}
$rule = $parent->get_rule_object();
if ($rule === null) {
return;
}
// Fetch or create the user competency for the parent.
$userid = $usercompetency->get('userid');
$parentuc = user_competency::get_record(array('userid' => $userid, 'competencyid' => $parent->get('id')));
if (!$parentuc) {
$parentuc = user_competency::create_relation($userid, $parent->get('id'));
$parentuc->create();
}
// Does the rule match?
if (!$rule->matches($parentuc)) {
return;
}
// Figuring out what to do.
$recommend = false;
if ($ruleoutcome == competency::OUTCOME_EVIDENCE) {
$action = evidence::ACTION_LOG;
} else if ($ruleoutcome == competency::OUTCOME_RECOMMEND) {
$action = evidence::ACTION_LOG;
$recommend = true;
} else if ($ruleoutcome == competency::OUTCOME_COMPLETE) {
$action = evidence::ACTION_COMPLETE;
} else {
throw new moodle_exception('Unexpected rule outcome: ' + $ruleoutcome);
}
// Finally add an evidence.
static::add_evidence(
$userid,
$parent,
$parent->get_context()->id,
$action,
'evidence_competencyrule',
'core_competency',
null,
$recommend,
null,
null,
null,
null,
$overridegrade
);
}
/**
* Observe when a course module is marked as completed.
*
* Note that the user being logged in while this happens may be anyone.
* Do not rely on capability checks here!
*
* @param \core\event\course_module_completion_updated $event
* @return void
*/
public static function observe_course_module_completion_updated(\core\event\course_module_completion_updated $event) {
if (!static::is_enabled()) {
return;
}
$eventdata = $event->get_record_snapshot('course_modules_completion', $event->objectid);
if ($eventdata->completionstate == COMPLETION_COMPLETE
|| $eventdata->completionstate == COMPLETION_COMPLETE_PASS) {
$coursemodulecompetencies = course_module_competency::list_course_module_competencies($eventdata->coursemoduleid);
$cm = get_coursemodule_from_id(null, $eventdata->coursemoduleid);
$fastmodinfo = get_fast_modinfo($cm->course)->cms[$cm->id];
$cmname = $fastmodinfo->name;
$url = $fastmodinfo->url;
foreach ($coursemodulecompetencies as $coursemodulecompetency) {
$outcome = $coursemodulecompetency->get('ruleoutcome');
$action = null;
$recommend = false;
$strdesc = 'evidence_coursemodulecompleted';
$overridegrade = $coursemodulecompetency->get('overridegrade');
if ($outcome == course_module_competency::OUTCOME_NONE) {
continue;
}
if ($outcome == course_module_competency::OUTCOME_EVIDENCE) {
$action = evidence::ACTION_LOG;
} else if ($outcome == course_module_competency::OUTCOME_RECOMMEND) {
$action = evidence::ACTION_LOG;
$recommend = true;
} else if ($outcome == course_module_competency::OUTCOME_COMPLETE) {
$action = evidence::ACTION_COMPLETE;
} else {
throw new moodle_exception('Unexpected rule outcome: ' + $outcome);
}
static::add_evidence(
$event->relateduserid,
$coursemodulecompetency->get('competencyid'),
$event->contextid,
$action,
$strdesc,
'core_competency',
$cmname,
$recommend,
$url,
null,
null,
null,
$overridegrade
);
}
}
}
/**
* Observe when a course is marked as completed.
*
* Note that the user being logged in while this happens may be anyone.
* Do not rely on capability checks here!
*
* @param \core\event\course_completed $event
* @return void
*/
public static function observe_course_completed(\core\event\course_completed $event) {
if (!static::is_enabled()) {
return;
}
$sql = 'courseid = :courseid AND ruleoutcome != :nooutcome';
$params = array(
'courseid' => $event->courseid,
'nooutcome' => course_competency::OUTCOME_NONE
);
$coursecompetencies = course_competency::get_records_select($sql, $params);
$course = get_course($event->courseid);
$courseshortname = format_string($course->shortname, null, array('context' => $event->contextid));
foreach ($coursecompetencies as $coursecompetency) {
$outcome = $coursecompetency->get('ruleoutcome');
$action = null;
$recommend = false;
$strdesc = 'evidence_coursecompleted';
if ($outcome == course_module_competency::OUTCOME_NONE) {
continue;
}
if ($outcome == course_competency::OUTCOME_EVIDENCE) {
$action = evidence::ACTION_LOG;
} else if ($outcome == course_competency::OUTCOME_RECOMMEND) {
$action = evidence::ACTION_LOG;
$recommend = true;
} else if ($outcome == course_competency::OUTCOME_COMPLETE) {
$action = evidence::ACTION_COMPLETE;
} else {
throw new moodle_exception('Unexpected rule outcome: ' + $outcome);
}
static::add_evidence(
$event->relateduserid,
$coursecompetency->get('competencyid'),
$event->contextid,
$action,
$strdesc,
'core_competency',
$courseshortname,
$recommend,
$event->get_url()
);
}
}
/**
* Action to perform when a course module is deleted.
*
* Do not call this directly, this is reserved for core use.
*
* @param stdClass $cm The CM object.
* @return void
*/
public static function hook_course_module_deleted(stdClass $cm) {
global $DB;
$DB->delete_records(course_module_competency::TABLE, array('cmid' => $cm->id));
}
/**
* Action to perform when a course is deleted.
*
* Do not call this directly, this is reserved for core use.
*
* @param stdClass $course The course object.
* @return void
*/
public static function hook_course_deleted(stdClass $course) {
global $DB;
$DB->delete_records(course_competency::TABLE, array('courseid' => $course->id));
$DB->delete_records(course_competency_settings::TABLE, array('courseid' => $course->id));
$DB->delete_records(user_competency_course::TABLE, array('courseid' => $course->id));
}
/**
* Action to perform when a course is being reset.
*
* Do not call this directly, this is reserved for core use.
*
* @param int $courseid The course ID.
* @return void
*/
public static function hook_course_reset_competency_ratings($courseid) {
global $DB;
$DB->delete_records(user_competency_course::TABLE, array('courseid' => $courseid));
}
/**
* Action to perform when a cohort is deleted.
*
* Do not call this directly, this is reserved for core use.
*
* @param \stdClass $cohort The cohort object.
* @return void
*/
public static function hook_cohort_deleted(\stdClass $cohort) {
global $DB;
$DB->delete_records(template_cohort::TABLE, array('cohortid' => $cohort->id));
}
/**
* Action to perform when a user is deleted.
*
* @param int $userid The user id.
*/
public static function hook_user_deleted($userid) {
global $DB;
$usercompetencies = $DB->get_records(user_competency::TABLE, ['userid' => $userid], '', 'id');
foreach ($usercompetencies as $usercomp) {
$DB->delete_records(evidence::TABLE, ['usercompetencyid' => $usercomp->id]);
}
$DB->delete_records(user_competency::TABLE, ['userid' => $userid]);
$DB->delete_records(user_competency_course::TABLE, ['userid' => $userid]);
$DB->delete_records(user_competency_plan::TABLE, ['userid' => $userid]);
// Delete any associated files.
$fs = get_file_storage();
$context = context_user::instance($userid);
$userevidences = $DB->get_records(user_evidence::TABLE, ['userid' => $userid], '', 'id');
foreach ($userevidences as $userevidence) {
$DB->delete_records(user_evidence_competency::TABLE, ['userevidenceid' => $userevidence->id]);
$DB->delete_records(user_evidence::TABLE, ['id' => $userevidence->id]);
$fs->delete_area_files($context->id, 'core_competency', 'userevidence', $userevidence->id);
}
$userplans = $DB->get_records(plan::TABLE, ['userid' => $userid], '', 'id');
foreach ($userplans as $userplan) {
$DB->delete_records(plan_competency::TABLE, ['planid' => $userplan->id]);
$DB->delete_records(plan::TABLE, ['id' => $userplan->id]);
}
}
/**
* Manually grade a user competency.
*
* @param int $userid
* @param int $competencyid
* @param int $grade
* @param string $note A note to attach to the evidence
* @return array of \core_competency\user_competency
*/
public static function grade_competency($userid, $competencyid, $grade, $note = null) {
global $USER;
static::require_enabled();
$uc = static::get_user_competency($userid, $competencyid);
$context = $uc->get_context();
if (!user_competency::can_grade_user($uc->get('userid'))) {
throw new required_capability_exception($context, 'moodle/competency:competencygrade', 'nopermissions', '');
}
// Throws exception if competency not in plan.
$competency = $uc->get_competency();
$competencycontext = $competency->get_context();
if (!has_any_capability(array('moodle/competency:competencyview', 'moodle/competency:competencymanage'),
$competencycontext)) {
throw new required_capability_exception($competencycontext, 'moodle/competency:competencyview', 'nopermissions', '');
}
$action = evidence::ACTION_OVERRIDE;
$desckey = 'evidence_manualoverride';
$result = self::add_evidence($uc->get('userid'),
$competency,
$context->id,
$action,
$desckey,
'core_competency',
null,
false,
null,
$grade,
$USER->id,
$note);
if ($result) {
$uc->read();
$event = \core\event\competency_user_competency_rated::create_from_user_competency($uc);
$event->trigger();
}
return $result;
}
/**
* Manually grade a user competency from the plans page.
*
* @param mixed $planorid
* @param int $competencyid
* @param int $grade
* @param string $note A note to attach to the evidence
* @return array of \core_competency\user_competency
*/
public static function grade_competency_in_plan($planorid, $competencyid, $grade, $note = null) {
global $USER;
static::require_enabled();
$plan = $planorid;
if (!is_object($planorid)) {
$plan = new plan($planorid);
}
$context = $plan->get_context();
if (!user_competency::can_grade_user($plan->get('userid'))) {
throw new required_capability_exception($context, 'moodle/competency:competencygrade', 'nopermissions', '');
}
// Throws exception if competency not in plan.
$competency = $plan->get_competency($competencyid);
$competencycontext = $competency->get_context();
if (!has_any_capability(array('moodle/competency:competencyview', 'moodle/competency:competencymanage'),
$competencycontext)) {
throw new required_capability_exception($competencycontext, 'moodle/competency:competencyview', 'nopermissions', '');
}
$action = evidence::ACTION_OVERRIDE;
$desckey = 'evidence_manualoverrideinplan';
$result = self::add_evidence($plan->get('userid'),
$competency,
$context->id,
$action,
$desckey,
'core_competency',
$plan->get('name'),
false,
null,
$grade,
$USER->id,
$note);
if ($result) {
$uc = static::get_user_competency($plan->get('userid'), $competency->get('id'));
$event = \core\event\competency_user_competency_rated_in_plan::create_from_user_competency($uc, $plan->get('id'));
$event->trigger();
}
return $result;
}
/**
* Manually grade a user course competency from the course page.
*
* This may push the rating to the user competency
* if the course is configured this way.
*
* @param mixed $courseorid
* @param int $userid
* @param int $competencyid
* @param int $grade
* @param string $note A note to attach to the evidence
* @return array of \core_competency\user_competency
*/
public static function grade_competency_in_course($courseorid, $userid, $competencyid, $grade, $note = null) {
global $USER, $DB;
static::require_enabled();
$course = $courseorid;
if (!is_object($courseorid)) {
$course = $DB->get_record('course', array('id' => $courseorid));
}
$context = context_course::instance($course->id);
// Check that we can view the user competency details in the course.
if (!user_competency::can_read_user_in_course($userid, $course->id)) {
throw new required_capability_exception($context, 'moodle/competency:usercompetencyview', 'nopermissions', '');
}
// Validate the permission to grade.
if (!user_competency::can_grade_user_in_course($userid, $course->id)) {
throw new required_capability_exception($context, 'moodle/competency:competencygrade', 'nopermissions', '');
}
// Check that competency is in course and visible to the current user.
$competency = course_competency::get_competency($course->id, $competencyid);
$competencycontext = $competency->get_context();
if (!has_any_capability(array('moodle/competency:competencyview', 'moodle/competency:competencymanage'),
$competencycontext)) {
throw new required_capability_exception($competencycontext, 'moodle/competency:competencyview', 'nopermissions', '');
}
// Check that the user is enrolled in the course, and is "gradable".
if (!is_enrolled($context, $userid, 'moodle/competency:coursecompetencygradable')) {
throw new coding_exception('The competency may not be rated at this time.');
}
$action = evidence::ACTION_OVERRIDE;
$desckey = 'evidence_manualoverrideincourse';
$result = self::add_evidence($userid,
$competency,
$context->id,
$action,
$desckey,
'core_competency',
$context->get_context_name(),
false,
null,
$grade,
$USER->id,
$note);
if ($result) {
$all = user_competency_course::get_multiple($userid, $course->id, array($competency->get('id')));
$uc = reset($all);
$event = \core\event\competency_user_competency_rated_in_course::create_from_user_competency_course($uc);
$event->trigger();
}
return $result;
}
/**
* Count the plans in the template, filtered by status.
*
* Requires moodle/competency:templateview capability at the system context.
*
* @param mixed $templateorid The id or the template.
* @param int $status One of the plan status constants (or 0 for all plans).
* @return int
*/
public static function count_plans_for_template($templateorid, $status = 0) {
static::require_enabled();
$template = $templateorid;
if (!is_object($template)) {
$template = new template($template);
}
// First we do a permissions check.
if (!$template->can_read()) {
throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview',
'nopermissions', '');
}
return plan::count_records_for_template($template->get('id'), $status);
}
/**
* Count the user-completency-plans in the template, optionally filtered by proficiency.
*
* Requires moodle/competency:templateview capability at the system context.
*
* @param mixed $templateorid The id or the template.
* @param mixed $proficiency If true, filter by proficiency, if false filter by not proficient, if null - no filter.
* @return int
*/
public static function count_user_competency_plans_for_template($templateorid, $proficiency = null) {
static::require_enabled();
$template = $templateorid;
if (!is_object($template)) {
$template = new template($template);
}
// First we do a permissions check.
if (!$template->can_read()) {
throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview',
'nopermissions', '');
}
return user_competency_plan::count_records_for_template($template->get('id'), $proficiency);
}
/**
* List the plans in the template, filtered by status.
*
* Requires moodle/competency:templateview capability at the system context.
*
* @param mixed $templateorid The id or the template.
* @param int $status One of the plan status constants (or 0 for all plans).
* @param int $skip The number of records to skip
* @param int $limit The max number of records to return
* @return plan[]
*/
public static function list_plans_for_template($templateorid, $status = 0, $skip = 0, $limit = 100) {
$template = $templateorid;
if (!is_object($template)) {
$template = new template($template);
}
// First we do a permissions check.
if (!$template->can_read()) {
throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview',
'nopermissions', '');
}
return plan::get_records_for_template($template->get('id'), $status, $skip, $limit);
}
/**
* Get the most often not completed competency for this course.
*
* Requires moodle/competency:coursecompetencyview capability at the course context.
*
* @param int $courseid The course id
* @param int $skip The number of records to skip
* @param int $limit The max number of records to return
* @return competency[]
*/
public static function get_least_proficient_competencies_for_course($courseid, $skip = 0, $limit = 100) {
static::require_enabled();
$coursecontext = context_course::instance($courseid);
if (!has_any_capability(array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage'),
$coursecontext)) {
throw new required_capability_exception($coursecontext, 'moodle/competency:coursecompetencyview', 'nopermissions', '');
}
return user_competency_course::get_least_proficient_competencies_for_course($courseid, $skip, $limit);
}
/**
* Get the most often not completed competency for this template.
*
* Requires moodle/competency:templateview capability at the system context.
*
* @param mixed $templateorid The id or the template.
* @param int $skip The number of records to skip
* @param int $limit The max number of records to return
* @return competency[]
*/
public static function get_least_proficient_competencies_for_template($templateorid, $skip = 0, $limit = 100) {
static::require_enabled();
$template = $templateorid;
if (!is_object($template)) {
$template = new template($template);
}
// First we do a permissions check.
if (!$template->can_read()) {
throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview',
'nopermissions', '');
}
return user_competency_plan::get_least_proficient_competencies_for_template($template->get('id'), $skip, $limit);
}
/**
* Template event viewed.
*
* Requires moodle/competency:templateview capability at the system context.
*
* @param mixed $templateorid The id or the template.
* @return boolean
*/
public static function template_viewed($templateorid) {
static::require_enabled();
$template = $templateorid;
if (!is_object($template)) {
$template = new template($template);
}
// First we do a permissions check.
if (!$template->can_read()) {
throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview',
'nopermissions', '');
}
// Trigger a template viewed event.
\core\event\competency_template_viewed::create_from_template($template)->trigger();
return true;
}
/**
* Get the competency settings for a course.
*
* Requires moodle/competency:coursecompetencyview capability at the course context.
*
* @param int $courseid The course id
* @return course_competency_settings
*/
public static function read_course_competency_settings($courseid) {
static::require_enabled();
// First we do a permissions check.
if (!course_competency_settings::can_read($courseid)) {
$context = context_course::instance($courseid);
throw new required_capability_exception($context, 'moodle/competency:coursecompetencyview', 'nopermissions', '');
}
return course_competency_settings::get_by_courseid($courseid);
}
/**
* Update the competency settings for a course.
*
* Requires moodle/competency:coursecompetencyconfigure capability at the course context.
*
* @param int $courseid The course id
* @param stdClass $settings List of settings. The only valid setting ATM is pushratginstouserplans (boolean).
* @return bool
*/
public static function update_course_competency_settings($courseid, $settings) {
static::require_enabled();
$settings = (object) $settings;
// Get all the valid settings.
$pushratingstouserplans = isset($settings->pushratingstouserplans) ? $settings->pushratingstouserplans : false;
// First we do a permissions check.
if (!course_competency_settings::can_manage_course($courseid)) {
$context = context_course::instance($courseid);
throw new required_capability_exception($context, 'moodle/competency:coursecompetencyconfigure', 'nopermissions', '');
}
$exists = course_competency_settings::get_record(array('courseid' => $courseid));
// Now update or insert.
if ($exists) {
$settings = $exists;
$settings->set('pushratingstouserplans', $pushratingstouserplans);
return $settings->update();
} else {
$data = (object) array('courseid' => $courseid, 'pushratingstouserplans' => $pushratingstouserplans);
$settings = new course_competency_settings(0, $data);
$result = $settings->create();
return !empty($result);
}
}
/**
* Function used to return a list of users where the given user has a particular capability.
*
* This is used e.g. to find all the users where someone is able to manage their learning plans,
* it also would be useful for mentees etc.
*
* @param string $capability - The capability string we are filtering for. If '' is passed,
* an always matching filter is returned.
* @param int $userid - The user id we are using for the access checks. Defaults to current user.
* @param int $type - The type of named params to return (passed to $DB->get_in_or_equal).
* @param string $prefix - The type prefix for the db table (passed to $DB->get_in_or_equal).
* @return list($sql, $params) Same as $DB->get_in_or_equal().
* @todo MDL-52243 Move this function to lib/accesslib.php
*/
public static function filter_users_with_capability_on_user_context_sql($capability, $userid = 0, $type = SQL_PARAMS_QM,
$prefix='param') {
global $USER, $DB;
$allresultsfilter = array('> 0', array());
$noresultsfilter = array('= -1', array());
if (empty($capability)) {
return $allresultsfilter;
}
if (!$capinfo = get_capability_info($capability)) {
throw new coding_exception('Capability does not exist: ' . $capability);
}
if (empty($userid)) {
$userid = $USER->id;
}
// Make sure the guest account and not-logged-in users never get any risky caps no matter what the actual settings are.
if (($capinfo->captype === 'write') or ($capinfo->riskbitmask & (RISK_XSS | RISK_CONFIG | RISK_DATALOSS))) {
if (isguestuser($userid) or $userid == 0) {
return $noresultsfilter;
}
}
if (is_siteadmin($userid)) {
// No filtering for site admins.
return $allresultsfilter;
}
// Check capability on system level.
$syscontext = context_system::instance();
$hassystem = has_capability($capability, $syscontext, $userid);
$access = get_user_roles_sitewide_accessdata($userid);
// Build up a list of level 2 contexts (candidates to be user context).
$filtercontexts = array();
// Build list of roles to check overrides.
$roles = array();
foreach ($access['ra'] as $path => $role) {
$parts = explode('/', $path);
if (count($parts) == 3) {
$filtercontexts[$parts[2]] = $parts[2];
} else if (count($parts) > 3) {
// We know this is not a user context because there is another path with more than 2 levels.
unset($filtercontexts[$parts[2]]);
}
$roles = array_merge($roles, $role);
}
// Add all contexts in which a role may be overidden.
$rdefs = get_role_definitions($roles);
foreach ($rdefs as $roledef) {
foreach ($roledef as $path => $caps) {
if (!isset($caps[$capability])) {
// The capability is not mentioned, we can ignore.
continue;
}
$parts = explode('/', $path);
if (count($parts) === 3) {
// Only get potential user contexts, they only ever have 2 slashes /parentId/Id.
$filtercontexts[$parts[2]] = $parts[2];
}
}
}
// No interesting contexts - return all or no results.
if (empty($filtercontexts)) {
if ($hassystem) {
return $allresultsfilter;
} else {
return $noresultsfilter;
}
}
// Fetch all interesting contexts for further examination.
list($insql, $params) = $DB->get_in_or_equal($filtercontexts, SQL_PARAMS_NAMED);
$params['level'] = CONTEXT_USER;
$fields = context_helper::get_preload_record_columns_sql('ctx');
$interestingcontexts = $DB->get_recordset_sql('SELECT ' . $fields . '
FROM {context} 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);
}
}