2 // This file is part of Moodle - http://moodle.org/
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.
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/>.
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();
29 use dml_missing_record_exception
;
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';
43 const STATUS_DRAFT
= 0;
46 const STATUS_ACTIVE
= 1;
48 /** Complete status. */
49 const STATUS_COMPLETE
= 2;
51 /** Waiting for review. */
52 const STATUS_WAITING_FOR_REVIEW
= 3;
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;
64 * Return the definition of the properties of this model.
68 protected static function define_properties() {
73 'description' => array(
74 'type' => PARAM_CLEANHTML
,
77 'descriptionformat' => array(
78 'choices' => array(FORMAT_HTML
, FORMAT_MOODLE
, FORMAT_PLAIN
, FORMAT_MARKDOWN
),
80 'default' => FORMAT_HTML
,
85 'templateid' => array(
88 'null' => NULL_ALLOWED
,
90 'origtemplateid' => array(
93 'null' => NULL_ALLOWED
,
96 'choices' => array(self
::STATUS_DRAFT
, self
::STATUS_COMPLETE
, self
::STATUS_ACTIVE
,
97 self
::STATUS_WAITING_FOR_REVIEW
, self
::STATUS_IN_REVIEW
),
99 'default' => self
::STATUS_DRAFT
,
105 'reviewerid' => array(
108 'null' => NULL_ALLOWED
,
114 * Hook to execute before validate.
118 protected function before_validate() {
119 $this->beforeupdate
= null;
122 if ($this->get('id')) {
123 $this->beforeupdate
= new self($this->get('id'));
128 * Whether the current user can comment on this plan.
132 public function can_comment() {
133 return static::can_comment_user($this->get('userid'));
137 * Whether the current user can manage the plan.
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.
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.
165 public function can_read_comments() {
166 return $this->can_read();
170 * Whether the current user can request a review of the plan.
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.
183 public function can_review() {
184 return self
::can_review_user($this->get('userid'));
188 * Get the comment object.
192 public function get_comment_object() {
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'),
208 $comment->set_fullwidth(true);
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'));
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.
242 public function get_competency($competencyid) {
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);
252 // Get the competency from the plan.
253 $competency = plan_competency
::get_competency($this->get('id'), $competencyid);
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.
272 public function get_statusname() {
274 $status = $this->get('status');
277 case self
::STATUS_DRAFT
:
280 case self
::STATUS_IN_REVIEW
:
281 $strname = 'inreview';
283 case self
::STATUS_WAITING_FOR_REVIEW
:
284 $strname = 'waitingforreview';
286 case self
::STATUS_ACTIVE
:
289 case self
::STATUS_COMPLETE
:
290 $strname = 'complete';
293 throw new \
moodle_exception('errorplanstatus', 'core_competency', '', $status);
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) {
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.
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');
342 * Validate the user ID.
345 * @return true|lang_string
347 protected function validate_userid($value) {
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');
365 * Can the current user comment on a user's plan?
367 * @param int $planuserid The user ID the plan belongs to.
370 public static function can_comment_user($planuserid) {
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.
387 public static function can_manage_user($planuserid) {
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.
405 public static function can_manage_user_draft($planuserid) {
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.
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.
434 public static function can_read_user($planuserid) {
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.
453 public static function can_read_user_draft($planuserid) {
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.
472 public static function can_request_review_user($planuserid) {
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.
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.
503 public static function get_by_user_and_competency($userid, $competencyid) {
507 FROM {' . self
::TABLE
. '} p
508 LEFT JOIN {' . plan_competency
::TABLE
. '} pc
510 AND pc.competencyid = :competencyid1
511 LEFT JOIN {' . user_competency_plan
::TABLE
. '} ucp
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)
524 'competencyid1' => $competencyid,
525 'competencyid2' => $competencyid,
526 'competencyid3' => $competencyid,
531 $records = $DB->get_records_sql($sql, $params);
532 foreach ($records as $record) {
533 $plans[$record->id
] = new plan(0, $record);
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() {
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.
566 public static function get_status_list($userid) {
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');
578 * Update from template.
580 * Bulk update a lot of plans from a template
582 * @param template $template
585 public static function update_multiple_from_template(template
$template) {
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.');
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
. "}
604 description = :description,
605 descriptionformat = :descriptionformat,
607 WHERE templateid = :templateid
608 AND status != :status";
610 return $DB->execute($sql, $params);
614 * Check if a template is associated to the plan.
618 public function is_based_on_template() {
619 return $this->get('templateid') !== null;
623 * Check if plan can be edited.
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()
643 ||
$this->get('status') == self
::STATUS_COMPLETE
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())) {
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');
674 * Checks if a template has user plan records.
676 * @param int $templateid The template ID
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.
690 public static function count_records_for_template($templateid, $status) {
691 $filters = array('templateid' => $templateid);
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
707 public static function get_records_for_template($templateid, $status = 0, $skip = 0, $limit = 100) {
708 $filters = array('templateid' => $templateid);
710 $filters['status'] = $status;
712 return self
::get_records($filters, $skip, $limit);