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 user_competency persistence.
19 *
20 * @package    core_competency
21 * @copyright  2015 Serge Gauthier
22 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 */
24namespace core_competency;
25defined('MOODLE_INTERNAL') || die();
26
27use coding_exception;
28use context_course;
29use context_user;
30use comment;
31use lang_string;
32
33/**
34 * Class for loading/storing user_competency from the DB.
35 *
36 * @copyright  2015 Serge Gauthier
37 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
38 */
39class user_competency extends persistent {
40
41    /** Table name for user_competency persistency */
42    const TABLE = 'competency_usercomp';
43
44    /** Idle status */
45    const STATUS_IDLE = 0;
46
47    /** Waiting for review status */
48    const STATUS_WAITING_FOR_REVIEW = 1;
49
50    /** In review status */
51    const STATUS_IN_REVIEW = 2;
52
53    /**
54     * Return the definition of the properties of this model.
55     *
56     * @return array
57     */
58    protected static function define_properties() {
59        return array(
60            'userid' => array(
61                'type' => PARAM_INT,
62            ),
63            'competencyid' => array(
64                'type' => PARAM_INT,
65            ),
66            'status' => array(
67                'choices' => array(
68                    self::STATUS_IDLE,
69                    self::STATUS_WAITING_FOR_REVIEW,
70                    self::STATUS_IN_REVIEW,
71                ),
72                'type' => PARAM_INT,
73                'default' => self::STATUS_IDLE,
74            ),
75            'reviewerid' => array(
76                'type' => PARAM_INT,
77                'default' => null,
78                'null' => NULL_ALLOWED,
79            ),
80            'proficiency' => array(
81                'type' => PARAM_BOOL,
82                'default' => null,
83                'null' => NULL_ALLOWED,
84            ),
85            'grade' => array(
86                'type' => PARAM_INT,
87                'default' => null,
88                'null' => NULL_ALLOWED,
89            ),
90        );
91    }
92
93    /**
94     * Whether the current user can comment on this user competency.
95     *
96     * @return bool
97     */
98    public function can_comment() {
99        return static::can_comment_user($this->get('userid'));
100    }
101
102    /**
103     * Whether the current user can read this user competency.
104     *
105     * @return bool
106     */
107    public function can_read() {
108        return static::can_read_user($this->get('userid'));
109    }
110
111    /**
112     * Whether the current user can read comments on this user competency.
113     *
114     * @return bool
115     */
116    public function can_read_comments() {
117        return static::can_read_comments_user($this->get('userid'));
118    }
119
120    /**
121     * Can the current user send the user competency for review?
122     *
123     * @return bool
124     */
125    public function can_request_review() {
126        return static::can_request_review_user($this->get('userid'));
127    }
128
129    /**
130     * Can the current user review the user competency?
131     *
132     * @return bool
133     */
134    public function can_review() {
135        return static::can_review_user($this->get('userid'));
136    }
137
138    /**
139     * Human readable status name.
140     *
141     * @param int $status The status code.
142     * @return lang_string
143     */
144    public static function get_status_name($status) {
145
146        switch ($status) {
147            case self::STATUS_IDLE:
148                $strname = 'idle';
149                break;
150            case self::STATUS_WAITING_FOR_REVIEW:
151                $strname = 'waitingforreview';
152                break;
153            case self::STATUS_IN_REVIEW:
154                $strname = 'inreview';
155                break;
156            default:
157                throw new \moodle_exception('errorusercomptencystatus', 'core_competency', '', $status);
158                break;
159        }
160
161        return new lang_string('usercompetencystatus_' . $strname, 'core_competency');
162    }
163
164    /**
165     * Get list of competency status.
166     *
167     * @return array
168     */
169    public static function get_status_list() {
170
171        static $list = null;
172
173        if ($list === null) {
174            $list = array(
175                self::STATUS_IDLE => self::get_status_name(self::STATUS_IDLE),
176                self::STATUS_WAITING_FOR_REVIEW => self::get_status_name(self::STATUS_WAITING_FOR_REVIEW),
177                self::STATUS_IN_REVIEW => self::get_status_name(self::STATUS_IN_REVIEW));
178        }
179
180        return $list;
181    }
182
183    /**
184     * Get the comment object.
185     *
186     * @return comment
187     */
188    public function get_comment_object() {
189        global $CFG;
190        require_once($CFG->dirroot . '/comment/lib.php');
191
192        if (!$this->get('id')) {
193            throw new coding_exception('The user competency record must exist.');
194        }
195
196        $comment = new comment((object) array(
197            'context' => $this->get_context(),
198            'component' => 'competency',    // This cannot be named 'core_competency'.
199            'itemid' => $this->get('id'),
200            'area' => 'user_competency',
201            'showcount' => true,
202        ));
203        $comment->set_fullwidth(true);
204        return $comment;
205    }
206
207    /**
208     * Return the competency Object.
209     *
210     * @return competency Competency Object
211     */
212    public function get_competency() {
213        return new competency($this->get('competencyid'));
214    }
215
216    /**
217     * Get the context.
218     *
219     * @return context The context.
220     */
221    public function get_context() {
222        return context_user::instance($this->get('userid'));
223    }
224
225    /**
226     * Find the plans for the user and this competency.
227     *
228     * Note that this:
229     * - does not perform any capability check.
230     * - may return completed plans.
231     * - may return an empty array.
232     *
233     * @return plans[]
234     */
235    public function get_plans() {
236        return plan::get_by_user_and_competency($this->get('userid'), $this->get('competencyid'));
237    }
238
239    /**
240     * Validate the user ID.
241     *
242     * @param int $value The value.
243     * @return true|lang_string
244     */
245    protected function validate_userid($value) {
246        global $DB;
247
248        if (!$DB->record_exists('user', array('id' => $value))) {
249            return new lang_string('invaliduserid', 'error');
250        }
251
252        return true;
253    }
254
255    /**
256     * Validate the competency ID.
257     *
258     * @param int $value The value.
259     * @return true|lang_string
260     */
261    protected function validate_competencyid($value) {
262        if (!competency::record_exists($value)) {
263            return new lang_string('errornocompetency', 'core_competency', $value);
264        }
265
266        return true;
267    }
268
269    /**
270     * Validate the proficiency.
271     *
272     * @param int $value The value.
273     * @return true|lang_string
274     */
275    protected function validate_proficiency($value) {
276        $grade = $this->get('grade');
277
278        if ($grade !== null && $value === null) {
279            // We must set a proficiency when we set a grade.
280            return new lang_string('invaliddata', 'error');
281
282        } else if ($grade === null && $value !== null) {
283            // We must not set a proficiency when we don't set a grade.
284            return new lang_string('invaliddata', 'error');
285        }
286
287        return true;
288    }
289
290    /**
291     * Validate the reviewer ID.
292     *
293     * @param int $value The value.
294     * @return true|lang_string
295     */
296    protected function validate_reviewerid($value) {
297        global $DB;
298
299        if ($value !== null && !$DB->record_exists('user', array('id' => $value))) {
300            return new lang_string('invaliduserid', 'error');
301        }
302
303        return true;
304    }
305
306    /**
307     * Validate the grade.
308     *
309     * @param int $value The value.
310     * @return true|lang_string
311     */
312    protected function validate_grade($value) {
313        if ($value !== null) {
314            if ($value <= 0) {
315                return new lang_string('invalidgrade', 'core_competency');
316            }
317
318            // TODO MDL-52243 Use a core method to validate the grade_scale item.
319            // Check if grade exist in the scale item values.
320            $competency = $this->get_competency();
321            if (!array_key_exists($value - 1 , $competency->get_scale()->scale_items)) {
322                return new lang_string('invalidgrade', 'core_competency');
323            }
324        }
325
326        return true;
327    }
328
329    /**
330     * Can the current user comment on a user's competency?
331     *
332     * @param int $userid The user ID the competency belongs to.
333     * @return bool
334     */
335    public static function can_comment_user($userid) {
336        global $USER;
337
338        $capabilities = array('moodle/competency:usercompetencycomment');
339        if ($USER->id == $userid) {
340            $capabilities[] = 'moodle/competency:usercompetencycommentown';
341        }
342
343        if (has_any_capability($capabilities, context_user::instance($userid))) {
344            return true;
345        }
346
347        return false;
348    }
349
350    /**
351     * Can the current user grade a user's user competency?
352     *
353     * @param int $userid The user ID the competency belongs to.
354     * @return bool
355     */
356    public static function can_grade_user($userid) {
357        $ratecap = 'moodle/competency:competencygrade';
358        return has_capability($ratecap, context_user::instance($userid));
359    }
360
361    /**
362     * Can the current user grade a user's user competency in a course?
363     *
364     * @param int $userid The user ID the competency belongs to.
365     * @param int $courseid The course ID.
366     * @return bool
367     */
368    public static function can_grade_user_in_course($userid, $courseid) {
369        $ratecap = 'moodle/competency:competencygrade';
370        return has_capability($ratecap, context_course::instance($courseid))
371            || static::can_grade_user($userid);
372    }
373
374    /**
375     * Can the current user read the comments on a user's competency?
376     *
377     * @param int $userid The user ID the competency belongs to.
378     * @return bool
379     */
380    public static function can_read_comments_user($userid) {
381        // Everyone who can read the user competency can read the comments.
382        return static::can_read_user($userid);
383    }
384
385    /**
386     * Can the current user read the user competencies of a user in a course?
387     *
388     * @param int $userid The user ID the competency belongs to.
389     * @param int $courseid The course ID.
390     * @return bool
391     */
392    public static function can_read_user_in_course($userid, $courseid) {
393        $capability = 'moodle/competency:usercompetencyview';
394        return has_capability($capability, context_course::instance($courseid))
395            || static::can_read_user($userid);
396    }
397
398    /**
399     * Can the current user read a user's competency?
400     *
401     * @param int $userid The user ID the competency belongs to.
402     * @return bool
403     */
404    public static function can_read_user($userid) {
405        $capability = 'moodle/competency:usercompetencyview';
406        return has_capability($capability, context_user::instance($userid))
407            || plan::can_read_user($userid);
408    }
409
410    /**
411     * Can the current user send a user's competency for review?
412     *
413     * Note that the status 'review' is not meant to be used for student to self-assess
414     * themselves, then to ask the teacher to review their assessment. It is more intended
415     * for a student to provide evidence of prior learning and request their review.
416     *
417     * @param int $userid The user ID the competency belongs to.
418     * @return bool
419     */
420    public static function can_request_review_user($userid) {
421        global $USER;
422
423        $capabilities = array('moodle/competency:usercompetencyrequestreview');
424        if ($USER->id == $userid) {
425            $capabilities[] = 'moodle/competency:usercompetencyrequestreviewown';
426        }
427
428        if (has_any_capability($capabilities, context_user::instance($userid))) {
429            return true;
430        }
431
432        return false;
433    }
434
435    /**
436     * Can the current user review the user competency?
437     *
438     * @param int $userid The user ID the competency belongs to.
439     * @return bool
440     */
441    public static function can_review_user($userid) {
442        $capability = 'moodle/competency:usercompetencyreview';
443        return has_capability($capability, context_user::instance($userid));
444    }
445
446    /**
447     * Create a new user_competency object.
448     *
449     * Note, this is intended to be used to create a blank relation, for instance when
450     * the record was not found in the database. This does not save the model.
451     *
452     * @param  int $userid The user ID.
453     * @param  int $competencyid The competency ID.
454     * @return \core_competency\user_competency
455     */
456    public static function create_relation($userid, $competencyid) {
457        $relation = new user_competency(0, (object) array('userid' => $userid, 'competencyid' => $competencyid));
458        return $relation;
459    }
460
461    /**
462     * Fetch a competency by user competency ID.
463     *
464     * This is a convenience method to attempt to efficiently fetch a competency when
465     * the only information we have is the user_competency ID, in evidence for instance.
466     *
467     * @param  int $id The user competency ID.
468     * @return competency
469     */
470    public static function get_competency_by_usercompetencyid($id) {
471        global $DB;
472        $sql = "SELECT c.*
473                  FROM {" . self::TABLE . "} uc
474                  JOIN {" . competency::TABLE . "} c
475                    ON c.id = uc.competencyid
476                 WHERE uc.id = ?";
477        $record = $DB->get_record_sql($sql, array($id), MUST_EXIST);
478        return new competency(0, $record);
479    }
480
481    /**
482     * Get multiple user_competency for a user.
483     *
484     * @param  int $userid
485     * @param  array  $competenciesorids Limit search to those competencies, or competency IDs.
486     * @return \core_competency\user_competency[]
487     */
488    public static function get_multiple($userid, array $competenciesorids = null) {
489        global $DB;
490
491        $params = array();
492        $params['userid'] = $userid;
493        $sql = '1 = 1';
494
495        if (!empty($competenciesorids)) {
496            $test = reset($competenciesorids);
497            if (is_number($test)) {
498                $ids = $competenciesorids;
499            } else {
500                $ids = array();
501                foreach ($competenciesorids as $comp) {
502                    $ids[] = $comp->get('id');
503                }
504            }
505
506            list($insql, $inparams) = $DB->get_in_or_equal($ids, SQL_PARAMS_NAMED);
507            $params += $inparams;
508            $sql = "competencyid $insql";
509        }
510
511        // Order by ID to prevent random ordering.
512        return self::get_records_select("userid = :userid AND $sql", $params, 'id ASC');
513    }
514
515    /**
516     * Checks if a competency has user competency records.
517     *
518     * @param  int $competencyid The competency ID
519     * @return boolean
520     */
521    public static function has_records_for_competency($competencyid) {
522        return self::record_exists_select('competencyid = ?', array($competencyid));
523    }
524
525    /**
526     * Checks if any of the competencies of a framework has a user competency record.
527     *
528     * @param  int $frameworkid The competency framework ID.
529     * @return boolean
530     */
531    public static function has_records_for_framework($frameworkid) {
532        global $DB;
533
534        $sql = "SELECT 'x'
535                  FROM {" . self::TABLE . "} uc
536                  JOIN {" . competency::TABLE . "} c
537                    ON uc.competencyid = c.id
538                 WHERE c.competencyframeworkid = ?";
539        $params = array($frameworkid);
540
541        return $DB->record_exists_sql($sql, $params);
542    }
543
544    /**
545     * Check if user competency has records for competencies.
546     *
547     * @param array $competencyids The competencies ids.
548     * @return boolean Return true if the delete was successfull.
549     */
550    public static function has_records_for_competencies($competencyids) {
551        global $DB;
552        list($insql, $params) = $DB->get_in_or_equal($competencyids, SQL_PARAMS_NAMED);
553        return self::record_exists_select("competencyid $insql", $params);
554    }
555
556}
557