Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

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