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 plans persistence.
19
 *
20
 * @package    core_competency
21
 * @copyright  2015 David Monllao
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 comment;
28
use context_user;
29
use dml_missing_record_exception;
30
use lang_string;
31
 
32
/**
33
 * Class for loading/storing plans from the DB.
34
 *
35
 * @copyright  2015 David Monllao
36
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
37
 */
38
class plan extends persistent {
39
 
40
    const TABLE = 'competency_plan';
41
 
42
    /** Draft status. */
43
    const STATUS_DRAFT = 0;
44
 
45
    /** Active status. */
46
    const STATUS_ACTIVE = 1;
47
 
48
    /** Complete status. */
49
    const STATUS_COMPLETE = 2;
50
 
51
    /** Waiting for review. */
52
    const STATUS_WAITING_FOR_REVIEW = 3;
53
 
54
    /** In review. */
55
    const STATUS_IN_REVIEW = 4;
56
 
57
    /** 10 minutes threshold **/
58
    const DUEDATE_THRESHOLD = 600;
59
 
60
    /** @var plan object before update. */
61
    protected $beforeupdate = null;
62
 
63
    /**
64
     * Return the definition of the properties of this model.
65
     *
66
     * @return array
67
     */
68
    protected static function define_properties() {
69
        return array(
70
            'name' => array(
71
                'type' => PARAM_TEXT,
72
            ),
73
            'description' => array(
74
                'type' => PARAM_CLEANHTML,
75
                'default' => ''
76
            ),
77
            'descriptionformat' => array(
78
                'choices' => array(FORMAT_HTML, FORMAT_MOODLE, FORMAT_PLAIN, FORMAT_MARKDOWN),
79
                'type' => PARAM_INT,
80
                'default' => FORMAT_HTML,
81
            ),
82
            'userid' => array(
83
                'type' => PARAM_INT,
84
            ),
85
            'templateid' => array(
86
                'type' => PARAM_INT,
87
                'default' => null,
88
                'null' => NULL_ALLOWED,
89
            ),
90
            'origtemplateid' => array(
91
                'type' => PARAM_INT,
92
                'default' => null,
93
                'null' => NULL_ALLOWED,
94
            ),
95
            'status' => array(
96
                'choices' => array(self::STATUS_DRAFT, self::STATUS_COMPLETE, self::STATUS_ACTIVE,
97
                    self::STATUS_WAITING_FOR_REVIEW, self::STATUS_IN_REVIEW),
98
                'type' => PARAM_INT,
99
                'default' => self::STATUS_DRAFT,
100
            ),
101
            'duedate' => array(
102
                'type' => PARAM_INT,
103
                'default' => 0,
104
            ),
105
            'reviewerid' => array(
106
                'type' => PARAM_INT,
107
                'default' => null,
108
                'null' => NULL_ALLOWED,
109
            )
110
        );
111
    }
112
 
113
    /**
114
     * Hook to execute before validate.
115
     *
116
     * @return void
117
     */
118
    protected function before_validate() {
119
        $this->beforeupdate = null;
120
 
121
        // During update.
122
        if ($this->get('id')) {
123
            $this->beforeupdate = new self($this->get('id'));
124
        }
125
    }
126
 
127
    /**
128
     * Whether the current user can comment on this plan.
129
     *
130
     * @return bool
131
     */
132
    public function can_comment() {
133
        return static::can_comment_user($this->get('userid'));
134
    }
135
 
136
    /**
137
     * Whether the current user can manage the plan.
138
     *
139
     * @return bool
140
     */
141
    public function can_manage() {
142
        if ($this->is_draft()) {
143
            return self::can_manage_user_draft($this->get('userid'));
144
        }
145
        return self::can_manage_user($this->get('userid'));
146
    }
147
 
148
    /**
149
     * Whether the current user can read the plan.
150
     *
151
     * @return bool
152
     */
153
    public function can_read() {
154
        if ($this->is_draft()) {
155
            return self::can_read_user_draft($this->get('userid'));
156
        }
157
        return self::can_read_user($this->get('userid'));
158
    }
159
 
160
    /**
161
     * Whether the current user can read comments on this plan.
162
     *
163
     * @return bool
164
     */
165
    public function can_read_comments() {
166
        return $this->can_read();
167
    }
168
 
169
    /**
170
     * Whether the current user can request a review of the plan.
171
     *
172
     * @return bool
173
     */
174
    public function can_request_review() {
175
        return self::can_request_review_user($this->get('userid'));
176
    }
177
 
178
    /**
179
     * Whether the current user can review the plan.
180
     *
181
     * @return bool
182
     */
183
    public function can_review() {
184
        return self::can_review_user($this->get('userid'));
185
    }
186
 
187
    /**
188
     * Get the comment object.
189
     *
190
     * @return comment
191
     */
192
    public function get_comment_object() {
193
        global $CFG;
194
        require_once($CFG->dirroot . '/comment/lib.php');
195
 
196
        if (!$this->get('id')) {
197
            throw new \coding_exception('The plan must exist.');
198
        }
199
 
200
        $comment = new comment((object) array(
201
            'client_id' => 'plancommentarea' . $this->get('id'),
202
            'context' => $this->get_context(),
203
            'component' => 'competency',    // This cannot be named 'core_competency'.
204
            'itemid' => $this->get('id'),
205
            'area' => 'plan',
206
            'showcount' => true,
207
        ));
208
        $comment->set_fullwidth(true);
209
        return $comment;
210
    }
211
 
212
    /**
213
     * Get the competencies in this plan.
214
     *
215
     * @return competency[]
216
     */
217
    public function get_competencies() {
218
        $competencies = array();
219
 
220
        if ($this->get('status') == self::STATUS_COMPLETE) {
221
            // Get the competencies from the archive of the plan.
222
            $competencies = user_competency_plan::list_competencies($this->get('id'), $this->get('userid'));
223
        } else if ($this->is_based_on_template()) {
224
            // Get the competencies from the template.
225
            $competencies = template_competency::list_competencies($this->get('templateid'));
226
        } else {
227
            // Get the competencies from the plan.
228
            $competencies = plan_competency::list_competencies($this->get('id'));
229
        }
230
 
231
        return $competencies;
232
    }
233
 
234
    /**
235
     * Get a single competency from this plan.
236
     *
237
     * This will throw an exception if the competency does not belong to the plan.
238
     *
239
     * @param int $competencyid The competency ID.
240
     * @return competency
241
     */
242
    public function get_competency($competencyid) {
243
        $competency = null;
244
 
245
        if ($this->get('status') == self::STATUS_COMPLETE) {
246
            // Get the competency from the archive of the plan.
247
            $competency = user_competency_plan::get_competency_by_planid($this->get('id'), $competencyid);
248
        } else if ($this->is_based_on_template()) {
249
            // Get the competency from the template.
250
            $competency = template_competency::get_competency($this->get('templateid'), $competencyid);
251
        } else {
252
            // Get the competency from the plan.
253
            $competency = plan_competency::get_competency($this->get('id'), $competencyid);
254
        }
255
        return $competency;
256
    }
257
 
258
    /**
259
     * Get the context in which the plan is attached.
260
     *
261
     * @return context_user
262
     */
263
    public function get_context() {
264
        return context_user::instance($this->get('userid'));
265
    }
266
 
267
    /**
268
     * Human readable status name.
269
     *
270
     * @return string
271
     */
272
    public function get_statusname() {
273
 
274
        $status = $this->get('status');
275
 
276
        switch ($status) {
277
            case self::STATUS_DRAFT:
278
                $strname = 'draft';
279
                break;
280
            case self::STATUS_IN_REVIEW:
281
                $strname = 'inreview';
282
                break;
283
            case self::STATUS_WAITING_FOR_REVIEW:
284
                $strname = 'waitingforreview';
285
                break;
286
            case self::STATUS_ACTIVE:
287
                $strname = 'active';
288
                break;
289
            case self::STATUS_COMPLETE:
290
                $strname = 'complete';
291
                break;
292
            default:
293
                throw new \moodle_exception('errorplanstatus', 'core_competency', '', $status);
294
                break;
295
        }
296
 
297
        return get_string('planstatus' . $strname, 'core_competency');
298
    }
299
 
300
    /**
301
     * Get the plan template.
302
     *
303
     * @return template|null
304
     */
305
    public function get_template() {
306
        $templateid = $this->get('templateid');
307
        if ($templateid === null) {
308
            return null;
309
        }
310
        return new template($templateid);
311
    }
312
 
313
    /**
314
     * Is the plan in draft mode?
315
     *
316
     * This method is convenient to know if the plan is a draft because whilst a draft
317
     * is being reviewed its status is not "draft" any more, but it still is a draft nonetheless.
318
     *
319
     * @return boolean
320
     */
321
    public function is_draft() {
322
        return in_array($this->get('status'), static::get_draft_statuses());
323
    }
324
 
325
    /**
326
     * Validate the template ID.
327
     *
328
     * @param mixed $value The value.
329
     * @return true|lang_string
330
     */
331
    protected function validate_templateid($value) {
332
 
333
        // Checks that the template exists.
334
        if (!empty($value) && !template::record_exists($value)) {
335
            return new lang_string('invaliddata', 'error');
336
        }
337
 
338
        return true;
339
    }
340
 
341
    /**
342
     * Validate the user ID.
343
     *
344
     * @param  int $value
345
     * @return true|lang_string
346
     */
347
    protected function validate_userid($value) {
348
        global $DB;
349
 
350
        // During create.
351
        if (!$this->get('id')) {
352
 
353
            // Check that the user exists. We do not need to do that on update because
354
            // the userid of a plan should never change.
355
            if (!$DB->record_exists('user', array('id' => $value))) {
356
                return new lang_string('invaliddata', 'error');
357
            }
358
 
359
        }
360
 
361
        return true;
362
    }
363
 
364
    /**
365
     * Can the current user comment on a user's plan?
366
     *
367
     * @param int $planuserid The user ID the plan belongs to.
368
     * @return bool
369
     */
370
    public static function can_comment_user($planuserid) {
371
        global $USER;
372
 
373
        $capabilities = array('moodle/competency:plancomment');
374
        if ($USER->id == $planuserid) {
375
            $capabilities[] = 'moodle/competency:plancommentown';
376
        }
377
 
378
        return has_any_capability($capabilities, context_user::instance($planuserid));
379
    }
380
 
381
    /**
382
     * Can the current user manage a user's plan?
383
     *
384
     * @param  int $planuserid The user to whom the plan would belong.
385
     * @return bool
386
     */
387
    public static function can_manage_user($planuserid) {
388
        global $USER;
389
        $context = context_user::instance($planuserid);
390
 
391
        $capabilities = array('moodle/competency:planmanage');
392
        if ($context->instanceid == $USER->id) {
393
            $capabilities[] = 'moodle/competency:planmanageown';
394
        }
395
 
396
        return has_any_capability($capabilities, $context);
397
    }
398
 
399
    /**
400
     * Can the current user manage a user's draft plan?
401
     *
402
     * @param  int $planuserid The user to whom the plan would belong.
403
     * @return bool
404
     */
405
    public static function can_manage_user_draft($planuserid) {
406
        global $USER;
407
        $context = context_user::instance($planuserid);
408
 
409
        $capabilities = array('moodle/competency:planmanagedraft');
410
        if ($context->instanceid == $USER->id) {
411
            $capabilities[] = 'moodle/competency:planmanageowndraft';
412
        }
413
 
414
        return has_any_capability($capabilities, $context);
415
    }
416
 
417
    /**
418
     * Can the current user read the comments on a user's plan?
419
     *
420
     * @param int $planuserid The user ID the plan belongs to.
421
     * @return bool
422
     */
423
    public static function can_read_comments_user($planuserid) {
424
        // Everyone who can read the plan can read the comments.
425
        return static::can_read_user($planuserid);
426
    }
427
 
428
    /**
429
     * Can the current user view a user's plan?
430
     *
431
     * @param  int $planuserid The user to whom the plan would belong.
432
     * @return bool
433
     */
434
    public static function can_read_user($planuserid) {
435
        global $USER;
436
        $context = context_user::instance($planuserid);
437
 
438
        $capabilities = array('moodle/competency:planview');
439
        if ($context->instanceid == $USER->id) {
440
            $capabilities[] = 'moodle/competency:planviewown';
441
        }
442
 
443
        return has_any_capability($capabilities, $context)
444
            || self::can_manage_user($planuserid);
445
    }
446
 
447
    /**
448
     * Can the current user view a user's draft plan?
449
     *
450
     * @param  int $planuserid The user to whom the plan would belong.
451
     * @return bool
452
     */
453
    public static function can_read_user_draft($planuserid) {
454
        global $USER;
455
        $context = context_user::instance($planuserid);
456
 
457
        $capabilities = array('moodle/competency:planviewdraft');
458
        if ($context->instanceid == $USER->id) {
459
            $capabilities[] = 'moodle/competency:planviewowndraft';
460
        }
461
 
462
        return has_any_capability($capabilities, $context)
463
            || self::can_manage_user_draft($planuserid);
464
    }
465
 
466
    /**
467
     * Can the current user request the draft to be reviewed.
468
     *
469
     * @param  int $planuserid The user to whom the plan would belong.
470
     * @return bool
471
     */
472
    public static function can_request_review_user($planuserid) {
473
        global $USER;
474
 
475
        $capabilities = array('moodle/competency:planrequestreview');
476
        if ($USER->id == $planuserid) {
477
            $capabilities[] = 'moodle/competency:planrequestreviewown';
478
        }
479
 
480
        return has_any_capability($capabilities, context_user::instance($planuserid));
481
    }
482
 
483
    /**
484
     * Can the current user review the plan.
485
     *
486
     * This means being able to send the plan from draft to active, and vice versa.
487
     *
488
     * @param  int $planuserid The user to whom the plan would belong.
489
     * @return bool
490
     */
491
    public static function can_review_user($planuserid) {
492
        return has_capability('moodle/competency:planreview', context_user::instance($planuserid))
493
            || self::can_manage_user($planuserid);
494
    }
495
 
496
    /**
497
     * Get the plans of a user containing a specific competency.
498
     *
499
     * @param  int $userid       The user ID.
500
     * @param  int $competencyid The competency ID.
501
     * @return plans[]
502
     */
503
    public static function get_by_user_and_competency($userid, $competencyid) {
504
        global $DB;
505
 
506
        $sql = 'SELECT p.*
507
                  FROM {' . self::TABLE . '} p
508
             LEFT JOIN {' . plan_competency::TABLE . '} pc
509
                    ON pc.planid = p.id
510
                   AND pc.competencyid = :competencyid1
511
             LEFT JOIN {' . user_competency_plan::TABLE . '} ucp
512
                    ON ucp.planid = p.id
513
                   AND ucp.competencyid = :competencyid2
514
             LEFT JOIN {' . template_competency::TABLE . '} tc
515
                    ON tc.templateid = p.templateid
516
                   AND tc.competencyid = :competencyid3
517
                 WHERE p.userid = :userid
518
                   AND (pc.id IS NOT NULL
519
                    OR ucp.id IS NOT NULL
520
                    OR tc.id IS NOT NULL)
521
              ORDER BY p.id ASC';
522
 
523
        $params = array(
524
            'competencyid1' => $competencyid,
525
            'competencyid2' => $competencyid,
526
            'competencyid3' => $competencyid,
527
            'userid' => $userid
528
        );
529
 
530
        $plans = array();
531
        $records = $DB->get_records_sql($sql, $params);
532
        foreach ($records as $record) {
533
            $plans[$record->id] = new plan(0, $record);
534
        }
535
 
536
        return $plans;
537
    }
538
 
539
    /**
540
     * Get the list of draft statuses.
541
     *
542
     * @return array Contains the status constants.
543
     */
544
    public static function get_draft_statuses() {
545
        return array(self::STATUS_DRAFT, self::STATUS_WAITING_FOR_REVIEW, self::STATUS_IN_REVIEW);
546
    }
547
 
548
    /**
549
     * Get the recordset of the plans that are due, incomplete and not draft.
550
     *
551
     * @return \moodle_recordset
552
     */
553
    public static function get_recordset_for_due_and_incomplete() {
554
        global $DB;
555
        $sql = "duedate > 0 AND duedate < :now AND status = :status";
556
        $params = array('now' => time(), 'status' => self::STATUS_ACTIVE);
557
        return $DB->get_recordset_select(self::TABLE, $sql, $params);
558
    }
559
 
560
    /**
561
     * Return a list of status depending on capabilities.
562
     *
563
     * @param  int $userid The user to whom the plan would belong.
564
     * @return array
565
     */
566
    public static function get_status_list($userid) {
567
        $status = array();
568
        if (self::can_manage_user_draft($userid)) {
569
            $status[self::STATUS_DRAFT] = get_string('planstatusdraft', 'core_competency');
570
        }
571
        if (self::can_manage_user($userid)) {
572
            $status[self::STATUS_ACTIVE] = get_string('planstatusactive', 'core_competency');
573
        }
574
        return $status;
575
    }
576
 
577
    /**
578
     * Update from template.
579
     *
580
     * Bulk update a lot of plans from a template
581
     *
582
     * @param  template $template
583
     * @return bool
584
     */
585
    public static function update_multiple_from_template(template $template) {
586
        global $DB;
587
        if (!$template->is_valid()) {
588
            // As we will bypass this model's validation we rely on the template being validated.
589
            throw new \coding_exception('The template must be validated before updating plans.');
590
        }
591
 
592
        $params = array(
593
            'templateid' => $template->get('id'),
594
            'status' => self::STATUS_COMPLETE,
595
 
596
            'name' => $template->get('shortname'),
597
            'description' => $template->get('description'),
598
            'descriptionformat' => $template->get('descriptionformat'),
599
            'duedate' => $template->get('duedate'),
600
        );
601
 
602
        $sql = "UPDATE {" . self::TABLE . "}
603
                   SET name = :name,
604
                       description = :description,
605
                       descriptionformat = :descriptionformat,
606
                       duedate = :duedate
607
                 WHERE templateid = :templateid
608
                   AND status != :status";
609
 
610
        return $DB->execute($sql, $params);
611
    }
612
 
613
    /**
614
     * Check if a template is associated to the plan.
615
     *
616
     * @return bool
617
     */
618
    public function is_based_on_template() {
619
        return $this->get('templateid') !== null;
620
    }
621
 
622
    /**
623
     * Check if plan can be edited.
624
     *
625
     * @return bool
626
     */
627
    public function can_be_edited() {
628
        return !$this->is_based_on_template() && $this->get('status') != self::STATUS_COMPLETE && $this->can_manage();
629
    }
630
 
631
    /**
632
     * Validate the due date.
633
     * When setting a due date it must not exceed the DUEDATE_THRESHOLD.
634
     *
635
     * @param  int $value The due date.
636
     * @return bool|lang_string
637
     */
638
    protected function validate_duedate($value) {
639
 
640
        // We do not check duedate when plan is draft, complete, unset, or based on a template.
641
        if ($this->is_based_on_template()
642
                || $this->is_draft()
643
                || $this->get('status') == self::STATUS_COMPLETE
644
                || empty($value)) {
645
            return true;
646
        }
647
 
648
        // During update.
649
        if ($this->get('id')) {
650
            $before = $this->beforeupdate->get('duedate');
651
            $beforestatus = $this->beforeupdate->get('status');
652
 
653
            // The value has not changed, then it's always OK. Though if we're going
654
            // from draft to active it has to has to be validated.
655
            if ($before == $value && !in_array($beforestatus, self::get_draft_statuses())) {
656
                return true;
657
            }
658
        }
659
 
660
        if ($value <= time()) {
661
            // We cannot set the date in the past.
662
            return new lang_string('errorcannotsetduedateinthepast', 'core_competency');
663
        }
664
 
665
        if ($value <= time() + self::DUEDATE_THRESHOLD) {
666
            // We cannot set the date too soon, but we can leave it empty.
667
            return new lang_string('errorcannotsetduedatetoosoon', 'core_competency');
668
        }
669
 
670
        return true;
671
    }
672
 
673
    /**
674
     * Checks if a template has user plan records.
675
     *
676
     * @param  int $templateid The template ID
677
     * @return boolean
678
     */
679
    public static function has_records_for_template($templateid) {
680
        return self::record_exists_select('templateid = ?', array($templateid));
681
    }
682
 
683
    /**
684
     * Count the number of plans for a template, optionally filtering by status.
685
     *
686
     * @param  int $templateid The template ID
687
     * @param  int $status The plan status. 0 means all statuses.
688
     * @return int
689
     */
690
    public static function count_records_for_template($templateid, $status) {
691
        $filters = array('templateid' => $templateid);
692
        if ($status > 0) {
693
            $filters['status'] = $status;
694
        }
695
        return self::count_records($filters);
696
    }
697
 
698
    /**
699
     * Get the plans for a template, optionally filtering by status.
700
     *
701
     * @param  int $templateid The template ID
702
     * @param  int $status The plan status. 0 means all statuses.
703
     * @param  int $skip The number of plans to skip
704
     * @param  int $limit The max number of plans to return
705
     * @return int
706
     */
707
    public static function get_records_for_template($templateid, $status = 0, $skip = 0, $limit = 100) {
708
        $filters = array('templateid' => $templateid);
709
        if ($status > 0) {
710
            $filters['status'] = $status;
711
        }
712
        return self::get_records($filters, $skip, $limit);
713
    }
714
}