2 // This file is part of Moodle - http://moodle.org/
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
18 * Class for loading/storing competency frameworks from the DB.
20 * @package core_competency
21 * @copyright 2015 Damyon Wiese
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24 namespace core_competency;
25 defined('MOODLE_INTERNAL') || die();
36 use require_login_exception;
39 use required_capability_exception;
42 * Class for doing things with competency frameworks.
44 * @copyright 2015 Damyon Wiese
45 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
50 * Returns whether competencies are enabled.
52 * This method should never do more than checking the config setting, the reason
53 * being that some other code could be checking the config value directly
54 * to avoid having to load this entire file into memory.
56 * @return boolean True when enabled.
58 public static function is_enabled() {
59 return get_config('core_competency', 'enabled');
63 * Throws an exception if competencies are not enabled.
66 * @throws moodle_exception
68 public static function require_enabled() {
69 if (!static::is_enabled()) {
70 throw new moodle_exception('competenciesarenotenabled', 'core_competency');
75 * Checks whether a scale is used anywhere in the plugin.
77 * This public API has two exceptions:
78 * - It MUST NOT perform any capability checks.
79 * - It MUST ignore whether competencies are enabled or not ({@link self::is_enabled()}).
81 * @param int $scaleid The scale ID.
84 public static function is_scale_used_anywhere($scaleid) {
88 LEFT JOIN {" . competency_framework::TABLE ."} f
89 ON f.scaleid = :scaleid1
90 LEFT JOIN {" . competency::TABLE ."} c
91 ON c.scaleid = :scaleid2
92 WHERE f.id IS NOT NULL
94 return $DB->record_exists_sql($sql, ['scaleid1' => $scaleid, 'scaleid2' => $scaleid]);
98 * Validate if current user have acces to the course_module if hidden.
100 * @param mixed $cmmixed The cm_info class, course module record or its ID.
101 * @param bool $throwexception Throw an exception or not.
104 protected static function validate_course_module($cmmixed, $throwexception = true) {
106 if (!is_object($cm)) {
107 $cmrecord = get_coursemodule_from_id(null, $cmmixed);
108 $modinfo = get_fast_modinfo($cmrecord->course);
109 $cm = $modinfo->get_cm($cmmixed);
110 } else if (!$cm instanceof cm_info) {
111 // Assume we got a course module record.
112 $modinfo = get_fast_modinfo($cm->course);
113 $cm = $modinfo->get_cm($cm->id);
116 if (!$cm->uservisible) {
117 if ($throwexception) {
118 throw new require_login_exception('Course module is hidden');
128 * Validate if current user have acces to the course if hidden.
130 * @param mixed $courseorid The course or it ID.
131 * @param bool $throwexception Throw an exception or not.
134 protected static function validate_course($courseorid, $throwexception = true) {
135 $course = $courseorid;
136 if (!is_object($course)) {
137 $course = get_course($course);
140 $coursecontext = context_course::instance($course->id);
141 if (!$course->visible and !has_capability('moodle/course:viewhiddencourses', $coursecontext)) {
142 if ($throwexception) {
143 throw new require_login_exception('Course is hidden');
153 * Create a competency from a record containing all the data for the class.
155 * Requires moodle/competency:competencymanage capability at the system context.
157 * @param stdClass $record Record containing all the data for an instance of the class.
160 public static function create_competency(stdClass $record) {
161 static::require_enabled();
162 $competency = new competency(0, $record);
164 // First we do a permissions check.
165 require_capability('moodle/competency:competencymanage', $competency->get_context());
167 // Reset the sortorder, use reorder instead.
168 $competency->set_sortorder(null);
169 $competency->create();
171 \core\event\competency_created::create_from_competency($competency)->trigger();
173 // Reset the rule of the parent.
174 $parent = $competency->get_parent();
176 $parent->reset_rule();
184 * Delete a competency by id.
186 * Requires moodle/competency:competencymanage capability at the system context.
188 * @param int $id The record to delete. This will delete alot of related data - you better be sure.
191 public static function delete_competency($id) {
193 static::require_enabled();
194 $competency = new competency($id);
196 // First we do a permissions check.
197 require_capability('moodle/competency:competencymanage', $competency->get_context());
200 $competencyids = array(intval($competency->get_id()));
201 $contextid = $competency->get_context()->id;
202 $competencyids = array_merge(competency::get_descendants_ids($competency), $competencyids);
203 if (!competency::can_all_be_deleted($competencyids)) {
206 $transaction = $DB->start_delegated_transaction();
210 // Reset the rule of the parent.
211 $parent = $competency->get_parent();
213 $parent->reset_rule();
217 // Delete the competency separately so the after_delete event can be triggered.
218 $competency->delete();
220 // Delete the competencies.
221 competency::delete_multiple($competencyids);
223 // Delete the competencies relation.
224 related_competency::delete_multiple_relations($competencyids);
226 // Delete competency evidences.
227 user_evidence_competency::delete_by_competencyids($competencyids);
229 // Register the competencies deleted events.
230 $events = \core\event\competency_deleted::create_multiple_from_competencyids($competencyids, $contextid);
232 } catch (\Exception $e) {
233 $transaction->rollback($e);
236 $transaction->allow_commit();
238 foreach ($events as $event) {
246 * Reorder this competency.
248 * Requires moodle/competency:competencymanage capability at the system context.
250 * @param int $id The id of the competency to move.
253 public static function move_down_competency($id) {
254 static::require_enabled();
255 $current = new competency($id);
257 // First we do a permissions check.
258 require_capability('moodle/competency:competencymanage', $current->get_context());
260 $max = self::count_competencies(array('parentid' => $current->get_parentid(),
261 'competencyframeworkid' => $current->get_competencyframeworkid()));
266 $sortorder = $current->get_sortorder();
267 if ($sortorder >= $max) {
270 $sortorder = $sortorder + 1;
271 $current->set_sortorder($sortorder);
273 $filters = array('parentid' => $current->get_parentid(),
274 'competencyframeworkid' => $current->get_competencyframeworkid(),
275 'sortorder' => $sortorder);
276 $children = self::list_competencies($filters, 'id');
277 foreach ($children as $needtoswap) {
278 $needtoswap->set_sortorder($sortorder - 1);
279 $needtoswap->update();
283 $result = $current->update();
289 * Reorder this competency.
291 * Requires moodle/competency:competencymanage capability at the system context.
293 * @param int $id The id of the competency to move.
296 public static function move_up_competency($id) {
297 static::require_enabled();
298 $current = new competency($id);
300 // First we do a permissions check.
301 require_capability('moodle/competency:competencymanage', $current->get_context());
303 $sortorder = $current->get_sortorder();
304 if ($sortorder == 0) {
308 $sortorder = $sortorder - 1;
309 $current->set_sortorder($sortorder);
311 $filters = array('parentid' => $current->get_parentid(),
312 'competencyframeworkid' => $current->get_competencyframeworkid(),
313 'sortorder' => $sortorder);
314 $children = self::list_competencies($filters, 'id');
315 foreach ($children as $needtoswap) {
316 $needtoswap->set_sortorder($sortorder + 1);
317 $needtoswap->update();
321 $result = $current->update();
327 * Move this competency so it sits in a new parent.
329 * Requires moodle/competency:competencymanage capability at the system context.
331 * @param int $id The id of the competency to move.
332 * @param int $newparentid The new parent id for the competency.
335 public static function set_parent_competency($id, $newparentid) {
337 static::require_enabled();
338 $current = new competency($id);
340 // First we do a permissions check.
341 require_capability('moodle/competency:competencymanage', $current->get_context());
342 if ($id == $newparentid) {
343 throw new coding_exception('Can not set a competency as a parent of itself.');
344 } if ($newparentid == $current->get_parentid()) {
345 throw new coding_exception('Can not move a competency to the same location.');
348 // Some great variable assignment right here.
349 $currentparent = $current->get_parent();
350 $parent = !empty($newparentid) ? new competency($newparentid) : null;
351 $parentpath = !empty($parent) ? $parent->get_path() : '/0/';
353 // We're going to change quite a few things.
354 $transaction = $DB->start_delegated_transaction();
356 // If we are moving a node to a child of itself:
357 // - promote all the child nodes by one level.
358 // - remove the rule on self.
359 // - re-read the parent.
360 $newparents = explode('/', $parentpath);
361 if (in_array($current->get_id(), $newparents)) {
362 $children = competency::get_records(array('parentid' => $current->get_id()), 'id');
363 foreach ($children as $child) {
364 $child->set_parentid($current->get_parentid());
368 // Reset the rule on self as our children have changed.
369 $current->reset_rule();
371 // The destination parent is one of our descendants, we need to re-fetch its values (path, parentid).
375 // Reset the rules of initial parent and destination.
376 if (!empty($currentparent)) {
377 $currentparent->reset_rule();
378 $currentparent->update();
380 if (!empty($parent)) {
381 $parent->reset_rule();
385 // Do the actual move.
386 $current->set_parentid($newparentid);
387 $result = $current->update();
389 // All right, let's commit this.
390 $transaction->allow_commit();
396 * Update the details for a competency.
398 * Requires moodle/competency:competencymanage capability at the system context.
400 * @param stdClass $record The new details for the competency.
401 * Note - must contain an id that points to the competency to update.
405 public static function update_competency($record) {
406 static::require_enabled();
407 $competency = new competency($record->id);
409 // First we do a permissions check.
410 require_capability('moodle/competency:competencymanage', $competency->get_context());
412 // Some things should not be changed in an update - they should use a more specific method.
413 $record->sortorder = $competency->get_sortorder();
414 $record->parentid = $competency->get_parentid();
415 $record->competencyframeworkid = $competency->get_competencyframeworkid();
417 $competency->from_record($record);
418 require_capability('moodle/competency:competencymanage', $competency->get_context());
421 $result = $competency->update();
423 // Trigger the update event.
424 \core\event\competency_updated::create_from_competency($competency)->trigger();
430 * Read a the details for a single competency and return a record.
432 * Requires moodle/competency:competencyview capability at the system context.
434 * @param int $id The id of the competency to read.
435 * @param bool $includerelated Include related tags or not.
438 public static function read_competency($id, $includerelated = false) {
439 static::require_enabled();
440 $competency = new competency($id);
442 // First we do a permissions check.
443 $context = $competency->get_context();
444 if (!has_any_capability(array('moodle/competency:competencyview', 'moodle/competency:competencymanage'), $context)) {
445 throw new required_capability_exception($context, 'moodle/competency:competencyview', 'nopermissions', '');
449 if ($includerelated) {
450 $relatedcompetency = new related_competency();
451 if ($related = $relatedcompetency->list_relations($id)) {
452 $competency->relatedcompetencies = $related;
460 * Perform a text search based and return all results and their parents.
462 * Requires moodle/competency:competencyview capability at the framework context.
464 * @param string $textsearch A string to search for.
465 * @param int $competencyframeworkid The id of the framework to limit the search.
466 * @return array of competencies
468 public static function search_competencies($textsearch, $competencyframeworkid) {
469 static::require_enabled();
470 $framework = new competency_framework($competencyframeworkid);
472 // First we do a permissions check.
473 $context = $framework->get_context();
474 if (!has_any_capability(array('moodle/competency:competencyview', 'moodle/competency:competencymanage'), $context)) {
475 throw new required_capability_exception($context, 'moodle/competency:competencyview', 'nopermissions', '');
479 $competencies = competency::search($textsearch, $competencyframeworkid);
480 return $competencies;
484 * Perform a search based on the provided filters and return a paginated list of records.
486 * Requires moodle/competency:competencyview capability at some context.
488 * @param array $filters A list of filters to apply to the list.
489 * @param string $sort The column to sort on
490 * @param string $order ('ASC' or 'DESC')
491 * @param int $skip Number of records to skip (pagination)
492 * @param int $limit Max of records to return (pagination)
493 * @return array of competencies
495 public static function list_competencies($filters, $sort = '', $order = 'ASC', $skip = 0, $limit = 0) {
496 static::require_enabled();
497 if (!isset($filters['competencyframeworkid'])) {
498 $context = context_system::instance();
500 $framework = new competency_framework($filters['competencyframeworkid']);
501 $context = $framework->get_context();
504 // First we do a permissions check.
505 if (!has_any_capability(array('moodle/competency:competencyview', 'moodle/competency:competencymanage'), $context)) {
506 throw new required_capability_exception($context, 'moodle/competency:competencyview', 'nopermissions', '');
510 return competency::get_records($filters, $sort, $order, $skip, $limit);
514 * Perform a search based on the provided filters and return a paginated list of records.
516 * Requires moodle/competency:competencyview capability at some context.
518 * @param array $filters A list of filters to apply to the list.
521 public static function count_competencies($filters) {
522 static::require_enabled();
523 if (!isset($filters['competencyframeworkid'])) {
524 $context = context_system::instance();
526 $framework = new competency_framework($filters['competencyframeworkid']);
527 $context = $framework->get_context();
530 // First we do a permissions check.
531 if (!has_any_capability(array('moodle/competency:competencyview', 'moodle/competency:competencymanage'), $context)) {
532 throw new required_capability_exception($context, 'moodle/competency:competencyview', 'nopermissions', '');
536 return competency::count_records($filters);
540 * Create a competency framework from a record containing all the data for the class.
542 * Requires moodle/competency:competencymanage capability at the system context.
544 * @param stdClass $record Record containing all the data for an instance of the class.
545 * @return competency_framework
547 public static function create_framework(stdClass $record) {
548 static::require_enabled();
549 $framework = new competency_framework(0, $record);
550 require_capability('moodle/competency:competencymanage', $framework->get_context());
552 // Account for different formats of taxonomies.
553 if (isset($record->taxonomies)) {
554 $framework->set_taxonomies($record->taxonomies);
557 $framework = $framework->create();
559 // Trigger a competency framework created event.
560 \core\event\competency_framework_created::create_from_framework($framework)->trigger();
566 * Duplicate a competency framework by id.
568 * Requires moodle/competency:competencymanage capability at the system context.
570 * @param int $id The record to duplicate. All competencies associated and related will be duplicated.
571 * @return competency_framework the framework duplicated
573 public static function duplicate_framework($id) {
575 static::require_enabled();
577 $framework = new competency_framework($id);
578 require_capability('moodle/competency:competencymanage', $framework->get_context());
579 // Starting transaction.
580 $transaction = $DB->start_delegated_transaction();
583 // Get a uniq idnumber based on the origin framework.
584 $idnumber = competency_framework::get_unused_idnumber($framework->get_idnumber());
585 $framework->set_idnumber($idnumber);
586 // Adding the suffix copy to the shortname.
587 $framework->set_shortname(get_string('duplicateditemname', 'core_competency', $framework->get_shortname()));
588 $framework->set_id(0);
589 $framework = $framework->create();
591 // Array that match the old competencies ids with the new one to use when copying related competencies.
592 $frameworkcompetency = competency::get_framework_tree($id);
593 $matchids = self::duplicate_competency_tree($framework->get_id(), $frameworkcompetency, 0, 0);
595 // Copy the related competencies.
596 $relcomps = related_competency::get_multiple_relations(array_keys($matchids));
598 foreach ($relcomps as $relcomp) {
599 $compid = $relcomp->get_competencyid();
600 $relcompid = $relcomp->get_relatedcompetencyid();
601 if (isset($matchids[$compid]) && isset($matchids[$relcompid])) {
602 $newcompid = $matchids[$compid]->get_id();
603 $newrelcompid = $matchids[$relcompid]->get_id();
604 if ($newcompid < $newrelcompid) {
605 $relcomp->set_competencyid($newcompid);
606 $relcomp->set_relatedcompetencyid($newrelcompid);
608 $relcomp->set_competencyid($newrelcompid);
609 $relcomp->set_relatedcompetencyid($newcompid);
614 // Debugging message when there is no match found.
615 debugging('related competency id not found');
619 // Setting rules on duplicated competencies.
620 self::migrate_competency_tree_rules($frameworkcompetency, $matchids);
622 $transaction->allow_commit();
624 } catch (\Exception $e) {
625 $transaction->rollback($e);
628 // Trigger a competency framework created event.
629 \core\event\competency_framework_created::create_from_framework($framework)->trigger();
635 * Delete a competency framework by id.
637 * Requires moodle/competency:competencymanage capability at the system context.
639 * @param int $id The record to delete. This will delete alot of related data - you better be sure.
642 public static function delete_framework($id) {
644 static::require_enabled();
645 $framework = new competency_framework($id);
646 require_capability('moodle/competency:competencymanage', $framework->get_context());
649 $competenciesid = competency::get_ids_by_frameworkid($id);
650 $contextid = $framework->get_contextid();
651 if (!competency::can_all_be_deleted($competenciesid)) {
654 $transaction = $DB->start_delegated_transaction();
656 if (!empty($competenciesid)) {
657 // Delete competencies.
658 competency::delete_by_frameworkid($id);
660 // Delete the related competencies.
661 related_competency::delete_multiple_relations($competenciesid);
663 // Delete the evidences for competencies.
664 user_evidence_competency::delete_by_competencyids($competenciesid);
667 // Create a competency framework deleted event.
668 $event = \core\event\competency_framework_deleted::create_from_framework($framework);
669 $result = $framework->delete();
671 // Register the deleted events competencies.
672 $events = \core\event\competency_deleted::create_multiple_from_competencyids($competenciesid, $contextid);
674 } catch (\Exception $e) {
675 $transaction->rollback($e);
678 // Commit the transaction.
679 $transaction->allow_commit();
681 // If all operations are successfull then trigger the delete event.
684 // Trigger deleted event competencies.
685 foreach ($events as $event) {
693 * Update the details for a competency framework.
695 * Requires moodle/competency:competencymanage capability at the system context.
697 * @param stdClass $record The new details for the framework. Note - must contain an id that points to the framework to update.
700 public static function update_framework($record) {
701 static::require_enabled();
702 $framework = new competency_framework($record->id);
704 // Check the permissions before update.
705 require_capability('moodle/competency:competencymanage', $framework->get_context());
707 // Account for different formats of taxonomies.
708 $framework->from_record($record);
709 if (isset($record->taxonomies)) {
710 $framework->set_taxonomies($record->taxonomies);
713 // Trigger a competency framework updated event.
714 \core\event\competency_framework_updated::create_from_framework($framework)->trigger();
716 return $framework->update();
720 * Read a the details for a single competency framework and return a record.
722 * Requires moodle/competency:competencyview capability at the system context.
724 * @param int $id The id of the framework to read.
725 * @return competency_framework
727 public static function read_framework($id) {
728 static::require_enabled();
729 $framework = new competency_framework($id);
730 if (!has_any_capability(array('moodle/competency:competencyview', 'moodle/competency:competencymanage'),
731 $framework->get_context())) {
732 throw new required_capability_exception($framework->get_context(), 'moodle/competency:competencyview',
733 'nopermissions', '');
739 * Logg the competency framework viewed event.
741 * @param competency_framework|int $frameworkorid The competency_framework object or competency framework id
744 public static function competency_framework_viewed($frameworkorid) {
745 static::require_enabled();
746 $framework = $frameworkorid;
747 if (!is_object($framework)) {
748 $framework = new competency_framework($framework);
750 if (!has_any_capability(array('moodle/competency:competencyview', 'moodle/competency:competencymanage'),
751 $framework->get_context())) {
752 throw new required_capability_exception($framework->get_context(), 'moodle/competency:competencyview',
753 'nopermissions', '');
755 \core\event\competency_framework_viewed::create_from_framework($framework)->trigger();
760 * Logg the competency viewed event.
762 * @param competency|int $competencyorid The competency object or competency id
765 public static function competency_viewed($competencyorid) {
766 static::require_enabled();
767 $competency = $competencyorid;
768 if (!is_object($competency)) {
769 $competency = new competency($competency);
772 if (!has_any_capability(array('moodle/competency:competencyview', 'moodle/competency:competencymanage'),
773 $competency->get_context())) {
774 throw new required_capability_exception($competency->get_context(), 'moodle/competency:competencyview',
775 'nopermissions', '');
778 \core\event\competency_viewed::create_from_competency($competency)->trigger();
783 * Perform a search based on the provided filters and return a paginated list of records.
785 * Requires moodle/competency:competencyview capability at the system context.
787 * @param string $sort The column to sort on
788 * @param string $order ('ASC' or 'DESC')
789 * @param int $skip Number of records to skip (pagination)
790 * @param int $limit Max of records to return (pagination)
791 * @param context $context The parent context of the frameworks.
792 * @param string $includes Defines what other contexts to fetch frameworks from.
793 * Accepted values are:
794 * - children: All descendants
795 * - parents: All parents, grand parents, etc...
796 * - self: Context passed only.
797 * @param bool $onlyvisible If true return only visible frameworks
798 * @param string $query A string to use to filter down the frameworks.
799 * @return array of competency_framework
801 public static function list_frameworks($sort, $order, $skip, $limit, $context, $includes = 'children',
802 $onlyvisible = false, $query = '') {
804 static::require_enabled();
806 // Get all the relevant contexts.
807 $contexts = self::get_related_contexts($context, $includes,
808 array('moodle/competency:competencyview', 'moodle/competency:competencymanage'));
810 if (empty($contexts)) {
811 throw new required_capability_exception($context, 'moodle/competency:competencyview', 'nopermissions', '');
815 list($insql, $inparams) = $DB->get_in_or_equal(array_keys($contexts), SQL_PARAMS_NAMED);
816 $select = "contextid $insql";
818 $select .= " AND visible = :visible";
819 $inparams['visible'] = 1;
822 if (!empty($query) || is_numeric($query)) {
823 $sqlnamelike = $DB->sql_like('shortname', ':namelike', false);
824 $sqlidnlike = $DB->sql_like('idnumber', ':idnlike', false);
826 $select .= " AND ($sqlnamelike OR $sqlidnlike) ";
827 $inparams['namelike'] = '%' . $DB->sql_like_escape($query) . '%';
828 $inparams['idnlike'] = '%' . $DB->sql_like_escape($query) . '%';
831 return competency_framework::get_records_select($select, $inparams, $sort . ' ' . $order, '*', $skip, $limit);
835 * Perform a search based on the provided filters and return a paginated list of records.
837 * Requires moodle/competency:competencyview capability at the system context.
839 * @param context $context The parent context of the frameworks.
840 * @param string $includes Defines what other contexts to fetch frameworks from.
841 * Accepted values are:
842 * - children: All descendants
843 * - parents: All parents, grand parents, etc...
844 * - self: Context passed only.
847 public static function count_frameworks($context, $includes) {
849 static::require_enabled();
851 // Get all the relevant contexts.
852 $contexts = self::get_related_contexts($context, $includes,
853 array('moodle/competency:competencyview', 'moodle/competency:competencymanage'));
855 if (empty($contexts)) {
856 throw new required_capability_exception($context, 'moodle/competency:competencyview', 'nopermissions', '');
860 list($insql, $inparams) = $DB->get_in_or_equal(array_keys($contexts), SQL_PARAMS_NAMED);
861 return competency_framework::count_records_select("contextid $insql", $inparams);
865 * Fetches all the relevant contexts.
867 * Note: This currently only supports system, category and user contexts. However user contexts
868 * behave a bit differently and will fallback on the system context. This is what makes the most
869 * sense because a user context does not have descendants, and only has system as a parent.
871 * @param context $context The context to start from.
872 * @param string $includes Defines what other contexts to find.
873 * Accepted values are:
874 * - children: All descendants
875 * - parents: All parents, grand parents, etc...
876 * - self: Context passed only.
877 * @param array $hasanycapability Array of capabilities passed to {@link has_any_capability()} in each context.
878 * @return context[] An array of contexts where keys are context IDs.
880 public static function get_related_contexts($context, $includes, array $hasanycapability = null) {
882 static::require_enabled();
884 if (!in_array($includes, array('children', 'parents', 'self'))) {
885 throw new coding_exception('Invalid parameter value for \'includes\'.');
888 // If context user swap it for the context_system.
889 if ($context->contextlevel == CONTEXT_USER) {
890 $context = context_system::instance();
893 $contexts = array($context->id => $context);
895 if ($includes == 'children') {
896 $params = array('coursecatlevel' => CONTEXT_COURSECAT, 'path' => $context->path . '/%');
897 $pathlike = $DB->sql_like('path', ':path');
898 $sql = "contextlevel = :coursecatlevel AND $pathlike";
899 $rs = $DB->get_recordset_select('context', $sql, $params);
900 foreach ($rs as $record) {
901 $ctxid = $record->id;
902 context_helper::preload_from_record($record);
903 $contexts[$ctxid] = context::instance_by_id($ctxid);
907 } else if ($includes == 'parents') {
908 $children = $context->get_parent_contexts();
909 foreach ($children as $ctx) {
910 $contexts[$ctx->id] = $ctx;
914 // Filter according to the capabilities required.
915 if (!empty($hasanycapability)) {
916 foreach ($contexts as $key => $ctx) {
917 if (!has_any_capability($hasanycapability, $ctx)) {
918 unset($contexts[$key]);
927 * Count all the courses using a competency.
929 * @param int $competencyid The id of the competency to check.
932 public static function count_courses_using_competency($competencyid) {
933 static::require_enabled();
936 $courses = course_competency::list_courses_min($competencyid);
939 // Now check permissions on each course.
940 foreach ($courses as $course) {
941 if (!self::validate_course($course, false)) {
945 $context = context_course::instance($course->id);
946 $capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage');
947 if (!has_any_capability($capabilities, $context)) {
958 * List all the courses modules using a competency in a course.
960 * @param int $competencyid The id of the competency to check.
961 * @param int $courseid The id of the course to check.
962 * @return array[int] Array of course modules ids.
964 public static function list_course_modules_using_competency($competencyid, $courseid) {
965 static::require_enabled();
968 self::validate_course($courseid);
970 $coursecontext = context_course::instance($courseid);
972 // We will not check each module - course permissions should be enough.
973 $capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage');
974 if (!has_any_capability($capabilities, $coursecontext)) {
975 throw new required_capability_exception($coursecontext, 'moodle/competency:coursecompetencyview', 'nopermissions', '');
978 $cmlist = course_module_competency::list_course_modules($competencyid, $courseid);
979 foreach ($cmlist as $cmid) {
980 if (self::validate_course_module($cmid, false)) {
981 array_push($result, $cmid);
989 * List all the competencies linked to a course module.
991 * @param mixed $cmorid The course module, or its ID.
992 * @return array[competency] Array of competency records.
994 public static function list_course_module_competencies_in_course_module($cmorid) {
995 static::require_enabled();
997 if (!is_object($cmorid)) {
998 $cm = get_coursemodule_from_id('', $cmorid, 0, true, MUST_EXIST);
1001 // Check the user have access to the course module.
1002 self::validate_course_module($cm);
1003 $context = context_module::instance($cm->id);
1005 $capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage');
1006 if (!has_any_capability($capabilities, $context)) {
1007 throw new required_capability_exception($context, 'moodle/competency:coursecompetencyview', 'nopermissions', '');
1012 $cmclist = course_module_competency::list_course_module_competencies($cm->id);
1013 foreach ($cmclist as $id => $cmc) {
1014 array_push($result, $cmc);
1021 * List all the courses using a competency.
1023 * @param int $competencyid The id of the competency to check.
1024 * @return array[stdClass] Array of stdClass containing id and shortname.
1026 public static function list_courses_using_competency($competencyid) {
1027 static::require_enabled();
1030 $courses = course_competency::list_courses($competencyid);
1033 // Now check permissions on each course.
1034 foreach ($courses as $id => $course) {
1035 $context = context_course::instance($course->id);
1036 $capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage');
1037 if (!has_any_capability($capabilities, $context)) {
1038 unset($courses[$id]);
1041 if (!self::validate_course($course, false)) {
1042 unset($courses[$id]);
1045 array_push($result, $course);
1052 * Count the proficient competencies in a course for one user.
1054 * @param int $courseid The id of the course to check.
1055 * @param int $userid The id of the user to check.
1058 public static function count_proficient_competencies_in_course_for_user($courseid, $userid) {
1059 static::require_enabled();
1060 // Check the user have access to the course.
1061 self::validate_course($courseid);
1063 // First we do a permissions check.
1064 $context = context_course::instance($courseid);
1066 $capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage');
1067 if (!has_any_capability($capabilities, $context)) {
1068 throw new required_capability_exception($context, 'moodle/competency:coursecompetencyview', 'nopermissions', '');
1072 return user_competency_course::count_proficient_competencies($courseid, $userid);
1076 * Count all the competencies in a course.
1078 * @param int $courseid The id of the course to check.
1081 public static function count_competencies_in_course($courseid) {
1082 static::require_enabled();
1083 // Check the user have access to the course.
1084 self::validate_course($courseid);
1086 // First we do a permissions check.
1087 $context = context_course::instance($courseid);
1089 $capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage');
1090 if (!has_any_capability($capabilities, $context)) {
1091 throw new required_capability_exception($context, 'moodle/competency:coursecompetencyview', 'nopermissions', '');
1095 return course_competency::count_competencies($courseid);
1099 * List the competencies associated to a course.
1101 * @param mixed $courseorid The course, or its ID.
1102 * @return array( array(
1103 * 'competency' => \core_competency\competency,
1104 * 'coursecompetency' => \core_competency\course_competency
1107 public static function list_course_competencies($courseorid) {
1108 static::require_enabled();
1109 $course = $courseorid;
1110 if (!is_object($courseorid)) {
1111 $course = get_course($courseorid);
1114 // Check the user have access to the course.
1115 self::validate_course($course);
1116 $context = context_course::instance($course->id);
1118 $capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage');
1119 if (!has_any_capability($capabilities, $context)) {
1120 throw new required_capability_exception($context, 'moodle/competency:coursecompetencyview', 'nopermissions', '');
1125 // TODO We could improve the performance of this into one single query.
1126 $coursecompetencies = course_competency::list_course_competencies($course->id);
1127 $competencies = course_competency::list_competencies($course->id);
1129 // Build the return values.
1130 foreach ($coursecompetencies as $key => $coursecompetency) {
1132 'competency' => $competencies[$coursecompetency->get_competencyid()],
1133 'coursecompetency' => $coursecompetency
1141 * Get a user competency.
1143 * @param int $userid The user ID.
1144 * @param int $competencyid The competency ID.
1145 * @return user_competency
1147 public static function get_user_competency($userid, $competencyid) {
1148 static::require_enabled();
1149 $existing = user_competency::get_multiple($userid, array($competencyid));
1150 $uc = array_pop($existing);
1153 $uc = user_competency::create_relation($userid, $competencyid);
1157 if (!$uc->can_read()) {
1158 throw new required_capability_exception($uc->get_context(), 'moodle/competency:usercompetencyview',
1159 'nopermissions', '');
1165 * Get a user competency by ID.
1167 * @param int $usercompetencyid The user competency ID.
1168 * @return user_competency
1170 public static function get_user_competency_by_id($usercompetencyid) {
1171 static::require_enabled();
1172 $uc = new user_competency($usercompetencyid);
1173 if (!$uc->can_read()) {
1174 throw new required_capability_exception($uc->get_context(), 'moodle/competency:usercompetencyview',
1175 'nopermissions', '');
1181 * List the competencies associated to a course module.
1183 * @param mixed $cmorid The course module, or its ID.
1184 * @return array( array(
1185 * 'competency' => \core_competency\competency,
1186 * 'coursemodulecompetency' => \core_competency\course_module_competency
1189 public static function list_course_module_competencies($cmorid) {
1190 static::require_enabled();
1192 if (!is_object($cmorid)) {
1193 $cm = get_coursemodule_from_id('', $cmorid, 0, true, MUST_EXIST);
1196 // Check the user have access to the course module.
1197 self::validate_course_module($cm);
1198 $context = context_module::instance($cm->id);
1200 $capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage');
1201 if (!has_any_capability($capabilities, $context)) {
1202 throw new required_capability_exception($context, 'moodle/competency:coursecompetencyview', 'nopermissions', '');
1207 // TODO We could improve the performance of this into one single query.
1208 $coursemodulecompetencies = course_competency::list_course_module_competencies($cm->id);
1209 $competencies = course_module_competency::list_competencies($cm->id);
1211 // Build the return values.
1212 foreach ($coursemodulecompetencies as $key => $coursemodulecompetency) {
1214 'competency' => $competencies[$coursemodulecompetency->get_competencyid()],
1215 'coursemodulecompetency' => $coursemodulecompetency
1223 * Get a user competency in a course.
1225 * @param int $courseid The id of the course to check.
1226 * @param int $userid The id of the course to check.
1227 * @param int $competencyid The id of the competency.
1228 * @return user_competency_course
1230 public static function get_user_competency_in_course($courseid, $userid, $competencyid) {
1231 static::require_enabled();
1232 // First we do a permissions check.
1233 $context = context_course::instance($courseid);
1235 $capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage');
1236 if (!has_any_capability($capabilities, $context)) {
1237 throw new required_capability_exception($context, 'moodle/competency:coursecompetencyview', 'nopermissions', '');
1238 } else if (!user_competency::can_read_user_in_course($userid, $courseid)) {
1239 throw new required_capability_exception($context, 'moodle/competency:usercompetencyview', 'nopermissions', '');
1242 // This will throw an exception if the competency does not belong to the course.
1243 $competency = course_competency::get_competency($courseid, $competencyid);
1245 $params = array('courseid' => $courseid, 'userid' => $userid, 'competencyid' => $competencyid);
1246 $exists = user_competency_course::get_record($params);
1251 $ucc = user_competency_course::create_relation($userid, $competency->get_id(), $courseid);
1259 * List all the user competencies in a course.
1261 * @param int $courseid The id of the course to check.
1262 * @param int $userid The id of the course to check.
1263 * @return array of user_competency_course objects
1265 public static function list_user_competencies_in_course($courseid, $userid) {
1266 static::require_enabled();
1267 // First we do a permissions check.
1268 $context = context_course::instance($courseid);
1271 $capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage');
1272 if (!has_any_capability($capabilities, $context)) {
1273 throw new required_capability_exception($context, 'moodle/competency:coursecompetencyview', 'nopermissions', '');
1274 } else if (!user_competency::can_read_user_in_course($userid, $courseid)) {
1275 throw new required_capability_exception($context, 'moodle/competency:usercompetencyview', 'nopermissions', '');
1279 $competencylist = course_competency::list_competencies($courseid, false);
1281 $existing = user_competency_course::get_multiple($userid, $courseid, $competencylist);
1283 $orderedusercompetencycourses = array();
1285 $somemissing = false;
1286 foreach ($competencylist as $coursecompetency) {
1288 foreach ($existing as $usercompetencycourse) {
1289 if ($usercompetencycourse->get_competencyid() == $coursecompetency->get_id()) {
1291 $orderedusercompetencycourses[$usercompetencycourse->get_id()] = $usercompetencycourse;
1296 $ucc = user_competency_course::create_relation($userid, $coursecompetency->get_id(), $courseid);
1298 $orderedusercompetencycourses[$ucc->get_id()] = $ucc;
1302 return $orderedusercompetencycourses;
1306 * List the user competencies to review.
1308 * The method returns values in this format:
1311 * 'competencies' => array(
1313 * 'usercompetency' => (user_competency),
1314 * 'competency' => (competency),
1321 * @param int $skip The number of records to skip.
1322 * @param int $limit The number of results to return.
1323 * @param int $userid The user we're getting the competencies to review for.
1324 * @return array Containing the keys 'count', and 'competencies'. The 'competencies' key contains an object
1325 * which contains 'competency', 'usercompetency' and 'user'.
1327 public static function list_user_competencies_to_review($skip = 0, $limit = 50, $userid = null) {
1329 static::require_enabled();
1330 if ($userid === null) {
1331 $userid = $USER->id;
1334 $capability = 'moodle/competency:usercompetencyreview';
1335 $ucfields = user_competency::get_sql_fields('uc', 'uc_');
1336 $compfields = competency::get_sql_fields('c', 'c_');
1337 $usercols = array('id') + get_user_fieldnames();
1338 $userfields = array();
1339 foreach ($usercols as $field) {
1340 $userfields[] = "u." . $field . " AS usr_" . $field;
1342 $userfields = implode(',', $userfields);
1344 $select = "SELECT $ucfields, $compfields, $userfields";
1345 $countselect = "SELECT COUNT('x')";
1346 $sql = " FROM {" . user_competency::TABLE . "} uc
1347 JOIN {" . competency::TABLE . "} c
1348 ON c.id = uc.competencyid
1351 WHERE (uc.status = :waitingforreview
1352 OR (uc.status = :inreview AND uc.reviewerid = :reviewerid))";
1353 $ordersql = " ORDER BY c.shortname ASC";
1355 'inreview' => user_competency::STATUS_IN_REVIEW,
1356 'reviewerid' => $userid,
1357 'waitingforreview' => user_competency::STATUS_WAITING_FOR_REVIEW,
1359 $countsql = $countselect . $sql;
1361 // Primary check to avoid the hard work of getting the users in which the user has permission.
1362 $count = $DB->count_records_sql($countselect . $sql, $params);
1364 return array('count' => 0, 'competencies' => array());
1367 // TODO MDL-52243 Use core function.
1368 list($insql, $inparams) = self::filter_users_with_capability_on_user_context_sql(
1369 $capability, $userid, SQL_PARAMS_NAMED);
1370 $params += $inparams;
1371 $countsql = $countselect . $sql . " AND uc.userid $insql";
1372 $getsql = $select . $sql . " AND uc.userid $insql " . $ordersql;
1374 // Extracting the results.
1375 $competencies = array();
1376 $records = $DB->get_recordset_sql($getsql, $params, $skip, $limit);
1377 foreach ($records as $record) {
1378 $objects = (object) array(
1379 'usercompetency' => new user_competency(0, user_competency::extract_record($record, 'uc_')),
1380 'competency' => new competency(0, competency::extract_record($record, 'c_')),
1381 'user' => persistent::extract_record($record, 'usr_'),
1383 $competencies[] = $objects;
1388 'count' => $DB->count_records_sql($countsql, $params),
1389 'competencies' => $competencies
1394 * Add a competency to this course module.
1396 * @param mixed $cmorid The course module, or id of the course module
1397 * @param int $competencyid The id of the competency
1400 public static function add_competency_to_course_module($cmorid, $competencyid) {
1401 static::require_enabled();
1403 if (!is_object($cmorid)) {
1404 $cm = get_coursemodule_from_id('', $cmorid, 0, true, MUST_EXIST);
1407 // Check the user have access to the course module.
1408 self::validate_course_module($cm);
1410 // First we do a permissions check.
1411 $context = context_module::instance($cm->id);
1413 require_capability('moodle/competency:coursecompetencymanage', $context);
1415 // Check that the competency belongs to the course.
1416 $exists = course_competency::get_records(array('courseid' => $cm->course, 'competencyid' => $competencyid));
1418 throw new coding_exception('Cannot add a competency to a module if it does not belong to the course');
1421 $record = new stdClass();
1422 $record->cmid = $cm->id;
1423 $record->competencyid = $competencyid;
1425 $coursemodulecompetency = new course_module_competency();
1426 $exists = $coursemodulecompetency->get_records(array('cmid' => $cm->id, 'competencyid' => $competencyid));
1428 $coursemodulecompetency->from_record($record);
1429 if ($coursemodulecompetency->create()) {
1437 * Remove a competency from this course module.
1439 * @param mixed $cmorid The course module, or id of the course module
1440 * @param int $competencyid The id of the competency
1443 public static function remove_competency_from_course_module($cmorid, $competencyid) {
1444 static::require_enabled();
1446 if (!is_object($cmorid)) {
1447 $cm = get_coursemodule_from_id('', $cmorid, 0, true, MUST_EXIST);
1449 // Check the user have access to the course module.
1450 self::validate_course_module($cm);
1452 // First we do a permissions check.
1453 $context = context_module::instance($cm->id);
1455 require_capability('moodle/competency:coursecompetencymanage', $context);
1457 $record = new stdClass();
1458 $record->cmid = $cm->id;
1459 $record->competencyid = $competencyid;
1461 $competency = new competency($competencyid);
1462 $exists = course_module_competency::get_record(array('cmid' => $cm->id, 'competencyid' => $competencyid));
1464 return $exists->delete();
1470 * Move the course module competency up or down in the display list.
1472 * Requires moodle/competency:coursecompetencymanage capability at the course module context.
1474 * @param mixed $cmorid The course module, or id of the course module
1475 * @param int $competencyidfrom The id of the competency we are moving.
1476 * @param int $competencyidto The id of the competency we are moving to.
1479 public static function reorder_course_module_competency($cmorid, $competencyidfrom, $competencyidto) {
1480 static::require_enabled();
1482 if (!is_object($cmorid)) {
1483 $cm = get_coursemodule_from_id('', $cmorid, 0, true, MUST_EXIST);
1485 // Check the user have access to the course module.
1486 self::validate_course_module($cm);
1488 // First we do a permissions check.
1489 $context = context_module::instance($cm->id);
1491 require_capability('moodle/competency:coursecompetencymanage', $context);
1494 $matches = course_module_competency::get_records(array('cmid' => $cm->id, 'competencyid' => $competencyidfrom));
1495 if (count($matches) == 0) {
1496 throw new coding_exception('The link does not exist');
1499 $competencyfrom = array_pop($matches);
1500 $matches = course_module_competency::get_records(array('cmid' => $cm->id, 'competencyid' => $competencyidto));
1501 if (count($matches) == 0) {
1502 throw new coding_exception('The link does not exist');
1505 $competencyto = array_pop($matches);
1507 $all = course_module_competency::get_records(array('cmid' => $cm->id), 'sortorder', 'ASC', 0, 0);
1509 if ($competencyfrom->get_sortorder() > $competencyto->get_sortorder()) {
1510 // We are moving up, so put it before the "to" item.
1514 foreach ($all as $id => $coursemodulecompetency) {
1515 $sort = $coursemodulecompetency->get_sortorder();
1516 if ($down && $sort > $competencyfrom->get_sortorder() && $sort <= $competencyto->get_sortorder()) {
1517 $coursemodulecompetency->set_sortorder($coursemodulecompetency->get_sortorder() - 1);
1518 $coursemodulecompetency->update();
1519 } else if (!$down && $sort >= $competencyto->get_sortorder() && $sort < $competencyfrom->get_sortorder()) {
1520 $coursemodulecompetency->set_sortorder($coursemodulecompetency->get_sortorder() + 1);
1521 $coursemodulecompetency->update();
1524 $competencyfrom->set_sortorder($competencyto->get_sortorder());
1525 return $competencyfrom->update();
1529 * Update ruleoutcome value for a course module competency.
1531 * @param int|course_module_competency $coursemodulecompetencyorid The course_module_competency, or its ID.
1532 * @param int $ruleoutcome The value of ruleoutcome.
1533 * @return bool True on success.
1535 public static function set_course_module_competency_ruleoutcome($coursemodulecompetencyorid, $ruleoutcome) {
1536 static::require_enabled();
1537 $coursemodulecompetency = $coursemodulecompetencyorid;
1538 if (!is_object($coursemodulecompetency)) {
1539 $coursemodulecompetency = new course_module_competency($coursemodulecompetencyorid);
1542 $cm = get_coursemodule_from_id('', $coursemodulecompetency->get_cmid(), 0, true, MUST_EXIST);
1544 self::validate_course_module($cm);
1545 $context = context_module::instance($cm->id);
1547 require_capability('moodle/competency:coursecompetencymanage', $context);
1549 $coursemodulecompetency->set_ruleoutcome($ruleoutcome);
1550 return $coursemodulecompetency->update();
1554 * Add a competency to this course.
1556 * @param int $courseid The id of the course
1557 * @param int $competencyid The id of the competency
1560 public static function add_competency_to_course($courseid, $competencyid) {
1561 static::require_enabled();
1562 // Check the user have access to the course.
1563 self::validate_course($courseid);
1565 // First we do a permissions check.
1566 $context = context_course::instance($courseid);
1568 require_capability('moodle/competency:coursecompetencymanage', $context);
1570 $record = new stdClass();
1571 $record->courseid = $courseid;
1572 $record->competencyid = $competencyid;
1574 $competency = new competency($competencyid);
1576 // Can not add a competency that belong to a hidden framework.
1577 if ($competency->get_framework()->get_visible() == false) {
1578 throw new coding_exception('A competency belonging to hidden framework can not be linked to course');
1581 $coursecompetency = new course_competency();
1582 $exists = $coursecompetency->get_records(array('courseid' => $courseid, 'competencyid' => $competencyid));
1584 $coursecompetency->from_record($record);
1585 if ($coursecompetency->create()) {
1593 * Remove a competency from this course.
1595 * @param int $courseid The id of the course
1596 * @param int $competencyid The id of the competency
1599 public static function remove_competency_from_course($courseid, $competencyid) {
1600 static::require_enabled();
1601 // Check the user have access to the course.
1602 self::validate_course($courseid);
1604 // First we do a permissions check.
1605 $context = context_course::instance($courseid);
1607 require_capability('moodle/competency:coursecompetencymanage', $context);
1609 $record = new stdClass();
1610 $record->courseid = $courseid;
1611 $record->competencyid = $competencyid;
1613 $coursecompetency = new course_competency();
1614 $exists = course_competency::get_record(array('courseid' => $courseid, 'competencyid' => $competencyid));
1616 // Delete all course_module_competencies for this competency in this course.
1617 $cmcs = course_module_competency::get_records_by_competencyid_in_course($competencyid, $courseid);
1618 foreach ($cmcs as $cmc) {
1621 return $exists->delete();
1627 * Move the course competency up or down in the display list.
1629 * Requires moodle/competency:coursecompetencymanage capability at the course context.
1631 * @param int $courseid The course
1632 * @param int $competencyidfrom The id of the competency we are moving.
1633 * @param int $competencyidto The id of the competency we are moving to.
1636 public static function reorder_course_competency($courseid, $competencyidfrom, $competencyidto) {
1637 static::require_enabled();
1638 // Check the user have access to the course.
1639 self::validate_course($courseid);
1641 // First we do a permissions check.
1642 $context = context_course::instance($courseid);
1644 require_capability('moodle/competency:coursecompetencymanage', $context);
1647 $coursecompetency = new course_competency();
1648 $matches = $coursecompetency->get_records(array('courseid' => $courseid, 'competencyid' => $competencyidfrom));
1649 if (count($matches) == 0) {
1650 throw new coding_exception('The link does not exist');
1653 $competencyfrom = array_pop($matches);
1654 $matches = $coursecompetency->get_records(array('courseid' => $courseid, 'competencyid' => $competencyidto));
1655 if (count($matches) == 0) {
1656 throw new coding_exception('The link does not exist');
1659 $competencyto = array_pop($matches);
1661 $all = $coursecompetency->get_records(array('courseid' => $courseid), 'sortorder', 'ASC', 0, 0);
1663 if ($competencyfrom->get_sortorder() > $competencyto->get_sortorder()) {
1664 // We are moving up, so put it before the "to" item.
1668 foreach ($all as $id => $coursecompetency) {
1669 $sort = $coursecompetency->get_sortorder();
1670 if ($down && $sort > $competencyfrom->get_sortorder() && $sort <= $competencyto->get_sortorder()) {
1671 $coursecompetency->set_sortorder($coursecompetency->get_sortorder() - 1);
1672 $coursecompetency->update();
1673 } else if (!$down && $sort >= $competencyto->get_sortorder() && $sort < $competencyfrom->get_sortorder()) {
1674 $coursecompetency->set_sortorder($coursecompetency->get_sortorder() + 1);
1675 $coursecompetency->update();
1678 $competencyfrom->set_sortorder($competencyto->get_sortorder());
1679 return $competencyfrom->update();
1683 * Update ruleoutcome value for a course competency.
1685 * @param int|course_competency $coursecompetencyorid The course_competency, or its ID.
1686 * @param int $ruleoutcome The value of ruleoutcome.
1687 * @return bool True on success.
1689 public static function set_course_competency_ruleoutcome($coursecompetencyorid, $ruleoutcome) {
1690 static::require_enabled();
1691 $coursecompetency = $coursecompetencyorid;
1692 if (!is_object($coursecompetency)) {
1693 $coursecompetency = new course_competency($coursecompetencyorid);
1696 $courseid = $coursecompetency->get_courseid();
1697 self::validate_course($courseid);
1698 $coursecontext = context_course::instance($courseid);
1700 require_capability('moodle/competency:coursecompetencymanage', $coursecontext);
1702 $coursecompetency->set_ruleoutcome($ruleoutcome);
1703 return $coursecompetency->update();
1707 * Create a learning plan template from a record containing all the data for the class.
1709 * Requires moodle/competency:templatemanage capability.
1711 * @param stdClass $record Record containing all the data for an instance of the class.
1714 public static function create_template(stdClass $record) {
1715 static::require_enabled();
1716 $template = new template(0, $record);
1718 // First we do a permissions check.
1719 if (!$template->can_manage()) {
1720 throw new required_capability_exception($template->get_context(), 'moodle/competency:templatemanage',
1721 'nopermissions', '');
1725 $template = $template->create();
1727 // Trigger a template created event.
1728 \core\event\competency_template_created::create_from_template($template)->trigger();
1734 * Duplicate a learning plan template.
1736 * Requires moodle/competency:templatemanage capability at the template context.
1738 * @param int $id the template id.
1741 public static function duplicate_template($id) {
1742 static::require_enabled();
1743 $template = new template($id);
1745 // First we do a permissions check.
1746 if (!$template->can_manage()) {
1747 throw new required_capability_exception($template->get_context(), 'moodle/competency:templatemanage',
1748 'nopermissions', '');
1752 $competencies = template_competency::list_competencies($id, false);
1754 // Adding the suffix copy.
1755 $template->set_shortname(get_string('duplicateditemname', 'core_competency', $template->get_shortname()));
1756 $template->set_id(0);
1758 $duplicatedtemplate = $template->create();
1760 // Associate each competency for the duplicated template.
1761 foreach ($competencies as $competency) {
1762 self::add_competency_to_template($duplicatedtemplate->get_id(), $competency->get_id());
1765 // Trigger a template created event.
1766 \core\event\competency_template_created::create_from_template($duplicatedtemplate)->trigger();
1768 return $duplicatedtemplate;
1772 * Delete a learning plan template by id.
1773 * If the learning plan template has associated cohorts they will be deleted.
1775 * Requires moodle/competency:templatemanage capability.
1777 * @param int $id The record to delete.
1778 * @param boolean $deleteplans True to delete plans associaated to template, false to unlink them.
1781 public static function delete_template($id, $deleteplans = true) {
1783 static::require_enabled();
1784 $template = new template($id);
1786 // First we do a permissions check.
1787 if (!$template->can_manage()) {
1788 throw new required_capability_exception($template->get_context(), 'moodle/competency:templatemanage',
1789 'nopermissions', '');
1792 $transaction = $DB->start_delegated_transaction();
1795 // Check if there are cohorts associated.
1796 $templatecohorts = template_cohort::get_relations_by_templateid($template->get_id());
1797 foreach ($templatecohorts as $templatecohort) {
1798 $success = $templatecohort->delete();
1804 // Still OK, delete or unlink the plans from the template.
1806 $plans = plan::get_records(array('templateid' => $template->get_id()));
1807 foreach ($plans as $plan) {
1808 $success = $deleteplans ? self::delete_plan($plan->get_id()) : self::unlink_plan_from_template($plan);
1815 // Still OK, delete the template comptencies.
1817 $success = template_competency::delete_by_templateid($template->get_id());
1822 // Create a template deleted event.
1823 $event = \core\event\competency_template_deleted::create_from_template($template);
1825 $success = $template->delete();
1829 // Trigger a template deleted event.
1832 // Commit the transaction.
1833 $transaction->allow_commit();
1835 $transaction->rollback(new moodle_exception('Error while deleting the template.'));
1842 * Update the details for a learning plan template.
1844 * Requires moodle/competency:templatemanage capability.
1846 * @param stdClass $record The new details for the template. Note - must contain an id that points to the template to update.
1849 public static function update_template($record) {
1851 static::require_enabled();
1852 $template = new template($record->id);
1854 // First we do a permissions check.
1855 if (!$template->can_manage()) {
1856 throw new required_capability_exception($template->get_context(), 'moodle/competency:templatemanage',
1857 'nopermissions', '');
1859 } else if (isset($record->contextid) && $record->contextid != $template->get_contextid()) {
1860 // We can never change the context of a template.
1861 throw new coding_exception('Changing the context of an existing tempalte is forbidden.');
1865 $updateplans = false;
1866 $before = $template->to_record();
1868 $template->from_record($record);
1869 $after = $template->to_record();
1871 // Should we update the related plans?
1872 if ($before->duedate != $after->duedate ||
1873 $before->shortname != $after->shortname ||
1874 $before->description != $after->description ||
1875 $before->descriptionformat != $after->descriptionformat) {
1876 $updateplans = true;
1879 $transaction = $DB->start_delegated_transaction();
1880 $success = $template->update();
1883 $transaction->rollback(new moodle_exception('Error while updating the template.'));
1887 // Trigger a template updated event.
1888 \core\event\competency_template_updated::create_from_template($template)->trigger();
1891 plan::update_multiple_from_template($template);
1894 $transaction->allow_commit();
1900 * Read a the details for a single learning plan template and return a record.
1902 * Requires moodle/competency:templateview capability at the system context.
1904 * @param int $id The id of the template to read.
1907 public static function read_template($id) {
1908 static::require_enabled();
1909 $template = new template($id);
1910 $context = $template->get_context();
1912 // First we do a permissions check.
1913 if (!$template->can_read()) {
1914 throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview',
1915 'nopermissions', '');
1923 * Perform a search based on the provided filters and return a paginated list of records.
1925 * Requires moodle/competency:templateview capability at the system context.
1927 * @param string $sort The column to sort on
1928 * @param string $order ('ASC' or 'DESC')
1929 * @param int $skip Number of records to skip (pagination)
1930 * @param int $limit Max of records to return (pagination)
1931 * @param context $context The parent context of the frameworks.
1932 * @param string $includes Defines what other contexts to fetch frameworks from.
1933 * Accepted values are:
1934 * - children: All descendants
1935 * - parents: All parents, grand parents, etc...
1936 * - self: Context passed only.
1937 * @param bool $onlyvisible If should list only visible templates
1938 * @return array of competency_framework
1940 public static function list_templates($sort, $order, $skip, $limit, $context, $includes = 'children', $onlyvisible = false) {
1942 static::require_enabled();
1944 // Get all the relevant contexts.
1945 $contexts = self::get_related_contexts($context, $includes,
1946 array('moodle/competency:templateview', 'moodle/competency:templatemanage'));
1948 // First we do a permissions check.
1949 if (empty($contexts)) {
1950 throw new required_capability_exception($context, 'moodle/competency:templateview', 'nopermissions', '');
1953 // Make the order by.
1955 if (!empty($sort)) {
1956 $orderby = $sort . ' ' . $order;
1960 $template = new template();
1961 list($insql, $params) = $DB->get_in_or_equal(array_keys($contexts), SQL_PARAMS_NAMED);
1962 $select = "contextid $insql";
1965 $select .= " AND visible = :visible";
1966 $params['visible'] = 1;
1968 return $template->get_records_select($select, $params, $orderby, '*', $skip, $limit);
1972 * Perform a search based on the provided filters and return how many results there are.
1974 * Requires moodle/competency:templateview capability at the system context.
1976 * @param context $context The parent context of the frameworks.
1977 * @param string $includes Defines what other contexts to fetch frameworks from.
1978 * Accepted values are:
1979 * - children: All descendants
1980 * - parents: All parents, grand parents, etc...
1981 * - self: Context passed only.
1984 public static function count_templates($context, $includes) {
1986 static::require_enabled();
1988 // First we do a permissions check.
1989 $contexts = self::get_related_contexts($context, $includes,
1990 array('moodle/competency:templateview', 'moodle/competency:templatemanage'));
1992 if (empty($contexts)) {
1993 throw new required_capability_exception($context, 'moodle/competency:templateview', 'nopermissions', '');
1997 $template = new template();
1998 list($insql, $inparams) = $DB->get_in_or_equal(array_keys($contexts), SQL_PARAMS_NAMED);
1999 return $template->count_records_select("contextid $insql", $inparams);
2003 * Count all the templates using a competency.
2005 * @param int $competencyid The id of the competency to check.
2008 public static function count_templates_using_competency($competencyid) {
2009 static::require_enabled();
2010 // First we do a permissions check.
2011 $context = context_system::instance();
2014 $capabilities = array('moodle/competency:templateview', 'moodle/competency:templatemanage');
2015 if (!has_any_capability($capabilities, $context)) {
2016 throw new required_capability_exception($context, 'moodle/competency:templateview', 'nopermissions', '');
2019 if (has_capability('moodle/competency:templatemanage', $context)) {
2024 return template_competency::count_templates($competencyid, $onlyvisible);
2028 * List all the learning plan templatesd using a competency.
2030 * @param int $competencyid The id of the competency to check.
2031 * @return array[stdClass] Array of stdClass containing id and shortname.
2033 public static function list_templates_using_competency($competencyid) {
2034 static::require_enabled();
2035 // First we do a permissions check.
2036 $context = context_system::instance();
2039 $capabilities = array('moodle/competency:templateview', 'moodle/competency:templatemanage');
2040 if (!has_any_capability($capabilities, $context)) {
2041 throw new required_capability_exception($context, 'moodle/competency:templateview', 'nopermissions', '');
2044 if (has_capability('moodle/competency:templatemanage', $context)) {
2049 return template_competency::list_templates($competencyid, $onlyvisible);
2054 * Count all the competencies in a learning plan template.
2056 * @param template|int $templateorid The template or its ID.
2059 public static function count_competencies_in_template($templateorid) {
2060 static::require_enabled();
2061 // First we do a permissions check.
2062 $template = $templateorid;
2063 if (!is_object($template)) {
2064 $template = new template($template);
2067 if (!$template->can_read()) {
2068 throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview',
2069 'nopermissions', '');
2073 return template_competency::count_competencies($template->get_id());
2077 * Count all the competencies in a learning plan template with no linked courses.
2079 * @param template|int $templateorid The template or its ID.
2082 public static function count_competencies_in_template_with_no_courses($templateorid) {
2083 // First we do a permissions check.
2084 $template = $templateorid;
2085 if (!is_object($template)) {
2086 $template = new template($template);
2089 if (!$template->can_read()) {
2090 throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview',
2091 'nopermissions', '');
2095 return template_competency::count_competencies_with_no_courses($template->get_id());
2099 * List all the competencies in a template.
2101 * @param template|int $templateorid The template or its ID.
2102 * @return array of competencies
2104 public static function list_competencies_in_template($templateorid) {
2105 static::require_enabled();
2106 // First we do a permissions check.
2107 $template = $templateorid;
2108 if (!is_object($template)) {
2109 $template = new template($template);
2112 if (!$template->can_read()) {
2113 throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview',
2114 'nopermissions', '');
2118 return template_competency::list_competencies($template->get_id());
2122 * Add a competency to this template.
2124 * @param int $templateid The id of the template
2125 * @param int $competencyid The id of the competency
2128 public static function add_competency_to_template($templateid, $competencyid) {
2129 static::require_enabled();
2130 // First we do a permissions check.
2131 $template = new template($templateid);
2132 if (!$template->can_manage()) {
2133 throw new required_capability_exception($template->get_context(), 'moodle/competency:templatemanage',
2134 'nopermissions', '');
2137 $record = new stdClass();
2138 $record->templateid = $templateid;
2139 $record->competencyid = $competencyid;
2141 $competency = new competency($competencyid);
2143 // Can not add a competency that belong to a hidden framework.
2144 if ($competency->get_framework()->get_visible() == false) {
2145 throw new coding_exception('A competency belonging to hidden framework can not be added');
2148 $exists = template_competency::get_records(array('templateid' => $templateid, 'competencyid' => $competencyid));
2150 $templatecompetency = new template_competency(0, $record);
2151 $templatecompetency->create();
2158 * Remove a competency from this template.
2160 * @param int $templateid The id of the template
2161 * @param int $competencyid The id of the competency
2164 public static function remove_competency_from_template($templateid, $competencyid) {
2165 static::require_enabled();
2166 // First we do a permissions check.
2167 $template = new template($templateid);
2168 if (!$template->can_manage()) {
2169 throw new required_capability_exception($template->get_context(), 'moodle/competency:templatemanage',
2170 'nopermissions', '');
2173 $record = new stdClass();
2174 $record->templateid = $templateid;
2175 $record->competencyid = $competencyid;
2177 $competency = new competency($competencyid);
2179 $exists = template_competency::get_records(array('templateid' => $templateid, 'competencyid' => $competencyid));
2181 $link = array_pop($exists);
2182 return $link->delete();
2188 * Move the template competency up or down in the display list.
2190 * Requires moodle/competency:templatemanage capability at the system context.
2192 * @param int $templateid The template id
2193 * @param int $competencyidfrom The id of the competency we are moving.
2194 * @param int $competencyidto The id of the competency we are moving to.
2197 public static function reorder_template_competency($templateid, $competencyidfrom, $competencyidto) {
2198 static::require_enabled();
2199 // First we do a permissions check.
2200 $context = context_system::instance();
2202 require_capability('moodle/competency:templatemanage', $context);
2205 $matches = template_competency::get_records(array('templateid' => $templateid, 'competencyid' => $competencyidfrom));
2206 if (count($matches) == 0) {
2207 throw new coding_exception('The link does not exist');
2210 $competencyfrom = array_pop($matches);
2211 $matches = template_competency::get_records(array('templateid' => $templateid, 'competencyid' => $competencyidto));
2212 if (count($matches) == 0) {
2213 throw new coding_exception('The link does not exist');
2216 $competencyto = array_pop($matches);
2218 $all = template_competency::get_records(array('templateid' => $templateid), 'sortorder', 'ASC', 0, 0);
2220 if ($competencyfrom->get_sortorder() > $competencyto->get_sortorder()) {
2221 // We are moving up, so put it before the "to" item.
2225 foreach ($all as $id => $templatecompetency) {
2226 $sort = $templatecompetency->get_sortorder();
2227 if ($down && $sort > $competencyfrom->get_sortorder() && $sort <= $competencyto->get_sortorder()) {
2228 $templatecompetency->set_sortorder($templatecompetency->get_sortorder() - 1);
2229 $templatecompetency->update();
2230 } else if (!$down && $sort >= $competencyto->get_sortorder() && $sort < $competencyfrom->get_sortorder()) {
2231 $templatecompetency->set_sortorder($templatecompetency->get_sortorder() + 1);
2232 $templatecompetency->update();
2235 $competencyfrom->set_sortorder($competencyto->get_sortorder());
2236 return $competencyfrom->update();
2240 * Create a relation between a template and a cohort.
2242 * This silently ignores when the relation already existed.
2244 * @param template|int $templateorid The template or its ID.
2245 * @param stdClass|int $cohortorid The cohort ot its ID.
2246 * @return template_cohort
2248 public static function create_template_cohort($templateorid, $cohortorid) {
2250 static::require_enabled();
2252 $template = $templateorid;
2253 if (!is_object($template)) {
2254 $template = new template($template);
2256 require_capability('moodle/competency:templatemanage', $template->get_context());
2258 $cohort = $cohortorid;
2259 if (!is_object($cohort)) {
2260 $cohort = $DB->get_record('cohort', array('id' => $cohort), '*', MUST_EXIST);
2263 // Replicate logic in cohort_can_view_cohort() because we can't use it directly as we don't have a course context.
2264 $cohortcontext = context::instance_by_id($cohort->contextid);
2265 if (!$cohort->visible && !has_capability('moodle/cohort:view', $cohortcontext)) {
2266 throw new required_capability_exception($cohortcontext, 'moodle/cohort:view', 'nopermissions', '');
2269 $tplcohort = template_cohort::get_relation($template->get_id(), $cohort->id);
2270 if (!$tplcohort->get_id()) {
2271 $tplcohort->create();
2278 * Remove a relation between a template and a cohort.
2280 * @param template|int $templateorid The template or its ID.
2281 * @param stdClass|int $cohortorid The cohort ot its ID.
2282 * @return boolean True on success or when the relation did not exist.
2284 public static function delete_template_cohort($templateorid, $cohortorid) {
2286 static::require_enabled();
2288 $template = $templateorid;
2289 if (!is_object($template)) {
2290 $template = new template($template);
2292 require_capability('moodle/competency:templatemanage', $template->get_context());
2294 $cohort = $cohortorid;
2295 if (!is_object($cohort)) {
2296 $cohort = $DB->get_record('cohort', array('id' => $cohort), '*', MUST_EXIST);
2299 $tplcohort = template_cohort::get_relation($template->get_id(), $cohort->id);
2300 if (!$tplcohort->get_id()) {
2304 return $tplcohort->delete();
2310 * @param int $userid
2311 * @return \core_competency\plan[]
2313 public static function list_user_plans($userid) {
2315 static::require_enabled();
2316 $select = 'userid = :userid';
2317 $params = array('userid' => $userid);
2318 $context = context_user::instance($userid);
2320 // Check that we can read something here.
2321 if (!plan::can_read_user($userid) && !plan::can_read_user_draft($userid)) {
2322 throw new required_capability_exception($context, 'moodle/competency:planview', 'nopermissions', '');
2325 // The user cannot view the drafts.
2326 if (!plan::can_read_user_draft($userid)) {
2327 list($insql, $inparams) = $DB->get_in_or_equal(plan::get_draft_statuses(), SQL_PARAMS_NAMED, 'param', false);
2328 $select .= " AND status $insql";
2329 $params += $inparams;
2331 // The user cannot view the non-drafts.
2332 if (!plan::can_read_user($userid)) {
2333 list($insql, $inparams) = $DB->get_in_or_equal(array(plan::STATUS_ACTIVE, plan::STATUS_COMPLETE),
2334 SQL_PARAMS_NAMED, 'param', false);
2335 $select .= " AND status $insql";
2336 $params += $inparams;
2339 return plan::get_records_select($select, $params, 'name ASC');
2343 * List the plans to review.
2345 * The method returns values in this format:
2351 * 'template' => (template),
2352 * 'owner' => (stdClass)
2358 * @param int $skip The number of records to skip.
2359 * @param int $limit The number of results to return.
2360 * @param int $userid The user we're getting the plans to review for.
2361 * @return array Containing the keys 'count', and 'plans'. The 'plans' key contains an object
2362 * which contains 'plan', 'template' and 'owner'.
2364 public static function list_plans_to_review($skip = 0, $limit = 100, $userid = null) {
2366 static::require_enabled();
2368 if ($userid === null) {
2369 $userid = $USER->id;
2372 $planfields = plan::get_sql_fields('p', 'plan_');
2373 $tplfields = template::get_sql_fields('t', 'tpl_');
2374 $usercols = array('id') + get_user_fieldnames();
2375 $userfields = array();
2376 foreach ($usercols as $field) {
2377 $userfields[] = "u." . $field . " AS usr_" . $field;
2379 $userfields = implode(',', $userfields);
2381 $select = "SELECT $planfields, $tplfields, $userfields";
2382 $countselect = "SELECT COUNT('x')";
2384 $sql = " FROM {" . plan::TABLE . "} p
2387 LEFT JOIN {" . template::TABLE . "} t
2388 ON t.id = p.templateid
2389 WHERE (p.status = :waitingforreview
2390 OR (p.status = :inreview AND p.reviewerid = :reviewerid))
2391 AND p.userid != :userid";
2394 'waitingforreview' => plan::STATUS_WAITING_FOR_REVIEW,
2395 'inreview' => plan::STATUS_IN_REVIEW,
2396 'reviewerid' => $userid,
2400 // Primary check to avoid the hard work of getting the users in which the user has permission.
2401 $count = $DB->count_records_sql($countselect . $sql, $params);
2403 return array('count' => 0, 'plans' => array());
2406 // TODO MDL-52243 Use core function.
2407 list($insql, $inparams) = self::filter_users_with_capability_on_user_context_sql('moodle/competency:planreview',
2408 $userid, SQL_PARAMS_NAMED);
2409 $sql .= " AND p.userid $insql";
2410 $params += $inparams;
2412 // Order by ID just to have some ordering in place.
2413 $ordersql = " ORDER BY p.id ASC";
2416 $records = $DB->get_recordset_sql($select . $sql . $ordersql, $params, $skip, $limit);
2417 foreach ($records as $record) {
2418 $plan = new plan(0, plan::extract_record($record, 'plan_'));
2421 if ($plan->is_based_on_template()) {
2422 $template = new template(0, template::extract_record($record, 'tpl_'));
2425 $plans[] = (object) array(
2427 'template' => $template,
2428 'owner' => persistent::extract_record($record, 'usr_'),
2434 'count' => $DB->count_records_sql($countselect . $sql, $params),
2440 * Creates a learning plan based on the provided data.
2442 * @param stdClass $record
2443 * @return \core_competency\plan
2445 public static function create_plan(stdClass $record) {
2447 static::require_enabled();
2448 $plan = new plan(0, $record);
2450 if ($plan->is_based_on_template()) {
2451 throw new coding_exception('To create a plan from a template use api::create_plan_from_template().');
2452 } else if ($plan->get_status() == plan::STATUS_COMPLETE) {
2453 throw new coding_exception('A plan cannot be created as complete.');
2456 if (!$plan->can_manage()) {
2457 $context = context_user::instance($plan->get_userid());
2458 throw new required_capability_exception($context, 'moodle/competency:planmanage', 'nopermissions', '');
2463 // Trigger created event.
2464 \core\event\competency_plan_created::create_from_plan($plan)->trigger();
2469 * Create a learning plan from a template.
2471 * @param mixed $templateorid The template object or ID.
2472 * @param int $userid
2473 * @return false|\core_competency\plan Returns false when the plan already exists.
2475 public static function create_plan_from_template($templateorid, $userid) {
2476 static::require_enabled();
2477 $template = $templateorid;
2478 if (!is_object($template)) {
2479 $template = new template($template);
2482 // The user must be able to view the template to use it as a base for a plan.
2483 if (!$template->can_read()) {
2484 throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview',
2485 'nopermissions', '');
2487 // Can not create plan from a hidden template.
2488 if ($template->get_visible() == false) {
2489 throw new coding_exception('A plan can not be created from a hidden template');
2492 // Convert the template to a plan.
2493 $record = $template->to_record();
2494 $record->templateid = $record->id;
2495 $record->userid = $userid;
2496 $record->name = $record->shortname;
2497 $record->status = plan::STATUS_ACTIVE;
2500 unset($record->timecreated);
2501 unset($record->timemodified);
2502 unset($record->usermodified);
2504 // Remove extra keys.
2505 $properties = plan::properties_definition();
2506 foreach ($record as $key => $value) {
2507 if (!array_key_exists($key, $properties)) {
2508 unset($record->$key);
2512 $plan = new plan(0, $record);
2513 if (!$plan->can_manage()) {
2514 throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage',
2515 'nopermissions', '');
2518 // We first apply the permission checks as we wouldn't want to leak information by returning early that
2519 // the plan already exists.
2520 if (plan::record_exists_select('templateid = :templateid AND userid = :userid', array(
2521 'templateid' => $template->get_id(), 'userid' => $userid))) {
2527 // Trigger created event.
2528 \core\event\competency_plan_created::create_from_plan($plan)->trigger();
2533 * Create learning plans from a template and cohort.
2535 * @param mixed $templateorid The template object or ID.
2536 * @param int $cohortid The cohort ID.
2537 * @param bool $recreateunlinked When true the plans that were unlinked from this template will be re-created.
2538 * @return int The number of plans created.
2540 public static function create_plans_from_template_cohort($templateorid, $cohortid, $recreateunlinked = false) {
2542 static::require_enabled();
2543 require_once($CFG->dirroot . '/cohort/lib.php');
2545 $template = $templateorid;
2546 if (!is_object($template)) {
2547 $template = new template($template);
2550 // The user must be able to view the template to use it as a base for a plan.
2551 if (!$template->can_read()) {
2552 throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview',
2553 'nopermissions', '');
2556 // Can not create plan from a hidden template.
2557 if ($template->get_visible() == false) {
2558 throw new coding_exception('A plan can not be created from a hidden template');
2561 // Replicate logic in cohort_can_view_cohort() because we can't use it directly as we don't have a course context.
2562 $cohort = $DB->get_record('cohort', array('id' => $cohortid), '*', MUST_EXIST);
2563 $cohortcontext = context::instance_by_id($cohort->contextid);
2564 if (!$cohort->visible && !has_capability('moodle/cohort:view', $cohortcontext)) {
2565 throw new required_capability_exception($cohortcontext, 'moodle/cohort:view', 'nopermissions', '');
2568 // Convert the template to a plan.
2569 $recordbase = $template->to_record();
2570 $recordbase->templateid = $recordbase->id;
2571 $recordbase->name = $recordbase->shortname;
2572 $recordbase->status = plan::STATUS_ACTIVE;
2574 unset($recordbase->id);
2575 unset($recordbase->timecreated);
2576 unset($recordbase->timemodified);
2577 unset($recordbase->usermodified);
2579 // Remove extra keys.
2580 $properties = plan::properties_definition();
2581 foreach ($recordbase as $key => $value) {
2582 if (!array_key_exists($key, $properties)) {
2583 unset($recordbase->$key);
2587 // Create the plans.
2589 $userids = template_cohort::get_missing_plans($template->get_id(), $cohortid, $recreateunlinked);
2590 foreach ($userids as $userid) {
2591 $record = (object) (array) $recordbase;
2592 $record->userid = $userid;
2594 $plan = new plan(0, $record);
2595 if (!$plan->can_manage()) {
2596 // Silently skip members where permissions are lacking.
2601 // Trigger created event.
2602 \core\event\competency_plan_created::create_from_plan($plan)->trigger();
2610 * Unlink a plan from its template.
2612 * @param \core_competency\plan|int $planorid The plan or its ID.
2615 public static function unlink_plan_from_template($planorid) {
2617 static::require_enabled();
2620 if (!is_object($planorid)) {
2621 $plan = new plan($planorid);
2624 // The user must be allowed to manage the plans of the user, nothing about the template.
2625 if (!$plan->can_manage()) {
2626 throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', '');
2629 // Only plan with status DRAFT or ACTIVE can be unliked..
2630 if ($plan->get_status() == plan::STATUS_COMPLETE) {
2631 throw new coding_exception('Only draft or active plan can be unliked from a template');
2634 // Early exit, it's already done...
2635 if (!$plan->is_based_on_template()) {
2639 // Fetch the template.
2640 $template = new template($plan->get_templateid());
2642 // Now, proceed by copying all competencies to the plan, then update the plan.
2643 $transaction = $DB->start_delegated_transaction();
2644 $competencies = template_competency::list_competencies($template->get_id(), false);
2646 foreach ($competencies as $competency) {
2647 $record = (object) array(
2648 'planid' => $plan->get_id(),
2649 'competencyid' => $competency->get_id(),
2652 $pc = new plan_competency(null, $record);
2655 $plan->set_origtemplateid($template->get_id());
2656 $plan->set_templateid(null);
2657 $success = $plan->update();
2658 $transaction->allow_commit();
2660 // Trigger unlinked event.
2661 \core\event\competency_plan_unlinked::create_from_plan($plan)->trigger();
2669 * @param stdClass $record
2670 * @return \core_competency\plan
2672 public static function update_plan(stdClass $record) {
2673 static::require_enabled();
2675 $plan = new plan($record->id);
2677 // Validate that the plan as it is can be managed.
2678 if (!$plan->can_manage()) {
2679 throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', '');
2681 } else if ($plan->get_status() == plan::STATUS_COMPLETE) {
2682 // A completed plan cannot be edited.
2683 throw new coding_exception('Completed plan cannot be edited.');
2685 } else if ($plan->is_based_on_template()) {
2686 // Prevent a plan based on a template to be edited.
2687 throw new coding_exception('Cannot update a plan that is based on a template.');
2689 } else if (isset($record->templateid) && $plan->get_templateid() != $record->templateid) {
2690 // Prevent a plan to be based on a template.
2691 throw new coding_exception('Cannot base a plan on a template.');
2693 } else if (isset($record->userid) && $plan->get_userid() != $record->userid) {
2694 // Prevent change of ownership as the capabilities are checked against that.
2695 throw new coding_exception('A plan cannot be transfered to another user');
2697 } else if (isset($record->status) && $plan->get_status() != $record->status) {
2698 // Prevent change of status.
2699 throw new coding_exception('To change the status of a plan use the appropriate methods.');
2703 $plan->from_record($record);
2706 // Trigger updated event.
2707 \core\event\competency_plan_updated::create_from_plan($plan)->trigger();
2713 * Returns a plan data.
2716 * @return \core_competency\plan
2718 public static function read_plan($id) {
2719 static::require_enabled();
2720 $plan = new plan($id);
2722 if (!$plan->can_read()) {
2723 $context = context_user::instance($plan->get_userid());
2724 throw new required_capability_exception($context, 'moodle/competency:planview', 'nopermissions', '');
2731 * Plan event viewed.
2733 * @param mixed $planorid The id or the plan.
2736 public static function plan_viewed($planorid) {
2737 static::require_enabled();
2739 if (!is_object($plan)) {
2740 $plan = new plan($plan);
2743 // First we do a permissions check.
2744 if (!$plan->can_read()) {
2745 $context = context_user::instance($plan->get_userid());
2746 throw new required_capability_exception($context, 'moodle/competency:planview', 'nopermissions', '');
2749 // Trigger a template viewed event.
2750 \core\event\competency_plan_viewed::create_from_plan($plan)->trigger();
2758 * Plans based on a template can be removed just like any other one.
2761 * @return bool Success?
2763 public static function delete_plan($id) {
2765 static::require_enabled();
2767 $plan = new plan($id);
2769 if (!$plan->can_manage()) {
2770 $context = context_user::instance($plan->get_userid());
2771 throw new required_capability_exception($context, 'moodle/competency:planmanage', 'nopermissions', '');
2774 // Wrap the suppression in a DB transaction.
2775 $transaction = $DB->start_delegated_transaction();
2777 // Delete plan competencies.
2778 $plancomps = plan_competency::get_records(array('planid' => $plan->get_id()));
2779 foreach ($plancomps as $plancomp) {
2780 $plancomp->delete();
2783 // Delete archive user competencies if the status of the plan is complete.
2784 if ($plan->get_status() == plan::STATUS_COMPLETE) {
2785 self::remove_archived_user_competencies_in_plan($plan);
2787 $event = \core\event\competency_plan_deleted::create_from_plan($plan);
2788 $success = $plan->delete();
2790 $transaction->allow_commit();
2792 // Trigger deleted event.
2799 * Cancel the review of a plan.
2801 * @param int|plan $planorid The plan, or its ID.
2804 public static function plan_cancel_review_request($planorid) {
2805 static::require_enabled();
2807 if (!is_object($plan)) {
2808 $plan = new plan($plan);
2811 // We need to be able to view the plan at least.
2812 if (!$plan->can_read()) {
2813 throw new required_capability_exception($plan->get_context(), 'moodle/competency:planview', 'nopermissions', '');
2816 if ($plan->is_based_on_template()) {
2817 throw new coding_exception('Template plans cannot be reviewed.'); // This should never happen.
2818 } else if ($plan->get_status() != plan::STATUS_WAITING_FOR_REVIEW) {
2819 throw new coding_exception('The plan review cannot be cancelled at this stage.');
2820 } else if (!$plan->can_request_review()) {
2821 throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', '');
2824 $plan->set_status(plan::STATUS_DRAFT);
2825 $result = $plan->update();
2827 // Trigger review request cancelled event.
2828 \core\event\competency_plan_review_request_cancelled::create_from_plan($plan)->trigger();
2834 * Request the review of a plan.
2836 * @param int|plan $planorid The plan, or its ID.
2839 public static function plan_request_review($planorid) {
2840 static::require_enabled();
2842 if (!is_object($plan)) {
2843 $plan = new plan($plan);
2846 // We need to be able to view the plan at least.
2847 if (!$plan->can_read()) {
2848 throw new required_capability_exception($plan->get_context(), 'moodle/competency:planview', 'nopermissions', '');
2851 if ($plan->is_based_on_template()) {
2852 throw new coding_exception('Template plans cannot be reviewed.'); // This should never happen.
2853 } else if ($plan->get_status() != plan::STATUS_DRAFT) {
2854 throw new coding_exception('The plan cannot be sent for review at this stage.');
2855 } else if (!$plan->can_request_review()) {
2856 throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', '');
2859 $plan->set_status(plan::STATUS_WAITING_FOR_REVIEW);
2860 $result = $plan->update();
2862 // Trigger review requested event.
2863 \core\event\competency_plan_review_requested::create_from_plan($plan)->trigger();
2869 * Start the review of a plan.
2871 * @param int|plan $planorid The plan, or its ID.
2874 public static function plan_start_review($planorid) {
2876 static::require_enabled();
2878 if (!is_object($plan)) {
2879 $plan = new plan($plan);
2882 // We need to be able to view the plan at least.
2883 if (!$plan->can_read()) {
2884 throw new required_capability_exception($plan->get_context(), 'moodle/competency:planview', 'nopermissions', '');
2887 if ($plan->is_based_on_template()) {
2888 throw new coding_exception('Template plans cannot be reviewed.'); // This should never happen.
2889 } else if ($plan->get_status() != plan::STATUS_WAITING_FOR_REVIEW) {
2890 throw new coding_exception('The plan review cannot be started at this stage.');
2891 } else if (!$plan->can_review()) {
2892 throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', '');
2895 $plan->set_status(plan::STATUS_IN_REVIEW);
2896 $plan->set_reviewerid($USER->id);
2897 $result = $plan->update();
2899 // Trigger review started event.
2900 \core\event\competency_plan_review_started::create_from_plan($plan)->trigger();
2906 * Stop reviewing a plan.
2908 * @param int|plan $planorid The plan, or its ID.
2911 public static function plan_stop_review($planorid) {
2912 static::require_enabled();
2914 if (!is_object($plan)) {
2915 $plan = new plan($plan);
2918 // We need to be able to view the plan at least.
2919 if (!$plan->can_read()) {
2920 throw new required_capability_exception($plan->get_context(), 'moodle/competency:planview', 'nopermissions', '');
2923 if ($plan->is_based_on_template()) {
2924 throw new coding_exception('Template plans cannot be reviewed.'); // This should never happen.
2925 } else if ($plan->get_status() != plan::STATUS_IN_REVIEW) {
2926 throw new coding_exception('The plan review cannot be stopped at this stage.');
2927 } else if (!$plan->can_review()) {
2928 throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', '');
2931 $plan->set_status(plan::STATUS_DRAFT);
2932 $plan->set_reviewerid(null);
2933 $result = $plan->update();
2935 // Trigger review stopped event.
2936 \core\event\competency_plan_review_stopped::create_from_plan($plan)->trigger();
2944 * This means making the plan active.
2946 * @param int|plan $planorid The plan, or its ID.
2949 public static function approve_plan($planorid) {
2950 static::require_enabled();
2952 if (!is_object($plan)) {
2953 $plan = new plan($plan);
2956 // We need to be able to view the plan at least.
2957 if (!$plan->can_read()) {
2958 throw new required_capability_exception($plan->get_context(), 'moodle/competency:planview', 'nopermissions', '');
2961 // We can approve a plan that is either a draft, in review, or waiting for review.
2962 if ($plan->is_based_on_template()) {
2963 throw new coding_exception('Template plans are already approved.'); // This should never happen.
2964 } else if (!$plan->is_draft()) {
2965 throw new coding_exception('The plan cannot be approved at this stage.');
2966 } else if (!$plan->can_review()) {
2967 throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', '');
2970 $plan->set_status(plan::STATUS_ACTIVE);
2971 $plan->set_reviewerid(null);
2972 $result = $plan->update();
2974 // Trigger approved event.
2975 \core\event\competency_plan_approved::create_from_plan($plan)->trigger();
2983 * This means making the plan draft.
2985 * @param int|plan $planorid The plan, or its ID.
2988 public static function unapprove_plan($planorid) {
2989 static::require_enabled();
2991 if (!is_object($plan)) {
2992 $plan = new plan($plan);
2995 // We need to be able to view the plan at least.
2996 if (!$plan->can_read()) {
2997 throw new required_capability_exception($plan->get_context(), 'moodle/competency:planview', 'nopermissions', '');
3000 if ($plan->is_based_on_template()) {
3001 throw new coding_exception('Template plans are always approved.'); // This should never happen.
3002 } else if ($plan->get_status() != plan::STATUS_ACTIVE) {
3003 throw new coding_exception('The plan cannot be sent back to draft at this stage.');
3004 } else if (!$plan->can_review()) {
3005 throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', '');
3008 $plan->set_status(plan::STATUS_DRAFT);
3009 $result = $plan->update();
3011 // Trigger unapproved event.
3012 \core\event\competency_plan_unapproved::create_from_plan($plan)->trigger();
3020 * @param int|plan $planorid The plan, or its ID.
3023 public static function complete_plan($planorid) {
3025 static::require_enabled();
3028 if (!is_object($planorid)) {
3029 $plan = new plan($planorid);
3032 // Validate that the plan can be managed.
3033 if (!$plan->can_manage()) {
3034 throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', '');
3037 // Check if the plan was already completed.
3038 if ($plan->get_status() == plan::STATUS_COMPLETE) {
3039 throw new coding_exception('The plan is already completed.');
3042 $originalstatus = $plan->get_status();
3043 $plan->set_status(plan::STATUS_COMPLETE);
3045 // The user should also be able to manage the plan when it's completed.
3046 if (!$plan->can_manage()) {
3047 throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', '');
3050 // Put back original status because archive needs it to extract competencies from the right table.
3051 $plan->set_status($originalstatus);
3054 $transaction = $DB->start_delegated_transaction();
3055 self::archive_user_competencies_in_plan($plan);
3056 $plan->set_status(plan::STATUS_COMPLETE);
3057 $success = $plan->update();
3060 $transaction->rollback(new moodle_exception('The plan could not be updated.'));
3064 $transaction->allow_commit();
3066 // Trigger updated event.
3067 \core\event\competency_plan_completed::create_from_plan($plan)->trigger();
3075 * @param int|plan $planorid The plan, or its ID.
3078 public static function reopen_plan($planorid) {
3080 static::require_enabled();
3083 if (!is_object($planorid)) {
3084 $plan = new plan($planorid);
3087 // Validate that the plan as it is can be managed.
3088 if (!$plan->can_manage()) {
3089 $context = context_user::instance($plan->get_userid());
3090 throw new required_capability_exception($context, 'moodle/competency:planmanage', 'nopermissions', '');
3093 $beforestatus = $plan->get_status();
3094 $plan->set_status(plan::STATUS_ACTIVE);
3096 // Validate if status can be changed.
3097 if (!$plan->can_manage()) {
3098 $context = context_user::instance($plan->get_userid());
3099 throw new required_capability_exception($context, 'moodle/competency:planmanage', 'nopermissions', '');
3102 // Wrap the updates in a DB transaction.
3103 $transaction = $DB->start_delegated_transaction();
3105 // Delete archived user competencies if the status of the plan is changed from complete to another status.
3106 $mustremovearchivedcompetencies = ($beforestatus == plan::STATUS_COMPLETE && $plan->get_status() != plan::STATUS_COMPLETE);
3107 if ($mustremovearchivedcompetencies) {
3108 self::remove_archived_user_competencies_in_plan($plan);
3111 // If duedate less than or equal to duedate_threshold unset it.
3112 if ($plan->get_duedate() <= time() + plan::DUEDATE_THRESHOLD) {
3113 $plan->set_duedate(0);
3116 $success = $plan->update();
3119 $transaction->rollback(new moodle_exception('The plan could not be updated.'));
3123 $transaction->allow_commit();
3125 // Trigger reopened event.
3126 \core\event\competency_plan_reopened::create_from_plan($plan)->trigger();
3132 * Get a single competency from the user plan.
3134 * @param int $planorid The plan, or its ID.
3135 * @param int $competencyid The competency id.
3136 * @return (object) array(
3137 * 'competency' => \core_competency\competency,
3138 * 'usercompetency' => \core_competency\user_competency
3139 * 'usercompetencyplan' => \core_competency\user_competency_plan
3141 * The values of of keys usercompetency and usercompetencyplan cannot be defined at the same time.
3143 public static function get_plan_competency($planorid, $competencyid) {
3144 static::require_enabled();
3146 if (!is_object($planorid)) {
3147 $plan = new plan($planorid);
3150 if (!user_competency::can_read_user($plan->get_userid())) {
3151 throw new required_capability_exception($plan->get_context(), 'moodle/competency:usercompetencyview',
3152 'nopermissions', '');
3155 $competency = $plan->get_competency($competencyid);
3157 // Get user competencies from user_competency_plan if the plan status is set to complete.
3158 $iscompletedplan = $plan->get_status() == plan::STATUS_COMPLETE;
3159 if ($iscompletedplan) {
3160 $usercompetencies = user_competency_plan::get_multiple($plan->get_userid(), $plan->get_id(), array($competencyid));
3161 $ucresultkey = 'usercompetencyplan';
3163 $usercompetencies = user_competency::get_multiple($plan->get_userid(), array($competencyid));
3164 $ucresultkey = 'usercompetency';
3167 $found = count($usercompetencies);
3170 $uc = array_pop($usercompetencies);
3172 if ($iscompletedplan) {
3173 throw new coding_exception('A user competency plan is missing');
3175 $uc = user_competency::create_relation($plan->get_userid(), $competency->get_id());
3180 $plancompetency = (object) array(
3181 'competency' => $competency,
3182 'usercompetency' => null,
3183 'usercompetencyplan' => null
3185 $plancompetency->$ucresultkey = $uc;
3187 return $plancompetency;
3191 * List the competencies in a user plan.
3193 * @param int $planorid The plan, or its ID.
3194 * @return array((object) array(
3195 * 'competency' => \core_competency\competency,
3196 * 'usercompetency' => \core_competency\user_competency
3197 * 'usercompetencyplan' => \core_competency\user_competency_plan
3199 * The values of of keys usercompetency and usercompetencyplan cannot be defined at the same time.
3201 public static function list_plan_competencies($planorid) {
3202 static::require_enabled();
3204 if (!is_object($planorid)) {
3205 $plan = new plan($planorid);
3208 if (!$plan->can_read()) {
3209 $context = context_user::instance($plan->get_userid());
3210 throw new required_capability_exception($context, 'moodle/competency:planview', 'nopermissions', '');
3214 $competencies = $plan->get_competencies();
3216 // Get user competencies from user_competency_plan if the plan status is set to complete.
3217 $iscompletedplan = $plan->get_status() == plan::STATUS_COMPLETE;
3218 if ($iscompletedplan) {
3219 $usercompetencies = user_competency_plan::get_multiple($plan->get_userid(), $plan->get_id(), $competencies);
3220 $ucresultkey = 'usercompetencyplan';
3222 $usercompetencies = user_competency::get_multiple($plan->get_userid(), $competencies);
3223 $ucresultkey = 'usercompetency';
3226 // Build the return values.
3227 foreach ($competencies as $key => $competency) {
3230 foreach ($usercompetencies as $uckey => $uc) {
3231 if ($uc->get_competencyid() == $competency->get_id()) {
3233 unset($usercompetencies[$uckey]);
3239 if ($iscompletedplan) {
3240 throw new coding_exception('A user competency plan is missing');
3242 $uc = user_competency::create_relation($plan->get_userid(), $competency->get_id());
3246 $plancompetency = (object) array(
3247 'competency' => $competency,
3248 'usercompetency' => null,
3249 'usercompetencyplan' => null
3251 $plancompetency->$ucresultkey = $uc;
3252 $result[] = $plancompetency;
3259 * Add a competency to a plan.
3261 * @param int $planid The id of the plan
3262 * @param int $competencyid The id of the competency
3265 public static function add_competency_to_plan($planid, $competencyid) {
3266 static::require_enabled();
3267 $plan = new plan($planid);
3269 // First we do a permissions check.
3270 if (!$plan->can_manage()) {
3271 throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', '');
3273 } else if ($plan->is_based_on_template()) {
3274 throw new coding_exception('A competency can not be added to a learning plan based on a template');
3277 if (!$plan->can_be_edited()) {
3278 throw new coding_exception('A competency can not be added to a learning plan completed');
3281 $competency = new competency($competencyid);
3283 // Can not add a competency that belong to a hidden framework.
3284 if ($competency->get_framework()->get_visible() == false) {
3285 throw new coding_exception('A competency belonging to hidden framework can not be added');
3288 $exists = plan_competency::get_record(array('planid' => $planid, 'competencyid' => $competencyid));
3290 $record = new stdClass();
3291 $record->planid = $planid;
3292 $record->competencyid = $competencyid;
3293 $plancompetency = new plan_competency(0, $record);
3294 $plancompetency->create();
3301 * Remove a competency from a plan.
3303 * @param int $planid The plan id
3304 * @param int $competencyid The id of the competency
3307 public static function remove_competency_from_plan($planid, $competencyid) {
3308 static::require_enabled();
3309 $plan = new plan($planid);
3311 // First we do a permissions check.
3312 if (!$plan->can_manage()) {
3313 $context = context_user::instance($plan->get_userid());
3314 throw new required_capability_exception($context, 'moodle/competency:planmanage', 'nopermissions', '');
3316 } else if ($plan->is_based_on_template()) {
3317 throw new coding_exception('A competency can not be removed from a learning plan based on a template');
3320 if (!$plan->can_be_edited()) {
3321 throw new coding_exception('A competency can not be removed from a learning plan completed');
3324 $link = plan_competency::get_record(array('planid' => $planid, 'competencyid' => $competencyid));
3326 return $link->delete();
3332 * Move the plan competency up or down in the display list.
3334 * Requires moodle/competency:planmanage capability at the system context.
3336 * @param int $planid The plan id
3337 * @param int $competencyidfrom The id of the competency we are moving.
3338 * @param int $competencyidto The id of the competency we are moving to.
3341 public static function reorder_plan_competency($planid, $competencyidfrom, $competencyidto) {
3342 static::require_enabled();
3343 $plan = new plan($planid);
3345 // First we do a permissions check.
3346 if (!$plan->can_manage()) {
3347 $context = context_user::instance($plan->get_userid());
3348 throw new required_capability_exception($context, 'moodle/competency:planmanage', 'nopermissions', '');
3350 } else if ($plan->is_based_on_template()) {
3351 throw new coding_exception('A competency can not be reordered in a learning plan based on a template');
3354 if (!$plan->can_be_edited()) {
3355 throw new coding_exception('A competency can not be reordered in a learning plan completed');
3359 $matches = plan_competency::get_records(array('planid' => $planid, 'competencyid' => $competencyidfrom));
3360 if (count($matches) == 0) {
3361 throw new coding_exception('The link does not exist');
3364 $competencyfrom = array_pop($matches);
3365 $matches = plan_competency::get_records(array('planid' => $planid, 'competencyid' => $competencyidto));
3366 if (count($matches) == 0) {
3367 throw new coding_exception('The link does not exist');
3370 $competencyto = array_pop($matches);
3372 $all = plan_competency::get_records(array('planid' => $planid), 'sortorder', 'ASC', 0, 0);
3374 if ($competencyfrom->get_sortorder() > $competencyto->get_sortorder()) {
3375 // We are moving up, so put it before the "to" item.
3379 foreach ($all as $id => $plancompetency) {
3380 $sort = $plancompetency->get_sortorder();
3381 if ($down && $sort > $competencyfrom->get_sortorder() && $sort <= $competencyto->get_sortorder()) {
3382 $plancompetency->set_sortorder($plancompetency->get_sortorder() - 1);
3383 $plancompetency->update();
3384 } else if (!$down && $sort >= $competencyto->get_sortorder() && $sort < $competencyfrom->get_sortorder()) {
3385 $plancompetency->set_sortorder($plancompetency->get_sortorder() + 1);
3386 $plancompetency->update();
3389 $competencyfrom->set_sortorder($competencyto->get_sortorder());
3390 return $competencyfrom->update();
3394 * Cancel a user competency review request.
3396 * @param int $userid The user ID.
3397 * @param int $competencyid The competency ID.
3400 public static function user_competency_cancel_review_request($userid, $competencyid) {
3401 static::require_enabled();
3402 $context = context_user::instance($userid);
3403 $uc = user_competency::get_record(array('userid' => $userid, 'competencyid' => $competencyid));
3404 if (!$uc || !$uc->can_read()) {
3405 throw new required_capability_exception($context, 'moodle/competency:usercompetencyview', 'nopermissions', '');
3406 } else if ($uc->get_status() != user_competency::STATUS_WAITING_FOR_REVIEW) {
3407 throw new coding_exception('The competency can not be cancel review request at this stage.');
3408 } else if (!$uc->can_request_review()) {
3409 throw new required_capability_exception($context, 'moodle/competency:usercompetencyrequestreview', 'nopermissions', '');
3412 $uc->set_status(user_competency::STATUS_IDLE);
3413 $result = $uc->update();
3415 \core\event\competency_user_competency_review_request_cancelled::create_from_user_competency($uc)->trigger();
3421 * Request a user competency review.
3423 * @param int $userid The user ID.
3424 * @param int $competencyid The competency ID.
3427 public static function user_competency_request_review($userid, $competencyid) {
3428 static::require_enabled();
3429 $uc = user_competency::get_record(array('userid' => $userid, 'competencyid' => $competencyid));
3431 $uc = user_competency::create_relation($userid, $competencyid);
3435 if (!$uc->can_read()) {
3436 throw new required_capability_exception($uc->get_context(), 'moodle/competency:usercompetencyview',
3437 'nopermissions', '');
3438 } else if ($uc->get_status() != user_competency::STATUS_IDLE) {
3439 throw new coding_exception('The competency can not be sent for review at this stage.');
3440 } else if (!$uc->can_request_review()) {
3441 throw new required_capability_exception($uc->get_context(), 'moodle/competency:usercompetencyrequestreview',
3442 'nopermissions', '');
3445 $uc->set_status(user_competency::STATUS_WAITING_FOR_REVIEW);
3446 $result = $uc->update();