Merge branch 'MDL-80072-main' of https://github.com/andrewnicols/moodle
[moodle.git] / competency / classes / api.php
blobdccc12f3403415e6ad8c09df39b6d7053147fd9c
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
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.
8 //
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/>.
17 /**
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();
27 use stdClass;
28 use cm_info;
29 use context;
30 use context_helper;
31 use context_system;
32 use context_course;
33 use context_module;
34 use context_user;
35 use coding_exception;
36 use require_login_exception;
37 use moodle_exception;
38 use moodle_url;
39 use required_capability_exception;
41 /**
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
47 class api {
49 /** @var boolean Allow api functions even if competencies are not enabled for the site. */
50 private static $skipenabled = false;
52 /**
53 * Returns whether competencies are enabled.
55 * This method should never do more than checking the config setting, the reason
56 * being that some other code could be checking the config value directly
57 * to avoid having to load this entire file into memory.
59 * @return boolean True when enabled.
61 public static function is_enabled() {
62 return self::$skipenabled || get_config('core_competency', 'enabled');
65 /**
66 * When competencies used to be enabled, we can show the text but do not include links.
68 * @return boolean True means show links.
70 public static function show_links() {
71 return isloggedin() && !isguestuser() && get_config('core_competency', 'enabled');
74 /**
75 * Allow calls to competency api functions even if competencies are not currently enabled.
77 public static function skip_enabled() {
78 self::$skipenabled = true;
81 /**
82 * Restore the checking that competencies are enabled with any api function.
84 public static function check_enabled() {
85 self::$skipenabled = false;
88 /**
89 * Throws an exception if competencies are not enabled.
91 * @return void
92 * @throws moodle_exception
94 public static function require_enabled() {
95 if (!static::is_enabled()) {
96 throw new moodle_exception('competenciesarenotenabled', 'core_competency');
101 * Checks whether a scale is used anywhere in the plugin.
103 * This public API has two exceptions:
104 * - It MUST NOT perform any capability checks.
105 * - It MUST ignore whether competencies are enabled or not ({@link self::is_enabled()}).
107 * @param int $scaleid The scale ID.
108 * @return bool
110 public static function is_scale_used_anywhere($scaleid) {
111 global $DB;
112 $sql = "SELECT s.id
113 FROM {scale} s
114 LEFT JOIN {" . competency_framework::TABLE ."} f
115 ON f.scaleid = :scaleid1
116 LEFT JOIN {" . competency::TABLE ."} c
117 ON c.scaleid = :scaleid2
118 WHERE f.id IS NOT NULL
119 OR c.id IS NOT NULL";
120 return $DB->record_exists_sql($sql, ['scaleid1' => $scaleid, 'scaleid2' => $scaleid]);
124 * Validate if current user have acces to the course_module if hidden.
126 * @param mixed $cmmixed The cm_info class, course module record or its ID.
127 * @param bool $throwexception Throw an exception or not.
128 * @return bool
130 protected static function validate_course_module($cmmixed, $throwexception = true) {
131 $cm = $cmmixed;
132 if (!is_object($cm)) {
133 $cmrecord = get_coursemodule_from_id(null, $cmmixed);
134 $modinfo = get_fast_modinfo($cmrecord->course);
135 $cm = $modinfo->get_cm($cmmixed);
136 } else if (!$cm instanceof cm_info) {
137 // Assume we got a course module record.
138 $modinfo = get_fast_modinfo($cm->course);
139 $cm = $modinfo->get_cm($cm->id);
142 if (!$cm->uservisible) {
143 if ($throwexception) {
144 throw new require_login_exception('Course module is hidden');
145 } else {
146 return false;
150 return true;
154 * Validate if current user have acces to the course if hidden.
156 * @param mixed $courseorid The course or it ID.
157 * @param bool $throwexception Throw an exception or not.
158 * @return bool
160 protected static function validate_course($courseorid, $throwexception = true) {
161 $course = $courseorid;
162 if (!is_object($course)) {
163 $course = get_course($course);
166 $coursecontext = context_course::instance($course->id);
167 if (!$course->visible and !has_capability('moodle/course:viewhiddencourses', $coursecontext)) {
168 if ($throwexception) {
169 throw new require_login_exception('Course is hidden');
170 } else {
171 return false;
175 return true;
179 * Create a competency from a record containing all the data for the class.
181 * Requires moodle/competency:competencymanage capability at the system context.
183 * @param stdClass $record Record containing all the data for an instance of the class.
184 * @return competency
186 public static function create_competency(stdClass $record) {
187 static::require_enabled();
188 $competency = new competency(0, $record);
190 // First we do a permissions check.
191 require_capability('moodle/competency:competencymanage', $competency->get_context());
193 // Reset the sortorder, use reorder instead.
194 $competency->set('sortorder', 0);
195 $competency->create();
197 \core\event\competency_created::create_from_competency($competency)->trigger();
199 // Reset the rule of the parent.
200 $parent = $competency->get_parent();
201 if ($parent) {
202 $parent->reset_rule();
203 $parent->update();
206 return $competency;
210 * Delete a competency by id.
212 * Requires moodle/competency:competencymanage capability at the system context.
214 * @param int $id The record to delete. This will delete alot of related data - you better be sure.
215 * @return boolean
217 public static function delete_competency($id) {
218 global $DB;
219 static::require_enabled();
220 $competency = new competency($id);
222 // First we do a permissions check.
223 require_capability('moodle/competency:competencymanage', $competency->get_context());
225 $events = array();
226 $competencyids = array(intval($competency->get('id')));
227 $contextid = $competency->get_context()->id;
228 $competencyids = array_merge(competency::get_descendants_ids($competency), $competencyids);
229 if (!competency::can_all_be_deleted($competencyids)) {
230 return false;
232 $transaction = $DB->start_delegated_transaction();
234 try {
236 // Reset the rule of the parent.
237 $parent = $competency->get_parent();
238 if ($parent) {
239 $parent->reset_rule();
240 $parent->update();
243 // Delete the competency separately so the after_delete event can be triggered.
244 $competency->delete();
246 // Delete the competencies.
247 competency::delete_multiple($competencyids);
249 // Delete the competencies relation.
250 related_competency::delete_multiple_relations($competencyids);
252 // Delete competency evidences.
253 user_evidence_competency::delete_by_competencyids($competencyids);
255 // Register the competencies deleted events.
256 $events = \core\event\competency_deleted::create_multiple_from_competencyids($competencyids, $contextid);
258 } catch (\Exception $e) {
259 $transaction->rollback($e);
262 $transaction->allow_commit();
263 // Trigger events.
264 foreach ($events as $event) {
265 $event->trigger();
268 return true;
272 * Reorder this competency.
274 * Requires moodle/competency:competencymanage capability at the system context.
276 * @param int $id The id of the competency to move.
277 * @return boolean
279 public static function move_down_competency($id) {
280 static::require_enabled();
281 $current = new competency($id);
283 // First we do a permissions check.
284 require_capability('moodle/competency:competencymanage', $current->get_context());
286 $max = self::count_competencies(array('parentid' => $current->get('parentid'),
287 'competencyframeworkid' => $current->get('competencyframeworkid')));
288 if ($max > 0) {
289 $max--;
292 $sortorder = $current->get('sortorder');
293 if ($sortorder >= $max) {
294 return false;
296 $sortorder = $sortorder + 1;
297 $current->set('sortorder', $sortorder);
299 $filters = array('parentid' => $current->get('parentid'),
300 'competencyframeworkid' => $current->get('competencyframeworkid'),
301 'sortorder' => $sortorder);
302 $children = self::list_competencies($filters, 'id');
303 foreach ($children as $needtoswap) {
304 $needtoswap->set('sortorder', $sortorder - 1);
305 $needtoswap->update();
308 // OK - all set.
309 $result = $current->update();
311 return $result;
315 * Reorder this competency.
317 * Requires moodle/competency:competencymanage capability at the system context.
319 * @param int $id The id of the competency to move.
320 * @return boolean
322 public static function move_up_competency($id) {
323 static::require_enabled();
324 $current = new competency($id);
326 // First we do a permissions check.
327 require_capability('moodle/competency:competencymanage', $current->get_context());
329 $sortorder = $current->get('sortorder');
330 if ($sortorder == 0) {
331 return false;
334 $sortorder = $sortorder - 1;
335 $current->set('sortorder', $sortorder);
337 $filters = array('parentid' => $current->get('parentid'),
338 'competencyframeworkid' => $current->get('competencyframeworkid'),
339 'sortorder' => $sortorder);
340 $children = self::list_competencies($filters, 'id');
341 foreach ($children as $needtoswap) {
342 $needtoswap->set('sortorder', $sortorder + 1);
343 $needtoswap->update();
346 // OK - all set.
347 $result = $current->update();
349 return $result;
353 * Move this competency so it sits in a new parent.
355 * Requires moodle/competency:competencymanage capability at the system context.
357 * @param int $id The id of the competency to move.
358 * @param int $newparentid The new parent id for the competency.
359 * @return boolean
361 public static function set_parent_competency($id, $newparentid) {
362 global $DB;
363 static::require_enabled();
364 $current = new competency($id);
366 // First we do a permissions check.
367 require_capability('moodle/competency:competencymanage', $current->get_context());
368 if ($id == $newparentid) {
369 throw new coding_exception('Can not set a competency as a parent of itself.');
370 } if ($newparentid == $current->get('parentid')) {
371 throw new coding_exception('Can not move a competency to the same location.');
374 // Some great variable assignment right here.
375 $currentparent = $current->get_parent();
376 $parent = !empty($newparentid) ? new competency($newparentid) : null;
377 $parentpath = !empty($parent) ? $parent->get('path') : '/0/';
379 // We're going to change quite a few things.
380 $transaction = $DB->start_delegated_transaction();
382 // If we are moving a node to a child of itself:
383 // - promote all the child nodes by one level.
384 // - remove the rule on self.
385 // - re-read the parent.
386 $newparents = explode('/', $parentpath);
387 if (in_array($current->get('id'), $newparents)) {
388 $children = competency::get_records(array('parentid' => $current->get('id')), 'id');
389 foreach ($children as $child) {
390 $child->set('parentid', $current->get('parentid'));
391 $child->update();
394 // Reset the rule on self as our children have changed.
395 $current->reset_rule();
397 // The destination parent is one of our descendants, we need to re-fetch its values (path, parentid).
398 $parent->read();
401 // Reset the rules of initial parent and destination.
402 if (!empty($currentparent)) {
403 $currentparent->reset_rule();
404 $currentparent->update();
406 if (!empty($parent)) {
407 $parent->reset_rule();
408 $parent->update();
411 // Do the actual move.
412 $current->set('parentid', $newparentid);
413 $result = $current->update();
415 // All right, let's commit this.
416 $transaction->allow_commit();
418 return $result;
422 * Update the details for a competency.
424 * Requires moodle/competency:competencymanage capability at the system context.
426 * @param stdClass $record The new details for the competency.
427 * Note - must contain an id that points to the competency to update.
429 * @return boolean
431 public static function update_competency($record) {
432 static::require_enabled();
433 $competency = new competency($record->id);
435 // First we do a permissions check.
436 require_capability('moodle/competency:competencymanage', $competency->get_context());
438 // Some things should not be changed in an update - they should use a more specific method.
439 $record->sortorder = $competency->get('sortorder');
440 $record->parentid = $competency->get('parentid');
441 $record->competencyframeworkid = $competency->get('competencyframeworkid');
443 $competency->from_record($record);
444 require_capability('moodle/competency:competencymanage', $competency->get_context());
446 // OK - all set.
447 $result = $competency->update();
449 // Trigger the update event.
450 \core\event\competency_updated::create_from_competency($competency)->trigger();
452 return $result;
456 * Read a the details for a single competency and return a record.
458 * Requires moodle/competency:competencyview capability at the system context.
460 * @param int $id The id of the competency to read.
461 * @param bool $includerelated Include related tags or not.
462 * @return competency
464 public static function read_competency($id, $includerelated = false) {
465 static::require_enabled();
466 $competency = new competency($id);
468 // First we do a permissions check.
469 $context = $competency->get_context();
470 if (!has_any_capability(array('moodle/competency:competencyview', 'moodle/competency:competencymanage'), $context)) {
471 throw new required_capability_exception($context, 'moodle/competency:competencyview', 'nopermissions', '');
474 // OK - all set.
475 if ($includerelated) {
476 $relatedcompetency = new related_competency();
477 if ($related = $relatedcompetency->list_relations($id)) {
478 $competency->relatedcompetencies = $related;
482 return $competency;
486 * Perform a text search based and return all results and their parents.
488 * Requires moodle/competency:competencyview capability at the framework context.
490 * @param string $textsearch A string to search for.
491 * @param int $competencyframeworkid The id of the framework to limit the search.
492 * @return array of competencies
494 public static function search_competencies($textsearch, $competencyframeworkid) {
495 static::require_enabled();
496 $framework = new competency_framework($competencyframeworkid);
498 // First we do a permissions check.
499 $context = $framework->get_context();
500 if (!has_any_capability(array('moodle/competency:competencyview', 'moodle/competency:competencymanage'), $context)) {
501 throw new required_capability_exception($context, 'moodle/competency:competencyview', 'nopermissions', '');
504 // OK - all set.
505 $competencies = competency::search($textsearch, $competencyframeworkid);
506 return $competencies;
510 * Perform a search based on the provided filters and return a paginated list of records.
512 * Requires moodle/competency:competencyview capability at some context.
514 * @param array $filters A list of filters to apply to the list.
515 * @param string $sort The column to sort on
516 * @param string $order ('ASC' or 'DESC')
517 * @param int $skip Number of records to skip (pagination)
518 * @param int $limit Max of records to return (pagination)
519 * @return array of competencies
521 public static function list_competencies($filters, $sort = '', $order = 'ASC', $skip = 0, $limit = 0) {
522 static::require_enabled();
523 if (!isset($filters['competencyframeworkid'])) {
524 $context = context_system::instance();
525 } else {
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', '');
535 // OK - all set.
536 return competency::get_records($filters, $sort, $order, $skip, $limit);
540 * Perform a search based on the provided filters and return a paginated list of records.
542 * Requires moodle/competency:competencyview capability at some context.
544 * @param array $filters A list of filters to apply to the list.
545 * @return int
547 public static function count_competencies($filters) {
548 static::require_enabled();
549 if (!isset($filters['competencyframeworkid'])) {
550 $context = context_system::instance();
551 } else {
552 $framework = new competency_framework($filters['competencyframeworkid']);
553 $context = $framework->get_context();
556 // First we do a permissions check.
557 if (!has_any_capability(array('moodle/competency:competencyview', 'moodle/competency:competencymanage'), $context)) {
558 throw new required_capability_exception($context, 'moodle/competency:competencyview', 'nopermissions', '');
561 // OK - all set.
562 return competency::count_records($filters);
566 * Create a competency framework from a record containing all the data for the class.
568 * Requires moodle/competency:competencymanage capability at the system context.
570 * @param stdClass $record Record containing all the data for an instance of the class.
571 * @return competency_framework
573 public static function create_framework(stdClass $record) {
574 static::require_enabled();
575 $framework = new competency_framework(0, $record);
576 require_capability('moodle/competency:competencymanage', $framework->get_context());
578 // Account for different formats of taxonomies.
579 if (isset($record->taxonomies)) {
580 $framework->set('taxonomies', $record->taxonomies);
583 $framework = $framework->create();
585 // Trigger a competency framework created event.
586 \core\event\competency_framework_created::create_from_framework($framework)->trigger();
588 return $framework;
592 * Duplicate a competency framework by id.
594 * Requires moodle/competency:competencymanage capability at the system context.
596 * @param int $id The record to duplicate. All competencies associated and related will be duplicated.
597 * @return competency_framework the framework duplicated
599 public static function duplicate_framework($id) {
600 global $DB;
601 static::require_enabled();
603 $framework = new competency_framework($id);
604 require_capability('moodle/competency:competencymanage', $framework->get_context());
605 // Starting transaction.
606 $transaction = $DB->start_delegated_transaction();
608 try {
609 // Get a uniq idnumber based on the origin framework.
610 $idnumber = competency_framework::get_unused_idnumber($framework->get('idnumber'));
611 $framework->set('idnumber', $idnumber);
612 // Adding the suffix copy to the shortname.
613 $framework->set('shortname', get_string('duplicateditemname', 'core_competency', $framework->get('shortname')));
614 $framework->set('id', 0);
615 $framework = $framework->create();
617 // Array that match the old competencies ids with the new one to use when copying related competencies.
618 $frameworkcompetency = competency::get_framework_tree($id);
619 $matchids = self::duplicate_competency_tree($framework->get('id'), $frameworkcompetency, 0, 0);
621 // Copy the related competencies.
622 $relcomps = related_competency::get_multiple_relations(array_keys($matchids));
624 foreach ($relcomps as $relcomp) {
625 $compid = $relcomp->get('competencyid');
626 $relcompid = $relcomp->get('relatedcompetencyid');
627 if (isset($matchids[$compid]) && isset($matchids[$relcompid])) {
628 $newcompid = $matchids[$compid]->get('id');
629 $newrelcompid = $matchids[$relcompid]->get('id');
630 if ($newcompid < $newrelcompid) {
631 $relcomp->set('competencyid', $newcompid);
632 $relcomp->set('relatedcompetencyid', $newrelcompid);
633 } else {
634 $relcomp->set('competencyid', $newrelcompid);
635 $relcomp->set('relatedcompetencyid', $newcompid);
637 $relcomp->set('id', 0);
638 $relcomp->create();
639 } else {
640 // Debugging message when there is no match found.
641 debugging('related competency id not found');
645 // Setting rules on duplicated competencies.
646 self::migrate_competency_tree_rules($frameworkcompetency, $matchids);
648 $transaction->allow_commit();
650 } catch (\Exception $e) {
651 $transaction->rollback($e);
654 // Trigger a competency framework created event.
655 \core\event\competency_framework_created::create_from_framework($framework)->trigger();
657 return $framework;
661 * Delete a competency framework by id.
663 * Requires moodle/competency:competencymanage capability at the system context.
665 * @param int $id The record to delete. This will delete alot of related data - you better be sure.
666 * @return boolean
668 public static function delete_framework($id) {
669 global $DB;
670 static::require_enabled();
671 $framework = new competency_framework($id);
672 require_capability('moodle/competency:competencymanage', $framework->get_context());
674 $events = array();
675 $competenciesid = competency::get_ids_by_frameworkid($id);
676 $contextid = $framework->get('contextid');
677 if (!competency::can_all_be_deleted($competenciesid)) {
678 return false;
680 $transaction = $DB->start_delegated_transaction();
681 try {
682 if (!empty($competenciesid)) {
683 // Delete competencies.
684 competency::delete_by_frameworkid($id);
686 // Delete the related competencies.
687 related_competency::delete_multiple_relations($competenciesid);
689 // Delete the evidences for competencies.
690 user_evidence_competency::delete_by_competencyids($competenciesid);
693 // Create a competency framework deleted event.
694 $event = \core\event\competency_framework_deleted::create_from_framework($framework);
695 $result = $framework->delete();
697 // Register the deleted events competencies.
698 $events = \core\event\competency_deleted::create_multiple_from_competencyids($competenciesid, $contextid);
700 } catch (\Exception $e) {
701 $transaction->rollback($e);
704 // Commit the transaction.
705 $transaction->allow_commit();
707 // If all operations are successfull then trigger the delete event.
708 $event->trigger();
710 // Trigger deleted event competencies.
711 foreach ($events as $event) {
712 $event->trigger();
715 return $result;
719 * Update the details for a competency framework.
721 * Requires moodle/competency:competencymanage capability at the system context.
723 * @param stdClass $record The new details for the framework. Note - must contain an id that points to the framework to update.
724 * @return boolean
726 public static function update_framework($record) {
727 static::require_enabled();
728 $framework = new competency_framework($record->id);
730 // Check the permissions before update.
731 require_capability('moodle/competency:competencymanage', $framework->get_context());
733 // Account for different formats of taxonomies.
734 $framework->from_record($record);
735 if (isset($record->taxonomies)) {
736 $framework->set('taxonomies', $record->taxonomies);
739 // Trigger a competency framework updated event.
740 \core\event\competency_framework_updated::create_from_framework($framework)->trigger();
742 return $framework->update();
746 * Read a the details for a single competency framework and return a record.
748 * Requires moodle/competency:competencyview capability at the system context.
750 * @param int $id The id of the framework to read.
751 * @return competency_framework
753 public static function read_framework($id) {
754 static::require_enabled();
755 $framework = new competency_framework($id);
756 if (!has_any_capability(array('moodle/competency:competencyview', 'moodle/competency:competencymanage'),
757 $framework->get_context())) {
758 throw new required_capability_exception($framework->get_context(), 'moodle/competency:competencyview',
759 'nopermissions', '');
761 return $framework;
765 * Logg the competency framework viewed event.
767 * @param competency_framework|int $frameworkorid The competency_framework object or competency framework id
768 * @return bool
770 public static function competency_framework_viewed($frameworkorid) {
771 static::require_enabled();
772 $framework = $frameworkorid;
773 if (!is_object($framework)) {
774 $framework = new competency_framework($framework);
776 if (!has_any_capability(array('moodle/competency:competencyview', 'moodle/competency:competencymanage'),
777 $framework->get_context())) {
778 throw new required_capability_exception($framework->get_context(), 'moodle/competency:competencyview',
779 'nopermissions', '');
781 \core\event\competency_framework_viewed::create_from_framework($framework)->trigger();
782 return true;
786 * Logg the competency viewed event.
788 * @param competency|int $competencyorid The competency object or competency id
789 * @return bool
791 public static function competency_viewed($competencyorid) {
792 static::require_enabled();
793 $competency = $competencyorid;
794 if (!is_object($competency)) {
795 $competency = new competency($competency);
798 if (!has_any_capability(array('moodle/competency:competencyview', 'moodle/competency:competencymanage'),
799 $competency->get_context())) {
800 throw new required_capability_exception($competency->get_context(), 'moodle/competency:competencyview',
801 'nopermissions', '');
804 \core\event\competency_viewed::create_from_competency($competency)->trigger();
805 return true;
809 * Perform a search based on the provided filters and return a paginated list of records.
811 * Requires moodle/competency:competencyview capability at the system context.
813 * @param string $sort The column to sort on
814 * @param string $order ('ASC' or 'DESC')
815 * @param int $skip Number of records to skip (pagination)
816 * @param int $limit Max of records to return (pagination)
817 * @param context $context The parent context of the frameworks.
818 * @param string $includes Defines what other contexts to fetch frameworks from.
819 * Accepted values are:
820 * - children: All descendants
821 * - parents: All parents, grand parents, etc...
822 * - self: Context passed only.
823 * @param bool $onlyvisible If true return only visible frameworks
824 * @param string $query A string to use to filter down the frameworks.
825 * @return array of competency_framework
827 public static function list_frameworks($sort, $order, $skip, $limit, $context, $includes = 'children',
828 $onlyvisible = false, $query = '') {
829 global $DB;
830 static::require_enabled();
832 // Get all the relevant contexts.
833 $contexts = self::get_related_contexts($context, $includes,
834 array('moodle/competency:competencyview', 'moodle/competency:competencymanage'));
836 if (empty($contexts)) {
837 throw new required_capability_exception($context, 'moodle/competency:competencyview', 'nopermissions', '');
840 // OK - all set.
841 list($insql, $inparams) = $DB->get_in_or_equal(array_keys($contexts), SQL_PARAMS_NAMED);
842 $select = "contextid $insql";
843 if ($onlyvisible) {
844 $select .= " AND visible = :visible";
845 $inparams['visible'] = 1;
848 if (!empty($query) || is_numeric($query)) {
849 $sqlnamelike = $DB->sql_like('shortname', ':namelike', false);
850 $sqlidnlike = $DB->sql_like('idnumber', ':idnlike', false);
852 $select .= " AND ($sqlnamelike OR $sqlidnlike) ";
853 $inparams['namelike'] = '%' . $DB->sql_like_escape($query) . '%';
854 $inparams['idnlike'] = '%' . $DB->sql_like_escape($query) . '%';
857 return competency_framework::get_records_select($select, $inparams, $sort . ' ' . $order, '*', $skip, $limit);
861 * Perform a search based on the provided filters and return a paginated list of records.
863 * Requires moodle/competency:competencyview capability at the system context.
865 * @param context $context The parent context of the frameworks.
866 * @param string $includes Defines what other contexts to fetch frameworks from.
867 * Accepted values are:
868 * - children: All descendants
869 * - parents: All parents, grand parents, etc...
870 * - self: Context passed only.
871 * @return int
873 public static function count_frameworks($context, $includes) {
874 global $DB;
875 static::require_enabled();
877 // Get all the relevant contexts.
878 $contexts = self::get_related_contexts($context, $includes,
879 array('moodle/competency:competencyview', 'moodle/competency:competencymanage'));
881 if (empty($contexts)) {
882 throw new required_capability_exception($context, 'moodle/competency:competencyview', 'nopermissions', '');
885 // OK - all set.
886 list($insql, $inparams) = $DB->get_in_or_equal(array_keys($contexts), SQL_PARAMS_NAMED);
887 return competency_framework::count_records_select("contextid $insql", $inparams);
891 * Fetches all the relevant contexts.
893 * Note: This currently only supports system, category and user contexts. However user contexts
894 * behave a bit differently and will fallback on the system context. This is what makes the most
895 * sense because a user context does not have descendants, and only has system as a parent.
897 * @param context $context The context to start from.
898 * @param string $includes Defines what other contexts to find.
899 * Accepted values are:
900 * - children: All descendants
901 * - parents: All parents, grand parents, etc...
902 * - self: Context passed only.
903 * @param array $hasanycapability Array of capabilities passed to {@link has_any_capability()} in each context.
904 * @return context[] An array of contexts where keys are context IDs.
906 public static function get_related_contexts($context, $includes, array $hasanycapability = null) {
907 global $DB;
908 static::require_enabled();
910 if (!in_array($includes, array('children', 'parents', 'self'))) {
911 throw new coding_exception('Invalid parameter value for \'includes\'.');
914 // If context user swap it for the context_system.
915 if ($context->contextlevel == CONTEXT_USER) {
916 $context = context_system::instance();
919 $contexts = array($context->id => $context);
921 if ($includes == 'children') {
922 $params = array('coursecatlevel' => CONTEXT_COURSECAT, 'path' => $context->path . '/%');
923 $pathlike = $DB->sql_like('path', ':path');
924 $sql = "contextlevel = :coursecatlevel AND $pathlike";
925 $rs = $DB->get_recordset_select('context', $sql, $params);
926 foreach ($rs as $record) {
927 $ctxid = $record->id;
928 context_helper::preload_from_record($record);
929 $contexts[$ctxid] = context::instance_by_id($ctxid);
931 $rs->close();
933 } else if ($includes == 'parents') {
934 $children = $context->get_parent_contexts();
935 foreach ($children as $ctx) {
936 $contexts[$ctx->id] = $ctx;
940 // Filter according to the capabilities required.
941 if (!empty($hasanycapability)) {
942 foreach ($contexts as $key => $ctx) {
943 if (!has_any_capability($hasanycapability, $ctx)) {
944 unset($contexts[$key]);
949 return $contexts;
953 * Count all the courses using a competency.
955 * @param int $competencyid The id of the competency to check.
956 * @return int
958 public static function count_courses_using_competency($competencyid) {
959 static::require_enabled();
961 // OK - all set.
962 $courses = course_competency::list_courses_min($competencyid);
963 $count = 0;
965 // Now check permissions on each course.
966 foreach ($courses as $course) {
967 if (!self::validate_course($course, false)) {
968 continue;
971 $context = context_course::instance($course->id);
972 $capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage');
973 if (!has_any_capability($capabilities, $context)) {
974 continue;
977 $count++;
980 return $count;
984 * List all the courses modules using a competency in a course.
986 * @param int $competencyid The id of the competency to check.
987 * @param int $courseid The id of the course to check.
988 * @return array[int] Array of course modules ids.
990 public static function list_course_modules_using_competency($competencyid, $courseid) {
991 static::require_enabled();
993 $result = array();
994 self::validate_course($courseid);
996 $coursecontext = context_course::instance($courseid);
998 // We will not check each module - course permissions should be enough.
999 $capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage');
1000 if (!has_any_capability($capabilities, $coursecontext)) {
1001 throw new required_capability_exception($coursecontext, 'moodle/competency:coursecompetencyview', 'nopermissions', '');
1004 $cmlist = course_module_competency::list_course_modules($competencyid, $courseid);
1005 foreach ($cmlist as $cmid) {
1006 if (self::validate_course_module($cmid, false)) {
1007 array_push($result, $cmid);
1011 return $result;
1015 * List all the competencies linked to a course module.
1017 * @param mixed $cmorid The course module, or its ID.
1018 * @return array[competency] Array of competency records.
1020 public static function list_course_module_competencies_in_course_module($cmorid) {
1021 static::require_enabled();
1022 $cm = $cmorid;
1023 if (!is_object($cmorid)) {
1024 $cm = get_coursemodule_from_id('', $cmorid, 0, true, MUST_EXIST);
1027 // Check the user have access to the course module.
1028 self::validate_course_module($cm);
1029 $context = context_module::instance($cm->id);
1031 $capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage');
1032 if (!has_any_capability($capabilities, $context)) {
1033 throw new required_capability_exception($context, 'moodle/competency:coursecompetencyview', 'nopermissions', '');
1036 $result = array();
1038 $cmclist = course_module_competency::list_course_module_competencies($cm->id);
1039 foreach ($cmclist as $id => $cmc) {
1040 array_push($result, $cmc);
1043 return $result;
1047 * List all the courses using a competency.
1049 * @param int $competencyid The id of the competency to check.
1050 * @return array[stdClass] Array of stdClass containing id and shortname.
1052 public static function list_courses_using_competency($competencyid) {
1053 static::require_enabled();
1055 // OK - all set.
1056 $courses = course_competency::list_courses($competencyid);
1057 $result = array();
1059 // Now check permissions on each course.
1060 foreach ($courses as $id => $course) {
1061 $context = context_course::instance($course->id);
1062 $capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage');
1063 if (!has_any_capability($capabilities, $context)) {
1064 unset($courses[$id]);
1065 continue;
1067 if (!self::validate_course($course, false)) {
1068 unset($courses[$id]);
1069 continue;
1071 array_push($result, $course);
1074 return $result;
1078 * Count the proficient competencies in a course for one user.
1080 * @param int $courseid The id of the course to check.
1081 * @param int $userid The id of the user to check.
1082 * @return int
1084 public static function count_proficient_competencies_in_course_for_user($courseid, $userid) {
1085 static::require_enabled();
1086 // Check the user have access to the course.
1087 self::validate_course($courseid);
1089 // First we do a permissions check.
1090 $context = context_course::instance($courseid);
1092 $capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage');
1093 if (!has_any_capability($capabilities, $context)) {
1094 throw new required_capability_exception($context, 'moodle/competency:coursecompetencyview', 'nopermissions', '');
1097 // OK - all set.
1098 return user_competency_course::count_proficient_competencies($courseid, $userid);
1102 * Count all the competencies in a course.
1104 * @param int $courseid The id of the course to check.
1105 * @return int
1107 public static function count_competencies_in_course($courseid) {
1108 static::require_enabled();
1109 // Check the user have access to the course.
1110 self::validate_course($courseid);
1112 // First we do a permissions check.
1113 $context = context_course::instance($courseid);
1115 $capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage');
1116 if (!has_any_capability($capabilities, $context)) {
1117 throw new required_capability_exception($context, 'moodle/competency:coursecompetencyview', 'nopermissions', '');
1120 // OK - all set.
1121 return course_competency::count_competencies($courseid);
1125 * List the competencies associated to a course.
1127 * @param mixed $courseorid The course, or its ID.
1128 * @return array( array(
1129 * 'competency' => \core_competency\competency,
1130 * 'coursecompetency' => \core_competency\course_competency
1131 * ))
1133 public static function list_course_competencies($courseorid) {
1134 static::require_enabled();
1135 $course = $courseorid;
1136 if (!is_object($courseorid)) {
1137 $course = get_course($courseorid);
1140 // Check the user have access to the course.
1141 self::validate_course($course);
1142 $context = context_course::instance($course->id);
1144 $capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage');
1145 if (!has_any_capability($capabilities, $context)) {
1146 throw new required_capability_exception($context, 'moodle/competency:coursecompetencyview', 'nopermissions', '');
1149 $result = array();
1151 // TODO We could improve the performance of this into one single query.
1152 $coursecompetencies = course_competency::list_course_competencies($course->id);
1153 $competencies = course_competency::list_competencies($course->id);
1155 // Build the return values.
1156 foreach ($coursecompetencies as $key => $coursecompetency) {
1157 $result[] = array(
1158 'competency' => $competencies[$coursecompetency->get('competencyid')],
1159 'coursecompetency' => $coursecompetency
1163 return $result;
1167 * Get a user competency.
1169 * @param int $userid The user ID.
1170 * @param int $competencyid The competency ID.
1171 * @return user_competency
1173 public static function get_user_competency($userid, $competencyid) {
1174 static::require_enabled();
1175 $existing = user_competency::get_multiple($userid, array($competencyid));
1176 $uc = array_pop($existing);
1178 if (!$uc) {
1179 $uc = user_competency::create_relation($userid, $competencyid);
1180 $uc->create();
1183 if (!$uc->can_read()) {
1184 throw new required_capability_exception($uc->get_context(), 'moodle/competency:usercompetencyview',
1185 'nopermissions', '');
1187 return $uc;
1191 * Get a user competency by ID.
1193 * @param int $usercompetencyid The user competency ID.
1194 * @return user_competency
1196 public static function get_user_competency_by_id($usercompetencyid) {
1197 static::require_enabled();
1198 $uc = new user_competency($usercompetencyid);
1199 if (!$uc->can_read()) {
1200 throw new required_capability_exception($uc->get_context(), 'moodle/competency:usercompetencyview',
1201 'nopermissions', '');
1203 return $uc;
1207 * Count the competencies associated to a course module.
1209 * @param mixed $cmorid The course module, or its ID.
1210 * @return int
1212 public static function count_course_module_competencies($cmorid) {
1213 static::require_enabled();
1214 $cm = $cmorid;
1215 if (!is_object($cmorid)) {
1216 $cm = get_coursemodule_from_id('', $cmorid, 0, true, MUST_EXIST);
1219 // Check the user have access to the course module.
1220 self::validate_course_module($cm);
1221 $context = context_module::instance($cm->id);
1223 $capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage');
1224 if (!has_any_capability($capabilities, $context)) {
1225 throw new required_capability_exception($context, 'moodle/competency:coursecompetencyview', 'nopermissions', '');
1228 return course_module_competency::count_competencies($cm->id);
1232 * List the competencies associated to a course module.
1234 * @param mixed $cmorid The course module, or its ID.
1235 * @return array( array(
1236 * 'competency' => \core_competency\competency,
1237 * 'coursemodulecompetency' => \core_competency\course_module_competency
1238 * ))
1240 public static function list_course_module_competencies($cmorid) {
1241 static::require_enabled();
1242 $cm = $cmorid;
1243 if (!is_object($cmorid)) {
1244 $cm = get_coursemodule_from_id('', $cmorid, 0, true, MUST_EXIST);
1247 // Check the user have access to the course module.
1248 self::validate_course_module($cm);
1249 $context = context_module::instance($cm->id);
1251 $capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage');
1252 if (!has_any_capability($capabilities, $context)) {
1253 throw new required_capability_exception($context, 'moodle/competency:coursecompetencyview', 'nopermissions', '');
1256 $result = array();
1258 // TODO We could improve the performance of this into one single query.
1259 $coursemodulecompetencies = course_module_competency::list_course_module_competencies($cm->id);
1260 $competencies = course_module_competency::list_competencies($cm->id);
1262 // Build the return values.
1263 foreach ($coursemodulecompetencies as $key => $coursemodulecompetency) {
1264 $result[] = array(
1265 'competency' => $competencies[$coursemodulecompetency->get('competencyid')],
1266 'coursemodulecompetency' => $coursemodulecompetency
1270 return $result;
1274 * Get a user competency in a course.
1276 * @param int $courseid The id of the course to check.
1277 * @param int $userid The id of the course to check.
1278 * @param int $competencyid The id of the competency.
1279 * @return user_competency_course
1281 public static function get_user_competency_in_course($courseid, $userid, $competencyid) {
1282 static::require_enabled();
1283 // First we do a permissions check.
1284 $context = context_course::instance($courseid);
1286 $capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage');
1287 if (!has_any_capability($capabilities, $context)) {
1288 throw new required_capability_exception($context, 'moodle/competency:coursecompetencyview', 'nopermissions', '');
1289 } else if (!user_competency::can_read_user_in_course($userid, $courseid)) {
1290 throw new required_capability_exception($context, 'moodle/competency:usercompetencyview', 'nopermissions', '');
1293 // This will throw an exception if the competency does not belong to the course.
1294 $competency = course_competency::get_competency($courseid, $competencyid);
1296 $params = array('courseid' => $courseid, 'userid' => $userid, 'competencyid' => $competencyid);
1297 $exists = user_competency_course::get_record($params);
1298 // Create missing.
1299 if ($exists) {
1300 $ucc = $exists;
1301 } else {
1302 $ucc = user_competency_course::create_relation($userid, $competency->get('id'), $courseid);
1303 $ucc->create();
1306 return $ucc;
1310 * List all the user competencies in a course.
1312 * @param int $courseid The id of the course to check.
1313 * @param int $userid The id of the course to check.
1314 * @return array of user_competency_course objects
1316 public static function list_user_competencies_in_course($courseid, $userid) {
1317 static::require_enabled();
1318 // First we do a permissions check.
1319 $context = context_course::instance($courseid);
1320 $onlyvisible = 1;
1322 $capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage');
1323 if (!has_any_capability($capabilities, $context)) {
1324 throw new required_capability_exception($context, 'moodle/competency:coursecompetencyview', 'nopermissions', '');
1325 } else if (!user_competency::can_read_user_in_course($userid, $courseid)) {
1326 throw new required_capability_exception($context, 'moodle/competency:usercompetencyview', 'nopermissions', '');
1329 // OK - all set.
1330 $competencylist = course_competency::list_competencies($courseid, false);
1332 $existing = user_competency_course::get_multiple($userid, $courseid, $competencylist);
1333 // Create missing.
1334 $orderedusercompetencycourses = array();
1336 $somemissing = false;
1337 foreach ($competencylist as $coursecompetency) {
1338 $found = false;
1339 foreach ($existing as $usercompetencycourse) {
1340 if ($usercompetencycourse->get('competencyid') == $coursecompetency->get('id')) {
1341 $found = true;
1342 $orderedusercompetencycourses[$usercompetencycourse->get('id')] = $usercompetencycourse;
1343 break;
1346 if (!$found) {
1347 $ucc = user_competency_course::create_relation($userid, $coursecompetency->get('id'), $courseid);
1348 $ucc->create();
1349 $orderedusercompetencycourses[$ucc->get('id')] = $ucc;
1353 return $orderedusercompetencycourses;
1357 * List the user competencies to review.
1359 * The method returns values in this format:
1361 * array(
1362 * 'competencies' => array(
1363 * (stdClass)(
1364 * 'usercompetency' => (user_competency),
1365 * 'competency' => (competency),
1366 * 'user' => (user)
1368 * ),
1369 * 'count' => (int)
1372 * @param int $skip The number of records to skip.
1373 * @param int $limit The number of results to return.
1374 * @param int $userid The user we're getting the competencies to review for.
1375 * @return array Containing the keys 'count', and 'competencies'. The 'competencies' key contains an object
1376 * which contains 'competency', 'usercompetency' and 'user'.
1378 public static function list_user_competencies_to_review($skip = 0, $limit = 50, $userid = null) {
1379 global $DB, $USER;
1380 static::require_enabled();
1381 if ($userid === null) {
1382 $userid = $USER->id;
1385 $capability = 'moodle/competency:usercompetencyreview';
1386 $ucfields = user_competency::get_sql_fields('uc', 'uc_');
1387 $compfields = competency::get_sql_fields('c', 'c_');
1388 $usercols = array('id') + get_user_fieldnames();
1389 $userfields = array();
1390 foreach ($usercols as $field) {
1391 $userfields[] = "u." . $field . " AS usr_" . $field;
1393 $userfields = implode(',', $userfields);
1395 $select = "SELECT $ucfields, $compfields, $userfields";
1396 $countselect = "SELECT COUNT('x')";
1397 $sql = " FROM {" . user_competency::TABLE . "} uc
1398 JOIN {" . competency::TABLE . "} c
1399 ON c.id = uc.competencyid
1400 JOIN {user} u
1401 ON u.id = uc.userid
1402 WHERE (uc.status = :waitingforreview
1403 OR (uc.status = :inreview AND uc.reviewerid = :reviewerid))
1404 AND u.deleted = 0";
1405 $ordersql = " ORDER BY c.shortname ASC";
1406 $params = array(
1407 'inreview' => user_competency::STATUS_IN_REVIEW,
1408 'reviewerid' => $userid,
1409 'waitingforreview' => user_competency::STATUS_WAITING_FOR_REVIEW,
1411 $countsql = $countselect . $sql;
1413 // Primary check to avoid the hard work of getting the users in which the user has permission.
1414 $count = $DB->count_records_sql($countselect . $sql, $params);
1415 if ($count < 1) {
1416 return array('count' => 0, 'competencies' => array());
1419 // TODO MDL-52243 Use core function.
1420 list($insql, $inparams) = self::filter_users_with_capability_on_user_context_sql(
1421 $capability, $userid, SQL_PARAMS_NAMED);
1422 $params += $inparams;
1423 $countsql = $countselect . $sql . " AND uc.userid $insql";
1424 $getsql = $select . $sql . " AND uc.userid $insql " . $ordersql;
1426 // Extracting the results.
1427 $competencies = array();
1428 $records = $DB->get_recordset_sql($getsql, $params, $skip, $limit);
1429 foreach ($records as $record) {
1430 $objects = (object) array(
1431 'usercompetency' => new user_competency(0, user_competency::extract_record($record, 'uc_')),
1432 'competency' => new competency(0, competency::extract_record($record, 'c_')),
1433 'user' => persistent::extract_record($record, 'usr_'),
1435 $competencies[] = $objects;
1437 $records->close();
1439 return array(
1440 'count' => $DB->count_records_sql($countsql, $params),
1441 'competencies' => $competencies
1446 * Add a competency to this course module.
1448 * @param mixed $cmorid The course module, or id of the course module
1449 * @param int $competencyid The id of the competency
1450 * @return bool
1452 public static function add_competency_to_course_module($cmorid, $competencyid) {
1453 static::require_enabled();
1454 $cm = $cmorid;
1455 if (!is_object($cmorid)) {
1456 $cm = get_coursemodule_from_id('', $cmorid, 0, true, MUST_EXIST);
1459 // Check the user have access to the course module.
1460 self::validate_course_module($cm);
1462 // First we do a permissions check.
1463 $context = context_module::instance($cm->id);
1465 require_capability('moodle/competency:coursecompetencymanage', $context);
1467 // Check that the competency belongs to the course.
1468 $exists = course_competency::get_records(array('courseid' => $cm->course, 'competencyid' => $competencyid));
1469 if (!$exists) {
1470 throw new coding_exception('Cannot add a competency to a module if it does not belong to the course');
1473 $record = new stdClass();
1474 $record->cmid = $cm->id;
1475 $record->competencyid = $competencyid;
1477 $coursemodulecompetency = new course_module_competency();
1478 $exists = $coursemodulecompetency->get_records(array('cmid' => $cm->id, 'competencyid' => $competencyid));
1479 if (!$exists) {
1480 $coursemodulecompetency->from_record($record);
1481 if ($coursemodulecompetency->create()) {
1482 return true;
1485 return false;
1489 * Remove a competency from this course module.
1491 * @param mixed $cmorid The course module, or id of the course module
1492 * @param int $competencyid The id of the competency
1493 * @return bool
1495 public static function remove_competency_from_course_module($cmorid, $competencyid) {
1496 static::require_enabled();
1497 $cm = $cmorid;
1498 if (!is_object($cmorid)) {
1499 $cm = get_coursemodule_from_id('', $cmorid, 0, true, MUST_EXIST);
1501 // Check the user have access to the course module.
1502 self::validate_course_module($cm);
1504 // First we do a permissions check.
1505 $context = context_module::instance($cm->id);
1507 require_capability('moodle/competency:coursecompetencymanage', $context);
1509 $record = new stdClass();
1510 $record->cmid = $cm->id;
1511 $record->competencyid = $competencyid;
1513 $competency = new competency($competencyid);
1514 $exists = course_module_competency::get_record(array('cmid' => $cm->id, 'competencyid' => $competencyid));
1515 if ($exists) {
1516 return $exists->delete();
1518 return false;
1522 * Move the course module competency up or down in the display list.
1524 * Requires moodle/competency:coursecompetencymanage capability at the course module context.
1526 * @param mixed $cmorid The course module, or id of the course module
1527 * @param int $competencyidfrom The id of the competency we are moving.
1528 * @param int $competencyidto The id of the competency we are moving to.
1529 * @return boolean
1531 public static function reorder_course_module_competency($cmorid, $competencyidfrom, $competencyidto) {
1532 static::require_enabled();
1533 $cm = $cmorid;
1534 if (!is_object($cmorid)) {
1535 $cm = get_coursemodule_from_id('', $cmorid, 0, true, MUST_EXIST);
1537 // Check the user have access to the course module.
1538 self::validate_course_module($cm);
1540 // First we do a permissions check.
1541 $context = context_module::instance($cm->id);
1543 require_capability('moodle/competency:coursecompetencymanage', $context);
1545 $down = true;
1546 $matches = course_module_competency::get_records(array('cmid' => $cm->id, 'competencyid' => $competencyidfrom));
1547 if (count($matches) == 0) {
1548 throw new coding_exception('The link does not exist');
1551 $competencyfrom = array_pop($matches);
1552 $matches = course_module_competency::get_records(array('cmid' => $cm->id, 'competencyid' => $competencyidto));
1553 if (count($matches) == 0) {
1554 throw new coding_exception('The link does not exist');
1557 $competencyto = array_pop($matches);
1559 $all = course_module_competency::get_records(array('cmid' => $cm->id), 'sortorder', 'ASC', 0, 0);
1561 if ($competencyfrom->get('sortorder') > $competencyto->get('sortorder')) {
1562 // We are moving up, so put it before the "to" item.
1563 $down = false;
1566 foreach ($all as $id => $coursemodulecompetency) {
1567 $sort = $coursemodulecompetency->get('sortorder');
1568 if ($down && $sort > $competencyfrom->get('sortorder') && $sort <= $competencyto->get('sortorder')) {
1569 $coursemodulecompetency->set('sortorder', $coursemodulecompetency->get('sortorder') - 1);
1570 $coursemodulecompetency->update();
1571 } else if (!$down && $sort >= $competencyto->get('sortorder') && $sort < $competencyfrom->get('sortorder')) {
1572 $coursemodulecompetency->set('sortorder', $coursemodulecompetency->get('sortorder') + 1);
1573 $coursemodulecompetency->update();
1576 $competencyfrom->set('sortorder', $competencyto->get('sortorder'));
1577 return $competencyfrom->update();
1581 * Update ruleoutcome value for a course module competency.
1583 * @param int|course_module_competency $coursemodulecompetencyorid The course_module_competency, or its ID.
1584 * @param int $ruleoutcome The value of ruleoutcome.
1585 * @param bool $overridegrade If true, will override existing grades in related competencies.
1586 * @return bool True on success.
1588 public static function set_course_module_competency_ruleoutcome($coursemodulecompetencyorid, $ruleoutcome,
1589 $overridegrade = false) {
1590 static::require_enabled();
1591 $coursemodulecompetency = $coursemodulecompetencyorid;
1592 if (!is_object($coursemodulecompetency)) {
1593 $coursemodulecompetency = new course_module_competency($coursemodulecompetencyorid);
1596 $cm = get_coursemodule_from_id('', $coursemodulecompetency->get('cmid'), 0, true, MUST_EXIST);
1598 self::validate_course_module($cm);
1599 $context = context_module::instance($cm->id);
1601 require_capability('moodle/competency:coursecompetencymanage', $context);
1603 $coursemodulecompetency->set('ruleoutcome', $ruleoutcome);
1604 $coursemodulecompetency->set('overridegrade', $overridegrade);
1606 return $coursemodulecompetency->update();
1610 * Add a competency to this course.
1612 * @param int $courseid The id of the course
1613 * @param int $competencyid The id of the competency
1614 * @return bool
1616 public static function add_competency_to_course($courseid, $competencyid) {
1617 static::require_enabled();
1618 // Check the user have access to the course.
1619 self::validate_course($courseid);
1621 // First we do a permissions check.
1622 $context = context_course::instance($courseid);
1624 require_capability('moodle/competency:coursecompetencymanage', $context);
1626 $record = new stdClass();
1627 $record->courseid = $courseid;
1628 $record->competencyid = $competencyid;
1630 $competency = new competency($competencyid);
1632 // Can not add a competency that belong to a hidden framework.
1633 if ($competency->get_framework()->get('visible') == false) {
1634 throw new coding_exception('A competency belonging to hidden framework can not be linked to course');
1637 $coursecompetency = new course_competency();
1638 $exists = $coursecompetency->get_records(array('courseid' => $courseid, 'competencyid' => $competencyid));
1639 if (!$exists) {
1640 $coursecompetency->from_record($record);
1641 if ($coursecompetency->create()) {
1642 return true;
1645 return false;
1649 * Remove a competency from this course.
1651 * @param int $courseid The id of the course
1652 * @param int $competencyid The id of the competency
1653 * @return bool
1655 public static function remove_competency_from_course($courseid, $competencyid) {
1656 static::require_enabled();
1657 // Check the user have access to the course.
1658 self::validate_course($courseid);
1660 // First we do a permissions check.
1661 $context = context_course::instance($courseid);
1663 require_capability('moodle/competency:coursecompetencymanage', $context);
1665 $record = new stdClass();
1666 $record->courseid = $courseid;
1667 $record->competencyid = $competencyid;
1669 $coursecompetency = new course_competency();
1670 $exists = course_competency::get_record(array('courseid' => $courseid, 'competencyid' => $competencyid));
1671 if ($exists) {
1672 // Delete all course_module_competencies for this competency in this course.
1673 $cmcs = course_module_competency::get_records_by_competencyid_in_course($competencyid, $courseid);
1674 foreach ($cmcs as $cmc) {
1675 $cmc->delete();
1677 return $exists->delete();
1679 return false;
1683 * Move the course competency up or down in the display list.
1685 * Requires moodle/competency:coursecompetencymanage capability at the course context.
1687 * @param int $courseid The course
1688 * @param int $competencyidfrom The id of the competency we are moving.
1689 * @param int $competencyidto The id of the competency we are moving to.
1690 * @return boolean
1692 public static function reorder_course_competency($courseid, $competencyidfrom, $competencyidto) {
1693 static::require_enabled();
1694 // Check the user have access to the course.
1695 self::validate_course($courseid);
1697 // First we do a permissions check.
1698 $context = context_course::instance($courseid);
1700 require_capability('moodle/competency:coursecompetencymanage', $context);
1702 $down = true;
1703 $coursecompetency = new course_competency();
1704 $matches = $coursecompetency->get_records(array('courseid' => $courseid, 'competencyid' => $competencyidfrom));
1705 if (count($matches) == 0) {
1706 throw new coding_exception('The link does not exist');
1709 $competencyfrom = array_pop($matches);
1710 $matches = $coursecompetency->get_records(array('courseid' => $courseid, 'competencyid' => $competencyidto));
1711 if (count($matches) == 0) {
1712 throw new coding_exception('The link does not exist');
1715 $competencyto = array_pop($matches);
1717 $all = $coursecompetency->get_records(array('courseid' => $courseid), 'sortorder', 'ASC', 0, 0);
1719 if ($competencyfrom->get('sortorder') > $competencyto->get('sortorder')) {
1720 // We are moving up, so put it before the "to" item.
1721 $down = false;
1724 foreach ($all as $id => $coursecompetency) {
1725 $sort = $coursecompetency->get('sortorder');
1726 if ($down && $sort > $competencyfrom->get('sortorder') && $sort <= $competencyto->get('sortorder')) {
1727 $coursecompetency->set('sortorder', $coursecompetency->get('sortorder') - 1);
1728 $coursecompetency->update();
1729 } else if (!$down && $sort >= $competencyto->get('sortorder') && $sort < $competencyfrom->get('sortorder')) {
1730 $coursecompetency->set('sortorder', $coursecompetency->get('sortorder') + 1);
1731 $coursecompetency->update();
1734 $competencyfrom->set('sortorder', $competencyto->get('sortorder'));
1735 return $competencyfrom->update();
1739 * Update ruleoutcome value for a course competency.
1741 * @param int|course_competency $coursecompetencyorid The course_competency, or its ID.
1742 * @param int $ruleoutcome The value of ruleoutcome.
1743 * @return bool True on success.
1745 public static function set_course_competency_ruleoutcome($coursecompetencyorid, $ruleoutcome) {
1746 static::require_enabled();
1747 $coursecompetency = $coursecompetencyorid;
1748 if (!is_object($coursecompetency)) {
1749 $coursecompetency = new course_competency($coursecompetencyorid);
1752 $courseid = $coursecompetency->get('courseid');
1753 self::validate_course($courseid);
1754 $coursecontext = context_course::instance($courseid);
1756 require_capability('moodle/competency:coursecompetencymanage', $coursecontext);
1758 $coursecompetency->set('ruleoutcome', $ruleoutcome);
1759 return $coursecompetency->update();
1763 * Create a learning plan template from a record containing all the data for the class.
1765 * Requires moodle/competency:templatemanage capability.
1767 * @param stdClass $record Record containing all the data for an instance of the class.
1768 * @return template
1770 public static function create_template(stdClass $record) {
1771 static::require_enabled();
1772 $template = new template(0, $record);
1774 // First we do a permissions check.
1775 if (!$template->can_manage()) {
1776 throw new required_capability_exception($template->get_context(), 'moodle/competency:templatemanage',
1777 'nopermissions', '');
1780 // OK - all set.
1781 $template = $template->create();
1783 // Trigger a template created event.
1784 \core\event\competency_template_created::create_from_template($template)->trigger();
1786 return $template;
1790 * Duplicate a learning plan template.
1792 * Requires moodle/competency:templatemanage capability at the template context.
1794 * @param int $id the template id.
1795 * @return template
1797 public static function duplicate_template($id) {
1798 static::require_enabled();
1799 $template = new template($id);
1801 // First we do a permissions check.
1802 if (!$template->can_manage()) {
1803 throw new required_capability_exception($template->get_context(), 'moodle/competency:templatemanage',
1804 'nopermissions', '');
1807 // OK - all set.
1808 $competencies = template_competency::list_competencies($id, false);
1810 // Adding the suffix copy.
1811 $template->set('shortname', get_string('duplicateditemname', 'core_competency', $template->get('shortname')));
1812 $template->set('id', 0);
1814 $duplicatedtemplate = $template->create();
1816 // Associate each competency for the duplicated template.
1817 foreach ($competencies as $competency) {
1818 self::add_competency_to_template($duplicatedtemplate->get('id'), $competency->get('id'));
1821 // Trigger a template created event.
1822 \core\event\competency_template_created::create_from_template($duplicatedtemplate)->trigger();
1824 return $duplicatedtemplate;
1828 * Delete a learning plan template by id.
1829 * If the learning plan template has associated cohorts they will be deleted.
1831 * Requires moodle/competency:templatemanage capability.
1833 * @param int $id The record to delete.
1834 * @param boolean $deleteplans True to delete plans associaated to template, false to unlink them.
1835 * @return boolean
1837 public static function delete_template($id, $deleteplans = true) {
1838 global $DB;
1839 static::require_enabled();
1840 $template = new template($id);
1842 // First we do a permissions check.
1843 if (!$template->can_manage()) {
1844 throw new required_capability_exception($template->get_context(), 'moodle/competency:templatemanage',
1845 'nopermissions', '');
1848 $transaction = $DB->start_delegated_transaction();
1849 $success = true;
1851 // Check if there are cohorts associated.
1852 $templatecohorts = template_cohort::get_relations_by_templateid($template->get('id'));
1853 foreach ($templatecohorts as $templatecohort) {
1854 $success = $templatecohort->delete();
1855 if (!$success) {
1856 break;
1860 // Still OK, delete or unlink the plans from the template.
1861 if ($success) {
1862 $plans = plan::get_records(array('templateid' => $template->get('id')));
1863 foreach ($plans as $plan) {
1864 $success = $deleteplans ? self::delete_plan($plan->get('id')) : self::unlink_plan_from_template($plan);
1865 if (!$success) {
1866 break;
1871 // Still OK, delete the template comptencies.
1872 if ($success) {
1873 $success = template_competency::delete_by_templateid($template->get('id'));
1876 // OK - all set.
1877 if ($success) {
1878 // Create a template deleted event.
1879 $event = \core\event\competency_template_deleted::create_from_template($template);
1881 $success = $template->delete();
1884 if ($success) {
1885 // Trigger a template deleted event.
1886 $event->trigger();
1888 // Commit the transaction.
1889 $transaction->allow_commit();
1890 } else {
1891 $transaction->rollback(new moodle_exception('Error while deleting the template.'));
1894 return $success;
1898 * Update the details for a learning plan template.
1900 * Requires moodle/competency:templatemanage capability.
1902 * @param stdClass $record The new details for the template. Note - must contain an id that points to the template to update.
1903 * @return boolean
1905 public static function update_template($record) {
1906 global $DB;
1907 static::require_enabled();
1908 $template = new template($record->id);
1910 // First we do a permissions check.
1911 if (!$template->can_manage()) {
1912 throw new required_capability_exception($template->get_context(), 'moodle/competency:templatemanage',
1913 'nopermissions', '');
1915 } else if (isset($record->contextid) && $record->contextid != $template->get('contextid')) {
1916 // We can never change the context of a template.
1917 throw new coding_exception('Changing the context of an existing tempalte is forbidden.');
1921 $updateplans = false;
1922 $before = $template->to_record();
1924 $template->from_record($record);
1925 $after = $template->to_record();
1927 // Should we update the related plans?
1928 if ($before->duedate != $after->duedate ||
1929 $before->shortname != $after->shortname ||
1930 $before->description != $after->description ||
1931 $before->descriptionformat != $after->descriptionformat) {
1932 $updateplans = true;
1935 $transaction = $DB->start_delegated_transaction();
1936 $success = $template->update();
1938 if (!$success) {
1939 $transaction->rollback(new moodle_exception('Error while updating the template.'));
1940 return $success;
1943 // Trigger a template updated event.
1944 \core\event\competency_template_updated::create_from_template($template)->trigger();
1946 if ($updateplans) {
1947 plan::update_multiple_from_template($template);
1950 $transaction->allow_commit();
1952 return $success;
1956 * Read a the details for a single learning plan template and return a record.
1958 * Requires moodle/competency:templateview capability at the system context.
1960 * @param int $id The id of the template to read.
1961 * @return template
1963 public static function read_template($id) {
1964 static::require_enabled();
1965 $template = new template($id);
1966 $context = $template->get_context();
1968 // First we do a permissions check.
1969 if (!$template->can_read()) {
1970 throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview',
1971 'nopermissions', '');
1974 // OK - all set.
1975 return $template;
1979 * Perform a search based on the provided filters and return a paginated list of records.
1981 * Requires moodle/competency:templateview capability at the system context.
1983 * @param string $sort The column to sort on
1984 * @param string $order ('ASC' or 'DESC')
1985 * @param int $skip Number of records to skip (pagination)
1986 * @param int $limit Max of records to return (pagination)
1987 * @param context $context The parent context of the frameworks.
1988 * @param string $includes Defines what other contexts to fetch frameworks from.
1989 * Accepted values are:
1990 * - children: All descendants
1991 * - parents: All parents, grand parents, etc...
1992 * - self: Context passed only.
1993 * @param bool $onlyvisible If should list only visible templates
1994 * @return array of competency_framework
1996 public static function list_templates($sort, $order, $skip, $limit, $context, $includes = 'children', $onlyvisible = false) {
1997 global $DB;
1998 static::require_enabled();
2000 // Get all the relevant contexts.
2001 $contexts = self::get_related_contexts($context, $includes,
2002 array('moodle/competency:templateview', 'moodle/competency:templatemanage'));
2004 // First we do a permissions check.
2005 if (empty($contexts)) {
2006 throw new required_capability_exception($context, 'moodle/competency:templateview', 'nopermissions', '');
2009 // Make the order by.
2010 $orderby = '';
2011 if (!empty($sort)) {
2012 $orderby = $sort . ' ' . $order;
2015 // OK - all set.
2016 $template = new template();
2017 list($insql, $params) = $DB->get_in_or_equal(array_keys($contexts), SQL_PARAMS_NAMED);
2018 $select = "contextid $insql";
2020 if ($onlyvisible) {
2021 $select .= " AND visible = :visible";
2022 $params['visible'] = 1;
2024 return $template->get_records_select($select, $params, $orderby, '*', $skip, $limit);
2028 * Perform a search based on the provided filters and return how many results there are.
2030 * Requires moodle/competency:templateview capability at the system context.
2032 * @param context $context The parent context of the frameworks.
2033 * @param string $includes Defines what other contexts to fetch frameworks from.
2034 * Accepted values are:
2035 * - children: All descendants
2036 * - parents: All parents, grand parents, etc...
2037 * - self: Context passed only.
2038 * @return int
2040 public static function count_templates($context, $includes) {
2041 global $DB;
2042 static::require_enabled();
2044 // First we do a permissions check.
2045 $contexts = self::get_related_contexts($context, $includes,
2046 array('moodle/competency:templateview', 'moodle/competency:templatemanage'));
2048 if (empty($contexts)) {
2049 throw new required_capability_exception($context, 'moodle/competency:templateview', 'nopermissions', '');
2052 // OK - all set.
2053 $template = new template();
2054 list($insql, $inparams) = $DB->get_in_or_equal(array_keys($contexts), SQL_PARAMS_NAMED);
2055 return $template->count_records_select("contextid $insql", $inparams);
2059 * Count all the templates using a competency.
2061 * @param int $competencyid The id of the competency to check.
2062 * @return int
2064 public static function count_templates_using_competency($competencyid) {
2065 static::require_enabled();
2066 // First we do a permissions check.
2067 $context = context_system::instance();
2068 $onlyvisible = 1;
2070 $capabilities = array('moodle/competency:templateview', 'moodle/competency:templatemanage');
2071 if (!has_any_capability($capabilities, $context)) {
2072 throw new required_capability_exception($context, 'moodle/competency:templateview', 'nopermissions', '');
2075 if (has_capability('moodle/competency:templatemanage', $context)) {
2076 $onlyvisible = 0;
2079 // OK - all set.
2080 return template_competency::count_templates($competencyid, $onlyvisible);
2084 * List all the learning plan templatesd using a competency.
2086 * @param int $competencyid The id of the competency to check.
2087 * @return array[stdClass] Array of stdClass containing id and shortname.
2089 public static function list_templates_using_competency($competencyid) {
2090 static::require_enabled();
2091 // First we do a permissions check.
2092 $context = context_system::instance();
2093 $onlyvisible = 1;
2095 $capabilities = array('moodle/competency:templateview', 'moodle/competency:templatemanage');
2096 if (!has_any_capability($capabilities, $context)) {
2097 throw new required_capability_exception($context, 'moodle/competency:templateview', 'nopermissions', '');
2100 if (has_capability('moodle/competency:templatemanage', $context)) {
2101 $onlyvisible = 0;
2104 // OK - all set.
2105 return template_competency::list_templates($competencyid, $onlyvisible);
2110 * Count all the competencies in a learning plan template.
2112 * @param template|int $templateorid The template or its ID.
2113 * @return int
2115 public static function count_competencies_in_template($templateorid) {
2116 static::require_enabled();
2117 // First we do a permissions check.
2118 $template = $templateorid;
2119 if (!is_object($template)) {
2120 $template = new template($template);
2123 if (!$template->can_read()) {
2124 throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview',
2125 'nopermissions', '');
2128 // OK - all set.
2129 return template_competency::count_competencies($template->get('id'));
2133 * Count all the competencies in a learning plan template with no linked courses.
2135 * @param template|int $templateorid The template or its ID.
2136 * @return int
2138 public static function count_competencies_in_template_with_no_courses($templateorid) {
2139 // First we do a permissions check.
2140 $template = $templateorid;
2141 if (!is_object($template)) {
2142 $template = new template($template);
2145 if (!$template->can_read()) {
2146 throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview',
2147 'nopermissions', '');
2150 // OK - all set.
2151 return template_competency::count_competencies_with_no_courses($template->get('id'));
2155 * List all the competencies in a template.
2157 * @param template|int $templateorid The template or its ID.
2158 * @return array of competencies
2160 public static function list_competencies_in_template($templateorid) {
2161 static::require_enabled();
2162 // First we do a permissions check.
2163 $template = $templateorid;
2164 if (!is_object($template)) {
2165 $template = new template($template);
2168 if (!$template->can_read()) {
2169 throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview',
2170 'nopermissions', '');
2173 // OK - all set.
2174 return template_competency::list_competencies($template->get('id'));
2178 * Add a competency to this template.
2180 * @param int $templateid The id of the template
2181 * @param int $competencyid The id of the competency
2182 * @return bool
2184 public static function add_competency_to_template($templateid, $competencyid) {
2185 static::require_enabled();
2186 // First we do a permissions check.
2187 $template = new template($templateid);
2188 if (!$template->can_manage()) {
2189 throw new required_capability_exception($template->get_context(), 'moodle/competency:templatemanage',
2190 'nopermissions', '');
2193 $record = new stdClass();
2194 $record->templateid = $templateid;
2195 $record->competencyid = $competencyid;
2197 $competency = new competency($competencyid);
2199 // Can not add a competency that belong to a hidden framework.
2200 if ($competency->get_framework()->get('visible') == false) {
2201 throw new coding_exception('A competency belonging to hidden framework can not be added');
2204 $exists = template_competency::get_records(array('templateid' => $templateid, 'competencyid' => $competencyid));
2205 if (!$exists) {
2206 $templatecompetency = new template_competency(0, $record);
2207 $templatecompetency->create();
2208 return true;
2210 return false;
2214 * Remove a competency from this template.
2216 * @param int $templateid The id of the template
2217 * @param int $competencyid The id of the competency
2218 * @return bool
2220 public static function remove_competency_from_template($templateid, $competencyid) {
2221 static::require_enabled();
2222 // First we do a permissions check.
2223 $template = new template($templateid);
2224 if (!$template->can_manage()) {
2225 throw new required_capability_exception($template->get_context(), 'moodle/competency:templatemanage',
2226 'nopermissions', '');
2229 $record = new stdClass();
2230 $record->templateid = $templateid;
2231 $record->competencyid = $competencyid;
2233 $competency = new competency($competencyid);
2235 $exists = template_competency::get_records(array('templateid' => $templateid, 'competencyid' => $competencyid));
2236 if ($exists) {
2237 $link = array_pop($exists);
2238 return $link->delete();
2240 return false;
2244 * Move the template competency up or down in the display list.
2246 * Requires moodle/competency:templatemanage capability at the system context.
2248 * @param int $templateid The template id
2249 * @param int $competencyidfrom The id of the competency we are moving.
2250 * @param int $competencyidto The id of the competency we are moving to.
2251 * @return boolean
2253 public static function reorder_template_competency($templateid, $competencyidfrom, $competencyidto) {
2254 static::require_enabled();
2255 $template = new template($templateid);
2257 // First we do a permissions check.
2258 if (!$template->can_manage()) {
2259 throw new required_capability_exception($template->get_context(), 'moodle/competency:templatemanage',
2260 'nopermissions', '');
2263 $down = true;
2264 $matches = template_competency::get_records(array('templateid' => $templateid, 'competencyid' => $competencyidfrom));
2265 if (count($matches) == 0) {
2266 throw new coding_exception('The link does not exist');
2269 $competencyfrom = array_pop($matches);
2270 $matches = template_competency::get_records(array('templateid' => $templateid, 'competencyid' => $competencyidto));
2271 if (count($matches) == 0) {
2272 throw new coding_exception('The link does not exist');
2275 $competencyto = array_pop($matches);
2277 $all = template_competency::get_records(array('templateid' => $templateid), 'sortorder', 'ASC', 0, 0);
2279 if ($competencyfrom->get('sortorder') > $competencyto->get('sortorder')) {
2280 // We are moving up, so put it before the "to" item.
2281 $down = false;
2284 foreach ($all as $id => $templatecompetency) {
2285 $sort = $templatecompetency->get('sortorder');
2286 if ($down && $sort > $competencyfrom->get('sortorder') && $sort <= $competencyto->get('sortorder')) {
2287 $templatecompetency->set('sortorder', $templatecompetency->get('sortorder') - 1);
2288 $templatecompetency->update();
2289 } else if (!$down && $sort >= $competencyto->get('sortorder') && $sort < $competencyfrom->get('sortorder')) {
2290 $templatecompetency->set('sortorder', $templatecompetency->get('sortorder') + 1);
2291 $templatecompetency->update();
2294 $competencyfrom->set('sortorder', $competencyto->get('sortorder'));
2295 return $competencyfrom->update();
2299 * Create a relation between a template and a cohort.
2301 * This silently ignores when the relation already existed.
2303 * @param template|int $templateorid The template or its ID.
2304 * @param stdClass|int $cohortorid The cohort ot its ID.
2305 * @return template_cohort
2307 public static function create_template_cohort($templateorid, $cohortorid) {
2308 global $DB;
2309 static::require_enabled();
2311 $template = $templateorid;
2312 if (!is_object($template)) {
2313 $template = new template($template);
2315 require_capability('moodle/competency:templatemanage', $template->get_context());
2317 $cohort = $cohortorid;
2318 if (!is_object($cohort)) {
2319 $cohort = $DB->get_record('cohort', array('id' => $cohort), '*', MUST_EXIST);
2322 // Replicate logic in cohort_can_view_cohort() because we can't use it directly as we don't have a course context.
2323 $cohortcontext = context::instance_by_id($cohort->contextid);
2324 if (!$cohort->visible && !has_capability('moodle/cohort:view', $cohortcontext)) {
2325 throw new required_capability_exception($cohortcontext, 'moodle/cohort:view', 'nopermissions', '');
2328 $tplcohort = template_cohort::get_relation($template->get('id'), $cohort->id);
2329 if (!$tplcohort->get('id')) {
2330 $tplcohort->create();
2333 return $tplcohort;
2337 * Remove a relation between a template and a cohort.
2339 * @param template|int $templateorid The template or its ID.
2340 * @param stdClass|int $cohortorid The cohort ot its ID.
2341 * @return boolean True on success or when the relation did not exist.
2343 public static function delete_template_cohort($templateorid, $cohortorid) {
2344 global $DB;
2345 static::require_enabled();
2347 $template = $templateorid;
2348 if (!is_object($template)) {
2349 $template = new template($template);
2351 require_capability('moodle/competency:templatemanage', $template->get_context());
2353 $cohort = $cohortorid;
2354 if (!is_object($cohort)) {
2355 $cohort = $DB->get_record('cohort', array('id' => $cohort), '*', MUST_EXIST);
2358 $tplcohort = template_cohort::get_relation($template->get('id'), $cohort->id);
2359 if (!$tplcohort->get('id')) {
2360 return true;
2363 return $tplcohort->delete();
2367 * Lists user plans.
2369 * @param int $userid
2370 * @return \core_competency\plan[]
2372 public static function list_user_plans($userid) {
2373 global $DB, $USER;
2374 static::require_enabled();
2375 $select = 'userid = :userid';
2376 $params = array('userid' => $userid);
2377 $context = context_user::instance($userid);
2379 // Check that we can read something here.
2380 if (!plan::can_read_user($userid) && !plan::can_read_user_draft($userid)) {
2381 throw new required_capability_exception($context, 'moodle/competency:planview', 'nopermissions', '');
2384 // The user cannot view the drafts.
2385 if (!plan::can_read_user_draft($userid)) {
2386 list($insql, $inparams) = $DB->get_in_or_equal(plan::get_draft_statuses(), SQL_PARAMS_NAMED, 'param', false);
2387 $select .= " AND status $insql";
2388 $params += $inparams;
2390 // The user cannot view the non-drafts.
2391 if (!plan::can_read_user($userid)) {
2392 list($insql, $inparams) = $DB->get_in_or_equal(array(plan::STATUS_ACTIVE, plan::STATUS_COMPLETE),
2393 SQL_PARAMS_NAMED, 'param', false);
2394 $select .= " AND status $insql";
2395 $params += $inparams;
2398 return plan::get_records_select($select, $params, 'name ASC');
2402 * List the plans to review.
2404 * The method returns values in this format:
2406 * array(
2407 * 'plans' => array(
2408 * (stdClass)(
2409 * 'plan' => (plan),
2410 * 'template' => (template),
2411 * 'owner' => (stdClass)
2413 * ),
2414 * 'count' => (int)
2417 * @param int $skip The number of records to skip.
2418 * @param int $limit The number of results to return.
2419 * @param int $userid The user we're getting the plans to review for.
2420 * @return array Containing the keys 'count', and 'plans'. The 'plans' key contains an object
2421 * which contains 'plan', 'template' and 'owner'.
2423 public static function list_plans_to_review($skip = 0, $limit = 100, $userid = null) {
2424 global $DB, $USER;
2425 static::require_enabled();
2427 if ($userid === null) {
2428 $userid = $USER->id;
2431 $planfields = plan::get_sql_fields('p', 'plan_');
2432 $tplfields = template::get_sql_fields('t', 'tpl_');
2433 $usercols = array('id') + get_user_fieldnames();
2434 $userfields = array();
2435 foreach ($usercols as $field) {
2436 $userfields[] = "u." . $field . " AS usr_" . $field;
2438 $userfields = implode(',', $userfields);
2440 $select = "SELECT $planfields, $tplfields, $userfields";
2441 $countselect = "SELECT COUNT('x')";
2443 $sql = " FROM {" . plan::TABLE . "} p
2444 JOIN {user} u
2445 ON u.id = p.userid
2446 LEFT JOIN {" . template::TABLE . "} t
2447 ON t.id = p.templateid
2448 WHERE (p.status = :waitingforreview
2449 OR (p.status = :inreview AND p.reviewerid = :reviewerid))
2450 AND p.userid != :userid";
2452 $params = array(
2453 'waitingforreview' => plan::STATUS_WAITING_FOR_REVIEW,
2454 'inreview' => plan::STATUS_IN_REVIEW,
2455 'reviewerid' => $userid,
2456 'userid' => $userid
2459 // Primary check to avoid the hard work of getting the users in which the user has permission.
2460 $count = $DB->count_records_sql($countselect . $sql, $params);
2461 if ($count < 1) {
2462 return array('count' => 0, 'plans' => array());
2465 // TODO MDL-52243 Use core function.
2466 list($insql, $inparams) = self::filter_users_with_capability_on_user_context_sql('moodle/competency:planreview',
2467 $userid, SQL_PARAMS_NAMED);
2468 $sql .= " AND p.userid $insql";
2469 $params += $inparams;
2471 // Order by ID just to have some ordering in place.
2472 $ordersql = " ORDER BY p.id ASC";
2474 $plans = array();
2475 $records = $DB->get_recordset_sql($select . $sql . $ordersql, $params, $skip, $limit);
2476 foreach ($records as $record) {
2477 $plan = new plan(0, plan::extract_record($record, 'plan_'));
2478 $template = null;
2480 if ($plan->is_based_on_template()) {
2481 $template = new template(0, template::extract_record($record, 'tpl_'));
2484 $plans[] = (object) array(
2485 'plan' => $plan,
2486 'template' => $template,
2487 'owner' => persistent::extract_record($record, 'usr_'),
2490 $records->close();
2492 return array(
2493 'count' => $DB->count_records_sql($countselect . $sql, $params),
2494 'plans' => $plans
2499 * Creates a learning plan based on the provided data.
2501 * @param stdClass $record
2502 * @return \core_competency\plan
2504 public static function create_plan(stdClass $record) {
2505 global $USER;
2506 static::require_enabled();
2507 $plan = new plan(0, $record);
2509 if ($plan->is_based_on_template()) {
2510 throw new coding_exception('To create a plan from a template use api::create_plan_from_template().');
2511 } else if ($plan->get('status') == plan::STATUS_COMPLETE) {
2512 throw new coding_exception('A plan cannot be created as complete.');
2515 if (!$plan->can_manage()) {
2516 $context = context_user::instance($plan->get('userid'));
2517 throw new required_capability_exception($context, 'moodle/competency:planmanage', 'nopermissions', '');
2520 $plan->create();
2522 // Trigger created event.
2523 \core\event\competency_plan_created::create_from_plan($plan)->trigger();
2524 return $plan;
2528 * Create a learning plan from a template.
2530 * @param mixed $templateorid The template object or ID.
2531 * @param int $userid
2532 * @return false|\core_competency\plan Returns false when the plan already exists.
2534 public static function create_plan_from_template($templateorid, $userid) {
2535 static::require_enabled();
2536 $template = $templateorid;
2537 if (!is_object($template)) {
2538 $template = new template($template);
2541 // The user must be able to view the template to use it as a base for a plan.
2542 if (!$template->can_read()) {
2543 throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview',
2544 'nopermissions', '');
2546 // Can not create plan from a hidden template.
2547 if ($template->get('visible') == false) {
2548 throw new coding_exception('A plan can not be created from a hidden template');
2551 // Convert the template to a plan.
2552 $record = $template->to_record();
2553 $record->templateid = $record->id;
2554 $record->userid = $userid;
2555 $record->name = $record->shortname;
2556 $record->status = plan::STATUS_ACTIVE;
2558 unset($record->id);
2559 unset($record->timecreated);
2560 unset($record->timemodified);
2561 unset($record->usermodified);
2563 // Remove extra keys.
2564 $properties = plan::properties_definition();
2565 foreach ($record as $key => $value) {
2566 if (!array_key_exists($key, $properties)) {
2567 unset($record->$key);
2571 $plan = new plan(0, $record);
2572 if (!$plan->can_manage()) {
2573 throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage',
2574 'nopermissions', '');
2577 // We first apply the permission checks as we wouldn't want to leak information by returning early that
2578 // the plan already exists.
2579 if (plan::record_exists_select('templateid = :templateid AND userid = :userid', array(
2580 'templateid' => $template->get('id'), 'userid' => $userid))) {
2581 return false;
2584 $plan->create();
2586 // Trigger created event.
2587 \core\event\competency_plan_created::create_from_plan($plan)->trigger();
2588 return $plan;
2592 * Create learning plans from a template and cohort.
2594 * @param mixed $templateorid The template object or ID.
2595 * @param int $cohortid The cohort ID.
2596 * @param bool $recreateunlinked When true the plans that were unlinked from this template will be re-created.
2597 * @return int The number of plans created.
2599 public static function create_plans_from_template_cohort($templateorid, $cohortid, $recreateunlinked = false) {
2600 global $DB, $CFG;
2601 static::require_enabled();
2602 require_once($CFG->dirroot . '/cohort/lib.php');
2604 $template = $templateorid;
2605 if (!is_object($template)) {
2606 $template = new template($template);
2609 // The user must be able to view the template to use it as a base for a plan.
2610 if (!$template->can_read()) {
2611 throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview',
2612 'nopermissions', '');
2615 // Can not create plan from a hidden template.
2616 if ($template->get('visible') == false) {
2617 throw new coding_exception('A plan can not be created from a hidden template');
2620 // Replicate logic in cohort_can_view_cohort() because we can't use it directly as we don't have a course context.
2621 $cohort = $DB->get_record('cohort', array('id' => $cohortid), '*', MUST_EXIST);
2622 $cohortcontext = context::instance_by_id($cohort->contextid);
2623 if (!$cohort->visible && !has_capability('moodle/cohort:view', $cohortcontext)) {
2624 throw new required_capability_exception($cohortcontext, 'moodle/cohort:view', 'nopermissions', '');
2627 // Convert the template to a plan.
2628 $recordbase = $template->to_record();
2629 $recordbase->templateid = $recordbase->id;
2630 $recordbase->name = $recordbase->shortname;
2631 $recordbase->status = plan::STATUS_ACTIVE;
2633 unset($recordbase->id);
2634 unset($recordbase->timecreated);
2635 unset($recordbase->timemodified);
2636 unset($recordbase->usermodified);
2638 // Remove extra keys.
2639 $properties = plan::properties_definition();
2640 foreach ($recordbase as $key => $value) {
2641 if (!array_key_exists($key, $properties)) {
2642 unset($recordbase->$key);
2646 // Create the plans.
2647 $created = 0;
2648 $userids = template_cohort::get_missing_plans($template->get('id'), $cohortid, $recreateunlinked);
2649 foreach ($userids as $userid) {
2650 $record = (object) (array) $recordbase;
2651 $record->userid = $userid;
2653 $plan = new plan(0, $record);
2654 if (!$plan->can_manage()) {
2655 // Silently skip members where permissions are lacking.
2656 continue;
2659 $plan->create();
2660 // Trigger created event.
2661 \core\event\competency_plan_created::create_from_plan($plan)->trigger();
2662 $created++;
2665 return $created;
2669 * Unlink a plan from its template.
2671 * @param \core_competency\plan|int $planorid The plan or its ID.
2672 * @return bool
2674 public static function unlink_plan_from_template($planorid) {
2675 global $DB;
2676 static::require_enabled();
2678 $plan = $planorid;
2679 if (!is_object($planorid)) {
2680 $plan = new plan($planorid);
2683 // The user must be allowed to manage the plans of the user, nothing about the template.
2684 if (!$plan->can_manage()) {
2685 throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', '');
2688 // Only plan with status DRAFT or ACTIVE can be unliked..
2689 if ($plan->get('status') == plan::STATUS_COMPLETE) {
2690 throw new coding_exception('Only draft or active plan can be unliked from a template');
2693 // Early exit, it's already done...
2694 if (!$plan->is_based_on_template()) {
2695 return true;
2698 // Fetch the template.
2699 $template = new template($plan->get('templateid'));
2701 // Now, proceed by copying all competencies to the plan, then update the plan.
2702 $transaction = $DB->start_delegated_transaction();
2703 $competencies = template_competency::list_competencies($template->get('id'), false);
2704 $i = 0;
2705 foreach ($competencies as $competency) {
2706 $record = (object) array(
2707 'planid' => $plan->get('id'),
2708 'competencyid' => $competency->get('id'),
2709 'sortorder' => $i++
2711 $pc = new plan_competency(null, $record);
2712 $pc->create();
2714 $plan->set('origtemplateid', $template->get('id'));
2715 $plan->set('templateid', null);
2716 $success = $plan->update();
2717 $transaction->allow_commit();
2719 // Trigger unlinked event.
2720 \core\event\competency_plan_unlinked::create_from_plan($plan)->trigger();
2722 return $success;
2726 * Updates a plan.
2728 * @param stdClass $record
2729 * @return \core_competency\plan
2731 public static function update_plan(stdClass $record) {
2732 static::require_enabled();
2734 $plan = new plan($record->id);
2736 // Validate that the plan as it is can be managed.
2737 if (!$plan->can_manage()) {
2738 throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', '');
2740 } else if ($plan->get('status') == plan::STATUS_COMPLETE) {
2741 // A completed plan cannot be edited.
2742 throw new coding_exception('Completed plan cannot be edited.');
2744 } else if ($plan->is_based_on_template()) {
2745 // Prevent a plan based on a template to be edited.
2746 throw new coding_exception('Cannot update a plan that is based on a template.');
2748 } else if (isset($record->templateid) && $plan->get('templateid') != $record->templateid) {
2749 // Prevent a plan to be based on a template.
2750 throw new coding_exception('Cannot base a plan on a template.');
2752 } else if (isset($record->userid) && $plan->get('userid') != $record->userid) {
2753 // Prevent change of ownership as the capabilities are checked against that.
2754 throw new coding_exception('A plan cannot be transfered to another user');
2756 } else if (isset($record->status) && $plan->get('status') != $record->status) {
2757 // Prevent change of status.
2758 throw new coding_exception('To change the status of a plan use the appropriate methods.');
2762 $plan->from_record($record);
2763 $plan->update();
2765 // Trigger updated event.
2766 \core\event\competency_plan_updated::create_from_plan($plan)->trigger();
2768 return $plan;
2772 * Returns a plan data.
2774 * @param int $id
2775 * @return \core_competency\plan
2777 public static function read_plan($id) {
2778 static::require_enabled();
2779 $plan = new plan($id);
2781 if (!$plan->can_read()) {
2782 $context = context_user::instance($plan->get('userid'));
2783 throw new required_capability_exception($context, 'moodle/competency:planview', 'nopermissions', '');
2786 return $plan;
2790 * Plan event viewed.
2792 * @param mixed $planorid The id or the plan.
2793 * @return boolean
2795 public static function plan_viewed($planorid) {
2796 static::require_enabled();
2797 $plan = $planorid;
2798 if (!is_object($plan)) {
2799 $plan = new plan($plan);
2802 // First we do a permissions check.
2803 if (!$plan->can_read()) {
2804 $context = context_user::instance($plan->get('userid'));
2805 throw new required_capability_exception($context, 'moodle/competency:planview', 'nopermissions', '');
2808 // Trigger a template viewed event.
2809 \core\event\competency_plan_viewed::create_from_plan($plan)->trigger();
2811 return true;
2815 * Deletes a plan.
2817 * Plans based on a template can be removed just like any other one.
2819 * @param int $id
2820 * @return bool Success?
2822 public static function delete_plan($id) {
2823 global $DB;
2824 static::require_enabled();
2826 $plan = new plan($id);
2828 if (!$plan->can_manage()) {
2829 $context = context_user::instance($plan->get('userid'));
2830 throw new required_capability_exception($context, 'moodle/competency:planmanage', 'nopermissions', '');
2833 // Wrap the suppression in a DB transaction.
2834 $transaction = $DB->start_delegated_transaction();
2836 // Delete plan competencies.
2837 $plancomps = plan_competency::get_records(array('planid' => $plan->get('id')));
2838 foreach ($plancomps as $plancomp) {
2839 $plancomp->delete();
2842 // Delete archive user competencies if the status of the plan is complete.
2843 if ($plan->get('status') == plan::STATUS_COMPLETE) {
2844 self::remove_archived_user_competencies_in_plan($plan);
2846 $event = \core\event\competency_plan_deleted::create_from_plan($plan);
2847 $success = $plan->delete();
2849 $transaction->allow_commit();
2851 // Trigger deleted event.
2852 $event->trigger();
2854 return $success;
2858 * Cancel the review of a plan.
2860 * @param int|plan $planorid The plan, or its ID.
2861 * @return bool
2863 public static function plan_cancel_review_request($planorid) {
2864 static::require_enabled();
2865 $plan = $planorid;
2866 if (!is_object($plan)) {
2867 $plan = new plan($plan);
2870 // We need to be able to view the plan at least.
2871 if (!$plan->can_read()) {
2872 throw new required_capability_exception($plan->get_context(), 'moodle/competency:planview', 'nopermissions', '');
2875 if ($plan->is_based_on_template()) {
2876 throw new coding_exception('Template plans cannot be reviewed.'); // This should never happen.
2877 } else if ($plan->get('status') != plan::STATUS_WAITING_FOR_REVIEW) {
2878 throw new coding_exception('The plan review cannot be cancelled at this stage.');
2879 } else if (!$plan->can_request_review()) {
2880 throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', '');
2883 $plan->set('status', plan::STATUS_DRAFT);
2884 $result = $plan->update();
2886 // Trigger review request cancelled event.
2887 \core\event\competency_plan_review_request_cancelled::create_from_plan($plan)->trigger();
2889 return $result;
2893 * Request the review of a plan.
2895 * @param int|plan $planorid The plan, or its ID.
2896 * @return bool
2898 public static function plan_request_review($planorid) {
2899 static::require_enabled();
2900 $plan = $planorid;
2901 if (!is_object($plan)) {
2902 $plan = new plan($plan);
2905 // We need to be able to view the plan at least.
2906 if (!$plan->can_read()) {
2907 throw new required_capability_exception($plan->get_context(), 'moodle/competency:planview', 'nopermissions', '');
2910 if ($plan->is_based_on_template()) {
2911 throw new coding_exception('Template plans cannot be reviewed.'); // This should never happen.
2912 } else if ($plan->get('status') != plan::STATUS_DRAFT) {
2913 throw new coding_exception('The plan cannot be sent for review at this stage.');
2914 } else if (!$plan->can_request_review()) {
2915 throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', '');
2918 $plan->set('status', plan::STATUS_WAITING_FOR_REVIEW);
2919 $result = $plan->update();
2921 // Trigger review requested event.
2922 \core\event\competency_plan_review_requested::create_from_plan($plan)->trigger();
2924 return $result;
2928 * Start the review of a plan.
2930 * @param int|plan $planorid The plan, or its ID.
2931 * @return bool
2933 public static function plan_start_review($planorid) {
2934 global $USER;
2935 static::require_enabled();
2936 $plan = $planorid;
2937 if (!is_object($plan)) {
2938 $plan = new plan($plan);
2941 // We need to be able to view the plan at least.
2942 if (!$plan->can_read()) {
2943 throw new required_capability_exception($plan->get_context(), 'moodle/competency:planview', 'nopermissions', '');
2946 if ($plan->is_based_on_template()) {
2947 throw new coding_exception('Template plans cannot be reviewed.'); // This should never happen.
2948 } else if ($plan->get('status') != plan::STATUS_WAITING_FOR_REVIEW) {
2949 throw new coding_exception('The plan review cannot be started at this stage.');
2950 } else if (!$plan->can_review()) {
2951 throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', '');
2954 $plan->set('status', plan::STATUS_IN_REVIEW);
2955 $plan->set('reviewerid', $USER->id);
2956 $result = $plan->update();
2958 // Trigger review started event.
2959 \core\event\competency_plan_review_started::create_from_plan($plan)->trigger();
2961 return $result;
2965 * Stop reviewing a plan.
2967 * @param int|plan $planorid The plan, or its ID.
2968 * @return bool
2970 public static function plan_stop_review($planorid) {
2971 static::require_enabled();
2972 $plan = $planorid;
2973 if (!is_object($plan)) {
2974 $plan = new plan($plan);
2977 // We need to be able to view the plan at least.
2978 if (!$plan->can_read()) {
2979 throw new required_capability_exception($plan->get_context(), 'moodle/competency:planview', 'nopermissions', '');
2982 if ($plan->is_based_on_template()) {
2983 throw new coding_exception('Template plans cannot be reviewed.'); // This should never happen.
2984 } else if ($plan->get('status') != plan::STATUS_IN_REVIEW) {
2985 throw new coding_exception('The plan review cannot be stopped at this stage.');
2986 } else if (!$plan->can_review()) {
2987 throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', '');
2990 $plan->set('status', plan::STATUS_DRAFT);
2991 $plan->set('reviewerid', null);
2992 $result = $plan->update();
2994 // Trigger review stopped event.
2995 \core\event\competency_plan_review_stopped::create_from_plan($plan)->trigger();
2997 return $result;
3001 * Approve a plan.
3003 * This means making the plan active.
3005 * @param int|plan $planorid The plan, or its ID.
3006 * @return bool
3008 public static function approve_plan($planorid) {
3009 static::require_enabled();
3010 $plan = $planorid;
3011 if (!is_object($plan)) {
3012 $plan = new plan($plan);
3015 // We need to be able to view the plan at least.
3016 if (!$plan->can_read()) {
3017 throw new required_capability_exception($plan->get_context(), 'moodle/competency:planview', 'nopermissions', '');
3020 // We can approve a plan that is either a draft, in review, or waiting for review.
3021 if ($plan->is_based_on_template()) {
3022 throw new coding_exception('Template plans are already approved.'); // This should never happen.
3023 } else if (!$plan->is_draft()) {
3024 throw new coding_exception('The plan cannot be approved at this stage.');
3025 } else if (!$plan->can_review()) {
3026 throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', '');
3029 $plan->set('status', plan::STATUS_ACTIVE);
3030 $plan->set('reviewerid', null);
3031 $result = $plan->update();
3033 // Trigger approved event.
3034 \core\event\competency_plan_approved::create_from_plan($plan)->trigger();
3036 return $result;
3040 * Unapprove a plan.
3042 * This means making the plan draft.
3044 * @param int|plan $planorid The plan, or its ID.
3045 * @return bool
3047 public static function unapprove_plan($planorid) {
3048 static::require_enabled();
3049 $plan = $planorid;
3050 if (!is_object($plan)) {
3051 $plan = new plan($plan);
3054 // We need to be able to view the plan at least.
3055 if (!$plan->can_read()) {
3056 throw new required_capability_exception($plan->get_context(), 'moodle/competency:planview', 'nopermissions', '');
3059 if ($plan->is_based_on_template()) {
3060 throw new coding_exception('Template plans are always approved.'); // This should never happen.
3061 } else if ($plan->get('status') != plan::STATUS_ACTIVE) {
3062 throw new coding_exception('The plan cannot be sent back to draft at this stage.');
3063 } else if (!$plan->can_review()) {
3064 throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', '');
3067 $plan->set('status', plan::STATUS_DRAFT);
3068 $result = $plan->update();
3070 // Trigger unapproved event.
3071 \core\event\competency_plan_unapproved::create_from_plan($plan)->trigger();
3073 return $result;
3077 * Complete a plan.
3079 * @param int|plan $planorid The plan, or its ID.
3080 * @return bool
3082 public static function complete_plan($planorid) {
3083 global $DB;
3084 static::require_enabled();
3086 $plan = $planorid;
3087 if (!is_object($planorid)) {
3088 $plan = new plan($planorid);
3091 // Validate that the plan can be managed.
3092 if (!$plan->can_manage()) {
3093 throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', '');
3096 // Check if the plan was already completed.
3097 if ($plan->get('status') == plan::STATUS_COMPLETE) {
3098 throw new coding_exception('The plan is already completed.');
3101 $originalstatus = $plan->get('status');
3102 $plan->set('status', plan::STATUS_COMPLETE);
3104 // The user should also be able to manage the plan when it's completed.
3105 if (!$plan->can_manage()) {
3106 throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', '');
3109 // Put back original status because archive needs it to extract competencies from the right table.
3110 $plan->set('status', $originalstatus);
3112 // Do the things.
3113 $transaction = $DB->start_delegated_transaction();
3114 self::archive_user_competencies_in_plan($plan);
3115 $plan->set('status', plan::STATUS_COMPLETE);
3116 $success = $plan->update();
3118 if (!$success) {
3119 $transaction->rollback(new moodle_exception('The plan could not be updated.'));
3120 return $success;
3123 $transaction->allow_commit();
3125 // Trigger updated event.
3126 \core\event\competency_plan_completed::create_from_plan($plan)->trigger();
3128 return $success;
3132 * Reopen a plan.
3134 * @param int|plan $planorid The plan, or its ID.
3135 * @return bool
3137 public static function reopen_plan($planorid) {
3138 global $DB;
3139 static::require_enabled();
3141 $plan = $planorid;
3142 if (!is_object($planorid)) {
3143 $plan = new plan($planorid);
3146 // Validate that the plan as it is can be managed.
3147 if (!$plan->can_manage()) {
3148 $context = context_user::instance($plan->get('userid'));
3149 throw new required_capability_exception($context, 'moodle/competency:planmanage', 'nopermissions', '');
3152 $beforestatus = $plan->get('status');
3153 $plan->set('status', plan::STATUS_ACTIVE);
3155 // Validate if status can be changed.
3156 if (!$plan->can_manage()) {
3157 $context = context_user::instance($plan->get('userid'));
3158 throw new required_capability_exception($context, 'moodle/competency:planmanage', 'nopermissions', '');
3161 // Wrap the updates in a DB transaction.
3162 $transaction = $DB->start_delegated_transaction();
3164 // Delete archived user competencies if the status of the plan is changed from complete to another status.
3165 $mustremovearchivedcompetencies = ($beforestatus == plan::STATUS_COMPLETE && $plan->get('status') != plan::STATUS_COMPLETE);
3166 if ($mustremovearchivedcompetencies) {
3167 self::remove_archived_user_competencies_in_plan($plan);
3170 // If duedate less than or equal to duedate_threshold unset it.
3171 if ($plan->get('duedate') <= time() + plan::DUEDATE_THRESHOLD) {
3172 $plan->set('duedate', 0);
3175 $success = $plan->update();
3177 if (!$success) {
3178 $transaction->rollback(new moodle_exception('The plan could not be updated.'));
3179 return $success;
3182 $transaction->allow_commit();
3184 // Trigger reopened event.
3185 \core\event\competency_plan_reopened::create_from_plan($plan)->trigger();
3187 return $success;
3191 * Get a single competency from the user plan.
3193 * @param int $planorid The plan, or its ID.
3194 * @param int $competencyid The competency id.
3195 * @return (object) array(
3196 * 'competency' => \core_competency\competency,
3197 * 'usercompetency' => \core_competency\user_competency
3198 * 'usercompetencyplan' => \core_competency\user_competency_plan
3200 * The values of of keys usercompetency and usercompetencyplan cannot be defined at the same time.
3202 public static function get_plan_competency($planorid, $competencyid) {
3203 static::require_enabled();
3204 $plan = $planorid;
3205 if (!is_object($planorid)) {
3206 $plan = new plan($planorid);
3209 if (!user_competency::can_read_user($plan->get('userid'))) {
3210 throw new required_capability_exception($plan->get_context(), 'moodle/competency:usercompetencyview',
3211 'nopermissions', '');
3214 $competency = $plan->get_competency($competencyid);
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'), array($competencyid));
3220 $ucresultkey = 'usercompetencyplan';
3221 } else {
3222 $usercompetencies = user_competency::get_multiple($plan->get('userid'), array($competencyid));
3223 $ucresultkey = 'usercompetency';
3226 $found = count($usercompetencies);
3228 if ($found) {
3229 $uc = array_pop($usercompetencies);
3230 } else {
3231 if ($iscompletedplan) {
3232 throw new coding_exception('A user competency plan is missing');
3233 } else {
3234 $uc = user_competency::create_relation($plan->get('userid'), $competency->get('id'));
3235 $uc->create();
3239 $plancompetency = (object) array(
3240 'competency' => $competency,
3241 'usercompetency' => null,
3242 'usercompetencyplan' => null
3244 $plancompetency->$ucresultkey = $uc;
3246 return $plancompetency;
3250 * List the plans with a competency.
3252 * @param int $userid The user id we want the plans for.
3253 * @param int $competencyorid The competency, or its ID.
3254 * @return array[plan] Array of learning plans.
3256 public static function list_plans_with_competency($userid, $competencyorid) {
3257 global $USER;
3259 static::require_enabled();
3260 $competencyid = $competencyorid;
3261 $competency = null;
3262 if (is_object($competencyid)) {
3263 $competency = $competencyid;
3264 $competencyid = $competency->get('id');
3267 $plans = plan::get_by_user_and_competency($userid, $competencyid);
3268 foreach ($plans as $index => $plan) {
3269 // Filter plans we cannot read.
3270 if (!$plan->can_read()) {
3271 unset($plans[$index]);
3274 return $plans;
3278 * List the competencies in a user plan.
3280 * @param int $planorid The plan, or its ID.
3281 * @return array((object) array(
3282 * 'competency' => \core_competency\competency,
3283 * 'usercompetency' => \core_competency\user_competency
3284 * 'usercompetencyplan' => \core_competency\user_competency_plan
3285 * ))
3286 * The values of of keys usercompetency and usercompetencyplan cannot be defined at the same time.
3288 public static function list_plan_competencies($planorid) {
3289 static::require_enabled();
3290 $plan = $planorid;
3291 if (!is_object($planorid)) {
3292 $plan = new plan($planorid);
3295 if (!$plan->can_read()) {
3296 $context = context_user::instance($plan->get('userid'));
3297 throw new required_capability_exception($context, 'moodle/competency:planview', 'nopermissions', '');
3300 $result = array();
3301 $competencies = $plan->get_competencies();
3303 // Get user competencies from user_competency_plan if the plan status is set to complete.
3304 $iscompletedplan = $plan->get('status') == plan::STATUS_COMPLETE;
3305 if ($iscompletedplan) {
3306 $usercompetencies = user_competency_plan::get_multiple($plan->get('userid'), $plan->get('id'), $competencies);
3307 $ucresultkey = 'usercompetencyplan';
3308 } else {
3309 $usercompetencies = user_competency::get_multiple($plan->get('userid'), $competencies);
3310 $ucresultkey = 'usercompetency';
3313 // Build the return values.
3314 foreach ($competencies as $key => $competency) {
3315 $found = false;
3317 foreach ($usercompetencies as $uckey => $uc) {
3318 if ($uc->get('competencyid') == $competency->get('id')) {
3319 $found = true;
3320 unset($usercompetencies[$uckey]);
3321 break;
3325 if (!$found) {
3326 if ($iscompletedplan) {
3327 throw new coding_exception('A user competency plan is missing');
3328 } else {
3329 $uc = user_competency::create_relation($plan->get('userid'), $competency->get('id'));
3333 $plancompetency = (object) array(
3334 'competency' => $competency,
3335 'usercompetency' => null,
3336 'usercompetencyplan' => null
3338 $plancompetency->$ucresultkey = $uc;
3339 $result[] = $plancompetency;
3342 return $result;
3346 * Add a competency to a plan.
3348 * @param int $planid The id of the plan
3349 * @param int $competencyid The id of the competency
3350 * @return bool
3352 public static function add_competency_to_plan($planid, $competencyid) {
3353 static::require_enabled();
3354 $plan = new plan($planid);
3356 // First we do a permissions check.
3357 if (!$plan->can_manage()) {
3358 throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', '');
3360 } else if ($plan->is_based_on_template()) {
3361 throw new coding_exception('A competency can not be added to a learning plan based on a template');
3364 if (!$plan->can_be_edited()) {
3365 throw new coding_exception('A competency can not be added to a learning plan completed');
3368 $competency = new competency($competencyid);
3370 // Can not add a competency that belong to a hidden framework.
3371 if ($competency->get_framework()->get('visible') == false) {
3372 throw new coding_exception('A competency belonging to hidden framework can not be added');
3375 $exists = plan_competency::get_record(array('planid' => $planid, 'competencyid' => $competencyid));
3376 if (!$exists) {
3377 $record = new stdClass();
3378 $record->planid = $planid;
3379 $record->competencyid = $competencyid;
3380 $plancompetency = new plan_competency(0, $record);
3381 $plancompetency->create();
3384 return true;
3388 * Remove a competency from a plan.
3390 * @param int $planid The plan id
3391 * @param int $competencyid The id of the competency
3392 * @return bool
3394 public static function remove_competency_from_plan($planid, $competencyid) {
3395 static::require_enabled();
3396 $plan = new plan($planid);
3398 // First we do a permissions check.
3399 if (!$plan->can_manage()) {
3400 $context = context_user::instance($plan->get('userid'));
3401 throw new required_capability_exception($context, 'moodle/competency:planmanage', 'nopermissions', '');
3403 } else if ($plan->is_based_on_template()) {
3404 throw new coding_exception('A competency can not be removed from a learning plan based on a template');
3407 if (!$plan->can_be_edited()) {
3408 throw new coding_exception('A competency can not be removed from a learning plan completed');
3411 $link = plan_competency::get_record(array('planid' => $planid, 'competencyid' => $competencyid));
3412 if ($link) {
3413 return $link->delete();
3415 return false;
3419 * Move the plan competency up or down in the display list.
3421 * Requires moodle/competency:planmanage capability at the system context.
3423 * @param int $planid The plan id
3424 * @param int $competencyidfrom The id of the competency we are moving.
3425 * @param int $competencyidto The id of the competency we are moving to.
3426 * @return boolean
3428 public static function reorder_plan_competency($planid, $competencyidfrom, $competencyidto) {
3429 static::require_enabled();
3430 $plan = new plan($planid);
3432 // First we do a permissions check.
3433 if (!$plan->can_manage()) {
3434 $context = context_user::instance($plan->get('userid'));
3435 throw new required_capability_exception($context, 'moodle/competency:planmanage', 'nopermissions', '');
3437 } else if ($plan->is_based_on_template()) {
3438 throw new coding_exception('A competency can not be reordered in a learning plan based on a template');
3441 if (!$plan->can_be_edited()) {
3442 throw new coding_exception('A competency can not be reordered in a learning plan completed');
3445 $down = true;
3446 $matches = plan_competency::get_records(array('planid' => $planid, 'competencyid' => $competencyidfrom));
3447 if (count($matches) == 0) {
3448 throw new coding_exception('The link does not exist');
3451 $competencyfrom = array_pop($matches);
3452 $matches = plan_competency::get_records(array('planid' => $planid, 'competencyid' => $competencyidto));
3453 if (count($matches) == 0) {
3454 throw new coding_exception('The link does not exist');
3457 $competencyto = array_pop($matches);
3459 $all = plan_competency::get_records(array('planid' => $planid), 'sortorder', 'ASC', 0, 0);
3461 if ($competencyfrom->get('sortorder') > $competencyto->get('sortorder')) {
3462 // We are moving up, so put it before the "to" item.
3463 $down = false;
3466 foreach ($all as $id => $plancompetency) {
3467 $sort = $plancompetency->get('sortorder');
3468 if ($down && $sort > $competencyfrom->get('sortorder') && $sort <= $competencyto->get('sortorder')) {
3469 $plancompetency->set('sortorder', $plancompetency->get('sortorder') - 1);
3470 $plancompetency->update();
3471 } else if (!$down && $sort >= $competencyto->get('sortorder') && $sort < $competencyfrom->get('sortorder')) {
3472 $plancompetency->set('sortorder', $plancompetency->get('sortorder') + 1);
3473 $plancompetency->update();
3476 $competencyfrom->set('sortorder', $competencyto->get('sortorder'));
3477 return $competencyfrom->update();
3481 * Cancel a user competency review request.
3483 * @param int $userid The user ID.
3484 * @param int $competencyid The competency ID.
3485 * @return bool
3487 public static function user_competency_cancel_review_request($userid, $competencyid) {
3488 static::require_enabled();
3489 $context = context_user::instance($userid);
3490 $uc = user_competency::get_record(array('userid' => $userid, 'competencyid' => $competencyid));
3491 if (!$uc || !$uc->can_read()) {
3492 throw new required_capability_exception($context, 'moodle/competency:usercompetencyview', 'nopermissions', '');
3493 } else if ($uc->get('status') != user_competency::STATUS_WAITING_FOR_REVIEW) {
3494 throw new coding_exception('The competency can not be cancel review request at this stage.');
3495 } else if (!$uc->can_request_review()) {
3496 throw new required_capability_exception($context, 'moodle/competency:usercompetencyrequestreview', 'nopermissions', '');
3499 $uc->set('status', user_competency::STATUS_IDLE);
3500 $result = $uc->update();
3501 if ($result) {
3502 \core\event\competency_user_competency_review_request_cancelled::create_from_user_competency($uc)->trigger();
3504 return $result;
3508 * Request a user competency review.
3510 * @param int $userid The user ID.
3511 * @param int $competencyid The competency ID.
3512 * @return bool
3514 public static function user_competency_request_review($userid, $competencyid) {
3515 static::require_enabled();
3516 $uc = user_competency::get_record(array('userid' => $userid, 'competencyid' => $competencyid));
3517 if (!$uc) {
3518 $uc = user_competency::create_relation($userid, $competencyid);
3519 $uc->create();
3522 if (!$uc->can_read()) {
3523 throw new required_capability_exception($uc->get_context(), 'moodle/competency:usercompetencyview',
3524 'nopermissions', '');
3525 } else if ($uc->get('status') != user_competency::STATUS_IDLE) {
3526 throw new coding_exception('The competency can not be sent for review at this stage.');
3527 } else if (!$uc->can_request_review()) {
3528 throw new required_capability_exception($uc->get_context(), 'moodle/competency:usercompetencyrequestreview',
3529 'nopermissions', '');
3532 $uc->set('status', user_competency::STATUS_WAITING_FOR_REVIEW);
3533 $result = $uc->update();
3534 if ($result) {
3535 \core\event\competency_user_competency_review_requested::create_from_user_competency($uc)->trigger();
3537 return $result;
3541 * Start a user competency review.
3543 * @param int $userid The user ID.
3544 * @param int $competencyid The competency ID.
3545 * @return bool
3547 public static function user_competency_start_review($userid, $competencyid) {
3548 global $USER;
3549 static::require_enabled();
3551 $context = context_user::instance($userid);
3552 $uc = user_competency::get_record(array('userid' => $userid, 'competencyid' => $competencyid));
3553 if (!$uc || !$uc->can_read()) {
3554 throw new required_capability_exception($context, 'moodle/competency:usercompetencyview', 'nopermissions', '');
3555 } else if ($uc->get('status') != user_competency::STATUS_WAITING_FOR_REVIEW) {
3556 throw new coding_exception('The competency review can not be started at this stage.');
3557 } else if (!$uc->can_review()) {
3558 throw new required_capability_exception($context, 'moodle/competency:usercompetencyreview', 'nopermissions', '');
3561 $uc->set('status', user_competency::STATUS_IN_REVIEW);
3562 $uc->set('reviewerid', $USER->id);
3563 $result = $uc->update();
3564 if ($result) {
3565 \core\event\competency_user_competency_review_started::create_from_user_competency($uc)->trigger();
3567 return $result;
3571 * Stop a user competency review.
3573 * @param int $userid The user ID.
3574 * @param int $competencyid The competency ID.
3575 * @return bool
3577 public static function user_competency_stop_review($userid, $competencyid) {
3578 static::require_enabled();
3579 $context = context_user::instance($userid);
3580 $uc = user_competency::get_record(array('userid' => $userid, 'competencyid' => $competencyid));
3581 if (!$uc || !$uc->can_read()) {
3582 throw new required_capability_exception($context, 'moodle/competency:usercompetencyview', 'nopermissions', '');
3583 } else if ($uc->get('status') != user_competency::STATUS_IN_REVIEW) {
3584 throw new coding_exception('The competency review can not be stopped at this stage.');
3585 } else if (!$uc->can_review()) {
3586 throw new required_capability_exception($context, 'moodle/competency:usercompetencyreview', 'nopermissions', '');
3589 $uc->set('status', user_competency::STATUS_IDLE);
3590 $result = $uc->update();
3591 if ($result) {
3592 \core\event\competency_user_competency_review_stopped::create_from_user_competency($uc)->trigger();
3594 return $result;
3598 * Log user competency viewed event.
3600 * @param user_competency|int $usercompetencyorid The user competency object or user competency id
3601 * @return bool
3603 public static function user_competency_viewed($usercompetencyorid) {
3604 static::require_enabled();
3605 $uc = $usercompetencyorid;
3606 if (!is_object($uc)) {
3607 $uc = new user_competency($uc);
3610 if (!$uc || !$uc->can_read()) {
3611 throw new required_capability_exception($uc->get_context(), 'moodle/competency:usercompetencyview',
3612 'nopermissions', '');
3615 \core\event\competency_user_competency_viewed::create_from_user_competency_viewed($uc)->trigger();
3616 return true;
3620 * Log user competency viewed in plan event.
3622 * @param user_competency|int $usercompetencyorid The user competency object or user competency id
3623 * @param int $planid The plan ID
3624 * @return bool
3626 public static function user_competency_viewed_in_plan($usercompetencyorid, $planid) {
3627 static::require_enabled();
3628 $uc = $usercompetencyorid;
3629 if (!is_object($uc)) {
3630 $uc = new user_competency($uc);
3633 if (!$uc || !$uc->can_read()) {
3634 throw new required_capability_exception($uc->get_context(), 'moodle/competency:usercompetencyview',
3635 'nopermissions', '');
3637 $plan = new plan($planid);
3638 if ($plan->get('status') == plan::STATUS_COMPLETE) {
3639 throw new coding_exception('To log the user competency in completed plan use user_competency_plan_viewed method.');
3642 \core\event\competency_user_competency_viewed_in_plan::create_from_user_competency_viewed_in_plan($uc, $planid)->trigger();
3643 return true;
3647 * Log user competency viewed in course event.
3649 * @param user_competency_course|int $usercoursecompetencyorid The user competency course object or its ID.
3650 * @param int $courseid The course ID
3651 * @return bool
3653 public static function user_competency_viewed_in_course($usercoursecompetencyorid) {
3654 static::require_enabled();
3655 $ucc = $usercoursecompetencyorid;
3656 if (!is_object($ucc)) {
3657 $ucc = new user_competency_course($ucc);
3660 if (!$ucc || !user_competency::can_read_user_in_course($ucc->get('userid'), $ucc->get('courseid'))) {
3661 throw new required_capability_exception($ucc->get_context(), 'moodle/competency:usercompetencyview',
3662 'nopermissions', '');
3665 // Validate the course, this will throw an exception if not valid.
3666 self::validate_course($ucc->get('courseid'));
3668 \core\event\competency_user_competency_viewed_in_course::create_from_user_competency_viewed_in_course($ucc)->trigger();
3669 return true;
3673 * Log user competency plan viewed event.
3675 * @param user_competency_plan|int $usercompetencyplanorid The user competency plan object or user competency plan id
3676 * @return bool
3678 public static function user_competency_plan_viewed($usercompetencyplanorid) {
3679 static::require_enabled();
3680 $ucp = $usercompetencyplanorid;
3681 if (!is_object($ucp)) {
3682 $ucp = new user_competency_plan($ucp);
3685 if (!$ucp || !user_competency::can_read_user($ucp->get('userid'))) {
3686 throw new required_capability_exception($ucp->get_context(), 'moodle/competency:usercompetencyview',
3687 'nopermissions', '');
3689 $plan = new plan($ucp->get('planid'));
3690 if ($plan->get('status') != plan::STATUS_COMPLETE) {
3691 throw new coding_exception('To log the user competency in non-completed plan use '
3692 . 'user_competency_viewed_in_plan method.');
3695 \core\event\competency_user_competency_plan_viewed::create_from_user_competency_plan($ucp)->trigger();
3696 return true;
3700 * Check if template has related data.
3702 * @param int $templateid The id of the template to check.
3703 * @return boolean
3705 public static function template_has_related_data($templateid) {
3706 static::require_enabled();
3707 // First we do a permissions check.
3708 $template = new template($templateid);
3710 if (!$template->can_read()) {
3711 throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview',
3712 'nopermissions', '');
3715 // OK - all set.
3716 return $template->has_plans();
3720 * List all the related competencies.
3722 * @param int $competencyid The id of the competency to check.
3723 * @return competency[]
3725 public static function list_related_competencies($competencyid) {
3726 static::require_enabled();
3727 $competency = new competency($competencyid);
3729 if (!has_any_capability(array('moodle/competency:competencyview', 'moodle/competency:competencymanage'),
3730 $competency->get_context())) {
3731 throw new required_capability_exception($competency->get_context(), 'moodle/competency:competencyview',
3732 'nopermissions', '');
3735 return $competency->get_related_competencies();
3739 * Add a related competency.
3741 * @param int $competencyid The id of the competency
3742 * @param int $relatedcompetencyid The id of the related competency.
3743 * @return bool False when create failed, true on success, or if the relation already existed.
3745 public static function add_related_competency($competencyid, $relatedcompetencyid) {
3746 static::require_enabled();
3747 $competency1 = new competency($competencyid);
3748 $competency2 = new competency($relatedcompetencyid);
3750 require_capability('moodle/competency:competencymanage', $competency1->get_context());
3752 $relatedcompetency = related_competency::get_relation($competency1->get('id'), $competency2->get('id'));
3753 if (!$relatedcompetency->get('id')) {
3754 $relatedcompetency->create();
3755 return true;
3758 return true;
3762 * Remove a related competency.
3764 * @param int $competencyid The id of the competency.
3765 * @param int $relatedcompetencyid The id of the related competency.
3766 * @return bool True when it was deleted, false when it wasn't or the relation doesn't exist.
3768 public static function remove_related_competency($competencyid, $relatedcompetencyid) {
3769 static::require_enabled();
3770 $competency = new competency($competencyid);
3772 // This only check if we have the permission in either competency because both competencies
3773 // should belong to the same framework.
3774 require_capability('moodle/competency:competencymanage', $competency->get_context());
3776 $relatedcompetency = related_competency::get_relation($competencyid, $relatedcompetencyid);
3777 if ($relatedcompetency->get('id')) {
3778 return $relatedcompetency->delete();
3781 return false;
3785 * Read a user evidence.
3787 * @param int $id
3788 * @return user_evidence
3790 public static function read_user_evidence($id) {
3791 static::require_enabled();
3792 $userevidence = new user_evidence($id);
3794 if (!$userevidence->can_read()) {
3795 $context = $userevidence->get_context();
3796 throw new required_capability_exception($context, 'moodle/competency:userevidenceview', 'nopermissions', '');
3799 return $userevidence;
3803 * Create a new user evidence.
3805 * @param object $data The data.
3806 * @param int $draftitemid The draft ID in which files have been saved.
3807 * @return user_evidence
3809 public static function create_user_evidence($data, $draftitemid = null) {
3810 static::require_enabled();
3811 $userevidence = new user_evidence(null, $data);
3812 $context = $userevidence->get_context();
3814 if (!$userevidence->can_manage()) {
3815 throw new required_capability_exception($context, 'moodle/competency:userevidencemanage', 'nopermissions', '');
3818 $userevidence->create();
3819 if (!empty($draftitemid)) {
3820 $fileareaoptions = array('subdirs' => true);
3821 $itemid = $userevidence->get('id');
3822 file_save_draft_area_files($draftitemid, $context->id, 'core_competency', 'userevidence', $itemid, $fileareaoptions);
3825 // Trigger an evidence of prior learning created event.
3826 \core\event\competency_user_evidence_created::create_from_user_evidence($userevidence)->trigger();
3828 return $userevidence;
3832 * Create a new user evidence.
3834 * @param object $data The data.
3835 * @param int $draftitemid The draft ID in which files have been saved.
3836 * @return user_evidence
3838 public static function update_user_evidence($data, $draftitemid = null) {
3839 static::require_enabled();
3840 $userevidence = new user_evidence($data->id);
3841 $context = $userevidence->get_context();
3843 if (!$userevidence->can_manage()) {
3844 throw new required_capability_exception($context, 'moodle/competency:userevidencemanage', 'nopermissions', '');
3846 } else if (property_exists($data, 'userid') && $data->userid != $userevidence->get('userid')) {
3847 throw new coding_exception('Can not change the userid of a user evidence.');
3850 $userevidence->from_record($data);
3851 $userevidence->update();
3853 if (!empty($draftitemid)) {
3854 $fileareaoptions = array('subdirs' => true);
3855 $itemid = $userevidence->get('id');
3856 file_save_draft_area_files($draftitemid, $context->id, 'core_competency', 'userevidence', $itemid, $fileareaoptions);
3859 // Trigger an evidence of prior learning updated event.
3860 \core\event\competency_user_evidence_updated::create_from_user_evidence($userevidence)->trigger();
3862 return $userevidence;
3866 * Delete a user evidence.
3868 * @param int $id The user evidence ID.
3869 * @return bool
3871 public static function delete_user_evidence($id) {
3872 static::require_enabled();
3873 $userevidence = new user_evidence($id);
3874 $context = $userevidence->get_context();
3876 if (!$userevidence->can_manage()) {
3877 throw new required_capability_exception($context, 'moodle/competency:userevidencemanage', 'nopermissions', '');
3880 // Delete the user evidence.
3881 $userevidence->delete();
3883 // Delete associated files.
3884 $fs = get_file_storage();
3885 $fs->delete_area_files($context->id, 'core_competency', 'userevidence', $id);
3887 // Delete relation between evidence and competencies.
3888 $userevidence->set('id', $id); // Restore the ID to fully mock the object.
3889 $competencies = user_evidence_competency::get_competencies_by_userevidenceid($id);
3890 foreach ($competencies as $competency) {
3891 static::delete_user_evidence_competency($userevidence, $competency->get('id'));
3894 // Trigger an evidence of prior learning deleted event.
3895 \core\event\competency_user_evidence_deleted::create_from_user_evidence($userevidence)->trigger();
3897 $userevidence->set('id', 0); // Restore the object.
3899 return true;
3903 * List the user evidence of a user.
3905 * @param int $userid The user ID.
3906 * @return user_evidence[]
3908 public static function list_user_evidence($userid) {
3909 static::require_enabled();
3910 if (!user_evidence::can_read_user($userid)) {
3911 $context = context_user::instance($userid);
3912 throw new required_capability_exception($context, 'moodle/competency:userevidenceview', 'nopermissions', '');
3915 $evidence = user_evidence::get_records(array('userid' => $userid), 'name');
3916 return $evidence;
3920 * Link a user evidence with a competency.
3922 * @param user_evidence|int $userevidenceorid User evidence or its ID.
3923 * @param int $competencyid Competency ID.
3924 * @return user_evidence_competency
3926 public static function create_user_evidence_competency($userevidenceorid, $competencyid) {
3927 global $USER;
3928 static::require_enabled();
3930 $userevidence = $userevidenceorid;
3931 if (!is_object($userevidence)) {
3932 $userevidence = self::read_user_evidence($userevidence);
3935 // Perform user evidence capability checks.
3936 if (!$userevidence->can_manage()) {
3937 $context = $userevidence->get_context();
3938 throw new required_capability_exception($context, 'moodle/competency:userevidencemanage', 'nopermissions', '');
3941 // Perform competency capability checks.
3942 $competency = self::read_competency($competencyid);
3944 // Get (and create) the relation.
3945 $relation = user_evidence_competency::get_relation($userevidence->get('id'), $competency->get('id'));
3946 if (!$relation->get('id')) {
3947 $relation->create();
3949 $link = url::user_evidence($userevidence->get('id'));
3950 self::add_evidence(
3951 $userevidence->get('userid'),
3952 $competency,
3953 $userevidence->get_context(),
3954 evidence::ACTION_LOG,
3955 'evidence_evidenceofpriorlearninglinked',
3956 'core_competency',
3957 $userevidence->get('name'),
3958 false,
3959 $link->out(false),
3960 null,
3961 $USER->id
3965 return $relation;
3969 * Delete a relationship between a user evidence and a competency.
3971 * @param user_evidence|int $userevidenceorid User evidence or its ID.
3972 * @param int $competencyid Competency ID.
3973 * @return bool
3975 public static function delete_user_evidence_competency($userevidenceorid, $competencyid) {
3976 global $USER;
3977 static::require_enabled();
3979 $userevidence = $userevidenceorid;
3980 if (!is_object($userevidence)) {
3981 $userevidence = self::read_user_evidence($userevidence);
3984 // Perform user evidence capability checks.
3985 if (!$userevidence->can_manage()) {
3986 $context = $userevidence->get_context();
3987 throw new required_capability_exception($context, 'moodle/competency:userevidencemanage', 'nopermissions', '');
3990 // Get (and delete) the relation.
3991 $relation = user_evidence_competency::get_relation($userevidence->get('id'), $competencyid);
3992 if (!$relation->get('id')) {
3993 return true;
3996 $success = $relation->delete();
3997 if ($success) {
3998 self::add_evidence(
3999 $userevidence->get('userid'),
4000 $competencyid,
4001 $userevidence->get_context(),
4002 evidence::ACTION_LOG,
4003 'evidence_evidenceofpriorlearningunlinked',
4004 'core_competency',
4005 $userevidence->get('name'),
4006 false,
4007 null,
4008 null,
4009 $USER->id
4013 return $success;
4017 * Send request review for user evidence competencies.
4019 * @param int $id The user evidence ID.
4020 * @return bool
4022 public static function request_review_of_user_evidence_linked_competencies($id) {
4023 $userevidence = new user_evidence($id);
4024 $context = $userevidence->get_context();
4025 $userid = $userevidence->get('userid');
4027 if (!$userevidence->can_manage()) {
4028 throw new required_capability_exception($context, 'moodle/competency:userevidencemanage', 'nopermissions', '');
4031 $usercompetencies = user_evidence_competency::get_user_competencies_by_userevidenceid($id);
4032 foreach ($usercompetencies as $usercompetency) {
4033 if ($usercompetency->get('status') == user_competency::STATUS_IDLE) {
4034 static::user_competency_request_review($userid, $usercompetency->get('competencyid'));
4038 return true;
4042 * Recursively duplicate competencies from a tree, we start duplicating from parents to children to have a correct path.
4043 * This method does not copy the related competencies.
4045 * @param int $frameworkid - framework id
4046 * @param stdClass[] $tree - list of framework competency nodes
4047 * @param int $oldparent - old parent id
4048 * @param int $newparent - new parent id
4049 * @return competency[] $matchids - List of old competencies ids matched with new competencies object.
4051 protected static function duplicate_competency_tree($frameworkid, $tree, $oldparent = 0, $newparent = 0) {
4052 $matchids = array();
4053 foreach ($tree as $node) {
4054 if ($node->competency->get('parentid') == $oldparent) {
4055 $parentid = $node->competency->get('id');
4057 // Create the competency.
4058 $competency = new competency(0, $node->competency->to_record());
4059 $competency->set('competencyframeworkid', $frameworkid);
4060 $competency->set('parentid', $newparent);
4061 $competency->set('path', '');
4062 $competency->set('id', 0);
4063 $competency->reset_rule();
4064 $competency->create();
4066 // Trigger the created event competency.
4067 \core\event\competency_created::create_from_competency($competency)->trigger();
4069 // Match the old id with the new one.
4070 $matchids[$parentid] = $competency;
4072 if (!empty($node->children)) {
4073 // Duplicate children competency.
4074 $childrenids = self::duplicate_competency_tree($frameworkid, $node->children, $parentid, $competency->get('id'));
4075 // Array_merge does not keep keys when merging so we use the + operator.
4076 $matchids = $matchids + $childrenids;
4080 return $matchids;
4084 * Recursively migrate competency rules.
4086 * @param array $tree - array of competencies object
4087 * @param competency[] $matchids - List of old competencies ids matched with new competencies object
4089 protected static function migrate_competency_tree_rules($tree, $matchids) {
4091 foreach ($tree as $node) {
4092 $oldcompid = $node->competency->get('id');
4093 if ($node->competency->get('ruletype') && array_key_exists($oldcompid, $matchids)) {
4094 try {
4095 // Get the new competency.
4096 $competency = $matchids[$oldcompid];
4097 $class = $node->competency->get('ruletype');
4098 $newruleconfig = $class::migrate_config($node->competency->get('ruleconfig'), $matchids);
4099 $competency->set('ruleconfig', $newruleconfig);
4100 $competency->set('ruletype', $class);
4101 $competency->set('ruleoutcome', $node->competency->get('ruleoutcome'));
4102 $competency->update();
4103 } catch (\Exception $e) {
4104 debugging('Could not migrate competency rule from: ' . $oldcompid . ' to: ' . $competency->get('id') . '.' .
4105 ' Exception: ' . $e->getMessage(), DEBUG_DEVELOPER);
4106 $competency->reset_rule();
4110 if (!empty($node->children)) {
4111 self::migrate_competency_tree_rules($node->children, $matchids);
4117 * Archive user competencies in a plan.
4119 * @param plan $plan The plan object.
4120 * @return void
4122 protected static function archive_user_competencies_in_plan($plan) {
4124 // Check if the plan was already completed.
4125 if ($plan->get('status') == plan::STATUS_COMPLETE) {
4126 throw new coding_exception('The plan is already completed.');
4129 $competencies = $plan->get_competencies();
4130 $usercompetencies = user_competency::get_multiple($plan->get('userid'), $competencies);
4132 $i = 0;
4133 foreach ($competencies as $competency) {
4134 $found = false;
4136 foreach ($usercompetencies as $uckey => $uc) {
4137 if ($uc->get('competencyid') == $competency->get('id')) {
4138 $found = true;
4140 $ucprecord = $uc->to_record();
4141 $ucprecord->planid = $plan->get('id');
4142 $ucprecord->sortorder = $i;
4143 unset($ucprecord->id);
4144 unset($ucprecord->status);
4145 unset($ucprecord->reviewerid);
4147 $usercompetencyplan = new user_competency_plan(0, $ucprecord);
4148 $usercompetencyplan->create();
4150 unset($usercompetencies[$uckey]);
4151 break;
4155 // If the user competency doesn't exist, we create a new relation in user_competency_plan.
4156 if (!$found) {
4157 $usercompetencyplan = user_competency_plan::create_relation($plan->get('userid'), $competency->get('id'),
4158 $plan->get('id'));
4159 $usercompetencyplan->set('sortorder', $i);
4160 $usercompetencyplan->create();
4162 $i++;
4167 * Delete archived user competencies in a plan.
4169 * @param plan $plan The plan object.
4170 * @return void
4172 protected static function remove_archived_user_competencies_in_plan($plan) {
4173 $competencies = $plan->get_competencies();
4174 $usercompetenciesplan = user_competency_plan::get_multiple($plan->get('userid'), $plan->get('id'), $competencies);
4176 foreach ($usercompetenciesplan as $ucpkey => $ucp) {
4177 $ucp->delete();
4182 * List all the evidence for a user competency.
4184 * @param int $userid The user id - only used if usercompetencyid is 0.
4185 * @param int $competencyid The competency id - only used it usercompetencyid is 0.
4186 * @param int $planid The plan id - not used yet - but can be used to only list archived evidence if a plan is completed.
4187 * @param string $sort The field to sort the evidence by.
4188 * @param string $order The ordering of the sorting.
4189 * @param int $skip Number of records to skip.
4190 * @param int $limit Number of records to return.
4191 * @return \core_competency\evidence[]
4192 * @return array of \core_competency\evidence
4194 public static function list_evidence($userid = 0, $competencyid = 0, $planid = 0, $sort = 'timecreated',
4195 $order = 'DESC', $skip = 0, $limit = 0) {
4196 static::require_enabled();
4198 if (!user_competency::can_read_user($userid)) {
4199 $context = context_user::instance($userid);
4200 throw new required_capability_exception($context, 'moodle/competency:usercompetencyview', 'nopermissions', '');
4203 $usercompetency = user_competency::get_record(array('userid' => $userid, 'competencyid' => $competencyid));
4204 if (!$usercompetency) {
4205 return array();
4208 $plancompleted = false;
4209 if ($planid != 0) {
4210 $plan = new plan($planid);
4211 if ($plan->get('status') == plan::STATUS_COMPLETE) {
4212 $plancompleted = true;
4216 $select = 'usercompetencyid = :usercompetencyid';
4217 $params = array('usercompetencyid' => $usercompetency->get('id'));
4218 if ($plancompleted) {
4219 $select .= ' AND timecreated <= :timecompleted';
4220 $params['timecompleted'] = $plan->get('timemodified');
4223 $orderby = $sort . ' ' . $order;
4224 $orderby .= !empty($orderby) ? ', id DESC' : 'id DESC'; // Prevent random ordering.
4226 $evidence = evidence::get_records_select($select, $params, $orderby, '*', $skip, $limit);
4227 return $evidence;
4231 * List all the evidence for a user competency in a course.
4233 * @param int $userid The user ID.
4234 * @param int $courseid The course ID.
4235 * @param int $competencyid The competency ID.
4236 * @param string $sort The field to sort the evidence by.
4237 * @param string $order The ordering of the sorting.
4238 * @param int $skip Number of records to skip.
4239 * @param int $limit Number of records to return.
4240 * @return \core_competency\evidence[]
4242 public static function list_evidence_in_course($userid = 0, $courseid = 0, $competencyid = 0, $sort = 'timecreated',
4243 $order = 'DESC', $skip = 0, $limit = 0) {
4244 static::require_enabled();
4246 if (!user_competency::can_read_user_in_course($userid, $courseid)) {
4247 $context = context_user::instance($userid);
4248 throw new required_capability_exception($context, 'moodle/competency:usercompetencyview', 'nopermissions', '');
4251 $usercompetency = user_competency::get_record(array('userid' => $userid, 'competencyid' => $competencyid));
4252 if (!$usercompetency) {
4253 return array();
4256 $context = context_course::instance($courseid);
4257 return evidence::get_records_for_usercompetency($usercompetency->get('id'), $context, $sort, $order, $skip, $limit);
4261 * Create an evidence from a list of parameters.
4263 * Requires no capability because evidence can be added in many situations under any user.
4265 * @param int $userid The user id for which evidence is added.
4266 * @param competency|int $competencyorid The competency, or its id for which evidence is added.
4267 * @param context|int $contextorid The context in which the evidence took place.
4268 * @param int $action The type of action to take on the competency. \core_competency\evidence::ACTION_*.
4269 * @param string $descidentifier The strings identifier.
4270 * @param string $desccomponent The strings component.
4271 * @param mixed $desca Any arguments the string requires.
4272 * @param bool $recommend When true, the user competency will be sent for review.
4273 * @param string $url The url the evidence may link to.
4274 * @param int $grade The grade, or scale ID item.
4275 * @param int $actionuserid The ID of the user who took the action of adding the evidence. Null when system.
4276 * This should be used when the action was taken by a real person, this will allow
4277 * to keep track of all the evidence given by a certain person.
4278 * @param string $note A note to attach to the evidence.
4279 * @return evidence
4280 * @throws coding_exception
4281 * @throws invalid_persistent_exception
4282 * @throws moodle_exception
4284 public static function add_evidence($userid, $competencyorid, $contextorid, $action, $descidentifier, $desccomponent,
4285 $desca = null, $recommend = false, $url = null, $grade = null, $actionuserid = null,
4286 $note = null, $overridegrade = false) {
4287 global $DB;
4288 static::require_enabled();
4290 // Some clearly important variable assignments right there.
4291 $competencyid = $competencyorid;
4292 $competency = null;
4293 if (is_object($competencyid)) {
4294 $competency = $competencyid;
4295 $competencyid = $competency->get('id');
4297 $contextid = $contextorid;
4298 $context = $contextorid;
4299 if (is_object($contextorid)) {
4300 $contextid = $contextorid->id;
4301 } else {
4302 $context = context::instance_by_id($contextorid);
4304 $setucgrade = false;
4305 $ucgrade = null;
4306 $ucproficiency = null;
4307 $usercompetencycourse = null;
4309 // Fetch or create the user competency.
4310 $usercompetency = user_competency::get_record(array('userid' => $userid, 'competencyid' => $competencyid));
4311 if (!$usercompetency) {
4312 $usercompetency = user_competency::create_relation($userid, $competencyid);
4313 $usercompetency->create();
4316 // What should we be doing?
4317 switch ($action) {
4319 // Completing a competency.
4320 case evidence::ACTION_COMPLETE:
4321 // The logic here goes like this:
4323 // if rating outside a course
4324 // - set the default grade and proficiency ONLY if there is no current grade
4325 // else we are in a course
4326 // - set the defautl grade and proficiency in the course ONLY if there is no current grade in the course
4327 // - then check the course settings to see if we should push the rating outside the course
4328 // - if we should push it
4329 // --- push it only if the user_competency (outside the course) has no grade
4330 // Done.
4332 if ($grade !== null) {
4333 throw new coding_exception("The grade MUST NOT be set with a 'completing' evidence.");
4336 // Fetch the default grade to attach to the evidence.
4337 if (empty($competency)) {
4338 $competency = new competency($competencyid);
4340 list($grade, $proficiency) = $competency->get_default_grade();
4342 // Add user_competency_course record when in a course or module.
4343 if (in_array($context->contextlevel, array(CONTEXT_COURSE, CONTEXT_MODULE))) {
4344 $coursecontext = $context->get_course_context();
4345 $courseid = $coursecontext->instanceid;
4346 $filterparams = array(
4347 'userid' => $userid,
4348 'competencyid' => $competencyid,
4349 'courseid' => $courseid
4351 // Fetch or create user competency course.
4352 $usercompetencycourse = user_competency_course::get_record($filterparams);
4353 if (!$usercompetencycourse) {
4354 $usercompetencycourse = user_competency_course::create_relation($userid, $competencyid, $courseid);
4355 $usercompetencycourse->create();
4357 // Only update the grade and proficiency if there is not already a grade or the override option is enabled.
4358 if ($usercompetencycourse->get('grade') === null || $overridegrade) {
4359 // Set grade.
4360 $usercompetencycourse->set('grade', $grade);
4361 // Set proficiency.
4362 $usercompetencycourse->set('proficiency', $proficiency);
4365 // Check the course settings to see if we should push to user plans.
4366 $coursesettings = course_competency_settings::get_by_courseid($courseid);
4367 $setucgrade = $coursesettings->get('pushratingstouserplans');
4369 if ($setucgrade) {
4370 // Only push to user plans if there is not already a grade or the override option is enabled.
4371 if ($usercompetency->get('grade') !== null && !$overridegrade) {
4372 $setucgrade = false;
4373 } else {
4374 $ucgrade = $grade;
4375 $ucproficiency = $proficiency;
4378 } else {
4380 // When completing the competency we fetch the default grade from the competency. But we only mark
4381 // the user competency when a grade has not been set yet or if override option is enabled.
4382 // Complete is an action to use with automated systems.
4383 if ($usercompetency->get('grade') === null || $overridegrade) {
4384 $setucgrade = true;
4385 $ucgrade = $grade;
4386 $ucproficiency = $proficiency;
4390 break;
4392 // We override the grade, even overriding back to not set.
4393 case evidence::ACTION_OVERRIDE:
4394 $setucgrade = true;
4395 $ucgrade = $grade;
4396 if (empty($competency)) {
4397 $competency = new competency($competencyid);
4399 if ($ucgrade !== null) {
4400 $ucproficiency = $competency->get_proficiency_of_grade($ucgrade);
4403 // Add user_competency_course record when in a course or module.
4404 if (in_array($context->contextlevel, array(CONTEXT_COURSE, CONTEXT_MODULE))) {
4405 $coursecontext = $context->get_course_context();
4406 $courseid = $coursecontext->instanceid;
4407 $filterparams = array(
4408 'userid' => $userid,
4409 'competencyid' => $competencyid,
4410 'courseid' => $courseid
4412 // Fetch or create user competency course.
4413 $usercompetencycourse = user_competency_course::get_record($filterparams);
4414 if (!$usercompetencycourse) {
4415 $usercompetencycourse = user_competency_course::create_relation($userid, $competencyid, $courseid);
4416 $usercompetencycourse->create();
4418 // Get proficiency.
4419 $proficiency = $ucproficiency;
4420 if ($proficiency === null) {
4421 if (empty($competency)) {
4422 $competency = new competency($competencyid);
4424 $proficiency = $competency->get_proficiency_of_grade($grade);
4426 // Set grade.
4427 $usercompetencycourse->set('grade', $grade);
4428 // Set proficiency.
4429 $usercompetencycourse->set('proficiency', $proficiency);
4431 $coursesettings = course_competency_settings::get_by_courseid($courseid);
4432 if (!$coursesettings->get('pushratingstouserplans')) {
4433 $setucgrade = false;
4437 break;
4439 // Simply logging an evidence.
4440 case evidence::ACTION_LOG:
4441 if ($grade !== null) {
4442 throw new coding_exception("The grade MUST NOT be set when 'logging' an evidence.");
4444 break;
4446 // Whoops, this is not expected.
4447 default:
4448 throw new coding_exception('Unexpected action parameter when registering an evidence.');
4449 break;
4452 // Should we recommend?
4453 if ($recommend && $usercompetency->get('status') == user_competency::STATUS_IDLE) {
4454 $usercompetency->set('status', user_competency::STATUS_WAITING_FOR_REVIEW);
4457 // Setting the grade and proficiency for the user competency.
4458 $wascompleted = false;
4459 if ($setucgrade == true) {
4460 if (!$usercompetency->get('proficiency') && $ucproficiency) {
4461 $wascompleted = true;
4463 $usercompetency->set('grade', $ucgrade);
4464 $usercompetency->set('proficiency', $ucproficiency);
4467 // Prepare the evidence.
4468 $record = new stdClass();
4469 $record->usercompetencyid = $usercompetency->get('id');
4470 $record->contextid = $contextid;
4471 $record->action = $action;
4472 $record->descidentifier = $descidentifier;
4473 $record->desccomponent = $desccomponent;
4474 $record->grade = $grade;
4475 $record->actionuserid = $actionuserid;
4476 $record->note = $note;
4477 $evidence = new evidence(0, $record);
4478 $evidence->set('desca', $desca);
4479 $evidence->set('url', $url);
4481 // Validate both models, we should not operate on one if the other will not save.
4482 if (!$usercompetency->is_valid()) {
4483 throw new invalid_persistent_exception($usercompetency->get_errors());
4484 } else if (!$evidence->is_valid()) {
4485 throw new invalid_persistent_exception($evidence->get_errors());
4488 // Save the user_competency_course record.
4489 if ($usercompetencycourse !== null) {
4490 // Validate and update.
4491 if (!$usercompetencycourse->is_valid()) {
4492 throw new invalid_persistent_exception($usercompetencycourse->get_errors());
4494 $usercompetencycourse->update();
4497 // Finally save. Pheww!
4498 $usercompetency->update();
4499 $evidence->create();
4501 // Trigger the evidence_created event.
4502 \core\event\competency_evidence_created::create_from_evidence($evidence, $usercompetency, $recommend)->trigger();
4504 // The competency was marked as completed, apply the rules.
4505 if ($wascompleted) {
4506 self::apply_competency_rules_from_usercompetency($usercompetency, $competency, $overridegrade);
4509 return $evidence;
4513 * Read an evidence.
4514 * @param int $evidenceid The evidence ID.
4515 * @return evidence
4517 public static function read_evidence($evidenceid) {
4518 static::require_enabled();
4520 $evidence = new evidence($evidenceid);
4521 $uc = new user_competency($evidence->get('usercompetencyid'));
4522 if (!$uc->can_read()) {
4523 throw new required_capability_exception($uc->get_context(), 'moodle/competency:usercompetencyview',
4524 'nopermissions', '');
4527 return $evidence;
4531 * Delete an evidence.
4533 * @param evidence|int $evidenceorid The evidence, or its ID.
4534 * @return bool
4536 public static function delete_evidence($evidenceorid) {
4537 $evidence = $evidenceorid;
4538 if (!is_object($evidence)) {
4539 $evidence = new evidence($evidenceorid);
4542 $uc = new user_competency($evidence->get('usercompetencyid'));
4543 if (!evidence::can_delete_user($uc->get('userid'))) {
4544 throw new required_capability_exception($uc->get_context(), 'moodle/competency:evidencedelete', 'nopermissions', '');
4547 return $evidence->delete();
4551 * Apply the competency rules from a user competency.
4553 * The user competency passed should be one that was recently marked as complete.
4554 * A user competency is considered 'complete' when it's proficiency value is true.
4556 * This method will check if the parent of this usercompetency's competency has any
4557 * rules and if so will see if they match. When matched it will take the required
4558 * step to add evidence and trigger completion, etc...
4560 * @param user_competency $usercompetency The user competency recently completed.
4561 * @param competency|null $competency The competency of the user competency, useful to avoid unnecessary read.
4562 * @return void
4564 protected static function apply_competency_rules_from_usercompetency(user_competency $usercompetency,
4565 competency $competency = null, $overridegrade = false) {
4567 // Perform some basic checks.
4568 if (!$usercompetency->get('proficiency')) {
4569 throw new coding_exception('The user competency passed is not completed.');
4571 if ($competency === null) {
4572 $competency = $usercompetency->get_competency();
4574 if ($competency->get('id') != $usercompetency->get('competencyid')) {
4575 throw new coding_exception('Mismatch between user competency and competency.');
4578 // Fetch the parent.
4579 $parent = $competency->get_parent();
4580 if ($parent === null) {
4581 return;
4584 // The parent should have a rule, and a meaningful outcome.
4585 $ruleoutcome = $parent->get('ruleoutcome');
4586 if ($ruleoutcome == competency::OUTCOME_NONE) {
4587 return;
4589 $rule = $parent->get_rule_object();
4590 if ($rule === null) {
4591 return;
4594 // Fetch or create the user competency for the parent.
4595 $userid = $usercompetency->get('userid');
4596 $parentuc = user_competency::get_record(array('userid' => $userid, 'competencyid' => $parent->get('id')));
4597 if (!$parentuc) {
4598 $parentuc = user_competency::create_relation($userid, $parent->get('id'));
4599 $parentuc->create();
4602 // Does the rule match?
4603 if (!$rule->matches($parentuc)) {
4604 return;
4607 // Figuring out what to do.
4608 $recommend = false;
4609 if ($ruleoutcome == competency::OUTCOME_EVIDENCE) {
4610 $action = evidence::ACTION_LOG;
4612 } else if ($ruleoutcome == competency::OUTCOME_RECOMMEND) {
4613 $action = evidence::ACTION_LOG;
4614 $recommend = true;
4616 } else if ($ruleoutcome == competency::OUTCOME_COMPLETE) {
4617 $action = evidence::ACTION_COMPLETE;
4619 } else {
4620 throw new moodle_exception('Unexpected rule outcome: ' + $ruleoutcome);
4623 // Finally add an evidence.
4624 static::add_evidence(
4625 $userid,
4626 $parent,
4627 $parent->get_context()->id,
4628 $action,
4629 'evidence_competencyrule',
4630 'core_competency',
4631 null,
4632 $recommend,
4633 null,
4634 null,
4635 null,
4636 null,
4637 $overridegrade
4642 * Observe when a course module is marked as completed.
4644 * Note that the user being logged in while this happens may be anyone.
4645 * Do not rely on capability checks here!
4647 * @param \core\event\course_module_completion_updated $event
4648 * @return void
4650 public static function observe_course_module_completion_updated(\core\event\course_module_completion_updated $event) {
4651 if (!static::is_enabled()) {
4652 return;
4655 $eventdata = $event->get_record_snapshot('course_modules_completion', $event->objectid);
4657 if ($eventdata->completionstate == COMPLETION_COMPLETE
4658 || $eventdata->completionstate == COMPLETION_COMPLETE_PASS) {
4659 $coursemodulecompetencies = course_module_competency::list_course_module_competencies($eventdata->coursemoduleid);
4661 $cm = get_coursemodule_from_id(null, $eventdata->coursemoduleid);
4662 $fastmodinfo = get_fast_modinfo($cm->course)->cms[$cm->id];
4664 $cmname = $fastmodinfo->name;
4665 $url = $fastmodinfo->url;
4667 foreach ($coursemodulecompetencies as $coursemodulecompetency) {
4668 $outcome = $coursemodulecompetency->get('ruleoutcome');
4669 $action = null;
4670 $recommend = false;
4671 $strdesc = 'evidence_coursemodulecompleted';
4672 $overridegrade = $coursemodulecompetency->get('overridegrade');
4674 if ($outcome == course_module_competency::OUTCOME_NONE) {
4675 continue;
4677 if ($outcome == course_module_competency::OUTCOME_EVIDENCE) {
4678 $action = evidence::ACTION_LOG;
4680 } else if ($outcome == course_module_competency::OUTCOME_RECOMMEND) {
4681 $action = evidence::ACTION_LOG;
4682 $recommend = true;
4684 } else if ($outcome == course_module_competency::OUTCOME_COMPLETE) {
4685 $action = evidence::ACTION_COMPLETE;
4687 } else {
4688 throw new moodle_exception('Unexpected rule outcome: ' + $outcome);
4691 static::add_evidence(
4692 $event->relateduserid,
4693 $coursemodulecompetency->get('competencyid'),
4694 $event->contextid,
4695 $action,
4696 $strdesc,
4697 'core_competency',
4698 $cmname,
4699 $recommend,
4700 $url,
4701 null,
4702 null,
4703 null,
4704 $overridegrade
4711 * Observe when a course is marked as completed.
4713 * Note that the user being logged in while this happens may be anyone.
4714 * Do not rely on capability checks here!
4716 * @param \core\event\course_completed $event
4717 * @return void
4719 public static function observe_course_completed(\core\event\course_completed $event) {
4720 if (!static::is_enabled()) {
4721 return;
4724 $sql = 'courseid = :courseid AND ruleoutcome != :nooutcome';
4725 $params = array(
4726 'courseid' => $event->courseid,
4727 'nooutcome' => course_competency::OUTCOME_NONE
4729 $coursecompetencies = course_competency::get_records_select($sql, $params);
4731 $course = get_course($event->courseid);
4732 $courseshortname = format_string($course->shortname, null, array('context' => $event->contextid));
4734 foreach ($coursecompetencies as $coursecompetency) {
4736 $outcome = $coursecompetency->get('ruleoutcome');
4737 $action = null;
4738 $recommend = false;
4739 $strdesc = 'evidence_coursecompleted';
4741 if ($outcome == course_module_competency::OUTCOME_NONE) {
4742 continue;
4744 if ($outcome == course_competency::OUTCOME_EVIDENCE) {
4745 $action = evidence::ACTION_LOG;
4747 } else if ($outcome == course_competency::OUTCOME_RECOMMEND) {
4748 $action = evidence::ACTION_LOG;
4749 $recommend = true;
4751 } else if ($outcome == course_competency::OUTCOME_COMPLETE) {
4752 $action = evidence::ACTION_COMPLETE;
4754 } else {
4755 throw new moodle_exception('Unexpected rule outcome: ' + $outcome);
4758 static::add_evidence(
4759 $event->relateduserid,
4760 $coursecompetency->get('competencyid'),
4761 $event->contextid,
4762 $action,
4763 $strdesc,
4764 'core_competency',
4765 $courseshortname,
4766 $recommend,
4767 $event->get_url()
4773 * Action to perform when a course module is deleted.
4775 * Do not call this directly, this is reserved for core use.
4777 * @param stdClass $cm The CM object.
4778 * @return void
4780 public static function hook_course_module_deleted(stdClass $cm) {
4781 global $DB;
4782 $DB->delete_records(course_module_competency::TABLE, array('cmid' => $cm->id));
4786 * Action to perform when a course is deleted.
4788 * Do not call this directly, this is reserved for core use.
4790 * @param stdClass $course The course object.
4791 * @return void
4793 public static function hook_course_deleted(stdClass $course) {
4794 global $DB;
4795 $DB->delete_records(course_competency::TABLE, array('courseid' => $course->id));
4796 $DB->delete_records(course_competency_settings::TABLE, array('courseid' => $course->id));
4797 $DB->delete_records(user_competency_course::TABLE, array('courseid' => $course->id));
4801 * Action to perform when a course is being reset.
4803 * Do not call this directly, this is reserved for core use.
4805 * @param int $courseid The course ID.
4806 * @return void
4808 public static function hook_course_reset_competency_ratings($courseid) {
4809 global $DB;
4810 $DB->delete_records(user_competency_course::TABLE, array('courseid' => $courseid));
4814 * Action to perform when a cohort is deleted.
4816 * Do not call this directly, this is reserved for core use.
4818 * @param \stdClass $cohort The cohort object.
4819 * @return void
4821 public static function hook_cohort_deleted(\stdClass $cohort) {
4822 global $DB;
4823 $DB->delete_records(template_cohort::TABLE, array('cohortid' => $cohort->id));
4827 * Action to perform when a user is deleted.
4829 * @param int $userid The user id.
4831 public static function hook_user_deleted($userid) {
4832 global $DB;
4834 $usercompetencies = $DB->get_records(user_competency::TABLE, ['userid' => $userid], '', 'id');
4835 foreach ($usercompetencies as $usercomp) {
4836 $DB->delete_records(evidence::TABLE, ['usercompetencyid' => $usercomp->id]);
4839 $DB->delete_records(user_competency::TABLE, ['userid' => $userid]);
4840 $DB->delete_records(user_competency_course::TABLE, ['userid' => $userid]);
4841 $DB->delete_records(user_competency_plan::TABLE, ['userid' => $userid]);
4843 // Delete any associated files.
4844 $fs = get_file_storage();
4845 $context = context_user::instance($userid);
4846 $userevidences = $DB->get_records(user_evidence::TABLE, ['userid' => $userid], '', 'id');
4847 foreach ($userevidences as $userevidence) {
4848 $DB->delete_records(user_evidence_competency::TABLE, ['userevidenceid' => $userevidence->id]);
4849 $DB->delete_records(user_evidence::TABLE, ['id' => $userevidence->id]);
4850 $fs->delete_area_files($context->id, 'core_competency', 'userevidence', $userevidence->id);
4853 $userplans = $DB->get_records(plan::TABLE, ['userid' => $userid], '', 'id');
4854 foreach ($userplans as $userplan) {
4855 $DB->delete_records(plan_competency::TABLE, ['planid' => $userplan->id]);
4856 $DB->delete_records(plan::TABLE, ['id' => $userplan->id]);
4861 * Manually grade a user competency.
4863 * @param int $userid
4864 * @param int $competencyid
4865 * @param int $grade
4866 * @param string $note A note to attach to the evidence
4867 * @return array of \core_competency\user_competency
4869 public static function grade_competency($userid, $competencyid, $grade, $note = null) {
4870 global $USER;
4871 static::require_enabled();
4873 $uc = static::get_user_competency($userid, $competencyid);
4874 $context = $uc->get_context();
4875 if (!user_competency::can_grade_user($uc->get('userid'))) {
4876 throw new required_capability_exception($context, 'moodle/competency:competencygrade', 'nopermissions', '');
4879 // Throws exception if competency not in plan.
4880 $competency = $uc->get_competency();
4881 $competencycontext = $competency->get_context();
4882 if (!has_any_capability(array('moodle/competency:competencyview', 'moodle/competency:competencymanage'),
4883 $competencycontext)) {
4884 throw new required_capability_exception($competencycontext, 'moodle/competency:competencyview', 'nopermissions', '');
4887 $action = evidence::ACTION_OVERRIDE;
4888 $desckey = 'evidence_manualoverride';
4890 $result = self::add_evidence($uc->get('userid'),
4891 $competency,
4892 $context->id,
4893 $action,
4894 $desckey,
4895 'core_competency',
4896 null,
4897 false,
4898 null,
4899 $grade,
4900 $USER->id,
4901 $note);
4902 if ($result) {
4903 $uc->read();
4904 $event = \core\event\competency_user_competency_rated::create_from_user_competency($uc);
4905 $event->trigger();
4907 return $result;
4911 * Manually grade a user competency from the plans page.
4913 * @param mixed $planorid
4914 * @param int $competencyid
4915 * @param int $grade
4916 * @param string $note A note to attach to the evidence
4917 * @return array of \core_competency\user_competency
4919 public static function grade_competency_in_plan($planorid, $competencyid, $grade, $note = null) {
4920 global $USER;
4921 static::require_enabled();
4923 $plan = $planorid;
4924 if (!is_object($planorid)) {
4925 $plan = new plan($planorid);
4928 $context = $plan->get_context();
4929 if (!user_competency::can_grade_user($plan->get('userid'))) {
4930 throw new required_capability_exception($context, 'moodle/competency:competencygrade', 'nopermissions', '');
4933 // Throws exception if competency not in plan.
4934 $competency = $plan->get_competency($competencyid);
4935 $competencycontext = $competency->get_context();
4936 if (!has_any_capability(array('moodle/competency:competencyview', 'moodle/competency:competencymanage'),
4937 $competencycontext)) {
4938 throw new required_capability_exception($competencycontext, 'moodle/competency:competencyview', 'nopermissions', '');
4941 $action = evidence::ACTION_OVERRIDE;
4942 $desckey = 'evidence_manualoverrideinplan';
4944 $result = self::add_evidence($plan->get('userid'),
4945 $competency,
4946 $context->id,
4947 $action,
4948 $desckey,
4949 'core_competency',
4950 $plan->get('name'),
4951 false,
4952 null,
4953 $grade,
4954 $USER->id,
4955 $note);
4956 if ($result) {
4957 $uc = static::get_user_competency($plan->get('userid'), $competency->get('id'));
4958 $event = \core\event\competency_user_competency_rated_in_plan::create_from_user_competency($uc, $plan->get('id'));
4959 $event->trigger();
4961 return $result;
4965 * Manually grade a user course competency from the course page.
4967 * This may push the rating to the user competency
4968 * if the course is configured this way.
4970 * @param mixed $courseorid
4971 * @param int $userid
4972 * @param int $competencyid
4973 * @param int $grade
4974 * @param string $note A note to attach to the evidence
4975 * @return array of \core_competency\user_competency
4977 public static function grade_competency_in_course($courseorid, $userid, $competencyid, $grade, $note = null) {
4978 global $USER, $DB;
4979 static::require_enabled();
4981 $course = $courseorid;
4982 if (!is_object($courseorid)) {
4983 $course = $DB->get_record('course', array('id' => $courseorid));
4985 $context = context_course::instance($course->id);
4987 // Check that we can view the user competency details in the course.
4988 if (!user_competency::can_read_user_in_course($userid, $course->id)) {
4989 throw new required_capability_exception($context, 'moodle/competency:usercompetencyview', 'nopermissions', '');
4992 // Validate the permission to grade.
4993 if (!user_competency::can_grade_user_in_course($userid, $course->id)) {
4994 throw new required_capability_exception($context, 'moodle/competency:competencygrade', 'nopermissions', '');
4997 // Check that competency is in course and visible to the current user.
4998 $competency = course_competency::get_competency($course->id, $competencyid);
4999 $competencycontext = $competency->get_context();
5000 if (!has_any_capability(array('moodle/competency:competencyview', 'moodle/competency:competencymanage'),
5001 $competencycontext)) {
5002 throw new required_capability_exception($competencycontext, 'moodle/competency:competencyview', 'nopermissions', '');
5005 // Check that the user is enrolled in the course, and is "gradable".
5006 if (!is_enrolled($context, $userid, 'moodle/competency:coursecompetencygradable')) {
5007 throw new coding_exception('The competency may not be rated at this time.');
5010 $action = evidence::ACTION_OVERRIDE;
5011 $desckey = 'evidence_manualoverrideincourse';
5013 $result = self::add_evidence($userid,
5014 $competency,
5015 $context->id,
5016 $action,
5017 $desckey,
5018 'core_competency',
5019 $context->get_context_name(),
5020 false,
5021 null,
5022 $grade,
5023 $USER->id,
5024 $note);
5025 if ($result) {
5026 $all = user_competency_course::get_multiple($userid, $course->id, array($competency->get('id')));
5027 $uc = reset($all);
5028 $event = \core\event\competency_user_competency_rated_in_course::create_from_user_competency_course($uc);
5029 $event->trigger();
5031 return $result;
5035 * Count the plans in the template, filtered by status.
5037 * Requires moodle/competency:templateview capability at the system context.
5039 * @param mixed $templateorid The id or the template.
5040 * @param int $status One of the plan status constants (or 0 for all plans).
5041 * @return int
5043 public static function count_plans_for_template($templateorid, $status = 0) {
5044 static::require_enabled();
5045 $template = $templateorid;
5046 if (!is_object($template)) {
5047 $template = new template($template);
5050 // First we do a permissions check.
5051 if (!$template->can_read()) {
5052 throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview',
5053 'nopermissions', '');
5056 return plan::count_records_for_template($template->get('id'), $status);
5060 * Count the user-completency-plans in the template, optionally filtered by proficiency.
5062 * Requires moodle/competency:templateview capability at the system context.
5064 * @param mixed $templateorid The id or the template.
5065 * @param mixed $proficiency If true, filter by proficiency, if false filter by not proficient, if null - no filter.
5066 * @return int
5068 public static function count_user_competency_plans_for_template($templateorid, $proficiency = null) {
5069 static::require_enabled();
5070 $template = $templateorid;
5071 if (!is_object($template)) {
5072 $template = new template($template);
5075 // First we do a permissions check.
5076 if (!$template->can_read()) {
5077 throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview',
5078 'nopermissions', '');
5081 return user_competency_plan::count_records_for_template($template->get('id'), $proficiency);
5085 * List the plans in the template, filtered by status.
5087 * Requires moodle/competency:templateview capability at the system context.
5089 * @param mixed $templateorid The id or the template.
5090 * @param int $status One of the plan status constants (or 0 for all plans).
5091 * @param int $skip The number of records to skip
5092 * @param int $limit The max number of records to return
5093 * @return plan[]
5095 public static function list_plans_for_template($templateorid, $status = 0, $skip = 0, $limit = 100) {
5096 $template = $templateorid;
5097 if (!is_object($template)) {
5098 $template = new template($template);
5101 // First we do a permissions check.
5102 if (!$template->can_read()) {
5103 throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview',
5104 'nopermissions', '');
5107 return plan::get_records_for_template($template->get('id'), $status, $skip, $limit);
5111 * Get the most often not completed competency for this course.
5113 * Requires moodle/competency:coursecompetencyview capability at the course context.
5115 * @param int $courseid The course id
5116 * @param int $skip The number of records to skip
5117 * @param int $limit The max number of records to return
5118 * @return competency[]
5120 public static function get_least_proficient_competencies_for_course($courseid, $skip = 0, $limit = 100) {
5121 static::require_enabled();
5122 $coursecontext = context_course::instance($courseid);
5124 if (!has_any_capability(array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage'),
5125 $coursecontext)) {
5126 throw new required_capability_exception($coursecontext, 'moodle/competency:coursecompetencyview', 'nopermissions', '');
5129 return user_competency_course::get_least_proficient_competencies_for_course($courseid, $skip, $limit);
5133 * Get the most often not completed competency for this template.
5135 * Requires moodle/competency:templateview capability at the system context.
5137 * @param mixed $templateorid The id or the template.
5138 * @param int $skip The number of records to skip
5139 * @param int $limit The max number of records to return
5140 * @return competency[]
5142 public static function get_least_proficient_competencies_for_template($templateorid, $skip = 0, $limit = 100) {
5143 static::require_enabled();
5144 $template = $templateorid;
5145 if (!is_object($template)) {
5146 $template = new template($template);
5149 // First we do a permissions check.
5150 if (!$template->can_read()) {
5151 throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview',
5152 'nopermissions', '');
5155 return user_competency_plan::get_least_proficient_competencies_for_template($template->get('id'), $skip, $limit);
5159 * Template event viewed.
5161 * Requires moodle/competency:templateview capability at the system context.
5163 * @param mixed $templateorid The id or the template.
5164 * @return boolean
5166 public static function template_viewed($templateorid) {
5167 static::require_enabled();
5168 $template = $templateorid;
5169 if (!is_object($template)) {
5170 $template = new template($template);
5173 // First we do a permissions check.
5174 if (!$template->can_read()) {
5175 throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview',
5176 'nopermissions', '');
5179 // Trigger a template viewed event.
5180 \core\event\competency_template_viewed::create_from_template($template)->trigger();
5182 return true;
5186 * Get the competency settings for a course.
5188 * Requires moodle/competency:coursecompetencyview capability at the course context.
5190 * @param int $courseid The course id
5191 * @return course_competency_settings
5193 public static function read_course_competency_settings($courseid) {
5194 static::require_enabled();
5196 // First we do a permissions check.
5197 if (!course_competency_settings::can_read($courseid)) {
5198 $context = context_course::instance($courseid);
5199 throw new required_capability_exception($context, 'moodle/competency:coursecompetencyview', 'nopermissions', '');
5202 return course_competency_settings::get_by_courseid($courseid);
5206 * Update the competency settings for a course.
5208 * Requires moodle/competency:coursecompetencyconfigure capability at the course context.
5210 * @param int $courseid The course id
5211 * @param stdClass $settings List of settings. The only valid setting ATM is pushratginstouserplans (boolean).
5212 * @return bool
5214 public static function update_course_competency_settings($courseid, $settings) {
5215 static::require_enabled();
5217 $settings = (object) $settings;
5219 // Get all the valid settings.
5220 $pushratingstouserplans = isset($settings->pushratingstouserplans) ? $settings->pushratingstouserplans : false;
5222 // First we do a permissions check.
5223 if (!course_competency_settings::can_manage_course($courseid)) {
5224 $context = context_course::instance($courseid);
5225 throw new required_capability_exception($context, 'moodle/competency:coursecompetencyconfigure', 'nopermissions', '');
5228 $exists = course_competency_settings::get_record(array('courseid' => $courseid));
5230 // Now update or insert.
5231 if ($exists) {
5232 $settings = $exists;
5233 $settings->set('pushratingstouserplans', $pushratingstouserplans);
5234 return $settings->update();
5235 } else {
5236 $data = (object) array('courseid' => $courseid, 'pushratingstouserplans' => $pushratingstouserplans);
5237 $settings = new course_competency_settings(0, $data);
5238 $result = $settings->create();
5239 return !empty($result);
5245 * Function used to return a list of users where the given user has a particular capability.
5247 * This is used e.g. to find all the users where someone is able to manage their learning plans,
5248 * it also would be useful for mentees etc.
5250 * @param string $capability - The capability string we are filtering for. If '' is passed,
5251 * an always matching filter is returned.
5252 * @param int $userid - The user id we are using for the access checks. Defaults to current user.
5253 * @param int $type - The type of named params to return (passed to $DB->get_in_or_equal).
5254 * @param string $prefix - The type prefix for the db table (passed to $DB->get_in_or_equal).
5255 * @return list($sql, $params) Same as $DB->get_in_or_equal().
5256 * @todo MDL-52243 Move this function to lib/accesslib.php
5258 public static function filter_users_with_capability_on_user_context_sql($capability, $userid = 0, $type = SQL_PARAMS_QM,
5259 $prefix='param') {
5261 global $USER, $DB;
5262 $allresultsfilter = array('> 0', array());
5263 $noresultsfilter = array('= -1', array());
5265 if (empty($capability)) {
5266 return $allresultsfilter;
5269 if (!$capinfo = get_capability_info($capability)) {
5270 throw new coding_exception('Capability does not exist: ' . $capability);
5273 if (empty($userid)) {
5274 $userid = $USER->id;
5277 // Make sure the guest account and not-logged-in users never get any risky caps no matter what the actual settings are.
5278 if (($capinfo->captype === 'write') or ($capinfo->riskbitmask & (RISK_XSS | RISK_CONFIG | RISK_DATALOSS))) {
5279 if (isguestuser($userid) or $userid == 0) {
5280 return $noresultsfilter;
5284 if (is_siteadmin($userid)) {
5285 // No filtering for site admins.
5286 return $allresultsfilter;
5289 // Check capability on system level.
5290 $syscontext = context_system::instance();
5291 $hassystem = has_capability($capability, $syscontext, $userid);
5293 $access = get_user_roles_sitewide_accessdata($userid);
5294 // Build up a list of level 2 contexts (candidates to be user context).
5295 $filtercontexts = array();
5296 // Build list of roles to check overrides.
5297 $roles = array();
5299 foreach ($access['ra'] as $path => $role) {
5300 $parts = explode('/', $path);
5301 if (count($parts) == 3) {
5302 $filtercontexts[$parts[2]] = $parts[2];
5303 } else if (count($parts) > 3) {
5304 // We know this is not a user context because there is another path with more than 2 levels.
5305 unset($filtercontexts[$parts[2]]);
5307 $roles = array_merge($roles, $role);
5310 // Add all contexts in which a role may be overidden.
5311 $rdefs = get_role_definitions($roles);
5312 foreach ($rdefs as $roledef) {
5313 foreach ($roledef as $path => $caps) {
5314 if (!isset($caps[$capability])) {
5315 // The capability is not mentioned, we can ignore.
5316 continue;
5318 $parts = explode('/', $path);
5319 if (count($parts) === 3) {
5320 // Only get potential user contexts, they only ever have 2 slashes /parentId/Id.
5321 $filtercontexts[$parts[2]] = $parts[2];
5326 // No interesting contexts - return all or no results.
5327 if (empty($filtercontexts)) {
5328 if ($hassystem) {
5329 return $allresultsfilter;
5330 } else {
5331 return $noresultsfilter;
5334 // Fetch all interesting contexts for further examination.
5335 list($insql, $params) = $DB->get_in_or_equal($filtercontexts, SQL_PARAMS_NAMED);
5336 $params['level'] = CONTEXT_USER;
5337 $fields = context_helper::get_preload_record_columns_sql('ctx');
5338 $interestingcontexts = $DB->get_recordset_sql('SELECT ' . $fields . '
5339 FROM {context} ctx
5340 WHERE ctx.contextlevel = :level
5341 AND ctx.id ' . $insql . '
5342 ORDER BY ctx.id', $params);
5343 if ($hassystem) {
5344 // If allowed at system, search for exceptions prohibiting the capability at user context.
5345 $excludeusers = array();
5346 foreach ($interestingcontexts as $contextrecord) {
5347 $candidateuserid = $contextrecord->ctxinstance;
5348 context_helper::preload_from_record($contextrecord);
5349 $usercontext = context_user::instance($candidateuserid);
5350 // Has capability should use the data already preloaded.
5351 if (!has_capability($capability, $usercontext, $userid)) {
5352 $excludeusers[$candidateuserid] = $candidateuserid;
5356 // Construct SQL excluding users with this role assigned for this user.
5357 if (empty($excludeusers)) {
5358 $interestingcontexts->close();
5359 return $allresultsfilter;
5361 list($sql, $params) = $DB->get_in_or_equal($excludeusers, $type, $prefix, false);
5362 } else {
5363 // If not allowed at system, search for exceptions allowing the capability at user context.
5364 $allowusers = array();
5365 foreach ($interestingcontexts as $contextrecord) {
5366 $candidateuserid = $contextrecord->ctxinstance;
5367 context_helper::preload_from_record($contextrecord);
5368 $usercontext = context_user::instance($candidateuserid);
5369 // Has capability should use the data already preloaded.
5370 if (has_capability($capability, $usercontext, $userid)) {
5371 $allowusers[$candidateuserid] = $candidateuserid;
5375 // Construct SQL excluding users with this role assigned for this user.
5376 if (empty($allowusers)) {
5377 $interestingcontexts->close();
5378 return $noresultsfilter;
5380 list($sql, $params) = $DB->get_in_or_equal($allowusers, $type, $prefix);
5382 $interestingcontexts->close();
5384 // Return the goods!.
5385 return array($sql, $params);