Merge branch 'MDL-73245-master' of https://github.com/cameron1729/moodle
[moodle.git] / competency / classes / plan.php
blob92c0d208fe019a2e4064ff8bddff7baec6b61485
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.
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/>.
17 /**
18 * Class for plans persistence.
20 * @package core_competency
21 * @copyright 2015 David Monllao
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24 namespace core_competency;
25 defined('MOODLE_INTERNAL') || die();
27 use comment;
28 use context_user;
29 use dml_missing_record_exception;
30 use lang_string;
32 /**
33 * Class for loading/storing plans from the DB.
35 * @copyright 2015 David Monllao
36 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
38 class plan extends persistent {
40 const TABLE = 'competency_plan';
42 /** Draft status. */
43 const STATUS_DRAFT = 0;
45 /** Active status. */
46 const STATUS_ACTIVE = 1;
48 /** Complete status. */
49 const STATUS_COMPLETE = 2;
51 /** Waiting for review. */
52 const STATUS_WAITING_FOR_REVIEW = 3;
54 /** In review. */
55 const STATUS_IN_REVIEW = 4;
57 /** 10 minutes threshold **/
58 const DUEDATE_THRESHOLD = 600;
60 /** @var plan object before update. */
61 protected $beforeupdate = null;
63 /**
64 * Return the definition of the properties of this model.
66 * @return array
68 protected static function define_properties() {
69 return array(
70 'name' => array(
71 'type' => PARAM_TEXT,
73 'description' => array(
74 'type' => PARAM_CLEANHTML,
75 'default' => ''
77 'descriptionformat' => array(
78 'choices' => array(FORMAT_HTML, FORMAT_MOODLE, FORMAT_PLAIN, FORMAT_MARKDOWN),
79 'type' => PARAM_INT,
80 'default' => FORMAT_HTML,
82 'userid' => array(
83 'type' => PARAM_INT,
85 'templateid' => array(
86 'type' => PARAM_INT,
87 'default' => null,
88 'null' => NULL_ALLOWED,
90 'origtemplateid' => array(
91 'type' => PARAM_INT,
92 'default' => null,
93 'null' => NULL_ALLOWED,
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,
101 'duedate' => array(
102 'type' => PARAM_INT,
103 'default' => 0,
105 'reviewerid' => array(
106 'type' => PARAM_INT,
107 'default' => null,
108 'null' => NULL_ALLOWED,
114 * Hook to execute before validate.
116 * @return void
118 protected function before_validate() {
119 $this->beforeupdate = null;
121 // During update.
122 if ($this->get('id')) {
123 $this->beforeupdate = new self($this->get('id'));
128 * Whether the current user can comment on this plan.
130 * @return bool
132 public function can_comment() {
133 return static::can_comment_user($this->get('userid'));
137 * Whether the current user can manage the plan.
139 * @return bool
141 public function can_manage() {
142 if ($this->is_draft()) {
143 return self::can_manage_user_draft($this->get('userid'));
145 return self::can_manage_user($this->get('userid'));
149 * Whether the current user can read the plan.
151 * @return bool
153 public function can_read() {
154 if ($this->is_draft()) {
155 return self::can_read_user_draft($this->get('userid'));
157 return self::can_read_user($this->get('userid'));
161 * Whether the current user can read comments on this plan.
163 * @return bool
165 public function can_read_comments() {
166 return $this->can_read();
170 * Whether the current user can request a review of the plan.
172 * @return bool
174 public function can_request_review() {
175 return self::can_request_review_user($this->get('userid'));
179 * Whether the current user can review the plan.
181 * @return bool
183 public function can_review() {
184 return self::can_review_user($this->get('userid'));
188 * Get the comment object.
190 * @return comment
192 public function get_comment_object() {
193 global $CFG;
194 require_once($CFG->dirroot . '/comment/lib.php');
196 if (!$this->get('id')) {
197 throw new \coding_exception('The plan must exist.');
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,
208 $comment->set_fullwidth(true);
209 return $comment;
213 * Get the competencies in this plan.
215 * @return competency[]
217 public function get_competencies() {
218 $competencies = array();
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'));
231 return $competencies;
235 * Get a single competency from this plan.
237 * This will throw an exception if the competency does not belong to the plan.
239 * @param int $competencyid The competency ID.
240 * @return competency
242 public function get_competency($competencyid) {
243 $competency = null;
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);
255 return $competency;
259 * Get the context in which the plan is attached.
261 * @return context_user
263 public function get_context() {
264 return context_user::instance($this->get('userid'));
268 * Human readable status name.
270 * @return string
272 public function get_statusname() {
274 $status = $this->get('status');
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;
297 return get_string('planstatus' . $strname, 'core_competency');
301 * Get the plan template.
303 * @return template|null
305 public function get_template() {
306 $templateid = $this->get('templateid');
307 if ($templateid === null) {
308 return null;
310 return new template($templateid);
314 * Is the plan in draft mode?
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.
319 * @return boolean
321 public function is_draft() {
322 return in_array($this->get('status'), static::get_draft_statuses());
326 * Validate the template ID.
328 * @param mixed $value The value.
329 * @return true|lang_string
331 protected function validate_templateid($value) {
333 // Checks that the template exists.
334 if (!empty($value) && !template::record_exists($value)) {
335 return new lang_string('invaliddata', 'error');
338 return true;
342 * Validate the user ID.
344 * @param int $value
345 * @return true|lang_string
347 protected function validate_userid($value) {
348 global $DB;
350 // During create.
351 if (!$this->get('id')) {
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');
361 return true;
365 * Can the current user comment on a user's plan?
367 * @param int $planuserid The user ID the plan belongs to.
368 * @return bool
370 public static function can_comment_user($planuserid) {
371 global $USER;
373 $capabilities = array('moodle/competency:plancomment');
374 if ($USER->id == $planuserid) {
375 $capabilities[] = 'moodle/competency:plancommentown';
378 return has_any_capability($capabilities, context_user::instance($planuserid));
382 * Can the current user manage a user's plan?
384 * @param int $planuserid The user to whom the plan would belong.
385 * @return bool
387 public static function can_manage_user($planuserid) {
388 global $USER;
389 $context = context_user::instance($planuserid);
391 $capabilities = array('moodle/competency:planmanage');
392 if ($context->instanceid == $USER->id) {
393 $capabilities[] = 'moodle/competency:planmanageown';
396 return has_any_capability($capabilities, $context);
400 * Can the current user manage a user's draft plan?
402 * @param int $planuserid The user to whom the plan would belong.
403 * @return bool
405 public static function can_manage_user_draft($planuserid) {
406 global $USER;
407 $context = context_user::instance($planuserid);
409 $capabilities = array('moodle/competency:planmanagedraft');
410 if ($context->instanceid == $USER->id) {
411 $capabilities[] = 'moodle/competency:planmanageowndraft';
414 return has_any_capability($capabilities, $context);
418 * Can the current user read the comments on a user's plan?
420 * @param int $planuserid The user ID the plan belongs to.
421 * @return bool
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);
429 * Can the current user view a user's plan?
431 * @param int $planuserid The user to whom the plan would belong.
432 * @return bool
434 public static function can_read_user($planuserid) {
435 global $USER;
436 $context = context_user::instance($planuserid);
438 $capabilities = array('moodle/competency:planview');
439 if ($context->instanceid == $USER->id) {
440 $capabilities[] = 'moodle/competency:planviewown';
443 return has_any_capability($capabilities, $context)
444 || self::can_manage_user($planuserid);
448 * Can the current user view a user's draft plan?
450 * @param int $planuserid The user to whom the plan would belong.
451 * @return bool
453 public static function can_read_user_draft($planuserid) {
454 global $USER;
455 $context = context_user::instance($planuserid);
457 $capabilities = array('moodle/competency:planviewdraft');
458 if ($context->instanceid == $USER->id) {
459 $capabilities[] = 'moodle/competency:planviewowndraft';
462 return has_any_capability($capabilities, $context)
463 || self::can_manage_user_draft($planuserid);
467 * Can the current user request the draft to be reviewed.
469 * @param int $planuserid The user to whom the plan would belong.
470 * @return bool
472 public static function can_request_review_user($planuserid) {
473 global $USER;
475 $capabilities = array('moodle/competency:planrequestreview');
476 if ($USER->id == $planuserid) {
477 $capabilities[] = 'moodle/competency:planrequestreviewown';
480 return has_any_capability($capabilities, context_user::instance($planuserid));
484 * Can the current user review the plan.
486 * This means being able to send the plan from draft to active, and vice versa.
488 * @param int $planuserid The user to whom the plan would belong.
489 * @return bool
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);
497 * Get the plans of a user containing a specific competency.
499 * @param int $userid The user ID.
500 * @param int $competencyid The competency ID.
501 * @return plans[]
503 public static function get_by_user_and_competency($userid, $competencyid) {
504 global $DB;
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';
523 $params = array(
524 'competencyid1' => $competencyid,
525 'competencyid2' => $competencyid,
526 'competencyid3' => $competencyid,
527 'userid' => $userid
530 $plans = array();
531 $records = $DB->get_records_sql($sql, $params);
532 foreach ($records as $record) {
533 $plans[$record->id] = new plan(0, $record);
536 return $plans;
540 * Get the list of draft statuses.
542 * @return array Contains the status constants.
544 public static function get_draft_statuses() {
545 return array(self::STATUS_DRAFT, self::STATUS_WAITING_FOR_REVIEW, self::STATUS_IN_REVIEW);
549 * Get the recordset of the plans that are due, incomplete and not draft.
551 * @return \moodle_recordset
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);
561 * Return a list of status depending on capabilities.
563 * @param int $userid The user to whom the plan would belong.
564 * @return array
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');
571 if (self::can_manage_user($userid)) {
572 $status[self::STATUS_ACTIVE] = get_string('planstatusactive', 'core_competency');
574 return $status;
578 * Update from template.
580 * Bulk update a lot of plans from a template
582 * @param template $template
583 * @return bool
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.');
592 $params = array(
593 'templateid' => $template->get('id'),
594 'status' => self::STATUS_COMPLETE,
596 'name' => $template->get('shortname'),
597 'description' => $template->get('description'),
598 'descriptionformat' => $template->get('descriptionformat'),
599 'duedate' => $template->get('duedate'),
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";
610 return $DB->execute($sql, $params);
614 * Check if a template is associated to the plan.
616 * @return bool
618 public function is_based_on_template() {
619 return $this->get('templateid') !== null;
623 * Check if plan can be edited.
625 * @return bool
627 public function can_be_edited() {
628 return !$this->is_based_on_template() && $this->get('status') != self::STATUS_COMPLETE && $this->can_manage();
632 * Validate the due date.
633 * When setting a due date it must not exceed the DUEDATE_THRESHOLD.
635 * @param int $value The due date.
636 * @return bool|lang_string
638 protected function validate_duedate($value) {
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;
648 // During update.
649 if ($this->get('id')) {
650 $before = $this->beforeupdate->get('duedate');
651 $beforestatus = $this->beforeupdate->get('status');
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;
660 if ($value <= time()) {
661 // We cannot set the date in the past.
662 return new lang_string('errorcannotsetduedateinthepast', 'core_competency');
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');
670 return true;
674 * Checks if a template has user plan records.
676 * @param int $templateid The template ID
677 * @return boolean
679 public static function has_records_for_template($templateid) {
680 return self::record_exists_select('templateid = ?', array($templateid));
684 * Count the number of plans for a template, optionally filtering by status.
686 * @param int $templateid The template ID
687 * @param int $status The plan status. 0 means all statuses.
688 * @return int
690 public static function count_records_for_template($templateid, $status) {
691 $filters = array('templateid' => $templateid);
692 if ($status > 0) {
693 $filters['status'] = $status;
695 return self::count_records($filters);
699 * Get the plans for a template, optionally filtering by status.
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
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;
712 return self::get_records($filters, $skip, $limit);