Merge branch 'MDL-73502' of https://github.com/stronk7/moodle
[moodle.git] / lib / grade / grade_object.php
blob66a795452bdbb29d61b877e6870ec643c8b480b1
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.
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
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
26 defined('MOODLE_INTERNAL') || die();
28 /**
29 * An abstract object that holds methods and attributes common to all grade_* objects defined here.
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
36 abstract class grade_object {
37 /**
38 * The database table this grade object is stored in
39 * @var string $table
41 public $table;
43 /**
44 * Array of required table fields, must start with 'id'.
45 * @var array $required_fields
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
54 public $optional_fields = array();
56 /**
57 * The PK.
58 * @var int $id
60 public $id;
62 /**
63 * The first time this grade_object was created.
64 * @var int $timecreated
66 public $timecreated;
68 /**
69 * The last time this grade_object was modified.
70 * @var int $timemodified
72 public $timemodified;
74 /**
75 * 0 if visible, 1 always hidden or date not visible until
76 * @var int $hidden
78 var $hidden = 0;
80 /**
81 * Constructor. Optionally (and by default) attempts to fetch corresponding row from the database
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
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);
97 } else {
98 grade_object::set_properties($this, $params);
101 } else {
102 grade_object::set_properties($this, $this->optional_fields);//apply defaults for optional fields
107 * Makes sure all the optional fields are loaded.
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.
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;
118 if (empty($this->id)) {
119 $this->$field = $default;
120 } else {
121 $this->$field = $DB->get_field($this->table, $field, array('id', $this->id));
127 * Finds and returns a grade_object instance based on params.
129 * @static
130 * @abstract
131 * @param array $params associative arrays varname=>value
132 * @return object grade_object instance or false if none found.
134 public static function fetch($params) {
135 throw new coding_exception('fetch() method needs to be overridden in each subclass of grade_object');
139 * Finds and returns all grade_object instances based on $params.
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.
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');
152 * Factory method which uses the parameters to retrieve matching instances from the database
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
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');
165 return reset($instances);
166 } else {
167 return false;
172 * Factory method which uses the parameters to retrieve all matching instances from the database
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
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;
196 if (!array_key_exists($var, $columns)) {
197 continue;
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 = ? ";
209 $newparams[] = $value;
213 if (empty($wheresql)) {
214 $wheresql = '';
215 } else {
216 $wheresql = implode("AND", $wheresql);
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;
227 $result = array();
228 foreach($rs as $data) {
229 $instance = new $classname();
230 grade_object::set_properties($instance, $data);
231 $result[$instance->id] = $instance;
233 $rs->close();
234 return $result;
238 * Updates this object in the Database, based on its object variables. ID must be set.
240 * @param string $source from where was the object updated (mod/forum, manual, etc.)
241 * @param bool $isbulkupdate If bulk grade update is happening.
242 * @return bool success
244 public function update($source = null, $isbulkupdate = false) {
245 global $USER, $CFG, $DB;
247 if (empty($this->id)) {
248 debugging('Can not update grade object, no id!');
249 return false;
252 $data = $this->get_record_data();
254 $DB->update_record($this->table, $data);
256 $historyid = null;
257 if (empty($CFG->disablegradehistory)) {
258 unset($data->timecreated);
259 $data->action = GRADE_HISTORY_UPDATE;
260 $data->oldid = $this->id;
261 $data->source = $source;
262 $data->timemodified = time();
263 $data->loggeduser = $USER->id;
264 $historyid = $DB->insert_record($this->table.'_history', $data);
267 $this->notify_changed(false, $isbulkupdate);
269 $this->update_feedback_files($historyid);
271 return true;
275 * Deletes this object from the database.
277 * @param string $source From where was the object deleted (mod/forum, manual, etc.)
278 * @return bool success
280 public function delete($source=null) {
281 global $USER, $CFG, $DB;
283 if (empty($this->id)) {
284 debugging('Can not delete grade object, no id!');
285 return false;
288 $data = $this->get_record_data();
290 if ($DB->delete_records($this->table, array('id'=>$this->id))) {
291 if (empty($CFG->disablegradehistory)) {
292 unset($data->id);
293 unset($data->timecreated);
294 $data->action = GRADE_HISTORY_DELETE;
295 $data->oldid = $this->id;
296 $data->source = $source;
297 $data->timemodified = time();
298 $data->loggeduser = $USER->id;
299 $DB->insert_record($this->table.'_history', $data);
302 $this->notify_changed(true);
304 $this->delete_feedback_files();
306 return true;
307 } else {
308 return false;
313 * Returns object with fields and values that are defined in database
315 * @return stdClass
317 public function get_record_data() {
318 $data = new stdClass();
320 foreach ($this as $var=>$value) {
321 if (in_array($var, $this->required_fields) or array_key_exists($var, $this->optional_fields)) {
322 if (is_object($value) or is_array($value)) {
323 debugging("Incorrect property '$var' found when inserting grade object");
324 } else {
325 $data->$var = $value;
329 return $data;
333 * Records this object in the Database, sets its id to the returned value, and returns that value.
334 * If successful this function also fetches the new object data from database and stores it
335 * in object properties.
337 * @param string $source From where was the object inserted (mod/forum, manual, etc.)
338 * @param string $isbulkupdate If bulk grade update is happening.
339 * @return int The new grade object ID if successful, false otherwise
341 public function insert($source = null, $isbulkupdate = false) {
342 global $USER, $CFG, $DB;
344 if (!empty($this->id)) {
345 debugging("Grade object already exists!");
346 return false;
349 $data = $this->get_record_data();
351 $this->id = $DB->insert_record($this->table, $data);
353 // set all object properties from real db data
354 $this->update_from_db();
356 $data = $this->get_record_data();
358 $historyid = null;
359 if (empty($CFG->disablegradehistory)) {
360 unset($data->timecreated);
361 $data->action = GRADE_HISTORY_INSERT;
362 $data->oldid = $this->id;
363 $data->source = $source;
364 $data->timemodified = time();
365 $data->loggeduser = $USER->id;
366 $historyid = $DB->insert_record($this->table.'_history', $data);
369 $this->notify_changed(false, $isbulkupdate);
371 $this->add_feedback_files($historyid);
373 return $this->id;
377 * Using this object's id field, fetches the matching record in the DB, and looks at
378 * each variable in turn. If the DB has different data, the db's data is used to update
379 * the object. This is different from the update() function, which acts on the DB record
380 * based on the object.
382 * @return bool True if successful
384 public function update_from_db() {
385 if (empty($this->id)) {
386 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.");
387 return false;
389 global $DB;
390 if (!$params = $DB->get_record($this->table, array('id' => $this->id))) {
391 debugging("Object with this id:{$this->id} does not exist in table:{$this->table}, can not update from db!");
392 return false;
395 grade_object::set_properties($this, $params);
397 return true;
401 * Given an associated array or object, cycles through each key/variable
402 * and assigns the value to the corresponding variable in this object.
404 * @param stdClass $instance The object to set the properties on
405 * @param array $params An array of properties to set like $propertyname => $propertyvalue
406 * @return array|stdClass Either an associative array or an object containing property name, property value pairs
408 public static function set_properties(&$instance, $params) {
409 $params = (array) $params;
410 foreach ($params as $var => $value) {
411 if (in_array($var, $instance->required_fields) or array_key_exists($var, $instance->optional_fields)) {
412 $instance->$var = $value;
418 * Called immediately after the object data has been inserted, updated, or
419 * deleted in the database. Default does nothing, can be overridden to
420 * hook in special behaviour.
422 * @param bool $deleted
424 protected function notify_changed($deleted) {
428 * Handles adding feedback files in the gradebook.
430 * @param int|null $historyid
432 protected function add_feedback_files(int $historyid = null) {
436 * Handles updating feedback files in the gradebook.
438 * @param int|null $historyid
440 protected function update_feedback_files(int $historyid = null) {
444 * Handles deleting feedback files in the gradebook.
446 protected function delete_feedback_files() {
450 * Returns the current hidden state of this grade_item
452 * This depends on the grade object hidden setting and the current time if hidden is set to a "hidden until" timestamp
454 * @return bool Current hidden state
456 function is_hidden() {
457 return ($this->hidden == 1 or ($this->hidden != 0 and $this->hidden > time()));
461 * Check grade object hidden status
463 * @return bool True if a "hidden until" timestamp is set, false if grade object is set to always visible or always hidden.
465 function is_hiddenuntil() {
466 return $this->hidden > 1;
470 * Check a grade item hidden status.
472 * @return int 0 means visible, 1 hidden always, a timestamp means "hidden until"
474 function get_hidden() {
475 return $this->hidden;
479 * Set a grade object hidden status
481 * @param int $hidden 0 means visiable, 1 means hidden always, a timestamp means "hidden until"
482 * @param bool $cascade Ignored
484 function set_hidden($hidden, $cascade=false) {
485 $this->hidden = $hidden;
486 $this->update();
490 * Returns whether the grade object can control the visibility of the grades.
492 * @return bool
494 public function can_control_visibility() {
495 return true;