2 // This file is part of Moodle - http://moodle.org/
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
18 * Class for loading/storing competency frameworks from the DB.
20 * @package core_competency
21 * @copyright 2015 Damyon Wiese
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24 namespace core_competency
;
25 defined('MOODLE_INTERNAL') ||
die();
36 use require_login_exception
;
39 use required_capability_exception
;
42 * Class for doing things with competency frameworks.
44 * @copyright 2015 Damyon Wiese
45 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
49 /** @var boolean Allow api functions even if competencies are not enabled for the site. */
50 private static $skipenabled = false;
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');
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');
75 * Allow calls to competency api functions even if competencies are not currently enabled.
77 public static function skip_enabled() {
78 self
::$skipenabled = true;
82 * Restore the checking that competencies are enabled with any api function.
84 public static function check_enabled() {
85 self
::$skipenabled = false;
89 * Throws an exception if competencies are not enabled.
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.
110 public static function is_scale_used_anywhere($scaleid) {
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.
130 protected static function validate_course_module($cmmixed, $throwexception = true) {
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');
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.
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');
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.
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();
202 $parent->reset_rule();
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.
217 public static function delete_competency($id) {
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());
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)) {
232 $transaction = $DB->start_delegated_transaction();
236 // Reset the rule of the parent.
237 $parent = $competency->get_parent();
239 $parent->reset_rule();
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();
264 foreach ($events as $event) {
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.
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')));
292 $sortorder = $current->get('sortorder');
293 if ($sortorder >= $max) {
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();
309 $result = $current->update();
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.
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) {
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();
347 $result = $current->update();
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.
361 public static function set_parent_competency($id, $newparentid) {
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'));
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).
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();
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();
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.
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());
447 $result = $competency->update();
449 // Trigger the update event.
450 \core\event\competency_updated
::create_from_competency($competency)->trigger();
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.
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', '');
475 if ($includerelated) {
476 $relatedcompetency = new related_competency();
477 if ($related = $relatedcompetency->list_relations($id)) {
478 $competency->relatedcompetencies
= $related;
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', '');
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();
526 $framework = new competency_framework($filters['competencyframeworkid']);
527 $context = $framework->get_context();
530 // First we do a permissions check.
531 if (!has_any_capability(array('moodle/competency:competencyview', 'moodle/competency:competencymanage'), $context)) {
532 throw new required_capability_exception($context, 'moodle/competency:competencyview', 'nopermissions', '');
536 return competency
::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.
547 public static function count_competencies($filters) {
548 static::require_enabled();
549 if (!isset($filters['competencyframeworkid'])) {
550 $context = context_system
::instance();
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', '');
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();
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) {
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();
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);
634 $relcomp->set('competencyid', $newrelcompid);
635 $relcomp->set('relatedcompetencyid', $newcompid);
637 $relcomp->set('id', 0);
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();
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.
668 public static function delete_framework($id) {
670 static::require_enabled();
671 $framework = new competency_framework($id);
672 require_capability('moodle/competency:competencymanage', $framework->get_context());
675 $competenciesid = competency
::get_ids_by_frameworkid($id);
676 $contextid = $framework->get('contextid');
677 if (!competency
::can_all_be_deleted($competenciesid)) {
680 $transaction = $DB->start_delegated_transaction();
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.
710 // Trigger deleted event competencies.
711 foreach ($events as $event) {
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.
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', '');
765 * Logg the competency framework viewed event.
767 * @param competency_framework|int $frameworkorid The competency_framework object or competency framework id
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();
786 * Logg the competency viewed event.
788 * @param competency|int $competencyorid The competency object or competency id
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();
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 = '') {
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', '');
841 list($insql, $inparams) = $DB->get_in_or_equal(array_keys($contexts), SQL_PARAMS_NAMED
);
842 $select = "contextid $insql";
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.
873 public static function count_frameworks($context, $includes) {
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', '');
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) {
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);
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]);
953 * Count all the courses using a competency.
955 * @param int $competencyid The id of the competency to check.
958 public static function count_courses_using_competency($competencyid) {
959 static::require_enabled();
962 $courses = course_competency
::list_courses_min($competencyid);
965 // Now check permissions on each course.
966 foreach ($courses as $course) {
967 if (!self
::validate_course($course, false)) {
971 $context = context_course
::instance($course->id
);
972 $capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage');
973 if (!has_any_capability($capabilities, $context)) {
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();
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);
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();
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', '');
1038 $cmclist = course_module_competency
::list_course_module_competencies($cm->id
);
1039 foreach ($cmclist as $id => $cmc) {
1040 array_push($result, $cmc);
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();
1056 $courses = course_competency
::list_courses($competencyid);
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]);
1067 if (!self
::validate_course($course, false)) {
1068 unset($courses[$id]);
1071 array_push($result, $course);
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.
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', '');
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.
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', '');
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
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', '');
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) {
1158 'competency' => $competencies[$coursecompetency->get('competencyid')],
1159 'coursecompetency' => $coursecompetency
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);
1179 $uc = user_competency
::create_relation($userid, $competencyid);
1183 if (!$uc->can_read()) {
1184 throw new required_capability_exception($uc->get_context(), 'moodle/competency:usercompetencyview',
1185 'nopermissions', '');
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', '');
1207 * Count the competencies associated to a course module.
1209 * @param mixed $cmorid The course module, or its ID.
1212 public static function count_course_module_competencies($cmorid) {
1213 static::require_enabled();
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
1240 public static function list_course_module_competencies($cmorid) {
1241 static::require_enabled();
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', '');
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) {
1265 'competency' => $competencies[$coursemodulecompetency->get('competencyid')],
1266 'coursemodulecompetency' => $coursemodulecompetency
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);
1302 $ucc = user_competency_course
::create_relation($userid, $competency->get('id'), $courseid);
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);
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', '');
1330 $competencylist = course_competency
::list_competencies($courseid, false);
1332 $existing = user_competency_course
::get_multiple($userid, $courseid, $competencylist);
1334 $orderedusercompetencycourses = array();
1336 $somemissing = false;
1337 foreach ($competencylist as $coursecompetency) {
1339 foreach ($existing as $usercompetencycourse) {
1340 if ($usercompetencycourse->get('competencyid') == $coursecompetency->get('id')) {
1342 $orderedusercompetencycourses[$usercompetencycourse->get('id')] = $usercompetencycourse;
1347 $ucc = user_competency_course
::create_relation($userid, $coursecompetency->get('id'), $courseid);
1349 $orderedusercompetencycourses[$ucc->get('id')] = $ucc;
1353 return $orderedusercompetencycourses;
1357 * List the user competencies to review.
1359 * The method returns values in this format:
1362 * 'competencies' => array(
1364 * 'usercompetency' => (user_competency),
1365 * 'competency' => (competency),
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) {
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
1402 WHERE (uc.status = :waitingforreview
1403 OR (uc.status = :inreview AND uc.reviewerid = :reviewerid))
1405 $ordersql = " ORDER BY c.shortname ASC";
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);
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;
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
1452 public static function add_competency_to_course_module($cmorid, $competencyid) {
1453 static::require_enabled();
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));
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));
1480 $coursemodulecompetency->from_record($record);
1481 if ($coursemodulecompetency->create()) {
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
1495 public static function remove_competency_from_course_module($cmorid, $competencyid) {
1496 static::require_enabled();
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));
1516 return $exists->delete();
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.
1531 public static function reorder_course_module_competency($cmorid, $competencyidfrom, $competencyidto) {
1532 static::require_enabled();
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);
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.
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
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));
1640 $coursecompetency->from_record($record);
1641 if ($coursecompetency->create()) {
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
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));
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) {
1677 return $exists->delete();
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.
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);
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.
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.
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', '');
1781 $template = $template->create();
1783 // Trigger a template created event.
1784 \core\event\competency_template_created
::create_from_template($template)->trigger();
1790 * Duplicate a learning plan template.
1792 * Requires moodle/competency:templatemanage capability at the template context.
1794 * @param int $id the template id.
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', '');
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.
1837 public static function delete_template($id, $deleteplans = true) {
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();
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();
1860 // Still OK, delete or unlink the plans from the template.
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);
1871 // Still OK, delete the template comptencies.
1873 $success = template_competency
::delete_by_templateid($template->get('id'));
1878 // Create a template deleted event.
1879 $event = \core\event\competency_template_deleted
::create_from_template($template);
1881 $success = $template->delete();
1885 // Trigger a template deleted event.
1888 // Commit the transaction.
1889 $transaction->allow_commit();
1891 $transaction->rollback(new moodle_exception('Error while deleting the template.'));
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.
1905 public static function update_template($record) {
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();
1939 $transaction->rollback(new moodle_exception('Error while updating the template.'));
1943 // Trigger a template updated event.
1944 \core\event\competency_template_updated
::create_from_template($template)->trigger();
1947 plan
::update_multiple_from_template($template);
1950 $transaction->allow_commit();
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.
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', '');
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) {
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.
2011 if (!empty($sort)) {
2012 $orderby = $sort . ' ' . $order;
2016 $template = new template();
2017 list($insql, $params) = $DB->get_in_or_equal(array_keys($contexts), SQL_PARAMS_NAMED
);
2018 $select = "contextid $insql";
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.
2040 public static function count_templates($context, $includes) {
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', '');
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.
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();
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)) {
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();
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)) {
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.
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', '');
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.
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', '');
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', '');
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
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));
2206 $templatecompetency = new template_competency(0, $record);
2207 $templatecompetency->create();
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
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));
2237 $link = array_pop($exists);
2238 return $link->delete();
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.
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', '');
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.
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) {
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();
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) {
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')) {
2363 return $tplcohort->delete();
2369 * @param int $userid
2370 * @return \core_competency\plan[]
2372 public static function list_user_plans($userid) {
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:
2410 * 'template' => (template),
2411 * 'owner' => (stdClass)
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) {
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
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";
2453 'waitingforreview' => plan
::STATUS_WAITING_FOR_REVIEW
,
2454 'inreview' => plan
::STATUS_IN_REVIEW
,
2455 'reviewerid' => $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);
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";
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_'));
2480 if ($plan->is_based_on_template()) {
2481 $template = new template(0, template
::extract_record($record, 'tpl_'));
2484 $plans[] = (object) array(
2486 'template' => $template,
2487 'owner' => persistent
::extract_record($record, 'usr_'),
2493 'count' => $DB->count_records_sql($countselect . $sql, $params),
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) {
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', '');
2522 // Trigger created event.
2523 \core\event\competency_plan_created
::create_from_plan($plan)->trigger();
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
;
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))) {
2586 // Trigger created event.
2587 \core\event\competency_plan_created
::create_from_plan($plan)->trigger();
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) {
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.
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.
2660 // Trigger created event.
2661 \core\event\competency_plan_created
::create_from_plan($plan)->trigger();
2669 * Unlink a plan from its template.
2671 * @param \core_competency\plan|int $planorid The plan or its ID.
2674 public static function unlink_plan_from_template($planorid) {
2676 static::require_enabled();
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()) {
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);
2705 foreach ($competencies as $competency) {
2706 $record = (object) array(
2707 'planid' => $plan->get('id'),
2708 'competencyid' => $competency->get('id'),
2711 $pc = new plan_competency(null, $record);
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();
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);
2765 // Trigger updated event.
2766 \core\event\competency_plan_updated
::create_from_plan($plan)->trigger();
2772 * Returns a plan data.
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', '');
2790 * Plan event viewed.
2792 * @param mixed $planorid The id or the plan.
2795 public static function plan_viewed($planorid) {
2796 static::require_enabled();
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();
2817 * Plans based on a template can be removed just like any other one.
2820 * @return bool Success?
2822 public static function delete_plan($id) {
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.
2858 * Cancel the review of a plan.
2860 * @param int|plan $planorid The plan, or its ID.
2863 public static function plan_cancel_review_request($planorid) {
2864 static::require_enabled();
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();
2893 * Request the review of a plan.
2895 * @param int|plan $planorid The plan, or its ID.
2898 public static function plan_request_review($planorid) {
2899 static::require_enabled();
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();
2928 * Start the review of a plan.
2930 * @param int|plan $planorid The plan, or its ID.
2933 public static function plan_start_review($planorid) {
2935 static::require_enabled();
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();
2965 * Stop reviewing a plan.
2967 * @param int|plan $planorid The plan, or its ID.
2970 public static function plan_stop_review($planorid) {
2971 static::require_enabled();
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();
3003 * This means making the plan active.
3005 * @param int|plan $planorid The plan, or its ID.
3008 public static function approve_plan($planorid) {
3009 static::require_enabled();
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();
3042 * This means making the plan draft.
3044 * @param int|plan $planorid The plan, or its ID.
3047 public static function unapprove_plan($planorid) {
3048 static::require_enabled();
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();
3079 * @param int|plan $planorid The plan, or its ID.
3082 public static function complete_plan($planorid) {
3084 static::require_enabled();
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);
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();
3119 $transaction->rollback(new moodle_exception('The plan could not be updated.'));
3123 $transaction->allow_commit();
3125 // Trigger updated event.
3126 \core\event\competency_plan_completed
::create_from_plan($plan)->trigger();
3134 * @param int|plan $planorid The plan, or its ID.
3137 public static function reopen_plan($planorid) {
3139 static::require_enabled();
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();
3178 $transaction->rollback(new moodle_exception('The plan could not be updated.'));
3182 $transaction->allow_commit();
3184 // Trigger reopened event.
3185 \core\event\competency_plan_reopened
::create_from_plan($plan)->trigger();
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();
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';
3222 $usercompetencies = user_competency
::get_multiple($plan->get('userid'), array($competencyid));
3223 $ucresultkey = 'usercompetency';
3226 $found = count($usercompetencies);
3229 $uc = array_pop($usercompetencies);
3231 if ($iscompletedplan) {
3232 throw new coding_exception('A user competency plan is missing');
3234 $uc = user_competency
::create_relation($plan->get('userid'), $competency->get('id'));
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) {
3259 static::require_enabled();
3260 $competencyid = $competencyorid;
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]);
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
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();
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', '');
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';
3309 $usercompetencies = user_competency
::get_multiple($plan->get('userid'), $competencies);
3310 $ucresultkey = 'usercompetency';
3313 // Build the return values.
3314 foreach ($competencies as $key => $competency) {
3317 foreach ($usercompetencies as $uckey => $uc) {
3318 if ($uc->get('competencyid') == $competency->get('id')) {
3320 unset($usercompetencies[$uckey]);
3326 if ($iscompletedplan) {
3327 throw new coding_exception('A user competency plan is missing');
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;
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
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));
3377 $record = new stdClass();
3378 $record->planid
= $planid;
3379 $record->competencyid
= $competencyid;
3380 $plancompetency = new plan_competency(0, $record);
3381 $plancompetency->create();
3388 * Remove a competency from a plan.
3390 * @param int $planid The plan id
3391 * @param int $competencyid The id of the competency
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));
3413 return $link->delete();
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.
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');
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.
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.
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();
3502 \core\event\competency_user_competency_review_request_cancelled
::create_from_user_competency($uc)->trigger();
3508 * Request a user competency review.
3510 * @param int $userid The user ID.
3511 * @param int $competencyid The competency ID.
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));
3518 $uc = user_competency
::create_relation($userid, $competencyid);
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();
3535 \core\event\competency_user_competency_review_requested
::create_from_user_competency($uc)->trigger();
3541 * Start a user competency review.
3543 * @param int $userid The user ID.
3544 * @param int $competencyid The competency ID.
3547 public static function user_competency_start_review($userid, $competencyid) {
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();
3565 \core\event\competency_user_competency_review_started
::create_from_user_competency($uc)->trigger();
3571 * Stop a user competency review.
3573 * @param int $userid The user ID.
3574 * @param int $competencyid The competency ID.
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();
3592 \core\event\competency_user_competency_review_stopped
::create_from_user_competency($uc)->trigger();
3598 * Log user competency viewed event.
3600 * @param user_competency|int $usercompetencyorid The user competency object or user competency id
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();
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
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();
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
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();
3673 * Log user competency plan viewed event.
3675 * @param user_competency_plan|int $usercompetencyplanorid The user competency plan object or user competency plan id
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();
3700 * Check if template has related data.
3702 * @param int $templateid The id of the template to check.
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', '');
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();
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();
3785 * Read a user evidence.
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.
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.
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');
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) {
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'));
3951 $userevidence->get('userid'),
3953 $userevidence->get_context(),
3954 evidence
::ACTION_LOG
,
3955 'evidence_evidenceofpriorlearninglinked',
3957 $userevidence->get('name'),
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.
3975 public static function delete_user_evidence_competency($userevidenceorid, $competencyid) {
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')) {
3996 $success = $relation->delete();
3999 $userevidence->get('userid'),
4001 $userevidence->get_context(),
4002 evidence
::ACTION_LOG
,
4003 'evidence_evidenceofpriorlearningunlinked',
4005 $userevidence->get('name'),
4017 * Send request review for user evidence competencies.
4019 * @param int $id The user evidence ID.
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'));
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;
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)) {
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.
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);
4133 foreach ($competencies as $competency) {
4136 foreach ($usercompetencies as $uckey => $uc) {
4137 if ($uc->get('competencyid') == $competency->get('id')) {
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]);
4155 // If the user competency doesn't exist, we create a new relation in user_competency_plan.
4157 $usercompetencyplan = user_competency_plan
::create_relation($plan->get('userid'), $competency->get('id'),
4159 $usercompetencyplan->set('sortorder', $i);
4160 $usercompetencyplan->create();
4167 * Delete archived user competencies in a plan.
4169 * @param plan $plan The plan object.
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) {
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) {
4208 $plancompleted = false;
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);
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) {
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.
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) {
4288 static::require_enabled();
4290 // Some clearly important variable assignments right there.
4291 $competencyid = $competencyorid;
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
;
4302 $context = context
::instance_by_id($contextorid);
4304 $setucgrade = false;
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?
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
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) {
4360 $usercompetencycourse->set('grade', $grade);
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');
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;
4375 $ucproficiency = $proficiency;
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) {
4386 $ucproficiency = $proficiency;
4392 // We override the grade, even overriding back to not set.
4393 case evidence
::ACTION_OVERRIDE
:
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();
4419 $proficiency = $ucproficiency;
4420 if ($proficiency === null) {
4421 if (empty($competency)) {
4422 $competency = new competency($competencyid);
4424 $proficiency = $competency->get_proficiency_of_grade($grade);
4427 $usercompetencycourse->set('grade', $grade);
4429 $usercompetencycourse->set('proficiency', $proficiency);
4431 $coursesettings = course_competency_settings
::get_by_courseid($courseid);
4432 if (!$coursesettings->get('pushratingstouserplans')) {
4433 $setucgrade = false;
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.");
4446 // Whoops, this is not expected.
4448 throw new coding_exception('Unexpected action parameter when registering an evidence.');
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);
4514 * @param int $evidenceid The evidence ID.
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', '');
4531 * Delete an evidence.
4533 * @param evidence|int $evidenceorid The evidence, or its ID.
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.
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) {
4584 // The parent should have a rule, and a meaningful outcome.
4585 $ruleoutcome = $parent->get('ruleoutcome');
4586 if ($ruleoutcome == competency
::OUTCOME_NONE
) {
4589 $rule = $parent->get_rule_object();
4590 if ($rule === null) {
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')));
4598 $parentuc = user_competency
::create_relation($userid, $parent->get('id'));
4599 $parentuc->create();
4602 // Does the rule match?
4603 if (!$rule->matches($parentuc)) {
4607 // Figuring out what to do.
4609 if ($ruleoutcome == competency
::OUTCOME_EVIDENCE
) {
4610 $action = evidence
::ACTION_LOG
;
4612 } else if ($ruleoutcome == competency
::OUTCOME_RECOMMEND
) {
4613 $action = evidence
::ACTION_LOG
;
4616 } else if ($ruleoutcome == competency
::OUTCOME_COMPLETE
) {
4617 $action = evidence
::ACTION_COMPLETE
;
4620 throw new moodle_exception('Unexpected rule outcome: ' +
$ruleoutcome);
4623 // Finally add an evidence.
4624 static::add_evidence(
4627 $parent->get_context()->id
,
4629 'evidence_competencyrule',
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
4650 public static function observe_course_module_completion_updated(\core\event\course_module_completion_updated
$event) {
4651 if (!static::is_enabled()) {
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');
4671 $strdesc = 'evidence_coursemodulecompleted';
4672 $overridegrade = $coursemodulecompetency->get('overridegrade');
4674 if ($outcome == course_module_competency
::OUTCOME_NONE
) {
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
;
4684 } else if ($outcome == course_module_competency
::OUTCOME_COMPLETE
) {
4685 $action = evidence
::ACTION_COMPLETE
;
4688 throw new moodle_exception('Unexpected rule outcome: ' +
$outcome);
4691 static::add_evidence(
4692 $event->relateduserid
,
4693 $coursemodulecompetency->get('competencyid'),
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
4719 public static function observe_course_completed(\core\event\course_completed
$event) {
4720 if (!static::is_enabled()) {
4724 $sql = 'courseid = :courseid AND ruleoutcome != :nooutcome';
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');
4739 $strdesc = 'evidence_coursecompleted';
4741 if ($outcome == course_module_competency
::OUTCOME_NONE
) {
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
;
4751 } else if ($outcome == course_competency
::OUTCOME_COMPLETE
) {
4752 $action = evidence
::ACTION_COMPLETE
;
4755 throw new moodle_exception('Unexpected rule outcome: ' +
$outcome);
4758 static::add_evidence(
4759 $event->relateduserid
,
4760 $coursecompetency->get('competencyid'),
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.
4780 public static function hook_course_module_deleted(stdClass
$cm) {
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.
4793 public static function hook_course_deleted(stdClass
$course) {
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.
4808 public static function hook_course_reset_competency_ratings($courseid) {
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.
4821 public static function hook_cohort_deleted(\stdClass
$cohort) {
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) {
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
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) {
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'),
4904 $event = \core\event\competency_user_competency_rated
::create_from_user_competency($uc);
4911 * Manually grade a user competency from the plans page.
4913 * @param mixed $planorid
4914 * @param int $competencyid
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) {
4921 static::require_enabled();
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'),
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'));
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
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) {
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,
5019 $context->get_context_name(),
5026 $all = user_competency_course
::get_multiple($userid, $course->id
, array($competency->get('id')));
5028 $event = \core\event\competency_user_competency_rated_in_course
::create_from_user_competency_course($uc);
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).
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.
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
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'),
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.
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();
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).
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.
5232 $settings = $exists;
5233 $settings->set('pushratingstouserplans', $pushratingstouserplans);
5234 return $settings->update();
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
,
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.
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.
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)) {
5329 return $allresultsfilter;
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 . '
5340 WHERE ctx.contextlevel = :level
5341 AND ctx.id ' . $insql . '
5342 ORDER BY ctx.id', $params);
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);
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);