654228e622c06ac68bdd983c46bfc50187cefc3b
[moodle.git] / lib / grade / grade_object.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 a grade object class for grade item, grade category etc to inherit from
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 /**
29  * An abstract object that holds methods and attributes common to all grade_* objects defined here.
30  *
31  * @package   core_grades
32  * @category  grade
33  * @copyright 2006 Nicolas Connault
34  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
35  */
36 abstract class grade_object {
37     /**
38      * The database table this grade object is stored in
39      * @var string $table
40      */
41     public $table;
43     /**
44      * Array of required table fields, must start with 'id'.
45      * @var array $required_fields
46      */
47     public $required_fields = array('id', 'timecreated', 'timemodified', 'hidden');
49     /**
50      * Array of optional fields with default values - usually long text information that is not always needed.
51      * If you want to create an instance without optional fields use: new grade_object($only_required_fields, false);
52      * @var array $optional_fields
53      */
54     public $optional_fields = array();
56     /**
57      * The PK.
58      * @var int $id
59      */
60     public $id;
62     /**
63      * The first time this grade_object was created.
64      * @var int $timecreated
65      */
66     public $timecreated;
68     /**
69      * The last time this grade_object was modified.
70      * @var int $timemodified
71      */
72     public $timemodified;
74     /**
75      * 0 if visible, 1 always hidden or date not visible until
76      * @var int $hidden
77      */
78     var $hidden = 0;
80     /**
81      * Constructor. Optionally (and by default) attempts to fetch corresponding row from the database
82      *
83      * @param array $params An array with required parameters for this grade object.
84      * @param bool $fetch Whether to fetch corresponding row from the database or not,
85      *        optional fields might not be defined if false used
86      */
87     public function __construct($params=NULL, $fetch=true) {
88         if (!empty($params) and (is_array($params) or is_object($params))) {
89             if ($fetch) {
90                 if ($data = $this->fetch($params)) {
91                     grade_object::set_properties($this, $data);
92                 } else {
93                     grade_object::set_properties($this, $this->optional_fields);//apply defaults for optional fields
94                     grade_object::set_properties($this, $params);
95                 }
97             } else {
98                 grade_object::set_properties($this, $params);
99             }
101         } else {
102             grade_object::set_properties($this, $this->optional_fields);//apply defaults for optional fields
103         }
104     }
106     /**
107      * Makes sure all the optional fields are loaded.
108      *
109      * If id present, meaning the instance exists in the database, then data will be fetched from the database.
110      * Defaults are used for new instances.
111      */
112     public function load_optional_fields() {
113         global $DB;
114         foreach ($this->optional_fields as $field=>$default) {
115             if (property_exists($this, $field)) {
116                 continue;
117             }
118             if (empty($this->id)) {
119                 $this->$field = $default;
120             } else {
121                 $this->$field = $DB->get_field($this->table, $field, array('id', $this->id));
122             }
123         }
124     }
126     /**
127      * Finds and returns a grade_object instance based on params.
128      *
129      * @static
130      * @abstract
131      * @param array $params associative arrays varname=>value
132      * @return object grade_object instance or false if none found.
133      */
134     public static function fetch($params) {
135         throw new coding_exception('fetch() method needs to be overridden in each subclass of grade_object');
136     }
138     /**
139      * Finds and returns all grade_object instances based on $params.
140      *
141      * @static
142      * @abstract
143      * @throws coding_exception Throws a coding exception if fetch_all() has not been overriden by the grade object subclass
144      * @param array $params Associative arrays varname=>value
145      * @return array|bool Array of grade_object instances or false if none found.
146      */
147     public static function fetch_all($params) {
148         throw new coding_exception('fetch_all() method needs to be overridden in each subclass of grade_object');
149     }
151     /**
152      * Factory method which uses the parameters to retrieve matching instances from the database
153      *
154      * @param string $table The table to retrieve from
155      * @param string $classname The name of the class to instantiate
156      * @param array $params An array of conditions like $fieldname => $fieldvalue
157      * @return mixed An object instance or false if not found
158      */
159     protected static function fetch_helper($table, $classname, $params) {
160         if ($instances = grade_object::fetch_all_helper($table, $classname, $params)) {
161             if (count($instances) > 1) {
162                 // we should not tolerate any errors here - problems might appear later
163                 print_error('morethanonerecordinfetch','debug');
164             }
165             return reset($instances);
166         } else {
167             return false;
168         }
169     }
171     /**
172      * Factory method which uses the parameters to retrieve all matching instances from the database
173      *
174      * @param string $table The table to retrieve from
175      * @param string $classname The name of the class to instantiate
176      * @param array $params An array of conditions like $fieldname => $fieldvalue
177      * @return array|bool Array of object instances or false if not found
178      */
179     public static function fetch_all_helper($table, $classname, $params) {
180         global $DB; // Need to introspect DB here.
182         $instance = new $classname();
184         $classvars = (array)$instance;
185         $params    = (array)$params;
187         $wheresql = array();
188         $newparams = array();
190         $columns = $DB->get_columns($table); // Cached, no worries.
192         foreach ($params as $var=>$value) {
193             if (!in_array($var, $instance->required_fields) and !array_key_exists($var, $instance->optional_fields)) {
194                 continue;
195             }
196             if (!array_key_exists($var, $columns)) {
197                 continue;
198             }
199             if (is_null($value)) {
200                 $wheresql[] = " $var IS NULL ";
201             } else {
202                 if ($columns[$var]->meta_type === 'X') {
203                     // We have a text/clob column, use the cross-db method for its comparison.
204                     $wheresql[] = ' ' . $DB->sql_compare_text($var) . ' = ' . $DB->sql_compare_text('?') . ' ';
205                 } else {
206                     // Other columns (varchar, integers...).
207                     $wheresql[] = " $var = ? ";
208                 }
209                 $newparams[] = $value;
210             }
211         }
213         if (empty($wheresql)) {
214             $wheresql = '';
215         } else {
216             $wheresql = implode("AND", $wheresql);
217         }
219         global $DB;
220         $rs = $DB->get_recordset_select($table, $wheresql, $newparams);
221         //returning false rather than empty array if nothing found
222         if (!$rs->valid()) {
223             $rs->close();
224             return false;
225         }
227         $result = array();
228         foreach($rs as $data) {
229             $instance = new $classname();
230             grade_object::set_properties($instance, $data);
231             $result[$instance->id] = $instance;
232         }
233         $rs->close();
234         return $result;
235     }
237     /**
238      * Updates this object in the Database, based on its object variables. ID must be set.
239      *
240      * @param string $source from where was the object updated (mod/forum, manual, etc.)
241      * @return bool success
242      */
243     public function update($source=null) {
244         global $USER, $CFG, $DB;
246         if (empty($this->id)) {
247             debugging('Can not update grade object, no id!');
248             return false;
249         }
251         $data = $this->get_record_data();
253         $DB->update_record($this->table, $data);
255         if (empty($CFG->disablegradehistory)) {
256             unset($data->timecreated);
257             $data->action       = GRADE_HISTORY_UPDATE;
258             $data->oldid        = $this->id;
259             $data->source       = $source;
260             $data->timemodified = time();
261             $data->loggeduser   = $USER->id;
262             $DB->insert_record($this->table.'_history', $data);
263         }
265         $this->notify_changed(false);
266         return true;
267     }
269     /**
270      * Deletes this object from the database.
271      *
272      * @param string $source From where was the object deleted (mod/forum, manual, etc.)
273      * @return bool success
274      */
275     public function delete($source=null) {
276         global $USER, $CFG, $DB;
278         if (empty($this->id)) {
279             debugging('Can not delete grade object, no id!');
280             return false;
281         }
283         $data = $this->get_record_data();
285         if ($DB->delete_records($this->table, array('id'=>$this->id))) {
286             if (empty($CFG->disablegradehistory)) {
287                 unset($data->id);
288                 unset($data->timecreated);
289                 $data->action       = GRADE_HISTORY_DELETE;
290                 $data->oldid        = $this->id;
291                 $data->source       = $source;
292                 $data->timemodified = time();
293                 $data->loggeduser   = $USER->id;
294                 $DB->insert_record($this->table.'_history', $data);
295             }
296             $this->notify_changed(true);
297             return true;
299         } else {
300             return false;
301         }
302     }
304     /**
305      * Returns object with fields and values that are defined in database
306      *
307      * @return stdClass
308      */
309     public function get_record_data() {
310         $data = new stdClass();
312         foreach ($this as $var=>$value) {
313             if (in_array($var, $this->required_fields) or array_key_exists($var, $this->optional_fields)) {
314                 if (is_object($value) or is_array($value)) {
315                     debugging("Incorrect property '$var' found when inserting grade object");
316                 } else {
317                     $data->$var = $value;
318                 }
319             }
320         }
321         return $data;
322     }
324     /**
325      * Records this object in the Database, sets its id to the returned value, and returns that value.
326      * If successful this function also fetches the new object data from database and stores it
327      * in object properties.
328      *
329      * @param string $source From where was the object inserted (mod/forum, manual, etc.)
330      * @return int The new grade object ID if successful, false otherwise
331      */
332     public function insert($source=null) {
333         global $USER, $CFG, $DB;
335         if (!empty($this->id)) {
336             debugging("Grade object already exists!");
337             return false;
338         }
340         $data = $this->get_record_data();
342         $this->id = $DB->insert_record($this->table, $data);
344         // set all object properties from real db data
345         $this->update_from_db();
347         $data = $this->get_record_data();
349         if (empty($CFG->disablegradehistory)) {
350             unset($data->timecreated);
351             $data->action       = GRADE_HISTORY_INSERT;
352             $data->oldid        = $this->id;
353             $data->source       = $source;
354             $data->timemodified = time();
355             $data->loggeduser   = $USER->id;
356             $DB->insert_record($this->table.'_history', $data);
357         }
359         $this->notify_changed(false);
360         return $this->id;
361     }
363     /**
364      * Using this object's id field, fetches the matching record in the DB, and looks at
365      * each variable in turn. If the DB has different data, the db's data is used to update
366      * the object. This is different from the update() function, which acts on the DB record
367      * based on the object.
368      *
369      * @return bool True if successful
370      */
371     public function update_from_db() {
372         if (empty($this->id)) {
373             debugging("The object could not be used in its state to retrieve a matching record from the DB, because its id field is not set.");
374             return false;
375         }
376         global $DB;
377         if (!$params = $DB->get_record($this->table, array('id' => $this->id))) {
378             debugging("Object with this id:{$this->id} does not exist in table:{$this->table}, can not update from db!");
379             return false;
380         }
382         grade_object::set_properties($this, $params);
384         return true;
385     }
387     /**
388      * Given an associated array or object, cycles through each key/variable
389      * and assigns the value to the corresponding variable in this object.
390      *
391      * @param stdClass $instance The object to set the properties on
392      * @param array $params An array of properties to set like $propertyname => $propertyvalue
393      * @return array|stdClass Either an associative array or an object containing property name, property value pairs
394      */
395     public static function set_properties(&$instance, $params) {
396         $params = (array) $params;
397         foreach ($params as $var => $value) {
398             if (in_array($var, $instance->required_fields) or array_key_exists($var, $instance->optional_fields)) {
399                 $instance->$var = $value;
400             }
401         }
402     }
404     /**
405      * Called immediately after the object data has been inserted, updated, or
406      * deleted in the database. Default does nothing, can be overridden to
407      * hook in special behaviour.
408      *
409      * @param bool $deleted
410      */
411     protected function notify_changed($deleted) {
412     }
414     /**
415      * Returns the current hidden state of this grade_item
416      *
417      * This depends on the grade object hidden setting and the current time if hidden is set to a "hidden until" timestamp
418      *
419      * @return bool Current hidden state
420      */
421     function is_hidden() {
422         return ($this->hidden == 1 or ($this->hidden != 0 and $this->hidden > time()));
423     }
425     /**
426      * Check grade object hidden status
427      *
428      * @return bool True if a "hidden until" timestamp is set, false if grade object is set to always visible or always hidden.
429      */
430     function is_hiddenuntil() {
431         return $this->hidden > 1;
432     }
434     /**
435      * Check a grade item hidden status.
436      *
437      * @return int 0 means visible, 1 hidden always, a timestamp means "hidden until"
438      */
439     function get_hidden() {
440         return $this->hidden;
441     }
443     /**
444      * Set a grade object hidden status
445      *
446      * @param int $hidden 0 means visiable, 1 means hidden always, a timestamp means "hidden until"
447      * @param bool $cascade Ignored
448      */
449     function set_hidden($hidden, $cascade=false) {
450         $this->hidden = $hidden;
451         $this->update();
452     }
454     /**
455      * Returns whether the grade object can control the visibility of the grades.
456      *
457      * @return bool
458      */
459     public function can_control_visibility() {
460         return true;
461     }