See Release Notes
Long Term Support Release
Differences Between: [Versions 401 and 402] [Versions 401 and 403]
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 competencies 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 coding_exception; 28 use context_system; 29 use lang_string; 30 use stdClass; 31 32 require_once($CFG->libdir . '/grade/grade_scale.php'); 33 34 /** 35 * Class for loading/storing competencies from the DB. 36 * 37 * @copyright 2015 Damyon Wiese 38 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 39 */ 40 class competency extends persistent { 41 42 const TABLE = 'competency'; 43 44 /** Outcome none. */ 45 const OUTCOME_NONE = 0; 46 /** Outcome evidence. */ 47 const OUTCOME_EVIDENCE = 1; 48 /** Outcome complete. */ 49 const OUTCOME_COMPLETE = 2; 50 /** Outcome recommend. */ 51 const OUTCOME_RECOMMEND = 3; 52 53 /** @var competency Object before update. */ 54 protected $beforeupdate = null; 55 56 /** 57 * Return the definition of the properties of this model. 58 * 59 * @return array 60 */ 61 protected static function define_properties() { 62 return array( 63 'shortname' => array( 64 'type' => PARAM_TEXT 65 ), 66 'idnumber' => array( 67 'type' => PARAM_RAW 68 ), 69 'description' => array( 70 'default' => '', 71 'type' => PARAM_CLEANHTML 72 ), 73 'descriptionformat' => array( 74 'choices' => array(FORMAT_HTML, FORMAT_MOODLE, FORMAT_PLAIN, FORMAT_MARKDOWN), 75 'type' => PARAM_INT, 76 'default' => FORMAT_HTML 77 ), 78 'sortorder' => array( 79 'default' => 0, 80 'type' => PARAM_INT 81 ), 82 'parentid' => array( 83 'default' => 0, 84 'type' => PARAM_INT 85 ), 86 'path' => array( 87 'default' => '/0/', 88 'type' => PARAM_RAW 89 ), 90 'ruleoutcome' => array( 91 'choices' => array(self::OUTCOME_NONE, self::OUTCOME_EVIDENCE, self::OUTCOME_COMPLETE, self::OUTCOME_RECOMMEND), 92 'default' => self::OUTCOME_NONE, 93 'type' => PARAM_INT 94 ), 95 'ruletype' => array( 96 'type' => PARAM_RAW, 97 'default' => null, 98 'null' => NULL_ALLOWED 99 ), 100 'ruleconfig' => array( 101 'default' => null, 102 'type' => PARAM_RAW, 103 'null' => NULL_ALLOWED 104 ), 105 'scaleid' => array( 106 'default' => null, 107 'type' => PARAM_INT, 108 'null' => NULL_ALLOWED 109 ), 110 'scaleconfiguration' => array( 111 'default' => null, 112 'type' => PARAM_RAW, 113 'null' => NULL_ALLOWED 114 ), 115 'competencyframeworkid' => array( 116 'default' => 0, 117 'type' => PARAM_INT 118 ), 119 ); 120 } 121 122 /** 123 * Hook to execute before validate. 124 * 125 * @return void 126 */ 127 protected function before_validate() { 128 $this->beforeupdate = null; 129 $this->newparent = null; 130 131 // During update. 132 if ($this->get('id')) { 133 $this->beforeupdate = new competency($this->get('id')); 134 135 // The parent ID has changed. 136 if ($this->beforeupdate->get('parentid') != $this->get('parentid')) { 137 $this->newparent = $this->get_parent(); 138 139 // Update path and sortorder. 140 $this->set_new_path($this->newparent); 141 $this->set_new_sortorder(); 142 } 143 144 } else { 145 // During create. 146 147 $this->set_new_path(); 148 // Always generate new sortorder when we create new competency. 149 $this->set_new_sortorder(); 150 151 } 152 } 153 154 /** 155 * Hook to execute after an update. 156 * 157 * @param bool $result Whether or not the update was successful. 158 * @return void 159 */ 160 protected function after_update($result) { 161 global $DB; 162 163 if (!$result) { 164 $this->beforeupdate = null; 165 return; 166 } 167 168 // The parent ID has changed, we need to fix all the paths of the children. 169 if ($this->beforeupdate->get('parentid') != $this->get('parentid')) { 170 $beforepath = $this->beforeupdate->get('path') . $this->get('id') . '/'; 171 172 $like = $DB->sql_like('path', '?'); 173 $likesearch = $DB->sql_like_escape($beforepath) . '%'; 174 175 $table = '{' . self::TABLE . '}'; 176 $sql = "UPDATE $table SET path = REPLACE(path, ?, ?) WHERE " . $like; 177 $DB->execute($sql, array( 178 $beforepath, 179 $this->get('path') . $this->get('id') . '/', 180 $likesearch 181 )); 182 183 // Resolving sortorder holes left after changing parent. 184 $table = '{' . self::TABLE . '}'; 185 $sql = "UPDATE $table SET sortorder = sortorder -1 " 186 . " WHERE competencyframeworkid = ? AND parentid = ? AND sortorder > ?"; 187 $DB->execute($sql, array($this->get('competencyframeworkid'), 188 $this->beforeupdate->get('parentid'), 189 $this->beforeupdate->get('sortorder') 190 )); 191 } 192 193 $this->beforeupdate = null; 194 } 195 196 197 /** 198 * Hook to execute after a delete. 199 * 200 * @param bool $result Whether or not the delete was successful. 201 * @return void 202 */ 203 protected function after_delete($result) { 204 global $DB; 205 if (!$result) { 206 return; 207 } 208 209 // Resolving sortorder holes left after delete. 210 $table = '{' . self::TABLE . '}'; 211 $sql = "UPDATE $table SET sortorder = sortorder -1 WHERE competencyframeworkid = ? AND parentid = ? AND sortorder > ?"; 212 $DB->execute($sql, array($this->get('competencyframeworkid'), $this->get('parentid'), $this->get('sortorder'))); 213 } 214 215 /** 216 * Extracts the default grade from the scale configuration. 217 * 218 * Returns an array where the first element is the grade, and the second 219 * is a boolean representing whether or not this grade is considered 'proficient'. 220 * 221 * @return array(int grade, bool proficient) 222 */ 223 public function get_default_grade() { 224 $scaleid = $this->get('scaleid'); 225 $scaleconfig = $this->get('scaleconfiguration'); 226 if ($scaleid === null) { 227 $scaleconfig = $this->get_framework()->get('scaleconfiguration'); 228 } 229 return competency_framework::get_default_grade_from_scale_configuration($scaleconfig); 230 } 231 232 /** 233 * Get the competency framework. 234 * 235 * @return competency_framework 236 */ 237 public function get_framework() { 238 return new competency_framework($this->get('competencyframeworkid')); 239 } 240 241 /** 242 * Get the competency level. 243 * 244 * @return int 245 */ 246 public function get_level() { 247 $path = $this->get('path'); 248 $path = trim($path, '/'); 249 return substr_count($path, '/') + 1; 250 } 251 252 /** 253 * Return the parent competency. 254 * 255 * @return null|competency 256 */ 257 public function get_parent() { 258 $parentid = $this->get('parentid'); 259 if (!$parentid) { 260 return null; 261 } 262 return new competency($parentid); 263 } 264 265 /** 266 * Extracts the proficiency of a grade from the scale configuration. 267 * 268 * @param int $grade The grade (scale item ID). 269 * @return array(int grade, bool proficient) 270 */ 271 public function get_proficiency_of_grade($grade) { 272 $scaleid = $this->get('scaleid'); 273 $scaleconfig = $this->get('scaleconfiguration'); 274 if ($scaleid === null) { 275 $scaleconfig = $this->get_framework()->get('scaleconfiguration'); 276 } 277 return competency_framework::get_proficiency_of_grade_from_scale_configuration($scaleconfig, $grade); 278 } 279 280 /** 281 * Return the related competencies. 282 * 283 * @return competency[] 284 */ 285 public function get_related_competencies() { 286 return related_competency::get_related_competencies($this->get('id')); 287 } 288 289 /** 290 * Get the rule object. 291 * 292 * @return null|competency_rule 293 */ 294 public function get_rule_object() { 295 $rule = $this->get('ruletype'); 296 297 if (!$rule || !is_subclass_of($rule, 'core_competency\\competency_rule')) { 298 // Double check that the rule is extending the right class to avoid bad surprises. 299 return null; 300 } 301 302 return new $rule($this); 303 } 304 305 /** 306 * Return the scale. 307 * 308 * @return \grade_scale 309 */ 310 public function get_scale() { 311 $scaleid = $this->get('scaleid'); 312 if ($scaleid === null) { 313 return $this->get_framework()->get_scale(); 314 } 315 $scale = \grade_scale::fetch(array('id' => $scaleid)); 316 $scale->load_items(); 317 return $scale; 318 } 319 320 /** 321 * Returns true when the competency has user competencies. 322 * 323 * This is useful to determine if the competency, or part of it, should be locked down. 324 * 325 * @return boolean 326 */ 327 public function has_user_competencies() { 328 return user_competency::has_records_for_competency($this->get('id')) || 329 user_competency_plan::has_records_for_competency($this->get('id')); 330 } 331 332 /** 333 * Check if the competency is the parent of passed competencies. 334 * 335 * @param array $ids IDs of supposedly direct children. 336 * @return boolean 337 */ 338 public function is_parent_of(array $ids) { 339 global $DB; 340 341 list($insql, $params) = $DB->get_in_or_equal($ids, SQL_PARAMS_NAMED); 342 $params['parentid'] = $this->get('id'); 343 344 return $DB->count_records_select(self::TABLE, "id $insql AND parentid = :parentid", $params) == count($ids); 345 } 346 347 /** 348 * Reset the rule. 349 * 350 * @return void 351 */ 352 public function reset_rule() { 353 $this->raw_set('ruleoutcome', static::OUTCOME_NONE); 354 $this->raw_set('ruletype', null); 355 $this->raw_set('ruleconfig', null); 356 } 357 358 /** 359 * Helper method to set the path. 360 * 361 * @param competency $parent The parent competency object. 362 * @return void 363 */ 364 protected function set_new_path(competency $parent = null) { 365 $path = '/0/'; 366 if ($this->get('parentid')) { 367 $parent = $parent !== null ? $parent : $this->get_parent(); 368 $path = $parent->get('path') . $this->get('parentid') . '/'; 369 } 370 $this->raw_set('path', $path); 371 } 372 373 /** 374 * Helper method to set the sortorder. 375 * 376 * @return void 377 */ 378 protected function set_new_sortorder() { 379 $search = array('parentid' => $this->get('parentid'), 'competencyframeworkid' => $this->get('competencyframeworkid')); 380 $this->raw_set('sortorder', $this->count_records($search)); 381 } 382 383 /** 384 * This does a specialised search that finds all nodes in the tree with matching text on any text like field, 385 * and returns this node and all its parents in a displayable sort order. 386 * 387 * @param string $searchtext The text to search for. 388 * @param int $competencyframeworkid The competency framework to limit the search. 389 * @return persistent[] 390 */ 391 public static function search($searchtext, $competencyframeworkid) { 392 global $DB; 393 394 $like1 = $DB->sql_like('shortname', ':like1', false); 395 $like2 = $DB->sql_like('idnumber', ':like2', false); 396 $like3 = $DB->sql_like('description', ':like3', false); 397 398 $params = array( 399 'like1' => '%' . $DB->sql_like_escape($searchtext) . '%', 400 'like2' => '%' . $DB->sql_like_escape($searchtext) . '%', 401 'like3' => '%' . $DB->sql_like_escape($searchtext) . '%', 402 'frameworkid' => $competencyframeworkid 403 ); 404 405 $sql = 'competencyframeworkid = :frameworkid AND ((' . $like1 . ') OR (' . $like2 . ') OR (' . $like3 . '))'; 406 $records = $DB->get_records_select(self::TABLE, $sql, $params, 'path, sortorder ASC', '*'); 407 408 // Now get all the parents. 409 $parents = array(); 410 foreach ($records as $record) { 411 $split = explode('/', trim($record->path, '/')); 412 foreach ($split as $parent) { 413 $parents[intval($parent)] = true; 414 } 415 } 416 $parents = array_keys($parents); 417 418 // Skip ones we already fetched. 419 foreach ($parents as $idx => $parent) { 420 if ($parent == 0 || isset($records[$parent])) { 421 unset($parents[$idx]); 422 } 423 } 424 425 if (count($parents)) { 426 list($parentsql, $parentparams) = $DB->get_in_or_equal($parents, SQL_PARAMS_NAMED); 427 428 $parentrecords = $DB->get_records_select(self::TABLE, 'id ' . $parentsql, 429 $parentparams, 'path, sortorder ASC', '*'); 430 431 foreach ($parentrecords as $id => $record) { 432 $records[$id] = $record; 433 } 434 } 435 436 $instances = array(); 437 // Convert to instances of this class. 438 foreach ($records as $record) { 439 $newrecord = new static(0, $record); 440 $instances[$newrecord->get('id')] = $newrecord; 441 } 442 return $instances; 443 } 444 445 /** 446 * Validate the competency framework ID. 447 * 448 * @param int $value The framework ID. 449 * @return true|lang_string 450 */ 451 protected function validate_competencyframeworkid($value) { 452 453 // During update. 454 if ($this->get('id')) { 455 456 // Ensure that we are not trying to move the competency across frameworks. 457 if ($this->beforeupdate->get('competencyframeworkid') != $value) { 458 return new lang_string('invaliddata', 'error'); 459 } 460 461 } else { 462 // During create. 463 464 // Check that the framework exists. 465 if (!competency_framework::record_exists($value)) { 466 return new lang_string('invaliddata', 'error'); 467 } 468 } 469 470 return true; 471 } 472 473 /** 474 * Validate the ID number. 475 * 476 * @param string $value The ID number. 477 * @return true|lang_string 478 */ 479 protected function validate_idnumber($value) { 480 global $DB; 481 $sql = 'idnumber = :idnumber AND competencyframeworkid = :competencyframeworkid AND id <> :id'; 482 $params = array( 483 'id' => $this->get('id'), 484 'idnumber' => $value, 485 'competencyframeworkid' => $this->get('competencyframeworkid') 486 ); 487 if ($DB->record_exists_select(self::TABLE, $sql, $params)) { 488 return new lang_string('idnumbertaken', 'error'); 489 } 490 return true; 491 } 492 493 /** 494 * Validate the path. 495 * 496 * @param string $value The path. 497 * @return true|lang_string 498 */ 499 protected function validate_path($value) { 500 501 // The last item should be the parent ID. 502 $id = $this->get('parentid'); 503 if (substr($value, -(strlen($id) + 2)) != '/' . $id . '/') { 504 return new lang_string('invaliddata', 'error'); 505 506 } else if (!preg_match('@/([0-9]+/)+@', $value)) { 507 // The format of the path is not correct. 508 return new lang_string('invaliddata', 'error'); 509 } 510 511 return true; 512 } 513 514 /** 515 * Validate the parent ID. 516 * 517 * @param string $value The ID. 518 * @return true|lang_string 519 */ 520 protected function validate_parentid($value) { 521 522 // Check that the parent exists. But only if we don't have it already, and we actually have a parent. 523 if (!empty($value) && !$this->newparent && !self::record_exists($value)) { 524 return new lang_string('invaliddata', 'error'); 525 } 526 527 // During update. 528 if ($this->get('id')) { 529 530 // If there is a new parent. 531 if ($this->beforeupdate->get('parentid') != $value && $this->newparent) { 532 533 // Check that the new parent belongs to the same framework. 534 if ($this->newparent->get('competencyframeworkid') != $this->get('competencyframeworkid')) { 535 return new lang_string('invaliddata', 'error'); 536 } 537 } 538 } 539 540 return true; 541 } 542 543 /** 544 * Validate the rule. 545 * 546 * @param string $value The ID. 547 * @return true|lang_string 548 */ 549 protected function validate_ruletype($value) { 550 if ($value === null) { 551 return true; 552 } 553 554 if (!class_exists($value) || !is_subclass_of($value, 'core_competency\\competency_rule')) { 555 return new lang_string('invaliddata', 'error'); 556 } 557 558 return true; 559 } 560 561 /** 562 * Validate the rule config. 563 * 564 * @param string $value The ID. 565 * @return true|lang_string 566 */ 567 protected function validate_ruleconfig($value) { 568 $rule = $this->get_rule_object(); 569 570 // We don't have a rule. 571 if (empty($rule)) { 572 if ($value === null) { 573 // No config, perfect. 574 return true; 575 } 576 // Config but no rules, whoops! 577 return new lang_string('invaliddata', 'error'); 578 } 579 580 $valid = $rule->validate_config($value); 581 if ($valid !== true) { 582 // Whoops! 583 return new lang_string('invaliddata', 'error'); 584 } 585 586 return true; 587 } 588 589 /** 590 * Validate the scale ID. 591 * 592 * Note that the value for a scale can never be 0, null has to be used when 593 * the framework's scale has to be used. 594 * 595 * @param int $value 596 * @return true|lang_string 597 */ 598 protected function validate_scaleid($value) { 599 global $DB; 600 601 if ($value === null) { 602 return true; 603 } 604 605 // Always validate that the scale exists. 606 if (!$DB->record_exists_select('scale', 'id = :id', array('id' => $value))) { 607 return new lang_string('invalidscaleid', 'error'); 608 } 609 610 // During update. 611 if ($this->get('id')) { 612 613 // Validate that we can only change the scale when it is not used yet. 614 if ($this->beforeupdate->get('scaleid') != $value) { 615 if ($this->has_user_competencies()) { 616 return new lang_string('errorscalealreadyused', 'core_competency'); 617 } 618 } 619 620 } 621 622 return true; 623 } 624 625 /** 626 * Validate the scale configuration. 627 * 628 * This logic is adapted from {@link \core_competency\competency_framework::validate_scaleconfiguration()}. 629 * 630 * @param string $value The scale configuration. 631 * @return bool|lang_string 632 */ 633 protected function validate_scaleconfiguration($value) { 634 $scaleid = $this->get('scaleid'); 635 if ($scaleid === null && $value === null) { 636 return true; 637 } 638 639 $scaledefaultselected = false; 640 $proficientselected = false; 641 $scaleconfigurations = json_decode($value); 642 643 if (is_array($scaleconfigurations)) { 644 645 // The first element of the array contains the scale ID. 646 $scaleinfo = array_shift($scaleconfigurations); 647 if (empty($scaleinfo) || !isset($scaleinfo->scaleid) || $scaleinfo->scaleid != $scaleid) { 648 // This should never happen. 649 return new lang_string('errorscaleconfiguration', 'core_competency'); 650 } 651 652 // Walk through the array to find proficient and default values. 653 foreach ($scaleconfigurations as $scaleconfiguration) { 654 if (isset($scaleconfiguration->scaledefault) && $scaleconfiguration->scaledefault) { 655 $scaledefaultselected = true; 656 } 657 if (isset($scaleconfiguration->proficient) && $scaleconfiguration->proficient) { 658 $proficientselected = true; 659 } 660 } 661 } 662 663 if (!$scaledefaultselected || !$proficientselected) { 664 return new lang_string('errorscaleconfiguration', 'core_competency'); 665 } 666 667 return true; 668 } 669 670 /** 671 * Return whether or not the competency IDs share the same framework. 672 * 673 * @param array $ids Competency IDs 674 * @return bool 675 */ 676 public static function share_same_framework(array $ids) { 677 global $DB; 678 list($insql, $params) = $DB->get_in_or_equal($ids); 679 $sql = "SELECT COUNT('x') FROM (SELECT DISTINCT(competencyframeworkid) FROM {" . self::TABLE . "} WHERE id {$insql}) f"; 680 return $DB->count_records_sql($sql, $params) == 1; 681 } 682 683 /** 684 * Get the available rules. 685 * 686 * @return array Keys are the class names, values are the name of the rule. 687 */ 688 public static function get_available_rules() { 689 // Fully qualified class names without leading slashes because get_class() does not add them either. 690 $rules = array( 691 'core_competency\\competency_rule_all' => competency_rule_all::get_name(), 692 'core_competency\\competency_rule_points' => competency_rule_points::get_name(), 693 ); 694 return $rules; 695 } 696 697 /** 698 * Return the current depth of a competency framework. 699 * 700 * @param int $frameworkid The framework ID. 701 * @return int 702 */ 703 public static function get_framework_depth($frameworkid) { 704 global $DB; 705 $totallength = $DB->sql_length('path'); 706 $trimmedlength = $DB->sql_length("REPLACE(path, '/', '')"); 707 $sql = "SELECT ($totallength - $trimmedlength - 1) AS depth 708 FROM {" . self::TABLE . "} 709 WHERE competencyframeworkid = :id 710 ORDER BY depth DESC"; 711 $record = $DB->get_record_sql($sql, array('id' => $frameworkid), IGNORE_MULTIPLE); 712 if (!$record) { 713 $depth = 0; 714 } else { 715 $depth = $record->depth; 716 } 717 return $depth; 718 } 719 720 /** 721 * Build a framework tree with competency nodes. 722 * 723 * @param int $frameworkid the framework id 724 * @return node[] tree of framework competency nodes 725 */ 726 public static function get_framework_tree($frameworkid) { 727 $competencies = self::search('', $frameworkid); 728 return self::build_tree($competencies, 0); 729 } 730 731 /** 732 * Get the context from the framework. 733 * 734 * @return context 735 */ 736 public function get_context() { 737 return $this->get_framework()->get_context(); 738 } 739 740 /** 741 * Recursively build up the tree of nodes. 742 * 743 * @param array $all - List of all competency classes. 744 * @param int $parentid - The current parent ID. Pass 0 to build the tree from the top. 745 * @return node[] $tree tree of nodes 746 */ 747 protected static function build_tree($all, $parentid) { 748 $tree = array(); 749 foreach ($all as $one) { 750 if ($one->get('parentid') == $parentid) { 751 $node = new stdClass(); 752 $node->competency = $one; 753 $node->children = self::build_tree($all, $one->get('id')); 754 $tree[] = $node; 755 } 756 } 757 return $tree; 758 } 759 760 /** 761 * Check if we can delete competencies safely. 762 * 763 * This moethod does not check any capablities. 764 * Check if competency is used in a plan and user competency. 765 * Check if competency is used in a template. 766 * Check if competency is linked to a course. 767 * 768 * @param array $ids Array of competencies ids. 769 * @return bool True if we can delete the competencies. 770 */ 771 public static function can_all_be_deleted($ids) { 772 global $CFG; 773 774 if (empty($ids)) { 775 return true; 776 } 777 // Check if competency is used in template. 778 if (template_competency::has_records_for_competencies($ids)) { 779 return false; 780 } 781 // Check if competency is used in plan. 782 if (plan_competency::has_records_for_competencies($ids)) { 783 return false; 784 } 785 // Check if competency is used in course. 786 if (course_competency::has_records_for_competencies($ids)) { 787 return false; 788 } 789 // Check if competency is used in user_competency. 790 if (user_competency::has_records_for_competencies($ids)) { 791 return false; 792 } 793 // Check if competency is used in user_competency_plan. 794 if (user_competency_plan::has_records_for_competencies($ids)) { 795 return false; 796 } 797 798 require_once($CFG->libdir . '/badgeslib.php'); 799 // Check if competency is used in a badge. 800 if (badge_award_criteria_competency_has_records_for_competencies($ids)) { 801 return false; 802 } 803 804 return true; 805 } 806 807 /** 808 * Delete the competencies. 809 * 810 * This method is reserved to core usage. 811 * This method does not trigger the after_delete event. 812 * This method does not delete related objects such as related competencies and evidences. 813 * 814 * @param array $ids The competencies ids. 815 * @return bool True if the competencies were deleted successfully. 816 */ 817 public static function delete_multiple($ids) { 818 global $DB; 819 list($insql, $params) = $DB->get_in_or_equal($ids, SQL_PARAMS_NAMED); 820 return $DB->delete_records_select(self::TABLE, "id $insql", $params); 821 } 822 823 /** 824 * Get descendant ids. 825 * 826 * @param competency $competency The competency. 827 * @return array Array of competencies ids. 828 */ 829 public static function get_descendants_ids($competency) { 830 global $DB; 831 832 $path = $DB->sql_like_escape($competency->get('path') . $competency->get('id') . '/') . '%'; 833 $like = $DB->sql_like('path', ':likepath'); 834 return $DB->get_fieldset_select(self::TABLE, 'id', $like, array('likepath' => $path)); 835 } 836 837 /** 838 * Get competencyids by frameworkid. 839 * 840 * @param int $frameworkid The competency framework ID. 841 * @return array Array of competency ids. 842 */ 843 public static function get_ids_by_frameworkid($frameworkid) { 844 global $DB; 845 846 return $DB->get_fieldset_select(self::TABLE, 'id', 'competencyframeworkid = :frmid', array('frmid' => $frameworkid)); 847 } 848 849 /** 850 * Delete competencies by framework ID. 851 * 852 * This method is reserved to core usage. 853 * This method does not trigger the after_delete event. 854 * This method does not delete related objects such as related competencies and evidences. 855 * 856 * @param int $id the framework ID 857 * @return bool Return true if delete was successful. 858 */ 859 public static function delete_by_frameworkid($id) { 860 global $DB; 861 return $DB->delete_records(self::TABLE, array('competencyframeworkid' => $id)); 862 } 863 864 /** 865 * Get competency ancestors. 866 * 867 * @return competency[] Return array of ancestors. 868 */ 869 public function get_ancestors() { 870 global $DB; 871 $ancestors = array(); 872 $ancestorsids = explode('/', trim($this->get('path'), '/')); 873 // Drop the root item from the array /0/. 874 array_shift($ancestorsids); 875 if (!empty($ancestorsids)) { 876 list($insql, $params) = $DB->get_in_or_equal($ancestorsids, SQL_PARAMS_NAMED); 877 $ancestors = self::get_records_select("id $insql", $params); 878 } 879 return $ancestors; 880 } 881 882 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body