d761eb2868a79b512b7259bb6f6d1c7487697476
[moodle.git] / admin / tool / lp / classes / api.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * Class for loading/storing competency frameworks from the DB.
19  *
20  * @package    tool_lp
21  * @copyright  2015 Damyon Wiese
22  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  */
24 namespace tool_lp;
25 defined('MOODLE_INTERNAL') || die();
27 use stdClass;
28 use context;
29 use context_helper;
30 use context_system;
31 use context_course;
32 use context_module;
33 use context_user;
34 use coding_exception;
35 use require_login_exception;
36 use moodle_exception;
37 use moodle_url;
38 use required_capability_exception;
40 /**
41  * Class for doing things with competency frameworks.
42  *
43  * @copyright  2015 Damyon Wiese
44  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
45  */
46 class api {
48     /**
49      * Returns whether competencies are enabled.
50      *
51      * @return boolean True when enabled.
52      */
53     public static function is_enabled() {
54         return get_config('tool_lp', 'enabled');
55     }
57     /**
58      * Throws an exception if competencies are not enabled.
59      *
60      * @return void
61      * @throws moodle_exception
62      */
63     public static function require_enabled() {
64         if (!static::is_enabled()) {
65             throw new moodle_exception('competenciesarenotenabled', 'tool_lp');
66         }
67     }
69     /**
70      * Checks whether a scale is used anywhere in the plugin.
71      *
72      * This public API has two exceptions:
73      * - It MUST NOT perform any capability checks.
74      * - It MUST ignore whether competencies are enabled or not ({@link self::is_enabled()}).
75      *
76      * @param int $scaleid The scale ID.
77      * @return bool
78      */
79     public static function is_scale_used_anywhere($scaleid) {
80         global $DB;
81         $sql = "SELECT s.id
82                   FROM {scale} s
83              LEFT JOIN {" . competency_framework::TABLE ."} f
84                     ON f.scaleid = :scaleid1
85              LEFT JOIN {" . competency::TABLE ."} c
86                     ON c.scaleid = :scaleid2
87                  WHERE f.id IS NOT NULL
88                     OR c.id IS NOT NULL";
89         return $DB->record_exists_sql($sql, ['scaleid1' => $scaleid, 'scaleid2' => $scaleid]);
90     }
92     /**
93      * Validate if current user have acces to the course_module if hidden.
94      *
95      * @param mixed $cmmixed The cm_info class, course module record or its ID.
96      * @param bool $throwexception Throw an exception or not.
97      * @return bool
98      */
99     protected static function validate_course_module($cmmixed, $throwexception = true) {
100         $cm = $cmmixed;
101         if (!is_object($cm)) {
102             $cmrecord = get_coursemodule_from_id(null, $cmmixed);
103             $modinfo = get_fast_modinfo($cmrecord->course);
104             $cm = $modinfo->get_cm($cmmixed);
105         } else if (!$cm instanceof cm_info) {
106             // Assume we got a course module record.
107             $modinfo = get_fast_modinfo($cm->course);
108             $cm = $modinfo->get_cm($cm->id);
109         }
111         if (!$cm->uservisible) {
112             if ($throwexception) {
113                 throw new require_login_exception('Course module is hidden');
114             } else {
115                 return false;
116             }
117         }
119         return true;
120     }
122     /**
123      * Validate if current user have acces to the course if hidden.
124      *
125      * @param mixed $courseorid The course or it ID.
126      * @param bool $throwexception Throw an exception or not.
127      * @return bool
128      */
129     protected static function validate_course($courseorid, $throwexception = true) {
130         $course = $courseorid;
131         if (!is_object($course)) {
132             $course = get_course($course);
133         }
135         $coursecontext = context_course::instance($course->id);
136         if (!$course->visible and !has_capability('moodle/course:viewhiddencourses', $coursecontext)) {
137             if ($throwexception) {
138                 throw new require_login_exception('Course is hidden');
139             } else {
140                 return false;
141             }
142         }
144         return true;
145     }
147     /**
148      * Create a competency from a record containing all the data for the class.
149      *
150      * Requires tool/lp:competencymanage capability at the system context.
151      *
152      * @param stdClass $record Record containing all the data for an instance of the class.
153      * @return competency
154      */
155     public static function create_competency(stdClass $record) {
156         static::require_enabled();
157         $competency = new competency(0, $record);
159         // First we do a permissions check.
160         require_capability('tool/lp:competencymanage', $competency->get_context());
162         // Reset the sortorder, use reorder instead.
163         $competency->set_sortorder(null);
164         $competency->create();
166         \tool_lp\event\competency_created::create_from_competency($competency)->trigger();
168         // Reset the rule of the parent.
169         $parent = $competency->get_parent();
170         if ($parent) {
171             $parent->reset_rule();
172             $parent->update();
173         }
175         return $competency;
176     }
178     /**
179      * Delete a competency by id.
180      *
181      * Requires tool/lp:competencymanage capability at the system context.
182      *
183      * @param int $id The record to delete. This will delete alot of related data - you better be sure.
184      * @return boolean
185      */
186     public static function delete_competency($id) {
187         global $DB;
188         static::require_enabled();
189         $competency = new competency($id);
191         // First we do a permissions check.
192         require_capability('tool/lp:competencymanage', $competency->get_context());
194         $events = array();
195         $competencyids = array(intval($competency->get_id()));
196         $contextid = $competency->get_context()->id;
197         $competencyids = array_merge(competency::get_descendants_ids($competency), $competencyids);
198         if (!competency::can_all_be_deleted($competencyids)) {
199             return false;
200         }
201         $transaction = $DB->start_delegated_transaction();
203         try {
205             // Reset the rule of the parent.
206             $parent = $competency->get_parent();
207             if ($parent) {
208                 $parent->reset_rule();
209                 $parent->update();
210             }
212             // Delete the competency separately so the after_delete event can be triggered.
213             $competency->delete();
215             // Delete the competencies.
216             competency::delete_multiple($competencyids);
218             // Delete the competencies relation.
219             related_competency::delete_multiple_relations($competencyids);
221             // Delete competency evidences.
222             user_evidence_competency::delete_by_competencyids($competencyids);
224             // Register the competencies deleted events.
225             $events = \tool_lp\event\competency_deleted::create_multiple_from_competencyids($competencyids, $contextid);
227         } catch (\Exception $e) {
228             $transaction->rollback($e);
229         }
231         $transaction->allow_commit();
232         // Trigger events.
233         foreach ($events as $event) {
234             $event->trigger();
235         }
237         return true;
238     }
240     /**
241      * Reorder this competency.
242      *
243      * Requires tool/lp:competencymanage capability at the system context.
244      *
245      * @param int $id The id of the competency to move.
246      * @return boolean
247      */
248     public static function move_down_competency($id) {
249         static::require_enabled();
250         $current = new competency($id);
252         // First we do a permissions check.
253         require_capability('tool/lp:competencymanage', $current->get_context());
255         $max = self::count_competencies(array('parentid' => $current->get_parentid(),
256                                               'competencyframeworkid' => $current->get_competencyframeworkid()));
257         if ($max > 0) {
258             $max--;
259         }
261         $sortorder = $current->get_sortorder();
262         if ($sortorder >= $max) {
263             return false;
264         }
265         $sortorder = $sortorder + 1;
266         $current->set_sortorder($sortorder);
268         $filters = array('parentid' => $current->get_parentid(),
269                          'competencyframeworkid' => $current->get_competencyframeworkid(),
270                          'sortorder' => $sortorder);
271         $children = self::list_competencies($filters, 'id');
272         foreach ($children as $needtoswap) {
273             $needtoswap->set_sortorder($sortorder - 1);
274             $needtoswap->update();
275         }
277         // OK - all set.
278         $result = $current->update();
280         return $result;
281     }
283     /**
284      * Reorder this competency.
285      *
286      * Requires tool/lp:competencymanage capability at the system context.
287      *
288      * @param int $id The id of the competency to move.
289      * @return boolean
290      */
291     public static function move_up_competency($id) {
292         static::require_enabled();
293         $current = new competency($id);
295         // First we do a permissions check.
296         require_capability('tool/lp:competencymanage', $current->get_context());
298         $sortorder = $current->get_sortorder();
299         if ($sortorder == 0) {
300             return false;
301         }
303         $sortorder = $sortorder - 1;
304         $current->set_sortorder($sortorder);
306         $filters = array('parentid' => $current->get_parentid(),
307                          'competencyframeworkid' => $current->get_competencyframeworkid(),
308                          'sortorder' => $sortorder);
309         $children = self::list_competencies($filters, 'id');
310         foreach ($children as $needtoswap) {
311             $needtoswap->set_sortorder($sortorder + 1);
312             $needtoswap->update();
313         }
315         // OK - all set.
316         $result = $current->update();
318         return $result;
319     }
321     /**
322      * Move this competency so it sits in a new parent.
323      *
324      * Requires tool/lp:competencymanage capability at the system context.
325      *
326      * @param int $id The id of the competency to move.
327      * @param int $newparentid The new parent id for the competency.
328      * @return boolean
329      */
330     public static function set_parent_competency($id, $newparentid) {
331         global $DB;
332         static::require_enabled();
333         $current = new competency($id);
335         // First we do a permissions check.
336         require_capability('tool/lp:competencymanage', $current->get_context());
337         if ($id == $newparentid) {
338             throw new coding_exception('Can not set a competency as a parent of itself.');
339         } if ($newparentid == $current->get_parentid()) {
340             throw new coding_exception('Can not move a competency to the same location.');
341         }
343         // Some great variable assignment right here.
344         $currentparent = $current->get_parent();
345         $parent = !empty($newparentid) ? new competency($newparentid) : null;
346         $parentpath = !empty($parent) ? $parent->get_path() : '/0/';
348         // We're going to change quite a few things.
349         $transaction = $DB->start_delegated_transaction();
351         // If we are moving a node to a child of itself:
352         // - promote all the child nodes by one level.
353         // - remove the rule on self.
354         // - re-read the parent.
355         $newparents = explode('/', $parentpath);
356         if (in_array($current->get_id(), $newparents)) {
357             $children = competency::get_records(array('parentid' => $current->get_id()), 'id');
358             foreach ($children as $child) {
359                 $child->set_parentid($current->get_parentid());
360                 $child->update();
361             }
363             // Reset the rule on self as our children have changed.
364             $current->reset_rule();
366             // The destination parent is one of our descendants, we need to re-fetch its values (path, parentid).
367             $parent->read();
368         }
370         // Reset the rules of initial parent and destination.
371         if (!empty($currentparent)) {
372             $currentparent->reset_rule();
373             $currentparent->update();
374         }
375         if (!empty($parent)) {
376             $parent->reset_rule();
377             $parent->update();
378         }
380         // Do the actual move.
381         $current->set_parentid($newparentid);
382         $result = $current->update();
384         // All right, let's commit this.
385         $transaction->allow_commit();
387         return $result;
388     }
390     /**
391      * Update the details for a competency.
392      *
393      * Requires tool/lp:competencymanage capability at the system context.
394      *
395      * @param stdClass $record The new details for the competency.
396      *                         Note - must contain an id that points to the competency to update.
397      *
398      * @return boolean
399      */
400     public static function update_competency($record) {
401         static::require_enabled();
402         $competency = new competency($record->id);
404         // First we do a permissions check.
405         require_capability('tool/lp:competencymanage', $competency->get_context());
407         // Some things should not be changed in an update - they should use a more specific method.
408         $record->sortorder = $competency->get_sortorder();
409         $record->parentid = $competency->get_parentid();
410         $record->competencyframeworkid = $competency->get_competencyframeworkid();
412         $competency->from_record($record);
413         require_capability('tool/lp:competencymanage', $competency->get_context());
415         // OK - all set.
416         $result = $competency->update();
418         // Trigger the update event.
419         \tool_lp\event\competency_updated::create_from_competency($competency)->trigger();
421         return $result;
422     }
424     /**
425      * Read a the details for a single competency and return a record.
426      *
427      * Requires tool/lp:competencyview capability at the system context.
428      *
429      * @param int $id The id of the competency to read.
430      * @param bool $includerelated Include related tags or not.
431      * @return stdClass
432      */
433     public static function read_competency($id, $includerelated = false) {
434         static::require_enabled();
435         $competency = new competency($id);
437         // First we do a permissions check.
438         $context = $competency->get_context();
439         if (!has_any_capability(array('tool/lp:competencyview', 'tool/lp:competencymanage'), $context)) {
440              throw new required_capability_exception($context, 'tool/lp:competencyview', 'nopermissions', '');
441         }
443         // OK - all set.
444         if ($includerelated) {
445             $relatedcompetency = new related_competency();
446             if ($related = $relatedcompetency->list_relations($id)) {
447                 $competency->relatedcompetencies = $related;
448             }
449         }
451         return $competency;
452     }
454     /**
455      * Perform a text search based and return all results and their parents.
456      *
457      * Requires tool/lp:competencyview capability at the framework context.
458      *
459      * @param string $textsearch A string to search for.
460      * @param int $competencyframeworkid The id of the framework to limit the search.
461      * @return array of competencies
462      */
463     public static function search_competencies($textsearch, $competencyframeworkid) {
464         static::require_enabled();
465         $framework = new competency_framework($competencyframeworkid);
467         // First we do a permissions check.
468         $context = $framework->get_context();
469         if (!has_any_capability(array('tool/lp:competencyview', 'tool/lp:competencymanage'), $context)) {
470              throw new required_capability_exception($context, 'tool/lp:competencyview', 'nopermissions', '');
471         }
473         // OK - all set.
474         $competencies = competency::search($textsearch, $competencyframeworkid);
475         return $competencies;
476     }
478     /**
479      * Perform a search based on the provided filters and return a paginated list of records.
480      *
481      * Requires tool/lp:competencyview capability at some context.
482      *
483      * @param array $filters A list of filters to apply to the list.
484      * @param string $sort The column to sort on
485      * @param string $order ('ASC' or 'DESC')
486      * @param int $skip Number of records to skip (pagination)
487      * @param int $limit Max of records to return (pagination)
488      * @return array of competencies
489      */
490     public static function list_competencies($filters, $sort = '', $order = 'ASC', $skip = 0, $limit = 0) {
491         static::require_enabled();
492         if (!isset($filters['competencyframeworkid'])) {
493             $context = context_system::instance();
494         } else {
495             $framework = new competency_framework($filters['competencyframeworkid']);
496             $context = $framework->get_context();
497         }
499         // First we do a permissions check.
500         if (!has_any_capability(array('tool/lp:competencyview', 'tool/lp:competencymanage'), $context)) {
501              throw new required_capability_exception($context, 'tool/lp:competencyview', 'nopermissions', '');
502         }
504         // OK - all set.
505         return competency::get_records($filters, $sort, $order, $skip, $limit);
506     }
508     /**
509      * Perform a search based on the provided filters and return a paginated list of records.
510      *
511      * Requires tool/lp:competencyview capability at some context.
512      *
513      * @param array $filters A list of filters to apply to the list.
514      * @return int
515      */
516     public static function count_competencies($filters) {
517         static::require_enabled();
518         if (!isset($filters['competencyframeworkid'])) {
519             $context = context_system::instance();
520         } else {
521             $framework = new competency_framework($filters['competencyframeworkid']);
522             $context = $framework->get_context();
523         }
525         // First we do a permissions check.
526         if (!has_any_capability(array('tool/lp:competencyview', 'tool/lp:competencymanage'), $context)) {
527              throw new required_capability_exception($context, 'tool/lp:competencyview', 'nopermissions', '');
528         }
530         // OK - all set.
531         return competency::count_records($filters);
532     }
534     /**
535      * Create a competency framework from a record containing all the data for the class.
536      *
537      * Requires tool/lp:competencymanage capability at the system context.
538      *
539      * @param stdClass $record Record containing all the data for an instance of the class.
540      * @return competency_framework
541      */
542     public static function create_framework(stdClass $record) {
543         static::require_enabled();
544         $framework = new competency_framework(0, $record);
545         require_capability('tool/lp:competencymanage', $framework->get_context());
547         // Account for different formats of taxonomies.
548         if (isset($record->taxonomies)) {
549             $framework->set_taxonomies($record->taxonomies);
550         }
552         $framework = $framework->create();
554         // Trigger a competency framework created event.
555         \tool_lp\event\competency_framework_created::create_from_framework($framework)->trigger();
557         return $framework;
558     }
560     /**
561      * Duplicate a competency framework by id.
562      *
563      * Requires tool/lp:competencymanage capability at the system context.
564      *
565      * @param int $id The record to duplicate. All competencies associated and related will be duplicated.
566      * @return competency_framework the framework duplicated
567      */
568     public static function duplicate_framework($id) {
569         global $DB;
570         static::require_enabled();
572         $framework = new competency_framework($id);
573         require_capability('tool/lp:competencymanage', $framework->get_context());
574         // Starting transaction.
575         $transaction = $DB->start_delegated_transaction();
577         try {
578             // Get a uniq idnumber based on the origin framework.
579             $idnumber = competency_framework::get_unused_idnumber($framework->get_idnumber());
580             $framework->set_idnumber($idnumber);
581             // Adding the suffix copy to the shortname.
582             $framework->set_shortname(get_string('duplicateditemname', 'tool_lp', $framework->get_shortname()));
583             $framework->set_id(0);
584             $framework = $framework->create();
586             // Array that match the old competencies ids with the new one to use when copying related competencies.
587             $frameworkcompetency = competency::get_framework_tree($id);
588             $matchids = self::duplicate_competency_tree($framework->get_id(), $frameworkcompetency, 0, 0);
590             // Copy the related competencies.
591             $relcomps = related_competency::get_multiple_relations(array_keys($matchids));
593             foreach ($relcomps as $relcomp) {
594                 $compid = $relcomp->get_competencyid();
595                 $relcompid = $relcomp->get_relatedcompetencyid();
596                 if (isset($matchids[$compid]) && isset($matchids[$relcompid])) {
597                     $newcompid = $matchids[$compid]->get_id();
598                     $newrelcompid = $matchids[$relcompid]->get_id();
599                     if ($newcompid < $newrelcompid) {
600                         $relcomp->set_competencyid($newcompid);
601                         $relcomp->set_relatedcompetencyid($newrelcompid);
602                     } else {
603                         $relcomp->set_competencyid($newrelcompid);
604                         $relcomp->set_relatedcompetencyid($newcompid);
605                     }
606                     $relcomp->set_id(0);
607                     $relcomp->create();
608                 } else {
609                     // Debugging message when there is no match found.
610                     debugging('related competency id not found');
611                 }
612             }
614             // Setting rules on duplicated competencies.
615             self::migrate_competency_tree_rules($frameworkcompetency, $matchids);
617             $transaction->allow_commit();
619         } catch (\Exception $e) {
620             $transaction->rollback($e);
621         }
623         // Trigger a competency framework created event.
624         \tool_lp\event\competency_framework_created::create_from_framework($framework)->trigger();
626         return $framework;
627     }
629     /**
630      * Delete a competency framework by id.
631      *
632      * Requires tool/lp:competencymanage capability at the system context.
633      *
634      * @param int $id The record to delete. This will delete alot of related data - you better be sure.
635      * @return boolean
636      */
637     public static function delete_framework($id) {
638         global $DB;
639         static::require_enabled();
640         $framework = new competency_framework($id);
641         require_capability('tool/lp:competencymanage', $framework->get_context());
643         $events = array();
644         $competenciesid = competency::get_ids_by_frameworkid($id);
645         $contextid = $framework->get_contextid();
646         if (!competency::can_all_be_deleted($competenciesid)) {
647             return false;
648         }
649         $transaction = $DB->start_delegated_transaction();
650         try {
651             if (!empty($competenciesid)) {
652                 // Delete competencies.
653                 competency::delete_by_frameworkid($id);
655                 // Delete the related competencies.
656                 related_competency::delete_multiple_relations($competenciesid);
658                 // Delete the evidences for competencies.
659                 user_evidence_competency::delete_by_competencyids($competenciesid);
660             }
662             // Create a competency framework deleted event.
663             $event = \tool_lp\event\competency_framework_deleted::create_from_framework($framework);
664             $result = $framework->delete();
666             // Register the deleted events competencies.
667             $events = \tool_lp\event\competency_deleted::create_multiple_from_competencyids($competenciesid, $contextid);
669         } catch (\Exception $e) {
670             $transaction->rollback($e);
671         }
673         // Commit the transaction.
674         $transaction->allow_commit();
676         // If all operations are successfull then trigger the delete event.
677         $event->trigger();
679         // Trigger deleted event competencies.
680         foreach ($events as $event) {
681             $event->trigger();
682         }
684         return $result;
685     }
687     /**
688      * Update the details for a competency framework.
689      *
690      * Requires tool/lp:competencymanage capability at the system context.
691      *
692      * @param stdClass $record The new details for the framework. Note - must contain an id that points to the framework to update.
693      * @return boolean
694      */
695     public static function update_framework($record) {
696         static::require_enabled();
697         $framework = new competency_framework($record->id);
699         // Check the permissions before update.
700         require_capability('tool/lp:competencymanage', $framework->get_context());
702         // Account for different formats of taxonomies.
703         $framework->from_record($record);
704         if (isset($record->taxonomies)) {
705             $framework->set_taxonomies($record->taxonomies);
706         }
708         // Trigger a competency framework updated event.
709         \tool_lp\event\competency_framework_updated::create_from_framework($framework)->trigger();
711         return $framework->update();
712     }
714     /**
715      * Read a the details for a single competency framework and return a record.
716      *
717      * Requires tool/lp:competencyview capability at the system context.
718      *
719      * @param int $id The id of the framework to read.
720      * @return competency_framework
721      */
722     public static function read_framework($id) {
723         static::require_enabled();
724         $framework = new competency_framework($id);
725         if (!has_any_capability(array('tool/lp:competencyview', 'tool/lp:competencymanage'), $framework->get_context())) {
726              throw new required_capability_exception($framework->get_context(), 'tool/lp:competencyview', 'nopermissions', '');
727         }
728         return $framework;
729     }
731     /**
732      * Logg the competency framework viewed event.
733      *
734      * @param competency_framework|int $frameworkorid The competency_framework object or competency framework id
735      * @return bool
736      */
737     public static function competency_framework_viewed($frameworkorid) {
738         static::require_enabled();
739         $framework = $frameworkorid;
740         if (!is_object($framework)) {
741             $framework = new competency_framework($framework);
742         }
743         if (!has_any_capability(array('tool/lp:competencyview', 'tool/lp:competencymanage'), $framework->get_context())) {
744              throw new required_capability_exception($framework->get_context(), 'tool/lp:competencyview', 'nopermissions', '');
745         }
746         \tool_lp\event\competency_framework_viewed::create_from_framework($framework)->trigger();
747         return true;
748     }
750     /**
751      * Logg the competency viewed event.
752      *
753      * @param competency|int $competencyorid The competency object or competency id
754      * @return bool
755      */
756     public static function competency_viewed($competencyorid) {
757         static::require_enabled();
758         $competency = $competencyorid;
759         if (!is_object($competency)) {
760             $competency = new competency($competency);
761         }
763         if (!has_any_capability(array('tool/lp:competencyview', 'tool/lp:competencymanage'), $competency->get_context())) {
764              throw new required_capability_exception($competency->get_context(), 'tool/lp:competencyview', 'nopermissions', '');
765         }
767         \tool_lp\event\competency_viewed::create_from_competency($competency)->trigger();
768         return true;
769     }
771     /**
772      * Perform a search based on the provided filters and return a paginated list of records.
773      *
774      * Requires tool/lp:competencyview capability at the system context.
775      *
776      * @param string $sort The column to sort on
777      * @param string $order ('ASC' or 'DESC')
778      * @param int $skip Number of records to skip (pagination)
779      * @param int $limit Max of records to return (pagination)
780      * @param context $context The parent context of the frameworks.
781      * @param string $includes Defines what other contexts to fetch frameworks from.
782      *                         Accepted values are:
783      *                          - children: All descendants
784      *                          - parents: All parents, grand parents, etc...
785      *                          - self: Context passed only.
786      * @param bool $onlyvisible If true return only visible frameworks
787      * @param string $query A string to use to filter down the frameworks.
788      * @return array of competency_framework
789      */
790     public static function list_frameworks($sort, $order, $skip, $limit, $context, $includes = 'children',
791             $onlyvisible = false, $query = '') {
792         global $DB;
793         static::require_enabled();
795         // Get all the relevant contexts.
796         $contexts = self::get_related_contexts($context, $includes,
797             array('tool/lp:competencyview', 'tool/lp:competencymanage'));
799         if (empty($contexts)) {
800             throw new required_capability_exception($context, 'tool/lp:competencyview', 'nopermissions', '');
801         }
803         // OK - all set.
804         list($insql, $inparams) = $DB->get_in_or_equal(array_keys($contexts), SQL_PARAMS_NAMED);
805         $select = "contextid $insql";
806         if ($onlyvisible) {
807             $select .= " AND visible = :visible";
808             $inparams['visible'] = 1;
809         }
811         if (!empty($query) || is_numeric($query)) {
812             $sqlnamelike = $DB->sql_like('shortname', ':namelike', false);
813             $sqlidnlike = $DB->sql_like('idnumber', ':idnlike', false);
815             $select .= " AND ($sqlnamelike OR $sqlidnlike) ";
816             $inparams['namelike'] = '%' . $DB->sql_like_escape($query) . '%';
817             $inparams['idnlike'] = '%' . $DB->sql_like_escape($query) . '%';
818         }
820         return competency_framework::get_records_select($select, $inparams, $sort . ' ' . $order, '*', $skip, $limit);
821     }
823     /**
824      * Perform a search based on the provided filters and return a paginated list of records.
825      *
826      * Requires tool/lp:competencyview capability at the system context.
827      *
828      * @param context $context The parent context of the frameworks.
829      * @param string $includes Defines what other contexts to fetch frameworks from.
830      *                         Accepted values are:
831      *                          - children: All descendants
832      *                          - parents: All parents, grand parents, etc...
833      *                          - self: Context passed only.
834      * @return int
835      */
836     public static function count_frameworks($context, $includes) {
837         global $DB;
838         static::require_enabled();
840         // Get all the relevant contexts.
841         $contexts = self::get_related_contexts($context, $includes,
842             array('tool/lp:competencyview', 'tool/lp:competencymanage'));
844         if (empty($contexts)) {
845             throw new required_capability_exception($context, 'tool/lp:competencyview', 'nopermissions', '');
846         }
848         // OK - all set.
849         list($insql, $inparams) = $DB->get_in_or_equal(array_keys($contexts), SQL_PARAMS_NAMED);
850         return competency_framework::count_records_select("contextid $insql", $inparams);
851     }
853     /**
854      * Fetches all the relevant contexts.
855      *
856      * Note: This currently only supports system, category and user contexts. However user contexts
857      * behave a bit differently and will fallback on the system context. This is what makes the most
858      * sense because a user context does not have descendants, and only has system as a parent.
859      *
860      * @param context $context The context to start from.
861      * @param string $includes Defines what other contexts to find.
862      *                         Accepted values are:
863      *                          - children: All descendants
864      *                          - parents: All parents, grand parents, etc...
865      *                          - self: Context passed only.
866      * @param array $hasanycapability Array of capabilities passed to {@link has_any_capability()} in each context.
867      * @return context[] An array of contexts where keys are context IDs.
868      */
869     public static function get_related_contexts($context, $includes, array $hasanycapability = null) {
870         global $DB;
871         static::require_enabled();
873         if (!in_array($includes, array('children', 'parents', 'self'))) {
874             throw new coding_exception('Invalid parameter value for \'includes\'.');
875         }
877         // If context user swap it for the context_system.
878         if ($context->contextlevel == CONTEXT_USER) {
879             $context = context_system::instance();
880         }
882         $contexts = array($context->id => $context);
884         if ($includes == 'children') {
885             $params = array('coursecatlevel' => CONTEXT_COURSECAT, 'path' => $context->path . '/%');
886             $pathlike = $DB->sql_like('path', ':path');
887             $sql = "contextlevel = :coursecatlevel AND $pathlike";
888             $rs = $DB->get_recordset_select('context', $sql, $params);
889             foreach ($rs as $record) {
890                 $ctxid = $record->id;
891                 context_helper::preload_from_record($record);
892                 $contexts[$ctxid] = context::instance_by_id($ctxid);
893             }
894             $rs->close();
896         } else if ($includes == 'parents') {
897             $children = $context->get_parent_contexts();
898             foreach ($children as $ctx) {
899                 $contexts[$ctx->id] = $ctx;
900             }
901         }
903         // Filter according to the capabilities required.
904         if (!empty($hasanycapability)) {
905             foreach ($contexts as $key => $ctx) {
906                 if (!has_any_capability($hasanycapability, $ctx)) {
907                     unset($contexts[$key]);
908                 }
909             }
910         }
912         return $contexts;
913     }
915     /**
916      * Count all the courses using a competency.
917      *
918      * @param int $competencyid The id of the competency to check.
919      * @return int
920      */
921     public static function count_courses_using_competency($competencyid) {
922         static::require_enabled();
924         // OK - all set.
925         $courses = course_competency::list_courses_min($competencyid);
926         $count = 0;
928         // Now check permissions on each course.
929         foreach ($courses as $course) {
930             if (!self::validate_course($course, false)) {
931                 continue;
932             }
934             $context = context_course::instance($course->id);
935             $capabilities = array('tool/lp:coursecompetencyview', 'tool/lp:coursecompetencymanage');
936             if (!has_any_capability($capabilities, $context)) {
937                 continue;
938             }
940             $count++;
941         }
943         return $count;
944     }
946     /**
947      * List all the courses modules using a competency in a course.
948      *
949      * @param int $competencyid The id of the competency to check.
950      * @param int $courseid The id of the course to check.
951      * @return array[int] Array of course modules ids.
952      */
953     public static function list_course_modules_using_competency($competencyid, $courseid) {
954         static::require_enabled();
956         $result = array();
957         self::validate_course($courseid);
959         $coursecontext = context_course::instance($courseid);
961         // We will not check each module - course permissions should be enough.
962         $capabilities = array('tool/lp:coursecompetencyview', 'tool/lp:coursecompetencymanage');
963         if (!has_any_capability($capabilities, $coursecontext)) {
964             throw new required_capability_exception($coursecontext, 'tool/lp:coursecompetencyview', 'nopermissions', '');
965         }
967         $cmlist = course_module_competency::list_course_modules($competencyid, $courseid);
968         foreach ($cmlist as $cmid) {
969             if (self::validate_course_module($cmid, false)) {
970                 array_push($result, $cmid);
971             }
972         }
974         return $result;
975     }
977     /**
978      * List all the competencies linked to a course module.
979      *
980      * @param mixed $cmorid The course module, or its ID.
981      * @return array[competency] Array of competency records.
982      */
983     public static function list_course_module_competencies_in_course_module($cmorid) {
984         static::require_enabled();
985         $cm = $cmorid;
986         if (!is_object($cmorid)) {
987             $cm = get_coursemodule_from_id('', $cmorid, 0, true, MUST_EXIST);
988         }
990         // Check the user have access to the course module.
991         self::validate_course_module($cm);
992         $context = context_module::instance($cm->id);
994         $capabilities = array('tool/lp:coursecompetencyview', 'tool/lp:coursecompetencymanage');
995         if (!has_any_capability($capabilities, $context)) {
996             throw new required_capability_exception($context, 'tool/lp:coursecompetencyview', 'nopermissions', '');
997         }
999         $result = array();
1001         $cmclist = course_module_competency::list_course_module_competencies($cm->id);
1002         foreach ($cmclist as $id => $cmc) {
1003             array_push($result, $cmc);
1004         }
1006         return $result;
1007     }
1009     /**
1010      * List all the courses using a competency.
1011      *
1012      * @param int $competencyid The id of the competency to check.
1013      * @return array[stdClass] Array of stdClass containing id and shortname.
1014      */
1015     public static function list_courses_using_competency($competencyid) {
1016         static::require_enabled();
1018         // OK - all set.
1019         $courses = course_competency::list_courses($competencyid);
1020         $result = array();
1022         // Now check permissions on each course.
1023         foreach ($courses as $id => $course) {
1024             $context = context_course::instance($course->id);
1025             $capabilities = array('tool/lp:coursecompetencyview', 'tool/lp:coursecompetencymanage');
1026             if (!has_any_capability($capabilities, $context)) {
1027                 unset($courses[$id]);
1028                 continue;
1029             }
1030             if (!self::validate_course($course, false)) {
1031                 unset($courses[$id]);
1032                 continue;
1033             }
1034             array_push($result, $course);
1035         }
1037         return $result;
1038     }
1040     /**
1041      * Count the proficient competencies in a course for one user.
1042      *
1043      * @param int $courseid The id of the course to check.
1044      * @param int $userid The id of the user to check.
1045      * @return int
1046      */
1047     public static function count_proficient_competencies_in_course_for_user($courseid, $userid) {
1048         static::require_enabled();
1049         // Check the user have access to the course.
1050         self::validate_course($courseid);
1052         // First we do a permissions check.
1053         $context = context_course::instance($courseid);
1055         $capabilities = array('tool/lp:coursecompetencyview', 'tool/lp:coursecompetencymanage');
1056         if (!has_any_capability($capabilities, $context)) {
1057              throw new required_capability_exception($context, 'tool/lp:coursecompetencyview', 'nopermissions', '');
1058         }
1060         // OK - all set.
1061         return user_competency_course::count_proficient_competencies($courseid, $userid);
1062     }
1064     /**
1065      * Count all the competencies in a course.
1066      *
1067      * @param int $courseid The id of the course to check.
1068      * @return int
1069      */
1070     public static function count_competencies_in_course($courseid) {
1071         static::require_enabled();
1072         // Check the user have access to the course.
1073         self::validate_course($courseid);
1075         // First we do a permissions check.
1076         $context = context_course::instance($courseid);
1078         $capabilities = array('tool/lp:coursecompetencyview', 'tool/lp:coursecompetencymanage');
1079         if (!has_any_capability($capabilities, $context)) {
1080              throw new required_capability_exception($context, 'tool/lp:coursecompetencyview', 'nopermissions', '');
1081         }
1083         // OK - all set.
1084         return course_competency::count_competencies($courseid);
1085     }
1087     /**
1088      * List the competencies associated to a course.
1089      *
1090      * @param mixed $courseorid The course, or its ID.
1091      * @return array( array(
1092      *                   'competency' => \tool_lp\competency,
1093      *                   'coursecompetency' => \tool_lp\course_competency
1094      *              ))
1095      */
1096     public static function list_course_competencies($courseorid) {
1097         static::require_enabled();
1098         $course = $courseorid;
1099         if (!is_object($courseorid)) {
1100             $course = get_course($courseorid);
1101         }
1103         // Check the user have access to the course.
1104         self::validate_course($course);
1105         $context = context_course::instance($course->id);
1107         $capabilities = array('tool/lp:coursecompetencyview', 'tool/lp:coursecompetencymanage');
1108         if (!has_any_capability($capabilities, $context)) {
1109             throw new required_capability_exception($context, 'tool/lp:coursecompetencyview', 'nopermissions', '');
1110         }
1112         $result = array();
1114         // TODO We could improve the performance of this into one single query.
1115         $coursecompetencies = course_competency::list_course_competencies($course->id);
1116         $competencies = course_competency::list_competencies($course->id);
1118         // Build the return values.
1119         foreach ($coursecompetencies as $key => $coursecompetency) {
1120             $result[] = array(
1121                 'competency' => $competencies[$coursecompetency->get_competencyid()],
1122                 'coursecompetency' => $coursecompetency
1123             );
1124         }
1126         return $result;
1127     }
1129     /**
1130      * Get a user competency.
1131      *
1132      * @param int $userid The user ID.
1133      * @param int $competencyid The competency ID.
1134      * @return user_competency
1135      */
1136     public static function get_user_competency($userid, $competencyid) {
1137         static::require_enabled();
1138         $existing = user_competency::get_multiple($userid, array($competencyid));
1139         $uc = array_pop($existing);
1141         if (!$uc) {
1142             $uc = user_competency::create_relation($userid, $competencyid);
1143             $uc->create();
1144         }
1146         if (!$uc->can_read()) {
1147             throw new required_capability_exception($uc->get_context(), 'tool/lp:usercompetencyview', 'nopermissions', '');
1148         }
1149         return $uc;
1150     }
1152     /**
1153      * Get a user competency by ID.
1154      *
1155      * @param int $usercompetencyid The user competency ID.
1156      * @return user_competency
1157      */
1158     public static function get_user_competency_by_id($usercompetencyid) {
1159         static::require_enabled();
1160         $uc = new user_competency($usercompetencyid);
1161         if (!$uc->can_read()) {
1162             throw new required_capability_exception($uc->get_context(), 'tool/lp:usercompetencyview', 'nopermissions', '');
1163         }
1164         return $uc;
1165     }
1167     /**
1168      * List the competencies associated to a course module.
1169      *
1170      * @param mixed $cmorid The course module, or its ID.
1171      * @return array( array(
1172      *                   'competency' => \tool_lp\competency,
1173      *                   'coursemodulecompetency' => \tool_lp\course_module_competency
1174      *              ))
1175      */
1176     public static function list_course_module_competencies($cmorid) {
1177         static::require_enabled();
1178         $cm = $cmorid;
1179         if (!is_object($cmorid)) {
1180             $cm = get_coursemodule_from_id('', $cmorid, 0, true, MUST_EXIST);
1181         }
1183         // Check the user have access to the course module.
1184         self::validate_course_module($cm);
1185         $context = context_module::instance($cm->id);
1187         $capabilities = array('tool/lp:coursecompetencyview', 'tool/lp:coursecompetencymanage');
1188         if (!has_any_capability($capabilities, $context)) {
1189             throw new required_capability_exception($context, 'tool/lp:coursecompetencyview', 'nopermissions', '');
1190         }
1192         $result = array();
1194         // TODO We could improve the performance of this into one single query.
1195         $coursemodulecompetencies = course_competency::list_course_module_competencies($cm->id);
1196         $competencies = course_module_competency::list_competencies($cm->id);
1198         // Build the return values.
1199         foreach ($coursemodulecompetencies as $key => $coursemodulecompetency) {
1200             $result[] = array(
1201                 'competency' => $competencies[$coursemodulecompetency->get_competencyid()],
1202                 'coursemodulecompetency' => $coursemodulecompetency
1203             );
1204         }
1206         return $result;
1207     }
1209     /**
1210      * Get a user competency in a course.
1211      *
1212      * @param int $courseid The id of the course to check.
1213      * @param int $userid The id of the course to check.
1214      * @param int $competencyid The id of the competency.
1215      * @return user_competency_course
1216      */
1217     public static function get_user_competency_in_course($courseid, $userid, $competencyid) {
1218         static::require_enabled();
1219         // First we do a permissions check.
1220         $context = context_course::instance($courseid);
1222         $capabilities = array('tool/lp:coursecompetencyview', 'tool/lp:coursecompetencymanage');
1223         if (!has_any_capability($capabilities, $context)) {
1224             throw new required_capability_exception($context, 'tool/lp:coursecompetencyview', 'nopermissions', '');
1225         } else if (!user_competency::can_read_user_in_course($userid, $courseid)) {
1226             throw new required_capability_exception($context, 'tool/lp:usercompetencyview', 'nopermissions', '');
1227         }
1229         // This will throw an exception if the competency does not belong to the course.
1230         $competency = course_competency::get_competency($courseid, $competencyid);
1232         $params = array('courseid' => $courseid, 'userid' => $userid, 'competencyid' => $competencyid);
1233         $exists = user_competency_course::get_record($params);
1234         // Create missing.
1235         if ($exists) {
1236             $ucc = $exists;
1237         } else {
1238             $ucc = user_competency_course::create_relation($userid, $competency->get_id(), $courseid);
1239             $ucc->create();
1240         }
1242         return $ucc;
1243     }
1245     /**
1246      * List all the user competencies in a course.
1247      *
1248      * @param int $courseid The id of the course to check.
1249      * @param int $userid The id of the course to check.
1250      * @return array of user_competency_course objects
1251      */
1252     public static function list_user_competencies_in_course($courseid, $userid) {
1253         static::require_enabled();
1254         // First we do a permissions check.
1255         $context = context_course::instance($courseid);
1256         $onlyvisible = 1;
1258         $capabilities = array('tool/lp:coursecompetencyview', 'tool/lp:coursecompetencymanage');
1259         if (!has_any_capability($capabilities, $context)) {
1260             throw new required_capability_exception($context, 'tool/lp:coursecompetencyview', 'nopermissions', '');
1261         } else if (!user_competency::can_read_user_in_course($userid, $courseid)) {
1262             throw new required_capability_exception($context, 'tool/lp:usercompetencyview', 'nopermissions', '');
1263         }
1265         // OK - all set.
1266         $competencylist = course_competency::list_competencies($courseid, false);
1268         $existing = user_competency_course::get_multiple($userid, $courseid, $competencylist);
1269         // Create missing.
1270         $orderedusercompetencycourses = array();
1272         $somemissing = false;
1273         foreach ($competencylist as $coursecompetency) {
1274             $found = false;
1275             foreach ($existing as $usercompetencycourse) {
1276                 if ($usercompetencycourse->get_competencyid() == $coursecompetency->get_id()) {
1277                     $found = true;
1278                     $orderedusercompetencycourses[$usercompetencycourse->get_id()] = $usercompetencycourse;
1279                     break;
1280                 }
1281             }
1282             if (!$found) {
1283                 $ucc = user_competency_course::create_relation($userid, $coursecompetency->get_id(), $courseid);
1284                 $ucc->create();
1285                 $orderedusercompetencycourses[$ucc->get_id()] = $ucc;
1286             }
1287         }
1289         return $orderedusercompetencycourses;
1290     }
1292     /**
1293      * List the user competencies to review.
1294      *
1295      * The method returns values in this format:
1296      *
1297      * array(
1298      *     'competencies' => array(
1299      *         (stdClass)(
1300      *             'usercompetency' => (user_competency),
1301      *             'competency' => (competency),
1302      *             'user' => (user)
1303      *         )
1304      *     ),
1305      *     'count' => (int)
1306      * )
1307      *
1308      * @param int $skip The number of records to skip.
1309      * @param int $limit The number of results to return.
1310      * @param int $userid The user we're getting the competencies to review for.
1311      * @return array Containing the keys 'count', and 'competencies'. The 'competencies' key contains an object
1312      *               which contains 'competency', 'usercompetency' and 'user'.
1313      */
1314     public static function list_user_competencies_to_review($skip = 0, $limit = 50, $userid = null) {
1315         global $DB, $USER;
1316         static::require_enabled();
1317         if ($userid === null) {
1318             $userid = $USER->id;
1319         }
1321         $capability = 'tool/lp:usercompetencyreview';
1322         $ucfields = user_competency::get_sql_fields('uc');
1323         $compfields = competency::get_sql_fields('c');
1324         $usercols = array('id') + get_user_fieldnames();
1325         $userfields = array();
1326         foreach ($usercols as $field) {
1327             $userfields[] = "u." . $field . " AS usr_" . $field;
1328         }
1329         $userfields = implode(',', $userfields);
1331         $select = "SELECT $ucfields, $compfields, $userfields";
1332         $countselect = "SELECT COUNT('x')";
1333         $sql = "  FROM {" . user_competency::TABLE . "} uc
1334                   JOIN {" . competency::TABLE . "} c
1335                     ON c.id = uc.competencyid
1336                   JOIN {user} u
1337                     ON u.id = uc.userid
1338                  WHERE (uc.status = :waitingforreview
1339                     OR (uc.status = :inreview AND uc.reviewerid = :reviewerid))";
1340         $ordersql = " ORDER BY c.shortname ASC";
1341         $params = array(
1342             'inreview' => user_competency::STATUS_IN_REVIEW,
1343             'reviewerid' => $userid,
1344             'waitingforreview' => user_competency::STATUS_WAITING_FOR_REVIEW,
1345         );
1346         $countsql = $countselect . $sql;
1348         // Primary check to avoid the hard work of getting the users in which the user has permission.
1349         $count = $DB->count_records_sql($countselect . $sql, $params);
1350         if ($count < 1) {
1351             return array('count' => 0, 'competencies' => array());
1352         }
1354         // TODO MDL-52243 Use core function.
1355         list($insql, $inparams) = external::filter_users_with_capability_on_user_context_sql(
1356             $capability, $userid, SQL_PARAMS_NAMED);
1357         $params += $inparams;
1358         $countsql = $countselect . $sql . " AND uc.userid $insql";
1359         $getsql = $select . $sql . " AND uc.userid $insql " . $ordersql;
1361         // Extracting the results.
1362         $competencies = array();
1363         $records = $DB->get_recordset_sql($getsql, $params, $skip, $limit);
1364         foreach ($records as $record) {
1365             $objects = (object) array(
1366                 'usercompetency' => new user_competency(0, user_competency::extract_record($record)),
1367                 'competency' => new competency(0, competency::extract_record($record)),
1368                 'user' => persistent::extract_record($record, 'usr_'),
1369             );
1370             $competencies[] = $objects;
1371         }
1372         $records->close();
1374         return array(
1375             'count' => $DB->count_records_sql($countsql, $params),
1376             'competencies' => $competencies
1377         );
1378     }
1380     /**
1381      * Add a competency to this course module.
1382      *
1383      * @param mixed $cmorid The course module, or id of the course module
1384      * @param int $competencyid The id of the competency
1385      * @return bool
1386      */
1387     public static function add_competency_to_course_module($cmorid, $competencyid) {
1388         static::require_enabled();
1389         $cm = $cmorid;
1390         if (!is_object($cmorid)) {
1391             $cm = get_coursemodule_from_id('', $cmorid, 0, true, MUST_EXIST);
1392         }
1394         // Check the user have access to the course module.
1395         self::validate_course_module($cm);
1397         // First we do a permissions check.
1398         $context = context_module::instance($cm->id);
1400         require_capability('tool/lp:coursecompetencymanage', $context);
1402         // Check that the competency belongs to the course.
1403         $exists = course_competency::get_records(array('courseid' => $cm->course, 'competencyid' => $competencyid));
1404         if (!$exists) {
1405             throw new coding_exception('Cannot add a competency to a module if it does not belong to the course');
1406         }
1408         $record = new stdClass();
1409         $record->cmid = $cm->id;
1410         $record->competencyid = $competencyid;
1412         $coursemodulecompetency = new course_module_competency();
1413         $exists = $coursemodulecompetency->get_records(array('cmid' => $cm->id, 'competencyid' => $competencyid));
1414         if (!$exists) {
1415             $coursemodulecompetency->from_record($record);
1416             if ($coursemodulecompetency->create()) {
1417                 return true;
1418             }
1419         }
1420         return false;
1421     }
1423     /**
1424      * Remove a competency from this course module.
1425      *
1426      * @param mixed $cmorid The course module, or id of the course module
1427      * @param int $competencyid The id of the competency
1428      * @return bool
1429      */
1430     public static function remove_competency_from_course_module($cmorid, $competencyid) {
1431         static::require_enabled();
1432         $cm = $cmorid;
1433         if (!is_object($cmorid)) {
1434             $cm = get_coursemodule_from_id('', $cmorid, 0, true, MUST_EXIST);
1435         }
1436         // Check the user have access to the course module.
1437         self::validate_course_module($cm);
1439         // First we do a permissions check.
1440         $context = context_module::instance($cm->id);
1442         require_capability('tool/lp:coursecompetencymanage', $context);
1444         $record = new stdClass();
1445         $record->cmid = $cm->id;
1446         $record->competencyid = $competencyid;
1448         $competency = new competency($competencyid);
1449         $exists = course_module_competency::get_record(array('cmid' => $cm->id, 'competencyid' => $competencyid));
1450         if ($exists) {
1451             return $exists->delete();
1452         }
1453         return false;
1454     }
1456     /**
1457      * Move the course module competency up or down in the display list.
1458      *
1459      * Requires tool/lp:coursecompetencymanage capability at the course module context.
1460      *
1461      * @param mixed $cmorid The course module, or id of the course module
1462      * @param int $competencyidfrom The id of the competency we are moving.
1463      * @param int $competencyidto The id of the competency we are moving to.
1464      * @return boolean
1465      */
1466     public static function reorder_course_module_competency($cmorid, $competencyidfrom, $competencyidto) {
1467         static::require_enabled();
1468         $cm = $cmorid;
1469         if (!is_object($cmorid)) {
1470             $cm = get_coursemodule_from_id('', $cmorid, 0, true, MUST_EXIST);
1471         }
1472         // Check the user have access to the course module.
1473         self::validate_course_module($cm);
1475         // First we do a permissions check.
1476         $context = context_module::instance($cm->id);
1478         require_capability('tool/lp:coursecompetencymanage', $context);
1480         $down = true;
1481         $matches = course_module_competency::get_records(array('cmid' => $cm->id, 'competencyid' => $competencyidfrom));
1482         if (count($matches) == 0) {
1483              throw new coding_exception('The link does not exist');
1484         }
1486         $competencyfrom = array_pop($matches);
1487         $matches = course_module_competency::get_records(array('cmid' => $cm->id, 'competencyid' => $competencyidto));
1488         if (count($matches) == 0) {
1489              throw new coding_exception('The link does not exist');
1490         }
1492         $competencyto = array_pop($matches);
1494         $all = course_module_competency::get_records(array('cmid' => $cm->id), 'sortorder', 'ASC', 0, 0);
1496         if ($competencyfrom->get_sortorder() > $competencyto->get_sortorder()) {
1497             // We are moving up, so put it before the "to" item.
1498             $down = false;
1499         }
1501         foreach ($all as $id => $coursemodulecompetency) {
1502             $sort = $coursemodulecompetency->get_sortorder();
1503             if ($down && $sort > $competencyfrom->get_sortorder() && $sort <= $competencyto->get_sortorder()) {
1504                 $coursemodulecompetency->set_sortorder($coursemodulecompetency->get_sortorder() - 1);
1505                 $coursemodulecompetency->update();
1506             } else if (!$down && $sort >= $competencyto->get_sortorder() && $sort < $competencyfrom->get_sortorder()) {
1507                 $coursemodulecompetency->set_sortorder($coursemodulecompetency->get_sortorder() + 1);
1508                 $coursemodulecompetency->update();
1509             }
1510         }
1511         $competencyfrom->set_sortorder($competencyto->get_sortorder());
1512         return $competencyfrom->update();
1513     }
1515     /**
1516      * Update ruleoutcome value for a course module competency.
1517      *
1518      * @param int|course_module_competency $coursemodulecompetencyorid The course_module_competency, or its ID.
1519      * @param int $ruleoutcome The value of ruleoutcome.
1520      * @return bool True on success.
1521      */
1522     public static function set_course_module_competency_ruleoutcome($coursemodulecompetencyorid, $ruleoutcome) {
1523         static::require_enabled();
1524         $coursemodulecompetency = $coursemodulecompetencyorid;
1525         if (!is_object($coursemodulecompetency)) {
1526             $coursemodulecompetency = new course_module_competency($coursemodulecompetencyorid);
1527         }
1529         $cm = get_coursemodule_from_id('', $coursemodulecompetency->get_cmid(), 0, true, MUST_EXIST);
1531         self::validate_course_module($cm);
1532         $context = context_module::instance($cm->id);
1534         require_capability('tool/lp:coursecompetencymanage', $context);
1536         $coursemodulecompetency->set_ruleoutcome($ruleoutcome);
1537         return $coursemodulecompetency->update();
1538     }
1540     /**
1541      * Add a competency to this course.
1542      *
1543      * @param int $courseid The id of the course
1544      * @param int $competencyid The id of the competency
1545      * @return bool
1546      */
1547     public static function add_competency_to_course($courseid, $competencyid) {
1548         static::require_enabled();
1549         // Check the user have access to the course.
1550         self::validate_course($courseid);
1552         // First we do a permissions check.
1553         $context = context_course::instance($courseid);
1555         require_capability('tool/lp:coursecompetencymanage', $context);
1557         $record = new stdClass();
1558         $record->courseid = $courseid;
1559         $record->competencyid = $competencyid;
1561         $competency = new competency($competencyid);
1563         // Can not add a competency that belong to a hidden framework.
1564         if ($competency->get_framework()->get_visible() == false) {
1565             throw new coding_exception('A competency belonging to hidden framework can not be linked to course');
1566         }
1568         $coursecompetency = new course_competency();
1569         $exists = $coursecompetency->get_records(array('courseid' => $courseid, 'competencyid' => $competencyid));
1570         if (!$exists) {
1571             $coursecompetency->from_record($record);
1572             if ($coursecompetency->create()) {
1573                 return true;
1574             }
1575         }
1576         return false;
1577     }
1579     /**
1580      * Remove a competency from this course.
1581      *
1582      * @param int $courseid The id of the course
1583      * @param int $competencyid The id of the competency
1584      * @return bool
1585      */
1586     public static function remove_competency_from_course($courseid, $competencyid) {
1587         static::require_enabled();
1588         // Check the user have access to the course.
1589         self::validate_course($courseid);
1591         // First we do a permissions check.
1592         $context = context_course::instance($courseid);
1594         require_capability('tool/lp:coursecompetencymanage', $context);
1596         $record = new stdClass();
1597         $record->courseid = $courseid;
1598         $record->competencyid = $competencyid;
1600         $competency = new competency($competencyid);
1601         $coursecompetency = new course_competency();
1602         $exists = $coursecompetency->get_record(array('courseid' => $courseid, 'competencyid' => $competencyid));
1603         if ($exists) {
1604             // Delete all course_module_competencies for this competency in this course.
1605             $cmcs = course_module_competency::list_course_module_competencies($competencyid, $courseid);
1606             foreach ($cmcs as $cmc) {
1607                 $cmc->delete();
1608             }
1609             return $exists->delete();
1610         }
1611         return false;
1612     }
1614     /**
1615      * Move the course competency up or down in the display list.
1616      *
1617      * Requires tool/lp:coursecompetencymanage capability at the course context.
1618      *
1619      * @param int $courseid The course
1620      * @param int $competencyidfrom The id of the competency we are moving.
1621      * @param int $competencyidto The id of the competency we are moving to.
1622      * @return boolean
1623      */
1624     public static function reorder_course_competency($courseid, $competencyidfrom, $competencyidto) {
1625         static::require_enabled();
1626         // Check the user have access to the course.
1627         self::validate_course($courseid);
1629         // First we do a permissions check.
1630         $context = context_course::instance($courseid);
1632         require_capability('tool/lp:coursecompetencymanage', $context);
1634         $down = true;
1635         $coursecompetency = new course_competency();
1636         $matches = $coursecompetency->get_records(array('courseid' => $courseid, 'competencyid' => $competencyidfrom));
1637         if (count($matches) == 0) {
1638              throw new coding_exception('The link does not exist');
1639         }
1641         $competencyfrom = array_pop($matches);
1642         $matches = $coursecompetency->get_records(array('courseid' => $courseid, 'competencyid' => $competencyidto));
1643         if (count($matches) == 0) {
1644              throw new coding_exception('The link does not exist');
1645         }
1647         $competencyto = array_pop($matches);
1649         $all = $coursecompetency->get_records(array('courseid' => $courseid), 'sortorder', 'ASC', 0, 0);
1651         if ($competencyfrom->get_sortorder() > $competencyto->get_sortorder()) {
1652             // We are moving up, so put it before the "to" item.
1653             $down = false;
1654         }
1656         foreach ($all as $id => $coursecompetency) {
1657             $sort = $coursecompetency->get_sortorder();
1658             if ($down && $sort > $competencyfrom->get_sortorder() && $sort <= $competencyto->get_sortorder()) {
1659                 $coursecompetency->set_sortorder($coursecompetency->get_sortorder() - 1);
1660                 $coursecompetency->update();
1661             } else if (!$down && $sort >= $competencyto->get_sortorder() && $sort < $competencyfrom->get_sortorder()) {
1662                 $coursecompetency->set_sortorder($coursecompetency->get_sortorder() + 1);
1663                 $coursecompetency->update();
1664             }
1665         }
1666         $competencyfrom->set_sortorder($competencyto->get_sortorder());
1667         return $competencyfrom->update();
1668     }
1670     /**
1671      * Update ruleoutcome value for a course competency.
1672      *
1673      * @param int|course_competency $coursecompetencyorid The course_competency, or its ID.
1674      * @param int $ruleoutcome The value of ruleoutcome.
1675      * @return bool True on success.
1676      */
1677     public static function set_course_competency_ruleoutcome($coursecompetencyorid, $ruleoutcome) {
1678         static::require_enabled();
1679         $coursecompetency = $coursecompetencyorid;
1680         if (!is_object($coursecompetency)) {
1681             $coursecompetency = new course_competency($coursecompetencyorid);
1682         }
1684         $courseid = $coursecompetency->get_courseid();
1685         self::validate_course($courseid);
1686         $coursecontext = context_course::instance($courseid);
1688         require_capability('tool/lp:coursecompetencymanage', $coursecontext);
1690         $coursecompetency->set_ruleoutcome($ruleoutcome);
1691         return $coursecompetency->update();
1692     }
1694     /**
1695      * Create a learning plan template from a record containing all the data for the class.
1696      *
1697      * Requires tool/lp:templatemanage capability.
1698      *
1699      * @param stdClass $record Record containing all the data for an instance of the class.
1700      * @return template
1701      */
1702     public static function create_template(stdClass $record) {
1703         static::require_enabled();
1704         $template = new template(0, $record);
1706         // First we do a permissions check.
1707         if (!$template->can_manage()) {
1708             throw new required_capability_exception($template->get_context(), 'tool/lp:templatemanage', 'nopermissions', '');
1709         }
1711         // OK - all set.
1712         $template = $template->create();
1714         // Trigger a template created event.
1715         \tool_lp\event\template_created::create_from_template($template)->trigger();
1717         return $template;
1718     }
1720     /**
1721      * Duplicate a learning plan template.
1722      *
1723      * Requires tool/lp:templatemanage capability at the template context.
1724      *
1725      * @param int $id the template id.
1726      * @return template
1727      */
1728     public static function duplicate_template($id) {
1729         static::require_enabled();
1730         $template = new template($id);
1732         // First we do a permissions check.
1733         if (!$template->can_manage()) {
1734             throw new required_capability_exception($template->get_context(), 'tool/lp:templatemanage', 'nopermissions', '');
1735         }
1737         // OK - all set.
1738         $competencies = template_competency::list_competencies($id, false);
1740         // Adding the suffix copy.
1741         $template->set_shortname(get_string('duplicateditemname', 'tool_lp', $template->get_shortname()));
1742         $template->set_id(0);
1744         $duplicatedtemplate = $template->create();
1746         // Associate each competency for the duplicated template.
1747         foreach ($competencies as $competency) {
1748             self::add_competency_to_template($duplicatedtemplate->get_id(), $competency->get_id());
1749         }
1751         // Trigger a template created event.
1752         \tool_lp\event\template_created::create_from_template($duplicatedtemplate)->trigger();
1754         return $duplicatedtemplate;
1755     }
1757     /**
1758      * Delete a learning plan template by id.
1759      * If the learning plan template has associated cohorts they will be deleted.
1760      *
1761      * Requires tool/lp:templatemanage capability.
1762      *
1763      * @param int $id The record to delete.
1764      * @param boolean $deleteplans True to delete plans associaated to template, false to unlink them.
1765      * @return boolean
1766      */
1767     public static function delete_template($id, $deleteplans = true) {
1768         global $DB;
1769         static::require_enabled();
1770         $template = new template($id);
1772         // First we do a permissions check.
1773         if (!$template->can_manage()) {
1774             throw new required_capability_exception($template->get_context(), 'tool/lp:templatemanage', 'nopermissions', '');
1775         }
1777         $transaction = $DB->start_delegated_transaction();
1778         $success = true;
1780         // Check if there are cohorts associated.
1781         $templatecohorts = template_cohort::get_relations_by_templateid($template->get_id());
1782         foreach ($templatecohorts as $templatecohort) {
1783             $success = $templatecohort->delete();
1784             if (!$success) {
1785                 break;
1786             }
1787         }
1789         // Still OK, delete or unlink the plans from the template.
1790         if ($success) {
1791             $plans = plan::get_records(array('templateid' => $template->get_id()));
1792             foreach ($plans as $plan) {
1793                 $success = $deleteplans ? self::delete_plan($plan->get_id()) : self::unlink_plan_from_template($plan);
1794                 if (!$success) {
1795                     break;
1796                 }
1797             }
1798         }
1800         // Still OK, delete the template comptencies.
1801         if ($success) {
1802             $success = template_competency::delete_by_templateid($template->get_id());
1803         }
1805         // OK - all set.
1806         if ($success) {
1807             // Create a template deleted event.
1808             $event = \tool_lp\event\template_deleted::create_from_template($template);
1810             $success = $template->delete();
1811         }
1813         if ($success) {
1814             // Trigger a template deleted event.
1815             $event->trigger();
1817             // Commit the transaction.
1818             $transaction->allow_commit();
1819         } else {
1820             $transaction->rollback(new moodle_exception('Error while deleting the template.'));
1821         }
1823         return $success;
1824     }
1826     /**
1827      * Update the details for a learning plan template.
1828      *
1829      * Requires tool/lp:templatemanage capability.
1830      *
1831      * @param stdClass $record The new details for the template. Note - must contain an id that points to the template to update.
1832      * @return boolean
1833      */
1834     public static function update_template($record) {
1835         global $DB;
1836         static::require_enabled();
1837         $template = new template($record->id);
1839         // First we do a permissions check.
1840         if (!$template->can_manage()) {
1841             throw new required_capability_exception($template->get_context(), 'tool/lp:templatemanage', 'nopermissions', '');
1843         } else if (isset($record->contextid) && $record->contextid != $template->get_contextid()) {
1844              // We can never change the context of a template.
1845             throw new coding_exception('Changing the context of an existing tempalte is forbidden.');
1847         }
1849         $updateplans = false;
1850         $before = $template->to_record();
1852         $template->from_record($record);
1853         $after = $template->to_record();
1855         // Should we update the related plans?
1856         if ($before->duedate != $after->duedate ||
1857                 $before->shortname != $after->shortname ||
1858                 $before->description != $after->description ||
1859                 $before->descriptionformat != $after->descriptionformat) {
1860             $updateplans = true;
1861         }
1863         $transaction = $DB->start_delegated_transaction();
1864         $success = $template->update();
1866         if (!$success) {
1867             $transaction->rollback(new moodle_exception('Error while updating the template.'));
1868             return $success;
1869         }
1871         // Trigger a template updated event.
1872         \tool_lp\event\template_updated::create_from_template($template)->trigger();
1874         if ($updateplans) {
1875             plan::update_multiple_from_template($template);
1876         }
1878         $transaction->allow_commit();
1880         return $success;
1881     }
1883     /**
1884      * Read a the details for a single learning plan template and return a record.
1885      *
1886      * Requires tool/lp:templateview capability at the system context.
1887      *
1888      * @param int $id The id of the template to read.
1889      * @return template
1890      */
1891     public static function read_template($id) {
1892         static::require_enabled();
1893         $template = new template($id);
1894         $context = $template->get_context();
1896         // First we do a permissions check.
1897         if (!$template->can_read()) {
1898              throw new required_capability_exception($template->get_context(), 'tool/lp:templateview', 'nopermissions', '');
1899         }
1901         // OK - all set.
1902         return $template;
1903     }
1905     /**
1906      * Perform a search based on the provided filters and return a paginated list of records.
1907      *
1908      * Requires tool/lp:templateview capability at the system context.
1909      *
1910      * @param string $sort The column to sort on
1911      * @param string $order ('ASC' or 'DESC')
1912      * @param int $skip Number of records to skip (pagination)
1913      * @param int $limit Max of records to return (pagination)
1914      * @param context $context The parent context of the frameworks.
1915      * @param string $includes Defines what other contexts to fetch frameworks from.
1916      *                         Accepted values are:
1917      *                          - children: All descendants
1918      *                          - parents: All parents, grand parents, etc...
1919      *                          - self: Context passed only.
1920      * @param bool $onlyvisible If should list only visible templates
1921      * @return array of competency_framework
1922      */
1923     public static function list_templates($sort, $order, $skip, $limit, $context, $includes = 'children', $onlyvisible = false) {
1924         global $DB;
1925         static::require_enabled();
1927         // Get all the relevant contexts.
1928         $contexts = self::get_related_contexts($context, $includes,
1929             array('tool/lp:templateview', 'tool/lp:templatemanage'));
1931         // First we do a permissions check.
1932         if (empty($contexts)) {
1933              throw new required_capability_exception($context, 'tool/lp:templateview', 'nopermissions', '');
1934         }
1936         // Make the order by.
1937         $orderby = '';
1938         if (!empty($sort)) {
1939             $orderby = $sort . ' ' . $order;
1940         }
1942         // OK - all set.
1943         $template = new template();
1944         list($insql, $params) = $DB->get_in_or_equal(array_keys($contexts), SQL_PARAMS_NAMED);
1945         $select = "contextid $insql";
1947         if ($onlyvisible) {
1948             $select .= " AND visible = :visible";
1949             $params['visible'] = 1;
1950         }
1951         return $template->get_records_select($select, $params, $orderby, '*', $skip, $limit);
1952     }
1954     /**
1955      * Perform a search based on the provided filters and return how many results there are.
1956      *
1957      * Requires tool/lp:templateview capability at the system context.
1958      *
1959      * @param context $context The parent context of the frameworks.
1960      * @param string $includes Defines what other contexts to fetch frameworks from.
1961      *                         Accepted values are:
1962      *                          - children: All descendants
1963      *                          - parents: All parents, grand parents, etc...
1964      *                          - self: Context passed only.
1965      * @return int
1966      */
1967     public static function count_templates($context, $includes) {
1968         global $DB;
1969         static::require_enabled();
1971         // First we do a permissions check.
1972         $contexts = self::get_related_contexts($context, $includes,
1973             array('tool/lp:templateview', 'tool/lp:templatemanage'));
1975         if (empty($contexts)) {
1976              throw new required_capability_exception($context, 'tool/lp:templateview', 'nopermissions', '');
1977         }
1979         // OK - all set.
1980         $template = new template();
1981         list($insql, $inparams) = $DB->get_in_or_equal(array_keys($contexts), SQL_PARAMS_NAMED);
1982         return $template->count_records_select("contextid $insql", $inparams);
1983     }
1985     /**
1986      * Count all the templates using a competency.
1987      *
1988      * @param int $competencyid The id of the competency to check.
1989      * @return int
1990      */
1991     public static function count_templates_using_competency($competencyid) {
1992         static::require_enabled();
1993         // First we do a permissions check.
1994         $context = context_system::instance();
1995         $onlyvisible = 1;
1997         $capabilities = array('tool/lp:templateview', 'tool/lp:templatemanage');
1998         if (!has_any_capability($capabilities, $context)) {
1999              throw new required_capability_exception($context, 'tool/lp:templateview', 'nopermissions', '');
2000         }
2002         if (has_capability('tool/lp:templatemanage', $context)) {
2003             $onlyvisible = 0;
2004         }
2006         // OK - all set.
2007         return template_competency::count_templates($competencyid, $onlyvisible);
2008     }
2010     /**
2011      * List all the learning plan templatesd using a competency.
2012      *
2013      * @param int $competencyid The id of the competency to check.
2014      * @return array[stdClass] Array of stdClass containing id and shortname.
2015      */
2016     public static function list_templates_using_competency($competencyid) {
2017         static::require_enabled();
2018         // First we do a permissions check.
2019         $context = context_system::instance();
2020         $onlyvisible = 1;
2022         $capabilities = array('tool/lp:templateview', 'tool/lp:templatemanage');
2023         if (!has_any_capability($capabilities, $context)) {
2024              throw new required_capability_exception($context, 'tool/lp:templateview', 'nopermissions', '');
2025         }
2027         if (has_capability('tool/lp:templatemanage', $context)) {
2028             $onlyvisible = 0;
2029         }
2031         // OK - all set.
2032         return template_competency::list_templates($competencyid, $onlyvisible);
2034     }
2036     /**
2037      * Count all the competencies in a learning plan template.
2038      *
2039      * @param  template|int $templateorid The template or its ID.
2040      * @return int
2041      */
2042     public static function count_competencies_in_template($templateorid) {
2043         static::require_enabled();
2044         // First we do a permissions check.
2045         $template = $templateorid;
2046         if (!is_object($template)) {
2047             $template = new template($template);
2048         }
2050         if (!$template->can_read()) {
2051             throw new required_capability_exception($template->get_context(), 'tool/lp:templateview', 'nopermissions', '');
2052         }
2054         // OK - all set.
2055         return template_competency::count_competencies($template->get_id());
2056     }
2058     /**
2059      * Count all the competencies in a learning plan template with no linked courses.
2060      *
2061      * @param  template|int $templateorid The template or its ID.
2062      * @return int
2063      */
2064     public static function count_competencies_in_template_with_no_courses($templateorid) {
2065         // First we do a permissions check.
2066         $template = $templateorid;
2067         if (!is_object($template)) {
2068             $template = new template($template);
2069         }
2071         if (!$template->can_read()) {
2072             throw new required_capability_exception($template->get_context(), 'tool/lp:templateview', 'nopermissions', '');
2073         }
2075         // OK - all set.
2076         return template_competency::count_competencies_with_no_courses($template->get_id());
2077     }
2079     /**
2080      * List all the competencies in a template.
2081      *
2082      * @param  template|int $templateorid The template or its ID.
2083      * @return array of competencies
2084      */
2085     public static function list_competencies_in_template($templateorid) {
2086         static::require_enabled();
2087         // First we do a permissions check.
2088         $template = $templateorid;
2089         if (!is_object($template)) {
2090             $template = new template($template);
2091         }
2093         if (!$template->can_read()) {
2094             throw new required_capability_exception($template->get_context(), 'tool/lp:templateview', 'nopermissions', '');
2095         }
2097         // OK - all set.
2098         return template_competency::list_competencies($template->get_id());
2099     }
2101     /**
2102      * Add a competency to this template.
2103      *
2104      * @param int $templateid The id of the template
2105      * @param int $competencyid The id of the competency
2106      * @return bool
2107      */
2108     public static function add_competency_to_template($templateid, $competencyid) {
2109         static::require_enabled();
2110         // First we do a permissions check.
2111         $template = new template($templateid);
2112         if (!$template->can_manage()) {
2113             throw new required_capability_exception($template->get_context(), 'tool/lp:templatemanage', 'nopermissions', '');
2114         }
2116         $record = new stdClass();
2117         $record->templateid = $templateid;
2118         $record->competencyid = $competencyid;
2120         $competency = new competency($competencyid);
2122         // Can not add a competency that belong to a hidden framework.
2123         if ($competency->get_framework()->get_visible() == false) {
2124             throw new coding_exception('A competency belonging to hidden framework can not be added');
2125         }
2127         $exists = template_competency::get_records(array('templateid' => $templateid, 'competencyid' => $competencyid));
2128         if (!$exists) {
2129             $templatecompetency = new template_competency(0, $record);
2130             $templatecompetency->create();
2131             return true;
2132         }
2133         return false;
2134     }
2136     /**
2137      * Remove a competency from this template.
2138      *
2139      * @param int $templateid The id of the template
2140      * @param int $competencyid The id of the competency
2141      * @return bool
2142      */
2143     public static function remove_competency_from_template($templateid, $competencyid) {
2144         static::require_enabled();
2145         // First we do a permissions check.
2146         $template = new template($templateid);
2147         if (!$template->can_manage()) {
2148             throw new required_capability_exception($template->get_context(), 'tool/lp:templatemanage', 'nopermissions', '');
2149         }
2151         $record = new stdClass();
2152         $record->templateid = $templateid;
2153         $record->competencyid = $competencyid;
2155         $competency = new competency($competencyid);
2157         $exists = template_competency::get_records(array('templateid' => $templateid, 'competencyid' => $competencyid));
2158         if ($exists) {
2159             $link = array_pop($exists);
2160             return $link->delete();
2161         }
2162         return false;
2163     }
2165     /**
2166      * Move the template competency up or down in the display list.
2167      *
2168      * Requires tool/lp:templatemanage capability at the system context.
2169      *
2170      * @param int $templateid The template id
2171      * @param int $competencyidfrom The id of the competency we are moving.
2172      * @param int $competencyidto The id of the competency we are moving to.
2173      * @return boolean
2174      */
2175     public static function reorder_template_competency($templateid, $competencyidfrom, $competencyidto) {
2176         static::require_enabled();
2177         // First we do a permissions check.
2178         $context = context_system::instance();
2180         require_capability('tool/lp:templatemanage', $context);
2182         $down = true;
2183         $matches = template_competency::get_records(array('templateid' => $templateid, 'competencyid' => $competencyidfrom));
2184         if (count($matches) == 0) {
2185             throw new coding_exception('The link does not exist');
2186         }
2188         $competencyfrom = array_pop($matches);
2189         $matches = template_competency::get_records(array('templateid' => $templateid, 'competencyid' => $competencyidto));
2190         if (count($matches) == 0) {
2191             throw new coding_exception('The link does not exist');
2192         }
2194         $competencyto = array_pop($matches);
2196         $all = template_competency::get_records(array('templateid' => $templateid), 'sortorder', 'ASC', 0, 0);
2198         if ($competencyfrom->get_sortorder() > $competencyto->get_sortorder()) {
2199             // We are moving up, so put it before the "to" item.
2200             $down = false;
2201         }
2203         foreach ($all as $id => $templatecompetency) {
2204             $sort = $templatecompetency->get_sortorder();
2205             if ($down && $sort > $competencyfrom->get_sortorder() && $sort <= $competencyto->get_sortorder()) {
2206                 $templatecompetency->set_sortorder($templatecompetency->get_sortorder() - 1);
2207                 $templatecompetency->update();
2208             } else if (!$down && $sort >= $competencyto->get_sortorder() && $sort < $competencyfrom->get_sortorder()) {
2209                 $templatecompetency->set_sortorder($templatecompetency->get_sortorder() + 1);
2210                 $templatecompetency->update();
2211             }
2212         }
2213         $competencyfrom->set_sortorder($competencyto->get_sortorder());
2214         return $competencyfrom->update();
2215     }
2217     /**
2218      * Create a relation between a template and a cohort.
2219      *
2220      * This silently ignores when the relation already existed.
2221      *
2222      * @param  template|int $templateorid The template or its ID.
2223      * @param  stdClass|int $cohortorid   The cohort ot its ID.
2224      * @return template_cohort
2225      */
2226     public static function create_template_cohort($templateorid, $cohortorid) {
2227         global $DB;
2228         static::require_enabled();
2230         $template = $templateorid;
2231         if (!is_object($template)) {
2232             $template = new template($template);
2233         }
2234         require_capability('tool/lp:templatemanage', $template->get_context());
2236         $cohort = $cohortorid;
2237         if (!is_object($cohort)) {
2238             $cohort = $DB->get_record('cohort', array('id' => $cohort), '*', MUST_EXIST);
2239         }
2241         // Replicate logic in cohort_can_view_cohort() because we can't use it directly as we don't have a course context.
2242         $cohortcontext = context::instance_by_id($cohort->contextid);
2243         if (!$cohort->visible && !has_capability('moodle/cohort:view', $cohortcontext)) {
2244             throw new required_capability_exception($cohortcontext, 'moodle/cohort:view', 'nopermissions', '');
2245         }
2247         $tplcohort = template_cohort::get_relation($template->get_id(), $cohort->id);
2248         if (!$tplcohort->get_id()) {
2249             $tplcohort->create();
2250         }
2252         return $tplcohort;
2253     }
2255     /**
2256      * Remove a relation between a template and a cohort.
2257      *
2258      * @param  template|int $templateorid The template or its ID.
2259      * @param  stdClass|int $cohortorid   The cohort ot its ID.
2260      * @return boolean True on success or when the relation did not exist.
2261      */
2262     public static function delete_template_cohort($templateorid, $cohortorid) {
2263         global $DB;
2264         static::require_enabled();
2266         $template = $templateorid;
2267         if (!is_object($template)) {
2268             $template = new template($template);
2269         }
2270         require_capability('tool/lp:templatemanage', $template->get_context());
2272         $cohort = $cohortorid;
2273         if (!is_object($cohort)) {
2274             $cohort = $DB->get_record('cohort', array('id' => $cohort), '*', MUST_EXIST);
2275         }
2277         $tplcohort = template_cohort::get_relation($template->get_id(), $cohort->id);
2278         if (!$tplcohort->get_id()) {
2279             return true;
2280         }
2282         return $tplcohort->delete();
2283     }
2285     /**
2286      * Lists user plans.
2287      *
2288      * @param int $userid
2289      * @return \tool_lp\plan[]
2290      */
2291     public static function list_user_plans($userid) {
2292         global $DB, $USER;
2293         static::require_enabled();
2294         $select = 'userid = :userid';
2295         $params = array('userid' => $userid);
2296         $context = context_user::instance($userid);
2298         // Check that we can read something here.
2299         if (!plan::can_read_user($userid) && !plan::can_read_user_draft($userid)) {
2300             throw new required_capability_exception($context, 'tool/lp:planview', 'nopermissions', '');
2301         }
2303         // The user cannot view the drafts.
2304         if (!plan::can_read_user_draft($userid)) {
2305             list($insql, $inparams) = $DB->get_in_or_equal(plan::get_draft_statuses(), SQL_PARAMS_NAMED, 'param', false);
2306             $select .= " AND status $insql";
2307             $params += $inparams;
2308         }
2309         // The user cannot view the non-drafts.
2310         if (!plan::can_read_user($userid)) {
2311             list($insql, $inparams) = $DB->get_in_or_equal(array(plan::STATUS_ACTIVE, plan::STATUS_COMPLETE),
2312                 SQL_PARAMS_NAMED, 'param', false);
2313             $select .= " AND status $insql";
2314             $params += $inparams;
2315         }
2317         return plan::get_records_select($select, $params, 'name ASC');
2318     }
2320     /**
2321      * List the plans to review.
2322      *
2323      * The method returns values in this format:
2324      *
2325      * array(
2326      *     'plans' => array(
2327      *         (stdClass)(
2328      *             'plan' => (plan),
2329      *             'template' => (template),
2330      *             'owner' => (stdClass)
2331      *         )
2332      *     ),
2333      *     'count' => (int)
2334      * )
2335      *
2336      * @param int $skip The number of records to skip.
2337      * @param int $limit The number of results to return.
2338      * @param int $userid The user we're getting the plans to review for.
2339      * @return array Containing the keys 'count', and 'plans'. The 'plans' key contains an object
2340      *               which contains 'plan', 'template' and 'owner'.
2341      */
2342     public static function list_plans_to_review($skip = 0, $limit = 100, $userid = null) {
2343         global $DB, $USER;
2344         static::require_enabled();
2346         if ($userid === null) {
2347             $userid = $USER->id;
2348         }
2350         $planfields = plan::get_sql_fields('p');
2351         $tplfields = template::get_sql_fields('t');
2352         $usercols = array('id') + get_user_fieldnames();
2353         $userfields = array();
2354         foreach ($usercols as $field) {
2355             $userfields[] = "u." . $field . " AS usr_" . $field;
2356         }
2357         $userfields = implode(',', $userfields);
2359         $select = "SELECT $planfields, $tplfields, $userfields";
2360         $countselect = "SELECT COUNT('x')";
2362         $sql = "  FROM {" . plan::TABLE . "} p
2363                   JOIN {user} u
2364                     ON u.id = p.userid
2365              LEFT JOIN {" . template::TABLE . "} t
2366                     ON t.id = p.templateid
2367                  WHERE (p.status = :waitingforreview
2368                     OR (p.status = :inreview AND p.reviewerid = :reviewerid))
2369                    AND p.userid != :userid";
2371         $params = array(
2372             'waitingforreview' => plan::STATUS_WAITING_FOR_REVIEW,
2373             'inreview' => plan::STATUS_IN_REVIEW,
2374             'reviewerid' => $userid,
2375             'userid' => $userid
2376         );
2378         // Primary check to avoid the hard work of getting the users in which the user has permission.
2379         $count = $DB->count_records_sql($countselect . $sql, $params);
2380         if ($count < 1) {
2381             return array('count' => 0, 'plans' => array());
2382         }
2384         // TODO MDL-52243 Use core function.
2385         list($insql, $inparams) = external::filter_users_with_capability_on_user_context_sql('tool/lp:planreview',
2386             $userid, SQL_PARAMS_NAMED);
2387         $sql .= " AND p.userid $insql";
2388         $params += $inparams;
2390         $plans = array();
2391         $records = $DB->get_recordset_sql($select . $sql, $params, $skip, $limit);
2392         foreach ($records as $record) {
2393             $plan = new plan(0, plan::extract_record($record));
2394             $template = null;
2396             if ($plan->is_based_on_template()) {
2397                 $template = new template(0, template::extract_record($record));
2398             }
2400             $plans[] = (object) array(
2401                 'plan' => $plan,
2402                 'template' => $template,
2403                 'owner' => persistent::extract_record($record, 'usr_'),
2404             );
2405         }
2406         $records->close();
2408         return array(
2409             'count' => $DB->count_records_sql($countselect . $sql, $params),
2410             'plans' => $plans
2411         );
2412     }
2414     /**
2415      * Creates a learning plan based on the provided data.
2416      *
2417      * @param stdClass $record
2418      * @return \tool_lp\plan
2419      */
2420     public static function create_plan(stdClass $record) {
2421         global $USER;
2422         static::require_enabled();
2423         $plan = new plan(0, $record);
2425         if ($plan->is_based_on_template()) {
2426             throw new coding_exception('To create a plan from a template use api::create_plan_from_template().');
2427         } else if ($plan->get_status() == plan::STATUS_COMPLETE) {
2428             throw new coding_exception('A plan cannot be created as complete.');
2429         }
2431         if (!$plan->can_manage()) {
2432             $context = context_user::instance($plan->get_userid());
2433             throw new required_capability_exception($context, 'tool/lp:planmanage', 'nopermissions', '');
2434         }
2436         $plan->create();
2438         // Trigger created event.
2439         \tool_lp\event\plan_created::create_from_plan($plan)->trigger();
2440         return $plan;
2441     }
2443     /**
2444      * Create a learning plan from a template.
2445      *
2446      * @param  mixed $templateorid The template object or ID.
2447      * @param  int $userid
2448      * @return false|\tool_lp\plan Returns false when the plan already exists.
2449      */
2450     public static function create_plan_from_template($templateorid, $userid) {
2451         static::require_enabled();
2452         $template = $templateorid;
2453         if (!is_object($template)) {
2454             $template = new template($template);
2455         }
2457         // The user must be able to view the template to use it as a base for a plan.
2458         if (!$template->can_read()) {
2459             throw new required_capability_exception($template->get_context(), 'tool/lp:templateview', 'nopermissions', '');
2460         }
2461         // Can not create plan from a hidden template.
2462         if ($template->get_visible() == false) {
2463             throw new coding_exception('A plan can not be created from a hidden template');
2464         }
2466         // Convert the template to a plan.
2467         $record = $template->to_record();
2468         $record->templateid = $record->id;
2469         $record->userid = $userid;
2470         $record->name = $record->shortname;
2471         $record->status = plan::STATUS_ACTIVE;
2473         unset($record->id);
2474         unset($record->timecreated);
2475         unset($record->timemodified);
2476         unset($record->usermodified);
2478         // Remove extra keys.
2479         $properties = plan::properties_definition();
2480         foreach ($record as $key => $value) {
2481             if (!array_key_exists($key, $properties)) {
2482                 unset($record->$key);
2483             }
2484         }
2486         $plan = new plan(0, $record);
2487         if (!$plan->can_manage()) {
2488             throw new required_capability_exception($plan->get_context(), 'tool/lp:planmanage', 'nopermissions', '');
2489         }
2491         // We first apply the permission checks as we wouldn't want to leak information by returning early that
2492         // the plan already exists.
2493         if (plan::record_exists_select('templateid = :templateid AND userid = :userid', array(
2494                 'templateid' => $template->get_id(), 'userid' => $userid))) {
2495             return false;
2496         }
2498         $plan->create();
2500         // Trigger created event.
2501         \tool_lp\event\plan_created::create_from_plan($plan)->trigger();
2502         return $plan;
2503     }
2505     /**
2506      * Create learning plans from a template and cohort.
2507      *
2508      * @param  mixed $templateorid The template object or ID.
2509      * @param  int $cohortid The cohort ID.
2510      * @param  bool $recreateunlinked When true the plans that were unlinked from this template will be re-created.
2511      * @return int The number of plans created.
2512      */
2513     public static function create_plans_from_template_cohort($templateorid, $cohortid, $recreateunlinked = false) {
2514         global $DB, $CFG;
2515         static::require_enabled();
2516         require_once($CFG->dirroot . '/cohort/lib.php');
2518         $template = $templateorid;
2519         if (!is_object($template)) {
2520             $template = new template($template);
2521         }
2523         // The user must be able to view the template to use it as a base for a plan.
2524         if (!$template->can_read()) {
2525             throw new required_capability_exception($template->get_context(), 'tool/lp:templateview', 'nopermissions', '');
2526         }
2528         // Can not create plan from a hidden template.
2529         if ($template->get_visible() == false) {
2530             throw new coding_exception('A plan can not be created from a hidden template');
2531         }
2533         // Replicate logic in cohort_can_view_cohort() because we can't use it directly as we don't have a course context.
2534         $cohort = $DB->get_record('cohort', array('id' => $cohortid), '*', MUST_EXIST);
2535         $cohortcontext = context::instance_by_id($cohort->contextid);
2536         if (!$cohort->visible && !has_capability('moodle/cohort:view', $cohortcontext)) {
2537             throw new required_capability_exception($cohortcontext, 'moodle/cohort:view', 'nopermissions', '');
2538         }
2540         // Convert the template to a plan.
2541         $recordbase = $template->to_record();
2542         $recordbase->templateid = $recordbase->id;
2543         $recordbase->name = $recordbase->shortname;
2544         $recordbase->status = plan::STATUS_ACTIVE;
2546         unset($recordbase->id);
2547         unset($recordbase->timecreated);
2548         unset($recordbase->timemodified);
2549         unset($recordbase->usermodified);
2551         // Remove extra keys.
2552         $properties = plan::properties_definition();
2553         foreach ($recordbase as $key => $value) {
2554             if (!array_key_exists($key, $properties)) {
2555                 unset($recordbase->$key);
2556             }
2557         }
2559         // Create the plans.
2560         $created = 0;
2561         $userids = template_cohort::get_missing_plans($template->get_id(), $cohortid, $recreateunlinked);
2562         foreach ($userids as $userid) {
2563             $record = (object) (array) $recordbase;
2564             $record->userid = $userid;
2566             $plan = new plan(0, $record);
2567             if (!$plan->can_manage()) {
2568                 // Silently skip members where permissions are lacking.
2569                 continue;
2570             }
2572             $plan->create();
2573             // Trigger created event.
2574             \tool_lp\event\plan_created::create_from_plan($plan)->trigger();
2575             $created++;
2576         }
2578         return $created;
2579     }
2581     /**
2582      * Unlink a plan from its template.
2583      *
2584      * @param  \tool_lp\plan|int $planorid The plan or its ID.
2585      * @return bool
2586      */
2587     public static function unlink_plan_from_template($planorid) {
2588         global $DB;
2589         static::require_enabled();
2591         $plan = $planorid;
2592         if (!is_object($planorid)) {
2593             $plan = new plan($planorid);
2594         }
2596         // The user must be allowed to manage the plans of the user, nothing about the template.
2597         if (!$plan->can_manage()) {
2598             throw new required_capability_exception($plan->get_context(), 'tool/lp:planmanage', 'nopermissions', '');
2599         }
2601         // Only plan with status DRAFT or ACTIVE can be unliked..
2602         if ($plan->get_status() == plan::STATUS_COMPLETE) {
2603             throw new coding_exception('Only draft or active plan can be unliked from a template');
2604         }
2606         // Early exit, it's already done...
2607         if (!$plan->is_based_on_template()) {
2608             return true;
2609         }
2611         // Fetch the template.
2612         $template = new template($plan->get_templateid());
2614         // Now, proceed by copying all competencies to the plan, then update the plan.
2615         $transaction = $DB->start_delegated_transaction();
2616         $competencies = template_competency::list_competencies($template->get_id(), false);
2617         $i = 0;
2618         foreach ($competencies as $competency) {
2619             $record = (object) array(
2620                 'planid' => $plan->get_id(),
2621                 'competencyid' => $competency->get_id(),
2622                 'sortorder' => $i++
2623             );
2624             $pc = new plan_competency(null, $record);
2625             $pc->create();
2626         }
2627         $plan->set_origtemplateid($template->get_id());
2628         $plan->set_templateid(null);
2629         $success = $plan->update();
2630         $transaction->allow_commit();
2632         // Trigger unlinked event.
2633         \tool_lp\event\plan_unlinked::create_from_plan($plan)->trigger();
2635         return $success;
2636     }
2638     /**
2639      * Updates a plan.
2640      *
2641      * @param stdClass $record
2642      * @return \tool_lp\plan
2643      */
2644     public static function update_plan(stdClass $record) {
2645         static::require_enabled();
2647         $plan = new plan($record->id);
2649         // Validate that the plan as it is can be managed.
2650         if (!$plan->can_manage()) {
2651             throw new required_capability_exception($plan->get_context(), 'tool/lp:planmanage', 'nopermissions', '');
2653         } else if ($plan->get_status() == plan::STATUS_COMPLETE) {
2654             // A completed plan cannot be edited.
2655             throw new coding_exception('Completed plan cannot be edited.');
2657         } else if ($plan->is_based_on_template()) {
2658             // Prevent a plan based on a template to be edited.
2659             throw new coding_exception('Cannot update a plan that is based on a template.');
2661         } else if (isset($record->templateid) && $plan->get_templateid() != $record->templateid) {
2662             // Prevent a plan to be based on a template.
2663             throw new coding_exception('Cannot base a plan on a template.');
2665         } else if (isset($record->userid) && $plan->get_userid() != $record->userid) {
2666             // Prevent change of ownership as the capabilities are checked against that.
2667             throw new coding_exception('A plan cannot be transfered to another user');
2669         } else if (isset($record->status) && $plan->get_status() != $record->status) {
2670             // Prevent change of status.
2671             throw new coding_exception('To change the status of a plan use the appropriate methods.');
2673         }
2675         $plan->from_record($record);
2676         $plan->update();
2678         // Trigger updated event.
2679         \tool_lp\event\plan_updated::create_from_plan($plan)->trigger();
2681         return $plan;
2682     }
2684     /**
2685      * Returns a plan data.
2686      *
2687      * @param int $id
2688      * @return \tool_lp\plan
2689      */
2690     public static function read_plan($id) {
2691         static::require_enabled();
2692         $plan = new plan($id);
2694         if (!$plan->can_read()) {
2695             $context = context_user::instance($plan->get_userid());
2696             throw new required_capability_exception($context, 'tool/lp:planview', 'nopermissions', '');
2697         }
2699         return $plan;
2700     }
2702     /**
2703      * Plan event viewed.
2704      *
2705      * @param mixed $planorid The id or the plan.
2706      * @return boolean
2707      */
2708     public static function plan_viewed($planorid) {
2709         static::require_enabled();
2710         $plan = $planorid;
2711         if (!is_object($plan)) {
2712             $plan = new plan($plan);
2713         }
2715         // First we do a permissions check.
2716         if (!$plan->can_read()) {
2717             $context = context_user::instance($plan->get_userid());
2718             throw new required_capability_exception($context, 'tool/lp:planview', 'nopermissions', '');
2719         }
2721         // Trigger a template viewed event.
2722         \tool_lp\event\plan_viewed::create_from_plan($plan)->trigger();
2724         return true;
2725     }
2727     /**
2728      * Deletes a plan.
2729      *
2730      * Plans based on a template can be removed just like any other one.
2731      *
2732      * @param int $id
2733      * @return bool Success?
2734      */
2735     public static function delete_plan($id) {
2736         global $DB;
2737         static::require_enabled();
2739         $plan = new plan($id);
2741         if (!$plan->can_manage()) {
2742             $context = context_user::instance($plan->get_userid());
2743             throw new required_capability_exception($context, 'tool/lp:planmanage', 'nopermissions', '');
2744         }
2746         // Wrap the suppression in a DB transaction.
2747         $transaction = $DB->start_delegated_transaction();
2749         // Delete plan competencies.
2750         $plancomps = plan_competency::get_records(array('planid' => $plan->get_id()));
2751         foreach ($plancomps as $plancomp) {
2752             $plancomp->delete();
2753         }
2755         // Delete archive user competencies if the status of the plan is complete.
2756         if ($plan->get_status() == plan::STATUS_COMPLETE) {
2757             self::remove_archived_user_competencies_in_plan($plan);
2758         }
2759         $event = \tool_lp\event\plan_deleted::create_from_plan($plan);
2760         $success = $plan->delete();
2762         $transaction->allow_commit();
2764         // Trigger deleted event.
2765         $event->trigger();
2767         return $success;
2768     }
2770     /**
2771      * Cancel the review of a plan.
2772      *
2773      * @param int|plan $planorid The plan, or its ID.
2774      * @return bool
2775      */
2776     public static function plan_cancel_review_request($planorid) {
2777         static::require_enabled();
2778         $plan = $planorid;
2779         if (!is_object($plan)) {
2780             $plan = new plan($plan);
2781         }
2783         // We need to be able to view the plan at least.
2784         if (!$plan->can_read()) {
2785             throw new required_capability_exception($plan->get_context(), 'tool/lp:planview', 'nopermissions', '');
2786         }
2788         if ($plan->is_based_on_template()) {
2789             throw new coding_exception('Template plans cannot be reviewed.');   // This should never happen.
2790         } else if ($plan->get_status() != plan::STATUS_WAITING_FOR_REVIEW) {
2791             throw new coding_exception('The plan review cannot be cancelled at this stage.');
2792         } else if (!$plan->can_request_review()) {
2793             throw new required_capability_exception($plan->get_context(), 'tool/lp:planmanage', 'nopermissions', '');
2794         }
2796         $plan->set_status(plan::STATUS_DRAFT);
2797         $result = $plan->update();
2799         // Trigger review request cancelled event.
2800         \tool_lp\event\plan_review_request_cancelled::create_from_plan($plan)->trigger();
2802         return $result;
2803     }
2805     /**
2806      * Request the review of a plan.
2807      *
2808      * @param int|plan $planorid The plan, or its ID.
2809      * @return bool
2810      */
2811     public static function plan_request_review($planorid) {
2812         static::require_enabled();
2813         $plan = $planorid;
2814         if (!is_object($plan)) {
2815             $plan = new plan($plan);
2816         }
2818         // We need to be able to view the plan at least.
2819         if (!$plan->can_read()) {
2820             throw new required_capability_exception($plan->get_context(), 'tool/lp:planview', 'nopermissions', '');
2821         }
2823         if ($plan->is_based_on_template()) {
2824             throw new coding_exception('Template plans cannot be reviewed.');   // This should never happen.
2825         } else if ($plan->get_status() != plan::STATUS_DRAFT) {
2826             throw new coding_exception('The plan cannot be sent for review at this stage.');
2827         } else if (!$plan->can_request_review()) {
2828             throw new required_capability_exception($plan->get_context(), 'tool/lp:planmanage', 'nopermissions', '');
2829         }
2831         $plan->set_status(plan::STATUS_WAITING_FOR_REVIEW);
2832         $result = $plan->update();
2834         // Trigger review requested event.
2835         \tool_lp\event\plan_review_requested::create_from_plan($plan)->trigger();
2837         return $result;
2838     }
2840     /**
2841      * Start the review of a plan.
2842      *
2843      * @param int|plan $planorid The plan, or its ID.
2844      * @return bool
2845      */
2846     public static function plan_start_review($planorid) {
2847         global $USER;
2848         static::require_enabled();
2849         $plan = $planorid;
2850         if (!is_object($plan)) {
2851             $plan = new plan($plan);
2852         }
2854         // We need to be able to view the plan at least.
2855         if (!$plan->can_read()) {
2856             throw new required_capability_exception($plan->get_context(), 'tool/lp:planview', 'nopermissions', '');
2857         }
2859         if ($plan->is_based_on_template()) {
2860             throw new coding_exception('Template plans cannot be reviewed.');   // This should never happen.
2861         } else if ($plan->get_status() != plan::STATUS_WAITING_FOR_REVIEW) {
2862             throw new coding_exception('The plan review cannot be started at this stage.');
2863         } else if (!$plan->can_review()) {
2864             throw new required_capability_exception($plan->get_context(), 'tool/lp:planmanage', 'nopermissions', '');
2865         }
2867         $plan->set_status(plan::STATUS_IN_REVIEW);
2868         $plan->set_reviewerid($USER->id);
2869         $result = $plan->update();
2871         // Trigger review started event.
2872         \tool_lp\event\plan_review_started::create_from_plan($plan)->trigger();
2874         return $result;
2875     }
2877     /**
2878      * Stop reviewing a plan.
2879      *
2880      * @param  int|plan $planorid The plan, or its ID.
2881      * @return bool
2882      */
2883     public static function plan_stop_review($planorid) {
2884         static::require_enabled();
2885         $plan = $planorid;
2886         if (!is_object($plan)) {
2887             $plan = new plan($plan);
2888         }
2890         // We need to be able to view the plan at least.
2891         if (!$plan->can_read()) {
2892             throw new required_capability_exception($plan->get_context(), 'tool/lp:planview', 'nopermissions', '');
2893         }
2895         if ($plan->is_based_on_template()) {
2896             throw new coding_exception('Template plans cannot be reviewed.');   // This should never happen.
2897         } else if ($plan->get_status() != plan::STATUS_IN_REVIEW) {
2898             throw new coding_exception('The plan review cannot be stopped at this stage.');
2899         } else if (!$plan->can_review()) {
2900             throw new required_capability_exception($plan->get_context(), 'tool/lp:planmanage', 'nopermissions', '');
2901         }
2903         $plan->set_status(plan::STATUS_DRAFT);
2904         $plan->set_reviewerid(null);
2905         $result = $plan->update();
2907         // Trigger review stopped event.
2908         \tool_lp\event\plan_review_stopped::create_from_plan($plan)->trigger();
2910         return $result;
2911     }
2913     /**
2914      * Approve a plan.
2915      *
2916      * This means making the plan active.
2917      *
2918      * @param  int|plan $planorid The plan, or its ID.
2919      * @return bool
2920      */
2921     public static function approve_plan($planorid) {
2922         static::require_enabled();
2923         $plan = $planorid;
2924         if (!is_object($plan)) {
2925             $plan = new plan($plan);
2926         }
2928         // We need to be able to view the plan at least.
2929         if (!$plan->can_read()) {
2930             throw new required_capability_exception($plan->get_context(), 'tool/lp:planview', 'nopermissions', '');
2931         }
2933         // We can approve a plan that is either a draft, in review, or waiting for review.
2934         if ($plan->is_based_on_template()) {
2935             throw new coding_exception('Template plans are already approved.');   // This should never happen.
2936         } else if (!$plan->is_draft()) {
2937             throw new coding_exception('The plan cannot be approved at this stage.');
2938         } else if (!$plan->can_review()) {
2939             throw new required_capability_exception($plan->get_context(), 'tool/lp:planmanage', 'nopermissions', '');
2940         }
2942         $plan->set_status(plan::STATUS_ACTIVE);
2943         $plan->set_reviewerid(null);
2944         $result = $plan->update();
2946         // Trigger approved event.
2947         \tool_lp\event\plan_approved::create_from_plan($plan)->trigger();
2949         return $result;
2950     }
2952     /**
2953      * Unapprove a plan.
2954      *
2955      * This means making the plan draft.
2956      *
2957      * @param  int|plan $planorid The plan, or its ID.
2958      * @return bool
2959      */
2960     public static function unapprove_plan($planorid) {
2961         static::require_enabled();
2962         $plan = $planorid;
2963         if (!is_object($plan)) {
2964             $plan = new plan($plan);
2965         }
2967         // We need to be able to view the plan at least.
2968         if (!$plan->can_read()) {
2969             throw new required_capability_exception($plan->get_context(), 'tool/lp:planview', 'nopermissions', '');
2970         }
2972         if ($plan->is_based_on_template()) {
2973             throw new coding_exception('Template plans are always approved.');   // This should never happen.
2974         } else if ($plan->get_status() != plan::STATUS_ACTIVE) {
2975             throw new coding_exception('The plan cannot be sent back to draft at this stage.');
2976         } else if (!$plan->can_review()) {
2977             throw new required_capability_exception($plan->get_context(), 'tool/lp:planmanage', 'nopermissions', '');
2978         }
2980         $plan->set_status(plan::STATUS_DRAFT);
2981         $result = $plan->update();
2983         // Trigger unapproved event.
2984         \tool_lp\event\plan_unapproved::create_from_plan($plan)->trigger();
2986         return $result;
2987     }
2989     /**
2990      * Complete a plan.
2991      *
2992      * @param int|plan $planorid The plan, or its ID.
2993      * @return bool
2994      */
2995     public static function complete_plan($planorid) {
2996         global $DB;
2997         static::require_enabled();
2999         $plan = $planorid;
3000         if (!is_object($planorid)) {
3001             $plan = new plan($planorid);
3002         }
3004         // Validate that the plan can be managed.
3005         if (!$plan->can_manage()) {
3006             throw new required_capability_exception($plan->get_context(), 'tool/lp:planmanage', 'nopermissions', '');
3007         }
3009         // Check if the plan was already completed.
3010         if ($plan->get_status() == plan::STATUS_COMPLETE) {
3011             throw new coding_exception('The plan is already completed.');
3012         }
3014         $originalstatus = $plan->get_status();
3015         $plan->set_status(plan::STATUS_COMPLETE);
3017         // The user should also be able to manage the plan when it's completed.
3018         if (!$plan->can_manage()) {
3019             throw new required_capability_exception($plan->get_context(), 'tool/lp:planmanage', 'nopermissions', '');
3020         }
3022         // Put back original status because archive needs it to extract competencies from the right table.
3023         $plan->set_status($originalstatus);
3025         // Do the things.
3026         $transaction = $DB->start_delegated_transaction();
3027         self::archive_user_competencies_in_plan($plan);
3028         $plan->set_status(plan::STATUS_COMPLETE);
3029         $success = $plan->update();
3031         if (!$success) {
3032             $transaction->rollback(new moodle_exception('The plan could not be updated.'));
3033             return $success;
3034         }
3036         $transaction->allow_commit();
3038         // Trigger updated event.
3039         \tool_lp\event\plan_completed::create_from_plan($plan)->trigger();
3041         return $success;
3042     }
3044     /**
3045      * Reopen a plan.
3046      *
3047      * @param int|plan $planorid The plan, or its ID.
3048      * @return bool
3049      */
3050     public static function reopen_plan($planorid) {
3051         global $DB;
3052         static::require_enabled();
3054         $plan = $planorid;
3055         if (!is_object($planorid)) {
3056             $plan = new plan($planorid);
3057         }
3059         // Validate that the plan as it is can be managed.
3060         if (!$plan->can_manage()) {
3061             $context = context_user::instance($plan->get_userid());
3062             throw new required_capability_exception($context, 'tool/lp:planmanage', 'nopermissions', '');
3063         }
3065         $beforestatus = $plan->get_status();
3066         $plan->set_status(plan::STATUS_ACTIVE);
3068         // Validate if status can be changed.
3069         if (!$plan->can_manage()) {
3070             $context = context_user::instance($plan->get_userid());
3071             throw new required_capability_exception($context, 'tool/lp:planmanage', 'nopermissions', '');
3072         }
3074         // Wrap the updates in a DB transaction.
3075         $transaction = $DB->start_delegated_transaction();
3077         // Delete archived user competencies if the status of the plan is changed from complete to another status.
3078         $mustremovearchivedcompetencies = ($beforestatus == plan::STATUS_COMPLETE && $plan->get_status() != plan::STATUS_COMPLETE);
3079         if ($mustremovearchivedcompetencies) {
3080             self::remove_archived_user_competencies_in_plan($plan);
3081         }
3083         // If duedate less than or equal to duedate_threshold unset it.
3084         if ($plan->get_duedate() <= time() + plan::DUEDATE_THRESHOLD) {
3085             $plan->set_duedate(0);
3086         }
3088         $success = $plan->update();
3090         if (!$success) {
3091             $transaction->rollback(new moodle_exception('The plan could not be updated.'));
3092             return $success;
3093         }
3095         $transaction->allow_commit();
3097         // Trigger reopened event.
3098         \tool_lp\event\plan_reopened::create_from_plan($plan)->trigger();
3100         return $success;
3101     }
3103     /**
3104      * Get a single competency from the user plan.
3105      *
3106      * @param  int $planorid The plan, or its ID.
3107      * @param  int $competencyid The competency id.
3108      * @return (object) array(
3109      *                      'competency' => \tool_lp\competency,
3110      *                      'usercompetency' => \tool_lp\user_competency
3111      *                      'usercompetencyplan' => \tool_lp\user_competency_plan
3112      *                  )
3113      *         The values of of keys usercompetency and usercompetencyplan cannot be defined at the same time.
3114      */
3115     public static function get_plan_competency($planorid, $competencyid) {
3116         static::require_enabled();
3117         $plan = $planorid;
3118         if (!is_object($planorid)) {
3119             $plan = new plan($planorid);
3120         }
3122         if (!user_competency::can_read_user($plan->get_userid())) {
3123             throw new required_capability_exception($plan->get_context(), 'tool/lp:usercompetencyview', 'nopermissions', '');
3124         }
3126         $competency = $plan->get_competency($competencyid);
3128         // Get user competencies from user_competency_plan if the plan status is set to complete.
3129         $iscompletedplan = $plan->get_status() == plan::STATUS_COMPLETE;
3130         if ($iscompletedplan) {
3131             $usercompetencies = user_competency_plan::get_multiple($plan->get_userid(), $plan->get_id(), array($competencyid));
3132             $ucresultkey = 'usercompetencyplan';
3133         } else {
3134             $usercompetencies = user_competency::get_multiple($plan->get_userid(), array($competencyid));
3135             $ucresultkey = 'usercompetency';
3136         }
3138         $found = count($usercompetencies);
3140         if ($found) {
3141             $uc = array_pop($usercompetencies);
3142         } else {
3143             if ($iscompletedplan) {
3144                 throw new coding_exception('A user competency plan is missing');
3145             } else {
3146                 $uc = user_competency::create_relation($plan->get_userid(), $competency->get_id());
3147                 $uc->create();
3148             }
3149         }
3151         $plancompetency = (object) array(
3152             'competency' => $competency,
3153             'usercompetency' => null,
3154             'usercompetencyplan' => null
3155         );
3156         $plancompetency->$ucresultkey = $uc;
3158         return $plancompetency;
3159     }
3161     /**
3162      * List the competencies in a user plan.
3163      *
3164      * @param  int $planorid The plan, or its ID.
3165      * @return array((object) array(
3166      *                            'competency' => \tool_lp\competency,
3167      *                            'usercompetency' => \tool_lp\user_competency
3168      *                            'usercompetencyplan' => \tool_lp\user_competency_plan
3169      *                        ))
3170      *         The values of of keys usercompetency and usercompetencyplan cannot be defined at the same time.
3171      */
3172     public static function list_plan_competencies($planorid) {
3173         static::require_enabled();
3174         $plan = $planorid;
3175         if (!is_object($planorid)) {
3176             $plan = new plan($planorid);
3177         }
3179         if (!$plan->can_read()) {
3180             $context = context_user::instance($plan->get_userid());
3181             throw new required_capability_exception($context, 'tool/lp:planview', 'nopermissions', '');
3182         }
3184         $result = array();
3185         $competencies = $plan->get_competencies();
3187         // Get user competencies from user_competency_plan if the plan status is set to complete.
3188         $iscompletedplan = $plan->get_status() == plan::STATUS_COMPLETE;
3189         if ($iscompletedplan) {
3190             $usercompetencies = user_competency_plan::get_multiple($plan->get_userid(), $plan->get_id(), $competencies);
3191             $ucresultkey = 'usercompetencyplan';
3192         } else {
3193             $usercompetencies = user_competency::get_multiple($plan->get_userid(), $competencies);
3194             $ucresultkey = 'usercompetency';
3195         }
3197         // Build the return values.
3198         foreach ($competencies as $key => $competency) {
3199             $found = false;
3201             foreach ($usercompetencies as $uckey => $uc) {
3202                 if ($uc->get_competencyid() == $competency->get_id()) {
3203                     $found = true;
3204                     unset($usercompetencies[$uckey]);
3205                     break;
3206                 }
3207             }
3209             if (!$found) {
3210                 if ($iscompletedplan) {
3211                     throw new coding_exception('A user competency plan is missing');
3212                 } else {
3213                     $uc = user_competency::create_relation($plan->get_userid(), $competency->get_id());
3214                 }
3215             }
3217             $plancompetency = (object) array(
3218                 'competency' => $competency,
3219                 'usercompetency' => null,
3220                 'usercompetencyplan' => null
3221             );
3222             $plancompetency->$ucresultkey = $uc;
3223             $result[] = $plancompetency;
3224         }
3226         return $result;
3227     }
3229     /**
3230      * Add a competency to a plan.
3231      *
3232      * @param int $planid The id of the plan
3233      * @param int $competencyid The id of the competency
3234      * @return bool
3235      */
3236     public static function add_competency_to_plan($planid, $competencyid) {
3237         static::require_enabled();
3238         $plan = new plan($planid);
3240         // First we do a permissions check.
3241         if (!$plan->can_manage()) {
3242             throw new required_capability_exception($plan->get_context(), 'tool/lp:planmanage', 'nopermissions', '');
3244         } else if ($plan->is_based_on_template()) {
3245             throw new coding_exception('A competency can not be added to a learning plan based on a template');
3246         }
3248         if (!$plan->can_be_edited()) {
3249             throw new coding_exception('A competency can not be added to a learning plan completed');
3250         }
3252         $competency = new competency($competencyid);
3254         // Can not add a competency that belong to a hidden framework.
3255         if ($competency->get_framework()->get_visible() == false) {
3256             throw new coding_exception('A competency belonging to hidden framework can not be added');
3257         }
3259         $exists = plan_competency::get_record(array('planid' => $planid, 'competencyid' => $competencyid));
3260         if (!$exists) {
3261             $record = new stdClass();
3262             $record->planid = $planid;
3263             $record->competencyid = $competencyid;
3264             $plancompetency = new plan_competency(0, $record);
3265             $plancompetency->create();
3266         }
3268         return true;
3269     }
3271     /**
3272      * Remove a competency from a plan.
3273      *
3274      * @param int $planid The plan id
3275      * @param int $competencyid The id of the competency
3276      * @return bool
3277      */
3278     public static function remove_competency_from_plan($planid, $competencyid) {
3279         static::require_enabled();
3280         $plan = new plan($planid);
3282         // First we do a permissions check.
3283         if (!$plan->can_manage()) {
3284             $context = context_user::instance($plan->get_userid());
3285             throw new required_capability_exception($context, 'tool/lp:planmanage', 'nopermissions', '');
3287         } else if ($plan->is_based_on_template()) {
3288             throw new coding_exception('A competency can not be removed from a learning plan based on a template');
3289         }
3291         if (!$plan->can_be_edited()) {
3292             throw new coding_exception('A competency can not be removed from a learning plan completed');
3293         }
3295         $link = plan_competency::get_record(array('planid' => $planid, 'competencyid' => $competencyid));
3296         if ($link) {
3297             return $link->delete();
3298         }
3299         return false;
3300     }
3302     /**
3303      * Move the plan competency up or down in the display list.
3304      *
3305      * Requires tool/lp:planmanage capability at the system context.
3306      *
3307      * @param int $planid The plan  id
3308      * @param int $competencyidfrom The id of the competency we are moving.
3309      * @param int $competencyidto The id of the competency we are moving to.
3310      * @return boolean
3311      */
3312     public static function reorder_plan_competency($planid, $competencyidfrom, $competencyidto) {
3313         static::require_enabled();
3314         $plan = new plan($planid);
3316         // First we do a permissions check.
3317         if (!$plan->can_manage()) {
3318             $context = context_user::instance($plan->get_userid());
3319             throw new required_capability_exception($context, 'tool/lp:planmanage', 'nopermissions', '');
3321         } else if ($plan->is_based_on_template()) {
3322             throw new coding_exception('A competency can not be reordered in a learning plan based on a template');
3323         }
3325         if (!$plan->can_be_edited()) {
3326             throw new coding_exception('A competency can not be reordered in a learning plan completed');
3327         }
3329         $down = true;
3330         $matches = plan_competency::get_records(array('planid' => $planid, 'competencyid' => $competencyidfrom));
3331         if (count($matches) == 0) {
3332             throw new coding_exception('The link does not exist');
3333         }
3335         $competencyfrom = array_pop($matches);
3336         $matches = plan_competency::get_records(array('planid' => $planid, 'competencyid' => $competencyidto));
3337         if (count($matches) == 0) {
3338             throw new coding_exception('The link does not exist');
3339         }
3341         $competencyto = array_pop($matches);
3343         $all = plan_competency::get_records(array('planid' => $planid), 'sortorder', 'ASC', 0, 0);
3345         if ($competencyfrom->get_sortorder() > $competencyto->get_sortorder()) {
3346             // We are moving up, so put it before the "to" item.
3347             $down = false;
3348         }
3350         foreach ($all as $id => $plancompetency) {
3351             $sort = $plancompetency->get_sortorder();
3352             if ($down && $sort > $competencyfrom->get_sortorder() && $sort <= $competencyto->get_sortorder()) {
3353                 $plancompetency->set_sortorder($plancompetency->get_sortorder() - 1);
3354                 $plancompetency->update();
3355             } else if (!$down && $sort >= $competencyto->get_sortorder() && $sort < $competencyfrom->get_sortorder()) {
3356                 $plancompetency->set_sortorder($plancompetency->get_sortorder() + 1);
3357                 $plancompetency->update();
3358             }
3359         }
3360         $competencyfrom->set_sortorder($competencyto->get_sortorder());
3361         return $competencyfrom->update();
3362     }
3364     /**
3365      * Cancel a user competency review request.
3366      *
3367      * @param  int $userid       The user ID.
3368      * @param  int $competencyid The competency ID.
3369      * @return bool
3370      */
3371     public static function user_competency_cancel_review_request($userid, $competencyid) {
3372         static::require_enabled();
3373         $context = context_user::instance($userid);
3374         $uc = user_competency::get_record(array('userid' => $userid, 'competencyid' => $competencyid));
3375         if (!$uc || !$uc->can_read()) {
3376             throw new required_capability_exception($context, 'tool/lp:usercompetencyview', 'nopermissions', '');
3377         } else if ($uc->get_status() != user_competency::STATUS_WAITING_FOR_REVIEW) {
3378             throw new coding_exception('The competency can not be cancel review request at this stage.');
3379         } else if (!$uc->can_request_review()) {
3380             throw new required_capability_exception($context, 'tool/lp:usercompetencyrequestreview', 'nopermissions', '');
3381         }
3383         $uc->set_status(user_competency::STATUS_IDLE);
3384         $result = $uc->update();
3385         if ($result) {
3386             \tool_lp\event\user_competency_review_request_cancelled::create_from_user_competency($uc)->trigger();
3387         }
3388         return $result;
3389     }
3391     /**
3392      * Request a user competency review.
3393      *
3394      * @param  int $userid       The user ID.
3395      * @param  int $competencyid The competency ID.
3396      * @return bool
3397      */
3398     public static function user_competency_request_review($userid, $competencyid) {
3399         static::require_enabled();
3400         $uc = user_competency::get_record(array('userid' => $userid, 'competencyid' => $competencyid));
3401         if (!$uc) {
3402             $uc = user_competency::create_relation($userid, $competencyid);
3403             $uc->create();
3404         }
3406         if (!$uc->can_read()) {
3407             throw new required_capability_exception($uc->get_context(), 'tool/lp:usercompetencyview', 'nopermissions', '');
3408         } else if ($uc->get_status() != user_competency::STATUS_IDLE) {
3409             throw new coding_exception('The competency can not be sent for review at this stage.');
3410         } else if (!$uc->can_request_review()) {
3411             throw new required_capability_exception($uc->get_context(), 'tool/lp:usercompetencyrequestreview', 'nopermissions', '');
3412         }
3414         $uc->set_status(user_competency::STATUS_WAITING_FOR_REVIEW);
3415         $result = $uc->update();
3416         if ($result) {
3417             \tool_lp\event\user_competency_review_requested::create_from_user_competency($uc)->trigger();
3418         }
3419         return $result;
3420     }
3422     /**
3423      * Start a user competency review.
3424      *
3425      * @param  int $userid       The user ID.
3426      * @param  int $competencyid The competency ID.
3427      * @return bool
3428      */
3429     public static function user_competency_start_review($userid, $competencyid) {
3430         global $USER;
3431         static::require_enabled();
3433         $context = context_user::instance($userid);
3434         $uc = user_competency::get_record(array('userid' => $userid, 'competencyid' => $competencyid));
3435         if (!$uc || !$uc->can_read()) {
3436             throw new required_capability_exception($context, 'tool/lp:usercompetencyview', 'nopermissions', '');
3437         } else if ($uc->get_status() != user_competency::STATUS_WAITING_FOR_REVIEW) {
3438             throw new coding_exception('The competency review can not be started at this stage.');
3439         } else if (!$uc->can_review()) {
3440             throw new required_capability_exception($context, 'tool/lp:usercompetencyreview', 'nopermissions', '');
3441         }
3443         $uc->set_status(user_competency::STATUS_IN_REVIEW);
3444         $uc->set_reviewerid($USER->id);
3445         $result = $uc->update();
3446         if ($result) {
3447             \tool_lp\event\user_competency_review_started::create_from_user_competency($uc)->trigger();
3448         }
3449         return $result;
3450     }
3452     /**
3453      * Stop a user competency review.
3454      *
3455      * @param  int $userid       The user ID.
3456      * @param  int $competencyid The competency ID.
3457      * @return bool
3458      */
3459     public static function user_competency_stop_review($userid, $competencyid) {
3460         static::require_enabled();
3461         $context = context_user::instance($userid);
3462         $uc = user_competency::get_record(array('userid' => $userid, 'competencyid' => $competencyid));
3463         if (!$uc || !$uc->can_read()) {
3464             throw new required_capability_exception($context, 'tool/lp:usercompetencyview', 'nopermissions', '');
3465         } else if ($uc->get_status() != user_competency::STATUS_IN_REVIEW) {
3466             throw new coding_exception('The competency review can not be stopped at this stage.');
3467         } else if (!$uc->can_review()) {
3468             throw new required_capability_exception($context, 'tool/lp:usercompetencyreview', 'nopermissions', '');
3469         }
3471         $uc->set_status(user_competency::STATUS_IDLE);
3472         $result = $uc->update();
3473         if ($result) {
3474             \tool_lp\event\user_competency_review_stopped::create_from_user_competency($uc)->trigger();
3475         }
3476         return $result;
3477     }
3479     /**
3480      * Log user competency viewed event.
3481      *
3482      * @param user_competency|int $usercompetencyorid The user competency object or user competency id
3483      * @return bool
3484      */
3485     public static function user_competency_viewed($usercompetencyorid) {
3486         static::require_enabled();
3487         $uc = $usercompetencyorid;
3488         if (!is_object($uc)) {
3489             $uc = new user_competency($uc);
3490         }
3492         if (!$uc || !$uc->can_read()) {
3493             throw new required_capability_exception($uc->get_context(), 'tool/lp:usercompetencyview', 'nopermissions', '');
3494         }
3496         \tool_lp\event\user_competency_viewed::create_from_user_competency_viewed($uc)->trigger();
3497         return true;
3498     }
3500     /**
3501      * Log user competency viewed in plan event.
3502      *
3503      * @param user_competency|int $usercompetencyorid The user competency object or user competency id
3504      * @param int $planid The plan ID
3505      * @return bool
3506      */
3507     public static function user_competency_viewed_in_plan($usercompetencyorid, $planid) {
3508         static::require_enabled();
3509         $uc = $usercompetencyorid;
3510         if (!is_object($uc)) {
3511             $uc = new user_competency($uc);
3512         }
3514         if (!$uc || !$uc->can_read()) {
3515             throw new required_capability_exception($uc->get_context(), 'tool/lp:usercompetencyview', 'nopermissions', '');
3516         }
3517         $plan = new plan($planid);
3518         if ($plan->get_status() == plan::STATUS_COMPLETE) {
3519             throw new coding_exception('To log the user competency in completed plan use user_competency_plan_viewed method.');
3520         }
3522         \tool_lp\event\user_competency_viewed_in_plan::create_from_user_competency_viewed_in_plan($uc, $planid)->trigger();
3523         return true;
3524     }
3526     /**
3527      * Log user competency viewed in course event.
3528      *
3529      * @param user_competency|int $usercompetencyorid The user competency object or user competency id
3530      * @param int $courseid The course ID
3531      * @return bool
3532      */
3533     public static function user_competency_viewed_in_course($usercoursecompetencyorid) {
3534         static::require_enabled();
3535         $ucc = $usercoursecompetencyorid;
3536         if (!is_object($ucc)) {
3537             $ucc = new user_competency_course($ucc);
3538         }
3540         if (!$ucc || !user_competency::can_read_user_in_course($ucc->get_userid(), $ucc->get_courseid())) {
3541             throw new required_capability_exception($ucc->get_context(), 'tool/lp:usercompetencyview', 'nopermissions', '');
3542         }
3544         // Validate the course, this will throw an exception if not valid.
3545         self::validate_course($ucc->get_courseid());
3547         \tool_lp\event\user_competency_viewed_in_course::create_from_user_competency_viewed_in_course($ucc)->trigger();
3548         return true;
3549     }
3551     /**
3552      * Log user competency plan viewed event.
3553      *
3554      * @param user_competency_plan|int $usercompetencyplanorid The user competency plan object or user competency plan id
3555      * @return bool
3556      */
3557     public static function user_competency_plan_viewed($usercompetencyplanorid) {
3558         static::require_enabled();
3559         $ucp = $usercompetencyplanorid;
3560         if (!is_object($ucp)) {
3561             $ucp = new user_competency_plan($ucp);
3562         }
3564         if (!$ucp || !user_competency::can_read_user($ucp->get_userid())) {
3565             throw new required_capability_exception($ucp->get_context(), 'tool/lp:usercompetencyview', 'nopermissions', '');
3566         }
3567         $plan = new plan($ucp->get_planid());
3568         if ($plan->get_status() != plan::STATUS_COMPLETE) {
3569             throw new coding_exception('To log the user competency in non-completed plan use '
3570                 . 'user_competency_viewed_in_plan method.');
3571         }
3573         \tool_lp\event\user_competency_plan_viewed::create_from_user_competency_plan($ucp)->trigger();
3574         return true;
3575     }
3577     /**
3578      * Check if template has related data.
3579      *
3580      * @param int $templateid The id of the template to check.
3581      * @return boolean
3582      */
3583     public static function template_has_related_data($templateid) {
3584         static::require_enabled();
3585         // First we do a permissions check.
3586         $template = new template($templateid);
3588         if (!$template->can_read()) {
3589             throw new required_capability_exception($template->get_context(), 'tool/lp:templateview', 'nopermissions', '');
3590         }
3592         // OK - all set.
3593         return $template->has_plans();
3594     }
3596     /**
3597      * List all the related competencies.
3598      *
3599      * @param int $competencyid The id of the competency to check.
3600      * @return competency[]
3601      */
3602     public static function list_related_competencies($competencyid) {
3603         static::require_enabled();
3604         $competency = new competency($competencyid);
3606         if (!has_any_capability(array('tool/lp:competencyview', 'tool/lp:competencymanage'), $competency->get_context())) {
3607              throw new required_capability_exception($competency->get_context(), 'tool/lp:competencyview', 'nopermissions', '');
3608         }
3610         return $competency->get_related_competencies();
3611     }
3613     /**
3614      * Add a related competency.
3615      *
3616      * @param int $competencyid The id of the competency
3617      * @param int $relatedcompetencyid The id of the related competency.
3618      * @return bool False when create failed, true on success, or if the relation already existed.
3619      */
3620     public static function add_related_competency($competencyid, $relatedcompetencyid) {
3621         static::require_enabled();
3622         $competency1 = new competency($competencyid);
3623         $competency2 = new competency($relatedcompetencyid);
3625         require_capability('tool/lp:competencymanage', $competency1->get_context());
3627         $relatedcompetency = related_competency::get_relation($competency1->get_id(), $competency2->get_id());
3628         if (!$relatedcompetency->get_id()) {
3629             $relatedcompetency->create();
3630             return true;
3631         }
3633         return true;
3634     }
3636     /**
3637      * Remove a related competency.
3638      *
3639      * @param int $competencyid The id of the competency.
3640      * @param int $relatedcompetencyid The id of the related competency.
3641      * @return bool True when it was deleted, false when it wasn't or the relation doesn't exist.
3642      */
3643     public static function remove_related_competency($competencyid, $relatedcompetencyid) {
3644         static::require_enabled();
3645         $competency = new competency($competencyid);
3647         // This only check if we have the permission in either competency because both competencies
3648         // should belong to the same framework.
3649         require_capability('tool/lp:competencymanage', $competency->get_context());
3651         $relatedcompetency = related_competency::get_relation($competencyid, $relatedcompetencyid);
3652         if ($relatedcompetency->get_id()) {
3653             return $relatedcompetency->delete();
3654         }
3656         return false;
3657     }
3659     /**
3660      * Read a user evidence.
3661      *
3662      * @param int $id
3663      * @return user_evidence
3664      */
3665     public static function read_user_evidence($id) {
3666         static::require_enabled();
3667         $userevidence = new user_evidence($id);
3669         if (!$userevidence->can_read()) {
3670             $context = $userevidence->get_context();
3671             throw new required_capability_exception($context, 'tool/lp:userevidenceview', 'nopermissions', '');
3672         }
3674         return $userevidence;
3675     }
3677     /**
3678      * Create a new user evidence.
3679      *
3680      * @param  object $data        The data.
3681      * @param  int    $draftitemid The draft ID in which files have been saved.
3682      * @return user_evidence
3683      */
3684     public static function create_user_evidence($data, $draftitemid = null) {
3685         static::require_enabled();
3686         $userevidence = new user_evidence(null, $data);
3687         $context = $userevidence->get_context();
3689         if (!$userevidence->can_manage()) {
3690             throw new required_capability_exception($context, 'tool/lp:userevidencemanage', 'nopermissions', '');
3691         }
3693         $userevidence->create();
3694         if (!empty($draftitemid)) {
3695             $fileareaoptions = array('subdirs' => true);
3696             $itemid = $userevidence->get_id();
3697             file_save_draft_area_files($draftitemid, $context->id, 'tool_lp', 'userevidence', $itemid, $fileareaoptions);
3698         }
3700         // Trigger an evidence of prior learning created event.
3701         \tool_lp\event\user_evidence_created::create_from_user_evidence($userevidence)->trigger();
3703         return $userevidence;
3704     }
3706     /**
3707      * Create a new user evidence.
3708      *
3709      * @param  object $data        The data.
3710      * @param  int    $draftitemid The draft ID in which files have been saved.
3711      * @return user_evidence
3712      */
3713     public static function update_user_evidence($data, $draftitemid = null) {
3714         static::require_enabled();
3715         $userevidence = new user_evidence($data->id);
3716         $context = $userevidence->get_context();
3718         if (!$userevidence->can_manage()) {
3719             throw new required_capability_exception($context, 'tool/lp:userevidencemanage', 'nopermissions', '');
3721         } else if (array_key_exists('userid', $data) && $data->userid != $userevidence->get_userid()) {
3722             throw new coding_exception('Can not change the userid of a user evidence.');
3723         }
3725         $userevidence->from_record($data);
3726         $userevidence->update();
3728         if (!empty($draftitemid)) {
3729             $fileareaoptions = array('subdirs' => true);
3730             $itemid = $userevidence->get_id();
3731             file_save_draft_area_files($draftitemid, $context->id, 'tool_lp', 'userevidence', $itemid, $fileareaoptions);
3732         }
3734         // Trigger an evidence of prior learning updated event.
3735         \tool_lp\event\user_evidence_updated::create_from_user_evidence($userevidence)->trigger();
3737         return $userevidence;
3738     }
3740     /**
3741      * Delete a user evidence.
3742      *
3743      * @param  int $id The user evidence ID.
3744      * @return bool
3745      */
3746     public static function delete_user_evidence($id) {
3747         static::require_enabled();
3748         $userevidence = new user_evidence($id);
3749         $context = $userevidence->get_context();
3751         if (!$userevidence->can_manage()) {
3752             throw new required_capability_exception($context, 'tool/lp:userevidencemanage', 'nopermissions', '');
3753         }
3755         // Delete the user evidence.
3756         $userevidence->delete();
3758         // Delete associated files.
3759         $fs = get_file_storage();
3760         $fs->delete_area_files($context->id, 'tool_lp', 'userevidence', $id);
3762         // Delete relation between evidence and competencies.
3763         $userevidence->set_id($id);     // Restore the ID to fully mock the object.
3764         $competencies = user_evidence_competency::get_competencies_by_userevidenceid($id);
3765         foreach ($competencies as $competency) {
3766             static::delete_user_evidence_competency($userevidence, $competency->get_id());
3767         }
3769         // Trigger an evidence of prior learning deleted event.
3770         \tool_lp\event\user_evidence_deleted::create_from_user_evidence($userevidence)->trigger();
3772         $userevidence->set_id(0);       // Restore the object.
3774         return true;
3775     }
3777     /**
3778      * List the user evidence of a user.
3779      *
3780      * @param  int $userid The user ID.
3781      * @return user_evidence[]
3782      */
3783     public static function list_user_evidence($userid) {
3784         static::require_enabled();
3785         if (!user_evidence::can_read_user($userid)) {
3786             $context = context_user::instance($userid);
3787             throw new required_capability_exception($context, 'tool/lp:userevidenceview', 'nopermissions', '');
3788         }
3790         $evidence = user_evidence::get_records(array('userid' => $userid), 'name');
3791         return $evidence;
3792     }
3794     /**
3795      * Link a user evidence with a competency.
3796      *
3797      * @param  user_evidence|int $userevidenceorid User evidence or its ID.
3798      * @param  int $competencyid Competency ID.
3799      * @return user_evidence_competency
3800      */
3801     public static function create_user_evidence_competency($userevidenceorid, $competencyid) {
3802         global $USER;
3803         static::require_enabled();
3805         $userevidence = $userevidenceorid;
3806         if (!is_object($userevidence)) {
3807             $userevidence = self::read_user_evidence($userevidence);
3808         }
3810         // Perform user evidence capability checks.
3811         if (!$userevidence->can_manage()) {
3812             $context = $userevidence->get_context();
3813             throw new required_capability_exception($context, 'tool/lp:userevidencemanage', 'nopermissions', '');
3814         }
3816         // Perform competency capability checks.
3817         $competency = self::read_competency($competencyid);
3819         // Get (and create) the relation.
3820         $relation = user_evidence_competency::get_relation($userevidence->get_id(), $competency->get_id());
3821         if (!$relation->get_id()) {
3822             $relation->create();
3824             $link = new moodle_url('/admin/tool/lp/user_evidence.php', array('id' => $userevidence->get_id()));
3825             self::add_evidence(
3826                 $userevidence->get_userid(),
3827                 $competency,
3828                 $userevidence->get_context(),
3829                 evidence::ACTION_LOG,
3830                 'evidence_evidenceofpriorlearninglinked',
3831                 'tool_lp',
3832                 $userevidence->get_name(),
3833                 false,
3834                 $link->out(false),
3835                 null,
3836                 $USER->id
3837             );
3838         }
3840         return $relation;
3841     }
3843     /**
3844      * Delete a relationship between a user evidence and a competency.
3845      *
3846      * @param  user_evidence|int $userevidenceorid User evidence or its ID.
3847      * @param  int $competencyid Competency ID.
3848      * @return bool
3849      */
3850     public static function delete_user_evidence_competency($userevidenceorid, $competencyid) {
3851         global $USER;
3852         static::require_enabled();
3854         $userevidence = $userevidenceorid;
3855         if (!is_object($userevidence)) {
3856             $userevidence = self::read_user_evidence($userevidence);
3857         }
3859         // Perform user evidence capability checks.
3860         if (!$userevidence->can_manage()) {
3861             $context = $userevidence->get_context();
3862             throw new required_capability_exception($context, 'tool/lp:userevidencemanage', 'nopermissions', '');
3863         }
3865         // Get (and delete) the relation.
3866         $relation = user_evidence_competency::get_relation($userevidence->get_id(), $competencyid);
3867         if (!$relation->get_id()) {
3868             return true;
3869         }
3871         $success = $relation->delete();
3872         if ($success) {
3873             self::add_evidence(
3874                 $userevidence->get_userid(),
3875                 $competencyid,
3876                 $userevidence->get_context(),
3877                 evidence::ACTION_LOG,
3878                 'evidence_evidenceofpriorlearningunlinked',
3879                 'tool_lp',
3880                 $userevidence->get_name(),
3881                 false,
3882                 null,
3883                 null,
3884                 $USER->id
3885             );
3886         }
3888         return $success;
3889     }
3891     /**
3892      * Send request review for user evidence competencies.
3893      *
3894      * @param  int $id The user evidence ID.
3895      * @return bool
3896      */
3897     public static function request_review_of_user_evidence_linked_competencies($id) {
3898         $userevidence = new user_evidence($id);
3899         $context = $userevidence->get_context();
3900         $userid = $userevidence->get_userid();
3902         if (!$userevidence->can_manage()) {
3903             throw new required_capability_exception($context, 'tool/lp:userevidencemanage', 'nopermissions', '');
3904         }
3906         $usercompetencies = user_evidence_competency::get_user_competencies_by_userevidenceid($id);
3907         foreach ($usercompetencies as $usercompetency) {
3908             if ($usercompetency->get_status() == user_competency::STATUS_IDLE) {
3909                 static::user_competency_request_review($userid, $usercompetency->get_competencyid());
3910             }
3911         }
3913         return true;
3914     }
3916     /**
3917      * Recursively duplicate competencies from a tree, we start duplicating from parents to children to have a correct path.
3918      * This method does not copy the related competencies.
3919      *
3920      * @param int $frameworkid - framework id
3921      * @param competency[] $tree - array of competencies object
3922      * @param int $oldparent - old parent id
3923      * @param int $newparent - new parent id
3924      * @return competency[] $matchids - List of old competencies ids matched with new competencies object.
3925      */
3926     protected static function duplicate_competency_tree($frameworkid, $tree, $oldparent = 0, $newparent = 0) {
3927         $matchids = array();
3928         foreach ($tree as $node) {
3929             if ($node->competency->get_parentid() == $oldparent) {
3930                 $parentid = $node->competency->get_id();
3932                 // Create the competency.
3933                 $competency = new competency(0, $node->competency->to_record());
3934                 $competency->set_competencyframeworkid($frameworkid);
3935                 $competency->set_parentid($newparent);
3936                 $competency->set_path('');
3937                 $competency->set_id(0);
3938                 $competency->reset_rule();
3939                 $competency->create();
3941                 // Trigger the created event competency.
3942                 \tool_lp\event\competency_created::create_from_competency($competency)->trigger();
3944                 // Match the old id with the new one.
3945                 $matchids[$parentid] = $competency;
3947                 if (!empty($node->children)) {
3948                     // Duplicate children competency.
3949                     $childrenids = self::duplicate_competency_tree($frameworkid, $node->children, $parentid, $competency->get_id());
3950                     // Array_merge does not keep keys when merging so we use the + operator.
3951                     $matchids = $matchids + $childrenids;
3952                 }
3953             }
3954         }
3955         return $matchids;
3956     }
3958     /**
3959      * Recursively migrate competency rules.
3960      *
3961      * @param competency[] $tree - array of competencies object
3962      * @param competency[] $matchids - List of old competencies ids matched with new competencies object
3963      */
3964     protected static function migrate_competency_tree_rules($tree, $matchids) {
3966         foreach ($tree as $node) {
3967             $oldcompid = $node->competency->get_id();
3968             if ($node->competency->get_ruletype() && array_key_exists($oldcompid, $matchids)) {
3969                 try {
3970                     // Get the new competency.
3971                     $competency = $matchids[$oldcompid];
3972                     $class = $node->competency->get_ruletype();
3973                     $newruleconfig = $class::migrate_config($node->competency->get_ruleconfig(), $matchids);
3974                     $competency->set_ruleconfig($newruleconfig);
3975                     $competency->set_ruletype($class);
3976                     $competency->set_ruleoutcome($node->competency->get_ruleoutcome());
3977                     $competency->update();
3978                 } catch (\Exception $e) {
3979                     debugging('Could not migrate competency rule from: ' . $oldcompid . ' to: ' . $competency->get_id() . '.' .
3980                         ' Exception: ' . $e->getMessage(), DEBUG_DEVELOPER);
3981                     $competency->reset_rule();
3982                 }
3983             }
3985             if (!empty($node->children)) {
3986                 self::migrate_competency_tree_rules($node->children, $matchids);
3987             }
3988         }
3989     }
3991     /**
3992      * Archive user competencies in a plan.
3993      *
3994      * @param int $plan The plan object.
3995      * @return void
3996      */
3997     protected static function archive_user_competencies_in_plan($plan) {
3999         // Check if the plan was already completed.
4000         if ($plan->get_status() == plan::STATUS_COMPLETE) {
4001             throw new coding_exception('The plan is already completed.');
4002         }
4004         $competen