MDL-54613 unit tests: Add iteminstance to test grade_item
[moodle.git] / lib / grade / grade_object.php
CommitLineData
4a0e2e63 1<?php
7ad5a627
PS
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/>.
a153c9f2 16
7ad5a627 17/**
a153c9f2 18 * Definition of a grade object class for grade item, grade category etc to inherit from
7ad5a627 19 *
a153c9f2
AD
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
7ad5a627 24 */
8a31e65c 25
7ad5a627 26defined('MOODLE_INTERNAL') || die();
a153c9f2 27
8a31e65c 28/**
29 * An abstract object that holds methods and attributes common to all grade_* objects defined here.
a153c9f2
AD
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
8a31e65c 35 */
da3801e8 36abstract class grade_object {
a153c9f2
AD
37 /**
38 * The database table this grade object is stored in
39 * @var string $table
40 */
da3801e8 41 public $table;
42
8a31e65c 43 /**
3f2b0c8a 44 * Array of required table fields, must start with 'id'.
45 * @var array $required_fields
8a31e65c 46 */
a25bb902 47 public $required_fields = array('id', 'timecreated', 'timemodified', 'hidden');
8a31e65c 48
49 /**
3f2b0c8a 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
8a31e65c 53 */
da3801e8 54 public $optional_fields = array();
772ddfbf 55
8a31e65c 56 /**
57 * The PK.
772ddfbf 58 * @var int $id
8a31e65c 59 */
da3801e8 60 public $id;
772ddfbf 61
8a31e65c 62 /**
3f2b0c8a 63 * The first time this grade_object was created.
8a31e65c 64 * @var int $timecreated
65 */
da3801e8 66 public $timecreated;
772ddfbf 67
8a31e65c 68 /**
3f2b0c8a 69 * The last time this grade_object was modified.
8a31e65c 70 * @var int $timemodified
71 */
da3801e8 72 public $timemodified;
772ddfbf 73
a25bb902
AD
74 /**
75 * 0 if visible, 1 always hidden or date not visible until
76 * @var int $hidden
77 */
78 var $hidden = 0;
79
8a31e65c 80 /**
a153c9f2
AD
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,
9f01047a 85 * optional fields might not be defined if false used
772ddfbf 86 */
da3801e8 87 public function __construct($params=NULL, $fetch=true) {
f92dcad8 88 if (!empty($params) and (is_array($params) or is_object($params))) {
3ab3dfd5 89 if ($fetch) {
90 if ($data = $this->fetch($params)) {
f3ac8eb4 91 grade_object::set_properties($this, $data);
3ab3dfd5 92 } else {
f3ac8eb4 93 grade_object::set_properties($this, $this->optional_fields);//apply defaults for optional fields
94 grade_object::set_properties($this, $params);
3ab3dfd5 95 }
f92dcad8 96
97 } else {
f3ac8eb4 98 grade_object::set_properties($this, $params);
f92dcad8 99 }
3ab3dfd5 100
101 } else {
f3ac8eb4 102 grade_object::set_properties($this, $this->optional_fields);//apply defaults for optional fields
f92dcad8 103 }
104 }
105
3f2b0c8a 106 /**
107 * Makes sure all the optional fields are loaded.
a153c9f2
AD
108 *
109 * If id present, meaning the instance exists in the database, then data will be fetched from the database.
3f2b0c8a 110 * Defaults are used for new instances.
111 */
da3801e8 112 public function load_optional_fields() {
113 global $DB;
3f2b0c8a 114 foreach ($this->optional_fields as $field=>$default) {
22a9b6d8 115 if (property_exists($this, $field)) {
3f2b0c8a 116 continue;
117 }
118 if (empty($this->id)) {
119 $this->$field = $default;
120 } else {
da3801e8 121 $this->$field = $DB->get_field($this->table, $field, array('id', $this->id));
3f2b0c8a 122 }
123 }
124 }
125
f92dcad8 126 /**
127 * Finds and returns a grade_object instance based on params.
f92dcad8 128 *
a153c9f2
AD
129 * @static
130 * @abstract
f92dcad8 131 * @param array $params associative arrays varname=>value
132 * @return object grade_object instance or false if none found.
133 */
7c109ea3
PS
134 public static function fetch($params) {
135 throw new coding_exception('fetch() method needs to be overridden in each subclass of grade_object');
136 }
f92dcad8 137
138 /**
a153c9f2 139 * Finds and returns all grade_object instances based on $params.
f92dcad8 140 *
a153c9f2
AD
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.
f92dcad8 146 */
7c109ea3
PS
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 }
f92dcad8 150
151 /**
a153c9f2
AD
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
f92dcad8 158 */
da3801e8 159 protected static function fetch_helper($table, $classname, $params) {
f3ac8eb4 160 if ($instances = grade_object::fetch_all_helper($table, $classname, $params)) {
f92dcad8 161 if (count($instances) > 1) {
514a3467 162 // we should not tolerate any errors here - problems might appear later
473e3682 163 print_error('morethanonerecordinfetch','debug');
3058964f 164 }
514a3467 165 return reset($instances);
f92dcad8 166 } else {
167 return false;
168 }
169 }
170
171 /**
a153c9f2
AD
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
f92dcad8 178 */
d24832f9 179 public static function fetch_all_helper($table, $classname, $params) {
eadcdee9
EL
180 global $DB; // Need to introspect DB here.
181
f92dcad8 182 $instance = new $classname();
183
184 $classvars = (array)$instance;
185 $params = (array)$params;
186
187 $wheresql = array();
b16b5857 188 $newparams = array();
f92dcad8 189
eadcdee9
EL
190 $columns = $DB->get_columns($table); // Cached, no worries.
191
f92dcad8 192 foreach ($params as $var=>$value) {
3f2b0c8a 193 if (!in_array($var, $instance->required_fields) and !array_key_exists($var, $instance->optional_fields)) {
f92dcad8 194 continue;
195 }
26331e3d 196 if (!array_key_exists($var, $columns)) {
eadcdee9
EL
197 continue;
198 }
f92dcad8 199 if (is_null($value)) {
200 $wheresql[] = " $var IS NULL ";
201 } else {
eadcdee9
EL
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 }
b16b5857 209 $newparams[] = $value;
f92dcad8 210 }
211 }
212
213 if (empty($wheresql)) {
214 $wheresql = '';
215 } else {
216 $wheresql = implode("AND", $wheresql);
217 }
218
da3801e8 219 global $DB;
2f9ea7d7 220 $rs = $DB->get_recordset_select($table, $wheresql, $newparams);
221 //returning false rather than empty array if nothing found
222 if (!$rs->valid()) {
fc61acce 223 $rs->close();
224 return false;
2f9ea7d7 225 }
4a0e2e63 226
2f9ea7d7 227 $result = array();
228 foreach($rs as $data) {
229 $instance = new $classname();
230 grade_object::set_properties($instance, $data);
c3b1c4f0 231 $result[$instance->id] = $instance;
772ddfbf 232 }
2f9ea7d7 233 $rs->close();
c3b1c4f0 234
2f9ea7d7 235 return $result;
8a31e65c 236 }
772ddfbf 237
8a31e65c 238 /**
239 * Updates this object in the Database, based on its object variables. ID must be set.
a153c9f2 240 *
aaff71da 241 * @param string $source from where was the object updated (mod/forum, manual, etc.)
a153c9f2 242 * @return bool success
8a31e65c 243 */
da3801e8 244 public function update($source=null) {
245 global $USER, $CFG, $DB;
1ee0df06 246
247 if (empty($this->id)) {
248 debugging('Can not update grade object, no id!');
249 return false;
250 }
f744267a 251
89a5f827 252 $data = $this->get_record_data();
b3ac6c3e 253
2a7eff41 254 $DB->update_record($this->table, $data);
aaff71da 255
1ee0df06 256 if (empty($CFG->disablegradehistory)) {
257 unset($data->timecreated);
258 $data->action = GRADE_HISTORY_UPDATE;
259 $data->oldid = $this->id;
260 $data->source = $source;
261 $data->timemodified = time();
f64e29e1 262 $data->loggeduser = $USER->id;
5b0af8c5 263 $DB->insert_record($this->table.'_history', $data);
1ee0df06 264 }
aaff71da 265
4e781c7b 266 $this->notify_changed(false);
aaff71da 267 return true;
8a31e65c 268 }
269
270 /**
271 * Deletes this object from the database.
a153c9f2
AD
272 *
273 * @param string $source From where was the object deleted (mod/forum, manual, etc.)
274 * @return bool success
8a31e65c 275 */
da3801e8 276 public function delete($source=null) {
277 global $USER, $CFG, $DB;
aaff71da 278
1ee0df06 279 if (empty($this->id)) {
280 debugging('Can not delete grade object, no id!');
281 return false;
aaff71da 282 }
283
1ee0df06 284 $data = $this->get_record_data();
285
f67cab32 286 if ($DB->delete_records($this->table, array('id'=>$this->id))) {
1ee0df06 287 if (empty($CFG->disablegradehistory)) {
288 unset($data->id);
289 unset($data->timecreated);
290 $data->action = GRADE_HISTORY_DELETE;
291 $data->oldid = $this->id;
292 $data->source = $source;
293 $data->timemodified = time();
bfc7353d 294 $data->loggeduser = $USER->id;
5b0af8c5 295 $DB->insert_record($this->table.'_history', $data);
aaff71da 296 }
4e781c7b 297 $this->notify_changed(true);
aaff71da 298 return true;
299
300 } else {
301 return false;
302 }
8a31e65c 303 }
772ddfbf 304
89a5f827 305 /**
306 * Returns object with fields and values that are defined in database
a153c9f2
AD
307 *
308 * @return stdClass
89a5f827 309 */
da3801e8 310 public function get_record_data() {
365a5941 311 $data = new stdClass();
294ce987 312
89a5f827 313 foreach ($this as $var=>$value) {
314 if (in_array($var, $this->required_fields) or array_key_exists($var, $this->optional_fields)) {
315 if (is_object($value) or is_array($value)) {
316 debugging("Incorrect property '$var' found when inserting grade object");
317 } else {
318 $data->$var = $value;
319 }
320 }
321 }
322 return $data;
323 }
324
8a31e65c 325 /**
326 * Records this object in the Database, sets its id to the returned value, and returns that value.
9b7e5a37 327 * If successful this function also fetches the new object data from database and stores it
328 * in object properties.
a153c9f2
AD
329 *
330 * @param string $source From where was the object inserted (mod/forum, manual, etc.)
331 * @return int The new grade object ID if successful, false otherwise
8a31e65c 332 */
da3801e8 333 public function insert($source=null) {
334 global $USER, $CFG, $DB;
f744267a 335
de420c11 336 if (!empty($this->id)) {
d9907766 337 debugging("Grade object already exists!");
338 return false;
b55997c1 339 }
340
89a5f827 341 $data = $this->get_record_data();
b3ac6c3e 342
2a7eff41 343 $this->id = $DB->insert_record($this->table, $data);
9b7e5a37 344
345 // set all object properties from real db data
346 $this->update_from_db();
347
1ee0df06 348 $data = $this->get_record_data();
349
350 if (empty($CFG->disablegradehistory)) {
351 unset($data->timecreated);
352 $data->action = GRADE_HISTORY_INSERT;
353 $data->oldid = $this->id;
354 $data->source = $source;
355 $data->timemodified = time();
bfc7353d 356 $data->loggeduser = $USER->id;
5b0af8c5 357 $DB->insert_record($this->table.'_history', $data);
1ee0df06 358 }
aaff71da 359
4e781c7b 360 $this->notify_changed(false);
9b7e5a37 361 return $this->id;
8a31e65c 362 }
2c72af1f 363
364 /**
365 * Using this object's id field, fetches the matching record in the DB, and looks at
366 * each variable in turn. If the DB has different data, the db's data is used to update
367 * the object. This is different from the update() function, which acts on the DB record
368 * based on the object.
a153c9f2
AD
369 *
370 * @return bool True if successful
2c72af1f 371 */
da3801e8 372 public function update_from_db() {
2c72af1f 373 if (empty($this->id)) {
ab53054f 374 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.");
2c72af1f 375 return false;
2c72af1f 376 }
da3801e8 377 global $DB;
378 if (!$params = $DB->get_record($this->table, array('id' => $this->id))) {
b3ac6c3e 379 debugging("Object with this id:{$this->id} does not exist in table:{$this->table}, can not update from db!");
9b7e5a37 380 return false;
381 }
382
f3ac8eb4 383 grade_object::set_properties($this, $params);
9b7e5a37 384
2c72af1f 385 return true;
386 }
772ddfbf 387
3058964f 388 /**
389 * Given an associated array or object, cycles through each key/variable
390 * and assigns the value to the corresponding variable in this object.
a153c9f2
AD
391 *
392 * @param stdClass $instance The object to set the properties on
393 * @param array $params An array of properties to set like $propertyname => $propertyvalue
394 * @return array|stdClass Either an associative array or an object containing property name, property value pairs
3058964f 395 */
da3801e8 396 public static function set_properties(&$instance, $params) {
76144765 397 $params = (array) $params;
f92dcad8 398 foreach ($params as $var => $value) {
3f2b0c8a 399 if (in_array($var, $instance->required_fields) or array_key_exists($var, $instance->optional_fields)) {
f92dcad8 400 $instance->$var = $value;
3058964f 401 }
772ddfbf 402 }
3058964f 403 }
4e781c7b 404
405 /**
4a0e2e63
PS
406 * Called immediately after the object data has been inserted, updated, or
407 * deleted in the database. Default does nothing, can be overridden to
4e781c7b 408 * hook in special behaviour.
409 *
410 * @param bool $deleted
411 */
e01efa2c 412 protected function notify_changed($deleted) {
4e781c7b 413 }
a25bb902
AD
414
415 /**
a153c9f2
AD
416 * Returns the current hidden state of this grade_item
417 *
418 * This depends on the grade object hidden setting and the current time if hidden is set to a "hidden until" timestamp
419 *
420 * @return bool Current hidden state
a25bb902
AD
421 */
422 function is_hidden() {
423 return ($this->hidden == 1 or ($this->hidden != 0 and $this->hidden > time()));
424 }
425
426 /**
a153c9f2
AD
427 * Check grade object hidden status
428 *
11ce79b0 429 * @return bool True if a "hidden until" timestamp is set, false if grade object is set to always visible or always hidden.
a25bb902
AD
430 */
431 function is_hiddenuntil() {
432 return $this->hidden > 1;
433 }
434
435 /**
a153c9f2
AD
436 * Check a grade item hidden status.
437 *
438 * @return int 0 means visible, 1 hidden always, a timestamp means "hidden until"
a25bb902
AD
439 */
440 function get_hidden() {
441 return $this->hidden;
442 }
443
a153c9f2
AD
444 /**
445 * Set a grade object hidden status
446 *
447 * @param int $hidden 0 means visiable, 1 means hidden always, a timestamp means "hidden until"
448 * @param bool $cascade Ignored
449 */
a25bb902
AD
450 function set_hidden($hidden, $cascade=false) {
451 $this->hidden = $hidden;
452 $this->update();
453 }
39873128
TH
454
455 /**
456 * Returns whether the grade object can control the visibility of the grades.
457 *
458 * @return bool
459 */
460 public function can_control_visibility() {
461 return true;
462 }
8a31e65c 463}