Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.
<?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) {
> 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) {
> $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. < if ($usercompetencycourse->get('grade') === null) {
> // 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. < if ($usercompetency->get('grade') !== null) {
> // 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. Complete is an action to use with automated systems. < if ($usercompetency->get('grade') === null) {
> // 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);
> 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) {
> 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
> $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
> $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); } }