You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
882 lines
28 KiB
882 lines
28 KiB
<?php
|
|
// This file is part of Moodle - http://moodle.org/
|
|
//
|
|
// Moodle is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// Moodle is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
/**
|
|
* Class for loading/storing competencies from the DB.
|
|
*
|
|
* @package core_competency
|
|
* @copyright 2015 Damyon Wiese
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
*/
|
|
namespace core_competency;
|
|
defined('MOODLE_INTERNAL') || die();
|
|
|
|
use coding_exception;
|
|
use context_system;
|
|
use lang_string;
|
|
use stdClass;
|
|
|
|
require_once($CFG->libdir . '/grade/grade_scale.php');
|
|
|
|
/**
|
|
* Class for loading/storing competencies from the DB.
|
|
*
|
|
* @copyright 2015 Damyon Wiese
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
*/
|
|
class competency extends persistent {
|
|
|
|
const TABLE = 'competency';
|
|
|
|
/** Outcome none. */
|
|
const OUTCOME_NONE = 0;
|
|
/** Outcome evidence. */
|
|
const OUTCOME_EVIDENCE = 1;
|
|
/** Outcome complete. */
|
|
const OUTCOME_COMPLETE = 2;
|
|
/** Outcome recommend. */
|
|
const OUTCOME_RECOMMEND = 3;
|
|
|
|
/** @var competency Object before update. */
|
|
protected $beforeupdate = null;
|
|
|
|
/**
|
|
* Return the definition of the properties of this model.
|
|
*
|
|
* @return array
|
|
*/
|
|
protected static function define_properties() {
|
|
return array(
|
|
'shortname' => array(
|
|
'type' => PARAM_TEXT
|
|
),
|
|
'idnumber' => array(
|
|
'type' => PARAM_RAW
|
|
),
|
|
'description' => array(
|
|
'default' => '',
|
|
'type' => PARAM_CLEANHTML
|
|
),
|
|
'descriptionformat' => array(
|
|
'choices' => array(FORMAT_HTML, FORMAT_MOODLE, FORMAT_PLAIN, FORMAT_MARKDOWN),
|
|
'type' => PARAM_INT,
|
|
'default' => FORMAT_HTML
|
|
),
|
|
'sortorder' => array(
|
|
'default' => 0,
|
|
'type' => PARAM_INT
|
|
),
|
|
'parentid' => array(
|
|
'default' => 0,
|
|
'type' => PARAM_INT
|
|
),
|
|
'path' => array(
|
|
'default' => '/0/',
|
|
'type' => PARAM_RAW
|
|
),
|
|
'ruleoutcome' => array(
|
|
'choices' => array(self::OUTCOME_NONE, self::OUTCOME_EVIDENCE, self::OUTCOME_COMPLETE, self::OUTCOME_RECOMMEND),
|
|
'default' => self::OUTCOME_NONE,
|
|
'type' => PARAM_INT
|
|
),
|
|
'ruletype' => array(
|
|
'type' => PARAM_RAW,
|
|
'default' => null,
|
|
'null' => NULL_ALLOWED
|
|
),
|
|
'ruleconfig' => array(
|
|
'default' => null,
|
|
'type' => PARAM_RAW,
|
|
'null' => NULL_ALLOWED
|
|
),
|
|
'scaleid' => array(
|
|
'default' => null,
|
|
'type' => PARAM_INT,
|
|
'null' => NULL_ALLOWED
|
|
),
|
|
'scaleconfiguration' => array(
|
|
'default' => null,
|
|
'type' => PARAM_RAW,
|
|
'null' => NULL_ALLOWED
|
|
),
|
|
'competencyframeworkid' => array(
|
|
'default' => 0,
|
|
'type' => PARAM_INT
|
|
),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Hook to execute before validate.
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function before_validate() {
|
|
$this->beforeupdate = null;
|
|
$this->newparent = null;
|
|
|
|
// During update.
|
|
if ($this->get('id')) {
|
|
$this->beforeupdate = new competency($this->get('id'));
|
|
|
|
// The parent ID has changed.
|
|
if ($this->beforeupdate->get('parentid') != $this->get('parentid')) {
|
|
$this->newparent = $this->get_parent();
|
|
|
|
// Update path and sortorder.
|
|
$this->set_new_path($this->newparent);
|
|
$this->set_new_sortorder();
|
|
}
|
|
|
|
} else {
|
|
// During create.
|
|
|
|
$this->set_new_path();
|
|
// Always generate new sortorder when we create new competency.
|
|
$this->set_new_sortorder();
|
|
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Hook to execute after an update.
|
|
*
|
|
* @param bool $result Whether or not the update was successful.
|
|
* @return void
|
|
*/
|
|
protected function after_update($result) {
|
|
global $DB;
|
|
|
|
if (!$result) {
|
|
$this->beforeupdate = null;
|
|
return;
|
|
}
|
|
|
|
// The parent ID has changed, we need to fix all the paths of the children.
|
|
if ($this->beforeupdate->get('parentid') != $this->get('parentid')) {
|
|
$beforepath = $this->beforeupdate->get('path') . $this->get('id') . '/';
|
|
|
|
$like = $DB->sql_like('path', '?');
|
|
$likesearch = $DB->sql_like_escape($beforepath) . '%';
|
|
|
|
$table = '{' . self::TABLE . '}';
|
|
$sql = "UPDATE $table SET path = REPLACE(path, ?, ?) WHERE " . $like;
|
|
$DB->execute($sql, array(
|
|
$beforepath,
|
|
$this->get('path') . $this->get('id') . '/',
|
|
$likesearch
|
|
));
|
|
|
|
// Resolving sortorder holes left after changing parent.
|
|
$table = '{' . self::TABLE . '}';
|
|
$sql = "UPDATE $table SET sortorder = sortorder -1 "
|
|
. " WHERE competencyframeworkid = ? AND parentid = ? AND sortorder > ?";
|
|
$DB->execute($sql, array($this->get('competencyframeworkid'),
|
|
$this->beforeupdate->get('parentid'),
|
|
$this->beforeupdate->get('sortorder')
|
|
));
|
|
}
|
|
|
|
$this->beforeupdate = null;
|
|
}
|
|
|
|
|
|
/**
|
|
* Hook to execute after a delete.
|
|
*
|
|
* @param bool $result Whether or not the delete was successful.
|
|
* @return void
|
|
*/
|
|
protected function after_delete($result) {
|
|
global $DB;
|
|
if (!$result) {
|
|
return;
|
|
}
|
|
|
|
// Resolving sortorder holes left after delete.
|
|
$table = '{' . self::TABLE . '}';
|
|
$sql = "UPDATE $table SET sortorder = sortorder -1 WHERE competencyframeworkid = ? AND parentid = ? AND sortorder > ?";
|
|
$DB->execute($sql, array($this->get('competencyframeworkid'), $this->get('parentid'), $this->get('sortorder')));
|
|
}
|
|
|
|
/**
|
|
* Extracts the default grade from the scale configuration.
|
|
*
|
|
* Returns an array where the first element is the grade, and the second
|
|
* is a boolean representing whether or not this grade is considered 'proficient'.
|
|
*
|
|
* @return array(int grade, bool proficient)
|
|
*/
|
|
public function get_default_grade() {
|
|
$scaleid = $this->get('scaleid');
|
|
$scaleconfig = $this->get('scaleconfiguration');
|
|
if ($scaleid === null) {
|
|
$scaleconfig = $this->get_framework()->get('scaleconfiguration');
|
|
}
|
|
return competency_framework::get_default_grade_from_scale_configuration($scaleconfig);
|
|
}
|
|
|
|
/**
|
|
* Get the competency framework.
|
|
*
|
|
* @return competency_framework
|
|
*/
|
|
public function get_framework() {
|
|
return new competency_framework($this->get('competencyframeworkid'));
|
|
}
|
|
|
|
/**
|
|
* Get the competency level.
|
|
*
|
|
* @return int
|
|
*/
|
|
public function get_level() {
|
|
$path = $this->get('path');
|
|
$path = trim($path, '/');
|
|
return substr_count($path, '/') + 1;
|
|
}
|
|
|
|
/**
|
|
* Return the parent competency.
|
|
*
|
|
* @return null|competency
|
|
*/
|
|
public function get_parent() {
|
|
$parentid = $this->get('parentid');
|
|
if (!$parentid) {
|
|
return null;
|
|
}
|
|
return new competency($parentid);
|
|
}
|
|
|
|
/**
|
|
* Extracts the proficiency of a grade from the scale configuration.
|
|
*
|
|
* @param int $grade The grade (scale item ID).
|
|
* @return array(int grade, bool proficient)
|
|
*/
|
|
public function get_proficiency_of_grade($grade) {
|
|
$scaleid = $this->get('scaleid');
|
|
$scaleconfig = $this->get('scaleconfiguration');
|
|
if ($scaleid === null) {
|
|
$scaleconfig = $this->get_framework()->get('scaleconfiguration');
|
|
}
|
|
return competency_framework::get_proficiency_of_grade_from_scale_configuration($scaleconfig, $grade);
|
|
}
|
|
|
|
/**
|
|
* Return the related competencies.
|
|
*
|
|
* @return competency[]
|
|
*/
|
|
public function get_related_competencies() {
|
|
return related_competency::get_related_competencies($this->get('id'));
|
|
}
|
|
|
|
/**
|
|
* Get the rule object.
|
|
*
|
|
* @return null|competency_rule
|
|
*/
|
|
public function get_rule_object() {
|
|
$rule = $this->get('ruletype');
|
|
|
|
if (!$rule || !is_subclass_of($rule, 'core_competency\\competency_rule')) {
|
|
// Double check that the rule is extending the right class to avoid bad surprises.
|
|
return null;
|
|
}
|
|
|
|
return new $rule($this);
|
|
}
|
|
|
|
/**
|
|
* Return the scale.
|
|
*
|
|
* @return \grade_scale
|
|
*/
|
|
public function get_scale() {
|
|
$scaleid = $this->get('scaleid');
|
|
if ($scaleid === null) {
|
|
return $this->get_framework()->get_scale();
|
|
}
|
|
$scale = \grade_scale::fetch(array('id' => $scaleid));
|
|
$scale->load_items();
|
|
return $scale;
|
|
}
|
|
|
|
/**
|
|
* Returns true when the competency has user competencies.
|
|
*
|
|
* This is useful to determine if the competency, or part of it, should be locked down.
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public function has_user_competencies() {
|
|
return user_competency::has_records_for_competency($this->get('id')) ||
|
|
user_competency_plan::has_records_for_competency($this->get('id'));
|
|
}
|
|
|
|
/**
|
|
* Check if the competency is the parent of passed competencies.
|
|
*
|
|
* @param array $ids IDs of supposedly direct children.
|
|
* @return boolean
|
|
*/
|
|
public function is_parent_of(array $ids) {
|
|
global $DB;
|
|
|
|
list($insql, $params) = $DB->get_in_or_equal($ids, SQL_PARAMS_NAMED);
|
|
$params['parentid'] = $this->get('id');
|
|
|
|
return $DB->count_records_select(self::TABLE, "id $insql AND parentid = :parentid", $params) == count($ids);
|
|
}
|
|
|
|
/**
|
|
* Reset the rule.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function reset_rule() {
|
|
$this->raw_set('ruleoutcome', static::OUTCOME_NONE);
|
|
$this->raw_set('ruletype', null);
|
|
$this->raw_set('ruleconfig', null);
|
|
}
|
|
|
|
/**
|
|
* Helper method to set the path.
|
|
*
|
|
* @param competency $parent The parent competency object.
|
|
* @return void
|
|
*/
|
|
protected function set_new_path(competency $parent = null) {
|
|
$path = '/0/';
|
|
if ($this->get('parentid')) {
|
|
$parent = $parent !== null ? $parent : $this->get_parent();
|
|
$path = $parent->get('path') . $this->get('parentid') . '/';
|
|
}
|
|
$this->raw_set('path', $path);
|
|
}
|
|
|
|
/**
|
|
* Helper method to set the sortorder.
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function set_new_sortorder() {
|
|
$search = array('parentid' => $this->get('parentid'), 'competencyframeworkid' => $this->get('competencyframeworkid'));
|
|
$this->raw_set('sortorder', $this->count_records($search));
|
|
}
|
|
|
|
/**
|
|
* This does a specialised search that finds all nodes in the tree with matching text on any text like field,
|
|
* and returns this node and all its parents in a displayable sort order.
|
|
*
|
|
* @param string $searchtext The text to search for.
|
|
* @param int $competencyframeworkid The competency framework to limit the search.
|
|
* @return persistent[]
|
|
*/
|
|
public static function search($searchtext, $competencyframeworkid) {
|
|
global $DB;
|
|
|
|
$like1 = $DB->sql_like('shortname', ':like1', false);
|
|
$like2 = $DB->sql_like('idnumber', ':like2', false);
|
|
$like3 = $DB->sql_like('description', ':like3', false);
|
|
|
|
$params = array(
|
|
'like1' => '%' . $DB->sql_like_escape($searchtext) . '%',
|
|
'like2' => '%' . $DB->sql_like_escape($searchtext) . '%',
|
|
'like3' => '%' . $DB->sql_like_escape($searchtext) . '%',
|
|
'frameworkid' => $competencyframeworkid
|
|
);
|
|
|
|
$sql = 'competencyframeworkid = :frameworkid AND ((' . $like1 . ') OR (' . $like2 . ') OR (' . $like3 . '))';
|
|
$records = $DB->get_records_select(self::TABLE, $sql, $params, 'path, sortorder ASC', '*');
|
|
|
|
// Now get all the parents.
|
|
$parents = array();
|
|
foreach ($records as $record) {
|
|
$split = explode('/', trim($record->path, '/'));
|
|
foreach ($split as $parent) {
|
|
$parents[intval($parent)] = true;
|
|
}
|
|
}
|
|
$parents = array_keys($parents);
|
|
|
|
// Skip ones we already fetched.
|
|
foreach ($parents as $idx => $parent) {
|
|
if ($parent == 0 || isset($records[$parent])) {
|
|
unset($parents[$idx]);
|
|
}
|
|
}
|
|
|
|
if (count($parents)) {
|
|
list($parentsql, $parentparams) = $DB->get_in_or_equal($parents, SQL_PARAMS_NAMED);
|
|
|
|
$parentrecords = $DB->get_records_select(self::TABLE, 'id ' . $parentsql,
|
|
$parentparams, 'path, sortorder ASC', '*');
|
|
|
|
foreach ($parentrecords as $id => $record) {
|
|
$records[$id] = $record;
|
|
}
|
|
}
|
|
|
|
$instances = array();
|
|
// Convert to instances of this class.
|
|
foreach ($records as $record) {
|
|
$newrecord = new static(0, $record);
|
|
$instances[$newrecord->get('id')] = $newrecord;
|
|
}
|
|
return $instances;
|
|
}
|
|
|
|
/**
|
|
* Validate the competency framework ID.
|
|
*
|
|
* @param int $value The framework ID.
|
|
* @return true|lang_string
|
|
*/
|
|
protected function validate_competencyframeworkid($value) {
|
|
|
|
// During update.
|
|
if ($this->get('id')) {
|
|
|
|
// Ensure that we are not trying to move the competency across frameworks.
|
|
if ($this->beforeupdate->get('competencyframeworkid') != $value) {
|
|
return new lang_string('invaliddata', 'error');
|
|
}
|
|
|
|
} else {
|
|
// During create.
|
|
|
|
// Check that the framework exists.
|
|
if (!competency_framework::record_exists($value)) {
|
|
return new lang_string('invaliddata', 'error');
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Validate the ID number.
|
|
*
|
|
* @param string $value The ID number.
|
|
* @return true|lang_string
|
|
*/
|
|
protected function validate_idnumber($value) {
|
|
global $DB;
|
|
$sql = 'idnumber = :idnumber AND competencyframeworkid = :competencyframeworkid AND id <> :id';
|
|
$params = array(
|
|
'id' => $this->get('id'),
|
|
'idnumber' => $value,
|
|
'competencyframeworkid' => $this->get('competencyframeworkid')
|
|
);
|
|
if ($DB->record_exists_select(self::TABLE, $sql, $params)) {
|
|
return new lang_string('idnumbertaken', 'error');
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Validate the path.
|
|
*
|
|
* @param string $value The path.
|
|
* @return true|lang_string
|
|
*/
|
|
protected function validate_path($value) {
|
|
|
|
// The last item should be the parent ID.
|
|
$id = $this->get('parentid');
|
|
if (substr($value, -(strlen($id) + 2)) != '/' . $id . '/') {
|
|
return new lang_string('invaliddata', 'error');
|
|
|
|
} else if (!preg_match('@/([0-9]+/)+@', $value)) {
|
|
// The format of the path is not correct.
|
|
return new lang_string('invaliddata', 'error');
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Validate the parent ID.
|
|
*
|
|
* @param string $value The ID.
|
|
* @return true|lang_string
|
|
*/
|
|
protected function validate_parentid($value) {
|
|
|
|
// Check that the parent exists. But only if we don't have it already, and we actually have a parent.
|
|
if (!empty($value) && !$this->newparent && !self::record_exists($value)) {
|
|
return new lang_string('invaliddata', 'error');
|
|
}
|
|
|
|
// During update.
|
|
if ($this->get('id')) {
|
|
|
|
// If there is a new parent.
|
|
if ($this->beforeupdate->get('parentid') != $value && $this->newparent) {
|
|
|
|
// Check that the new parent belongs to the same framework.
|
|
if ($this->newparent->get('competencyframeworkid') != $this->get('competencyframeworkid')) {
|
|
return new lang_string('invaliddata', 'error');
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Validate the rule.
|
|
*
|
|
* @param string $value The ID.
|
|
* @return true|lang_string
|
|
*/
|
|
protected function validate_ruletype($value) {
|
|
if ($value === null) {
|
|
return true;
|
|
}
|
|
|
|
if (!class_exists($value) || !is_subclass_of($value, 'core_competency\\competency_rule')) {
|
|
return new lang_string('invaliddata', 'error');
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Validate the rule config.
|
|
*
|
|
* @param string $value The ID.
|
|
* @return true|lang_string
|
|
*/
|
|
protected function validate_ruleconfig($value) {
|
|
$rule = $this->get_rule_object();
|
|
|
|
// We don't have a rule.
|
|
if (empty($rule)) {
|
|
if ($value === null) {
|
|
// No config, perfect.
|
|
return true;
|
|
}
|
|
// Config but no rules, whoops!
|
|
return new lang_string('invaliddata', 'error');
|
|
}
|
|
|
|
$valid = $rule->validate_config($value);
|
|
if ($valid !== true) {
|
|
// Whoops!
|
|
return new lang_string('invaliddata', 'error');
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Validate the scale ID.
|
|
*
|
|
* Note that the value for a scale can never be 0, null has to be used when
|
|
* the framework's scale has to be used.
|
|
*
|
|
* @param int $value
|
|
* @return true|lang_string
|
|
*/
|
|
protected function validate_scaleid($value) {
|
|
global $DB;
|
|
|
|
if ($value === null) {
|
|
return true;
|
|
}
|
|
|
|
// Always validate that the scale exists.
|
|
if (!$DB->record_exists_select('scale', 'id = :id', array('id' => $value))) {
|
|
return new lang_string('invalidscaleid', 'error');
|
|
}
|
|
|
|
// During update.
|
|
if ($this->get('id')) {
|
|
|
|
// Validate that we can only change the scale when it is not used yet.
|
|
if ($this->beforeupdate->get('scaleid') != $value) {
|
|
if ($this->has_user_competencies()) {
|
|
return new lang_string('errorscalealreadyused', 'core_competency');
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Validate the scale configuration.
|
|
*
|
|
* This logic is adapted from {@link \core_competency\competency_framework::validate_scaleconfiguration()}.
|
|
*
|
|
* @param string $value The scale configuration.
|
|
* @return bool|lang_string
|
|
*/
|
|
protected function validate_scaleconfiguration($value) {
|
|
$scaleid = $this->get('scaleid');
|
|
if ($scaleid === null && $value === null) {
|
|
return true;
|
|
}
|
|
|
|
$scaledefaultselected = false;
|
|
$proficientselected = false;
|
|
$scaleconfigurations = json_decode($value);
|
|
|
|
if (is_array($scaleconfigurations)) {
|
|
|
|
// The first element of the array contains the scale ID.
|
|
$scaleinfo = array_shift($scaleconfigurations);
|
|
if (empty($scaleinfo) || !isset($scaleinfo->scaleid) || $scaleinfo->scaleid != $scaleid) {
|
|
// This should never happen.
|
|
return new lang_string('errorscaleconfiguration', 'core_competency');
|
|
}
|
|
|
|
// Walk through the array to find proficient and default values.
|
|
foreach ($scaleconfigurations as $scaleconfiguration) {
|
|
if (isset($scaleconfiguration->scaledefault) && $scaleconfiguration->scaledefault) {
|
|
$scaledefaultselected = true;
|
|
}
|
|
if (isset($scaleconfiguration->proficient) && $scaleconfiguration->proficient) {
|
|
$proficientselected = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!$scaledefaultselected || !$proficientselected) {
|
|
return new lang_string('errorscaleconfiguration', 'core_competency');
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Return whether or not the competency IDs share the same framework.
|
|
*
|
|
* @param array $ids Competency IDs
|
|
* @return bool
|
|
*/
|
|
public static function share_same_framework(array $ids) {
|
|
global $DB;
|
|
list($insql, $params) = $DB->get_in_or_equal($ids);
|
|
$sql = "SELECT COUNT('x') FROM (SELECT DISTINCT(competencyframeworkid) FROM {" . self::TABLE . "} WHERE id {$insql}) f";
|
|
return $DB->count_records_sql($sql, $params) == 1;
|
|
}
|
|
|
|
/**
|
|
* Get the available rules.
|
|
*
|
|
* @return array Keys are the class names, values are the name of the rule.
|
|
*/
|
|
public static function get_available_rules() {
|
|
// Fully qualified class names without leading slashes because get_class() does not add them either.
|
|
$rules = array(
|
|
'core_competency\\competency_rule_all' => competency_rule_all::get_name(),
|
|
'core_competency\\competency_rule_points' => competency_rule_points::get_name(),
|
|
);
|
|
return $rules;
|
|
}
|
|
|
|
/**
|
|
* Return the current depth of a competency framework.
|
|
*
|
|
* @param int $frameworkid The framework ID.
|
|
* @return int
|
|
*/
|
|
public static function get_framework_depth($frameworkid) {
|
|
global $DB;
|
|
$totallength = $DB->sql_length('path');
|
|
$trimmedlength = $DB->sql_length("REPLACE(path, '/', '')");
|
|
$sql = "SELECT ($totallength - $trimmedlength - 1) AS depth
|
|
FROM {" . self::TABLE . "}
|
|
WHERE competencyframeworkid = :id
|
|
ORDER BY depth DESC";
|
|
$record = $DB->get_record_sql($sql, array('id' => $frameworkid), IGNORE_MULTIPLE);
|
|
if (!$record) {
|
|
$depth = 0;
|
|
} else {
|
|
$depth = $record->depth;
|
|
}
|
|
return $depth;
|
|
}
|
|
|
|
/**
|
|
* Build a framework tree with competency nodes.
|
|
*
|
|
* @param int $frameworkid the framework id
|
|
* @return node[] tree of framework competency nodes
|
|
*/
|
|
public static function get_framework_tree($frameworkid) {
|
|
$competencies = self::search('', $frameworkid);
|
|
return self::build_tree($competencies, 0);
|
|
}
|
|
|
|
/**
|
|
* Get the context from the framework.
|
|
*
|
|
* @return context
|
|
*/
|
|
public function get_context() {
|
|
return $this->get_framework()->get_context();
|
|
}
|
|
|
|
/**
|
|
* Recursively build up the tree of nodes.
|
|
*
|
|
* @param array $all - List of all competency classes.
|
|
* @param int $parentid - The current parent ID. Pass 0 to build the tree from the top.
|
|
* @return node[] $tree tree of nodes
|
|
*/
|
|
protected static function build_tree($all, $parentid) {
|
|
$tree = array();
|
|
foreach ($all as $one) {
|
|
if ($one->get('parentid') == $parentid) {
|
|
$node = new stdClass();
|
|
$node->competency = $one;
|
|
$node->children = self::build_tree($all, $one->get('id'));
|
|
$tree[] = $node;
|
|
}
|
|
}
|
|
return $tree;
|
|
}
|
|
|
|
/**
|
|
* Check if we can delete competencies safely.
|
|
*
|
|
* This moethod does not check any capablities.
|
|
* Check if competency is used in a plan and user competency.
|
|
* Check if competency is used in a template.
|
|
* Check if competency is linked to a course.
|
|
*
|
|
* @param array $ids Array of competencies ids.
|
|
* @return bool True if we can delete the competencies.
|
|
*/
|
|
public static function can_all_be_deleted($ids) {
|
|
global $CFG;
|
|
|
|
if (empty($ids)) {
|
|
return true;
|
|
}
|
|
// Check if competency is used in template.
|
|
if (template_competency::has_records_for_competencies($ids)) {
|
|
return false;
|
|
}
|
|
// Check if competency is used in plan.
|
|
if (plan_competency::has_records_for_competencies($ids)) {
|
|
return false;
|
|
}
|
|
// Check if competency is used in course.
|
|
if (course_competency::has_records_for_competencies($ids)) {
|
|
return false;
|
|
}
|
|
// Check if competency is used in user_competency.
|
|
if (user_competency::has_records_for_competencies($ids)) {
|
|
return false;
|
|
}
|
|
// Check if competency is used in user_competency_plan.
|
|
if (user_competency_plan::has_records_for_competencies($ids)) {
|
|
return false;
|
|
}
|
|
|
|
require_once($CFG->libdir . '/badgeslib.php');
|
|
// Check if competency is used in a badge.
|
|
if (badge_award_criteria_competency_has_records_for_competencies($ids)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Delete the competencies.
|
|
*
|
|
* This method is reserved to core usage.
|
|
* This method does not trigger the after_delete event.
|
|
* This method does not delete related objects such as related competencies and evidences.
|
|
*
|
|
* @param array $ids The competencies ids.
|
|
* @return bool True if the competencies were deleted successfully.
|
|
*/
|
|
public static function delete_multiple($ids) {
|
|
global $DB;
|
|
list($insql, $params) = $DB->get_in_or_equal($ids, SQL_PARAMS_NAMED);
|
|
return $DB->delete_records_select(self::TABLE, "id $insql", $params);
|
|
}
|
|
|
|
/**
|
|
* Get descendant ids.
|
|
*
|
|
* @param competency $competency The competency.
|
|
* @return array Array of competencies ids.
|
|
*/
|
|
public static function get_descendants_ids($competency) {
|
|
global $DB;
|
|
|
|
$path = $DB->sql_like_escape($competency->get('path') . $competency->get('id') . '/') . '%';
|
|
$like = $DB->sql_like('path', ':likepath');
|
|
return $DB->get_fieldset_select(self::TABLE, 'id', $like, array('likepath' => $path));
|
|
}
|
|
|
|
/**
|
|
* Get competencyids by frameworkid.
|
|
*
|
|
* @param int $frameworkid The competency framework ID.
|
|
* @return array Array of competency ids.
|
|
*/
|
|
public static function get_ids_by_frameworkid($frameworkid) {
|
|
global $DB;
|
|
|
|
return $DB->get_fieldset_select(self::TABLE, 'id', 'competencyframeworkid = :frmid', array('frmid' => $frameworkid));
|
|
}
|
|
|
|
/**
|
|
* Delete competencies by framework ID.
|
|
*
|
|
* This method is reserved to core usage.
|
|
* This method does not trigger the after_delete event.
|
|
* This method does not delete related objects such as related competencies and evidences.
|
|
*
|
|
* @param int $id the framework ID
|
|
* @return bool Return true if delete was successful.
|
|
*/
|
|
public static function delete_by_frameworkid($id) {
|
|
global $DB;
|
|
return $DB->delete_records(self::TABLE, array('competencyframeworkid' => $id));
|
|
}
|
|
|
|
/**
|
|
* Get competency ancestors.
|
|
*
|
|
* @return competency[] Return array of ancestors.
|
|
*/
|
|
public function get_ancestors() {
|
|
global $DB;
|
|
$ancestors = array();
|
|
$ancestorsids = explode('/', trim($this->get('path'), '/'));
|
|
// Drop the root item from the array /0/.
|
|
array_shift($ancestorsids);
|
|
if (!empty($ancestorsids)) {
|
|
list($insql, $params) = $DB->get_in_or_equal($ancestorsids, SQL_PARAMS_NAMED);
|
|
$ancestors = self::get_records_select("id $insql", $params);
|
|
}
|
|
return $ancestors;
|
|
}
|
|
|
|
}
|
|
|