[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
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 core_competency 21 * @copyright 2015 Damyon Wiese 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 namespace core_competency; 25 defined('MOODLE_INTERNAL') || die(); 26 27 use stdClass; 28 use cm_info; 29 use context; 30 use context_helper; 31 use context_system; 32 use context_course; 33 use context_module; 34 use context_user; 35 use coding_exception; 36 use require_login_exception; 37 use moodle_exception; 38 use moodle_url; 39 use required_capability_exception; 40 41 /** 42 * Class for doing things with competency frameworks. 43 * 44 * @copyright 2015 Damyon Wiese 45 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 46 */ 47 class api { 48 49 /** 50 * Returns whether competencies are enabled. 51 * 52 * This method should never do more than checking the config setting, the reason 53 * being that some other code could be checking the config value directly 54 * to avoid having to load this entire file into memory. 55 * 56 * @return boolean True when enabled. 57 */ 58 public static function is_enabled() { 59 return get_config('core_competency', 'enabled'); 60 } 61 62 /** 63 * Throws an exception if competencies are not enabled. 64 * 65 * @return void 66 * @throws moodle_exception 67 */ 68 public static function require_enabled() { 69 if (!static::is_enabled()) { 70 throw new moodle_exception('competenciesarenotenabled', 'core_competency'); 71 } 72 } 73 74 /** 75 * Checks whether a scale is used anywhere in the plugin. 76 * 77 * This public API has two exceptions: 78 * - It MUST NOT perform any capability checks. 79 * - It MUST ignore whether competencies are enabled or not ({@link self::is_enabled()}). 80 * 81 * @param int $scaleid The scale ID. 82 * @return bool 83 */ 84 public static function is_scale_used_anywhere($scaleid) { 85 global $DB; 86 $sql = "SELECT s.id 87 FROM {scale} s 88 LEFT JOIN {" . competency_framework::TABLE ."} f 89 ON f.scaleid = :scaleid1 90 LEFT JOIN {" . competency::TABLE ."} c 91 ON c.scaleid = :scaleid2 92 WHERE f.id IS NOT NULL 93 OR c.id IS NOT NULL"; 94 return $DB->record_exists_sql($sql, ['scaleid1' => $scaleid, 'scaleid2' => $scaleid]); 95 } 96 97 /** 98 * Validate if current user have acces to the course_module if hidden. 99 * 100 * @param mixed $cmmixed The cm_info class, course module record or its ID. 101 * @param bool $throwexception Throw an exception or not. 102 * @return bool 103 */ 104 protected static function validate_course_module($cmmixed, $throwexception = true) { 105 $cm = $cmmixed; 106 if (!is_object($cm)) { 107 $cmrecord = get_coursemodule_from_id(null, $cmmixed); 108 $modinfo = get_fast_modinfo($cmrecord->course); 109 $cm = $modinfo->get_cm($cmmixed); 110 } else if (!$cm instanceof cm_info) { 111 // Assume we got a course module record. 112 $modinfo = get_fast_modinfo($cm->course); 113 $cm = $modinfo->get_cm($cm->id); 114 } 115 116 if (!$cm->uservisible) { 117 if ($throwexception) { 118 throw new require_login_exception('Course module is hidden'); 119 } else { 120 return false; 121 } 122 } 123 124 return true; 125 } 126 127 /** 128 * Validate if current user have acces to the course if hidden. 129 * 130 * @param mixed $courseorid The course or it ID. 131 * @param bool $throwexception Throw an exception or not. 132 * @return bool 133 */ 134 protected static function validate_course($courseorid, $throwexception = true) { 135 $course = $courseorid; 136 if (!is_object($course)) { 137 $course = get_course($course); 138 } 139 140 $coursecontext = context_course::instance($course->id); 141 if (!$course->visible and !has_capability('moodle/course:viewhiddencourses', $coursecontext)) { 142 if ($throwexception) { 143 throw new require_login_exception('Course is hidden'); 144 } else { 145 return false; 146 } 147 } 148 149 return true; 150 } 151 152 /** 153 * Create a competency from a record containing all the data for the class. 154 * 155 * Requires moodle/competency:competencymanage capability at the system context. 156 * 157 * @param stdClass $record Record containing all the data for an instance of the class. 158 * @return competency 159 */ 160 public static function create_competency(stdClass $record) { 161 static::require_enabled(); 162 $competency = new competency(0, $record); 163 164 // First we do a permissions check. 165 require_capability('moodle/competency:competencymanage', $competency->get_context()); 166 167 // Reset the sortorder, use reorder instead. 168 $competency->set_sortorder(null); 169 $competency->create(); 170 171 \core\event\competency_created::create_from_competency($competency)->trigger(); 172 173 // Reset the rule of the parent. 174 $parent = $competency->get_parent(); 175 if ($parent) { 176 $parent->reset_rule(); 177 $parent->update(); 178 } 179 180 return $competency; 181 } 182 183 /** 184 * Delete a competency by id. 185 * 186 * Requires moodle/competency:competencymanage capability at the system context. 187 * 188 * @param int $id The record to delete. This will delete alot of related data - you better be sure. 189 * @return boolean 190 */ 191 public static function delete_competency($id) { 192 global $DB; 193 static::require_enabled(); 194 $competency = new competency($id); 195 196 // First we do a permissions check. 197 require_capability('moodle/competency:competencymanage', $competency->get_context()); 198 199 $events = array(); 200 $competencyids = array(intval($competency->get_id())); 201 $contextid = $competency->get_context()->id; 202 $competencyids = array_merge(competency::get_descendants_ids($competency), $competencyids); 203 if (!competency::can_all_be_deleted($competencyids)) { 204 return false; 205 } 206 $transaction = $DB->start_delegated_transaction(); 207 208 try { 209 210 // Reset the rule of the parent. 211 $parent = $competency->get_parent(); 212 if ($parent) { 213 $parent->reset_rule(); 214 $parent->update(); 215 } 216 217 // Delete the competency separately so the after_delete event can be triggered. 218 $competency->delete(); 219 220 // Delete the competencies. 221 competency::delete_multiple($competencyids); 222 223 // Delete the competencies relation. 224 related_competency::delete_multiple_relations($competencyids); 225 226 // Delete competency evidences. 227 user_evidence_competency::delete_by_competencyids($competencyids); 228 229 // Register the competencies deleted events. 230 $events = \core\event\competency_deleted::create_multiple_from_competencyids($competencyids, $contextid); 231 232 } catch (\Exception $e) { 233 $transaction->rollback($e); 234 } 235 236 $transaction->allow_commit(); 237 // Trigger events. 238 foreach ($events as $event) { 239 $event->trigger(); 240 } 241 242 return true; 243 } 244 245 /** 246 * Reorder this competency. 247 * 248 * Requires moodle/competency:competencymanage capability at the system context. 249 * 250 * @param int $id The id of the competency to move. 251 * @return boolean 252 */ 253 public static function move_down_competency($id) { 254 static::require_enabled(); 255 $current = new competency($id); 256 257 // First we do a permissions check. 258 require_capability('moodle/competency:competencymanage', $current->get_context()); 259 260 $max = self::count_competencies(array('parentid' => $current->get_parentid(), 261 'competencyframeworkid' => $current->get_competencyframeworkid())); 262 if ($max > 0) { 263 $max--; 264 } 265 266 $sortorder = $current->get_sortorder(); 267 if ($sortorder >= $max) { 268 return false; 269 } 270 $sortorder = $sortorder + 1; 271 $current->set_sortorder($sortorder); 272 273 $filters = array('parentid' => $current->get_parentid(), 274 'competencyframeworkid' => $current->get_competencyframeworkid(), 275 'sortorder' => $sortorder); 276 $children = self::list_competencies($filters, 'id'); 277 foreach ($children as $needtoswap) { 278 $needtoswap->set_sortorder($sortorder - 1); 279 $needtoswap->update(); 280 } 281 282 // OK - all set. 283 $result = $current->update(); 284 285 return $result; 286 } 287 288 /** 289 * Reorder this competency. 290 * 291 * Requires moodle/competency:competencymanage capability at the system context. 292 * 293 * @param int $id The id of the competency to move. 294 * @return boolean 295 */ 296 public static function move_up_competency($id) { 297 static::require_enabled(); 298 $current = new competency($id); 299 300 // First we do a permissions check. 301 require_capability('moodle/competency:competencymanage', $current->get_context()); 302 303 $sortorder = $current->get_sortorder(); 304 if ($sortorder == 0) { 305 return false; 306 } 307 308 $sortorder = $sortorder - 1; 309 $current->set_sortorder($sortorder); 310 311 $filters = array('parentid' => $current->get_parentid(), 312 'competencyframeworkid' => $current->get_competencyframeworkid(), 313 'sortorder' => $sortorder); 314 $children = self::list_competencies($filters, 'id'); 315 foreach ($children as $needtoswap) { 316 $needtoswap->set_sortorder($sortorder + 1); 317 $needtoswap->update(); 318 } 319 320 // OK - all set. 321 $result = $current->update(); 322 323 return $result; 324 } 325 326 /** 327 * Move this competency so it sits in a new parent. 328 * 329 * Requires moodle/competency:competencymanage capability at the system context. 330 * 331 * @param int $id The id of the competency to move. 332 * @param int $newparentid The new parent id for the competency. 333 * @return boolean 334 */ 335 public static function set_parent_competency($id, $newparentid) { 336 global $DB; 337 static::require_enabled(); 338 $current = new competency($id); 339 340 // First we do a permissions check. 341 require_capability('moodle/competency:competencymanage', $current->get_context()); 342 if ($id == $newparentid) { 343 throw new coding_exception('Can not set a competency as a parent of itself.'); 344 } if ($newparentid == $current->get_parentid()) { 345 throw new coding_exception('Can not move a competency to the same location.'); 346 } 347 348 // Some great variable assignment right here. 349 $currentparent = $current->get_parent(); 350 $parent = !empty($newparentid) ? new competency($newparentid) : null; 351 $parentpath = !empty($parent) ? $parent->get_path() : '/0/'; 352 353 // We're going to change quite a few things. 354 $transaction = $DB->start_delegated_transaction(); 355 356 // If we are moving a node to a child of itself: 357 // - promote all the child nodes by one level. 358 // - remove the rule on self. 359 // - re-read the parent. 360 $newparents = explode('/', $parentpath); 361 if (in_array($current->get_id(), $newparents)) { 362 $children = competency::get_records(array('parentid' => $current->get_id()), 'id'); 363 foreach ($children as $child) { 364 $child->set_parentid($current->get_parentid()); 365 $child->update(); 366 } 367 368 // Reset the rule on self as our children have changed. 369 $current->reset_rule(); 370 371 // The destination parent is one of our descendants, we need to re-fetch its values (path, parentid). 372 $parent->read(); 373 } 374 375 // Reset the rules of initial parent and destination. 376 if (!empty($currentparent)) { 377 $currentparent->reset_rule(); 378 $currentparent->update(); 379 } 380 if (!empty($parent)) { 381 $parent->reset_rule(); 382 $parent->update(); 383 } 384 385 // Do the actual move. 386 $current->set_parentid($newparentid); 387 $result = $current->update(); 388 389 // All right, let's commit this. 390 $transaction->allow_commit(); 391 392 return $result; 393 } 394 395 /** 396 * Update the details for a competency. 397 * 398 * Requires moodle/competency:competencymanage capability at the system context. 399 * 400 * @param stdClass $record The new details for the competency. 401 * Note - must contain an id that points to the competency to update. 402 * 403 * @return boolean 404 */ 405 public static function update_competency($record) { 406 static::require_enabled(); 407 $competency = new competency($record->id); 408 409 // First we do a permissions check. 410 require_capability('moodle/competency:competencymanage', $competency->get_context()); 411 412 // Some things should not be changed in an update - they should use a more specific method. 413 $record->sortorder = $competency->get_sortorder(); 414 $record->parentid = $competency->get_parentid(); 415 $record->competencyframeworkid = $competency->get_competencyframeworkid(); 416 417 $competency->from_record($record); 418 require_capability('moodle/competency:competencymanage', $competency->get_context()); 419 420 // OK - all set. 421 $result = $competency->update(); 422 423 // Trigger the update event. 424 \core\event\competency_updated::create_from_competency($competency)->trigger(); 425 426 return $result; 427 } 428 429 /** 430 * Read a the details for a single competency and return a record. 431 * 432 * Requires moodle/competency:competencyview capability at the system context. 433 * 434 * @param int $id The id of the competency to read. 435 * @param bool $includerelated Include related tags or not. 436 * @return stdClass 437 */ 438 public static function read_competency($id, $includerelated = false) { 439 static::require_enabled(); 440 $competency = new competency($id); 441 442 // First we do a permissions check. 443 $context = $competency->get_context(); 444 if (!has_any_capability(array('moodle/competency:competencyview', 'moodle/competency:competencymanage'), $context)) { 445 throw new required_capability_exception($context, 'moodle/competency:competencyview', 'nopermissions', ''); 446 } 447 448 // OK - all set. 449 if ($includerelated) { 450 $relatedcompetency = new related_competency(); 451 if ($related = $relatedcompetency->list_relations($id)) { 452 $competency->relatedcompetencies = $related; 453 } 454 } 455 456 return $competency; 457 } 458 459 /** 460 * Perform a text search based and return all results and their parents. 461 * 462 * Requires moodle/competency:competencyview capability at the framework context. 463 * 464 * @param string $textsearch A string to search for. 465 * @param int $competencyframeworkid The id of the framework to limit the search. 466 * @return array of competencies 467 */ 468 public static function search_competencies($textsearch, $competencyframeworkid) { 469 static::require_enabled(); 470 $framework = new competency_framework($competencyframeworkid); 471 472 // First we do a permissions check. 473 $context = $framework->get_context(); 474 if (!has_any_capability(array('moodle/competency:competencyview', 'moodle/competency:competencymanage'), $context)) { 475 throw new required_capability_exception($context, 'moodle/competency:competencyview', 'nopermissions', ''); 476 } 477 478 // OK - all set. 479 $competencies = competency::search($textsearch, $competencyframeworkid); 480 return $competencies; 481 } 482 483 /** 484 * Perform a search based on the provided filters and return a paginated list of records. 485 * 486 * Requires moodle/competency:competencyview capability at some context. 487 * 488 * @param array $filters A list of filters to apply to the list. 489 * @param string $sort The column to sort on 490 * @param string $order ('ASC' or 'DESC') 491 * @param int $skip Number of records to skip (pagination) 492 * @param int $limit Max of records to return (pagination) 493 * @return array of competencies 494 */ 495 public static function list_competencies($filters, $sort = '', $order = 'ASC', $skip = 0, $limit = 0) { 496 static::require_enabled(); 497 if (!isset($filters['competencyframeworkid'])) { 498 $context = context_system::instance(); 499 } else { 500 $framework = new competency_framework($filters['competencyframeworkid']); 501 $context = $framework->get_context(); 502 } 503 504 // First we do a permissions check. 505 if (!has_any_capability(array('moodle/competency:competencyview', 'moodle/competency:competencymanage'), $context)) { 506 throw new required_capability_exception($context, 'moodle/competency:competencyview', 'nopermissions', ''); 507 } 508 509 // OK - all set. 510 return competency::get_records($filters, $sort, $order, $skip, $limit); 511 } 512 513 /** 514 * Perform a search based on the provided filters and return a paginated list of records. 515 * 516 * Requires moodle/competency:competencyview capability at some context. 517 * 518 * @param array $filters A list of filters to apply to the list. 519 * @return int 520 */ 521 public static function count_competencies($filters) { 522 static::require_enabled(); 523 if (!isset($filters['competencyframeworkid'])) { 524 $context = context_system::instance(); 525 } else { 526 $framework = new competency_framework($filters['competencyframeworkid']); 527 $context = $framework->get_context(); 528 } 529 530 // First we do a permissions check. 531 if (!has_any_capability(array('moodle/competency:competencyview', 'moodle/competency:competencymanage'), $context)) { 532 throw new required_capability_exception($context, 'moodle/competency:competencyview', 'nopermissions', ''); 533 } 534 535 // OK - all set. 536 return competency::count_records($filters); 537 } 538 539 /** 540 * Create a competency framework from a record containing all the data for the class. 541 * 542 * Requires moodle/competency:competencymanage capability at the system context. 543 * 544 * @param stdClass $record Record containing all the data for an instance of the class. 545 * @return competency_framework 546 */ 547 public static function create_framework(stdClass $record) { 548 static::require_enabled(); 549 $framework = new competency_framework(0, $record); 550 require_capability('moodle/competency:competencymanage', $framework->get_context()); 551 552 // Account for different formats of taxonomies. 553 if (isset($record->taxonomies)) { 554 $framework->set_taxonomies($record->taxonomies); 555 } 556 557 $framework = $framework->create(); 558 559 // Trigger a competency framework created event. 560 \core\event\competency_framework_created::create_from_framework($framework)->trigger(); 561 562 return $framework; 563 } 564 565 /** 566 * Duplicate a competency framework by id. 567 * 568 * Requires moodle/competency:competencymanage capability at the system context. 569 * 570 * @param int $id The record to duplicate. All competencies associated and related will be duplicated. 571 * @return competency_framework the framework duplicated 572 */ 573 public static function duplicate_framework($id) { 574 global $DB; 575 static::require_enabled(); 576 577 $framework = new competency_framework($id); 578 require_capability('moodle/competency:competencymanage', $framework->get_context()); 579 // Starting transaction. 580 $transaction = $DB->start_delegated_transaction(); 581 582 try { 583 // Get a uniq idnumber based on the origin framework. 584 $idnumber = competency_framework::get_unused_idnumber($framework->get_idnumber()); 585 $framework->set_idnumber($idnumber); 586 // Adding the suffix copy to the shortname. 587 $framework->set_shortname(get_string('duplicateditemname', 'core_competency', $framework->get_shortname())); 588 $framework->set_id(0); 589 $framework = $framework->create(); 590 591 // Array that match the old competencies ids with the new one to use when copying related competencies. 592 $frameworkcompetency = competency::get_framework_tree($id); 593 $matchids = self::duplicate_competency_tree($framework->get_id(), $frameworkcompetency, 0, 0); 594 595 // Copy the related competencies. 596 $relcomps = related_competency::get_multiple_relations(array_keys($matchids)); 597 598 foreach ($relcomps as $relcomp) { 599 $compid = $relcomp->get_competencyid(); 600 $relcompid = $relcomp->get_relatedcompetencyid(); 601 if (isset($matchids[$compid]) && isset($matchids[$relcompid])) { 602 $newcompid = $matchids[$compid]->get_id(); 603 $newrelcompid = $matchids[$relcompid]->get_id(); 604 if ($newcompid < $newrelcompid) { 605 $relcomp->set_competencyid($newcompid); 606 $relcomp->set_relatedcompetencyid($newrelcompid); 607 } else { 608 $relcomp->set_competencyid($newrelcompid); 609 $relcomp->set_relatedcompetencyid($newcompid); 610 } 611 $relcomp->set_id(0); 612 $relcomp->create(); 613 } else { 614 // Debugging message when there is no match found. 615 debugging('related competency id not found'); 616 } 617 } 618 619 // Setting rules on duplicated competencies. 620 self::migrate_competency_tree_rules($frameworkcompetency, $matchids); 621 622 $transaction->allow_commit(); 623 624 } catch (\Exception $e) { 625 $transaction->rollback($e); 626 } 627 628 // Trigger a competency framework created event. 629 \core\event\competency_framework_created::create_from_framework($framework)->trigger(); 630 631 return $framework; 632 } 633 634 /** 635 * Delete a competency framework by id. 636 * 637 * Requires moodle/competency:competencymanage capability at the system context. 638 * 639 * @param int $id The record to delete. This will delete alot of related data - you better be sure. 640 * @return boolean 641 */ 642 public static function delete_framework($id) { 643 global $DB; 644 static::require_enabled(); 645 $framework = new competency_framework($id); 646 require_capability('moodle/competency:competencymanage', $framework->get_context()); 647 648 $events = array(); 649 $competenciesid = competency::get_ids_by_frameworkid($id); 650 $contextid = $framework->get_contextid(); 651 if (!competency::can_all_be_deleted($competenciesid)) { 652 return false; 653 } 654 $transaction = $DB->start_delegated_transaction(); 655 try { 656 if (!empty($competenciesid)) { 657 // Delete competencies. 658 competency::delete_by_frameworkid($id); 659 660 // Delete the related competencies. 661 related_competency::delete_multiple_relations($competenciesid); 662 663 // Delete the evidences for competencies. 664 user_evidence_competency::delete_by_competencyids($competenciesid); 665 } 666 667 // Create a competency framework deleted event. 668 $event = \core\event\competency_framework_deleted::create_from_framework($framework); 669 $result = $framework->delete(); 670 671 // Register the deleted events competencies. 672 $events = \core\event\competency_deleted::create_multiple_from_competencyids($competenciesid, $contextid); 673 674 } catch (\Exception $e) { 675 $transaction->rollback($e); 676 } 677 678 // Commit the transaction. 679 $transaction->allow_commit(); 680 681 // If all operations are successfull then trigger the delete event. 682 $event->trigger(); 683 684 // Trigger deleted event competencies. 685 foreach ($events as $event) { 686 $event->trigger(); 687 } 688 689 return $result; 690 } 691 692 /** 693 * Update the details for a competency framework. 694 * 695 * Requires moodle/competency:competencymanage capability at the system context. 696 * 697 * @param stdClass $record The new details for the framework. Note - must contain an id that points to the framework to update. 698 * @return boolean 699 */ 700 public static function update_framework($record) { 701 static::require_enabled(); 702 $framework = new competency_framework($record->id); 703 704 // Check the permissions before update. 705 require_capability('moodle/competency:competencymanage', $framework->get_context()); 706 707 // Account for different formats of taxonomies. 708 $framework->from_record($record); 709 if (isset($record->taxonomies)) { 710 $framework->set_taxonomies($record->taxonomies); 711 } 712 713 // Trigger a competency framework updated event. 714 \core\event\competency_framework_updated::create_from_framework($framework)->trigger(); 715 716 return $framework->update(); 717 } 718 719 /** 720 * Read a the details for a single competency framework and return a record. 721 * 722 * Requires moodle/competency:competencyview capability at the system context. 723 * 724 * @param int $id The id of the framework to read. 725 * @return competency_framework 726 */ 727 public static function read_framework($id) { 728 static::require_enabled(); 729 $framework = new competency_framework($id); 730 if (!has_any_capability(array('moodle/competency:competencyview', 'moodle/competency:competencymanage'), 731 $framework->get_context())) { 732 throw new required_capability_exception($framework->get_context(), 'moodle/competency:competencyview', 733 'nopermissions', ''); 734 } 735 return $framework; 736 } 737 738 /** 739 * Logg the competency framework viewed event. 740 * 741 * @param competency_framework|int $frameworkorid The competency_framework object or competency framework id 742 * @return bool 743 */ 744 public static function competency_framework_viewed($frameworkorid) { 745 static::require_enabled(); 746 $framework = $frameworkorid; 747 if (!is_object($framework)) { 748 $framework = new competency_framework($framework); 749 } 750 if (!has_any_capability(array('moodle/competency:competencyview', 'moodle/competency:competencymanage'), 751 $framework->get_context())) { 752 throw new required_capability_exception($framework->get_context(), 'moodle/competency:competencyview', 753 'nopermissions', ''); 754 } 755 \core\event\competency_framework_viewed::create_from_framework($framework)->trigger(); 756 return true; 757 } 758 759 /** 760 * Logg the competency viewed event. 761 * 762 * @param competency|int $competencyorid The competency object or competency id 763 * @return bool 764 */ 765 public static function competency_viewed($competencyorid) { 766 static::require_enabled(); 767 $competency = $competencyorid; 768 if (!is_object($competency)) { 769 $competency = new competency($competency); 770 } 771 772 if (!has_any_capability(array('moodle/competency:competencyview', 'moodle/competency:competencymanage'), 773 $competency->get_context())) { 774 throw new required_capability_exception($competency->get_context(), 'moodle/competency:competencyview', 775 'nopermissions', ''); 776 } 777 778 \core\event\competency_viewed::create_from_competency($competency)->trigger(); 779 return true; 780 } 781 782 /** 783 * Perform a search based on the provided filters and return a paginated list of records. 784 * 785 * Requires moodle/competency:competencyview capability at the system context. 786 * 787 * @param string $sort The column to sort on 788 * @param string $order ('ASC' or 'DESC') 789 * @param int $skip Number of records to skip (pagination) 790 * @param int $limit Max of records to return (pagination) 791 * @param context $context The parent context of the frameworks. 792 * @param string $includes Defines what other contexts to fetch frameworks from. 793 * Accepted values are: 794 * - children: All descendants 795 * - parents: All parents, grand parents, etc... 796 * - self: Context passed only. 797 * @param bool $onlyvisible If true return only visible frameworks 798 * @param string $query A string to use to filter down the frameworks. 799 * @return array of competency_framework 800 */ 801 public static function list_frameworks($sort, $order, $skip, $limit, $context, $includes = 'children', 802 $onlyvisible = false, $query = '') { 803 global $DB; 804 static::require_enabled(); 805 806 // Get all the relevant contexts. 807 $contexts = self::get_related_contexts($context, $includes, 808 array('moodle/competency:competencyview', 'moodle/competency:competencymanage')); 809 810 if (empty($contexts)) { 811 throw new required_capability_exception($context, 'moodle/competency:competencyview', 'nopermissions', ''); 812 } 813 814 // OK - all set. 815 list($insql, $inparams) = $DB->get_in_or_equal(array_keys($contexts), SQL_PARAMS_NAMED); 816 $select = "contextid $insql"; 817 if ($onlyvisible) { 818 $select .= " AND visible = :visible"; 819 $inparams['visible'] = 1; 820 } 821 822 if (!empty($query) || is_numeric($query)) { 823 $sqlnamelike = $DB->sql_like('shortname', ':namelike', false); 824 $sqlidnlike = $DB->sql_like('idnumber', ':idnlike', false); 825 826 $select .= " AND ($sqlnamelike OR $sqlidnlike) "; 827 $inparams['namelike'] = '%' . $DB->sql_like_escape($query) . '%'; 828 $inparams['idnlike'] = '%' . $DB->sql_like_escape($query) . '%'; 829 } 830 831 return competency_framework::get_records_select($select, $inparams, $sort . ' ' . $order, '*', $skip, $limit); 832 } 833 834 /** 835 * Perform a search based on the provided filters and return a paginated list of records. 836 * 837 * Requires moodle/competency:competencyview capability at the system context. 838 * 839 * @param context $context The parent context of the frameworks. 840 * @param string $includes Defines what other contexts to fetch frameworks from. 841 * Accepted values are: 842 * - children: All descendants 843 * - parents: All parents, grand parents, etc... 844 * - self: Context passed only. 845 * @return int 846 */ 847 public static function count_frameworks($context, $includes) { 848 global $DB; 849 static::require_enabled(); 850 851 // Get all the relevant contexts. 852 $contexts = self::get_related_contexts($context, $includes, 853 array('moodle/competency:competencyview', 'moodle/competency:competencymanage')); 854 855 if (empty($contexts)) { 856 throw new required_capability_exception($context, 'moodle/competency:competencyview', 'nopermissions', ''); 857 } 858 859 // OK - all set. 860 list($insql, $inparams) = $DB->get_in_or_equal(array_keys($contexts), SQL_PARAMS_NAMED); 861 return competency_framework::count_records_select("contextid $insql", $inparams); 862 } 863 864 /** 865 * Fetches all the relevant contexts. 866 * 867 * Note: This currently only supports system, category and user contexts. However user contexts 868 * behave a bit differently and will fallback on the system context. This is what makes the most 869 * sense because a user context does not have descendants, and only has system as a parent. 870 * 871 * @param context $context The context to start from. 872 * @param string $includes Defines what other contexts to find. 873 * Accepted values are: 874 * - children: All descendants 875 * - parents: All parents, grand parents, etc... 876 * - self: Context passed only. 877 * @param array $hasanycapability Array of capabilities passed to {@link has_any_capability()} in each context. 878 * @return context[] An array of contexts where keys are context IDs. 879 */ 880 public static function get_related_contexts($context, $includes, array $hasanycapability = null) { 881 global $DB; 882 static::require_enabled(); 883 884 if (!in_array($includes, array('children', 'parents', 'self'))) { 885 throw new coding_exception('Invalid parameter value for \'includes\'.'); 886 } 887 888 // If context user swap it for the context_system. 889 if ($context->contextlevel == CONTEXT_USER) { 890 $context = context_system::instance(); 891 } 892 893 $contexts = array($context->id => $context); 894 895 if ($includes == 'children') { 896 $params = array('coursecatlevel' => CONTEXT_COURSECAT, 'path' => $context->path . '/%'); 897 $pathlike = $DB->sql_like('path', ':path'); 898 $sql = "contextlevel = :coursecatlevel AND $pathlike"; 899 $rs = $DB->get_recordset_select('context', $sql, $params); 900 foreach ($rs as $record) { 901 $ctxid = $record->id; 902 context_helper::preload_from_record($record); 903 $contexts[$ctxid] = context::instance_by_id($ctxid); 904 } 905 $rs->close(); 906 907 } else if ($includes == 'parents') { 908 $children = $context->get_parent_contexts(); 909 foreach ($children as $ctx) { 910 $contexts[$ctx->id] = $ctx; 911 } 912 } 913 914 // Filter according to the capabilities required. 915 if (!empty($hasanycapability)) { 916 foreach ($contexts as $key => $ctx) { 917 if (!has_any_capability($hasanycapability, $ctx)) { 918 unset($contexts[$key]); 919 } 920 } 921 } 922 923 return $contexts; 924 } 925 926 /** 927 * Count all the courses using a competency. 928 * 929 * @param int $competencyid The id of the competency to check. 930 * @return int 931 */ 932 public static function count_courses_using_competency($competencyid) { 933 static::require_enabled(); 934 935 // OK - all set. 936 $courses = course_competency::list_courses_min($competencyid); 937 $count = 0; 938 939 // Now check permissions on each course. 940 foreach ($courses as $course) { 941 if (!self::validate_course($course, false)) { 942 continue; 943 } 944 945 $context = context_course::instance($course->id); 946 $capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage'); 947 if (!has_any_capability($capabilities, $context)) { 948 continue; 949 } 950 951 $count++; 952 } 953 954 return $count; 955 } 956 957 /** 958 * List all the courses modules using a competency in a course. 959 * 960 * @param int $competencyid The id of the competency to check. 961 * @param int $courseid The id of the course to check. 962 * @return array[int] Array of course modules ids. 963 */ 964 public static function list_course_modules_using_competency($competencyid, $courseid) { 965 static::require_enabled(); 966 967 $result = array(); 968 self::validate_course($courseid); 969 970 $coursecontext = context_course::instance($courseid); 971 972 // We will not check each module - course permissions should be enough. 973 $capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage'); 974 if (!has_any_capability($capabilities, $coursecontext)) { 975 throw new required_capability_exception($coursecontext, 'moodle/competency:coursecompetencyview', 'nopermissions', ''); 976 } 977 978 $cmlist = course_module_competency::list_course_modules($competencyid, $courseid); 979 foreach ($cmlist as $cmid) { 980 if (self::validate_course_module($cmid, false)) { 981 array_push($result, $cmid); 982 } 983 } 984 985 return $result; 986 } 987 988 /** 989 * List all the competencies linked to a course module. 990 * 991 * @param mixed $cmorid The course module, or its ID. 992 * @return array[competency] Array of competency records. 993 */ 994 public static function list_course_module_competencies_in_course_module($cmorid) { 995 static::require_enabled(); 996 $cm = $cmorid; 997 if (!is_object($cmorid)) { 998 $cm = get_coursemodule_from_id('', $cmorid, 0, true, MUST_EXIST); 999 } 1000 1001 // Check the user have access to the course module. 1002 self::validate_course_module($cm); 1003 $context = context_module::instance($cm->id); 1004 1005 $capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage'); 1006 if (!has_any_capability($capabilities, $context)) { 1007 throw new required_capability_exception($context, 'moodle/competency:coursecompetencyview', 'nopermissions', ''); 1008 } 1009 1010 $result = array(); 1011 1012 $cmclist = course_module_competency::list_course_module_competencies($cm->id); 1013 foreach ($cmclist as $id => $cmc) { 1014 array_push($result, $cmc); 1015 } 1016 1017 return $result; 1018 } 1019 1020 /** 1021 * List all the courses using a competency. 1022 * 1023 * @param int $competencyid The id of the competency to check. 1024 * @return array[stdClass] Array of stdClass containing id and shortname. 1025 */ 1026 public static function list_courses_using_competency($competencyid) { 1027 static::require_enabled(); 1028 1029 // OK - all set. 1030 $courses = course_competency::list_courses($competencyid); 1031 $result = array(); 1032 1033 // Now check permissions on each course. 1034 foreach ($courses as $id => $course) { 1035 $context = context_course::instance($course->id); 1036 $capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage'); 1037 if (!has_any_capability($capabilities, $context)) { 1038 unset($courses[$id]); 1039 continue; 1040 } 1041 if (!self::validate_course($course, false)) { 1042 unset($courses[$id]); 1043 continue; 1044 } 1045 array_push($result, $course); 1046 } 1047 1048 return $result; 1049 } 1050 1051 /** 1052 * Count the proficient competencies in a course for one user. 1053 * 1054 * @param int $courseid The id of the course to check. 1055 * @param int $userid The id of the user to check. 1056 * @return int 1057 */ 1058 public static function count_proficient_competencies_in_course_for_user($courseid, $userid) { 1059 static::require_enabled(); 1060 // Check the user have access to the course. 1061 self::validate_course($courseid); 1062 1063 // First we do a permissions check. 1064 $context = context_course::instance($courseid); 1065 1066 $capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage'); 1067 if (!has_any_capability($capabilities, $context)) { 1068 throw new required_capability_exception($context, 'moodle/competency:coursecompetencyview', 'nopermissions', ''); 1069 } 1070 1071 // OK - all set. 1072 return user_competency_course::count_proficient_competencies($courseid, $userid); 1073 } 1074 1075 /** 1076 * Count all the competencies in a course. 1077 * 1078 * @param int $courseid The id of the course to check. 1079 * @return int 1080 */ 1081 public static function count_competencies_in_course($courseid) { 1082 static::require_enabled(); 1083 // Check the user have access to the course. 1084 self::validate_course($courseid); 1085 1086 // First we do a permissions check. 1087 $context = context_course::instance($courseid); 1088 1089 $capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage'); 1090 if (!has_any_capability($capabilities, $context)) { 1091 throw new required_capability_exception($context, 'moodle/competency:coursecompetencyview', 'nopermissions', ''); 1092 } 1093 1094 // OK - all set. 1095 return course_competency::count_competencies($courseid); 1096 } 1097 1098 /** 1099 * List the competencies associated to a course. 1100 * 1101 * @param mixed $courseorid The course, or its ID. 1102 * @return array( array( 1103 * 'competency' => \core_competency\competency, 1104 * 'coursecompetency' => \core_competency\course_competency 1105 * )) 1106 */ 1107 public static function list_course_competencies($courseorid) { 1108 static::require_enabled(); 1109 $course = $courseorid; 1110 if (!is_object($courseorid)) { 1111 $course = get_course($courseorid); 1112 } 1113 1114 // Check the user have access to the course. 1115 self::validate_course($course); 1116 $context = context_course::instance($course->id); 1117 1118 $capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage'); 1119 if (!has_any_capability($capabilities, $context)) { 1120 throw new required_capability_exception($context, 'moodle/competency:coursecompetencyview', 'nopermissions', ''); 1121 } 1122 1123 $result = array(); 1124 1125 // TODO We could improve the performance of this into one single query. 1126 $coursecompetencies = course_competency::list_course_competencies($course->id); 1127 $competencies = course_competency::list_competencies($course->id); 1128 1129 // Build the return values. 1130 foreach ($coursecompetencies as $key => $coursecompetency) { 1131 $result[] = array( 1132 'competency' => $competencies[$coursecompetency->get_competencyid()], 1133 'coursecompetency' => $coursecompetency 1134 ); 1135 } 1136 1137 return $result; 1138 } 1139 1140 /** 1141 * Get a user competency. 1142 * 1143 * @param int $userid The user ID. 1144 * @param int $competencyid The competency ID. 1145 * @return user_competency 1146 */ 1147 public static function get_user_competency($userid, $competencyid) { 1148 static::require_enabled(); 1149 $existing = user_competency::get_multiple($userid, array($competencyid)); 1150 $uc = array_pop($existing); 1151 1152 if (!$uc) { 1153 $uc = user_competency::create_relation($userid, $competencyid); 1154 $uc->create(); 1155 } 1156 1157 if (!$uc->can_read()) { 1158 throw new required_capability_exception($uc->get_context(), 'moodle/competency:usercompetencyview', 1159 'nopermissions', ''); 1160 } 1161 return $uc; 1162 } 1163 1164 /** 1165 * Get a user competency by ID. 1166 * 1167 * @param int $usercompetencyid The user competency ID. 1168 * @return user_competency 1169 */ 1170 public static function get_user_competency_by_id($usercompetencyid) { 1171 static::require_enabled(); 1172 $uc = new user_competency($usercompetencyid); 1173 if (!$uc->can_read()) { 1174 throw new required_capability_exception($uc->get_context(), 'moodle/competency:usercompetencyview', 1175 'nopermissions', ''); 1176 } 1177 return $uc; 1178 } 1179 1180 /** 1181 * List the competencies associated to a course module. 1182 * 1183 * @param mixed $cmorid The course module, or its ID. 1184 * @return array( array( 1185 * 'competency' => \core_competency\competency, 1186 * 'coursemodulecompetency' => \core_competency\course_module_competency 1187 * )) 1188 */ 1189 public static function list_course_module_competencies($cmorid) { 1190 static::require_enabled(); 1191 $cm = $cmorid; 1192 if (!is_object($cmorid)) { 1193 $cm = get_coursemodule_from_id('', $cmorid, 0, true, MUST_EXIST); 1194 } 1195 1196 // Check the user have access to the course module. 1197 self::validate_course_module($cm); 1198 $context = context_module::instance($cm->id); 1199 1200 $capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage'); 1201 if (!has_any_capability($capabilities, $context)) { 1202 throw new required_capability_exception($context, 'moodle/competency:coursecompetencyview', 'nopermissions', ''); 1203 } 1204 1205 $result = array(); 1206 1207 // TODO We could improve the performance of this into one single query. 1208 $coursemodulecompetencies = course_competency::list_course_module_competencies($cm->id); 1209 $competencies = course_module_competency::list_competencies($cm->id); 1210 1211 // Build the return values. 1212 foreach ($coursemodulecompetencies as $key => $coursemodulecompetency) { 1213 $result[] = array( 1214 'competency' => $competencies[$coursemodulecompetency->get_competencyid()], 1215 'coursemodulecompetency' => $coursemodulecompetency 1216 ); 1217 } 1218 1219 return $result; 1220 } 1221 1222 /** 1223 * Get a user competency in a course. 1224 * 1225 * @param int $courseid The id of the course to check. 1226 * @param int $userid The id of the course to check. 1227 * @param int $competencyid The id of the competency. 1228 * @return user_competency_course 1229 */ 1230 public static function get_user_competency_in_course($courseid, $userid, $competencyid) { 1231 static::require_enabled(); 1232 // First we do a permissions check. 1233 $context = context_course::instance($courseid); 1234 1235 $capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage'); 1236 if (!has_any_capability($capabilities, $context)) { 1237 throw new required_capability_exception($context, 'moodle/competency:coursecompetencyview', 'nopermissions', ''); 1238 } else if (!user_competency::can_read_user_in_course($userid, $courseid)) { 1239 throw new required_capability_exception($context, 'moodle/competency:usercompetencyview', 'nopermissions', ''); 1240 } 1241 1242 // This will throw an exception if the competency does not belong to the course. 1243 $competency = course_competency::get_competency($courseid, $competencyid); 1244 1245 $params = array('courseid' => $courseid, 'userid' => $userid, 'competencyid' => $competencyid); 1246 $exists = user_competency_course::get_record($params); 1247 // Create missing. 1248 if ($exists) { 1249 $ucc = $exists; 1250 } else { 1251 $ucc = user_competency_course::create_relation($userid, $competency->get_id(), $courseid); 1252 $ucc->create(); 1253 } 1254 1255 return $ucc; 1256 } 1257 1258 /** 1259 * List all the user competencies in a course. 1260 * 1261 * @param int $courseid The id of the course to check. 1262 * @param int $userid The id of the course to check. 1263 * @return array of user_competency_course objects 1264 */ 1265 public static function list_user_competencies_in_course($courseid, $userid) { 1266 static::require_enabled(); 1267 // First we do a permissions check. 1268 $context = context_course::instance($courseid); 1269 $onlyvisible = 1; 1270 1271 $capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage'); 1272 if (!has_any_capability($capabilities, $context)) { 1273 throw new required_capability_exception($context, 'moodle/competency:coursecompetencyview', 'nopermissions', ''); 1274 } else if (!user_competency::can_read_user_in_course($userid, $courseid)) { 1275 throw new required_capability_exception($context, 'moodle/competency:usercompetencyview', 'nopermissions', ''); 1276 } 1277 1278 // OK - all set. 1279 $competencylist = course_competency::list_competencies($courseid, false); 1280 1281 $existing = user_competency_course::get_multiple($userid, $courseid, $competencylist); 1282 // Create missing. 1283 $orderedusercompetencycourses = array(); 1284 1285 $somemissing = false; 1286 foreach ($competencylist as $coursecompetency) { 1287 $found = false; 1288 foreach ($existing as $usercompetencycourse) { 1289 if ($usercompetencycourse->get_competencyid() == $coursecompetency->get_id()) { 1290 $found = true; 1291 $orderedusercompetencycourses[$usercompetencycourse->get_id()] = $usercompetencycourse; 1292 break; 1293 } 1294 } 1295 if (!$found) { 1296 $ucc = user_competency_course::create_relation($userid, $coursecompetency->get_id(), $courseid); 1297 $ucc->create(); 1298 $orderedusercompetencycourses[$ucc->get_id()] = $ucc; 1299 } 1300 } 1301 1302 return $orderedusercompetencycourses; 1303 } 1304 1305 /** 1306 * List the user competencies to review. 1307 * 1308 * The method returns values in this format: 1309 * 1310 * array( 1311 * 'competencies' => array( 1312 * (stdClass)( 1313 * 'usercompetency' => (user_competency), 1314 * 'competency' => (competency), 1315 * 'user' => (user) 1316 * ) 1317 * ), 1318 * 'count' => (int) 1319 * ) 1320 * 1321 * @param int $skip The number of records to skip. 1322 * @param int $limit The number of results to return. 1323 * @param int $userid The user we're getting the competencies to review for. 1324 * @return array Containing the keys 'count', and 'competencies'. The 'competencies' key contains an object 1325 * which contains 'competency', 'usercompetency' and 'user'. 1326 */ 1327 public static function list_user_competencies_to_review($skip = 0, $limit = 50, $userid = null) { 1328 global $DB, $USER; 1329 static::require_enabled(); 1330 if ($userid === null) { 1331 $userid = $USER->id; 1332 } 1333 1334 $capability = 'moodle/competency:usercompetencyreview'; 1335 $ucfields = user_competency::get_sql_fields('uc', 'uc_'); 1336 $compfields = competency::get_sql_fields('c', 'c_'); 1337 $usercols = array('id') + get_user_fieldnames(); 1338 $userfields = array(); 1339 foreach ($usercols as $field) { 1340 $userfields[] = "u." . $field . " AS usr_" . $field; 1341 } 1342 $userfields = implode(',', $userfields); 1343 1344 $select = "SELECT $ucfields, $compfields, $userfields"; 1345 $countselect = "SELECT COUNT('x')"; 1346 $sql = " FROM {" . user_competency::TABLE . "} uc 1347 JOIN {" . competency::TABLE . "} c 1348 ON c.id = uc.competencyid 1349 JOIN {user} u 1350 ON u.id = uc.userid 1351 WHERE (uc.status = :waitingforreview 1352 OR (uc.status = :inreview AND uc.reviewerid = :reviewerid))"; 1353 $ordersql = " ORDER BY c.shortname ASC"; 1354 $params = array( 1355 'inreview' => user_competency::STATUS_IN_REVIEW, 1356 'reviewerid' => $userid, 1357 'waitingforreview' => user_competency::STATUS_WAITING_FOR_REVIEW, 1358 ); 1359 $countsql = $countselect . $sql; 1360 1361 // Primary check to avoid the hard work of getting the users in which the user has permission. 1362 $count = $DB->count_records_sql($countselect . $sql, $params); 1363 if ($count < 1) { 1364 return array('count' => 0, 'competencies' => array()); 1365 } 1366 1367 // TODO MDL-52243 Use core function. 1368 list($insql, $inparams) = self::filter_users_with_capability_on_user_context_sql( 1369 $capability, $userid, SQL_PARAMS_NAMED); 1370 $params += $inparams; 1371 $countsql = $countselect . $sql . " AND uc.userid $insql"; 1372 $getsql = $select . $sql . " AND uc.userid $insql " . $ordersql; 1373 1374 // Extracting the results. 1375 $competencies = array(); 1376 $records = $DB->get_recordset_sql($getsql, $params, $skip, $limit); 1377 foreach ($records as $record) { 1378 $objects = (object) array( 1379 'usercompetency' => new user_competency(0, user_competency::extract_record($record, 'uc_')), 1380 'competency' => new competency(0, competency::extract_record($record, 'c_')), 1381 'user' => persistent::extract_record($record, 'usr_'), 1382 ); 1383 $competencies[] = $objects; 1384 } 1385 $records->close(); 1386 1387 return array( 1388 'count' => $DB->count_records_sql($countsql, $params), 1389 'competencies' => $competencies 1390 ); 1391 } 1392 1393 /** 1394 * Add a competency to this course module. 1395 * 1396 * @param mixed $cmorid The course module, or id of the course module 1397 * @param int $competencyid The id of the competency 1398 * @return bool 1399 */ 1400 public static function add_competency_to_course_module($cmorid, $competencyid) { 1401 static::require_enabled(); 1402 $cm = $cmorid; 1403 if (!is_object($cmorid)) { 1404 $cm = get_coursemodule_from_id('', $cmorid, 0, true, MUST_EXIST); 1405 } 1406 1407 // Check the user have access to the course module. 1408 self::validate_course_module($cm); 1409 1410 // First we do a permissions check. 1411 $context = context_module::instance($cm->id); 1412 1413 require_capability('moodle/competency:coursecompetencymanage', $context); 1414 1415 // Check that the competency belongs to the course. 1416 $exists = course_competency::get_records(array('courseid' => $cm->course, 'competencyid' => $competencyid)); 1417 if (!$exists) { 1418 throw new coding_exception('Cannot add a competency to a module if it does not belong to the course'); 1419 } 1420 1421 $record = new stdClass(); 1422 $record->cmid = $cm->id; 1423 $record->competencyid = $competencyid; 1424 1425 $coursemodulecompetency = new course_module_competency(); 1426 $exists = $coursemodulecompetency->get_records(array('cmid' => $cm->id, 'competencyid' => $competencyid)); 1427 if (!$exists) { 1428 $coursemodulecompetency->from_record($record); 1429 if ($coursemodulecompetency->create()) { 1430 return true; 1431 } 1432 } 1433 return false; 1434 } 1435 1436 /** 1437 * Remove a competency from this course module. 1438 * 1439 * @param mixed $cmorid The course module, or id of the course module 1440 * @param int $competencyid The id of the competency 1441 * @return bool 1442 */ 1443 public static function remove_competency_from_course_module($cmorid, $competencyid) { 1444 static::require_enabled(); 1445 $cm = $cmorid; 1446 if (!is_object($cmorid)) { 1447 $cm = get_coursemodule_from_id('', $cmorid, 0, true, MUST_EXIST); 1448 } 1449 // Check the user have access to the course module. 1450 self::validate_course_module($cm); 1451 1452 // First we do a permissions check. 1453 $context = context_module::instance($cm->id); 1454 1455 require_capability('moodle/competency:coursecompetencymanage', $context); 1456 1457 $record = new stdClass(); 1458 $record->cmid = $cm->id; 1459 $record->competencyid = $competencyid; 1460 1461 $competency = new competency($competencyid); 1462 $exists = course_module_competency::get_record(array('cmid' => $cm->id, 'competencyid' => $competencyid)); 1463 if ($exists) { 1464 return $exists->delete(); 1465 } 1466 return false; 1467 } 1468 1469 /** 1470 * Move the course module competency up or down in the display list. 1471 * 1472 * Requires moodle/competency:coursecompetencymanage capability at the course module context. 1473 * 1474 * @param mixed $cmorid The course module, or id of the course module 1475 * @param int $competencyidfrom The id of the competency we are moving. 1476 * @param int $competencyidto The id of the competency we are moving to. 1477 * @return boolean 1478 */ 1479 public static function reorder_course_module_competency($cmorid, $competencyidfrom, $competencyidto) { 1480 static::require_enabled(); 1481 $cm = $cmorid; 1482 if (!is_object($cmorid)) { 1483 $cm = get_coursemodule_from_id('', $cmorid, 0, true, MUST_EXIST); 1484 } 1485 // Check the user have access to the course module. 1486 self::validate_course_module($cm); 1487 1488 // First we do a permissions check. 1489 $context = context_module::instance($cm->id); 1490 1491 require_capability('moodle/competency:coursecompetencymanage', $context); 1492 1493 $down = true; 1494 $matches = course_module_competency::get_records(array('cmid' => $cm->id, 'competencyid' => $competencyidfrom)); 1495 if (count($matches) == 0) { 1496 throw new coding_exception('The link does not exist'); 1497 } 1498 1499 $competencyfrom = array_pop($matches); 1500 $matches = course_module_competency::get_records(array('cmid' => $cm->id, 'competencyid' => $competencyidto)); 1501 if (count($matches) == 0) { 1502 throw new coding_exception('The link does not exist'); 1503 } 1504 1505 $competencyto = array_pop($matches); 1506 1507 $all = course_module_competency::get_records(array('cmid' => $cm->id), 'sortorder', 'ASC', 0, 0); 1508 1509 if ($competencyfrom->get_sortorder() > $competencyto->get_sortorder()) { 1510 // We are moving up, so put it before the "to" item. 1511 $down = false; 1512 } 1513 1514 foreach ($all as $id => $coursemodulecompetency) { 1515 $sort = $coursemodulecompetency->get_sortorder(); 1516 if ($down && $sort > $competencyfrom->get_sortorder() && $sort <= $competencyto->get_sortorder()) { 1517 $coursemodulecompetency->set_sortorder($coursemodulecompetency->get_sortorder() - 1); 1518 $coursemodulecompetency->update(); 1519 } else if (!$down && $sort >= $competencyto->get_sortorder() && $sort < $competencyfrom->get_sortorder()) { 1520 $coursemodulecompetency->set_sortorder($coursemodulecompetency->get_sortorder() + 1); 1521 $coursemodulecompetency->update(); 1522 } 1523 } 1524 $competencyfrom->set_sortorder($competencyto->get_sortorder()); 1525 return $competencyfrom->update(); 1526 } 1527 1528 /** 1529 * Update ruleoutcome value for a course module competency. 1530 * 1531 * @param int|course_module_competency $coursemodulecompetencyorid The course_module_competency, or its ID. 1532 * @param int $ruleoutcome The value of ruleoutcome. 1533 * @return bool True on success. 1534 */ 1535 public static function set_course_module_competency_ruleoutcome($coursemodulecompetencyorid, $ruleoutcome) { 1536 static::require_enabled(); 1537 $coursemodulecompetency = $coursemodulecompetencyorid; 1538 if (!is_object($coursemodulecompetency)) { 1539 $coursemodulecompetency = new course_module_competency($coursemodulecompetencyorid); 1540 } 1541 1542 $cm = get_coursemodule_from_id('', $coursemodulecompetency->get_cmid(), 0, true, MUST_EXIST); 1543 1544 self::validate_course_module($cm); 1545 $context = context_module::instance($cm->id); 1546 1547 require_capability('moodle/competency:coursecompetencymanage', $context); 1548 1549 $coursemodulecompetency->set_ruleoutcome($ruleoutcome); 1550 return $coursemodulecompetency->update(); 1551 } 1552 1553 /** 1554 * Add a competency to this course. 1555 * 1556 * @param int $courseid The id of the course 1557 * @param int $competencyid The id of the competency 1558 * @return bool 1559 */ 1560 public static function add_competency_to_course($courseid, $competencyid) { 1561 static::require_enabled(); 1562 // Check the user have access to the course. 1563 self::validate_course($courseid); 1564 1565 // First we do a permissions check. 1566 $context = context_course::instance($courseid); 1567 1568 require_capability('moodle/competency:coursecompetencymanage', $context); 1569 1570 $record = new stdClass(); 1571 $record->courseid = $courseid; 1572 $record->competencyid = $competencyid; 1573 1574 $competency = new competency($competencyid); 1575 1576 // Can not add a competency that belong to a hidden framework. 1577 if ($competency->get_framework()->get_visible() == false) { 1578 throw new coding_exception('A competency belonging to hidden framework can not be linked to course'); 1579 } 1580 1581 $coursecompetency = new course_competency(); 1582 $exists = $coursecompetency->get_records(array('courseid' => $courseid, 'competencyid' => $competencyid)); 1583 if (!$exists) { 1584 $coursecompetency->from_record($record); 1585 if ($coursecompetency->create()) { 1586 return true; 1587 } 1588 } 1589 return false; 1590 } 1591 1592 /** 1593 * Remove a competency from this course. 1594 * 1595 * @param int $courseid The id of the course 1596 * @param int $competencyid The id of the competency 1597 * @return bool 1598 */ 1599 public static function remove_competency_from_course($courseid, $competencyid) { 1600 static::require_enabled(); 1601 // Check the user have access to the course. 1602 self::validate_course($courseid); 1603 1604 // First we do a permissions check. 1605 $context = context_course::instance($courseid); 1606 1607 require_capability('moodle/competency:coursecompetencymanage', $context); 1608 1609 $record = new stdClass(); 1610 $record->courseid = $courseid; 1611 $record->competencyid = $competencyid; 1612 1613 $coursecompetency = new course_competency(); 1614 $exists = course_competency::get_record(array('courseid' => $courseid, 'competencyid' => $competencyid)); 1615 if ($exists) { 1616 // Delete all course_module_competencies for this competency in this course. 1617 $cmcs = course_module_competency::get_records_by_competencyid_in_course($competencyid, $courseid); 1618 foreach ($cmcs as $cmc) { 1619 $cmc->delete(); 1620 } 1621 return $exists->delete(); 1622 } 1623 return false; 1624 } 1625 1626 /** 1627 * Move the course competency up or down in the display list. 1628 * 1629 * Requires moodle/competency:coursecompetencymanage capability at the course context. 1630 * 1631 * @param int $courseid The course 1632 * @param int $competencyidfrom The id of the competency we are moving. 1633 * @param int $competencyidto The id of the competency we are moving to. 1634 * @return boolean 1635 */ 1636 public static function reorder_course_competency($courseid, $competencyidfrom, $competencyidto) { 1637 static::require_enabled(); 1638 // Check the user have access to the course. 1639 self::validate_course($courseid); 1640 1641 // First we do a permissions check. 1642 $context = context_course::instance($courseid); 1643 1644 require_capability('moodle/competency:coursecompetencymanage', $context); 1645 1646 $down = true; 1647 $coursecompetency = new course_competency(); 1648 $matches = $coursecompetency->get_records(array('courseid' => $courseid, 'competencyid' => $competencyidfrom)); 1649 if (count($matches) == 0) { 1650 throw new coding_exception('The link does not exist'); 1651 } 1652 1653 $competencyfrom = array_pop($matches); 1654 $matches = $coursecompetency->get_records(array('courseid' => $courseid, 'competencyid' => $competencyidto)); 1655 if (count($matches) == 0) { 1656 throw new coding_exception('The link does not exist'); 1657 } 1658 1659 $competencyto = array_pop($matches); 1660 1661 $all = $coursecompetency->get_records(array('courseid' => $courseid), 'sortorder', 'ASC', 0, 0); 1662 1663 if ($competencyfrom->get_sortorder() > $competencyto->get_sortorder()) { 1664 // We are moving up, so put it before the "to" item. 1665 $down = false; 1666 } 1667 1668 foreach ($all as $id => $coursecompetency) { 1669 $sort = $coursecompetency->get_sortorder(); 1670 if ($down && $sort > $competencyfrom->get_sortorder() && $sort <= $competencyto->get_sortorder()) { 1671 $coursecompetency->set_sortorder($coursecompetency->get_sortorder() - 1); 1672 $coursecompetency->update(); 1673 } else if (!$down && $sort >= $competencyto->get_sortorder() && $sort < $competencyfrom->get_sortorder()) { 1674 $coursecompetency->set_sortorder($coursecompetency->get_sortorder() + 1); 1675 $coursecompetency->update(); 1676 } 1677 } 1678 $competencyfrom->set_sortorder($competencyto->get_sortorder()); 1679 return $competencyfrom->update(); 1680 } 1681 1682 /** 1683 * Update ruleoutcome value for a course competency. 1684 * 1685 * @param int|course_competency $coursecompetencyorid The course_competency, or its ID. 1686 * @param int $ruleoutcome The value of ruleoutcome. 1687 * @return bool True on success. 1688 */ 1689 public static function set_course_competency_ruleoutcome($coursecompetencyorid, $ruleoutcome) { 1690 static::require_enabled(); 1691 $coursecompetency = $coursecompetencyorid; 1692 if (!is_object($coursecompetency)) { 1693 $coursecompetency = new course_competency($coursecompetencyorid); 1694 } 1695 1696 $courseid = $coursecompetency->get_courseid(); 1697 self::validate_course($courseid); 1698 $coursecontext = context_course::instance($courseid); 1699 1700 require_capability('moodle/competency:coursecompetencymanage', $coursecontext); 1701 1702 $coursecompetency->set_ruleoutcome($ruleoutcome); 1703 return $coursecompetency->update(); 1704 } 1705 1706 /** 1707 * Create a learning plan template from a record containing all the data for the class. 1708 * 1709 * Requires moodle/competency:templatemanage capability. 1710 * 1711 * @param stdClass $record Record containing all the data for an instance of the class. 1712 * @return template 1713 */ 1714 public static function create_template(stdClass $record) { 1715 static::require_enabled(); 1716 $template = new template(0, $record); 1717 1718 // First we do a permissions check. 1719 if (!$template->can_manage()) { 1720 throw new required_capability_exception($template->get_context(), 'moodle/competency:templatemanage', 1721 'nopermissions', ''); 1722 } 1723 1724 // OK - all set. 1725 $template = $template->create(); 1726 1727 // Trigger a template created event. 1728 \core\event\competency_template_created::create_from_template($template)->trigger(); 1729 1730 return $template; 1731 } 1732 1733 /** 1734 * Duplicate a learning plan template. 1735 * 1736 * Requires moodle/competency:templatemanage capability at the template context. 1737 * 1738 * @param int $id the template id. 1739 * @return template 1740 */ 1741 public static function duplicate_template($id) { 1742 static::require_enabled(); 1743 $template = new template($id); 1744 1745 // First we do a permissions check. 1746 if (!$template->can_manage()) { 1747 throw new required_capability_exception($template->get_context(), 'moodle/competency:templatemanage', 1748 'nopermissions', ''); 1749 } 1750 1751 // OK - all set. 1752 $competencies = template_competency::list_competencies($id, false); 1753 1754 // Adding the suffix copy. 1755 $template->set_shortname(get_string('duplicateditemname', 'core_competency', $template->get_shortname())); 1756 $template->set_id(0); 1757 1758 $duplicatedtemplate = $template->create(); 1759 1760 // Associate each competency for the duplicated template. 1761 foreach ($competencies as $competency) { 1762 self::add_competency_to_template($duplicatedtemplate->get_id(), $competency->get_id()); 1763 } 1764 1765 // Trigger a template created event. 1766 \core\event\competency_template_created::create_from_template($duplicatedtemplate)->trigger(); 1767 1768 return $duplicatedtemplate; 1769 } 1770 1771 /** 1772 * Delete a learning plan template by id. 1773 * If the learning plan template has associated cohorts they will be deleted. 1774 * 1775 * Requires moodle/competency:templatemanage capability. 1776 * 1777 * @param int $id The record to delete. 1778 * @param boolean $deleteplans True to delete plans associaated to template, false to unlink them. 1779 * @return boolean 1780 */ 1781 public static function delete_template($id, $deleteplans = true) { 1782 global $DB; 1783 static::require_enabled(); 1784 $template = new template($id); 1785 1786 // First we do a permissions check. 1787 if (!$template->can_manage()) { 1788 throw new required_capability_exception($template->get_context(), 'moodle/competency:templatemanage', 1789 'nopermissions', ''); 1790 } 1791 1792 $transaction = $DB->start_delegated_transaction(); 1793 $success = true; 1794 1795 // Check if there are cohorts associated. 1796 $templatecohorts = template_cohort::get_relations_by_templateid($template->get_id()); 1797 foreach ($templatecohorts as $templatecohort) { 1798 $success = $templatecohort->delete(); 1799 if (!$success) { 1800 break; 1801 } 1802 } 1803 1804 // Still OK, delete or unlink the plans from the template. 1805 if ($success) { 1806 $plans = plan::get_records(array('templateid' => $template->get_id())); 1807 foreach ($plans as $plan) { 1808 $success = $deleteplans ? self::delete_plan($plan->get_id()) : self::unlink_plan_from_template($plan); 1809 if (!$success) { 1810 break; 1811 } 1812 } 1813 } 1814 1815 // Still OK, delete the template comptencies. 1816 if ($success) { 1817 $success = template_competency::delete_by_templateid($template->get_id()); 1818 } 1819 1820 // OK - all set. 1821 if ($success) { 1822 // Create a template deleted event. 1823 $event = \core\event\competency_template_deleted::create_from_template($template); 1824 1825 $success = $template->delete(); 1826 } 1827 1828 if ($success) { 1829 // Trigger a template deleted event. 1830 $event->trigger(); 1831 1832 // Commit the transaction. 1833 $transaction->allow_commit(); 1834 } else { 1835 $transaction->rollback(new moodle_exception('Error while deleting the template.')); 1836 } 1837 1838 return $success; 1839 } 1840 1841 /** 1842 * Update the details for a learning plan template. 1843 * 1844 * Requires moodle/competency:templatemanage capability. 1845 * 1846 * @param stdClass $record The new details for the template. Note - must contain an id that points to the template to update. 1847 * @return boolean 1848 */ 1849 public static function update_template($record) { 1850 global $DB; 1851 static::require_enabled(); 1852 $template = new template($record->id); 1853 1854 // First we do a permissions check. 1855 if (!$template->can_manage()) { 1856 throw new required_capability_exception($template->get_context(), 'moodle/competency:templatemanage', 1857 'nopermissions', ''); 1858 1859 } else if (isset($record->contextid) && $record->contextid != $template->get_contextid()) { 1860 // We can never change the context of a template. 1861 throw new coding_exception('Changing the context of an existing tempalte is forbidden.'); 1862 1863 } 1864 1865 $updateplans = false; 1866 $before = $template->to_record(); 1867 1868 $template->from_record($record); 1869 $after = $template->to_record(); 1870 1871 // Should we update the related plans? 1872 if ($before->duedate != $after->duedate || 1873 $before->shortname != $after->shortname || 1874 $before->description != $after->description || 1875 $before->descriptionformat != $after->descriptionformat) { 1876 $updateplans = true; 1877 } 1878 1879 $transaction = $DB->start_delegated_transaction(); 1880 $success = $template->update(); 1881 1882 if (!$success) { 1883 $transaction->rollback(new moodle_exception('Error while updating the template.')); 1884 return $success; 1885 } 1886 1887 // Trigger a template updated event. 1888 \core\event\competency_template_updated::create_from_template($template)->trigger(); 1889 1890 if ($updateplans) { 1891 plan::update_multiple_from_template($template); 1892 } 1893 1894 $transaction->allow_commit(); 1895 1896 return $success; 1897 } 1898 1899 /** 1900 * Read a the details for a single learning plan template and return a record. 1901 * 1902 * Requires moodle/competency:templateview capability at the system context. 1903 * 1904 * @param int $id The id of the template to read. 1905 * @return template 1906 */ 1907 public static function read_template($id) { 1908 static::require_enabled(); 1909 $template = new template($id); 1910 $context = $template->get_context(); 1911 1912 // First we do a permissions check. 1913 if (!$template->can_read()) { 1914 throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview', 1915 'nopermissions', ''); 1916 } 1917 1918 // OK - all set. 1919 return $template; 1920 } 1921 1922 /** 1923 * Perform a search based on the provided filters and return a paginated list of records. 1924 * 1925 * Requires moodle/competency:templateview capability at the system context. 1926 * 1927 * @param string $sort The column to sort on 1928 * @param string $order ('ASC' or 'DESC') 1929 * @param int $skip Number of records to skip (pagination) 1930 * @param int $limit Max of records to return (pagination) 1931 * @param context $context The parent context of the frameworks. 1932 * @param string $includes Defines what other contexts to fetch frameworks from. 1933 * Accepted values are: 1934 * - children: All descendants 1935 * - parents: All parents, grand parents, etc... 1936 * - self: Context passed only. 1937 * @param bool $onlyvisible If should list only visible templates 1938 * @return array of competency_framework 1939 */ 1940 public static function list_templates($sort, $order, $skip, $limit, $context, $includes = 'children', $onlyvisible = false) { 1941 global $DB; 1942 static::require_enabled(); 1943 1944 // Get all the relevant contexts. 1945 $contexts = self::get_related_contexts($context, $includes, 1946 array('moodle/competency:templateview', 'moodle/competency:templatemanage')); 1947 1948 // First we do a permissions check. 1949 if (empty($contexts)) { 1950 throw new required_capability_exception($context, 'moodle/competency:templateview', 'nopermissions', ''); 1951 } 1952 1953 // Make the order by. 1954 $orderby = ''; 1955 if (!empty($sort)) { 1956 $orderby = $sort . ' ' . $order; 1957 } 1958 1959 // OK - all set. 1960 $template = new template(); 1961 list($insql, $params) = $DB->get_in_or_equal(array_keys($contexts), SQL_PARAMS_NAMED); 1962 $select = "contextid $insql"; 1963 1964 if ($onlyvisible) { 1965 $select .= " AND visible = :visible"; 1966 $params['visible'] = 1; 1967 } 1968 return $template->get_records_select($select, $params, $orderby, '*', $skip, $limit); 1969 } 1970 1971 /** 1972 * Perform a search based on the provided filters and return how many results there are. 1973 * 1974 * Requires moodle/competency:templateview capability at the system context. 1975 * 1976 * @param context $context The parent context of the frameworks. 1977 * @param string $includes Defines what other contexts to fetch frameworks from. 1978 * Accepted values are: 1979 * - children: All descendants 1980 * - parents: All parents, grand parents, etc... 1981 * - self: Context passed only. 1982 * @return int 1983 */ 1984 public static function count_templates($context, $includes) { 1985 global $DB; 1986 static::require_enabled(); 1987 1988 // First we do a permissions check. 1989 $contexts = self::get_related_contexts($context, $includes, 1990 array('moodle/competency:templateview', 'moodle/competency:templatemanage')); 1991 1992 if (empty($contexts)) { 1993 throw new required_capability_exception($context, 'moodle/competency:templateview', 'nopermissions', ''); 1994 } 1995 1996 // OK - all set. 1997 $template = new template(); 1998 list($insql, $inparams) = $DB->get_in_or_equal(array_keys($contexts), SQL_PARAMS_NAMED); 1999 return $template->count_records_select("contextid $insql", $inparams); 2000 } 2001 2002 /** 2003 * Count all the templates using a competency. 2004 * 2005 * @param int $competencyid The id of the competency to check. 2006 * @return int 2007 */ 2008 public static function count_templates_using_competency($competencyid) { 2009 static::require_enabled(); 2010 // First we do a permissions check. 2011 $context = context_system::instance(); 2012 $onlyvisible = 1; 2013 2014 $capabilities = array('moodle/competency:templateview', 'moodle/competency:templatemanage'); 2015 if (!has_any_capability($capabilities, $context)) { 2016 throw new required_capability_exception($context, 'moodle/competency:templateview', 'nopermissions', ''); 2017 } 2018 2019 if (has_capability('moodle/competency:templatemanage', $context)) { 2020 $onlyvisible = 0; 2021 } 2022 2023 // OK - all set. 2024 return template_competency::count_templates($competencyid, $onlyvisible); 2025 } 2026 2027 /** 2028 * List all the learning plan templatesd using a competency. 2029 * 2030 * @param int $competencyid The id of the competency to check. 2031 * @return array[stdClass] Array of stdClass containing id and shortname. 2032 */ 2033 public static function list_templates_using_competency($competencyid) { 2034 static::require_enabled(); 2035 // First we do a permissions check. 2036 $context = context_system::instance(); 2037 $onlyvisible = 1; 2038 2039 $capabilities = array('moodle/competency:templateview', 'moodle/competency:templatemanage'); 2040 if (!has_any_capability($capabilities, $context)) { 2041 throw new required_capability_exception($context, 'moodle/competency:templateview', 'nopermissions', ''); 2042 } 2043 2044 if (has_capability('moodle/competency:templatemanage', $context)) { 2045 $onlyvisible = 0; 2046 } 2047 2048 // OK - all set. 2049 return template_competency::list_templates($competencyid, $onlyvisible); 2050 2051 } 2052 2053 /** 2054 * Count all the competencies in a learning plan template. 2055 * 2056 * @param template|int $templateorid The template or its ID. 2057 * @return int 2058 */ 2059 public static function count_competencies_in_template($templateorid) { 2060 static::require_enabled(); 2061 // First we do a permissions check. 2062 $template = $templateorid; 2063 if (!is_object($template)) { 2064 $template = new template($template); 2065 } 2066 2067 if (!$template->can_read()) { 2068 throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview', 2069 'nopermissions', ''); 2070 } 2071 2072 // OK - all set. 2073 return template_competency::count_competencies($template->get_id()); 2074 } 2075 2076 /** 2077 * Count all the competencies in a learning plan template with no linked courses. 2078 * 2079 * @param template|int $templateorid The template or its ID. 2080 * @return int 2081 */ 2082 public static function count_competencies_in_template_with_no_courses($templateorid) { 2083 // First we do a permissions check. 2084 $template = $templateorid; 2085 if (!is_object($template)) { 2086 $template = new template($template); 2087 } 2088 2089 if (!$template->can_read()) { 2090 throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview', 2091 'nopermissions', ''); 2092 } 2093 2094 // OK - all set. 2095 return template_competency::count_competencies_with_no_courses($template->get_id()); 2096 } 2097 2098 /** 2099 * List all the competencies in a template. 2100 * 2101 * @param template|int $templateorid The template or its ID. 2102 * @return array of competencies 2103 */ 2104 public static function list_competencies_in_template($templateorid) { 2105 static::require_enabled(); 2106 // First we do a permissions check. 2107 $template = $templateorid; 2108 if (!is_object($template)) { 2109 $template = new template($template); 2110 } 2111 2112 if (!$template->can_read()) { 2113 throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview', 2114 'nopermissions', ''); 2115 } 2116 2117 // OK - all set. 2118 return template_competency::list_competencies($template->get_id()); 2119 } 2120 2121 /** 2122 * Add a competency to this template. 2123 * 2124 * @param int $templateid The id of the template 2125 * @param int $competencyid The id of the competency 2126 * @return bool 2127 */ 2128 public static function add_competency_to_template($templateid, $competencyid) { 2129 static::require_enabled(); 2130 // First we do a permissions check. 2131 $template = new template($templateid); 2132 if (!$template->can_manage()) { 2133 throw new required_capability_exception($template->get_context(), 'moodle/competency:templatemanage', 2134 'nopermissions', ''); 2135 } 2136 2137 $record = new stdClass(); 2138 $record->templateid = $templateid; 2139 $record->competencyid = $competencyid; 2140 2141 $competency = new competency($competencyid); 2142 2143 // Can not add a competency that belong to a hidden framework. 2144 if ($competency->get_framework()->get_visible() == false) { 2145 throw new coding_exception('A competency belonging to hidden framework can not be added'); 2146 } 2147 2148 $exists = template_competency::get_records(array('templateid' => $templateid, 'competencyid' => $competencyid)); 2149 if (!$exists) { 2150 $templatecompetency = new template_competency(0, $record); 2151 $templatecompetency->create(); 2152 return true; 2153 } 2154 return false; 2155 } 2156 2157 /** 2158 * Remove a competency from this template. 2159 * 2160 * @param int $templateid The id of the template 2161 * @param int $competencyid The id of the competency 2162 * @return bool 2163 */ 2164 public static function remove_competency_from_template($templateid, $competencyid) { 2165 static::require_enabled(); 2166 // First we do a permissions check. 2167 $template = new template($templateid); 2168 if (!$template->can_manage()) { 2169 throw new required_capability_exception($template->get_context(), 'moodle/competency:templatemanage', 2170 'nopermissions', ''); 2171 } 2172 2173 $record = new stdClass(); 2174 $record->templateid = $templateid; 2175 $record->competencyid = $competencyid; 2176 2177 $competency = new competency($competencyid); 2178 2179 $exists = template_competency::get_records(array('templateid' => $templateid, 'competencyid' => $competencyid)); 2180 if ($exists) { 2181 $link = array_pop($exists); 2182 return $link->delete(); 2183 } 2184 return false; 2185 } 2186 2187 /** 2188 * Move the template competency up or down in the display list. 2189 * 2190 * Requires moodle/competency:templatemanage capability at the system context. 2191 * 2192 * @param int $templateid The template id 2193 * @param int $competencyidfrom The id of the competency we are moving. 2194 * @param int $competencyidto The id of the competency we are moving to. 2195 * @return boolean 2196 */ 2197 public static function reorder_template_competency($templateid, $competencyidfrom, $competencyidto) { 2198 static::require_enabled(); 2199 // First we do a permissions check. 2200 $context = context_system::instance(); 2201 2202 require_capability('moodle/competency:templatemanage', $context); 2203 2204 $down = true; 2205 $matches = template_competency::get_records(array('templateid' => $templateid, 'competencyid' => $competencyidfrom)); 2206 if (count($matches) == 0) { 2207 throw new coding_exception('The link does not exist'); 2208 } 2209 2210 $competencyfrom = array_pop($matches); 2211 $matches = template_competency::get_records(array('templateid' => $templateid, 'competencyid' => $competencyidto)); 2212 if (count($matches) == 0) { 2213 throw new coding_exception('The link does not exist'); 2214 } 2215 2216 $competencyto = array_pop($matches); 2217 2218 $all = template_competency::get_records(array('templateid' => $templateid), 'sortorder', 'ASC', 0, 0); 2219 2220 if ($competencyfrom->get_sortorder() > $competencyto->get_sortorder()) { 2221 // We are moving up, so put it before the "to" item. 2222 $down = false; 2223 } 2224 2225 foreach ($all as $id => $templatecompetency) { 2226 $sort = $templatecompetency->get_sortorder(); 2227 if ($down && $sort > $competencyfrom->get_sortorder() && $sort <= $competencyto->get_sortorder()) { 2228 $templatecompetency->set_sortorder($templatecompetency->get_sortorder() - 1); 2229 $templatecompetency->update(); 2230 } else if (!$down && $sort >= $competencyto->get_sortorder() && $sort < $competencyfrom->get_sortorder()) { 2231 $templatecompetency->set_sortorder($templatecompetency->get_sortorder() + 1); 2232 $templatecompetency->update(); 2233 } 2234 } 2235 $competencyfrom->set_sortorder($competencyto->get_sortorder()); 2236 return $competencyfrom->update(); 2237 } 2238 2239 /** 2240 * Create a relation between a template and a cohort. 2241 * 2242 * This silently ignores when the relation already existed. 2243 * 2244 * @param template|int $templateorid The template or its ID. 2245 * @param stdClass|int $cohortorid The cohort ot its ID. 2246 * @return template_cohort 2247 */ 2248 public static function create_template_cohort($templateorid, $cohortorid) { 2249 global $DB; 2250 static::require_enabled(); 2251 2252 $template = $templateorid; 2253 if (!is_object($template)) { 2254 $template = new template($template); 2255 } 2256 require_capability('moodle/competency:templatemanage', $template->get_context()); 2257 2258 $cohort = $cohortorid; 2259 if (!is_object($cohort)) { 2260 $cohort = $DB->get_record('cohort', array('id' => $cohort), '*', MUST_EXIST); 2261 } 2262 2263 // Replicate logic in cohort_can_view_cohort() because we can't use it directly as we don't have a course context. 2264 $cohortcontext = context::instance_by_id($cohort->contextid); 2265 if (!$cohort->visible && !has_capability('moodle/cohort:view', $cohortcontext)) { 2266 throw new required_capability_exception($cohortcontext, 'moodle/cohort:view', 'nopermissions', ''); 2267 } 2268 2269 $tplcohort = template_cohort::get_relation($template->get_id(), $cohort->id); 2270 if (!$tplcohort->get_id()) { 2271 $tplcohort->create(); 2272 } 2273 2274 return $tplcohort; 2275 } 2276 2277 /** 2278 * Remove a relation between a template and a cohort. 2279 * 2280 * @param template|int $templateorid The template or its ID. 2281 * @param stdClass|int $cohortorid The cohort ot its ID. 2282 * @return boolean True on success or when the relation did not exist. 2283 */ 2284 public static function delete_template_cohort($templateorid, $cohortorid) { 2285 global $DB; 2286 static::require_enabled(); 2287 2288 $template = $templateorid; 2289 if (!is_object($template)) { 2290 $template = new template($template); 2291 } 2292 require_capability('moodle/competency:templatemanage', $template->get_context()); 2293 2294 $cohort = $cohortorid; 2295 if (!is_object($cohort)) { 2296 $cohort = $DB->get_record('cohort', array('id' => $cohort), '*', MUST_EXIST); 2297 } 2298 2299 $tplcohort = template_cohort::get_relation($template->get_id(), $cohort->id); 2300 if (!$tplcohort->get_id()) { 2301 return true; 2302 } 2303 2304 return $tplcohort->delete(); 2305 } 2306 2307 /** 2308 * Lists user plans. 2309 * 2310 * @param int $userid 2311 * @return \core_competency\plan[] 2312 */ 2313 public static function list_user_plans($userid) { 2314 global $DB, $USER; 2315 static::require_enabled(); 2316 $select = 'userid = :userid'; 2317 $params = array('userid' => $userid); 2318 $context = context_user::instance($userid); 2319 2320 // Check that we can read something here. 2321 if (!plan::can_read_user($userid) && !plan::can_read_user_draft($userid)) { 2322 throw new required_capability_exception($context, 'moodle/competency:planview', 'nopermissions', ''); 2323 } 2324 2325 // The user cannot view the drafts. 2326 if (!plan::can_read_user_draft($userid)) { 2327 list($insql, $inparams) = $DB->get_in_or_equal(plan::get_draft_statuses(), SQL_PARAMS_NAMED, 'param', false); 2328 $select .= " AND status $insql"; 2329 $params += $inparams; 2330 } 2331 // The user cannot view the non-drafts. 2332 if (!plan::can_read_user($userid)) { 2333 list($insql, $inparams) = $DB->get_in_or_equal(array(plan::STATUS_ACTIVE, plan::STATUS_COMPLETE), 2334 SQL_PARAMS_NAMED, 'param', false); 2335 $select .= " AND status $insql"; 2336 $params += $inparams; 2337 } 2338 2339 return plan::get_records_select($select, $params, 'name ASC'); 2340 } 2341 2342 /** 2343 * List the plans to review. 2344 * 2345 * The method returns values in this format: 2346 * 2347 * array( 2348 * 'plans' => array( 2349 * (stdClass)( 2350 * 'plan' => (plan), 2351 * 'template' => (template), 2352 * 'owner' => (stdClass) 2353 * ) 2354 * ), 2355 * 'count' => (int) 2356 * ) 2357 * 2358 * @param int $skip The number of records to skip. 2359 * @param int $limit The number of results to return. 2360 * @param int $userid The user we're getting the plans to review for. 2361 * @return array Containing the keys 'count', and 'plans'. The 'plans' key contains an object 2362 * which contains 'plan', 'template' and 'owner'. 2363 */ 2364 public static function list_plans_to_review($skip = 0, $limit = 100, $userid = null) { 2365 global $DB, $USER; 2366 static::require_enabled(); 2367 2368 if ($userid === null) { 2369 $userid = $USER->id; 2370 } 2371 2372 $planfields = plan::get_sql_fields('p', 'plan_'); 2373 $tplfields = template::get_sql_fields('t', 'tpl_'); 2374 $usercols = array('id') + get_user_fieldnames(); 2375 $userfields = array(); 2376 foreach ($usercols as $field) { 2377 $userfields[] = "u." . $field . " AS usr_" . $field; 2378 } 2379 $userfields = implode(',', $userfields); 2380 2381 $select = "SELECT $planfields, $tplfields, $userfields"; 2382 $countselect = "SELECT COUNT('x')"; 2383 2384 $sql = " FROM {" . plan::TABLE . "} p 2385 JOIN {user} u 2386 ON u.id = p.userid 2387 LEFT JOIN {" . template::TABLE . "} t 2388 ON t.id = p.templateid 2389 WHERE (p.status = :waitingforreview 2390 OR (p.status = :inreview AND p.reviewerid = :reviewerid)) 2391 AND p.userid != :userid"; 2392 2393 $params = array( 2394 'waitingforreview' => plan::STATUS_WAITING_FOR_REVIEW, 2395 'inreview' => plan::STATUS_IN_REVIEW, 2396 'reviewerid' => $userid, 2397 'userid' => $userid 2398 ); 2399 2400 // Primary check to avoid the hard work of getting the users in which the user has permission. 2401 $count = $DB->count_records_sql($countselect . $sql, $params); 2402 if ($count < 1) { 2403 return array('count' => 0, 'plans' => array()); 2404 } 2405 2406 // TODO MDL-52243 Use core function. 2407 list($insql, $inparams) = self::filter_users_with_capability_on_user_context_sql('moodle/competency:planreview', 2408 $userid, SQL_PARAMS_NAMED); 2409 $sql .= " AND p.userid $insql"; 2410 $params += $inparams; 2411 2412 // Order by ID just to have some ordering in place. 2413 $ordersql = " ORDER BY p.id ASC"; 2414 2415 $plans = array(); 2416 $records = $DB->get_recordset_sql($select . $sql . $ordersql, $params, $skip, $limit); 2417 foreach ($records as $record) { 2418 $plan = new plan(0, plan::extract_record($record, 'plan_')); 2419 $template = null; 2420 2421 if ($plan->is_based_on_template()) { 2422 $template = new template(0, template::extract_record($record, 'tpl_')); 2423 } 2424 2425 $plans[] = (object) array( 2426 'plan' => $plan, 2427 'template' => $template, 2428 'owner' => persistent::extract_record($record, 'usr_'), 2429 ); 2430 } 2431 $records->close(); 2432 2433 return array( 2434 'count' => $DB->count_records_sql($countselect . $sql, $params), 2435 'plans' => $plans 2436 ); 2437 } 2438 2439 /** 2440 * Creates a learning plan based on the provided data. 2441 * 2442 * @param stdClass $record 2443 * @return \core_competency\plan 2444 */ 2445 public static function create_plan(stdClass $record) { 2446 global $USER; 2447 static::require_enabled(); 2448 $plan = new plan(0, $record); 2449 2450 if ($plan->is_based_on_template()) { 2451 throw new coding_exception('To create a plan from a template use api::create_plan_from_template().'); 2452 } else if ($plan->get_status() == plan::STATUS_COMPLETE) { 2453 throw new coding_exception('A plan cannot be created as complete.'); 2454 } 2455 2456 if (!$plan->can_manage()) { 2457 $context = context_user::instance($plan->get_userid()); 2458 throw new required_capability_exception($context, 'moodle/competency:planmanage', 'nopermissions', ''); 2459 } 2460 2461 $plan->create(); 2462 2463 // Trigger created event. 2464 \core\event\competency_plan_created::create_from_plan($plan)->trigger(); 2465 return $plan; 2466 } 2467 2468 /** 2469 * Create a learning plan from a template. 2470 * 2471 * @param mixed $templateorid The template object or ID. 2472 * @param int $userid 2473 * @return false|\core_competency\plan Returns false when the plan already exists. 2474 */ 2475 public static function create_plan_from_template($templateorid, $userid) { 2476 static::require_enabled(); 2477 $template = $templateorid; 2478 if (!is_object($template)) { 2479 $template = new template($template); 2480 } 2481 2482 // The user must be able to view the template to use it as a base for a plan. 2483 if (!$template->can_read()) { 2484 throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview', 2485 'nopermissions', ''); 2486 } 2487 // Can not create plan from a hidden template. 2488 if ($template->get_visible() == false) { 2489 throw new coding_exception('A plan can not be created from a hidden template'); 2490 } 2491 2492 // Convert the template to a plan. 2493 $record = $template->to_record(); 2494 $record->templateid = $record->id; 2495 $record->userid = $userid; 2496 $record->name = $record->shortname; 2497 $record->status = plan::STATUS_ACTIVE; 2498 2499 unset($record->id); 2500 unset($record->timecreated); 2501 unset($record->timemodified); 2502 unset($record->usermodified); 2503 2504 // Remove extra keys. 2505 $properties = plan::properties_definition(); 2506 foreach ($record as $key => $value) { 2507 if (!array_key_exists($key, $properties)) { 2508 unset($record->$key); 2509 } 2510 } 2511 2512 $plan = new plan(0, $record); 2513 if (!$plan->can_manage()) { 2514 throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 2515 'nopermissions', ''); 2516 } 2517 2518 // We first apply the permission checks as we wouldn't want to leak information by returning early that 2519 // the plan already exists. 2520 if (plan::record_exists_select('templateid = :templateid AND userid = :userid', array( 2521 'templateid' => $template->get_id(), 'userid' => $userid))) { 2522 return false; 2523 } 2524 2525 $plan->create(); 2526 2527 // Trigger created event. 2528 \core\event\competency_plan_created::create_from_plan($plan)->trigger(); 2529 return $plan; 2530 } 2531 2532 /** 2533 * Create learning plans from a template and cohort. 2534 * 2535 * @param mixed $templateorid The template object or ID. 2536 * @param int $cohortid The cohort ID. 2537 * @param bool $recreateunlinked When true the plans that were unlinked from this template will be re-created. 2538 * @return int The number of plans created. 2539 */ 2540 public static function create_plans_from_template_cohort($templateorid, $cohortid, $recreateunlinked = false) { 2541 global $DB, $CFG; 2542 static::require_enabled(); 2543 require_once($CFG->dirroot . '/cohort/lib.php'); 2544 2545 $template = $templateorid; 2546 if (!is_object($template)) { 2547 $template = new template($template); 2548 } 2549 2550 // The user must be able to view the template to use it as a base for a plan. 2551 if (!$template->can_read()) { 2552 throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview', 2553 'nopermissions', ''); 2554 } 2555 2556 // Can not create plan from a hidden template. 2557 if ($template->get_visible() == false) { 2558 throw new coding_exception('A plan can not be created from a hidden template'); 2559 } 2560 2561 // Replicate logic in cohort_can_view_cohort() because we can't use it directly as we don't have a course context. 2562 $cohort = $DB->get_record('cohort', array('id' => $cohortid), '*', MUST_EXIST); 2563 $cohortcontext = context::instance_by_id($cohort->contextid); 2564 if (!$cohort->visible && !has_capability('moodle/cohort:view', $cohortcontext)) { 2565 throw new required_capability_exception($cohortcontext, 'moodle/cohort:view', 'nopermissions', ''); 2566 } 2567 2568 // Convert the template to a plan. 2569 $recordbase = $template->to_record(); 2570 $recordbase->templateid = $recordbase->id; 2571 $recordbase->name = $recordbase->shortname; 2572 $recordbase->status = plan::STATUS_ACTIVE; 2573 2574 unset($recordbase->id); 2575 unset($recordbase->timecreated); 2576 unset($recordbase->timemodified); 2577 unset($recordbase->usermodified); 2578 2579 // Remove extra keys. 2580 $properties = plan::properties_definition(); 2581 foreach ($recordbase as $key => $value) { 2582 if (!array_key_exists($key, $properties)) { 2583 unset($recordbase->$key); 2584 } 2585 } 2586 2587 // Create the plans. 2588 $created = 0; 2589 $userids = template_cohort::get_missing_plans($template->get_id(), $cohortid, $recreateunlinked); 2590 foreach ($userids as $userid) { 2591 $record = (object) (array) $recordbase; 2592 $record->userid = $userid; 2593 2594 $plan = new plan(0, $record); 2595 if (!$plan->can_manage()) { 2596 // Silently skip members where permissions are lacking. 2597 continue; 2598 } 2599 2600 $plan->create(); 2601 // Trigger created event. 2602 \core\event\competency_plan_created::create_from_plan($plan)->trigger(); 2603 $created++; 2604 } 2605 2606 return $created; 2607 } 2608 2609 /** 2610 * Unlink a plan from its template. 2611 * 2612 * @param \core_competency\plan|int $planorid The plan or its ID. 2613 * @return bool 2614 */ 2615 public static function unlink_plan_from_template($planorid) { 2616 global $DB; 2617 static::require_enabled(); 2618 2619 $plan = $planorid; 2620 if (!is_object($planorid)) { 2621 $plan = new plan($planorid); 2622 } 2623 2624 // The user must be allowed to manage the plans of the user, nothing about the template. 2625 if (!$plan->can_manage()) { 2626 throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', ''); 2627 } 2628 2629 // Only plan with status DRAFT or ACTIVE can be unliked.. 2630 if ($plan->get_status() == plan::STATUS_COMPLETE) { 2631 throw new coding_exception('Only draft or active plan can be unliked from a template'); 2632 } 2633 2634 // Early exit, it's already done... 2635 if (!$plan->is_based_on_template()) { 2636 return true; 2637 } 2638 2639 // Fetch the template. 2640 $template = new template($plan->get_templateid()); 2641 2642 // Now, proceed by copying all competencies to the plan, then update the plan. 2643 $transaction = $DB->start_delegated_transaction(); 2644 $competencies = template_competency::list_competencies($template->get_id(), false); 2645 $i = 0; 2646 foreach ($competencies as $competency) { 2647 $record = (object) array( 2648 'planid' => $plan->get_id(), 2649 'competencyid' => $competency->get_id(), 2650 'sortorder' => $i++ 2651 ); 2652 $pc = new plan_competency(null, $record); 2653 $pc->create(); 2654 } 2655 $plan->set_origtemplateid($template->get_id()); 2656 $plan->set_templateid(null); 2657 $success = $plan->update(); 2658 $transaction->allow_commit(); 2659 2660 // Trigger unlinked event. 2661 \core\event\competency_plan_unlinked::create_from_plan($plan)->trigger(); 2662 2663 return $success; 2664 } 2665 2666 /** 2667 * Updates a plan. 2668 * 2669 * @param stdClass $record 2670 * @return \core_competency\plan 2671 */ 2672 public static function update_plan(stdClass $record) { 2673 static::require_enabled(); 2674 2675 $plan = new plan($record->id); 2676 2677 // Validate that the plan as it is can be managed. 2678 if (!$plan->can_manage()) { 2679 throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', ''); 2680 2681 } else if ($plan->get_status() == plan::STATUS_COMPLETE) { 2682 // A completed plan cannot be edited. 2683 throw new coding_exception('Completed plan cannot be edited.'); 2684 2685 } else if ($plan->is_based_on_template()) { 2686 // Prevent a plan based on a template to be edited. 2687 throw new coding_exception('Cannot update a plan that is based on a template.'); 2688 2689 } else if (isset($record->templateid) && $plan->get_templateid() != $record->templateid) { 2690 // Prevent a plan to be based on a template. 2691 throw new coding_exception('Cannot base a plan on a template.'); 2692 2693 } else if (isset($record->userid) && $plan->get_userid() != $record->userid) { 2694 // Prevent change of ownership as the capabilities are checked against that. 2695 throw new coding_exception('A plan cannot be transfered to another user'); 2696 2697 } else if (isset($record->status) && $plan->get_status() != $record->status) { 2698 // Prevent change of status. 2699 throw new coding_exception('To change the status of a plan use the appropriate methods.'); 2700 2701 } 2702 2703 $plan->from_record($record); 2704 $plan->update(); 2705 2706 // Trigger updated event. 2707 \core\event\competency_plan_updated::create_from_plan($plan)->trigger(); 2708 2709 return $plan; 2710 } 2711 2712 /** 2713 * Returns a plan data. 2714 * 2715 * @param int $id 2716 * @return \core_competency\plan 2717 */ 2718 public static function read_plan($id) { 2719 static::require_enabled(); 2720 $plan = new plan($id); 2721 2722 if (!$plan->can_read()) { 2723 $context = context_user::instance($plan->get_userid()); 2724 throw new required_capability_exception($context, 'moodle/competency:planview', 'nopermissions', ''); 2725 } 2726 2727 return $plan; 2728 } 2729 2730 /** 2731 * Plan event viewed. 2732 * 2733 * @param mixed $planorid The id or the plan. 2734 * @return boolean 2735 */ 2736 public static function plan_viewed($planorid) { 2737 static::require_enabled(); 2738 $plan = $planorid; 2739 if (!is_object($plan)) { 2740 $plan = new plan($plan); 2741 } 2742 2743 // First we do a permissions check. 2744 if (!$plan->can_read()) { 2745 $context = context_user::instance($plan->get_userid()); 2746 throw new required_capability_exception($context, 'moodle/competency:planview', 'nopermissions', ''); 2747 } 2748 2749 // Trigger a template viewed event. 2750 \core\event\competency_plan_viewed::create_from_plan($plan)->trigger(); 2751 2752 return true; 2753 } 2754 2755 /** 2756 * Deletes a plan. 2757 * 2758 * Plans based on a template can be removed just like any other one. 2759 * 2760 * @param int $id 2761 * @return bool Success? 2762 */ 2763 public static function delete_plan($id) { 2764 global $DB; 2765 static::require_enabled(); 2766 2767 $plan = new plan($id); 2768 2769 if (!$plan->can_manage()) { 2770 $context = context_user::instance($plan->get_userid()); 2771 throw new required_capability_exception($context, 'moodle/competency:planmanage', 'nopermissions', ''); 2772 } 2773 2774 // Wrap the suppression in a DB transaction. 2775 $transaction = $DB->start_delegated_transaction(); 2776 2777 // Delete plan competencies. 2778 $plancomps = plan_competency::get_records(array('planid' => $plan->get_id())); 2779 foreach ($plancomps as $plancomp) { 2780 $plancomp->delete(); 2781 } 2782 2783 // Delete archive user competencies if the status of the plan is complete. 2784 if ($plan->get_status() == plan::STATUS_COMPLETE) { 2785 self::remove_archived_user_competencies_in_plan($plan); 2786 } 2787 $event = \core\event\competency_plan_deleted::create_from_plan($plan); 2788 $success = $plan->delete(); 2789 2790 $transaction->allow_commit(); 2791 2792 // Trigger deleted event. 2793 $event->trigger(); 2794 2795 return $success; 2796 } 2797 2798 /** 2799 * Cancel the review of a plan. 2800 * 2801 * @param int|plan $planorid The plan, or its ID. 2802 * @return bool 2803 */ 2804 public static function plan_cancel_review_request($planorid) { 2805 static::require_enabled(); 2806 $plan = $planorid; 2807 if (!is_object($plan)) { 2808 $plan = new plan($plan); 2809 } 2810 2811 // We need to be able to view the plan at least. 2812 if (!$plan->can_read()) { 2813 throw new required_capability_exception($plan->get_context(), 'moodle/competency:planview', 'nopermissions', ''); 2814 } 2815 2816 if ($plan->is_based_on_template()) { 2817 throw new coding_exception('Template plans cannot be reviewed.'); // This should never happen. 2818 } else if ($plan->get_status() != plan::STATUS_WAITING_FOR_REVIEW) { 2819 throw new coding_exception('The plan review cannot be cancelled at this stage.'); 2820 } else if (!$plan->can_request_review()) { 2821 throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', ''); 2822 } 2823 2824 $plan->set_status(plan::STATUS_DRAFT); 2825 $result = $plan->update(); 2826 2827 // Trigger review request cancelled event. 2828 \core\event\competency_plan_review_request_cancelled::create_from_plan($plan)->trigger(); 2829 2830 return $result; 2831 } 2832 2833 /** 2834 * Request the review of a plan. 2835 * 2836 * @param int|plan $planorid The plan, or its ID. 2837 * @return bool 2838 */ 2839 public static function plan_request_review($planorid) { 2840 static::require_enabled(); 2841 $plan = $planorid; 2842 if (!is_object($plan)) { 2843 $plan = new plan($plan); 2844 } 2845 2846 // We need to be able to view the plan at least. 2847 if (!$plan->can_read()) { 2848 throw new required_capability_exception($plan->get_context(), 'moodle/competency:planview', 'nopermissions', ''); 2849 } 2850 2851 if ($plan->is_based_on_template()) { 2852 throw new coding_exception('Template plans cannot be reviewed.'); // This should never happen. 2853 } else if ($plan->get_status() != plan::STATUS_DRAFT) { 2854 throw new coding_exception('The plan cannot be sent for review at this stage.'); 2855 } else if (!$plan->can_request_review()) { 2856 throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', ''); 2857 } 2858 2859 $plan->set_status(plan::STATUS_WAITING_FOR_REVIEW); 2860 $result = $plan->update(); 2861 2862 // Trigger review requested event. 2863 \core\event\competency_plan_review_requested::create_from_plan($plan)->trigger(); 2864 2865 return $result; 2866 } 2867 2868 /** 2869 * Start the review of a plan. 2870 * 2871 * @param int|plan $planorid The plan, or its ID. 2872 * @return bool 2873 */ 2874 public static function plan_start_review($planorid) { 2875 global $USER; 2876 static::require_enabled(); 2877 $plan = $planorid; 2878 if (!is_object($plan)) { 2879 $plan = new plan($plan); 2880 } 2881 2882 // We need to be able to view the plan at least. 2883 if (!$plan->can_read()) { 2884 throw new required_capability_exception($plan->get_context(), 'moodle/competency:planview', 'nopermissions', ''); 2885 } 2886 2887 if ($plan->is_based_on_template()) { 2888 throw new coding_exception('Template plans cannot be reviewed.'); // This should never happen. 2889 } else if ($plan->get_status() != plan::STATUS_WAITING_FOR_REVIEW) { 2890 throw new coding_exception('The plan review cannot be started at this stage.'); 2891 } else if (!$plan->can_review()) { 2892 throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', ''); 2893 } 2894 2895 $plan->set_status(plan::STATUS_IN_REVIEW); 2896 $plan->set_reviewerid($USER->id); 2897 $result = $plan->update(); 2898 2899 // Trigger review started event. 2900 \core\event\competency_plan_review_started::create_from_plan($plan)->trigger(); 2901 2902 return $result; 2903 } 2904 2905 /** 2906 * Stop reviewing a plan. 2907 * 2908 * @param int|plan $planorid The plan, or its ID. 2909 * @return bool 2910 */ 2911 public static function plan_stop_review($planorid) { 2912 static::require_enabled(); 2913 $plan = $planorid; 2914 if (!is_object($plan)) { 2915 $plan = new plan($plan); 2916 } 2917 2918 // We need to be able to view the plan at least. 2919 if (!$plan->can_read()) { 2920 throw new required_capability_exception($plan->get_context(), 'moodle/competency:planview', 'nopermissions', ''); 2921 } 2922 2923 if ($plan->is_based_on_template()) { 2924 throw new coding_exception('Template plans cannot be reviewed.'); // This should never happen. 2925 } else if ($plan->get_status() != plan::STATUS_IN_REVIEW) { 2926 throw new coding_exception('The plan review cannot be stopped at this stage.'); 2927 } else if (!$plan->can_review()) { 2928 throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', ''); 2929 } 2930 2931 $plan->set_status(plan::STATUS_DRAFT); 2932 $plan->set_reviewerid(null); 2933 $result = $plan->update(); 2934 2935 // Trigger review stopped event. 2936 \core\event\competency_plan_review_stopped::create_from_plan($plan)->trigger(); 2937 2938 return $result; 2939 } 2940 2941 /** 2942 * Approve a plan. 2943 * 2944 * This means making the plan active. 2945 * 2946 * @param int|plan $planorid The plan, or its ID. 2947 * @return bool 2948 */ 2949 public static function approve_plan($planorid) { 2950 static::require_enabled(); 2951 $plan = $planorid; 2952 if (!is_object($plan)) { 2953 $plan = new plan($plan); 2954 } 2955 2956 // We need to be able to view the plan at least. 2957 if (!$plan->can_read()) { 2958 throw new required_capability_exception($plan->get_context(), 'moodle/competency:planview', 'nopermissions', ''); 2959 } 2960 2961 // We can approve a plan that is either a draft, in review, or waiting for review. 2962 if ($plan->is_based_on_template()) { 2963 throw new coding_exception('Template plans are already approved.'); // This should never happen. 2964 } else if (!$plan->is_draft()) { 2965 throw new coding_exception('The plan cannot be approved at this stage.'); 2966 } else if (!$plan->can_review()) { 2967 throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', ''); 2968 } 2969 2970 $plan->set_status(plan::STATUS_ACTIVE); 2971 $plan->set_reviewerid(null); 2972 $result = $plan->update(); 2973 2974 // Trigger approved event. 2975 \core\event\competency_plan_approved::create_from_plan($plan)->trigger(); 2976 2977 return $result; 2978 } 2979 2980 /** 2981 * Unapprove a plan. 2982 * 2983 * This means making the plan draft. 2984 * 2985 * @param int|plan $planorid The plan, or its ID. 2986 * @return bool 2987 */ 2988 public static function unapprove_plan($planorid) { 2989 static::require_enabled(); 2990 $plan = $planorid; 2991 if (!is_object($plan)) { 2992 $plan = new plan($plan); 2993 } 2994 2995 // We need to be able to view the plan at least. 2996 if (!$plan->can_read()) { 2997 throw new required_capability_exception($plan->get_context(), 'moodle/competency:planview', 'nopermissions', ''); 2998 } 2999 3000 if ($plan->is_based_on_template()) { 3001 throw new coding_exception('Template plans are always approved.'); // This should never happen. 3002 } else if ($plan->get_status() != plan::STATUS_ACTIVE) { 3003 throw new coding_exception('The plan cannot be sent back to draft at this stage.'); 3004 } else if (!$plan->can_review()) { 3005 throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', ''); 3006 } 3007 3008 $plan->set_status(plan::STATUS_DRAFT); 3009 $result = $plan->update(); 3010 3011 // Trigger unapproved event. 3012 \core\event\competency_plan_unapproved::create_from_plan($plan)->trigger(); 3013 3014 return $result; 3015 } 3016 3017 /** 3018 * Complete a plan. 3019 * 3020 * @param int|plan $planorid The plan, or its ID. 3021 * @return bool 3022 */ 3023 public static function complete_plan($planorid) { 3024 global $DB; 3025 static::require_enabled(); 3026 3027 $plan = $planorid; 3028 if (!is_object($planorid)) { 3029 $plan = new plan($planorid); 3030 } 3031 3032 // Validate that the plan can be managed. 3033 if (!$plan->can_manage()) { 3034 throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', ''); 3035 } 3036 3037 // Check if the plan was already completed. 3038 if ($plan->get_status() == plan::STATUS_COMPLETE) { 3039 throw new coding_exception('The plan is already completed.'); 3040 } 3041 3042 $originalstatus = $plan->get_status(); 3043 $plan->set_status(plan::STATUS_COMPLETE); 3044 3045 // The user should also be able to manage the plan when it's completed. 3046 if (!$plan->can_manage()) { 3047 throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', ''); 3048 } 3049 3050 // Put back original status because archive needs it to extract competencies from the right table. 3051 $plan->set_status($originalstatus); 3052 3053 // Do the things. 3054 $transaction = $DB->start_delegated_transaction(); 3055 self::archive_user_competencies_in_plan($plan); 3056 $plan->set_status(plan::STATUS_COMPLETE); 3057 $success = $plan->update(); 3058 3059 if (!$success) { 3060 $transaction->rollback(new moodle_exception('The plan could not be updated.')); 3061 return $success; 3062 } 3063 3064 $transaction->allow_commit(); 3065 3066 // Trigger updated event. 3067 \core\event\competency_plan_completed::create_from_plan($plan)->trigger(); 3068 3069 return $success; 3070 } 3071 3072 /** 3073 * Reopen a plan. 3074 * 3075 * @param int|plan $planorid The plan, or its ID. 3076 * @return bool 3077 */ 3078 public static function reopen_plan($planorid) { 3079 global $DB; 3080 static::require_enabled(); 3081 3082 $plan = $planorid; 3083 if (!is_object($planorid)) { 3084 $plan = new plan($planorid); 3085 } 3086 3087 // Validate that the plan as it is can be managed. 3088 if (!$plan->can_manage()) { 3089 $context = context_user::instance($plan->get_userid()); 3090 throw new required_capability_exception($context, 'moodle/competency:planmanage', 'nopermissions', ''); 3091 } 3092 3093 $beforestatus = $plan->get_status(); 3094 $plan->set_status(plan::STATUS_ACTIVE); 3095 3096 // Validate if status can be changed. 3097 if (!$plan->can_manage()) { 3098 $context = context_user::instance($plan->get_userid()); 3099 throw new required_capability_exception($context, 'moodle/competency:planmanage', 'nopermissions', ''); 3100 } 3101 3102 // Wrap the updates in a DB transaction. 3103 $transaction = $DB->start_delegated_transaction(); 3104 3105 // Delete archived user competencies if the status of the plan is changed from complete to another status. 3106 $mustremovearchivedcompetencies = ($beforestatus == plan::STATUS_COMPLETE && $plan->get_status() != plan::STATUS_COMPLETE); 3107 if ($mustremovearchivedcompetencies) { 3108 self::remove_archived_user_competencies_in_plan($plan); 3109 } 3110 3111 // If duedate less than or equal to duedate_threshold unset it. 3112 if ($plan->get_duedate() <= time() + plan::DUEDATE_THRESHOLD) { 3113 $plan->set_duedate(0); 3114 } 3115 3116 $success = $plan->update(); 3117 3118 if (!$success) { 3119 $transaction->rollback(new moodle_exception('The plan could not be updated.')); 3120 return $success; 3121 } 3122 3123 $transaction->allow_commit(); 3124 3125 // Trigger reopened event. 3126 \core\event\competency_plan_reopened::create_from_plan($plan)->trigger(); 3127 3128 return $success; 3129 } 3130 3131 /** 3132 * Get a single competency from the user plan. 3133 * 3134 * @param int $planorid The plan, or its ID. 3135 * @param int $competencyid The competency id. 3136 * @return (object) array( 3137 * 'competency' => \core_competency\competency, 3138 * 'usercompetency' => \core_competency\user_competency 3139 * 'usercompetencyplan' => \core_competency\user_competency_plan 3140 * ) 3141 * The values of of keys usercompetency and usercompetencyplan cannot be defined at the same time. 3142 */ 3143 public static function get_plan_competency($planorid, $competencyid) { 3144 static::require_enabled(); 3145 $plan = $planorid; 3146 if (!is_object($planorid)) { 3147 $plan = new plan($planorid); 3148 } 3149 3150 if (!user_competency::can_read_user($plan->get_userid())) { 3151 throw new required_capability_exception($plan->get_context(), 'moodle/competency:usercompetencyview', 3152 'nopermissions', ''); 3153 } 3154 3155 $competency = $plan->get_competency($competencyid); 3156 3157 // Get user competencies from user_competency_plan if the plan status is set to complete. 3158 $iscompletedplan = $plan->get_status() == plan::STATUS_COMPLETE; 3159 if ($iscompletedplan) { 3160 $usercompetencies = user_competency_plan::get_multiple($plan->get_userid(), $plan->get_id(), array($competencyid)); 3161 $ucresultkey = 'usercompetencyplan'; 3162 } else { 3163 $usercompetencies = user_competency::get_multiple($plan->get_userid(), array($competencyid)); 3164 $ucresultkey = 'usercompetency'; 3165 } 3166 3167 $found = count($usercompetencies); 3168 3169 if ($found) { 3170 $uc = array_pop($usercompetencies); 3171 } else { 3172 if ($iscompletedplan) { 3173 throw new coding_exception('A user competency plan is missing'); 3174 } else { 3175 $uc = user_competency::create_relation($plan->get_userid(), $competency->get_id()); 3176 $uc->create(); 3177 } 3178 } 3179 3180 $plancompetency = (object) array( 3181 'competency' => $competency, 3182 'usercompetency' => null, 3183 'usercompetencyplan' => null 3184 ); 3185 $plancompetency->$ucresultkey = $uc; 3186 3187 return $plancompetency; 3188 } 3189 3190 /** 3191 * List the competencies in a user plan. 3192 * 3193 * @param int $planorid The plan, or its ID. 3194 * @return array((object) array( 3195 * 'competency' => \core_competency\competency, 3196 * 'usercompetency' => \core_competency\user_competency 3197 * 'usercompetencyplan' => \core_competency\user_competency_plan 3198 * )) 3199 * The values of of keys usercompetency and usercompetencyplan cannot be defined at the same time. 3200 */ 3201 public static function list_plan_competencies($planorid) { 3202 static::require_enabled(); 3203 $plan = $planorid; 3204 if (!is_object($planorid)) { 3205 $plan = new plan($planorid); 3206 } 3207 3208 if (!$plan->can_read()) { 3209 $context = context_user::instance($plan->get_userid()); 3210 throw new required_capability_exception($context, 'moodle/competency:planview', 'nopermissions', ''); 3211 } 3212 3213 $result = array(); 3214 $competencies = $plan->get_competencies(); 3215 3216 // Get user competencies from user_competency_plan if the plan status is set to complete. 3217 $iscompletedplan = $plan->get_status() == plan::STATUS_COMPLETE; 3218 if ($iscompletedplan) { 3219 $usercompetencies = user_competency_plan::get_multiple($plan->get_userid(), $plan->get_id(), $competencies); 3220 $ucresultkey = 'usercompetencyplan'; 3221 } else { 3222 $usercompetencies = user_competency::get_multiple($plan->get_userid(), $competencies); 3223 $ucresultkey = 'usercompetency'; 3224 } 3225 3226 // Build the return values. 3227 foreach ($competencies as $key => $competency) { 3228 $found = false; 3229 3230 foreach ($usercompetencies as $uckey => $uc) { 3231 if ($uc->get_competencyid() == $competency->get_id()) { 3232 $found = true; 3233 unset($usercompetencies[$uckey]); 3234 break; 3235 } 3236 } 3237 3238 if (!$found) { 3239 if ($iscompletedplan) { 3240 throw new coding_exception('A user competency plan is missing'); 3241 } else { 3242 $uc = user_competency::create_relation($plan->get_userid(), $competency->get_id()); 3243 } 3244 } 3245 3246 $plancompetency = (object) array( 3247 'competency' => $competency, 3248 'usercompetency' => null, 3249 'usercompetencyplan' => null 3250 ); 3251 $plancompetency->$ucresultkey = $uc; 3252 $result[] = $plancompetency; 3253 } 3254 3255 return $result; 3256 } 3257 3258 /** 3259 * Add a competency to a plan. 3260 * 3261 * @param int $planid The id of the plan 3262 * @param int $competencyid The id of the competency 3263 * @return bool 3264 */ 3265 public static function add_competency_to_plan($planid, $competencyid) { 3266 static::require_enabled(); 3267 $plan = new plan($planid); 3268 3269 // First we do a permissions check. 3270 if (!$plan->can_manage()) { 3271 throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', ''); 3272 3273 } else if ($plan->is_based_on_template()) { 3274 throw new coding_exception('A competency can not be added to a learning plan based on a template'); 3275 } 3276 3277 if (!$plan->can_be_edited()) { 3278 throw new coding_exception('A competency can not be added to a learning plan completed'); 3279 } 3280 3281 $competency = new competency($competencyid); 3282 3283 // Can not add a competency that belong to a hidden framework. 3284 if ($competency->get_framework()->get_visible() == false) { 3285 throw new coding_exception('A competency belonging to hidden framework can not be added'); 3286 } 3287 3288 $exists = plan_competency::get_record(array('planid' => $planid, 'competencyid' => $competencyid)); 3289 if (!$exists) { 3290 $record = new stdClass(); 3291 $record->planid = $planid; 3292 $record->competencyid = $competencyid; 3293 $plancompetency = new plan_competency(0, $record); 3294 $plancompetency->create(); 3295 } 3296 3297 return true; 3298 } 3299 3300 /** 3301 * Remove a competency from a plan. 3302 * 3303 * @param int $planid The plan id 3304 * @param int $competencyid The id of the competency 3305 * @return bool 3306 */ 3307 public static function remove_competency_from_plan($planid, $competencyid) { 3308 static::require_enabled(); 3309 $plan = new plan($planid); 3310 3311 // First we do a permissions check. 3312 if (!$plan->can_manage()) { 3313 $context = context_user::instance($plan->get_userid()); 3314 throw new required_capability_exception($context, 'moodle/competency:planmanage', 'nopermissions', ''); 3315 3316 } else if ($plan->is_based_on_template()) { 3317 throw new coding_exception('A competency can not be removed from a learning plan based on a template'); 3318 } 3319 3320 if (!$plan->can_be_edited()) { 3321 throw new coding_exception('A competency can not be removed from a learning plan completed'); 3322 } 3323 3324 $link = plan_competency::get_record(array('planid' => $planid, 'competencyid' => $competencyid)); 3325 if ($link) { 3326 return $link->delete(); 3327 } 3328 return false; 3329 } 3330 3331 /** 3332 * Move the plan competency up or down in the display list. 3333 * 3334 * Requires moodle/competency:planmanage capability at the system context. 3335 * 3336 * @param int $planid The plan id 3337 * @param int $competencyidfrom The id of the competency we are moving. 3338 * @param int $competencyidto The id of the competency we are moving to. 3339 * @return boolean 3340 */ 3341 public static function reorder_plan_competency($planid, $competencyidfrom, $competencyidto) { 3342 static::require_enabled(); 3343 $plan = new plan($planid); 3344 3345 // First we do a permissions check. 3346 if (!$plan->can_manage()) { 3347 $context = context_user::instance($plan->get_userid()); 3348 throw new required_capability_exception($context, 'moodle/competency:planmanage', 'nopermissions', ''); 3349 3350 } else if ($plan->is_based_on_template()) { 3351 throw new coding_exception('A competency can not be reordered in a learning plan based on a template'); 3352 } 3353 3354 if (!$plan->can_be_edited()) { 3355 throw new coding_exception('A competency can not be reordered in a learning plan completed'); 3356 } 3357 3358 $down = true; 3359 $matches = plan_competency::get_records(array('planid' => $planid, 'competencyid' => $competencyidfrom)); 3360 if (count($matches) == 0) { 3361 throw new coding_exception('The link does not exist'); 3362 } 3363 3364 $competencyfrom = array_pop($matches); 3365 $matches = plan_competency::get_records(array('planid' => $planid, 'competencyid' => $competencyidto)); 3366 if (count($matches) == 0) { 3367 throw new coding_exception('The link does not exist'); 3368 } 3369 3370 $competencyto = array_pop($matches); 3371 3372 $all = plan_competency::get_records(array('planid' => $planid), 'sortorder', 'ASC', 0, 0); 3373 3374 if ($competencyfrom->get_sortorder() > $competencyto->get_sortorder()) { 3375 // We are moving up, so put it before the "to" item. 3376 $down = false; 3377 } 3378 3379 foreach ($all as $id => $plancompetency) { 3380 $sort = $plancompetency->get_sortorder(); 3381 if ($down && $sort > $competencyfrom->get_sortorder() && $sort <= $competencyto->get_sortorder()) { 3382 $plancompetency->set_sortorder($plancompetency->get_sortorder() - 1); 3383 $plancompetency->update(); 3384 } else if (!$down && $sort >= $competencyto->get_sortorder() && $sort < $competencyfrom->get_sortorder()) { 3385 $plancompetency->set_sortorder($plancompetency->get_sortorder() + 1); 3386 $plancompetency->update(); 3387 } 3388 } 3389 $competencyfrom->set_sortorder($competencyto->get_sortorder()); 3390 return $competencyfrom->update(); 3391 } 3392 3393 /** 3394 * Cancel a user competency review request. 3395 * 3396 * @param int $userid The user ID. 3397 * @param int $competencyid The competency ID. 3398 * @return bool 3399 */ 3400 public static function user_competency_cancel_review_request($userid, $competencyid) { 3401 static::require_enabled(); 3402 $context = context_user::instance($userid); 3403 $uc = user_competency::get_record(array('userid' => $userid, 'competencyid' => $competencyid)); 3404 if (!$uc || !$uc->can_read()) { 3405 throw new required_capability_exception($context, 'moodle/competency:usercompetencyview', 'nopermissions', ''); 3406 } else if ($uc->get_status() != user_competency::STATUS_WAITING_FOR_REVIEW) { 3407 throw new coding_exception('The competency can not be cancel review request at this stage.'); 3408 } else if (!$uc->can_request_review()) { 3409 throw new required_capability_exception($context, 'moodle/competency:usercompetencyrequestreview', 'nopermissions', ''); 3410 } 3411 3412 $uc->set_status(user_competency::STATUS_IDLE); 3413 $result = $uc->update(); 3414 if ($result) { 3415 \core\event\competency_user_competency_review_request_cancelled::create_from_user_competency($uc)->trigger(); 3416 } 3417 return $result; 3418 } 3419 3420 /** 3421 * Request a user competency review. 3422 * 3423 * @param int $userid The user ID. 3424 * @param int $competencyid The competency ID. 3425 * @return bool 3426 */ 3427 public static function user_competency_request_review($userid, $competencyid) { 3428 static::require_enabled(); 3429 $uc = user_competency::get_record(array('userid' => $userid, 'competencyid' => $competencyid)); 3430 if (!$uc) { 3431 $uc = user_competency::create_relation($userid, $competencyid); 3432 $uc->create(); 3433 } 3434 3435 if (!$uc->can_read()) { 3436 throw new required_capability_exception($uc->get_context(), 'moodle/competency:usercompetencyview', 3437 'nopermissions', ''); 3438 } else if ($uc->get_status() != user_competency::STATUS_IDLE) { 3439 throw new coding_exception('The competency can not be sent for review at this stage.'); 3440 } else if (!$uc->can_request_review()) { 3441 throw new required_capability_exception($uc->get_context(), 'moodle/competency:usercompetencyrequestreview', 3442 'nopermissions', ''); 3443 } 3444 3445 $uc->set_status(user_competency::STATUS_WAITING_FOR_REVIEW); 3446 $result = $uc->update(); 3447 if ($result) { 3448 \core\event\competency_user_competency_review_requested::create_from_user_competency($uc)->trigger(); 3449 } 3450 return $result; 3451 } 3452 3453 /** 3454 * Start a user competency review. 3455 * 3456 * @param int $userid The user ID. 3457 * @param int $competencyid The competency ID. 3458 * @return bool 3459 */ 3460 public static function user_competency_start_review($userid, $competencyid) { 3461 global $USER; 3462 static::require_enabled(); 3463 3464 $context = context_user::instance($userid); 3465 $uc = user_competency::get_record(array('userid' => $userid, 'competencyid' => $competencyid)); 3466 if (!$uc || !$uc->can_read()) { 3467 throw new required_capability_exception($context, 'moodle/competency:usercompetencyview', 'nopermissions', ''); 3468 } else if ($uc->get_status() != user_competency::STATUS_WAITING_FOR_REVIEW) { 3469 throw new coding_exception('The competency review can not be started at this stage.'); 3470 } else if (!$uc->can_review()) { 3471 throw new required_capability_exception($context, 'moodle/competency:usercompetencyreview', 'nopermissions', ''); 3472 } 3473 3474 $uc->set_status(user_competency::STATUS_IN_REVIEW); 3475 $uc->set_reviewerid($USER->id); 3476 $result = $uc->update(); 3477 if ($result) { 3478 \core\event\competency_user_competency_review_started::create_from_user_competency($uc)->trigger(); 3479 } 3480 return $result; 3481 } 3482 3483 /** 3484 * Stop a user competency review. 3485 * 3486 * @param int $userid The user ID. 3487 * @param int $competencyid The competency ID. 3488 * @return bool 3489 */ 3490 public static function user_competency_stop_review($userid, $competencyid) { 3491 static::require_enabled(); 3492 $context = context_user::instance($userid); 3493 $uc = user_competency::get_record(array('userid' => $userid, 'competencyid' => $competencyid)); 3494 if (!$uc || !$uc->can_read()) { 3495 throw new required_capability_exception($context, 'moodle/competency:usercompetencyview', 'nopermissions', ''); 3496 } else if ($uc->get_status() != user_competency::STATUS_IN_REVIEW) { 3497 throw new coding_exception('The competency review can not be stopped at this stage.'); 3498 } else if (!$uc->can_review()) { 3499 throw new required_capability_exception($context, 'moodle/competency:usercompetencyreview', 'nopermissions', ''); 3500 } 3501 3502 $uc->set_status(user_competency::STATUS_IDLE); 3503 $result = $uc->update(); 3504 if ($result) { 3505 \core\event\competency_user_competency_review_stopped::create_from_user_competency($uc)->trigger(); 3506 } 3507 return $result; 3508 } 3509 3510 /** 3511 * Log user competency viewed event. 3512 * 3513 * @param user_competency|int $usercompetencyorid The user competency object or user competency id 3514 * @return bool 3515 */ 3516 public static function user_competency_viewed($usercompetencyorid) { 3517 static::require_enabled(); 3518 $uc = $usercompetencyorid; 3519 if (!is_object($uc)) { 3520 $uc = new user_competency($uc); 3521 } 3522 3523 if (!$uc || !$uc->can_read()) { 3524 throw new required_capability_exception($uc->get_context(), 'moodle/competency:usercompetencyview', 3525 'nopermissions', ''); 3526 } 3527 3528 \core\event\competency_user_competency_viewed::create_from_user_competency_viewed($uc)->trigger(); 3529 return true; 3530 } 3531 3532 /** 3533 * Log user competency viewed in plan event. 3534 * 3535 * @param user_competency|int $usercompetencyorid The user competency object or user competency id 3536 * @param int $planid The plan ID 3537 * @return bool 3538 */ 3539 public static function user_competency_viewed_in_plan($usercompetencyorid, $planid) { 3540 static::require_enabled(); 3541 $uc = $usercompetencyorid; 3542 if (!is_object($uc)) { 3543 $uc = new user_competency($uc); 3544 } 3545 3546 if (!$uc || !$uc->can_read()) { 3547 throw new required_capability_exception($uc->get_context(), 'moodle/competency:usercompetencyview', 3548 'nopermissions', ''); 3549 } 3550 $plan = new plan($planid); 3551 if ($plan->get_status() == plan::STATUS_COMPLETE) { 3552 throw new coding_exception('To log the user competency in completed plan use user_competency_plan_viewed method.'); 3553 } 3554 3555 \core\event\competency_user_competency_viewed_in_plan::create_from_user_competency_viewed_in_plan($uc, $planid)->trigger(); 3556 return true; 3557 } 3558 3559 /** 3560 * Log user competency viewed in course event. 3561 * 3562 * @param user_competency_course|int $usercoursecompetencyorid The user competency course object or its ID. 3563 * @param int $courseid The course ID 3564 * @return bool 3565 */ 3566 public static function user_competency_viewed_in_course($usercoursecompetencyorid) { 3567 static::require_enabled(); 3568 $ucc = $usercoursecompetencyorid; 3569 if (!is_object($ucc)) { 3570 $ucc = new user_competency_course($ucc); 3571 } 3572 3573 if (!$ucc || !user_competency::can_read_user_in_course($ucc->get_userid(), $ucc->get_courseid())) { 3574 throw new required_capability_exception($ucc->get_context(), 'moodle/competency:usercompetencyview', 3575 'nopermissions', ''); 3576 } 3577 3578 // Validate the course, this will throw an exception if not valid. 3579 self::validate_course($ucc->get_courseid()); 3580 3581 \core\event\competency_user_competency_viewed_in_course::create_from_user_competency_viewed_in_course($ucc)->trigger(); 3582 return true; 3583 } 3584 3585 /** 3586 * Log user competency plan viewed event. 3587 * 3588 * @param user_competency_plan|int $usercompetencyplanorid The user competency plan object or user competency plan id 3589 * @return bool 3590 */ 3591 public static function user_competency_plan_viewed($usercompetencyplanorid) { 3592 static::require_enabled(); 3593 $ucp = $usercompetencyplanorid; 3594 if (!is_object($ucp)) { 3595 $ucp = new user_competency_plan($ucp); 3596 } 3597 3598 if (!$ucp || !user_competency::can_read_user($ucp->get_userid())) { 3599 throw new required_capability_exception($ucp->get_context(), 'moodle/competency:usercompetencyview', 3600 'nopermissions', ''); 3601 } 3602 $plan = new plan($ucp->get_planid()); 3603 if ($plan->get_status() != plan::STATUS_COMPLETE) { 3604 throw new coding_exception('To log the user competency in non-completed plan use ' 3605 . 'user_competency_viewed_in_plan method.'); 3606 } 3607 3608 \core\event\competency_user_competency_plan_viewed::create_from_user_competency_plan($ucp)->trigger(); 3609 return true; 3610 } 3611 3612 /** 3613 * Check if template has related data. 3614 * 3615 * @param int $templateid The id of the template to check. 3616 * @return boolean 3617 */ 3618 public static function template_has_related_data($templateid) { 3619 static::require_enabled(); 3620 // First we do a permissions check. 3621 $template = new template($templateid); 3622 3623 if (!$template->can_read()) { 3624 throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview', 3625 'nopermissions', ''); 3626 } 3627 3628 // OK - all set. 3629 return $template->has_plans(); 3630 } 3631 3632 /** 3633 * List all the related competencies. 3634 * 3635 * @param int $competencyid The id of the competency to check. 3636 * @return competency[] 3637 */ 3638 public static function list_related_competencies($competencyid) { 3639 static::require_enabled(); 3640 $competency = new competency($competencyid); 3641 3642 if (!has_any_capability(array('moodle/competency:competencyview', 'moodle/competency:competencymanage'), 3643 $competency->get_context())) { 3644 throw new required_capability_exception($competency->get_context(), 'moodle/competency:competencyview', 3645 'nopermissions', ''); 3646 } 3647 3648 return $competency->get_related_competencies(); 3649 } 3650 3651 /** 3652 * Add a related competency. 3653 * 3654 * @param int $competencyid The id of the competency 3655 * @param int $relatedcompetencyid The id of the related competency. 3656 * @return bool False when create failed, true on success, or if the relation already existed. 3657 */ 3658 public static function add_related_competency($competencyid, $relatedcompetencyid) { 3659 static::require_enabled(); 3660 $competency1 = new competency($competencyid); 3661 $competency2 = new competency($relatedcompetencyid); 3662 3663 require_capability('moodle/competency:competencymanage', $competency1->get_context()); 3664 3665 $relatedcompetency = related_competency::get_relation($competency1->get_id(), $competency2->get_id()); 3666 if (!$relatedcompetency->get_id()) { 3667 $relatedcompetency->create(); 3668 return true; 3669 } 3670 3671 return true; 3672 } 3673 3674 /** 3675 * Remove a related competency. 3676 * 3677 * @param int $competencyid The id of the competency. 3678 * @param int $relatedcompetencyid The id of the related competency. 3679 * @return bool True when it was deleted, false when it wasn't or the relation doesn't exist. 3680 */ 3681 public static function remove_related_competency($competencyid, $relatedcompetencyid) { 3682 static::require_enabled(); 3683 $competency = new competency($competencyid); 3684 3685 // This only check if we have the permission in either competency because both competencies 3686 // should belong to the same framework. 3687 require_capability('moodle/competency:competencymanage', $competency->get_context()); 3688 3689 $relatedcompetency = related_competency::get_relation($competencyid, $relatedcompetencyid); 3690 if ($relatedcompetency->get_id()) { 3691 return $relatedcompetency->delete(); 3692 } 3693 3694 return false; 3695 } 3696 3697 /** 3698 * Read a user evidence. 3699 * 3700 * @param int $id 3701 * @return user_evidence 3702 */ 3703 public static function read_user_evidence($id) { 3704 static::require_enabled(); 3705 $userevidence = new user_evidence($id); 3706 3707 if (!$userevidence->can_read()) { 3708 $context = $userevidence->get_context(); 3709 throw new required_capability_exception($context, 'moodle/competency:userevidenceview', 'nopermissions', ''); 3710 } 3711 3712 return $userevidence; 3713 } 3714 3715 /** 3716 * Create a new user evidence. 3717 * 3718 * @param object $data The data. 3719 * @param int $draftitemid The draft ID in which files have been saved. 3720 * @return user_evidence 3721 */ 3722 public static function create_user_evidence($data, $draftitemid = null) { 3723 static::require_enabled(); 3724 $userevidence = new user_evidence(null, $data); 3725 $context = $userevidence->get_context(); 3726 3727 if (!$userevidence->can_manage()) { 3728 throw new required_capability_exception($context, 'moodle/competency:userevidencemanage', 'nopermissions', ''); 3729 } 3730 3731 $userevidence->create(); 3732 if (!empty($draftitemid)) { 3733 $fileareaoptions = array('subdirs' => true); 3734 $itemid = $userevidence->get_id(); 3735 file_save_draft_area_files($draftitemid, $context->id, 'core_competency', 'userevidence', $itemid, $fileareaoptions); 3736 } 3737 3738 // Trigger an evidence of prior learning created event. 3739 \core\event\competency_user_evidence_created::create_from_user_evidence($userevidence)->trigger(); 3740 3741 return $userevidence; 3742 } 3743 3744 /** 3745 * Create a new user evidence. 3746 * 3747 * @param object $data The data. 3748 * @param int $draftitemid The draft ID in which files have been saved. 3749 * @return user_evidence 3750 */ 3751 public static function update_user_evidence($data, $draftitemid = null) { 3752 static::require_enabled(); 3753 $userevidence = new user_evidence($data->id); 3754 $context = $userevidence->get_context(); 3755 3756 if (!$userevidence->can_manage()) { 3757 throw new required_capability_exception($context, 'moodle/competency:userevidencemanage', 'nopermissions', ''); 3758 3759 } else if (array_key_exists('userid', $data) && $data->userid != $userevidence->get_userid()) { 3760 throw new coding_exception('Can not change the userid of a user evidence.'); 3761 } 3762 3763 $userevidence->from_record($data); 3764 $userevidence->update(); 3765 3766 if (!empty($draftitemid)) { 3767 $fileareaoptions = array('subdirs' => true); 3768 $itemid = $userevidence->get_id(); 3769 file_save_draft_area_files($draftitemid, $context->id, 'core_competency', 'userevidence', $itemid, $fileareaoptions); 3770 } 3771 3772 // Trigger an evidence of prior learning updated event. 3773 \core\event\competency_user_evidence_updated::create_from_user_evidence($userevidence)->trigger(); 3774 3775 return $userevidence; 3776 } 3777 3778 /** 3779 * Delete a user evidence. 3780 * 3781 * @param int $id The user evidence ID. 3782 * @return bool 3783 */ 3784 public static function delete_user_evidence($id) { 3785 static::require_enabled(); 3786 $userevidence = new user_evidence($id); 3787 $context = $userevidence->get_context(); 3788 3789 if (!$userevidence->can_manage()) { 3790 throw new required_capability_exception($context, 'moodle/competency:userevidencemanage', 'nopermissions', ''); 3791 } 3792 3793 // Delete the user evidence. 3794 $userevidence->delete(); 3795 3796 // Delete associated files. 3797 $fs = get_file_storage(); 3798 $fs->delete_area_files($context->id, 'core_competency', 'userevidence', $id); 3799 3800 // Delete relation between evidence and competencies. 3801 $userevidence->set_id($id); // Restore the ID to fully mock the object. 3802 $competencies = user_evidence_competency::get_competencies_by_userevidenceid($id); 3803 foreach ($competencies as $competency) { 3804 static::delete_user_evidence_competency($userevidence, $competency->get_id()); 3805 } 3806 3807 // Trigger an evidence of prior learning deleted event. 3808 \core\event\competency_user_evidence_deleted::create_from_user_evidence($userevidence)->trigger(); 3809 3810 $userevidence->set_id(0); // Restore the object. 3811 3812 return true; 3813 } 3814 3815 /** 3816 * List the user evidence of a user. 3817 * 3818 * @param int $userid The user ID. 3819 * @return user_evidence[] 3820 */ 3821 public static function list_user_evidence($userid) { 3822 static::require_enabled(); 3823 if (!user_evidence::can_read_user($userid)) { 3824 $context = context_user::instance($userid); 3825 throw new required_capability_exception($context, 'moodle/competency:userevidenceview', 'nopermissions', ''); 3826 } 3827 3828 $evidence = user_evidence::get_records(array('userid' => $userid), 'name'); 3829 return $evidence; 3830 } 3831 3832 /** 3833 * Link a user evidence with a competency. 3834 * 3835 * @param user_evidence|int $userevidenceorid User evidence or its ID. 3836 * @param int $competencyid Competency ID. 3837 * @return user_evidence_competency 3838 */ 3839 public static function create_user_evidence_competency($userevidenceorid, $competencyid) { 3840 global $USER; 3841 static::require_enabled(); 3842 3843 $userevidence = $userevidenceorid; 3844 if (!is_object($userevidence)) { 3845 $userevidence = self::read_user_evidence($userevidence); 3846 } 3847 3848 // Perform user evidence capability checks. 3849 if (!$userevidence->can_manage()) { 3850 $context = $userevidence->get_context(); 3851 throw new required_capability_exception($context, 'moodle/competency:userevidencemanage', 'nopermissions', ''); 3852 } 3853 3854 // Perform competency capability checks. 3855 $competency = self::read_competency($competencyid); 3856 3857 // Get (and create) the relation. 3858 $relation = user_evidence_competency::get_relation($userevidence->get_id(), $competency->get_id()); 3859 if (!$relation->get_id()) { 3860 $relation->create(); 3861 3862 $link = url::user_evidence($userevidence->get_id()); 3863 self::add_evidence( 3864 $userevidence->get_userid(), 3865 $competency, 3866 $userevidence->get_context(), 3867 evidence::ACTION_LOG, 3868 'evidence_evidenceofpriorlearninglinked', 3869 'core_competency', 3870 $userevidence->get_name(), 3871 false, 3872 $link->out(false), 3873 null, 3874 $USER->id 3875 ); 3876 } 3877 3878 return $relation; 3879 } 3880 3881 /** 3882 * Delete a relationship between a user evidence and a competency. 3883 * 3884 * @param user_evidence|int $userevidenceorid User evidence or its ID. 3885 * @param int $competencyid Competency ID. 3886 * @return bool 3887 */ 3888 public static function delete_user_evidence_competency($userevidenceorid, $competencyid) { 3889 global $USER; 3890 static::require_enabled(); 3891 3892 $userevidence = $userevidenceorid; 3893 if (!is_object($userevidence)) { 3894 $userevidence = self::read_user_evidence($userevidence); 3895 } 3896 3897 // Perform user evidence capability checks. 3898 if (!$userevidence->can_manage()) { 3899 $context = $userevidence->get_context(); 3900 throw new required_capability_exception($context, 'moodle/competency:userevidencemanage', 'nopermissions', ''); 3901 } 3902 3903 // Get (and delete) the relation. 3904 $relation = user_evidence_competency::get_relation($userevidence->get_id(), $competencyid); 3905 if (!$relation->get_id()) { 3906 return true; 3907 } 3908 3909 $success = $relation->delete(); 3910 if ($success) { 3911 self::add_evidence( 3912 $userevidence->get_userid(), 3913 $competencyid, 3914 $userevidence->get_context(), 3915 evidence::ACTION_LOG, 3916 'evidence_evidenceofpriorlearningunlinked', 3917 'core_competency', 3918 $userevidence->get_name(), 3919 false, 3920 null, 3921 null, 3922 $USER->id 3923 ); 3924 } 3925 3926 return $success; 3927 } 3928 3929 /** 3930 * Send request review for user evidence competencies. 3931 * 3932 * @param int $id The user evidence ID. 3933 * @return bool 3934 */ 3935 public static function request_review_of_user_evidence_linked_competencies($id) { 3936 $userevidence = new user_evidence($id); 3937 $context = $userevidence->get_context(); 3938 $userid = $userevidence->get_userid(); 3939 3940 if (!$userevidence->can_manage()) { 3941 throw new required_capability_exception($context, 'moodle/competency:userevidencemanage', 'nopermissions', ''); 3942 } 3943 3944 $usercompetencies = user_evidence_competency::get_user_competencies_by_userevidenceid($id); 3945 foreach ($usercompetencies as $usercompetency) { 3946 if ($usercompetency->get_status() == user_competency::STATUS_IDLE) { 3947 static::user_competency_request_review($userid, $usercompetency->get_competencyid()); 3948 } 3949 } 3950 3951 return true; 3952 } 3953 3954 /** 3955 * Recursively duplicate competencies from a tree, we start duplicating from parents to children to have a correct path. 3956 * This method does not copy the related competencies. 3957 * 3958 * @param int $frameworkid - framework id 3959 * @param competency[] $tree - array of competencies object 3960 * @param int $oldparent - old parent id 3961 * @param int $newparent - new parent id 3962 * @return competency[] $matchids - List of old competencies ids matched with new competencies object. 3963 */ 3964 protected static function duplicate_competency_tree($frameworkid, $tree, $oldparent = 0, $newparent = 0) { 3965 $matchids = array(); 3966 foreach ($tree as $node) { 3967 if ($node->competency->get_parentid() == $oldparent) { 3968 $parentid = $node->competency->get_id(); 3969 3970 // Create the competency. 3971 $competency = new competency(0, $node->competency->to_record()); 3972 $competency->set_competencyframeworkid($frameworkid); 3973 $competency->set_parentid($newparent); 3974 $competency->set_path(''); 3975 $competency->set_id(0); 3976 $competency->reset_rule(); 3977 $competency->create(); 3978 3979 // Trigger the created event competency. 3980 \core\event\competency_created::create_from_competency($competency)->trigger(); 3981 3982 // Match the old id with the new one. 3983 $matchids[$parentid] = $competency; 3984 3985 if (!empty($node->children)) { 3986 // Duplicate children competency. 3987 $childrenids = self::duplicate_competency_tree($frameworkid, $node->children, $parentid, $competency->get_id()); 3988 // Array_merge does not keep keys when merging so we use the + operator. 3989 $matchids = $matchids + $childrenids; 3990 } 3991 } 3992 } 3993 return $matchids; 3994 } 3995 3996 /** 3997 * Recursively migrate competency rules. 3998 * 3999 * @param competency[] $tree - array of competencies object 4000 * @param competency[] $matchids - List of old competencies ids matched with new competencies object 4001 */ 4002 protected static function migrate_competency_tree_rules($tree, $matchids) { 4003 4004 foreach ($tree as $node) { 4005 $oldcompid = $node->competency->get_id(); 4006 if ($node->competency->get_ruletype() && array_key_exists($oldcompid, $matchids)) { 4007 try { 4008 // Get the new competency. 4009 $competency = $matchids[$oldcompid]; 4010 $class = $node->competency->get_ruletype(); 4011 $newruleconfig = $class::migrate_config($node->competency->get_ruleconfig(), $matchids); 4012 $competency->set_ruleconfig($newruleconfig); 4013 $competency->set_ruletype($class); 4014 $competency->set_ruleoutcome($node->competency->get_ruleoutcome()); 4015 $competency->update(); 4016 } catch (\Exception $e) { 4017 debugging('Could not migrate competency rule from: ' . $oldcompid . ' to: ' . $competency->get_id() . '.' . 4018 ' Exception: ' . $e->getMessage(), DEBUG_DEVELOPER); 4019 $competency->reset_rule(); 4020 } 4021 } 4022 4023 if (!empty($node->children)) { 4024 self::migrate_competency_tree_rules($node->children, $matchids); 4025 } 4026 } 4027 } 4028 4029 /** 4030 * Archive user competencies in a plan. 4031 * 4032 * @param int $plan The plan object. 4033 * @return void 4034 */ 4035 protected static function archive_user_competencies_in_plan($plan) { 4036 4037 // Check if the plan was already completed. 4038 if ($plan->get_status() == plan::STATUS_COMPLETE) { 4039 throw new coding_exception('The plan is already completed.'); 4040 } 4041 4042 $competencies = $plan->get_competencies(); 4043 $usercompetencies = user_competency::get_multiple($plan->get_userid(), $competencies); 4044 4045 $i = 0; 4046 foreach ($competencies as $competency) { 4047 $found = false; 4048 4049 foreach ($usercompetencies as $uckey => $uc) { 4050 if ($uc->get_competencyid() == $competency->get_id()) { 4051 $found = true; 4052 4053 $ucprecord = $uc->to_record(); 4054 $ucprecord->planid = $plan->get_id(); 4055 $ucprecord->sortorder = $i; 4056 unset($ucprecord->id); 4057 unset($ucprecord->status); 4058 unset($ucprecord->reviewerid); 4059 4060 $usercompetencyplan = new user_competency_plan(0, $ucprecord); 4061 $usercompetencyplan->create(); 4062 4063 unset($usercompetencies[$uckey]); 4064 break; 4065 } 4066 } 4067 4068 // If the user competency doesn't exist, we create a new relation in user_competency_plan. 4069 if (!$found) { 4070 $usercompetencyplan = user_competency_plan::create_relation($plan->get_userid(), $competency->get_id(), 4071 $plan->get_id()); 4072 $usercompetencyplan->set_sortorder($i); 4073 $usercompetencyplan->create(); 4074 } 4075 $i++; 4076 } 4077 } 4078 4079 /** 4080 * Delete archived user competencies in a plan. 4081 * 4082 * @param int $plan The plan object. 4083 * @return void 4084 */ 4085 protected static function remove_archived_user_competencies_in_plan($plan) { 4086 $competencies = $plan->get_competencies(); 4087 $usercompetenciesplan = user_competency_plan::get_multiple($plan->get_userid(), $plan->get_id(), $competencies); 4088 4089 foreach ($usercompetenciesplan as $ucpkey => $ucp) { 4090 $ucp->delete(); 4091 } 4092 } 4093 4094 /** 4095 * List all the evidence for a user competency. 4096 * 4097 * @param int $userid The user id - only used if usercompetencyid is 0. 4098 * @param int $competencyid The competency id - only used it usercompetencyid is 0. 4099 * @param int $planid The plan id - not used yet - but can be used to only list archived evidence if a plan is completed. 4100 * @param string $sort The field to sort the evidence by. 4101 * @param string $order The ordering of the sorting. 4102 * @param int $skip Number of records to skip. 4103 * @param int $limit Number of records to return. 4104 * @return \core_competency\evidence[] 4105 * @return array of \core_competency\evidence 4106 */ 4107 public static function list_evidence($userid = 0, $competencyid = 0, $planid = 0, $sort = 'timecreated', 4108 $order = 'DESC', $skip = 0, $limit = 0) { 4109 static::require_enabled(); 4110 4111 if (!user_competency::can_read_user($userid)) { 4112 $context = context_user::instance($userid); 4113 throw new required_capability_exception($context, 'moodle/competency:usercompetencyview', 'nopermissions', ''); 4114 } 4115 4116 $usercompetency = user_competency::get_record(array('userid' => $userid, 'competencyid' => $competencyid)); 4117 if (!$usercompetency) { 4118 return array(); 4119 } 4120 4121 $plancompleted = false; 4122 if ($planid != 0) { 4123 $plan = new plan($planid); 4124 if ($plan->get_status() == plan::STATUS_COMPLETE) { 4125 $plancompleted = true; 4126 } 4127 } 4128 4129 $select = 'usercompetencyid = :usercompetencyid'; 4130 $params = array('usercompetencyid' => $usercompetency->get_id()); 4131 if ($plancompleted) { 4132 $select .= ' AND timecreated <= :timecompleted'; 4133 $params['timecompleted'] = $plan->get_timemodified(); 4134 } 4135 4136 $orderby = $sort . ' ' . $order; 4137 $orderby .= !empty($orderby) ? ', id DESC' : 'id DESC'; // Prevent random ordering. 4138 4139 $evidence = evidence::get_records_select($select, $params, $orderby, '*', $skip, $limit); 4140 return $evidence; 4141 } 4142 4143 /** 4144 * List all the evidence for a user competency in a course. 4145 * 4146 * @param int $userid The user ID. 4147 * @param int $courseid The course ID. 4148 * @param int $competencyid The competency ID. 4149 * @param string $sort The field to sort the evidence by. 4150 * @param string $order The ordering of the sorting. 4151 * @param int $skip Number of records to skip. 4152 * @param int $limit Number of records to return. 4153 * @return \core_competency\evidence[] 4154 */ 4155 public static function list_evidence_in_course($userid = 0, $courseid = 0, $competencyid = 0, $sort = 'timecreated', 4156 $order = 'DESC', $skip = 0, $limit = 0) { 4157 static::require_enabled(); 4158 4159 if (!user_competency::can_read_user_in_course($userid, $courseid)) { 4160 $context = context_user::instance($userid); 4161 throw new required_capability_exception($context, 'moodle/competency:usercompetencyview', 'nopermissions', ''); 4162 } 4163 4164 $usercompetency = user_competency::get_record(array('userid' => $userid, 'competencyid' => $competencyid)); 4165 if (!$usercompetency) { 4166 return array(); 4167 } 4168 4169 $context = context_course::instance($courseid); 4170 return evidence::get_records_for_usercompetency($usercompetency->get_id(), $context, $sort, $order, $skip, $limit); 4171 } 4172 4173 /** 4174 * Create an evidence from a list of parameters. 4175 * 4176 * Requires no capability because evidence can be added in many situations under any user. 4177 * 4178 * @param int $userid The user id for which evidence is added. 4179 * @param competency|int $competencyorid The competency, or its id for which evidence is added. 4180 * @param context|int $contextorid The context in which the evidence took place. 4181 * @param int $action The type of action to take on the competency. \core_competency\evidence::ACTION_*. 4182 * @param string $descidentifier The strings identifier. 4183 * @param string $desccomponent The strings component. 4184 * @param mixed $desca Any arguments the string requires. 4185 * @param bool $recommend When true, the user competency will be sent for review. 4186 * @param string $url The url the evidence may link to. 4187 * @param int $grade The grade, or scale ID item. 4188 * @param int $actionuserid The ID of the user who took the action of adding the evidence. Null when system. 4189 * This should be used when the action was taken by a real person, this will allow 4190 * to keep track of all the evidence given by a certain person. 4191 * @param string $note A note to attach to the evidence. 4192 * @return evidence 4193 * @throws coding_exception 4194 * @throws invalid_persistent_exception 4195 * @throws moodle_exception 4196 */ 4197 public static function add_evidence($userid, $competencyorid, $contextorid, $action, $descidentifier, $desccomponent, 4198 $desca = null, $recommend = false, $url = null, $grade = null, $actionuserid = null, 4199 $note = null) { 4200 global $DB; 4201 static::require_enabled(); 4202 4203 // Some clearly important variable assignments right there. 4204 $competencyid = $competencyorid; 4205 $competency = null; 4206 if (is_object($competencyid)) { 4207 $competency = $competencyid; 4208 $competencyid = $competency->get_id(); 4209 } 4210 $contextid = $contextorid; 4211 $context = $contextorid; 4212 if (is_object($contextorid)) { 4213 $contextid = $contextorid->id; 4214 } else { 4215 $context = context::instance_by_id($contextorid); 4216 } 4217 $setucgrade = false; 4218 $ucgrade = null; 4219 $ucproficiency = null; 4220 $usercompetencycourse = null; 4221 4222 // Fetch or create the user competency. 4223 $usercompetency = user_competency::get_record(array('userid' => $userid, 'competencyid' => $competencyid)); 4224 if (!$usercompetency) { 4225 $usercompetency = user_competency::create_relation($userid, $competencyid); 4226 $usercompetency->create(); 4227 } 4228 4229 // What should we be doing? 4230 switch ($action) { 4231 4232 // Completing a competency. 4233 case evidence::ACTION_COMPLETE: 4234 // The logic here goes like this: 4235 // 4236 // if rating outside a course 4237 // - set the default grade and proficiency ONLY if there is no current grade 4238 // else we are in a course 4239 // - set the defautl grade and proficiency in the course ONLY if there is no current grade in the course 4240 // - then check the course settings to see if we should push the rating outside the course 4241 // - if we should push it 4242 // --- push it only if the user_competency (outside the course) has no grade 4243 // Done. 4244 4245 if ($grade !== null) { 4246 throw new coding_exception("The grade MUST NOT be set with a 'completing' evidence."); 4247 } 4248 4249 // Fetch the default grade to attach to the evidence. 4250 if (empty($competency)) { 4251 $competency = new competency($competencyid); 4252 } 4253 list($grade, $proficiency) = $competency->get_default_grade(); 4254 4255 // Add user_competency_course record when in a course or module. 4256 if (in_array($context->contextlevel, array(CONTEXT_COURSE, CONTEXT_MODULE))) { 4257 $coursecontext = $context->get_course_context(); 4258 $courseid = $coursecontext->instanceid; 4259 $filterparams = array( 4260 'userid' => $userid, 4261 'competencyid' => $competencyid, 4262 'courseid' => $courseid 4263 ); 4264 // Fetch or create user competency course. 4265 $usercompetencycourse = user_competency_course::get_record($filterparams); 4266 if (!$usercompetencycourse) { 4267 $usercompetencycourse = user_competency_course::create_relation($userid, $competencyid, $courseid); 4268 $usercompetencycourse->create(); 4269 } 4270 // Only update the grade and proficiency if there is not already a grade. 4271 if ($usercompetencycourse->get_grade() === null) { 4272 // Set grade. 4273 $usercompetencycourse->set_grade($grade); 4274 // Set proficiency. 4275 $usercompetencycourse->set_proficiency($proficiency); 4276 } 4277 4278 // Check the course settings to see if we should push to user plans. 4279 $coursesettings = course_competency_settings::get_by_courseid($courseid); 4280 $setucgrade = $coursesettings->get_pushratingstouserplans(); 4281 4282 if ($setucgrade) { 4283 // Only push to user plans if there is not already a grade. 4284 if ($usercompetency->get_grade() !== null) { 4285 $setucgrade = false; 4286 } else { 4287 $ucgrade = $grade; 4288 $ucproficiency = $proficiency; 4289 } 4290 } 4291 } else { 4292 4293 // When completing the competency we fetch the default grade from the competency. But we only mark 4294 // the user competency when a grade has not been set yet. Complete is an action to use with automated systems. 4295 if ($usercompetency->get_grade() === null) { 4296 $setucgrade = true; 4297 $ucgrade = $grade; 4298 $ucproficiency = $proficiency; 4299 } 4300 } 4301 4302 break; 4303 4304 // We override the grade, even overriding back to not set. 4305 case evidence::ACTION_OVERRIDE: 4306 $setucgrade = true; 4307 $ucgrade = $grade; 4308 if (empty($competency)) { 4309 $competency = new competency($competencyid); 4310 } 4311 if ($ucgrade !== null) { 4312 $ucproficiency = $competency->get_proficiency_of_grade($ucgrade); 4313 } 4314 4315 // Add user_competency_course record when in a course or module. 4316 if (in_array($context->contextlevel, array(CONTEXT_COURSE, CONTEXT_MODULE))) { 4317 $coursecontext = $context->get_course_context(); 4318 $courseid = $coursecontext->instanceid; 4319 $filterparams = array( 4320 'userid' => $userid, 4321 'competencyid' => $competencyid, 4322 'courseid' => $courseid 4323 ); 4324 // Fetch or create user competency course. 4325 $usercompetencycourse = user_competency_course::get_record($filterparams); 4326 if (!$usercompetencycourse) { 4327 $usercompetencycourse = user_competency_course::create_relation($userid, $competencyid, $courseid); 4328 $usercompetencycourse->create(); 4329 } 4330 // Get proficiency. 4331 $proficiency = $ucproficiency; 4332 if ($proficiency === null) { 4333 if (empty($competency)) { 4334 $competency = new competency($competencyid); 4335 } 4336 $proficiency = $competency->get_proficiency_of_grade($grade); 4337 } 4338 // Set grade. 4339 $usercompetencycourse->set_grade($grade); 4340 // Set proficiency. 4341 $usercompetencycourse->set_proficiency($proficiency); 4342 4343 $coursesettings = course_competency_settings::get_by_courseid($courseid); 4344 if (!$coursesettings->get_pushratingstouserplans()) { 4345 $setucgrade = false; 4346 } 4347 } 4348 4349 break; 4350 4351 // Simply logging an evidence. 4352 case evidence::ACTION_LOG: 4353 if ($grade !== null) { 4354 throw new coding_exception("The grade MUST NOT be set when 'logging' an evidence."); 4355 } 4356 break; 4357 4358 // Whoops, this is not expected. 4359 default: 4360 throw new coding_exception('Unexpected action parameter when registering an evidence.'); 4361 break; 4362 } 4363 4364 // Should we recommend? 4365 if ($recommend && $usercompetency->get_status() == user_competency::STATUS_IDLE) { 4366 $usercompetency->set_status(user_competency::STATUS_WAITING_FOR_REVIEW); 4367 } 4368 4369 // Setting the grade and proficiency for the user competency. 4370 $wascompleted = false; 4371 if ($setucgrade == true) { 4372 if (!$usercompetency->get_proficiency() && $ucproficiency) { 4373 $wascompleted = true; 4374 } 4375 $usercompetency->set_grade($ucgrade); 4376 $usercompetency->set_proficiency($ucproficiency); 4377 } 4378 4379 // Prepare the evidence. 4380 $record = new stdClass(); 4381 $record->usercompetencyid = $usercompetency->get_id(); 4382 $record->contextid = $contextid; 4383 $record->action = $action; 4384 $record->descidentifier = $descidentifier; 4385 $record->desccomponent = $desccomponent; 4386 $record->grade = $grade; 4387 $record->actionuserid = $actionuserid; 4388 $record->note = $note; 4389 $evidence = new evidence(0, $record); 4390 $evidence->set_desca($desca); 4391 $evidence->set_url($url); 4392 4393 // Validate both models, we should not operate on one if the other will not save. 4394 if (!$usercompetency->is_valid()) { 4395 throw new invalid_persistent_exception($usercompetency->get_errors()); 4396 } else if (!$evidence->is_valid()) { 4397 throw new invalid_persistent_exception($evidence->get_errors()); 4398 } 4399 4400 // Save the user_competency_course record. 4401 if ($usercompetencycourse !== null) { 4402 // Validate and update. 4403 if (!$usercompetencycourse->is_valid()) { 4404 throw new invalid_persistent_exception($usercompetencycourse->get_errors()); 4405 } 4406 $usercompetencycourse->update(); 4407 } 4408 4409 // Finally save. Pheww! 4410 $usercompetency->update(); 4411 $evidence->create(); 4412 4413 // Trigger the evidence_created event. 4414 \core\event\competency_evidence_created::create_from_evidence($evidence, $usercompetency, $recommend)->trigger(); 4415 4416 // The competency was marked as completed, apply the rules. 4417 if ($wascompleted) { 4418 self::apply_competency_rules_from_usercompetency($usercompetency, $competency); 4419 } 4420 4421 return $evidence; 4422 } 4423 4424 /** 4425 * Read an evidence. 4426 * @param int $evidenceid The evidence ID. 4427 * @return evidence 4428 */ 4429 public static function read_evidence($evidenceid) { 4430 static::require_enabled(); 4431 4432 $evidence = new evidence($evidenceid); 4433 $uc = new user_competency($evidence->get_usercompetencyid()); 4434 if (!$uc->can_read()) { 4435 throw new required_capability_exception($uc->get_context(), 'moodle/competency:usercompetencyview', 4436 'nopermissions', ''); 4437 } 4438 4439 return $evidence; 4440 } 4441 4442 /** 4443 * Delete an evidence. 4444 * 4445 * @param evidence|int $evidenceorid The evidence, or its ID. 4446 * @return bool 4447 */ 4448 public static function delete_evidence($evidenceorid) { 4449 $evidence = $evidenceorid; 4450 if (!is_object($evidence)) { 4451 $evidence = new evidence($evidenceorid); 4452 } 4453 4454 $uc = new user_competency($evidence->get_usercompetencyid()); 4455 if (!evidence::can_delete_user($uc->get_userid())) { 4456 throw new required_capability_exception($uc->get_context(), 'moodle/competency:evidencedelete', 'nopermissions', ''); 4457 } 4458 4459 return $evidence->delete(); 4460 } 4461 4462 /** 4463 * Apply the competency rules from a user competency. 4464 * 4465 * The user competency passed should be one that was recently marked as complete. 4466 * A user competency is considered 'complete' when it's proficiency value is true. 4467 * 4468 * This method will check if the parent of this usercompetency's competency has any 4469 * rules and if so will see if they match. When matched it will take the required 4470 * step to add evidence and trigger completion, etc... 4471 * 4472 * @param user_competency $usercompetency The user competency recently completed. 4473 * @param competency|null $competency The competency of the user competency, useful to avoid unnecessary read. 4474 * @return void 4475 */ 4476 protected static function apply_competency_rules_from_usercompetency(user_competency $usercompetency, 4477 competency $competency = null) { 4478 4479 // Perform some basic checks. 4480 if (!$usercompetency->get_proficiency()) { 4481 throw new coding_exception('The user competency passed is not completed.'); 4482 } 4483 if ($competency === null) { 4484 $competency = $usercompetency->get_competency(); 4485 } 4486 if ($competency->get_id() != $usercompetency->get_competencyid()) { 4487 throw new coding_exception('Mismatch between user competency and competency.'); 4488 } 4489 4490 // Fetch the parent. 4491 $parent = $competency->get_parent(); 4492 if ($parent === null) { 4493 return; 4494 } 4495 4496 // The parent should have a rule, and a meaningful outcome. 4497 $ruleoutcome = $parent->get_ruleoutcome(); 4498 if ($ruleoutcome == competency::OUTCOME_NONE) { 4499 return; 4500 } 4501 $rule = $parent->get_rule_object(); 4502 if ($rule === null) { 4503 return; 4504 } 4505 4506 // Fetch or create the user competency for the parent. 4507 $userid = $usercompetency->get_userid(); 4508 $parentuc = user_competency::get_record(array('userid' => $userid, 'competencyid' => $parent->get_id())); 4509 if (!$parentuc) { 4510 $parentuc = user_competency::create_relation($userid, $parent->get_id()); 4511 $parentuc->create(); 4512 } 4513 4514 // Does the rule match? 4515 if (!$rule->matches($parentuc)) { 4516 return; 4517 } 4518 4519 // Figuring out what to do. 4520 $recommend = false; 4521 if ($ruleoutcome == competency::OUTCOME_EVIDENCE) { 4522 $action = evidence::ACTION_LOG; 4523 4524 } else if ($ruleoutcome == competency::OUTCOME_RECOMMEND) { 4525 $action = evidence::ACTION_LOG; 4526 $recommend = true; 4527 4528 } else if ($ruleoutcome == competency::OUTCOME_COMPLETE) { 4529 $action = evidence::ACTION_COMPLETE; 4530 4531 } else { 4532 throw new moodle_exception('Unexpected rule outcome: ' + $ruleoutcome); 4533 } 4534 4535 // Finally add an evidence. 4536 static::add_evidence( 4537 $userid, 4538 $parent, 4539 $parent->get_context()->id, 4540 $action, 4541 'evidence_competencyrule', 4542 'core_competency', 4543 null, 4544 $recommend 4545 ); 4546 } 4547 4548 /** 4549 * Observe when a course module is marked as completed. 4550 * 4551 * Note that the user being logged in while this happens may be anyone. 4552 * Do not rely on capability checks here! 4553 * 4554 * @param \core\event\course_module_completion_updated $event 4555 * @return void 4556 */ 4557 public static function observe_course_module_completion_updated(\core\event\course_module_completion_updated $event) { 4558 if (!static::is_enabled()) { 4559 return; 4560 } 4561 4562 $eventdata = $event->get_record_snapshot('course_modules_completion', $event->objectid); 4563 4564 if ($eventdata->completionstate == COMPLETION_COMPLETE 4565 || $eventdata->completionstate == COMPLETION_COMPLETE_PASS) { 4566 $coursemodulecompetencies = course_module_competency::list_course_module_competencies($eventdata->coursemoduleid); 4567 4568 $cm = get_coursemodule_from_id(null, $eventdata->coursemoduleid); 4569 $fastmodinfo = get_fast_modinfo($cm->course)->cms[$cm->id]; 4570 4571 $cmname = $fastmodinfo->name; 4572 $url = $fastmodinfo->url; 4573 4574 foreach ($coursemodulecompetencies as $coursemodulecompetency) { 4575 $outcome = $coursemodulecompetency->get_ruleoutcome(); 4576 $action = null; 4577 $recommend = false; 4578 $strdesc = 'evidence_coursemodulecompleted'; 4579 4580 if ($outcome == course_module_competency::OUTCOME_EVIDENCE) { 4581 $action = evidence::ACTION_LOG; 4582 4583 } else if ($outcome == course_module_competency::OUTCOME_RECOMMEND) { 4584 $action = evidence::ACTION_LOG; 4585 $recommend = true; 4586 4587 } else if ($outcome == course_module_competency::OUTCOME_COMPLETE) { 4588 $action = evidence::ACTION_COMPLETE; 4589 4590 } else { 4591 throw new moodle_exception('Unexpected rule outcome: ' + $outcome); 4592 } 4593 4594 static::add_evidence( 4595 $event->relateduserid, 4596 $coursemodulecompetency->get_competencyid(), 4597 $event->contextid, 4598 $action, 4599 $strdesc, 4600 'core_competency', 4601 $cmname, 4602 $recommend, 4603 $url 4604 ); 4605 } 4606 } 4607 } 4608 4609 /** 4610 * Observe when a course is marked as completed. 4611 * 4612 * Note that the user being logged in while this happens may be anyone. 4613 * Do not rely on capability checks here! 4614 * 4615 * @param \core\event\course_completed $event 4616 * @return void 4617 */ 4618 public static function observe_course_completed(\core\event\course_completed $event) { 4619 if (!static::is_enabled()) { 4620 return; 4621 } 4622 4623 $sql = 'courseid = :courseid AND ruleoutcome != :nooutcome'; 4624 $params = array( 4625 'courseid' => $event->courseid, 4626 'nooutcome' => course_competency::OUTCOME_NONE 4627 ); 4628 $coursecompetencies = course_competency::get_records_select($sql, $params); 4629 4630 $course = get_course($event->courseid); 4631 $courseshortname = format_string($course->shortname, null, array('context' => $event->contextid)); 4632 4633 foreach ($coursecompetencies as $coursecompetency) { 4634 4635 $outcome = $coursecompetency->get_ruleoutcome(); 4636 $action = null; 4637 $recommend = false; 4638 $strdesc = 'evidence_coursecompleted'; 4639 4640 if ($outcome == course_competency::OUTCOME_EVIDENCE) { 4641 $action = evidence::ACTION_LOG; 4642 4643 } else if ($outcome == course_competency::OUTCOME_RECOMMEND) { 4644 $action = evidence::ACTION_LOG; 4645 $recommend = true; 4646 4647 } else if ($outcome == course_competency::OUTCOME_COMPLETE) { 4648 $action = evidence::ACTION_COMPLETE; 4649 4650 } else { 4651 throw new moodle_exception('Unexpected rule outcome: ' + $outcome); 4652 } 4653 4654 static::add_evidence( 4655 $event->relateduserid, 4656 $coursecompetency->get_competencyid(), 4657 $event->contextid, 4658 $action, 4659 $strdesc, 4660 'core_competency', 4661 $courseshortname, 4662 $recommend, 4663 $event->get_url() 4664 ); 4665 } 4666 } 4667 4668 /** 4669 * Action to perform when a course module is deleted. 4670 * 4671 * Do not call this directly, this is reserved for core use. 4672 * 4673 * @param stdClass $cm The CM object. 4674 * @return void 4675 */ 4676 public static function hook_course_module_deleted(stdClass $cm) { 4677 global $DB; 4678 $DB->delete_records(course_module_competency::TABLE, array('cmid' => $cm->id)); 4679 } 4680 4681 /** 4682 * Action to perform when a course is deleted. 4683 * 4684 * Do not call this directly, this is reserved for core use. 4685 * 4686 * @param stdClass $course The course object. 4687 * @return void 4688 */ 4689 public static function hook_course_deleted(stdClass $course) { 4690 global $DB; 4691 $DB->delete_records(course_competency::TABLE, array('courseid' => $course->id)); 4692 $DB->delete_records(course_competency_settings::TABLE, array('courseid' => $course->id)); 4693 $DB->delete_records(user_competency_course::TABLE, array('courseid' => $course->id)); 4694 } 4695 4696 /** 4697 * Action to perform when a course is being reset. 4698 * 4699 * Do not call this directly, this is reserved for core use. 4700 * 4701 * @param int $courseid The course ID. 4702 * @return void 4703 */ 4704 public static function hook_course_reset_competency_ratings($courseid) { 4705 global $DB; 4706 $DB->delete_records(user_competency_course::TABLE, array('courseid' => $courseid)); 4707 } 4708 4709 /** 4710 * Action to perform when a cohort is deleted. 4711 * 4712 * Do not call this directly, this is reserved for core use. 4713 * 4714 * @param \stdClass $cohort The cohort object. 4715 * @return void 4716 */ 4717 public static function hook_cohort_deleted(\stdClass $cohort) { 4718 global $DB; 4719 $DB->delete_records(template_cohort::TABLE, array('cohortid' => $cohort->id)); 4720 } 4721 4722 /** 4723 * Manually grade a user competency. 4724 * 4725 * @param int $userid 4726 * @param int $competencyid 4727 * @param int $grade 4728 * @param string $note A note to attach to the evidence 4729 * @return array of \core_competency\user_competency 4730 */ 4731 public static function grade_competency($userid, $competencyid, $grade, $note = null) { 4732 global $USER; 4733 static::require_enabled(); 4734 4735 $uc = static::get_user_competency($userid, $competencyid); 4736 $context = $uc->get_context(); 4737 if (!user_competency::can_grade_user($uc->get_userid())) { 4738 throw new required_capability_exception($context, 'moodle/competency:competencygrade', 'nopermissions', ''); 4739 } 4740 4741 // Throws exception if competency not in plan. 4742 $competency = $uc->get_competency(); 4743 $competencycontext = $competency->get_context(); 4744 if (!has_any_capability(array('moodle/competency:competencyview', 'moodle/competency:competencymanage'), 4745 $competencycontext)) { 4746 throw new required_capability_exception($competencycontext, 'moodle/competency:competencyview', 'nopermissions', ''); 4747 } 4748 4749 $action = evidence::ACTION_OVERRIDE; 4750 $desckey = 'evidence_manualoverride'; 4751 4752 $result = self::add_evidence($uc->get_userid(), 4753 $competency, 4754 $context->id, 4755 $action, 4756 $desckey, 4757 'core_competency', 4758 null, 4759 false, 4760 null, 4761 $grade, 4762 $USER->id, 4763 $note); 4764 if ($result) { 4765 $uc->read(); 4766 $event = \core\event\competency_user_competency_rated::create_from_user_competency($uc); 4767 $event->trigger(); 4768 } 4769 return $result; 4770 } 4771 4772 /** 4773 * Manually grade a user competency from the plans page. 4774 * 4775 * @param mixed $planorid 4776 * @param int $competencyid 4777 * @param int $grade 4778 * @param string $note A note to attach to the evidence 4779 * @return array of \core_competency\user_competency 4780 */ 4781 public static function grade_competency_in_plan($planorid, $competencyid, $grade, $note = null) { 4782 global $USER; 4783 static::require_enabled(); 4784 4785 $plan = $planorid; 4786 if (!is_object($planorid)) { 4787 $plan = new plan($planorid); 4788 } 4789 4790 $context = $plan->get_context(); 4791 if (!user_competency::can_grade_user($plan->get_userid())) { 4792 throw new required_capability_exception($context, 'moodle/competency:competencygrade', 'nopermissions', ''); 4793 } 4794 4795 // Throws exception if competency not in plan. 4796 $competency = $plan->get_competency($competencyid); 4797 $competencycontext = $competency->get_context(); 4798 if (!has_any_capability(array('moodle/competency:competencyview', 'moodle/competency:competencymanage'), 4799 $competencycontext)) { 4800 throw new required_capability_exception($competencycontext, 'moodle/competency:competencyview', 'nopermissions', ''); 4801 } 4802 4803 $action = evidence::ACTION_OVERRIDE; 4804 $desckey = 'evidence_manualoverrideinplan'; 4805 4806 $result = self::add_evidence($plan->get_userid(), 4807 $competency, 4808 $context->id, 4809 $action, 4810 $desckey, 4811 'core_competency', 4812 $plan->get_name(), 4813 false, 4814 null, 4815 $grade, 4816 $USER->id, 4817 $note); 4818 if ($result) { 4819 $uc = static::get_user_competency($plan->get_userid(), $competency->get_id()); 4820 $event = \core\event\competency_user_competency_rated_in_plan::create_from_user_competency($uc, $plan->get_id()); 4821 $event->trigger(); 4822 } 4823 return $result; 4824 } 4825 4826 /** 4827 * Manually grade a user course competency from the course page. 4828 * 4829 * This may push the rating to the user competency 4830 * if the course is configured this way. 4831 * 4832 * @param mixed $courseorid 4833 * @param int $userid 4834 * @param int $competencyid 4835 * @param int $grade 4836 * @param string $note A note to attach to the evidence 4837 * @return array of \core_competency\user_competency 4838 */ 4839 public static function grade_competency_in_course($courseorid, $userid, $competencyid, $grade, $note = null) { 4840 global $USER, $DB; 4841 static::require_enabled(); 4842 4843 $course = $courseorid; 4844 if (!is_object($courseorid)) { 4845 $course = $DB->get_record('course', array('id' => $courseorid)); 4846 } 4847 $context = context_course::instance($course->id); 4848 4849 // Check that we can view the user competency details in the course. 4850 if (!user_competency::can_read_user_in_course($userid, $course->id)) { 4851 throw new required_capability_exception($context, 'moodle/competency:usercompetencyview', 'nopermissions', ''); 4852 } 4853 4854 // Validate the permission to grade. 4855 if (!user_competency::can_grade_user_in_course($userid, $course->id)) { 4856 throw new required_capability_exception($context, 'moodle/competency:competencygrade', 'nopermissions', ''); 4857 } 4858 4859 // Check that competency is in course and visible to the current user. 4860 $competency = course_competency::get_competency($course->id, $competencyid); 4861 $competencycontext = $competency->get_context(); 4862 if (!has_any_capability(array('moodle/competency:competencyview', 'moodle/competency:competencymanage'), 4863 $competencycontext)) { 4864 throw new required_capability_exception($competencycontext, 'moodle/competency:competencyview', 'nopermissions', ''); 4865 } 4866 4867 // Check that the user is enrolled in the course, and is "gradable". 4868 if (!is_enrolled($context, $userid, 'moodle/competency:coursecompetencygradable')) { 4869 throw new coding_exception('The competency may not be rated at this time.'); 4870 } 4871 4872 $action = evidence::ACTION_OVERRIDE; 4873 $desckey = 'evidence_manualoverrideincourse'; 4874 4875 $result = self::add_evidence($userid, 4876 $competency, 4877 $context->id, 4878 $action, 4879 $desckey, 4880 'core_competency', 4881 $context->get_context_name(), 4882 false, 4883 null, 4884 $grade, 4885 $USER->id, 4886 $note); 4887 if ($result) { 4888 $all = user_competency_course::get_multiple($userid, $course->id, array($competency->get_id())); 4889 $uc = reset($all); 4890 $event = \core\event\competency_user_competency_rated_in_course::create_from_user_competency_course($uc); 4891 $event->trigger(); 4892 } 4893 return $result; 4894 } 4895 4896 /** 4897 * Count the plans in the template, filtered by status. 4898 * 4899 * Requires moodle/competency:templateview capability at the system context. 4900 * 4901 * @param mixed $templateorid The id or the template. 4902 * @param int $status One of the plan status constants (or 0 for all plans). 4903 * @return int 4904 */ 4905 public static function count_plans_for_template($templateorid, $status = 0) { 4906 static::require_enabled(); 4907 $template = $templateorid; 4908 if (!is_object($template)) { 4909 $template = new template($template); 4910 } 4911 4912 // First we do a permissions check. 4913 if (!$template->can_read()) { 4914 throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview', 4915 'nopermissions', ''); 4916 } 4917 4918 return plan::count_records_for_template($template->get_id(), $status); 4919 } 4920 4921 /** 4922 * Count the user-completency-plans in the template, optionally filtered by proficiency. 4923 * 4924 * Requires moodle/competency:templateview capability at the system context. 4925 * 4926 * @param mixed $templateorid The id or the template. 4927 * @param mixed $proficiency If true, filter by proficiency, if false filter by not proficient, if null - no filter. 4928 * @return int 4929 */ 4930 public static function count_user_competency_plans_for_template($templateorid, $proficiency = null) { 4931 static::require_enabled(); 4932 $template = $templateorid; 4933 if (!is_object($template)) { 4934 $template = new template($template); 4935 } 4936 4937 // First we do a permissions check. 4938 if (!$template->can_read()) { 4939 throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview', 4940 'nopermissions', ''); 4941 } 4942 4943 return user_competency_plan::count_records_for_template($template->get_id(), $proficiency); 4944 } 4945 4946 /** 4947 * List the plans in the template, filtered by status. 4948 * 4949 * Requires moodle/competency:templateview capability at the system context. 4950 * 4951 * @param mixed $templateorid The id or the template. 4952 * @param int $status One of the plan status constants (or 0 for all plans). 4953 * @param int $skip The number of records to skip 4954 * @param int $limit The max number of records to return 4955 * @return plan[] 4956 */ 4957 public static function list_plans_for_template($templateorid, $status = 0, $skip = 0, $limit = 100) { 4958 $template = $templateorid; 4959 if (!is_object($template)) { 4960 $template = new template($template); 4961 } 4962 4963 // First we do a permissions check. 4964 if (!$template->can_read()) { 4965 throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview', 4966 'nopermissions', ''); 4967 } 4968 4969 return plan::get_records_for_template($template->get_id(), $status, $skip, $limit); 4970 } 4971 4972 /** 4973 * Get the most often not completed competency for this course. 4974 * 4975 * Requires moodle/competency:coursecompetencyview capability at the course context. 4976 * 4977 * @param int $courseid The course id 4978 * @param int $skip The number of records to skip 4979 * @param int $limit The max number of records to return 4980 * @return competency[] 4981 */ 4982 public static function get_least_proficient_competencies_for_course($courseid, $skip = 0, $limit = 100) { 4983 static::require_enabled(); 4984 $coursecontext = context_course::instance($courseid); 4985 4986 if (!has_any_capability(array('moodle/competency:competencyview', 'moodle/competency:competencymanage'), $coursecontext)) { 4987 throw new required_capability_exception($coursecontext, 'moodle/competency:competencyview', 'nopermissions', ''); 4988 } 4989 4990 return user_competency_course::get_least_proficient_competencies_for_course($courseid, $skip, $limit); 4991 } 4992 4993 /** 4994 * Get the most often not completed competency for this template. 4995 * 4996 * Requires moodle/competency:templateview capability at the system context. 4997 * 4998 * @param mixed $templateorid The id or the template. 4999 * @param int $skip The number of records to skip 5000 * @param int $limit The max number of records to return 5001 * @return competency[] 5002 */ 5003 public static function get_least_proficient_competencies_for_template($templateorid, $skip = 0, $limit = 100) { 5004 static::require_enabled(); 5005 $template = $templateorid; 5006 if (!is_object($template)) { 5007 $template = new template($template); 5008 } 5009 5010 // First we do a permissions check. 5011 if (!$template->can_read()) { 5012 throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview', 5013 'nopermissions', ''); 5014 } 5015 5016 return user_competency_plan::get_least_proficient_competencies_for_template($template->get_id(), $skip, $limit); 5017 } 5018 5019 /** 5020 * Template event viewed. 5021 * 5022 * Requires moodle/competency:templateview capability at the system context. 5023 * 5024 * @param mixed $templateorid The id or the template. 5025 * @return boolean 5026 */ 5027 public static function template_viewed($templateorid) { 5028 static::require_enabled(); 5029 $template = $templateorid; 5030 if (!is_object($template)) { 5031 $template = new template($template); 5032 } 5033 5034 // First we do a permissions check. 5035 if (!$template->can_read()) { 5036 throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview', 5037 'nopermissions', ''); 5038 } 5039 5040 // Trigger a template viewed event. 5041 \core\event\competency_template_viewed::create_from_template($template)->trigger(); 5042 5043 return true; 5044 } 5045 5046 /** 5047 * Get the competency settings for a course. 5048 * 5049 * Requires moodle/competency:coursecompetencyview capability at the course context. 5050 * 5051 * @param int $courseid The course id 5052 * @return course_competency_settings 5053 */ 5054 public static function read_course_competency_settings($courseid) { 5055 static::require_enabled(); 5056 5057 // First we do a permissions check. 5058 if (!course_competency_settings::can_read($courseid)) { 5059 $context = context_course::instance($courseid); 5060 throw new required_capability_exception($context, 'moodle/competency:coursecompetencyview', 'nopermissions', ''); 5061 } 5062 5063 return course_competency_settings::get_by_courseid($courseid); 5064 } 5065 5066 /** 5067 * Update the competency settings for a course. 5068 * 5069 * Requires moodle/competency:coursecompetencyconfigure capability at the course context. 5070 * 5071 * @param int $courseid The course id 5072 * @param stdClass $settings List of settings. The only valid setting ATM is pushratginstouserplans (boolean). 5073 * @return bool 5074 */ 5075 public static function update_course_competency_settings($courseid, $settings) { 5076 static::require_enabled(); 5077 5078 $settings = (object) $settings; 5079 5080 // Get all the valid settings. 5081 $pushratingstouserplans = isset($settings->pushratingstouserplans) ? $settings->pushratingstouserplans : false; 5082 5083 // First we do a permissions check. 5084 if (!course_competency_settings::can_manage_course($courseid)) { 5085 $context = context_course::instance($courseid); 5086 throw new required_capability_exception($context, 'moodle/competency:coursecompetencyconfigure', 'nopermissions', ''); 5087 } 5088 5089 $exists = course_competency_settings::get_record(array('courseid' => $courseid)); 5090 5091 // Now update or insert. 5092 if ($exists) { 5093 $settings = $exists; 5094 $settings->set_pushratingstouserplans($pushratingstouserplans); 5095 return $settings->update(); 5096 } else { 5097 $data = (object) array('courseid' => $courseid, 'pushratingstouserplans' => $pushratingstouserplans); 5098 $settings = new course_competency_settings(0, $data); 5099 $result = $settings->create(); 5100 return !empty($result); 5101 } 5102 } 5103 5104 5105 /** 5106 * Function used to return a list of users where the given user has a particular capability. 5107 * 5108 * This is used e.g. to find all the users where someone is able to manage their learning plans, 5109 * it also would be useful for mentees etc. 5110 * 5111 * @param string $capability - The capability string we are filtering for. If '' is passed, 5112 * an always matching filter is returned. 5113 * @param int $userid - The user id we are using for the access checks. Defaults to current user. 5114 * @param int $type - The type of named params to return (passed to $DB->get_in_or_equal). 5115 * @param string $prefix - The type prefix for the db table (passed to $DB->get_in_or_equal). 5116 * @return list($sql, $params) Same as $DB->get_in_or_equal(). 5117 * @todo MDL-52243 Move this function to lib/accesslib.php 5118 */ 5119 public static function filter_users_with_capability_on_user_context_sql($capability, $userid = 0, $type = SQL_PARAMS_QM, 5120 $prefix='param') { 5121 5122 global $USER, $DB; 5123 $allresultsfilter = array('> 0', array()); 5124 $noresultsfilter = array('= -1', array()); 5125 5126 if (empty($capability)) { 5127 return $allresultsfilter; 5128 } 5129 5130 if (!$capinfo = get_capability_info($capability)) { 5131 throw new coding_exception('Capability does not exist: ' . $capability); 5132 } 5133 5134 if (empty($userid)) { 5135 $userid = $USER->id; 5136 } 5137 5138 // Make sure the guest account and not-logged-in users never get any risky caps no matter what the actual settings are. 5139 if (($capinfo->captype === 'write') or ($capinfo->riskbitmask & (RISK_XSS | RISK_CONFIG | RISK_DATALOSS))) { 5140 if (isguestuser($userid) or $userid == 0) { 5141 return $noresultsfilter; 5142 } 5143 } 5144 5145 if (is_siteadmin($userid)) { 5146 // No filtering for site admins. 5147 return $allresultsfilter; 5148 } 5149 5150 // Check capability on system level. 5151 $syscontext = context_system::instance(); 5152 $hassystem = has_capability($capability, $syscontext, $userid); 5153 5154 $access = get_user_access_sitewide($userid); 5155 // Build up a list of level 2 contexts (candidates to be user context). 5156 $filtercontexts = array(); 5157 foreach ($access['ra'] as $path => $role) { 5158 $parts = explode('/', $path); 5159 if (count($parts) == 3) { 5160 $filtercontexts[$parts[2]] = $parts[2]; 5161 } else if (count($parts) > 3) { 5162 // We know this is not a user context because there is another path with more than 2 levels. 5163 unset($filtercontexts[$parts[2]]); 5164 } 5165 } 5166 5167 // Add all contexts in which a role may be overidden. 5168 foreach ($access['rdef'] as $pathandroleid => $def) { 5169 $matches = array(); 5170 if (!isset($def[$capability])) { 5171 // The capability is not mentioned, we can ignore. 5172 continue; 5173 } 5174 5175 list($contextpath, $roleid) = explode(':', $pathandroleid, 2); 5176 $parts = explode('/', $contextpath); 5177 if (count($parts) != 3) { 5178 // Only get potential user contexts, they only ever have 2 slashes /parentId/Id. 5179 continue; 5180 } 5181 5182 $filtercontexts[$parts[2]] = $parts[2]; 5183 } 5184 5185 // No interesting contexts - return all or no results. 5186 if (empty($filtercontexts)) { 5187 if ($hassystem) { 5188 return $allresultsfilter; 5189 } else { 5190 return $noresultsfilter; 5191 } 5192 } 5193 // Fetch all interesting contexts for further examination. 5194 list($insql, $params) = $DB->get_in_or_equal($filtercontexts, SQL_PARAMS_NAMED); 5195 $params['level'] = CONTEXT_USER; 5196 $fields = context_helper::get_preload_record_columns_sql('ctx'); 5197 $interestingcontexts = $DB->get_recordset_sql('SELECT ' . $fields . ' 5198 FROM {context} ctx 5199 WHERE ctx.contextlevel = :level 5200 AND ctx.id ' . $insql . ' 5201 ORDER BY ctx.id', $params); 5202 if ($hassystem) { 5203 // If allowed at system, search for exceptions prohibiting the capability at user context. 5204 $excludeusers = array(); 5205 foreach ($interestingcontexts as $contextrecord) { 5206 $candidateuserid = $contextrecord->ctxinstance; 5207 context_helper::preload_from_record($contextrecord); 5208 $usercontext = context_user::instance($candidateuserid); 5209 // Has capability should use the data already preloaded. 5210 if (!has_capability($capability, $usercontext, $userid)) { 5211 $excludeusers[$candidateuserid] = $candidateuserid; 5212 } 5213 } 5214 5215 // Construct SQL excluding users with this role assigned for this user. 5216 if (empty($excludeusers)) { 5217 $interestingcontexts->close(); 5218 return $allresultsfilter; 5219 } 5220 list($sql, $params) = $DB->get_in_or_equal($excludeusers, $type, $prefix, false); 5221 } else { 5222 // If not allowed at system, search for exceptions allowing the capability at user context. 5223 $allowusers = array(); 5224 foreach ($interestingcontexts as $contextrecord) { 5225 $candidateuserid = $contextrecord->ctxinstance; 5226 context_helper::preload_from_record($contextrecord); 5227 $usercontext = context_user::instance($candidateuserid); 5228 // Has capability should use the data already preloaded. 5229 if (has_capability($capability, $usercontext, $userid)) { 5230 $allowusers[$candidateuserid] = $candidateuserid; 5231 } 5232 } 5233 5234 // Construct SQL excluding users with this role assigned for this user. 5235 if (empty($allowusers)) { 5236 $interestingcontexts->close(); 5237 return $noresultsfilter; 5238 } 5239 list($sql, $params) = $DB->get_in_or_equal($allowusers, $type, $prefix); 5240 } 5241 $interestingcontexts->close(); 5242 5243 // Return the goods!. 5244 return array($sql, $params); 5245 } 5246 5247 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |