gradebook MDL-25713 now closing the recordset even when theres a problem
[moodle.git] / lib / grade / grade_object.php
1 <?php
3 // This file is part of Moodle - http://moodle.org/
4 //
5 // Moodle is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // Moodle is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * Definitions of grade object class
19  *
20  * @package    core
21  * @subpackage 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();
27 /**
28  * An abstract object that holds methods and attributes common to all grade_* objects defined here.
29  * @abstract
30  */
31 abstract class grade_object {
32     public $table;
34     /**
35      * Array of required table fields, must start with 'id'.
36      * @var array $required_fields
37      */
38     public $required_fields = array('id', 'timecreated', 'timemodified', 'hidden');
40     /**
41      * Array of optional fields with default values - usually long text information that is not always needed.
42      * If you want to create an instance without optional fields use: new grade_object($only_required_fields, false);
43      * @var array $optional_fields
44      */
45     public $optional_fields = array();
47     /**
48      * The PK.
49      * @var int $id
50      */
51     public $id;
53     /**
54      * The first time this grade_object was created.
55      * @var int $timecreated
56      */
57     public $timecreated;
59     /**
60      * The last time this grade_object was modified.
61      * @var int $timemodified
62      */
63     public $timemodified;
65     /**
66      * 0 if visible, 1 always hidden or date not visible until
67      * @var int $hidden
68      */
69     var $hidden = 0;
71     /**
72      * Constructor. Optionally (and by default) attempts to fetch corresponding row from DB.
73      * @param array $params an array with required parameters for this grade object.
74      * @param boolean $fetch Whether to fetch corresponding row from DB or not,
75      *        optional fields might not be defined if false used
76      */
77     public function __construct($params=NULL, $fetch=true) {
78         if (!empty($params) and (is_array($params) or is_object($params))) {
79             if ($fetch) {
80                 if ($data = $this->fetch($params)) {
81                     grade_object::set_properties($this, $data);
82                 } else {
83                     grade_object::set_properties($this, $this->optional_fields);//apply defaults for optional fields
84                     grade_object::set_properties($this, $params);
85                 }
87             } else {
88                 grade_object::set_properties($this, $params);
89             }
91         } else {
92             grade_object::set_properties($this, $this->optional_fields);//apply defaults for optional fields
93         }
94     }
96     /**
97      * Makes sure all the optional fields are loaded.
98      * If id present (==instance exists in db) fetches data from db.
99      * Defaults are used for new instances.
100      */
101     public function load_optional_fields() {
102         global $DB;
103         foreach ($this->optional_fields as $field=>$default) {
104             if (property_exists($this, $field)) {
105                 continue;
106             }
107             if (empty($this->id)) {
108                 $this->$field = $default;
109             } else {
110                 $this->$field = $DB->get_field($this->table, $field, array('id', $this->id));
111             }
112         }
113     }
115     /**
116      * Finds and returns a grade_object instance based on params.
117      * @static abstract
118      *
119      * @param array $params associative arrays varname=>value
120      * @return object grade_object instance or false if none found.
121      */
122     public static function fetch($params) {
123         throw new coding_exception('fetch() method needs to be overridden in each subclass of grade_object');
124     }
126     /**
127      * Finds and returns all grade_object instances based on params.
128      * @static abstract
129      *
130      * @param array $params associative arrays varname=>value
131      * @return array array of grade_object instances or false if none found.
132      */
133     public static function fetch_all($params) {
134         throw new coding_exception('fetch_all() method needs to be overridden in each subclass of grade_object');
135     }
137     /**
138      * Factory method - uses the parameters to retrieve matching instance from the DB.
139      * @static final protected
140      * @return mixed object instance or false if not found
141      */
142     protected static function fetch_helper($table, $classname, $params) {
143         if ($instances = grade_object::fetch_all_helper($table, $classname, $params)) {
144             if (count($instances) > 1) {
145                 // we should not tolerate any errors here - problems might appear later
146                 print_error('morethanonerecordinfetch','debug');
147             }
148             return reset($instances);
149         } else {
150             return false;
151         }
152     }
154     /**
155      * Factory method - uses the parameters to retrieve all matching instances from the DB.
156      * @static final protected
157      * @return mixed array of object instances or false if not found
158      */
159     public static function fetch_all_helper($table, $classname, $params) {
160         $instance = new $classname();
162         $classvars = (array)$instance;
163         $params    = (array)$params;
165         $wheresql = array();
166         $newparams = array();
168         foreach ($params as $var=>$value) {
169             if (!in_array($var, $instance->required_fields) and !array_key_exists($var, $instance->optional_fields)) {
170                 continue;
171             }
172             if (is_null($value)) {
173                 $wheresql[] = " $var IS NULL ";
174             } else {
175                 $wheresql[] = " $var = ? ";
176                 $newparams[] = $value;
177             }
178         }
180         if (empty($wheresql)) {
181             $wheresql = '';
182         } else {
183             $wheresql = implode("AND", $wheresql);
184         }
186         global $DB;
187         $rs = $DB->get_recordset_select($table, $wheresql, $newparams);
188         //returning false rather than empty array if nothing found
189         if (!$rs->valid()) {
190             $rs->close();
191             return false;
192         }
194         $result = array();
195         foreach($rs as $data) {
196             $instance = new $classname();
197             grade_object::set_properties($instance, $data);
198             $result[$instance->id] = $instance; 
199         }
200         $rs->close();
201         
202         return $result;
203     }
205     /**
206      * Updates this object in the Database, based on its object variables. ID must be set.
207      * @param string $source from where was the object updated (mod/forum, manual, etc.)
208      * @return boolean success
209      */
210     public function update($source=null) {
211         global $USER, $CFG, $DB;
213         if (empty($this->id)) {
214             debugging('Can not update grade object, no id!');
215             return false;
216         }
218         $data = $this->get_record_data();
220         $DB->update_record($this->table, $data);
222         if (empty($CFG->disablegradehistory)) {
223             unset($data->timecreated);
224             $data->action       = GRADE_HISTORY_UPDATE;
225             $data->oldid        = $this->id;
226             $data->source       = $source;
227             $data->timemodified = time();
228             $data->loggeduser   = $USER->id;
229             $DB->insert_record($this->table.'_history', $data);
230         }
232         $this->notify_changed(false);
233         return true;
234     }
236     /**
237      * Deletes this object from the database.
238      * @param string $source from where was the object deleted (mod/forum, manual, etc.)
239      * @return boolean success
240      */
241     public function delete($source=null) {
242         global $USER, $CFG, $DB;
244         if (empty($this->id)) {
245             debugging('Can not delete grade object, no id!');
246             return false;
247         }
249         $data = $this->get_record_data();
251         if ($DB->delete_records($this->table, array('id'=>$this->id))) {
252             if (empty($CFG->disablegradehistory)) {
253                 unset($data->id);
254                 unset($data->timecreated);
255                 $data->action       = GRADE_HISTORY_DELETE;
256                 $data->oldid        = $this->id;
257                 $data->source       = $source;
258                 $data->timemodified = time();
259                 $data->loggeduser   = $USER->id;
260                 $DB->insert_record($this->table.'_history', $data);
261             }
262             $this->notify_changed(true);
263             return true;
265         } else {
266             return false;
267         }
268     }
270     /**
271      * Returns object with fields and values that are defined in database
272      */
273     public function get_record_data() {
274         $data = new stdClass();
276         foreach ($this as $var=>$value) {
277             if (in_array($var, $this->required_fields) or array_key_exists($var, $this->optional_fields)) {
278                 if (is_object($value) or is_array($value)) {
279                     debugging("Incorrect property '$var' found when inserting grade object");
280                 } else {
281                     $data->$var = $value;
282                 }
283             }
284         }
285         return $data;
286     }
288     /**
289      * Records this object in the Database, sets its id to the returned value, and returns that value.
290      * If successful this function also fetches the new object data from database and stores it
291      * in object properties.
292      * @param string $source from where was the object inserted (mod/forum, manual, etc.)
293      * @return int PK ID if successful, false otherwise
294      */
295     public function insert($source=null) {
296         global $USER, $CFG, $DB;
298         if (!empty($this->id)) {
299             debugging("Grade object already exists!");
300             return false;
301         }
303         $data = $this->get_record_data();
305         $this->id = $DB->insert_record($this->table, $data);
307         // set all object properties from real db data
308         $this->update_from_db();
310         $data = $this->get_record_data();
312         if (empty($CFG->disablegradehistory)) {
313             unset($data->timecreated);
314             $data->action       = GRADE_HISTORY_INSERT;
315             $data->oldid        = $this->id;
316             $data->source       = $source;
317             $data->timemodified = time();
318             $data->loggeduser   = $USER->id;
319             $DB->insert_record($this->table.'_history', $data);
320         }
322         $this->notify_changed(false);
323         return $this->id;
324     }
326     /**
327      * Using this object's id field, fetches the matching record in the DB, and looks at
328      * each variable in turn. If the DB has different data, the db's data is used to update
329      * the object. This is different from the update() function, which acts on the DB record
330      * based on the object.
331      */
332     public function update_from_db() {
333         if (empty($this->id)) {
334             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.");
335             return false;
336         }
337         global $DB;
338         if (!$params = $DB->get_record($this->table, array('id' => $this->id))) {
339             debugging("Object with this id:{$this->id} does not exist in table:{$this->table}, can not update from db!");
340             return false;
341         }
343         grade_object::set_properties($this, $params);
345         return true;
346     }
348     /**
349      * Given an associated array or object, cycles through each key/variable
350      * and assigns the value to the corresponding variable in this object.
351      * @static final
352      */
353     public static function set_properties(&$instance, $params) {
354         $params = (array) $params;
355         foreach ($params as $var => $value) {
356             if (in_array($var, $instance->required_fields) or array_key_exists($var, $instance->optional_fields)) {
357                 $instance->$var = $value;
358             }
359         }
360     }
362     /**
363      * Called immediately after the object data has been inserted, updated, or
364      * deleted in the database. Default does nothing, can be overridden to
365      * hook in special behaviour.
366      *
367      * @param bool $deleted
368      */
369     function notify_changed($deleted) {
370     }
372     /**
373      * Returns the hidden state of this grade_item
374      * @return boolean hidden state
375      */
376     function is_hidden() {
377         return ($this->hidden == 1 or ($this->hidden != 0 and $this->hidden > time()));
378     }
380     /**
381      * Check grade hidden status. Uses data from both grade item and grade.
382      * @return boolean true if hiddenuntil, false if not
383      */
384     function is_hiddenuntil() {
385         return $this->hidden > 1;
386     }
388     /**
389      * Check grade item hidden status.
390      * @return int 0 means visible, 1 hidden always, timestamp hidden until
391      */
392     function get_hidden() {
393         return $this->hidden;
394     }
396     function set_hidden($hidden, $cascade=false) {
397         $this->hidden = $hidden;
398         $this->update();
399     }