MDL-54613 unit tests: Add iteminstance to test grade_item
[moodle.git] / lib / grade / grade_outcome.php
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/>.
17 /**
18  * Definition of grade outcome class
19  *
20  * @package   core_grades
21  * @category  grade
22  * @copyright 2006 Nicolas Connault
23  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24  */
26 defined('MOODLE_INTERNAL') || die();
28 require_once('grade_object.php');
30 /**
31  * Class representing a grade outcome.
32  *
33  * It is responsible for handling its DB representation, modifying and returning its metadata.
34  *
35  * @package   core_grades
36  * @category  grade
37  * @copyright 2006 Nicolas Connault
38  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
39  */
40 class grade_outcome extends grade_object {
41     /**
42      * DB Table (used by grade_object).
43      * @var string $table
44      */
45     public $table = 'grade_outcomes';
47     /**
48      * Array of required table fields, must start with 'id'.
49      * @var array $required_fields
50      */
51     public $required_fields = array('id', 'courseid', 'shortname', 'fullname', 'scaleid','description',
52                                  'descriptionformat', 'timecreated', 'timemodified', 'usermodified');
54     /**
55      * The course this outcome belongs to.
56      * @var int $courseid
57      */
58     public $courseid;
60     /**
61      * The shortname of the outcome.
62      * @var string $shortname
63      */
64     public $shortname;
66     /**
67      * The fullname of the outcome.
68      * @var string $fullname
69      */
70     public $fullname;
72     /**
73      * A full grade_scale object referenced by $this->scaleid.
74      * @var object $scale
75      */
76     public $scale;
78     /**
79      * The id of the scale referenced by this outcome.
80      * @var int $scaleid
81      */
82     public $scaleid;
84     /**
85      * The description of this outcome - FORMAT_MOODLE.
86      * @var string $description
87      */
88     public $description;
90     /**
91      * The userid of the person who last modified this outcome.
92      *
93      * @var int $usermodified
94      */
95     public $usermodified;
97     /**
98      * Deletes this outcome from the database.
99      *
100      * @param string $source from where was the object deleted (mod/forum, manual, etc.)
101      * @return bool success
102      */
103     public function delete($source=null) {
104         global $DB;
105         if (!empty($this->courseid)) {
106             $DB->delete_records('grade_outcomes_courses', array('outcomeid' => $this->id, 'courseid' => $this->courseid));
107         }
108         if (parent::delete($source)) {
109             $context = context_system::instance();
110             $fs = get_file_storage();
111             $files = $fs->get_area_files($context->id, 'grade', 'outcome', $this->id);
112             foreach ($files as $file) {
113                 $file->delete();
114             }
115             return true;
116         }
117         return false;
118     }
120     /**
121      * Records this object in the Database, sets its id to the returned value, and returns that value.
122      * If successful this function also fetches the new object data from database and stores it
123      * in object properties.
124      *
125      * @param string $source from where was the object inserted (mod/forum, manual, etc.)
126      * @return int PK ID if successful, false otherwise
127      */
128     public function insert($source=null) {
129         global $DB;
131         $this->timecreated = $this->timemodified = time();
133         if ($result = parent::insert($source)) {
134             if (!empty($this->courseid)) {
135                 $goc = new stdClass();
136                 $goc->courseid = $this->courseid;
137                 $goc->outcomeid = $this->id;
138                 $DB->insert_record('grade_outcomes_courses', $goc);
139             }
140         }
141         return $result;
142     }
144     /**
145      * In addition to update() it also updates grade_outcomes_courses if needed
146      *
147      * @param string $source from where was the object inserted
148      * @return bool success
149      */
150     public function update($source=null) {
151         $this->timemodified = time();
153         if ($result = parent::update($source)) {
154             if (!empty($this->courseid)) {
155                 $this->use_in($this->courseid);
156             }
157         }
158         return $result;
159     }
161     /**
162      * Mark outcome as used in a course
163      *
164      * @param int $courseid
165      * @return False if invalid courseid requested
166      */
167     public function use_in($courseid) {
168         global $DB;
169         if (!empty($this->courseid) and $courseid != $this->courseid) {
170             return false;
171         }
173         if (!$DB->record_exists('grade_outcomes_courses', array('courseid' => $courseid, 'outcomeid' => $this->id))) {
174             $goc = new stdClass();
175             $goc->courseid  = $courseid;
176             $goc->outcomeid = $this->id;
177             $DB->insert_record('grade_outcomes_courses', $goc);
178         }
179         return true;
180     }
182     /**
183      * Finds and returns a grade_outcome instance based on params.
184      *
185      * @static
186      * @param array $params associative arrays varname=>value
187      * @return object grade_outcome instance or false if none found.
188      */
189     public static function fetch($params) {
190         return grade_object::fetch_helper('grade_outcomes', 'grade_outcome', $params);
191     }
193     /**
194      * Finds and returns all grade_outcome instances based on params.
195      *
196      * @static
197      * @param array $params associative arrays varname=>value
198      * @return array array of grade_outcome insatnces or false if none found.
199      */
200     public static function fetch_all($params) {
201         return grade_object::fetch_all_helper('grade_outcomes', 'grade_outcome', $params);
202     }
204     /**
205      * Instantiates a grade_scale object whose data is retrieved from the database
206      *
207      * @return grade_scale
208      */
209     public function load_scale() {
210         if (empty($this->scale->id) or $this->scale->id != $this->scaleid) {
211             $this->scale = grade_scale::fetch(array('id'=>$this->scaleid));
212             $this->scale->load_items();
213         }
214         return $this->scale;
215     }
217     /**
218      * Static function returning all global outcomes
219      *
220      * @static
221      * @return array
222      */
223     public static function fetch_all_global() {
224         if (!$outcomes = grade_outcome::fetch_all(array('courseid'=>null))) {
225             $outcomes = array();
226         }
227         return $outcomes;
228     }
230     /**
231      * Static function returning all local course outcomes
232      *
233      * @static
234      * @param int $courseid
235      * @return array
236      */
237     public static function fetch_all_local($courseid) {
238         if (!$outcomes =grade_outcome::fetch_all(array('courseid'=>$courseid))) {
239             $outcomes = array();
240         }
241         return $outcomes;
242     }
244     /**
245      * Static method that returns all outcomes available in course
246      *
247      * @static
248      * @param int $courseid
249      * @return array
250      */
251     public static function fetch_all_available($courseid) {
252         global $CFG, $DB;
254         $result = array();
255         $params = array($courseid);
256         $sql = "SELECT go.*
257                   FROM {grade_outcomes} go, {grade_outcomes_courses} goc
258                  WHERE go.id = goc.outcomeid AND goc.courseid = ?
259               ORDER BY go.id ASC";
261         if ($datas = $DB->get_records_sql($sql, $params)) {
262             foreach($datas as $data) {
263                 $instance = new grade_outcome();
264                 grade_object::set_properties($instance, $data);
265                 $result[$instance->id] = $instance;
266             }
267         }
268         return $result;
269     }
272     /**
273      * Returns the most descriptive field for this object. This is a standard method used
274      * when we do not know the exact type of an object.
275      *
276      * @return string name
277      */
278     public function get_name() {
279         return format_string($this->fullname);
280     }
282     /**
283      * Returns unique outcome short name.
284      *
285      * @return string name
286      */
287     public function get_shortname() {
288         return $this->shortname;
289     }
291     /**
292      * Returns the formatted grade description with URLs converted
293      *
294      * @return string
295      */
296     public function get_description() {
297         global $CFG;
298         require_once($CFG->libdir . '/filelib.php');
300         $options = new stdClass;
301         $options->noclean = true;
302         $systemcontext = context_system::instance();
303         $description = file_rewrite_pluginfile_urls($this->description, 'pluginfile.php', $systemcontext->id, 'grade', 'outcome', $this->id);
304         return format_text($description, $this->descriptionformat, $options);
305     }
307     /**
308      * Checks if outcome can be deleted.
309      *
310      * @return bool
311      */
312     public function can_delete() {
313         if ($this->get_item_uses_count()) {
314             return false;
315         }
316         if (empty($this->courseid)) {
317             if ($this->get_course_uses_count()) {
318                 return false;
319             }
320         }
321         return true;
322     }
324     /**
325      * Returns the number of places where outcome is used.
326      *
327      * @return int
328      */
329     public function get_course_uses_count() {
330         global $DB;
332         if (!empty($this->courseid)) {
333             return 1;
334         }
336         return $DB->count_records('grade_outcomes_courses', array('outcomeid' => $this->id));
337     }
339     /**
340      * Returns the number of grade items that use this grade outcome
341      *
342      * @return int
343      */
344     public function get_item_uses_count() {
345         global $DB;
346         return $DB->count_records('grade_items', array('outcomeid' => $this->id));
347     }
349     /**
350      * Computes then returns extra information about this outcome and other objects that are linked to it.
351      * The average of all grades that use this outcome, for all courses (or 1 course if courseid is given) can
352      * be requested, and is returned as a float if requested alone. If the list of items that use this outcome
353      * is also requested, then a single array is returned, which contains the grade_items AND the average grade
354      * if such is still requested (array('items' => array(...), 'avg' => 2.30)). This combining of two
355      * methods into one is to save on DB queries, since both queries are similar and can be performed together.
356      *
357      * @param int $courseid An optional courseid to narrow down the average to 1 course only
358      * @param bool $average Whether or not to return the average grade for this outcome
359      * @param bool $items Whether or not to return the list of items using this outcome
360      * @return float
361      */
362     public function get_grade_info($courseid=null, $average=true, $items=false) {
363         global $CFG, $DB;
365         if (!isset($this->id)) {
366             debugging("You must setup the outcome's id before calling its get_grade_info() method!");
367             return false; // id must be defined for this to work
368         }
370         if ($average === false && $items === false) {
371             debugging('Either the 1st or 2nd param of grade_outcome::get_grade_info() must be true, or both, but not both false!');
372             return false;
373         }
375         $params = array($this->id);
377         $wheresql = '';
378         if (!is_null($courseid)) {
379             $wheresql = " AND {grade_items}.courseid = ? ";
380             $params[] = $courseid;
381         }
383         $selectadd = '';
384         if ($items !== false) {
385             $selectadd = ", {grade_items}.* ";
386         }
388         $sql = "SELECT finalgrade $selectadd
389                   FROM {grade_grades}, {grade_items}, {grade_outcomes}
390                  WHERE {grade_outcomes}.id = {grade_items}.outcomeid
391                    AND {grade_items}.id = {grade_grades}.itemid
392                    AND {grade_outcomes}.id = ?
393                    $wheresql";
395         $grades = $DB->get_records_sql($sql, $params);
396         $retval = array();
398         if ($average !== false && count($grades) > 0) {
399             $count = 0;
400             $total = 0;
402             foreach ($grades as $k => $grade) {
403                 // Skip null finalgrades
404                 if (!is_null($grade->finalgrade)) {
405                     $total += $grade->finalgrade;
406                     $count++;
407                 }
408                 unset($grades[$k]->finalgrade);
409             }
411             $retval['avg'] = $total / $count;
412         }
414         if ($items !== false) {
415             foreach ($grades as $grade) {
416                 $retval['items'][$grade->id] = new grade_item($grade);
417             }
418         }
420         return $retval;
421     }