MDL-53471 tool_lp: Hooking in to report when scale is being used
[moodle.git] / admin / tool / lp / classes / api.php
CommitLineData
d9a39950
DW
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/>.
16
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 */
24namespace tool_lp;
dd1ce763 25defined('MOODLE_INTERNAL') || die();
d9a39950
DW
26
27use stdClass;
2de75345
FM
28use context;
29use context_helper;
d9a39950
DW
30use context_system;
31use context_course;
db650737 32use context_module;
4db373d5 33use context_user;
d9a39950 34use coding_exception;
d4c0a2f6 35use require_login_exception;
914b580e 36use moodle_exception;
cc8348db 37use moodle_url;
d9a39950
DW
38use required_capability_exception;
39
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 */
46class api {
47
338386aa
FM
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 }
56
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 }
68
4e89144c
FM
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 }
91
f446b2e1
DW
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 }
110
111 if (!$cm->uservisible) {
112 if ($throwexception) {
113 throw new require_login_exception('Course module is hidden');
114 } else {
115 return false;
116 }
117 }
118
119 return true;
120 }
121
d660824b
FM
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 }
134
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 }
143
144 return true;
145 }
146
d9a39950
DW
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) {
338386aa 156 static::require_enabled();
2de75345
FM
157 $competency = new competency(0, $record);
158
d9a39950 159 // First we do a permissions check.
a214d35e 160 require_capability('tool/lp:competencymanage', $competency->get_context());
d9a39950 161
14199b85
IT
162 // Reset the sortorder, use reorder instead.
163 $competency->set_sortorder(null);
20c2fe3e
FM
164 $competency->create();
165
d32d64bc
IT
166 \tool_lp\event\competency_created::create_from_competency($competency)->trigger();
167
20c2fe3e
FM
168 // Reset the rule of the parent.
169 $parent = $competency->get_parent();
170 if ($parent) {
171 $parent->reset_rule();
172 $parent->update();
173 }
14199b85 174
d9a39950
DW
175 return $competency;
176 }
177
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) {
9a325eda 187 global $DB;
338386aa 188 static::require_enabled();
2de75345
FM
189 $competency = new competency($id);
190
d9a39950 191 // First we do a permissions check.
a214d35e 192 require_capability('tool/lp:competencymanage', $competency->get_context());
d9a39950 193
d32d64bc 194 $events = array();
9a325eda 195 $competencyids = array(intval($competency->get_id()));
d32d64bc 196 $contextid = $competency->get_context()->id;
9a325eda
IT
197 $competencyids = array_merge(competency::get_descendants_ids($competency), $competencyids);
198 if (!competency::can_all_be_deleted($competencyids)) {
199 return false;
20c2fe3e 200 }
9a325eda
IT
201 $transaction = $DB->start_delegated_transaction();
202
203 try {
204
205 // Reset the rule of the parent.
206 $parent = $competency->get_parent();
207 if ($parent) {
208 $parent->reset_rule();
209 $parent->update();
210 }
211
212 // Delete the competency separately so the after_delete event can be triggered.
213 $competency->delete();
214
215 // Delete the competencies.
216 competency::delete_multiple($competencyids);
217
218 // Delete the competencies relation.
219 related_competency::delete_multiple_relations($competencyids);
20c2fe3e 220
9a325eda
IT
221 // Delete competency evidences.
222 user_evidence_competency::delete_by_competencyids($competencyids);
223
d32d64bc
IT
224 // Register the competencies deleted events.
225 $events = \tool_lp\event\competency_deleted::create_multiple_from_competencyids($competencyids, $contextid);
9a325eda
IT
226
227 } catch (\Exception $e) {
228 $transaction->rollback($e);
229 }
d32d64bc
IT
230
231 $transaction->allow_commit();
232 // Trigger events.
233 foreach ($events as $event) {
234 $event->trigger();
235 }
236
237 return true;
d9a39950
DW
238 }
239
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) {
338386aa 249 static::require_enabled();
d9a39950
DW
250 $current = new competency($id);
251
2de75345 252 // First we do a permissions check.
a214d35e 253 require_capability('tool/lp:competencymanage', $current->get_context());
2de75345 254
d9a39950 255 $max = self::count_competencies(array('parentid' => $current->get_parentid(),
c27113d9 256 'competencyframeworkid' => $current->get_competencyframeworkid()));
d9a39950
DW
257 if ($max > 0) {
258 $max--;
259 }
260
261 $sortorder = $current->get_sortorder();
262 if ($sortorder >= $max) {
263 return false;
264 }
265 $sortorder = $sortorder + 1;
266 $current->set_sortorder($sortorder);
267
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 }
276
277 // OK - all set.
d32d64bc 278 $result = $current->update();
aec5363e 279
d32d64bc 280 return $result;
d9a39950
DW
281 }
282
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) {
338386aa 292 static::require_enabled();
d9a39950
DW
293 $current = new competency($id);
294
2de75345 295 // First we do a permissions check.
a214d35e 296 require_capability('tool/lp:competencymanage', $current->get_context());
2de75345 297
d9a39950
DW
298 $sortorder = $current->get_sortorder();
299 if ($sortorder == 0) {
300 return false;
301 }
302
303 $sortorder = $sortorder - 1;
304 $current->set_sortorder($sortorder);
305
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 }
314
315 // OK - all set.
d32d64bc
IT
316 $result = $current->update();
317
318 return $result;
d9a39950
DW
319 }
320
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) {
20c2fe3e 331 global $DB;
338386aa 332 static::require_enabled();
2de75345
FM
333 $current = new competency($id);
334
d9a39950 335 // First we do a permissions check.
a214d35e 336 require_capability('tool/lp:competencymanage', $current->get_context());
20c2fe3e
FM
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.');
d9a39950
DW
341 }
342
20c2fe3e
FM
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/';
347
348 // We're going to change quite a few things.
349 $transaction = $DB->start_delegated_transaction();
d9a39950 350
20c2fe3e
FM
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.
d9a39950
DW
355 $newparents = explode('/', $parentpath);
356 if (in_array($current->get_id(), $newparents)) {
20c2fe3e 357 $children = competency::get_records(array('parentid' => $current->get_id()), 'id');
d9a39950
DW
358 foreach ($children as $child) {
359 $child->set_parentid($current->get_parentid());
360 $child->update();
361 }
20c2fe3e
FM
362
363 // Reset the rule on self as our children have changed.
364 $current->reset_rule();
365
366 // The destination parent is one of our descendants, we need to re-fetch its values (path, parentid).
367 $parent->read();
d9a39950
DW
368 }
369
20c2fe3e
FM
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 }
379
380 // Do the actual move.
d9a39950 381 $current->set_parentid($newparentid);
d32d64bc 382 $result = $current->update();
d9a39950 383
20c2fe3e
FM
384 // All right, let's commit this.
385 $transaction->allow_commit();
386
d32d64bc 387 return $result;
d9a39950
DW
388 }
389
390 /**
391 * Update the details for a competency.
392 *
393 * Requires tool/lp:competencymanage capability at the system context.
394 *
b17d3d10
AA
395 * @param stdClass $record The new details for the competency.
396 * Note - must contain an id that points to the competency to update.
397 *
d9a39950
DW
398 * @return boolean
399 */
400 public static function update_competency($record) {
338386aa 401 static::require_enabled();
1896274f 402 $competency = new competency($record->id);
2de75345 403
d9a39950 404 // First we do a permissions check.
a214d35e 405 require_capability('tool/lp:competencymanage', $competency->get_context());
d9a39950
DW
406
407 // Some things should not be changed in an update - they should use a more specific method.
1896274f
FM
408 $record->sortorder = $competency->get_sortorder();
409 $record->parentid = $competency->get_parentid();
410 $record->competencyframeworkid = $competency->get_competencyframeworkid();
d9a39950 411
1896274f 412 $competency->from_record($record);
a214d35e 413 require_capability('tool/lp:competencymanage', $competency->get_context());
2de75345
FM
414
415 // OK - all set.
d32d64bc
IT
416 $result = $competency->update();
417
418 // Trigger the update event.
419 \tool_lp\event\competency_updated::create_from_competency($competency)->trigger();
420
421 return $result;
d9a39950
DW
422 }
423
424 /**
425 * Read a the details for a single competency and return a record.
426 *
44e8cba4 427 * Requires tool/lp:competencyview capability at the system context.
d9a39950
DW
428 *
429 * @param int $id The id of the competency to read.
4f815459 430 * @param bool $includerelated Include related tags or not.
d9a39950
DW
431 * @return stdClass
432 */
4f815459 433 public static function read_competency($id, $includerelated = false) {
338386aa 434 static::require_enabled();
2de75345
FM
435 $competency = new competency($id);
436
d9a39950 437 // First we do a permissions check.
a214d35e 438 $context = $competency->get_context();
44e8cba4
SG
439 if (!has_any_capability(array('tool/lp:competencyview', 'tool/lp:competencymanage'), $context)) {
440 throw new required_capability_exception($context, 'tool/lp:competencyview', 'nopermissions', '');
d9a39950
DW
441 }
442
443 // OK - all set.
4f815459
DM
444 if ($includerelated) {
445 $relatedcompetency = new related_competency();
446 if ($related = $relatedcompetency->list_relations($id)) {
447 $competency->relatedcompetencies = $related;
448 }
449 }
450
2de75345 451 return $competency;
d9a39950
DW
452 }
453
454 /**
455 * Perform a text search based and return all results and their parents.
456 *
44e8cba4 457 * Requires tool/lp:competencyview capability at the framework context.
d9a39950
DW
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 */
aec5363e 463 public static function search_competencies($textsearch, $competencyframeworkid) {
338386aa 464 static::require_enabled();
2de75345
FM
465 $framework = new competency_framework($competencyframeworkid);
466
d9a39950 467 // First we do a permissions check.
2de75345 468 $context = $framework->get_context();
44e8cba4
SG
469 if (!has_any_capability(array('tool/lp:competencyview', 'tool/lp:competencymanage'), $context)) {
470 throw new required_capability_exception($context, 'tool/lp:competencyview', 'nopermissions', '');
d9a39950
DW
471 }
472
473 // OK - all set.
4f815459 474 $competencies = competency::search($textsearch, $competencyframeworkid);
4f815459 475 return $competencies;
d9a39950
DW
476 }
477
478 /**
479 * Perform a search based on the provided filters and return a paginated list of records.
480 *
44e8cba4 481 * Requires tool/lp:competencyview capability at some context.
d9a39950
DW
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 */
e90c24d0 490 public static function list_competencies($filters, $sort = '', $order = 'ASC', $skip = 0, $limit = 0) {
338386aa 491 static::require_enabled();
2de75345
FM
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 }
498
d9a39950 499 // First we do a permissions check.
44e8cba4
SG
500 if (!has_any_capability(array('tool/lp:competencyview', 'tool/lp:competencymanage'), $context)) {
501 throw new required_capability_exception($context, 'tool/lp:competencyview', 'nopermissions', '');
d9a39950
DW
502 }
503
504 // OK - all set.
c27113d9 505 return competency::get_records($filters, $sort, $order, $skip, $limit);
d9a39950
DW
506 }
507
508 /**
509 * Perform a search based on the provided filters and return a paginated list of records.
510 *
44e8cba4 511 * Requires tool/lp:competencyview capability at some context.
d9a39950
DW
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) {
338386aa 517 static::require_enabled();
2de75345
FM
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 }
524
d9a39950 525 // First we do a permissions check.
44e8cba4
SG
526 if (!has_any_capability(array('tool/lp:competencyview', 'tool/lp:competencymanage'), $context)) {
527 throw new required_capability_exception($context, 'tool/lp:competencyview', 'nopermissions', '');
d9a39950
DW
528 }
529
530 // OK - all set.
c27113d9 531 return competency::count_records($filters);
d9a39950
DW
532 }
533
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) {
338386aa 543 static::require_enabled();
d9a39950 544 $framework = new competency_framework(0, $record);
2de75345 545 require_capability('tool/lp:competencymanage', $framework->get_context());
a54d5b04
FM
546
547 // Account for different formats of taxonomies.
548 if (isset($record->taxonomies)) {
549 $framework->set_taxonomies($record->taxonomies);
550 }
551
a3acafa0
SG
552 $framework = $framework->create();
553
554 // Trigger a competency framework created event.
555 \tool_lp\event\competency_framework_created::create_from_framework($framework)->trigger();
556
d9a39950
DW
557 return $framework;
558 }
559
c61db7bb
IT
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) {
4c4a8d41 569 global $DB;
338386aa 570 static::require_enabled();
4c4a8d41 571
c61db7bb
IT
572 $framework = new competency_framework($id);
573 require_capability('tool/lp:competencymanage', $framework->get_context());
4c4a8d41
IT
574 // Starting transaction.
575 $transaction = $DB->start_delegated_transaction();
c61db7bb 576
4c4a8d41
IT
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);
a3acafa0 584 $framework = $framework->create();
4c4a8d41
IT
585
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);
589
590 // Copy the related competencies.
591 $relcomps = related_competency::get_multiple_relations(array_keys($matchids));
592
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();
c61db7bb 608 } else {
4c4a8d41
IT
609 // Debugging message when there is no match found.
610 debugging('related competency id not found');
c61db7bb 611 }
c61db7bb 612 }
4c4a8d41
IT
613
614 // Setting rules on duplicated competencies.
615 self::migrate_competency_tree_rules($frameworkcompetency, $matchids);
616
617 $transaction->allow_commit();
618
619 } catch (\Exception $e) {
20e75095 620 $transaction->rollback($e);
c61db7bb 621 }
4c4a8d41 622
a3acafa0
SG
623 // Trigger a competency framework created event.
624 \tool_lp\event\competency_framework_created::create_from_framework($framework)->trigger();
625
c61db7bb
IT
626 return $framework;
627 }
628
d9a39950
DW
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) {
90c7bdaf 638 global $DB;
338386aa 639 static::require_enabled();
2de75345
FM
640 $framework = new competency_framework($id);
641 require_capability('tool/lp:competencymanage', $framework->get_context());
a3acafa0 642
d32d64bc 643 $events = array();
90c7bdaf 644 $competenciesid = competency::get_ids_by_frameworkid($id);
d32d64bc 645 $contextid = $framework->get_contextid();
90c7bdaf
IT
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);
654
655 // Delete the related competencies.
656 related_competency::delete_multiple_relations($competenciesid);
657
658 // Delete the evidences for competencies.
659 user_evidence_competency::delete_by_competencyids($competenciesid);
660 }
661
662 // Create a competency framework deleted event.
663 $event = \tool_lp\event\competency_framework_deleted::create_from_framework($framework);
664 $result = $framework->delete();
665
d32d64bc
IT
666 // Register the deleted events competencies.
667 $events = \tool_lp\event\competency_deleted::create_multiple_from_competencyids($competenciesid, $contextid);
668
90c7bdaf
IT
669 } catch (\Exception $e) {
670 $transaction->rollback($e);
671 }
672
673 // Commit the transaction.
674 $transaction->allow_commit();
675
676 // If all operations are successfull then trigger the delete event.
677 $event->trigger();
a3acafa0 678
d32d64bc
IT
679 // Trigger deleted event competencies.
680 foreach ($events as $event) {
681 $event->trigger();
682 }
683
90c7bdaf 684 return $result;
d9a39950
DW
685 }
686
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) {
338386aa 696 static::require_enabled();
2de75345 697 $framework = new competency_framework($record->id);
a54d5b04 698
2de75345
FM
699 // Check the permissions before update.
700 require_capability('tool/lp:competencymanage', $framework->get_context());
a54d5b04
FM
701
702 // Account for different formats of taxonomies.
2de75345 703 $framework->from_record($record);
a54d5b04
FM
704 if (isset($record->taxonomies)) {
705 $framework->set_taxonomies($record->taxonomies);
706 }
707
a3acafa0
SG
708 // Trigger a competency framework updated event.
709 \tool_lp\event\competency_framework_updated::create_from_framework($framework)->trigger();
710
d9a39950
DW
711 return $framework->update();
712 }
713
714 /**
715 * Read a the details for a single competency framework and return a record.
716 *
44e8cba4 717 * Requires tool/lp:competencyview capability at the system context.
d9a39950
DW
718 *
719 * @param int $id The id of the framework to read.
55683895 720 * @return competency_framework
d9a39950
DW
721 */
722 public static function read_framework($id) {
338386aa 723 static::require_enabled();
2de75345 724 $framework = new competency_framework($id);
44e8cba4
SG
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', '');
d9a39950 727 }
2de75345 728 return $framework;
d9a39950
DW
729 }
730
f73a29c1
IT
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) {
338386aa 738 static::require_enabled();
f73a29c1
IT
739 $framework = $frameworkorid;
740 if (!is_object($framework)) {
741 $framework = new competency_framework($framework);
742 }
44e8cba4
SG
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', '');
f73a29c1
IT
745 }
746 \tool_lp\event\competency_framework_viewed::create_from_framework($framework)->trigger();
747 return true;
748 }
749
3e6a8e16
IT
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) {
338386aa 757 static::require_enabled();
3e6a8e16
IT
758 $competency = $competencyorid;
759 if (!is_object($competency)) {
760 $competency = new competency($competency);
761 }
762
44e8cba4
SG
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', '');
3e6a8e16
IT
765 }
766
767 \tool_lp\event\competency_viewed::create_from_competency($competency)->trigger();
768 return true;
769 }
770
d9a39950
DW
771 /**
772 * Perform a search based on the provided filters and return a paginated list of records.
773 *
44e8cba4 774 * Requires tool/lp:competencyview capability at the system context.
d9a39950 775 *
d9a39950
DW
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)
2de75345
FM
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.
964afa98 786 * @param bool $onlyvisible If true return only visible frameworks
0fe5aac7 787 * @param string $query A string to use to filter down the frameworks.
d9a39950
DW
788 * @return array of competency_framework
789 */
0fe5aac7
FM
790 public static function list_frameworks($sort, $order, $skip, $limit, $context, $includes = 'children',
791 $onlyvisible = false, $query = '') {
2de75345 792 global $DB;
338386aa 793 static::require_enabled();
2de75345 794
f610a957 795 // Get all the relevant contexts.
f0da26a4 796 $contexts = self::get_related_contexts($context, $includes,
44e8cba4 797 array('tool/lp:competencyview', 'tool/lp:competencymanage'));
f610a957
FM
798
799 if (empty($contexts)) {
44e8cba4 800 throw new required_capability_exception($context, 'tool/lp:competencyview', 'nopermissions', '');
f610a957
FM
801 }
802
803 // OK - all set.
f610a957 804 list($insql, $inparams) = $DB->get_in_or_equal(array_keys($contexts), SQL_PARAMS_NAMED);
964afa98
IT
805 $select = "contextid $insql";
806 if ($onlyvisible) {
807 $select .= " AND visible = :visible";
808 $inparams['visible'] = 1;
809 }
810
0fe5aac7
FM
811 if (!empty($query) || is_numeric($query)) {
812 $sqlnamelike = $DB->sql_like('shortname', ':namelike', false);
813 $sqlidnlike = $DB->sql_like('idnumber', ':idnlike', false);
814
815 $select .= " AND ($sqlnamelike OR $sqlidnlike) ";
816 $inparams['namelike'] = '%' . $DB->sql_like_escape($query) . '%';
817 $inparams['idnlike'] = '%' . $DB->sql_like_escape($query) . '%';
818 }
819
964afa98 820 return competency_framework::get_records_select($select, $inparams, $sort . ' ' . $order, '*', $skip, $limit);
f610a957
FM
821 }
822
823 /**
824 * Perform a search based on the provided filters and return a paginated list of records.
825 *
44e8cba4 826 * Requires tool/lp:competencyview capability at the system context.
f610a957
FM
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;
338386aa 838 static::require_enabled();
f610a957
FM
839
840 // Get all the relevant contexts.
f0da26a4 841 $contexts = self::get_related_contexts($context, $includes,
44e8cba4 842 array('tool/lp:competencyview', 'tool/lp:competencymanage'));
f610a957
FM
843
844 if (empty($contexts)) {
44e8cba4 845 throw new required_capability_exception($context, 'tool/lp:competencyview', 'nopermissions', '');
f610a957
FM
846 }
847
848 // OK - all set.
f610a957 849 list($insql, $inparams) = $DB->get_in_or_equal(array_keys($contexts), SQL_PARAMS_NAMED);
b6070264 850 return competency_framework::count_records_select("contextid $insql", $inparams);
f610a957
FM
851 }
852
853 /**
f0da26a4
FM
854 * Fetches all the relevant contexts.
855 *
8ff9ae8d
FM
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.
f610a957
FM
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 */
f0da26a4 869 public static function get_related_contexts($context, $includes, array $hasanycapability = null) {
f610a957 870 global $DB;
338386aa 871 static::require_enabled();
f610a957 872
2de75345
FM
873 if (!in_array($includes, array('children', 'parents', 'self'))) {
874 throw new coding_exception('Invalid parameter value for \'includes\'.');
875 }
8ff9ae8d 876
554b369b
IT
877 // If context user swap it for the context_system.
878 if ($context->contextlevel == CONTEXT_USER) {
8ff9ae8d 879 $context = context_system::instance();
554b369b 880 }
2de75345 881
2de75345
FM
882 $contexts = array($context->id => $context);
883
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();
895
896 } else if ($includes == 'parents') {
897 $children = $context->get_parent_contexts();
898 foreach ($children as $ctx) {
899 $contexts[$ctx->id] = $ctx;
900 }
901 }
902
f610a957
FM
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 }
2de75345
FM
909 }
910 }
911
f610a957 912 return $contexts;
d9a39950
DW
913 }
914
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) {
338386aa 922 static::require_enabled();
d9a39950
DW
923
924 // OK - all set.
46fdd1d8 925 $courses = course_competency::list_courses_min($competencyid);
d9a39950 926 $count = 0;
46fdd1d8 927
d9a39950
DW
928 // Now check permissions on each course.
929 foreach ($courses as $course) {
d660824b 930 if (!self::validate_course($course, false)) {
d9a39950
DW
931 continue;
932 }
933
d660824b 934 $context = context_course::instance($course->id);
44e8cba4 935 $capabilities = array('tool/lp:coursecompetencyview', 'tool/lp:coursecompetencymanage');
d660824b 936 if (!has_any_capability($capabilities, $context)) {
d9a39950
DW
937 continue;
938 }
939
940 $count++;
941 }
942
943 return $count;
944 }
945
db650737
DW
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.
f446b2e1 951 * @return array[int] Array of course modules ids.
db650737
DW
952 */
953 public static function list_course_modules_using_competency($competencyid, $courseid) {
338386aa 954 static::require_enabled();
db650737
DW
955
956 $result = array();
957 self::validate_course($courseid);
958
959 $coursecontext = context_course::instance($courseid);
960
961 // We will not check each module - course permissions should be enough.
44e8cba4 962 $capabilities = array('tool/lp:coursecompetencyview', 'tool/lp:coursecompetencymanage');
db650737 963 if (!has_any_capability($capabilities, $coursecontext)) {
44e8cba4 964 throw new required_capability_exception($coursecontext, 'tool/lp:coursecompetencyview', 'nopermissions', '');
db650737
DW
965 }
966
967 $cmlist = course_module_competency::list_course_modules($competencyid, $courseid);
f446b2e1
DW
968 foreach ($cmlist as $cmid) {
969 if (self::validate_course_module($cmid, false)) {
970 array_push($result, $cmid);
971 }
db650737
DW
972 }
973
974 return $result;
975 }
976
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) {
338386aa 984 static::require_enabled();
db650737
DW
985 $cm = $cmorid;
986 if (!is_object($cmorid)) {
987 $cm = get_coursemodule_from_id('', $cmorid, 0, true, MUST_EXIST);
988 }
989
f446b2e1
DW
990 // Check the user have access to the course module.
991 self::validate_course_module($cm);
db650737
DW
992 $context = context_module::instance($cm->id);
993
44e8cba4 994 $capabilities = array('tool/lp:coursecompetencyview', 'tool/lp:coursecompetencymanage');
db650737 995 if (!has_any_capability($capabilities, $context)) {
44e8cba4 996 throw new required_capability_exception($context, 'tool/lp:coursecompetencyview', 'nopermissions', '');
db650737
DW
997 }
998
999 $result = array();
db650737
DW
1000
1001 $cmclist = course_module_competency::list_course_module_competencies($cm->id);
1002 foreach ($cmclist as $id => $cmc) {
1003 array_push($result, $cmc);
1004 }
1005
1006 return $result;
1007 }
1008
d9a39950
DW
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) {
338386aa 1016 static::require_enabled();
d9a39950
DW
1017
1018 // OK - all set.
46fdd1d8 1019 $courses = course_competency::list_courses($competencyid);
d9a39950 1020 $result = array();
46fdd1d8 1021
d9a39950
DW
1022 // Now check permissions on each course.
1023 foreach ($courses as $id => $course) {
1024 $context = context_course::instance($course->id);
44e8cba4 1025 $capabilities = array('tool/lp:coursecompetencyview', 'tool/lp:coursecompetencymanage');
d9a39950
DW
1026 if (!has_any_capability($capabilities, $context)) {
1027 unset($courses[$id]);
1028 continue;
1029 }
d660824b 1030 if (!self::validate_course($course, false)) {
d9a39950
DW
1031 unset($courses[$id]);
1032 continue;
1033 }
d9a39950
DW
1034 array_push($result, $course);
1035 }
1036
1037 return $result;
1038 }
1039
1040 /**
1041 * Count all the competencies in a course.
1042 *
1043 * @param int $courseid The id of the course to check.
1044 * @return int
1045 */
1046 public static function count_competencies_in_course($courseid) {
338386aa 1047 static::require_enabled();
d4c0a2f6
SG
1048 // Check the user have access to the course.
1049 self::validate_course($courseid);
1050
d9a39950
DW
1051 // First we do a permissions check.
1052 $context = context_course::instance($courseid);
d9a39950 1053
44e8cba4 1054 $capabilities = array('tool/lp:coursecompetencyview', 'tool/lp:coursecompetencymanage');
d9a39950 1055 if (!has_any_capability($capabilities, $context)) {
44e8cba4 1056 throw new required_capability_exception($context, 'tool/lp:coursecompetencyview', 'nopermissions', '');
d9a39950
DW
1057 }
1058
d9a39950 1059 // OK - all set.
964afa98 1060 return course_competency::count_competencies($courseid);
d9a39950
DW
1061 }
1062
1063 /**
d4c0a2f6 1064 * List the competencies associated to a course.
d9a39950 1065 *
d4c0a2f6
SG
1066 * @param mixed $courseorid The course, or its ID.
1067 * @return array( array(
1068 * 'competency' => \tool_lp\competency,
1069 * 'coursecompetency' => \tool_lp\course_competency
1070 * ))
d9a39950 1071 */
d4c0a2f6 1072 public static function list_course_competencies($courseorid) {
338386aa 1073 static::require_enabled();
d4c0a2f6
SG
1074 $course = $courseorid;
1075 if (!is_object($courseorid)) {
1076 $course = get_course($courseorid);
1077 }
1078
1079 // Check the user have access to the course.
1080 self::validate_course($course);
d660824b 1081 $context = context_course::instance($course->id);
d660824b 1082
44e8cba4 1083 $capabilities = array('tool/lp:coursecompetencyview', 'tool/lp:coursecompetencymanage');
d660824b 1084 if (!has_any_capability($capabilities, $context)) {
44e8cba4 1085 throw new required_capability_exception($context, 'tool/lp:coursecompetencyview', 'nopermissions', '');
d660824b
FM
1086 }
1087
d4c0a2f6 1088 $result = array();
d660824b
FM
1089
1090 // TODO We could improve the performance of this into one single query.
964afa98
IT
1091 $coursecompetencies = course_competency::list_course_competencies($course->id);
1092 $competencies = course_competency::list_competencies($course->id);
d9a39950 1093
d4c0a2f6
SG
1094 // Build the return values.
1095 foreach ($coursecompetencies as $key => $coursecompetency) {
d4c0a2f6 1096 $result[] = array(
d660824b 1097 'competency' => $competencies[$coursecompetency->get_competencyid()],
d4c0a2f6
SG
1098 'coursecompetency' => $coursecompetency
1099 );
d9a39950
DW
1100 }
1101
d4c0a2f6 1102 return $result;
d9a39950
DW
1103 }
1104
ad6d3004
FM
1105 /**
1106 * Get a user competency.
1107 *
1108 * @param int $userid The user ID.
1109 * @param int $competencyid The competency ID.
1110 * @return user_competency
1111 */
1112 public static function get_user_competency($userid, $competencyid) {
338386aa 1113 static::require_enabled();
ad6d3004
FM
1114 $existing = user_competency::get_multiple($userid, array($competencyid));
1115 $uc = array_pop($existing);
1116
1117 if (!$uc) {
1118 $uc = user_competency::create_relation($userid, $competencyid);
1119 $uc->create();
1120 }
1121
1122 if (!$uc->can_read()) {
1123 throw new required_capability_exception($uc->get_context(), 'tool/lp:usercompetencyview', 'nopermissions', '');
1124 }
1125 return $uc;
1126 }
1127
1128 /**
1129 * Get a user competency by ID.
1130 *
1131 * @param int $usercompetencyid The user competency ID.
1132 * @return user_competency
1133 */
1134 public static function get_user_competency_by_id($usercompetencyid) {
338386aa 1135 static::require_enabled();
ad6d3004
FM
1136 $uc = new user_competency($usercompetencyid);
1137 if (!$uc->can_read()) {
1138 throw new required_capability_exception($uc->get_context(), 'tool/lp:usercompetencyview', 'nopermissions', '');
1139 }
1140 return $uc;
1141 }
1142
db650737
DW
1143 /**
1144 * List the competencies associated to a course module.
1145 *
1146 * @param mixed $cmorid The course module, or its ID.
1147 * @return array( array(
1148 * 'competency' => \tool_lp\competency,
1149 * 'coursemodulecompetency' => \tool_lp\course_module_competency
1150 * ))
1151 */
1152 public static function list_course_module_competencies($cmorid) {
338386aa 1153 static::require_enabled();
db650737
DW
1154 $cm = $cmorid;
1155 if (!is_object($cmorid)) {
1156 $cm = get_coursemodule_from_id('', $cmorid, 0, true, MUST_EXIST);
1157 }
1158
f446b2e1
DW
1159 // Check the user have access to the course module.
1160 self::validate_course_module($cm);
db650737
DW
1161 $context = context_module::instance($cm->id);
1162
44e8cba4 1163 $capabilities = array('tool/lp:coursecompetencyview', 'tool/lp:coursecompetencymanage');
db650737 1164 if (!has_any_capability($capabilities, $context)) {
44e8cba4 1165 throw new required_capability_exception($context, 'tool/lp:coursecompetencyview', 'nopermissions', '');
db650737
DW
1166 }
1167
1168 $result = array();
1169
1170 // TODO We could improve the performance of this into one single query.
1171 $coursemodulecompetencies = course_competency::list_course_module_competencies($cm->id);
1172 $competencies = course_module_competency::list_competencies($cm->id);
1173
1174 // Build the return values.
1175 foreach ($coursemodulecompetencies as $key => $coursemodulecompetency) {
1176 $result[] = array(
1177 'competency' => $competencies[$coursemodulecompetency->get_competencyid()],
1178 'coursemodulecompetency' => $coursemodulecompetency
1179 );
1180 }
1181
1182 return $result;
1183 }
1184
65a67e23
DW
1185 /**
1186 * Get a user competency in a course.
1187 *
1188 * @param int $courseid The id of the course to check.
1189 * @param int $userid The id of the course to check.
1190 * @param int $competencyid The id of the competency.
1191 * @return user_competency
1192 */
1193 public static function get_user_competency_in_course($courseid, $userid, $competencyid) {
338386aa 1194 static::require_enabled();
65a67e23
DW
1195 // First we do a permissions check.
1196 $context = context_course::instance($courseid);
1197
44e8cba4 1198 $capabilities = array('tool/lp:coursecompetencyview', 'tool/lp:coursecompetencymanage');
65a67e23 1199 if (!has_any_capability($capabilities, $context)) {
44e8cba4 1200 throw new required_capability_exception($context, 'tool/lp:coursecompetencyview', 'nopermissions', '');
3b11d638 1201 } else if (!user_competency::can_read_user_in_course($userid, $courseid)) {
943989c2 1202 throw new required_capability_exception($context, 'tool/lp:usercompetencyview', 'nopermissions', '');
65a67e23
DW
1203 }
1204
1205 // This will throw an exception if the competency does not belong to the course.
1206 $competency = course_competency::get_competency($courseid, $competencyid);
1207
1208 $existing = user_competency::get_multiple($userid, array($competencyid));
1209 // Create missing.
1210 $found = count($existing);
1211 if ($found) {
1212 $uc = array_pop($existing);
1213 } else {
d54c4f8b 1214 $uc = user_competency::create_relation($userid, $competency->get_id());
65a67e23
DW
1215 $uc->create();
1216 }
1217
1218 return $uc;
1219 }
1220
bf780fbf
DW
1221 /**
1222 * List all the user competencies in a course.
1223 *
1224 * @param int $courseid The id of the course to check.
1225 * @param int $userid The id of the course to check.
1226 * @return array of competencies
1227 */
1228 public static function list_user_competencies_in_course($courseid, $userid) {
338386aa 1229 static::require_enabled();
bf780fbf
DW
1230 // First we do a permissions check.
1231 $context = context_course::instance($courseid);
1232 $onlyvisible = 1;
1233
44e8cba4 1234 $capabilities = array('tool/lp:coursecompetencyview', 'tool/lp:coursecompetencymanage');
bf780fbf 1235 if (!has_any_capability($capabilities, $context)) {
44e8cba4 1236 throw new required_capability_exception($context, 'tool/lp:coursecompetencyview', 'nopermissions', '');
943989c2
FM
1237 } else if (!user_competency::can_read_user_in_course($userid, $courseid)) {
1238 throw new required_capability_exception($context, 'tool/lp:usercompetencyview', 'nopermissions', '');
bf780fbf
DW
1239 }
1240
bf780fbf 1241 // OK - all set.
65a67e23 1242 $competencylist = course_competency::list_competencies($courseid, false);
bf780fbf 1243
02f9db88
DW
1244 $existing = user_competency::get_multiple($userid, $competencylist);
1245 // Create missing.
65a67e23 1246 $orderedusercompetencies = array();
02f9db88
DW
1247
1248 $somemissing = false;
1249 foreach ($competencylist as $coursecompetency) {
1250 $found = false;
1251 foreach ($existing as $usercompetency) {
1252 if ($usercompetency->get_competencyid() == $coursecompetency->get_id()) {
1253 $found = true;
65a67e23
DW
1254 $orderedusercompetencies[$usercompetency->get_id()] = $usercompetency;
1255 break;
02f9db88
DW
1256 }
1257 }
1258 if (!$found) {
02f9db88
DW
1259 $uc = user_competency::create_relation($userid, $coursecompetency->get_id());
1260 $uc->create();
65a67e23 1261 $orderedusercompetencies[$uc->get_id()] = $uc;
02f9db88
DW
1262 }
1263 }
02f9db88 1264
65a67e23 1265 return $orderedusercompetencies;
bf780fbf
DW
1266 }
1267
7bb5ccbe
FM
1268 /**
1269 * List the user competencies to review.
1270 *
1271 * The method returns values in this format:
1272 *
1273 * array(
1274 * 'competencies' => array(
1275 * (stdClass)(
1276 * 'usercompetency' => (user_competency),
1277 * 'competency' => (competency),
1278 * 'user' => (user)
1279 * )
1280 * ),
1281 * 'count' => (int)
1282 * )
1283 *
1284 * @param int $skip The number of records to skip.
1285 * @param int $limit The number of results to return.
1286 * @param int $userid The user we're getting the competencies to review for.
1287 * @return array Containing the keys 'count', and 'competencies'. The 'competencies' key contains an object
1288 * which contains 'competency', 'usercompetency' and 'user'.
1289 */
1290 public static function list_user_competencies_to_review($skip = 0, $limit = 50, $userid = null) {
1291 global $DB, $USER;
338386aa 1292 static::require_enabled();
7bb5ccbe
FM
1293 if ($userid === null) {
1294 $userid = $USER->id;
1295 }
1296
1297 $capability = 'tool/lp:usercompetencyreview';
1298 $ucfields = user_competency::get_sql_fields('uc');
1299 $compfields = competency::get_sql_fields('c');
1300 $usercols = array('id') + get_user_fieldnames();
d58eabed
FM
1301 $userfields = array();
1302 foreach ($usercols as $field) {
1303 $userfields[] = "u." . $field . " AS usr_" . $field;
1304 }
1305 $userfields = implode(',', $userfields);
7bb5ccbe
FM
1306
1307 $select = "SELECT $ucfields, $compfields, $userfields";
1308 $countselect = "SELECT COUNT('x')";
1309 $sql = " FROM {" . user_competency::TABLE . "} uc
1310 JOIN {" . competency::TABLE . "} c
1311 ON c.id = uc.competencyid
1312 JOIN {user} u
1313 ON u.id = uc.userid
1314 WHERE (uc.status = :waitingforreview
1315 OR (uc.status = :inreview AND uc.reviewerid = :reviewerid))";
1316 $ordersql = " ORDER BY c.shortname ASC";
1317 $params = array(
1318 'inreview' => user_competency::STATUS_IN_REVIEW,
1319 'reviewerid' => $userid,
1320 'waitingforreview' => user_competency::STATUS_WAITING_FOR_REVIEW,
1321 );
1322 $countsql = $countselect . $sql;
1323
1324 // Primary check to avoid the hard work of getting the users in which the user has permission.
1325 $count = $DB->count_records_sql($countselect . $sql, $params);
1326 if ($count < 1) {
1327 return array('count' => 0, 'competencies' => array());
1328 }
1329
1330 // TODO MDL-52243 Use core function.
1331 list($insql, $inparams) = external::filter_users_with_capability_on_user_context_sql(
1332 $capability, $userid, SQL_PARAMS_NAMED);
1333 $params += $inparams;
1334 $countsql = $countselect . $sql . " AND uc.userid $insql";
1335 $getsql = $select . $sql . " AND uc.userid $insql " . $ordersql;
1336
1337 // Extracting the results.
1338 $competencies = array();
1339 $records = $DB->get_recordset_sql($getsql, $params, $skip, $limit);
1340 foreach ($records as $record) {
1341 $objects = (object) array(
1342 'usercompetency' => new user_competency(0, user_competency::extract_record($record)),
1343 'competency' => new competency(0, competency::extract_record($record)),
1344 'user' => persistent::extract_record($record, 'usr_'),
1345 );
1346 $competencies[] = $objects;
1347 }
1348 $records->close();
1349
1350 return array(
1351 'count' => $DB->count_records_sql($countsql, $params),
1352 'competencies' => $competencies
1353 );
1354 }
1355
db650737
DW
1356 /**
1357 * Add a competency to this course module.
1358 *
1359 * @param mixed $cmorid The course module, or id of the course module
1360 * @param int $competencyid The id of the competency
1361 * @return bool
1362 */
1363 public static function add_competency_to_course_module($cmorid, $competencyid) {
338386aa 1364 static::require_enabled();
db650737
DW
1365 $cm = $cmorid;
1366 if (!is_object($cmorid)) {
1367 $cm = get_coursemodule_from_id('', $cmorid, 0, true, MUST_EXIST);
1368 }
1369
f446b2e1
DW
1370 // Check the user have access to the course module.
1371 self::validate_course_module($cm);
db650737
DW
1372
1373 // First we do a permissions check.
1374 $context = context_module::instance($cm->id);
1375
1376 require_capability('tool/lp:coursecompetencymanage', $context);
1377
1378 // Check that the competency belongs to the course.
1379 $exists = course_competency::get_records(array('courseid' => $cm->course, 'competencyid' => $competencyid));
1380 if (!$exists) {
1381 throw new coding_exception('Cannot add a competency to a module if it does not belong to the course');
1382 }
1383
db650737
DW
1384 $record = new stdClass();
1385 $record->cmid = $cm->id;
1386 $record->competencyid = $competencyid;
1387
1388 $coursemodulecompetency = new course_module_competency();
1389 $exists = $coursemodulecompetency->get_records(array('cmid' => $cm->id, 'competencyid' => $competencyid));
1390 if (!$exists) {
1391 $coursemodulecompetency->from_record($record);
1392 if ($coursemodulecompetency->create()) {
1393 return true;
1394 }
1395 }
1396 return false;
1397 }
1398
1399 /**
1400 * Remove a competency from this course module.
1401 *
1402 * @param mixed $cmorid The course module, or id of the course module
1403 * @param int $competencyid The id of the competency
1404 * @return bool
1405 */
1406 public static function remove_competency_from_course_module($cmorid, $competencyid) {
338386aa 1407 static::require_enabled();
db650737
DW
1408 $cm = $cmorid;
1409 if (!is_object($cmorid)) {
1410 $cm = get_coursemodule_from_id('', $cmorid, 0, true, MUST_EXIST);
1411 }
f446b2e1
DW
1412 // Check the user have access to the course module.
1413 self::validate_course_module($cm);
db650737
DW
1414
1415 // First we do a permissions check.
1416 $context = context_module::instance($cm->id);
1417
1418 require_capability('tool/lp:coursecompetencymanage', $context);
1419
1420 $record = new stdClass();
1421 $record->cmid = $cm->id;
1422 $record->competencyid = $competencyid;
1423
1424 $competency = new competency($competencyid);
f446b2e1 1425 $exists = course_module_competency::get_record(array('cmid' => $cm->id, 'competencyid' => $competencyid));
db650737 1426 if ($exists) {
f446b2e1 1427 return $exists->delete();
db650737
DW
1428 }
1429 return false;
1430 }
1431
1432 /**
1433 * Move the course module competency up or down in the display list.
1434 *
1435 * Requires tool/lp:coursecompetencymanage capability at the course module context.
1436 *
1437 * @param mixed $cmorid The course module, or id of the course module
1438 * @param int $competencyidfrom The id of the competency we are moving.
1439 * @param int $competencyidto The id of the competency we are moving to.
1440 * @return boolean
1441 */
1442 public static function reorder_course_module_competency($cmorid, $competencyidfrom, $competencyidto) {
338386aa 1443 static::require_enabled();
db650737
DW
1444 $cm = $cmorid;
1445 if (!is_object($cmorid)) {
1446 $cm = get_coursemodule_from_id('', $cmorid, 0, true, MUST_EXIST);
1447 }
f446b2e1
DW
1448 // Check the user have access to the course module.
1449 self::validate_course_module($cm);
db650737
DW
1450
1451 // First we do a permissions check.
1452 $context = context_module::instance($cm->id);
1453
1454 require_capability('tool/lp:coursecompetencymanage', $context);
1455
1456 $down = true;
1457 $matches = course_module_competency::get_records(array('cmid' => $cm->id, 'competencyid' => $competencyidfrom));
1458 if (count($matches) == 0) {
1459 throw new coding_exception('The link does not exist');
1460 }
1461
1462 $competencyfrom = array_pop($matches);
1463 $matches = course_module_competency::get_records(array('cmid' => $cm->id, 'competencyid' => $competencyidto));
1464 if (count($matches) == 0) {
1465 throw new coding_exception('The link does not exist');
1466 }
1467
1468 $competencyto = array_pop($matches);
1469
1470 $all = course_module_competency::get_records(array('cmid' => $cm->id), 'sortorder', 'ASC', 0, 0);
1471
1472 if ($competencyfrom->get_sortorder() > $competencyto->get_sortorder()) {
1473 // We are moving up, so put it before the "to" item.
1474 $down = false;
1475 }
1476
1477 foreach ($all as $id => $coursemodulecompetency) {
1478 $sort = $coursemodulecompetency->get_sortorder();
1479 if ($down && $sort > $competencyfrom->get_sortorder() && $sort <= $competencyto->get_sortorder()) {
1480 $coursemodulecompetency->set_sortorder($coursemodulecompetency->get_sortorder() - 1);
1481 $coursemodulecompetency->update();
1482 } else if (!$down && $sort >= $competencyto->get_sortorder() && $sort < $competencyfrom->get_sortorder()) {
1483 $coursemodulecompetency->set_sortorder($coursemodulecompetency->get_sortorder() + 1);
1484 $coursemodulecompetency->update();
1485 }
1486 }
1487 $competencyfrom->set_sortorder($competencyto->get_sortorder());
1488 return $competencyfrom->update();
1489 }
1490
1491 /**
1492 * Update ruleoutcome value for a course module competency.
1493 *
1494 * @param int|course_module_competency $coursemodulecompetencyorid The course_module_competency, or its ID.
1495 * @param int $ruleoutcome The value of ruleoutcome.
1496 * @return bool True on success.
1497 */
1498 public static function set_course_module_competency_ruleoutcome($coursemodulecompetencyorid, $ruleoutcome) {
338386aa 1499 static::require_enabled();
db650737
DW
1500 $coursemodulecompetency = $coursemodulecompetencyorid;
1501 if (!is_object($coursemodulecompetency)) {
1502 $coursemodulecompetency = new course_module_competency($coursemodulecompetencyorid);
1503 }
1504
1505 $cm = get_coursemodule_from_id('', $coursemodulecompetency->get_cmid(), 0, true, MUST_EXIST);
1506
f446b2e1 1507 self::validate_course_module($cm);
db650737
DW
1508 $context = context_module::instance($cm->id);
1509
1510 require_capability('tool/lp:coursecompetencymanage', $context);
1511
1512 $coursemodulecompetency->set_ruleoutcome($ruleoutcome);
1513 return $coursemodulecompetency->update();
1514 }
1515
d9a39950
DW
1516 /**
1517 * Add a competency to this course.
1518 *
1519 * @param int $courseid The id of the course
1520 * @param int $competencyid The id of the competency
1521 * @return bool
1522 */
1523 public static function add_competency_to_course($courseid, $competencyid) {
338386aa 1524 static::require_enabled();
d4c0a2f6
SG
1525 // Check the user have access to the course.
1526 self::validate_course($courseid);
1527
d9a39950
DW
1528 // First we do a permissions check.
1529 $context = context_course::instance($courseid);
1530
e90c24d0
MN
1531 require_capability('tool/lp:coursecompetencymanage', $context);
1532
d9a39950
DW
1533 $record = new stdClass();
1534 $record->courseid = $courseid;
1535 $record->competencyid = $competencyid;
1536
964afa98
IT
1537 $competency = new competency($competencyid);
1538
1539 // Can not add a competency that belong to a hidden framework.
1540 if ($competency->get_framework()->get_visible() == false) {
1541 throw new coding_exception('A competency belonging to hidden framework can not be linked to course');
d9a39950
DW
1542 }
1543
1544 $coursecompetency = new course_competency();
1545 $exists = $coursecompetency->get_records(array('courseid' => $courseid, 'competencyid' => $competencyid));
1546 if (!$exists) {
1547 $coursecompetency->from_record($record);
1548 if ($coursecompetency->create()) {
1549 return true;
1550 }
1551 }
1552 return false;
1553 }
1554
1555 /**
1556 * Remove a competency from this course.
1557 *
1558 * @param int $courseid The id of the course
1559 * @param int $competencyid The id of the competency
1560 * @return bool
1561 */
1562 public static function remove_competency_from_course($courseid, $competencyid) {
338386aa 1563 static::require_enabled();
d4c0a2f6
SG
1564 // Check the user have access to the course.
1565 self::validate_course($courseid);
1566
d9a39950
DW
1567 // First we do a permissions check.
1568 $context = context_course::instance($courseid);
1569
e90c24d0
MN
1570 require_capability('tool/lp:coursecompetencymanage', $context);
1571
d9a39950
DW
1572 $record = new stdClass();
1573 $record->courseid = $courseid;
1574 $record->competencyid = $competencyid;
1575
964afa98 1576 $competency = new competency($competencyid);
d9a39950 1577 $coursecompetency = new course_competency();
f446b2e1 1578 $exists = $coursecompetency->get_record(array('courseid' => $courseid, 'competencyid' => $competencyid));
d9a39950 1579 if ($exists) {
f446b2e1
DW
1580 // Delete all course_module_competencies for this competency in this course.
1581 $cmcs = course_module_competency::list_course_module_competencies($competencyid, $courseid);
1582 foreach ($cmcs as $cmc) {
1583 $cmc->delete();
1584 }
1585 return $exists->delete();
d9a39950
DW
1586 }
1587 return false;
1588 }
1589
1590 /**
1591 * Move the course competency up or down in the display list.
1592 *
1593 * Requires tool/lp:coursecompetencymanage capability at the course context.
1594 *
1595 * @param int $courseid The course
1596 * @param int $competencyidfrom The id of the competency we are moving.
1597 * @param int $competencyidto The id of the competency we are moving to.
1598 * @return boolean
1599 */
1600 public static function reorder_course_competency($courseid, $competencyidfrom, $competencyidto) {
338386aa 1601 static::require_enabled();
d4c0a2f6
SG
1602 // Check the user have access to the course.
1603 self::validate_course($courseid);
1604
d9a39950
DW
1605 // First we do a permissions check.
1606 $context = context_course::instance($courseid);
1607
e90c24d0 1608 require_capability('tool/lp:coursecompetencymanage', $context);
d9a39950
DW
1609
1610 $down = true;
1611 $coursecompetency = new course_competency();
1612 $matches = $coursecompetency->get_records(array('courseid' => $courseid, 'competencyid' => $competencyidfrom));
1613 if (count($matches) == 0) {
1614 throw new coding_exception('The link does not exist');
1615 }
1616
1617 $competencyfrom = array_pop($matches);
1618 $matches = $coursecompetency->get_records(array('courseid' => $courseid, 'competencyid' => $competencyidto));
1619 if (count($matches) == 0) {
1620 throw new coding_exception('The link does not exist');
1621 }
1622
1623 $competencyto = array_pop($matches);
1624
1625 $all = $coursecompetency->get_records(array('courseid' => $courseid), 'sortorder', 'ASC', 0, 0);
1626
1627 if ($competencyfrom->get_sortorder() > $competencyto->get_sortorder()) {
1628 // We are moving up, so put it before the "to" item.
1629 $down = false;
1630 }
1631
1632 foreach ($all as $id => $coursecompetency) {
1633 $sort = $coursecompetency->get_sortorder();
1634 if ($down && $sort > $competencyfrom->get_sortorder() && $sort <= $competencyto->get_sortorder()) {
1635 $coursecompetency->set_sortorder($coursecompetency->get_sortorder() - 1);
1636 $coursecompetency->update();
1637 } else if (!$down && $sort >= $competencyto->get_sortorder() && $sort < $competencyfrom->get_sortorder()) {
1638 $coursecompetency->set_sortorder($coursecompetency->get_sortorder() + 1);
1639 $coursecompetency->update();
1640 }
1641 }
1642 $competencyfrom->set_sortorder($competencyto->get_sortorder());
1643 return $competencyfrom->update();
1644 }
1645
d660824b
FM
1646 /**
1647 * Update ruleoutcome value for a course competency.
1648 *
1649 * @param int|course_competency $coursecompetencyorid The course_competency, or its ID.
1650 * @param int $ruleoutcome The value of ruleoutcome.
1651 * @return bool True on success.
1652 */
1653 public static function set_course_competency_ruleoutcome($coursecompetencyorid, $ruleoutcome) {
338386aa 1654 static::require_enabled();
d660824b
FM
1655 $coursecompetency = $coursecompetencyorid;
1656 if (!is_object($coursecompetency)) {
1657 $coursecompetency = new course_competency($coursecompetencyorid);
1658 }
1659
1660 $courseid = $coursecompetency->get_courseid();
1661 self::validate_course($courseid);
1662 $coursecontext = context_course::instance($courseid);
1663
1664 require_capability('tool/lp:coursecompetencymanage', $coursecontext);
1665
1666 $coursecompetency->set_ruleoutcome($ruleoutcome);
1667 return $coursecompetency->update();
1668 }
1669
d9a39950
DW
1670 /**
1671 * Create a learning plan template from a record containing all the data for the class.
1672 *
f0da26a4 1673 * Requires tool/lp:templatemanage capability.
d9a39950
DW
1674 *
1675 * @param stdClass $record Record containing all the data for an instance of the class.
1676 * @return template
1677 */
1678 public static function create_template(stdClass $record) {
338386aa 1679 static::require_enabled();
f0da26a4
FM
1680 $template = new template(0, $record);
1681
d9a39950 1682 // First we do a permissions check.
f85ea576
FM
1683 if (!$template->can_manage()) {
1684 throw new required_capability_exception($template->get_context(), 'tool/lp:templatemanage', 'nopermissions', '');
1685 }
d9a39950
DW
1686
1687 // OK - all set.
85e45ff8
SG
1688 $template = $template->create();
1689
1690 // Trigger a template created event.
1691 \tool_lp\event\template_created::create_from_template($template)->trigger();
1692
d9a39950
DW
1693 return $template;
1694 }
1695
76c107ea
IT
1696 /**
1697 * Duplicate a learning plan template.
1698 *
1699 * Requires tool/lp:templatemanage capability at the template context.
1700 *
1701 * @param int $id the template id.
1702 * @return template
1703 */
1704 public static function duplicate_template($id) {
338386aa 1705 static::require_enabled();
76c107ea
IT
1706 $template = new template($id);
1707
1708 // First we do a permissions check.
f85ea576
FM
1709 if (!$template->can_manage()) {
1710 throw new required_capability_exception($template->get_context(), 'tool/lp:templatemanage', 'nopermissions', '');
1711 }
76c107ea
IT
1712
1713 // OK - all set.
14d9abc2 1714 $competencies = template_competency::list_competencies($id, false);
76c107ea
IT
1715
1716 // Adding the suffix copy.
1717 $template->set_shortname(get_string('duplicateditemname', 'tool_lp', $template->get_shortname()));
03c66658 1718 $template->set_id(0);
76c107ea
IT
1719
1720 $duplicatedtemplate = $template->create();
1721
1722 // Associate each competency for the duplicated template.
1723 foreach ($competencies as $competency) {
1724 self::add_competency_to_template($duplicatedtemplate->get_id(), $competency->get_id());
1725 }
85e45ff8
SG
1726
1727 // Trigger a template created event.
1728 \tool_lp\event\template_created::create_from_template($duplicatedtemplate)->trigger();
1729
76c107ea
IT
1730 return $duplicatedtemplate;
1731 }
1732
d9a39950
DW
1733 /**
1734 * Delete a learning plan template by id.
5a4ee001 1735 * If the learning plan template has associated cohorts they will be deleted.
d9a39950 1736 *
f0da26a4 1737 * Requires tool/lp:templatemanage capability.
d9a39950
DW
1738 *
1739 * @param int $id The record to delete.
8fabc738 1740 * @param boolean $deleteplans True to delete plans associaated to template, false to unlink them.
d9a39950
DW
1741 * @return boolean
1742 */
8fabc738 1743 public static function delete_template($id, $deleteplans = true) {
5a4ee001 1744 global $DB;
338386aa 1745 static::require_enabled();
f0da26a4
FM
1746 $template = new template($id);
1747
d9a39950 1748 // First we do a permissions check.
f85ea576
FM
1749 if (!$template->can_manage()) {
1750 throw new required_capability_exception($template->get_context(), 'tool/lp:templatemanage', 'nopermissions', '');
1751 }
d9a39950 1752
5a4ee001
IT
1753 $transaction = $DB->start_delegated_transaction();
1754 $success = true;
1755
1756 // Check if there are cohorts associated.
1757 $templatecohorts = template_cohort::get_relations_by_templateid($template->get_id());
1758 foreach ($templatecohorts as $templatecohort) {
1759 $success = $templatecohort->delete();
1760 if (!$success) {
1761 break;
1762 }
1763 }
1764
8fabc738
SG
1765 // Still OK, delete or unlink the plans from the template.
1766 if ($success) {
1767 $plans = plan::get_records(array('templateid' => $template->get_id()));
1768 foreach ($plans as $plan) {
1769 $success = $deleteplans ? self::delete_plan($plan->get_id()) : self::unlink_plan_from_template($plan);
1770 if (!$success) {
1771 break;
1772 }
1773 }
1774 }
1775
1776 // Still OK, delete the template comptencies.
1777 if ($success) {
1778 $success = template_competency::delete_by_templateid($template->get_id());
1779 }
1780
d9a39950 1781 // OK - all set.
5a4ee001 1782 if ($success) {
85e45ff8
SG
1783 // Create a template deleted event.
1784 $event = \tool_lp\event\template_deleted::create_from_template($template);
1785
5a4ee001
IT
1786 $success = $template->delete();
1787 }
1788
1789 if ($success) {
85e45ff8
SG
1790 // Trigger a template deleted event.
1791 $event->trigger();
1792
5a4ee001
IT
1793 // Commit the transaction.
1794 $transaction->allow_commit();
1795 } else {
1796 $transaction->rollback(new moodle_exception('Error while deleting the template.'));
1797 }
1798
1799 return $success;
d9a39950
DW
1800 }
1801
1802 /**
1803 * Update the details for a learning plan template.
1804 *
f0da26a4 1805 * Requires tool/lp:templatemanage capability.
d9a39950
DW
1806 *
1807 * @param stdClass $record The new details for the template. Note - must contain an id that points to the template to update.
1808 * @return boolean
1809 */
1810 public static function update_template($record) {
c7999f6d 1811 global $DB;
338386aa 1812 static::require_enabled();
f0da26a4
FM
1813 $template = new template($record->id);
1814
d9a39950 1815 // First we do a permissions check.
f85ea576
FM
1816 if (!$template->can_manage()) {
1817 throw new required_capability_exception($template->get_context(), 'tool/lp:templatemanage', 'nopermissions', '');
1818
f85ea576 1819 } else if (isset($record->contextid) && $record->contextid != $template->get_contextid()) {
d805cc37 1820 // We can never change the context of a template.
f0da26a4 1821 throw new coding_exception('Changing the context of an existing tempalte is forbidden.');
f85ea576 1822
f0da26a4 1823 }
d9a39950 1824
c7999f6d
FM
1825 $updateplans = false;
1826 $before = $template->to_record();
1827
f0da26a4 1828 $template->from_record($record);
c7999f6d
FM
1829 $after = $template->to_record();
1830
1831 // Should we update the related plans?
1832 if ($before->duedate != $after->duedate ||
1833 $before->shortname != $after->shortname ||
1834 $before->description != $after->description ||
1835 $before->descriptionformat != $after->descriptionformat) {
1836 $updateplans = true;
1837 }
1838
1839 $transaction = $DB->start_delegated_transaction();
1840 $success = $template->update();
1841
1842 if (!$success) {
1843 $transaction->rollback(new moodle_exception('Error while updating the template.'));
1844 return $success;
1845 }
1846
85e45ff8
SG
1847 // Trigger a template updated event.
1848 \tool_lp\event\template_updated::create_from_template($template)->trigger();
1849
c7999f6d
FM
1850 if ($updateplans) {
1851 plan::update_multiple_from_template($template);
1852 }
1853
1854 $transaction->allow_commit();
1855
1856 return $success;
d9a39950
DW
1857 }
1858
1859 /**
1860 * Read a the details for a single learning plan template and return a record.
1861 *
44e8cba4 1862 * Requires tool/lp:templateview capability at the system context.
d9a39950
DW
1863 *
1864 * @param int $id The id of the template to read.
55683895 1865 * @return template
d9a39950
DW
1866 */
1867 public static function read_template($id) {
338386aa 1868 static::require_enabled();
f0da26a4
FM
1869 $template = new template($id);
1870 $context = $template->get_context();
1871
d9a39950 1872 // First we do a permissions check.
f85ea576 1873 if (!$template->can_read()) {
44e8cba4 1874 throw new required_capability_exception($template->get_context(), 'tool/lp:templateview', 'nopermissions', '');
d9a39950
DW
1875 }
1876
1877 // OK - all set.
f0da26a4 1878 return $template;
d9a39950
DW
1879 }
1880
d9a39950
DW
1881 /**
1882 * Perform a search based on the provided filters and return a paginated list of records.
1883 *
44e8cba4 1884 * Requires tool/lp:templateview capability at the system context.
d9a39950 1885 *
d9a39950
DW
1886 * @param string $sort The column to sort on
1887 * @param string $order ('ASC' or 'DESC')
1888 * @param int $skip Number of records to skip (pagination)
1889 * @param int $limit Max of records to return (pagination)
f0da26a4
FM
1890 * @param context $context The parent context of the frameworks.
1891 * @param string $includes Defines what other contexts to fetch frameworks from.
1892 * Accepted values are:
1893 * - children: All descendants
1894 * - parents: All parents, grand parents, etc...
1895 * - self: Context passed only.
964afa98 1896 * @param bool $onlyvisible If should list only visible templates
d9a39950
DW
1897 * @return array of competency_framework
1898 */
964afa98 1899 public static function list_templates($sort, $order, $skip, $limit, $context, $includes = 'children', $onlyvisible = false) {
f0da26a4 1900 global $DB;
338386aa 1901 static::require_enabled();
f0da26a4
FM
1902
1903 // Get all the relevant contexts.
1904 $contexts = self::get_related_contexts($context, $includes,
44e8cba4 1905 array('tool/lp:templateview', 'tool/lp:templatemanage'));
f0da26a4 1906
d9a39950 1907 // First we do a permissions check.
f0da26a4 1908 if (empty($contexts)) {
44e8cba4 1909 throw new required_capability_exception($context, 'tool/lp:templateview', 'nopermissions', '');
d9a39950
DW
1910 }
1911
f0da26a4
FM
1912 // Make the order by.
1913 $orderby = '';
1914 if (!empty($sort)) {
1915 $orderby = $sort . ' ' . $order;
1916 }
1917
d9a39950
DW
1918 // OK - all set.
1919 $template = new template();
964afa98
IT
1920 list($insql, $params) = $DB->get_in_or_equal(array_keys($contexts), SQL_PARAMS_NAMED);
1921 $select = "contextid $insql";
1922
1923 if ($onlyvisible) {
1924 $select .= " AND visible = :visible";
1925 $params['visible'] = 1;
1926 }
1927 return $template->get_records_select($select, $params, $orderby, '*', $skip, $limit);
d9a39950
DW
1928 }
1929
1930 /**
1931 * Perform a search based on the provided filters and return how many results there are.
1932 *
44e8cba4 1933 * Requires tool/lp:templateview capability at the system context.
d9a39950 1934 *
f0da26a4
FM
1935 * @param context $context The parent context of the frameworks.
1936 * @param string $includes Defines what other contexts to fetch frameworks from.
1937 * Accepted values are:
1938 * - children: All descendants
1939 * - parents: All parents, grand parents, etc...
1940 * - self: Context passed only.
d9a39950
DW
1941 * @return int
1942 */
f0da26a4
FM
1943 public static function count_templates($context, $includes) {
1944 global $DB;
338386aa 1945 static::require_enabled();
f0da26a4 1946
d9a39950 1947 // First we do a permissions check.
f0da26a4 1948 $contexts = self::get_related_contexts($context, $includes,
44e8cba4 1949 array('tool/lp:templateview', 'tool/lp:templatemanage'));
f0da26a4
FM
1950
1951 if (empty($contexts)) {
44e8cba4 1952 throw new required_capability_exception($context, 'tool/lp:templateview', 'nopermissions', '');
d9a39950
DW
1953 }
1954
1955 // OK - all set.
1956 $template = new template();
f0da26a4
FM
1957 list($insql, $inparams) = $DB->get_in_or_equal(array_keys($contexts), SQL_PARAMS_NAMED);
1958 return $template->count_records_select("contextid $insql", $inparams);
d9a39950
DW
1959 }
1960
1961 /**
1962 * Count all the templates using a competency.
1963 *
1964 * @param int $competencyid The id of the competency to check.
1965 * @return int
1966 */
1967 public static function count_templates_using_competency($competencyid) {
338386aa 1968 static::require_enabled();
d9a39950
DW
1969 // First we do a permissions check.
1970 $context = context_system::instance();
1971 $onlyvisible = 1;
1972
44e8cba4 1973 $capabilities = array('tool/lp:templateview', 'tool/lp:templatemanage');
d9a39950 1974 if (!has_any_capability($capabilities, $context)) {
44e8cba4 1975 throw new required_capability_exception($context, 'tool/lp:templateview', 'nopermissions', '');
d9a39950
DW
1976 }
1977
1978 if (has_capability('tool/lp:templatemanage', $context)) {
1979 $onlyvisible = 0;
1980 }
1981
1982 // OK - all set.
cc643d16 1983 return template_competency::count_templates($competencyid, $onlyvisible);
d9a39950
DW
1984 }
1985
1986 /**
1987 * List all the learning plan templatesd using a competency.
1988 *
1989 * @param int $competencyid The id of the competency to check.
1990 * @return array[stdClass] Array of stdClass containing id and shortname.
1991 */
1992 public static function list_templates_using_competency($competencyid) {
338386aa 1993 static::require_enabled();
d9a39950
DW
1994 // First we do a permissions check.
1995 $context = context_system::instance();
1996 $onlyvisible = 1;
1997
44e8cba4 1998 $capabilities = array('tool/lp:templateview', 'tool/lp:templatemanage');
d9a39950 1999 if (!has_any_capability($capabilities, $context)) {
44e8cba4 2000 throw new required_capability_exception($context, 'tool/lp:templateview', 'nopermissions', '');
d9a39950
DW
2001 }
2002
2003 if (has_capability('tool/lp:templatemanage', $context)) {
2004 $onlyvisible = 0;
2005 }
2006
2007 // OK - all set.
cc643d16 2008 return template_competency::list_templates($competencyid, $onlyvisible);
d9a39950
DW
2009
2010 }
2011
2012 /**
2013 * Count all the competencies in a learning plan template.
2014 *
3edcd295 2015 * @param template|int $templateorid The template or its ID.
d9a39950
DW
2016 * @return int
2017 */
3edcd295 2018 public static function count_competencies_in_template($templateorid) {
338386aa 2019 static::require_enabled();
d9a39950 2020 // First we do a permissions check.
3edcd295
DW
2021 $template = $templateorid;
2022 if (!is_object($template)) {
2023 $template = new template($template);
2024 }
2025
2026 if (!$template->can_read()) {
44e8cba4 2027 throw new required_capability_exception($template->get_context(), 'tool/lp:templateview', 'nopermissions', '');
3edcd295
DW
2028 }
2029
2030 // OK - all set.
2031 return template_competency::count_competencies($template->get_id());
2032 }
2033
2034 /**
2035 * Count all the competencies in a learning plan template with no linked courses.
2036 *
2037 * @param template|int $templateorid The template or its ID.
2038 * @return int
2039 */
2040 public static function count_competencies_in_template_with_no_courses($templateorid) {
2041 // First we do a permissions check.
2042 $template = $templateorid;
2043 if (!is_object($template)) {
2044 $template = new template($template);
2045 }
d9a39950 2046
f85ea576 2047 if (!$template->can_read()) {
44e8cba4 2048 throw new required_capability_exception($template->get_context(), 'tool/lp:templateview', 'nopermissions', '');
d9a39950
DW
2049 }
2050
d9a39950 2051 // OK - all set.
3edcd295 2052 return template_competency::count_competencies_with_no_courses($template->get_id());
d9a39950
DW
2053 }
2054
2055 /**
2056 * List all the competencies in a template.
2057 *
3edcd295 2058 * @param template|int $templateorid The template or its ID.
d9a39950
DW
2059 * @return array of competencies
2060 */
3edcd295 2061 public static function list_competencies_in_template($templateorid) {
338386aa 2062 static::require_enabled();
d9a39950 2063 // First we do a permissions check.
3edcd295
DW
2064 $template = $templateorid;
2065 if (!is_object($template)) {
2066 $template = new template($template);
2067 }
d9a39950 2068
f85ea576 2069 if (!$template->can_read()) {
44e8cba4 2070 throw new required_capability_exception($template->get_context(), 'tool/lp:templateview', 'nopermissions', '');
d9a39950
DW
2071 }
2072
d9a39950 2073 // OK - all set.
3edcd295 2074 return template_competency::list_competencies($template->get_id());
d9a39950
DW
2075 }
2076
2077 /**
2078 * Add a competency to this template.
2079 *
2080 * @param int $templateid The id of the template
2081 * @param int $competencyid The id of the competency
2082 * @return bool
2083 */
2084 public static function add_competency_to_template($templateid, $competencyid) {
338386aa 2085 static::require_enabled();
d9a39950 2086 // First we do a permissions check.
f0da26a4 2087 $template = new template($templateid);
f85ea576
FM
2088 if (!$template->can_manage()) {
2089 throw new required_capability_exception($template->get_context(), 'tool/lp:templatemanage', 'nopermissions', '');
2090 }
d9a39950
DW
2091
2092 $record = new stdClass();
2093 $record->templateid = $templateid;
2094 $record->competencyid = $competencyid;
2095
964afa98
IT
2096 $competency = new competency($competencyid);
2097
2098 // Can not add a competency that belong to a hidden framework.
2099 if ($competency->get_framework()->get_visible() == false) {
2100 throw new coding_exception('A competency belonging to hidden framework can not be added');
d9a39950
DW
2101 }
2102
cc643d16 2103 $exists = template_competency::get_records(array('templateid' => $templateid, 'competencyid' => $competencyid));
d9a39950 2104 if (!$exists) {
cc643d16
FM
2105 $templatecompetency = new template_competency(0, $record);
2106 $templatecompetency->create();
2107 return true;
d9a39950
DW
2108 }
2109 return false;
2110 }
2111
2112 /**
2113 * Remove a competency from this template.
2114 *
2115 * @param int $templateid The id of the template
2116 * @param int $competencyid The id of the competency
2117 * @return bool
2118 */
2119 public static function remove_competency_from_template($templateid, $competencyid) {
338386aa 2120 static::require_enabled();
d9a39950 2121 // First we do a permissions check.
f0da26a4 2122 $template = new template($templateid);
f85ea576
FM
2123 if (!$template->can_manage()) {
2124 throw new required_capability_exception($template->get_context(), 'tool/lp:templatemanage', 'nopermissions', '');
2125 }
d9a39950
DW
2126
2127 $record = new stdClass();
2128 $record->templateid = $templateid;
2129 $record->competencyid = $competencyid;
2130
f0da26a4 2131 $competency = new competency($competencyid);
d9a39950 2132
cc643d16 2133 $exists = template_competency::get_records(array('templateid' => $templateid, 'competencyid' => $competencyid));
d9a39950
DW
2134 if ($exists) {
2135 $link = array_pop($exists);
2136 return $link->delete();
2137 }
2138 return false;
2139 }
4db373d5 2140
55683895
MN
2141 /**
2142 * Move the template competency up or down in the display list.
2143 *
2144 * Requires tool/lp:templatemanage capability at the system context.
2145 *
2146 * @param int $templateid The template id
2147 * @param int $competencyidfrom The id of the competency we are moving.
2148 * @param int $competencyidto The id of the competency we are moving to.
2149 * @return boolean
2150 */
2151 public static function reorder_template_competency($templateid, $competencyidfrom, $competencyidto) {
338386aa 2152 static::require_enabled();
55683895
MN
2153 // First we do a permissions check.
2154 $context = context_system::instance();
2155
2156 require_capability('tool/lp:templatemanage', $context);
2157
2158 $down = true;
cc643d16 2159 $matches = template_competency::get_records(array('templateid' => $templateid, 'competencyid' => $competencyidfrom));
55683895
MN
2160 if (count($matches) == 0) {
2161 throw new coding_exception('The link does not exist');
2162 }
2163
2164 $competencyfrom = array_pop($matches);
cc643d16 2165 $matches = template_competency::get_records(array('templateid' => $templateid, 'competencyid' => $competencyidto));
55683895
MN
2166 if (count($matches) == 0) {
2167 throw new coding_exception('The link does not exist');
2168 }
2169
2170 $competencyto = array_pop($matches);
2171
cc643d16 2172 $all = template_competency::get_records(array('templateid' => $templateid), 'sortorder', 'ASC', 0, 0);
55683895
MN
2173
2174 if ($competencyfrom->get_sortorder() > $competencyto->get_sortorder()) {
2175 // We are moving up, so put it before the "to" item.
2176 $down = false;
2177 }
2178
2179 foreach ($all as $id => $templatecompetency) {
2180 $sort = $templatecompetency->get_sortorder();
2181 if ($down && $sort > $competencyfrom->get_sortorder() && $sort <= $competencyto->get_sortorder()) {
2182 $templatecompetency->set_sortorder($templatecompetency->get_sortorder() - 1);
2183 $templatecompetency->update();
2184 } else if (!$down && $sort >= $competencyto->get_sortorder() && $sort < $competencyfrom->get_sortorder()) {
2185 $templatecompetency->set_sortorder($templatecompetency->get_sortorder() + 1);
2186 $templatecompetency->update();
2187 }
2188 }
2189 $competencyfrom->set_sortorder($competencyto->get_sortorder());
2190 return $competencyfrom->update();
2191 }
2192
bee480a4
FM
2193 /**
2194 * Create a relation between a template and a cohort.
2195 *
2196 * This silently ignores when the relation already existed.
2197 *
2198 * @param template|int $templateorid The template or its ID.
96c2b847 2199 * @param stdClass|int $cohortorid The cohort ot its ID.
bee480a4
FM
2200 * @return template_cohort
2201 */
2202 public static function create_template_cohort($templateorid, $cohortorid) {
2203 global $DB;
338386aa 2204 static::require_enabled();
bee480a4
FM
2205
2206 $template = $templateorid;
2207 if (!is_object($template)) {
2208 $template = new template($template);
2209 }
2210 require_capability('tool/lp:templatemanage', $template->get_context());
2211
2212 $cohort = $cohortorid;
2213 if (!is_object($cohort)) {
2214 $cohort = $DB->get_record('cohort', array('id' => $cohort), '*', MUST_EXIST);
2215 }
2216
6f0c979f 2217 // Replicate logic in cohort_can_view_cohort() because we can't use it directly as we don't have a course context.
bee480a4 2218 $cohortcontext = context::instance_by_id($cohort->contextid);
6f0c979f 2219 if (!$cohort->visible && !has_capability('moodle/cohort:view', $cohortcontext)) {
bee480a4
FM
2220 throw new required_capability_exception($cohortcontext, 'moodle/cohort:view', 'nopermissions', '');
2221 }
2222
2223 $tplcohort = template_cohort::get_relation($template->get_id(), $cohort->id);
2224 if (!$tplcohort->get_id()) {
2225 $tplcohort->create();
2226 }
2227
2228 return $tplcohort;
2229 }
2230
2231 /**
2232 * Remove a relation between a template and a cohort.
2233 *
2234 * @param template|int $templateorid The template or its ID.
96c2b847 2235 * @param stdClass|int $cohortorid The cohort ot its ID.
bee480a4
FM
2236 * @return boolean True on success or when the relation did not exist.
2237 */
2238 public static function delete_template_cohort($templateorid, $cohortorid) {
2239 global $DB;
338386aa 2240 static::require_enabled();
bee480a4
FM
2241
2242 $template = $templateorid;
2243 if (!is_object($template)) {
2244 $template = new template($template);
2245 }
2246 require_capability('tool/lp:templatemanage', $template->get_context());
2247
2248 $cohort = $cohortorid;
2249 if (!is_object($cohort)) {
2250 $cohort = $DB->get_record('cohort', array('id' => $cohort), '*', MUST_EXIST);
2251 }
2252
2253 $tplcohort = template_cohort::get_relation($template->get_id(), $cohort->id);
2254 if (!$tplcohort->get_id()) {
2255 return true;
2256 }
2257
2258 return $tplcohort->delete();
2259 }
2260
4db373d5
DM
2261 /**
2262 * Lists user plans.
2263 *
55683895 2264 * @param int $userid
4db373d5
DM
2265 * @return \tool_lp\plan[]
2266 */
2267 public static function list_user_plans($userid) {
3c230247 2268 global $DB, $USER;
338386aa 2269 static::require_enabled();
4db373d5
DM
2270 $select = 'userid = :userid';
2271 $params = array('userid' => $userid);
4db373d5
DM
2272 $context = context_user::instance($userid);
2273
5159d679
FM
2274 // Check that we can read something here.
2275 if (!plan::can_read_user($userid) && !plan::can_read_user_draft($userid)) {
2276 throw new required_capability_exception($context, 'tool/lp:planview', 'nopermissions', '');
4db373d5
DM
2277 }
2278
5159d679
FM
2279 // The user cannot view the drafts.
2280 if (!plan::can_read_user_draft($userid)) {
3c230247
FM
2281 list($insql, $inparams) = $DB->get_in_or_equal(plan::get_draft_statuses(), SQL_PARAMS_NAMED, 'param', false);
2282 $select .= " AND status $insql";
2283 $params += $inparams;
4db373d5 2284 }
5159d679
FM
2285 // The user cannot view the non-drafts.
2286 if (!plan::can_read_user($userid)) {
3c230247
FM
2287 list($insql, $inparams) = $DB->get_in_or_equal(array(plan::STATUS_ACTIVE, plan::STATUS_COMPLETE),
2288 SQL_PARAMS_NAMED, 'param', false);
2289 $select .= " AND status $insql";
2290 $params += $inparams;
5159d679 2291 }
4db373d5 2292
52eda876 2293 return plan::get_records_select($select, $params, 'name ASC');
4db373d5
DM
2294 }
2295
7bb5ccbe
FM
2296 /**
2297 * List the plans to review.
2298 *
2299 * The method returns values in this format:
2300 *
2301 * array(
2302 * 'plans' => array(
2303 * (stdClass)(
2304 * 'plan' => (plan),
2305 * 'template' => (template),
2306 * 'owner' => (stdClass)
2307 * )
2308 * ),
2309 * 'count' => (int)
2310 * )
2311 *
2312 * @param int $skip The number of records to skip.
2313 * @param int $limit The number of results to return.
2314 * @param int $userid The user we're getting the plans to review for.
2315 * @return array Containing the keys 'count', and 'plans'. The 'plans' key contains an object
2316 * which contains 'plan', 'template' and 'owner'.
2317 */
2318 public static function list_plans_to_review($skip = 0, $limit = 100, $userid = null) {
2319 global $DB, $USER;
338386aa 2320 static::require_enabled();
7bb5ccbe
FM
2321
2322 if ($userid === null) {
2323 $userid = $USER->id;
2324 }
2325
2326 $planfields = plan::get_sql_fields('p');
2327 $tplfields = template::get_sql_fields('t');
2328 $usercols = array('id') + get_user_fieldnames();
d58eabed
FM
2329 $userfields = array();
2330 foreach ($usercols as $field) {
2331 $userfields[] = "u." . $field . " AS usr_" . $field;
2332 }
2333 $userfields = implode(',', $userfields);
7bb5ccbe
FM
2334
2335 $select = "SELECT $planfields, $tplfields, $userfields";
2336 $countselect = "SELECT COUNT('x')";
2337
2338 $sql = " FROM {" . plan::TABLE . "} p
2339 JOIN {user} u
2340 ON u.id = p.userid
2341 LEFT JOIN {" . template::TABLE . "} t
2342 ON t.id = p.templateid
2343 WHERE (p.status = :waitingforreview
2344 OR (p.status = :inreview AND p.reviewerid = :reviewerid))
2345 AND p.userid != :userid";
2346
2347 $params = array(
2348 'waitingforreview' => plan::STATUS_WAITING_FOR_REVIEW,
2349 'inreview' => plan::STATUS_IN_REVIEW,
2350 'reviewerid' => $userid,
2351 'userid' => $userid
2352 );
2353
2354 // Primary check to avoid the hard work of getting the users in which the user has permission.
2355 $count = $DB->count_records_sql($countselect . $sql, $params);
2356 if ($count < 1) {
2357 return array('count' => 0, 'plans' => array());
2358 }
2359
2360 // TODO MDL-52243 Use core function.
96c2b847
FM
2361 list($insql, $inparams) = external::filter_users_with_capability_on_user_context_sql('tool/lp:planreview',
2362 $userid, SQL_PARAMS_NAMED);
7bb5ccbe
FM
2363 $sql .= " AND p.userid $insql";
2364 $params += $inparams;
2365
2366 $plans = array();
2367 $records = $DB->get_recordset_sql($select . $sql, $params, $skip, $limit);
2368 foreach ($records as $record) {
2369 $plan = new plan(0, plan::extract_record($record));
2370 $template = null;
2371
2372 if ($plan->is_based_on_template()) {
2373 $template = new template(0, template::extract_record($record));
2374 }
2375
2376 $plans[] = (object) array(
2377 'plan' => $plan,
2378 'template' => $template,
2379 'owner' => persistent::extract_record($record, 'usr_'),
2380 );
2381 }
2382 $records->close();
2383
2384 return array(
2385 'count' => $DB->count_records_sql($countselect . $sql, $params),
2386 'plans' => $plans
2387 );
2388 }
2389
4db373d5
DM
2390 /**
2391 * Creates a learning plan based on the provided data.
2392 *
2393 * @param stdClass $record
2394 * @return \tool_lp\plan
2395 */
2396 public static function create_plan(stdClass $record) {
2397 global $USER;
338386aa 2398 static::require_enabled();
5159d679 2399 $plan = new plan(0, $record);
4db373d5 2400
2388d46d
FM
2401 if ($plan->is_based_on_template()) {
2402 throw new coding_exception('To create a plan from a template use api::create_plan_from_template().');
3c230247
FM
2403 } else if ($plan->get_status() == plan::STATUS_COMPLETE) {
2404 throw new coding_exception('A plan cannot be created as complete.');
2388d46d
FM
2405 }
2406
5159d679
FM
2407 if (!$plan->can_manage()) {
2408 $context = context_user::instance($plan->get_userid());
53084abb 2409 throw new required_capability_exception($context, 'tool/lp:planmanage', 'nopermissions', '');
4db373d5
DM
2410 }
2411
5af3778e 2412 $plan->create();
4ed524b7
IT
2413
2414 // Trigger created event.
2415 \tool_lp\event\plan_created::create_from_plan($plan)->trigger();
4db373d5
DM
2416 return $plan;
2417 }
2418
2388d46d
FM
2419 /**
2420 * Create a learning plan from a template.
2421 *
2422 * @param mixed $templateorid The template object or ID.
2423 * @param int $userid
2424 * @return false|\tool_lp\plan Returns false when the plan already exists.
2425 */
2426 public static function create_plan_from_template($templateorid, $userid) {
338386aa 2427 static::require_enabled();
2388d46d
FM
2428 $template = $templateorid;
2429 if (!is_object($template)) {
2430 $template = new template($template);
2431 }
f85ea576
FM
2432
2433 // The user must be able to view the template to use it as a base for a plan.
2434 if (!$template->can_read()) {
44e8cba4 2435 throw new required_capability_exception($template->get_context(), 'tool/lp:templateview', 'nopermissions', '');
f85ea576 2436 }
964afa98
IT
2437 // Can not create plan from a hidden template.
2438 if ($template->get_visible() == false) {
2439 throw new coding_exception('A plan can not be created from a hidden template');
2440 }
2388d46d
FM
2441
2442 // Convert the template to a plan.
2443 $record = $template->to_record();
2444 $record->templateid = $record->id;
2445 $record->userid = $userid;
2446 $record->name = $record->shortname;
2447 $record->status = plan::STATUS_ACTIVE;
2448
2449 unset($record->id);
2450 unset($record->timecreated);
2451 unset($record->timemodified);
2452 unset($record->usermodified);
2453
2454 // Remove extra keys.
2455 $properties = plan::properties_definition();
2456 foreach ($record as $key => $value) {
2457 if (!array_key_exists($key, $properties)) {
2458 unset($record->$key);
2459 }
2460 }
2461
2462 $plan = new plan(0, $record);
2463 if (!$plan->can_manage()) {
2464 throw new required_capability_exception($plan->get_context(), 'tool/lp:planmanage', 'nopermissions', '');
2465 }
2466
2467 // We first apply the permission checks as we wouldn't want to leak information by returning early that
2468 // the plan already exists.
2469 if (plan::record_exists_select('templateid = :templateid AND userid = :userid', array(
2470 'templateid' => $template->get_id(), 'userid' => $userid))) {
2471 return false;
2472 }
2473
2474 $plan->create();
4ed524b7
IT
2475
2476 // Trigger created event.
2477 \tool_lp\event\plan_created::create_from_plan($plan)->trigger();
2388d46d
FM
2478 return $plan;
2479 }
2480
dd1df082
FM
2481 /**
2482 * Create learning plans from a template and cohort.
2483 *
2484 * @param mixed $templateorid The template object or ID.
2485 * @param int $cohortid The cohort ID.
2486 * @param bool $recreateunlinked When true the plans that were unlinked from this template will be re-created.
2487 * @return int The number of plans created.
2488 */
2489 public static function create_plans_from_template_cohort($templateorid, $cohortid, $recreateunlinked = false) {
2490 global $DB, $CFG;
338386aa 2491 static::require_enabled();
dd1df082
FM
2492 require_once($CFG->dirroot . '/cohort/lib.php');
2493
2494 $template = $templateorid;
2495 if (!is_object($template)) {
2496 $template = new template($template);
2497 }
2498
2499 // The user must be able to view the template to use it as a base for a plan.
2500 if (!$template->can_read()) {
44e8cba4 2501 throw new required_capability_exception($template->get_context(), 'tool/lp:templateview', 'nopermissions', '');
dd1df082
FM
2502 }
2503
964afa98
IT
2504 // Can not create plan from a hidden template.
2505 if ($template->get_visible() == false) {
2506 throw new coding_exception('A plan can not be created from a hidden template');
2507 }
2508
6f0c979f 2509 // Replicate logic in cohort_can_view_cohort() because we can't use it directly as we don't have a course context.
dd1df082 2510 $cohort = $DB->get_record('cohort', array('id' => $cohortid), '*', MUST_EXIST);
6f0c979f
FM
2511 $cohortcontext = context::instance_by_id($cohort->contextid);
2512 if (!$cohort->visible && !has_capability('moodle/cohort:view', $cohortcontext)) {
2513 throw new required_capability_exception($cohortcontext, 'moodle/cohort:view', 'nopermissions', '');
dd1df082
FM
2514 }
2515
2516 // Convert the template to a plan.
2517 $recordbase = $template->to_record();
2518 $recordbase->templateid = $recordbase->id;
2519 $recordbase->name = $recordbase->shortname;
2520 $recordbase->status = plan::STATUS_ACTIVE;
2521
2522 unset($recordbase->id);
2523 unset($recordbase->timecreated);
2524 unset($recordbase->timemodified);
2525 unset($recordbase->usermodified);
2526
2527 // Remove extra keys.
2528 $properties = plan::properties_definition();
2529 foreach ($recordbase as $key => $value) {
2530 if (!array_key_exists($key, $properties)) {
2531 unset($recordbase->$key);
2532 }
2533 }
2534
2535 // Create the plans.
2536 $created = 0;
2537 $userids = template_cohort::get_missing_plans($template->get_id(), $cohortid, $recreateunlinked);
2538 foreach ($userids as $userid) {
2539 $record = (object) (array) $recordbase;
2540 $record->userid = $userid;
2541
9031be44
FM
2542 $plan = new plan(0, $record);
2543 if (!$plan->can_manage()) {
dd1df082
FM
2544 // Silently skip members where permissions are lacking.
2545 continue;
2546 }
2547
2548 $plan->create();
4ed524b7
IT
2549 // Trigger created event.
2550 \tool_lp\event\plan_created::create_from_plan($plan)->trigger();
dd1df082
FM
2551 $created++;
2552 }
2553
2554 return $created;
2555 }
2556
6d2c2e86
FM
2557 /**
2558 * Unlink a plan from its template.
2559 *
2560 * @param \tool_lp\plan|int $planorid The plan or its ID.
2561 * @return bool
2562 */
2563 public static function unlink_plan_from_template($planorid) {
2564 global $DB;
338386aa 2565 static::require_enabled();
6d2c2e86
FM
2566
2567 $plan = $planorid;
2568 if (!is_object($planorid)) {
2569 $plan = new plan($planorid);
2570 }
2571
f85ea576 2572 // The user must be allowed to manage the plans of the user, nothing about the template.
6d2c2e86
FM
2573 if (!$plan->can_manage()) {
2574 throw new required_capability_exception($plan->get_context(), 'tool/lp:planmanage', 'nopermissions', '');
2575 }
2576
a8902ee2
SG
2577 // Only plan with status DRAFT or ACTIVE can be unliked..
2578 if ($plan->get_status() == plan::STATUS_COMPLETE) {
2579 throw new coding_exception('Only draft or active plan can be unliked from a template');
2580 }
2581
6d2c2e86
FM
2582 // Early exit, it's already done...
2583 if (!$plan->is_based_on_template()) {
2584 return true;
2585 }
2586
2587 // Fetch the template.
2588 $template = new template($plan->get_templateid());
2589
2590 // Now, proceed by copying all competencies to the plan, then update the plan.
2591 $transaction = $DB->start_delegated_transaction();
2592 $competencies = template_competency::list_competencies($template->get_id(), false);
2593 $i = 0;
2594 foreach ($competencies as $competency) {
2595 $record = (object) array(
2596 'planid' => $plan->get_id(),
2597 'competencyid' => $competency->get_id(),
2598 'sortorder' => $i++
2599 );
2600 $pc = new plan_competency(null, $record);
2601 $pc->create();
2602 }
2603 $plan->set_origtemplateid($template->get_id());
2604 $plan->set_templateid(null);
2605 $success = $plan->update();
2606 $transaction->allow_commit();
2607
25deee17
IT
2608 // Trigger unlinked event.
2609 \tool_lp\event\plan_unlinked::create_from_plan($plan)->trigger();
4ed524b7 2610
6d2c2e86
FM
2611 return $success;
2612 }
2613
4db373d5
DM
2614 /**
2615 * Updates a plan.
2616 *
2617 * @param stdClass $record
2618 * @return \tool_lp\plan
2619 */
2620 public static function update_plan(stdClass $record) {
338386aa 2621 static::require_enabled();
192569ed 2622
5159d679 2623 $plan = new plan($record->id);
4db373d5 2624
5159d679
FM
2625 // Validate that the plan as it is can be managed.
2626 if (!$plan->can_manage()) {
9f1e6c13 2627 throw new required_capability_exception($plan->get_context(), 'tool/lp:planmanage', 'nopermissions', '');
4de877eb 2628
3c230247
FM
2629 } else if ($plan->get_status() == plan::STATUS_COMPLETE) {
2630 // A completed plan cannot be edited.
2631 throw new coding_exception('Completed plan cannot be edited.');
2632
4de877eb 2633 } else if ($plan->is_based_on_template()) {
d805cc37 2634 // Prevent a plan based on a template to be edited.
4de877eb 2635 throw new coding_exception('Cannot update a plan that is based on a template.');
4db373d5 2636
3c230247
FM
2637 } else if (isset($record->templateid) && $plan->get_templateid() != $record->templateid) {
2638 // Prevent a plan to be based on a template.
2639 throw new coding_exception('Cannot base a plan on a template.');
2640
4de877eb 2641 } else if (isset($record->userid) && $plan->get_userid() != $record->userid) {
d805cc37 2642 // Prevent change of ownership as the capabilities are checked against that.
5159d679 2643 throw new coding_exception('A plan cannot be transfered to another user');
192569ed 2644
3c230247
FM
2645 } else if (isset($record->status) && $plan->get_status() != $record->status) {
2646 // Prevent change of status.
2647 throw new coding_exception('To change the status of a plan use the appropriate methods.');
4db373d5 2648
d805cc37 2649 }
192569ed 2650
3c230247 2651 $plan->from_record($record);
d805cc37 2652 $plan->update();
192569ed 2653
4ed524b7
IT
2654 // Trigger updated event.
2655 \tool_lp\event\plan_updated::create_from_plan($plan)->trigger();
2656
5159d679 2657 return $plan;
4db373d5
DM
2658 }
2659
2660 /**
2661 * Returns a plan data.
2662 *
2663 * @param int $id
2664 * @return \tool_lp\plan
2665 */
2666 public static function read_plan($id) {
338386aa 2667 static::require_enabled();
4db373d5 2668 $plan = new plan($id);
4db373d5 2669
5159d679
FM
2670 if (!$plan->can_read()) {
2671 $context = context_user::instance($plan->get_userid());
2672 throw new required_capability_exception($context, 'tool/lp:planview', 'nopermissions', '');
4db373d5
DM
2673 }
2674
4db373d5
DM
2675 return $plan;
2676 }
2677
4ed524b7
IT
2678 /**
2679 * Plan event viewed.
2680 *
2681 * @param mixed $planorid The id or the plan.
2682 * @return boolean
2683 */
2684 public static function plan_viewed($planorid) {
338386aa 2685 static::require_enabled();
4ed524b7
IT
2686 $plan = $planorid;
2687 if (!is_object($plan)) {
2688 $plan = new plan($plan);
2689 }
2690
2691 // First we do a permissions check.
2692 if (!$plan->can_read()) {
2693 $context = context_user::instance($plan->get_userid());
2694 throw new required_capability_exception($context, 'tool/lp:planview', 'nopermissions', '');
2695 }
2696
2697 // Trigger a template viewed event.
2698 \tool_lp\event\plan_viewed::create_from_plan($plan)->trigger();
2699
2700 return true;
2701 }
2702
4db373d5
DM
2703 /**
2704 * Deletes a plan.
2705 *
f85ea576
FM
2706 * Plans based on a template can be removed just like any other one.
2707 *
4db373d5
DM
2708 * @param int $id
2709 * @return bool Success?
2710 */
2711 public static function delete_plan($id) {
192569ed 2712 global $DB;
338386aa 2713 static::require_enabled();
192569ed 2714
4db373d5
DM
2715 $plan = new plan($id);
2716
5159d679
FM
2717 if (!$plan->can_manage()) {
2718 $context = context_user::instance($plan->get_userid());
53084abb 2719 throw new required_capability_exception($context, 'tool/lp:planmanage', 'nopermissions', '');
4db373d5
DM
2720 }
2721
192569ed
JPG
2722 // Wrap the suppression in a DB transaction.
2723 $transaction = $DB->start_delegated_transaction();
2724
357d3862
FM
2725 // Delete plan competencies.
2726 $plancomps = plan_competency::get_records(array('planid' => $plan->get_id()));
2727 foreach ($plancomps as $plancomp) {
2728 $plancomp->delete();
2729 }
2730
192569ed
JPG
2731 // Delete archive user competencies if the status of the plan is complete.
2732 if ($plan->get_status() == plan::STATUS_COMPLETE) {
2733 self::remove_archived_user_competencies_in_plan($plan);
2734 }
4ed524b7 2735 $event = \tool_lp\event\plan_deleted::create_from_plan($plan);
192569ed
JPG
2736 $success = $plan->delete();
2737
2738 $transaction->allow_commit();
2739
4ed524b7
IT
2740 // Trigger deleted event.
2741 $event->trigger();
2742
192569ed 2743 return $success;
4db373d5 2744 }
4f815459 2745
3c230247
FM
2746 /**
2747 * Cancel the review of a plan.
2748 *
2749 * @param int|plan $planorid The plan, or its ID.
2750 * @return bool
2751 */
2752 public static function plan_cancel_review_request($planorid) {
338386aa 2753 static::require_enabled();
3c230247
FM
2754 $plan = $planorid;
2755 if (!is_object($plan)) {
2756 $plan = new plan($plan);
2757 }
2758
2759 // We need to be able to view the plan at least.
2760 if (!$plan->can_read()) {
2761 throw new required_capability_exception($plan->get_context(), 'tool/lp:planview', 'nopermissions', '');
2762 }
2763
2764 if ($plan->is_based_on_template()) {
2765 throw new coding_exception('Template plans cannot be reviewed.'); // This should never happen.
2766 } else if ($plan->get_status() != plan::STATUS_WAITING_FOR_REVIEW) {
2767 throw new coding_exception('The plan review cannot be cancelled at this stage.');
2768 } else if (!$plan->can_request_review()) {
2769 throw new required_capability_exception($plan->get_context(), 'tool/lp:planmanage', 'nopermissions', '');
2770 }
2771
2772 $plan->set_status(plan::STATUS_DRAFT);
4ed524b7
IT
2773 $result = $plan->update();
2774
25deee17
IT
2775 // Trigger review request cancelled event.
2776 \tool_lp\event\plan_review_request_cancelled::create_from_plan($plan)->trigger();
4ed524b7
IT
2777
2778 return $result;
3c230247
FM
2779 }
2780
2781 /**
2782 * Request the review of a plan.
2783 *
2784 * @param int|plan $planorid The plan, or its ID.
2785 * @return bool
2786 */
2787 public static function plan_request_review($planorid) {
338386aa 2788 static::require_enabled();
3c230247
FM
2789 $plan = $planorid;
2790 if (!is_object($plan)) {
2791 $plan = new plan($plan);
2792 }
2793
2794 // We need to be able to view the plan at least.
2795 if (!$plan->can_read()) {
2796 throw new required_capability_exception($plan->get_context(), 'tool/lp:planview', 'nopermissions', '');
2797 }
2798
2799 if ($plan->is_based_on_template()) {
2800 throw new coding_exception('Template plans cannot be reviewed.'); // This should never happen.
2801 } else if ($plan->get_status() != plan::STATUS_DRAFT) {
2802 throw new coding_exception('The plan cannot be sent for review at this stage.');
2803 } else if (!$plan->can_request_review()) {
2804 throw new required_capability_exception($plan->get_context(), 'tool/lp:planmanage', 'nopermissions', '');
2805 }
2806
2807 $plan->set_status(plan::STATUS_WAITING_FOR_REVIEW);
4ed524b7
IT
2808 $result = $plan->update();
2809
25deee17
IT
2810 // Trigger review requested event.
2811 \tool_lp\event\plan_review_requested::create_from_plan($plan)->trigger();
4ed524b7
IT
2812
2813 return $result;
3c230247
FM
2814 }
2815
2816 /**
2817 * Start the review of a plan.
2818 *
2819 * @param int|plan $planorid The plan, or its ID.
2820 * @return bool
2821 */
2822 public static function plan_start_review($planorid) {
2823 global $USER;
338386aa 2824 static::require_enabled();
3c230247
FM
2825 $plan = $planorid;
2826 if (!is_object($plan)) {
2827 $plan = new plan($plan);
2828 }
2829
2830 // We need to be able to view the plan at least.
2831 if (!$plan->can_read()) {
2832 throw new required_capability_exception($plan->get_context(), 'tool/lp:planview', 'nopermissions', '');
2833 }
2834
2835 if ($plan->is_based_on_template()) {
2836 throw new coding_exception('Template plans cannot be reviewed.'); // This should never happen.
2837 } else if ($plan->get_status() != plan::STATUS_WAITING_FOR_REVIEW) {
2838 throw new coding_exception('The plan review cannot be started at this stage.');
2839 } else if (!$plan->can_review()) {
2840 throw new required_capability_exception($plan->get_context(), 'tool/lp:planmanage', 'nopermissions', '');
2841 }
2842
2843 $plan->set_status(plan::STATUS_IN_REVIEW);
2844 $plan->set_reviewerid($USER->id);
4ed524b7
IT
2845 $result = $plan->update();
2846
25deee17
IT
2847 // Trigger review started event.
2848 \tool_lp\event\plan_review_started::create_from_plan($plan)->trigger();
4ed524b7
IT
2849
2850 return $result;
3c230247
FM
2851 }
2852
2853 /**
2854 * Stop reviewing a plan.
2855 *
2856 * @param int|plan $planorid The plan, or its ID.
2857 * @return bool
2858 */
2859 public static function plan_stop_review($planorid) {
338386aa 2860 static::require_enabled();
3c230247
FM
2861 $plan = $planorid;
2862 if (!is_object($plan)) {
2863 $plan = new plan($plan);
2864 }
2865
2866 // We need to be able to view the plan at least.
2867 if (!$plan->can_read()) {
2868 throw new required_capability_exception($plan->get_context(), 'tool/lp:planview', 'nopermissions', '');
2869 }
2870
2871 if ($plan->is_based_on_template()) {
2872 throw new coding_exception('Template plans cannot be reviewed.'); // This should never happen.
2873 } else if ($plan->get_status() != plan::STATUS_IN_REVIEW) {
2874 throw new coding_exception('The plan review cannot be stopped at this stage.');
2875 } else if (!$plan->can_review()) {
2876 throw new required_capability_exception($plan->get_context(), 'tool/lp:planmanage', 'nopermissions', '');
2877 }
2878
2879 $plan->set_status(plan::STATUS_DRAFT);
2880 $plan->set_reviewerid(null);
4ed524b7
IT
2881 $result = $plan->update();
2882
25deee17
IT
2883 // Trigger review stopped event.
2884 \tool_lp\event\plan_review_stopped::create_from_plan($plan)->trigger();
4ed524b7
IT
2885
2886 return $result;
3c230247
FM
2887 }
2888
2889 /**
2890 * Approve a plan.
2891 *
2892 * This means making the plan active.
2893 *
2894 * @param int|plan $planorid The plan, or its ID.
2895 * @return bool
2896 */
2897 public static function approve_plan($planorid) {
338386aa 2898 static::require_enabled();
3c230247
FM
2899 $plan = $planorid;
2900 if (!is_object($plan)) {
2901 $plan = new plan($plan);
2902 }
2903
2904 // We need to be able to view the plan at least.
2905 if (!$plan->can_read()) {
2906 throw new required_capability_exception($plan->get_context(), 'tool/lp:planview', 'nopermissions', '');
2907 }
2908
2909 // We can approve a plan that is either a draft, in review, or waiting for review.
2910 if ($plan->is_based_on_template()) {
2911 throw new coding_exception('Template plans are already approved.'); // This should never happen.
2912 } else if (!$plan->is_draft()) {
2913 throw new coding_exception('The plan cannot be approved at this stage.');
2914 } else if (!$plan->can_review()) {
2915 throw new required_capability_exception($plan->get_context(), 'tool/lp:planmanage', 'nopermissions', '');
2916 }
2917
2918 $plan->set_status(plan::STATUS_ACTIVE);
2919 $plan->set_reviewerid(null);
4ed524b7
IT
2920 $result = $plan->update();
2921
25deee17
IT
2922 // Trigger approved event.
2923 \tool_lp\event\plan_approved::create_from_plan($plan)->trigger();
4ed524b7
IT
2924
2925 return $result;
3c230247
FM
2926 }
2927
2928 /**
2929 * Unapprove a plan.
2930 *
2931 * This means making the plan draft.
2932 *
2933 * @param int|plan $planorid The plan, or its ID.
2934 * @return bool
2935 */
2936 public static function unapprove_plan($planorid) {
338386aa 2937 static::require_enabled();
3c230247
FM
2938 $plan = $planorid;
2939 if (!is_object($plan)) {
2940 $plan = new plan($plan);
2941 }
2942
2943 // We need to be able to view the plan at least.
2944 if (!$plan->can_read()) {
2945 throw new required_capability_exception($plan->get_context(), 'tool/lp:planview', 'nopermissions', '');
2946 }
2947
2948 if ($plan->is_based_on_template()) {
2949 throw new coding_exception('Template plans are always approved.'); // This should never happen.
2950 } else if ($plan->get_status() != plan::STATUS_ACTIVE) {
2951 throw new coding_exception('The plan cannot be sent back to draft at this stage.');
2952 } else if (!$plan->can_review()) {
2953 throw new required_capability_exception($plan->get_context(), 'tool/lp:planmanage', 'nopermissions', '');
2954 }
2955
2956 $plan->set_status(plan::STATUS_DRAFT);
4ed524b7
IT
2957 $result = $plan->update();
2958
25deee17
IT
2959 // Trigger unapproved event.
2960 \tool_lp\event\plan_unapproved::create_from_plan($plan)->trigger();
4ed524b7
IT
2961
2962 return $result;
3c230247
FM
2963 }
2964
5bfab685
FM
2965 /**
2966 * Complete a plan.
2967 *
2968 * @param int|plan $planorid The plan, or its ID.
2969 * @return bool
2970 */
2971 public static function complete_plan($planorid) {
2972 global $DB;
338386aa 2973 static::require_enabled();
5bfab685
FM
2974
2975 $plan = $planorid;
2976 if (!is_object($planorid)) {
2977 $plan = new plan($planorid);
2978 }
2979
2980 // Validate that the plan can be managed.
2981 if (!$plan->can_manage()) {
2982 throw new required_capability_exception($plan->get_context(), 'tool/lp:planmanage', 'nopermissions', '');
2983 }
2984
2985 // Check if the plan was already completed.
2986 if ($plan->get_status() == plan::STATUS_COMPLETE) {
d3275795 2987 throw new coding_exception('The plan is already completed.');
5bfab685
FM
2988 }
2989
d3275795 2990 $originalstatus = $plan->get_status();
5bfab685
FM
2991 $plan->set_status(plan::STATUS_COMPLETE);
2992
2993 // The user should also be able to manage the plan when it's completed.
2994 if (!$plan->can_manage()) {
2995 throw new required_capability_exception($plan->get_context(), 'tool/lp:planmanage', 'nopermissions', '');
2996 }
2997
d3275795
SG
2998 // Put back original status because archive needs it to extract competencies from the right table.
2999 $plan->set_status($originalstatus);
3000
5bfab685
FM
3001 // Do the things.
3002 $transaction = $DB->start_delegated_transaction();
3003 self::archive_user_competencies_in_plan($plan);
d3275795 3004 $plan->set_status(plan::STATUS_COMPLETE);
5bfab685
FM
3005 $success = $plan->update();
3006
3007 if (!$success) {
3008 $transaction->rollback(new moodle_exception('The plan could not be updated.'));
3009 return $success;
3010 }
3011
3012 $transaction->allow_commit();
4ed524b7
IT
3013
3014 // Trigger updated event.
25deee17 3015 \tool_lp\event\plan_completed::create_from_plan($plan)->trigger();
4ed524b7 3016
5bfab685
FM
3017 return $success;
3018 }
3019
d805cc37
IT
3020 /**
3021 * Reopen a plan.
3022 *
3023 * @param int|plan $planorid The plan, or its ID.
3024 * @return bool
3025 */
3026 public static function reopen_plan($planorid) {
3027 global $DB;
338386aa 3028 static::require_enabled();
d805cc37
IT
3029
3030 $plan = $planorid;
3031 if (!is_object($planorid)) {
3032 $plan = new plan($planorid);
3033 }
3034
3035 // Validate that the plan as it is can be managed.
3036 if (!$plan->can_manage()) {
3037 $context = context_user::instance($plan->get_userid());
3038 throw new required_capability_exception($context, 'tool/lp:planmanage', 'nopermissions', '');
3039 }
3040
3041 $beforestatus = $plan->get_status();
3042 $plan->set_status(plan::STATUS_ACTIVE);
3043
3044 // Validate if status can be changed.
3045 if (!$plan->can_manage()) {
3046 $context = context_user::instance($plan->get_userid());
3047 throw new required_capability_exception($context, 'tool/lp:planmanage', 'nopermissions', '');
3048 }
3049
3050 // Wrap the updates in a DB transaction.
3051 $transaction = $DB->start_delegated_transaction();
3052
3053 // Delete archived user competencies if the status of the plan is changed from complete to another status.
3054 $mustremovearchivedcompetencies = ($beforestatus == plan::STATUS_COMPLETE && $plan->get_status() != plan::STATUS_COMPLETE);
3055 if ($mustremovearchivedcompetencies) {
3056 self::remove_archived_user_competencies_in_plan($plan);
3057 }
3058
3c581164
IT
3059 // If duedate less than or equal to duedate_threshold unset it.
3060 if ($plan->get_duedate() <= time() + plan::DUEDATE_THRESHOLD) {
3061 $plan->set_duedate(0);
3062 }
3063
d805cc37
IT
3064 $success = $plan->update();
3065
3066 if (!$success) {
3067 $transaction->rollback(new moodle_exception('The plan could not be updated.'));
3068 return $success;
3069 }
3070
3071 $transaction->allow_commit();
4ed524b7 3072
25deee17
IT
3073 // Trigger reopened event.
3074 \tool_lp\event\plan_reopened::create_from_plan($plan)->trigger();
4ed524b7 3075
d805cc37
IT
3076 return $success;
3077 }
3078
65a67e23
DW
3079 /**
3080 * Get a single competency from the user plan.
3081 *
3082 * @param int $planorid The plan, or its ID.
3083 * @param int $competencyid The competency id.
3084 * @return (object) array(
3085 * 'competency' => \tool_lp\competency,
3086 * 'usercompetency' => \tool_lp\user_competency
3087 * 'usercompetencyplan' => \tool_lp\user_competency_plan
3088 * )
3089 * The values of of keys usercompetency and usercompetencyplan cannot be defined at the same time.
3090 */
3091 public static function get_plan_competency($planorid, $competencyid) {
338386aa 3092 static::require_enabled();
65a67e23
DW
3093 $plan = $planorid;
3094 if (!is_object($planorid)) {
3095 $plan = new plan($planorid);
3096 }
3097
3b11d638 3098 if (!user_competency::can_read_user($plan->get_userid())) {
943989c2 3099 throw new required_capability_exception($plan->get_context(), 'tool/lp:usercompetencyview', 'nopermissions', '');
65a67e23
DW
3100 }
3101
3102 $competency = $plan->get_competency($competencyid);
3103
3104 // Get user competencies from user_competency_plan if the plan status is set to complete.
3105 $iscompletedplan = $plan->get_status() == plan::STATUS_COMPLETE;
3106 if ($iscompletedplan) {
3107 $usercompetencies = user_competency_plan::get_multiple($plan->get_userid(), $plan->get_id(), array($competencyid));
3108 $ucresultkey = 'usercompetencyplan';
3109 } else {
3110 $usercompetencies = user_competency::get_multiple($plan->get_userid(), array($competencyid));
3111 $ucresultkey = 'usercompetency';
3112 }
3113
3114 $found = count($usercompetencies);
3115
3116 if ($found) {
3117 $uc = array_pop($usercompetencies);
3118 } else {
3119 if ($iscompletedplan) {
3120 throw new coding_exception('A user competency plan is missing');
3121 } else {
3122 $uc = user_competency::create_relation($plan->get_userid(), $competency->get_id());
c008e92d 3123 $uc->create();
65a67e23
DW
3124 }
3125 }
3126
3127 $plancompetency = (object) array(
3128 'competency' => $competency,
3129 'usercompetency' => null,
3130 'usercompetencyplan' => null
3131 );
3132 $plancompetency->$ucresultkey = $uc;
3133
3134 return $plancompetency;
3135 }
3136
1d8f0a6f
FM
3137 /**
3138 * List the competencies in a user plan.
3139 *
3140 * @param int $planorid The plan, or its ID.
192569ed
JPG
3141 * @return array((object) array(
3142 * 'competency' => \tool_lp\competency,
3143 * 'usercompetency' => \tool_lp\user_competency
3144 * 'usercompetencyplan' => \tool_lp\user_competency_plan
3145 * ))
3146 * The values of of keys usercompetency and usercompetencyplan cannot be defined at the same time.
1d8f0a6f
FM
3147 */
3148 public static function list_plan_competencies($planorid) {
338386aa 3149 static::require_enabled();
1d8f0a6f
FM
3150 $plan = $planorid;
3151 if (!is_object($planorid)) {
3152 $plan = new plan($planorid);
3153 }
3154
3155 if (!$plan->can_read()) {
3156 $context = context_user::instance($plan->get_userid());
3157 throw new required_capability_exception($context, 'tool/lp:planview', 'nopermissions', '');
3158 }
3159
3160 $result = array();
3161 $competencies = $plan->get_competencies();
192569ed
JPG
3162
3163 // Get user competencies from user_competency_plan if the plan status is set to complete.
3164 $iscompletedplan = $plan->get_status() == plan::STATUS_COMPLETE;
3165 if ($iscompletedplan) {
3166 $usercompetencies = user_competency_plan::get_multiple($plan->get_userid(), $plan->get_id(), $competencies);
3167 $ucresultkey = 'usercompetencyplan';
3168 } else {
3169 $usercompetencies = user_competency::get_multiple($plan->get_userid(), $competencies);
3170 $ucresultkey = 'usercompetency';
3171 }
1d8f0a6f
FM
3172
3173 // Build the return values.
3174 foreach ($competencies as $key => $competency) {
3175 $found = false;
3176
3177 foreach ($usercompetencies as $uckey => $uc) {
3178 if ($uc->get_competencyid() == $competency->get_id()) {
3179 $found = true;
3180 unset($usercompetencies[$uckey]);
3181 break;
3182 }
3183 }
3184
3185 if (!$found) {
192569ed
JPG
3186 if ($iscompletedplan) {
3187 throw new coding_exception('A user competency plan is missing');
3188 } else {
3189 $uc = user_competency::create_relation($plan->get_userid(), $competency->get_id());
3190 }
1d8f0a6f
FM
3191 }
3192
192569ed 3193 $plancompetency = (object) array(
1d8f0a6f 3194 'competency' => $competency,
192569ed
JPG
3195 'usercompetency' => null,
3196 'usercompetencyplan' => null
1d8f0a6f 3197 );
192569ed
JPG
3198 $plancompetency->$ucresultkey = $uc;
3199 $result[] = $plancompetency;
1d8f0a6f
FM
3200 }
3201
3202 return $result;
3203 }
3204
554b369b
IT
3205 /**
3206 * Add a competency to a plan.
3207 *
3208 * @param int $planid The id of the plan
3209 * @param int $competencyid The id of the competency
3210 * @return bool
3211 */
3212 public static function add_competency_to_plan($planid, $competencyid) {
338386aa 3213 static::require_enabled();
554b369b 3214 $plan = new plan($planid);
8ff9ae8d
FM
3215
3216 // First we do a permissions check.
554b369b 3217 if (!$plan->can_manage()) {
8ff9ae8d 3218 throw new required_capability_exception($plan->get_context(), 'tool/lp:planmanage', 'nopermissions', '');
554b369b 3219
8ff9ae8d 3220 } else if ($plan->is_based_on_template()) {
554b369b
IT
3221 throw new coding_exception('A competency can not be added to a learning plan based on a template');
3222 }
3223
d805cc37
IT
3224 if (!$plan->can_be_edited()) {
3225 throw new coding_exception('A competency can not be added to a learning plan completed');
3226 }
3227
964afa98
IT
3228 $competency = new competency($competencyid);
3229
3230 // Can not add a competency that belong to a hidden framework.
3231 if ($competency->get_framework()->get_visible() == false) {
3232 throw new coding_exception('A competency belonging to hidden framework can not be added');
3233 }
3234
8ff9ae8d 3235 $exists = plan_competency::get_record(array('planid' => $planid, 'competencyid' => $competencyid));
554b369b 3236 if (!$exists) {
8ff9ae8d
FM
3237 $record = new stdClass();
3238 $record->planid = $planid;
3239 $record->competencyid = $competencyid;
554b369b
IT
3240 $plancompetency = new plan_competency(0, $record);
3241 $plancompetency->create();
3242 }
8ff9ae8d 3243
554b369b
IT
3244 return true;
3245 }
3246
3247 /**
3248 * Remove a competency from a plan.
3249 *
3250 * @param int $planid The plan id
3251 * @param int $competencyid The id of the competency
3252 * @return bool
3253 */
3254 public static function remove_competency_from_plan($planid, $competencyid) {
338386aa 3255 static::require_enabled();
554b369b 3256 $plan = new plan($planid);
8ff9ae8d
FM
3257
3258 // First we do a permissions check.
554b369b
IT
3259 if (!$plan->can_manage()) {
3260 $context = context_user::instance($plan->get_userid());
3261 throw new required_capability_exception($context, 'tool/lp:planmanage', 'nopermissions', '');
554b369b 3262
8ff9ae8d 3263 } else if ($plan->is_based_on_template()) {
554b369b
IT
3264 throw new coding_exception('A competency can not be removed from a learning plan based on a template');
3265 }
3266
d805cc37
IT
3267 if (!$plan->can_be_edited()) {
3268 throw new coding_exception('A competency can not be removed from a learning plan completed');
3269 }
3270
554b369b
IT
3271 $link = plan_competency::get_record(array('planid' => $planid, 'competencyid' => $competencyid));
3272 if ($link) {
3273 return $link->delete();
3274 }
3275 return false;
3276 }
3277
3278 /**
3279 * Move the plan competency up or down in the display list.
3280 *
3281 * Requires tool/lp:planmanage capability at the system context.
3282 *
3283 * @param int $planid The plan id
3284 * @param int $competencyidfrom The id of the competency we are moving.
3285 * @param int $competencyidto The id of the competency we are moving to.
3286 * @return boolean
3287 */