Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

Differences Between: [Versions 311 and 400] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403]

   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/>.
  16  
  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   */
  25  
  26  defined('MOODLE_INTERNAL') || die();
  27  
  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;
  42  
  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');
  48  
  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();
  55  
  56      /**
  57       * The PK.
  58       * @var int $id
  59       */
  60      public $id;
  61  
  62      /**
  63       * The first time this grade_object was created.
  64       * @var int $timecreated
  65       */
  66      public $timecreated;
  67  
  68      /**
  69       * The last time this grade_object was modified.
  70       * @var int $timemodified
  71       */
  72      public $timemodified;
  73  
  74      /**
  75       * 0 if visible, 1 always hidden or date not visible until
  76       * @var int $hidden
  77       */
  78      var $hidden = 0;
  79  
  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                  }
  96  
  97              } else {
  98                  grade_object::set_properties($this, $params);
  99              }
 100  
 101          } else {
 102              grade_object::set_properties($this, $this->optional_fields);//apply defaults for optional fields
 103          }
 104      }
 105  
 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      }
 125  
 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      }
 137  
 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      }
 150  
 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      }
 170  
 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.
 181  
 182          $instance = new $classname();
 183  
 184          $classvars = (array)$instance;
 185          $params    = (array)$params;
 186  
 187          $wheresql = array();
 188          $newparams = array();
 189  
 190          $columns = $DB->get_columns($table); // Cached, no worries.
 191  
 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          }
 212  
 213          if (empty($wheresql)) {
 214              $wheresql = '';
 215          } else {
 216              $wheresql = implode("AND", $wheresql);
 217          }
 218  
 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          }
 226  
 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      }
 236  
 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;
 245  
 246          if (empty($this->id)) {
 247              debugging('Can not update grade object, no id!');
 248              return false;
 249          }
 250  
 251          $data = $this->get_record_data();
 252  
 253          $DB->update_record($this->table, $data);
 254  
 255          $historyid = null;
 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();
 262              $data->loggeduser   = $USER->id;
 263              $historyid = $DB->insert_record($this->table.'_history', $data);
 264          }
 265  
 266          $this->notify_changed(false);
 267  
 268          $this->update_feedback_files($historyid);
 269  
 270          return true;
 271      }
 272  
 273      /**
 274       * Deletes this object from the database.
 275       *
 276       * @param string $source From where was the object deleted (mod/forum, manual, etc.)
 277       * @return bool success
 278       */
 279      public function delete($source=null) {
 280          global $USER, $CFG, $DB;
 281  
 282          if (empty($this->id)) {
 283              debugging('Can not delete grade object, no id!');
 284              return false;
 285          }
 286  
 287          $data = $this->get_record_data();
 288  
 289          if ($DB->delete_records($this->table, array('id'=>$this->id))) {
 290              if (empty($CFG->disablegradehistory)) {
 291                  unset($data->id);
 292                  unset($data->timecreated);
 293                  $data->action       = GRADE_HISTORY_DELETE;
 294                  $data->oldid        = $this->id;
 295                  $data->source       = $source;
 296                  $data->timemodified = time();
 297                  $data->loggeduser   = $USER->id;
 298                  $DB->insert_record($this->table.'_history', $data);
 299              }
 300  
 301              $this->notify_changed(true);
 302  
 303              $this->delete_feedback_files();
 304  
 305              return true;
 306          } else {
 307              return false;
 308          }
 309      }
 310  
 311      /**
 312       * Returns object with fields and values that are defined in database
 313       *
 314       * @return stdClass
 315       */
 316      public function get_record_data() {
 317          $data = new stdClass();
 318  
 319          foreach ($this as $var=>$value) {
 320              if (in_array($var, $this->required_fields) or array_key_exists($var, $this->optional_fields)) {
 321                  if (is_object($value) or is_array($value)) {
 322                      debugging("Incorrect property '$var' found when inserting grade object");
 323                  } else {
 324                      $data->$var = $value;
 325                  }
 326              }
 327          }
 328          return $data;
 329      }
 330  
 331      /**
 332       * Records this object in the Database, sets its id to the returned value, and returns that value.
 333       * If successful this function also fetches the new object data from database and stores it
 334       * in object properties.
 335       *
 336       * @param string $source From where was the object inserted (mod/forum, manual, etc.)
 337       * @return int The new grade object ID if successful, false otherwise
 338       */
 339      public function insert($source=null) {
 340          global $USER, $CFG, $DB;
 341  
 342          if (!empty($this->id)) {
 343              debugging("Grade object already exists!");
 344              return false;
 345          }
 346  
 347          $data = $this->get_record_data();
 348  
 349          $this->id = $DB->insert_record($this->table, $data);
 350  
 351          // set all object properties from real db data
 352          $this->update_from_db();
 353  
 354          $data = $this->get_record_data();
 355  
 356          $historyid = null;
 357          if (empty($CFG->disablegradehistory)) {
 358              unset($data->timecreated);
 359              $data->action       = GRADE_HISTORY_INSERT;
 360              $data->oldid        = $this->id;
 361              $data->source       = $source;
 362              $data->timemodified = time();
 363              $data->loggeduser   = $USER->id;
 364              $historyid = $DB->insert_record($this->table.'_history', $data);
 365          }
 366  
 367          $this->notify_changed(false);
 368  
 369          $this->add_feedback_files($historyid);
 370  
 371          return $this->id;
 372      }
 373  
 374      /**
 375       * Using this object's id field, fetches the matching record in the DB, and looks at
 376       * each variable in turn. If the DB has different data, the db's data is used to update
 377       * the object. This is different from the update() function, which acts on the DB record
 378       * based on the object.
 379       *
 380       * @return bool True if successful
 381       */
 382      public function update_from_db() {
 383          if (empty($this->id)) {
 384              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.");
 385              return false;
 386          }
 387          global $DB;
 388          if (!$params = $DB->get_record($this->table, array('id' => $this->id))) {
 389              debugging("Object with this id:{$this->id} does not exist in table:{$this->table}, can not update from db!");
 390              return false;
 391          }
 392  
 393          grade_object::set_properties($this, $params);
 394  
 395          return true;
 396      }
 397  
 398      /**
 399       * Given an associated array or object, cycles through each key/variable
 400       * and assigns the value to the corresponding variable in this object.
 401       *
 402       * @param stdClass $instance The object to set the properties on
 403       * @param array $params An array of properties to set like $propertyname => $propertyvalue
 404       * @return array|stdClass Either an associative array or an object containing property name, property value pairs
 405       */
 406      public static function set_properties(&$instance, $params) {
 407          $params = (array) $params;
 408          foreach ($params as $var => $value) {
 409              if (in_array($var, $instance->required_fields) or array_key_exists($var, $instance->optional_fields)) {
 410                  $instance->$var = $value;
 411              }
 412          }
 413      }
 414  
 415      /**
 416       * Called immediately after the object data has been inserted, updated, or
 417       * deleted in the database. Default does nothing, can be overridden to
 418       * hook in special behaviour.
 419       *
 420       * @param bool $deleted
 421       */
 422      protected function notify_changed($deleted) {
 423      }
 424  
 425      /**
 426       * Handles adding feedback files in the gradebook.
 427       *
 428       * @param int|null $historyid
 429       */
 430      protected function add_feedback_files(int $historyid = null) {
 431      }
 432  
 433      /**
 434       * Handles updating feedback files in the gradebook.
 435       *
 436       * @param int|null $historyid
 437       */
 438      protected function update_feedback_files(int $historyid = null) {
 439      }
 440  
 441      /**
 442       * Handles deleting feedback files in the gradebook.
 443       */
 444      protected function delete_feedback_files() {
 445      }
 446  
 447      /**
 448       * Returns the current hidden state of this grade_item
 449       *
 450       * This depends on the grade object hidden setting and the current time if hidden is set to a "hidden until" timestamp
 451       *
 452       * @return bool Current hidden state
 453       */
 454      function is_hidden() {
 455          return ($this->hidden == 1 or ($this->hidden != 0 and $this->hidden > time()));
 456      }
 457  
 458      /**
 459       * Check grade object hidden status
 460       *
 461       * @return bool True if a "hidden until" timestamp is set, false if grade object is set to always visible or always hidden.
 462       */
 463      function is_hiddenuntil() {
 464          return $this->hidden > 1;
 465      }
 466  
 467      /**
 468       * Check a grade item hidden status.
 469       *
 470       * @return int 0 means visible, 1 hidden always, a timestamp means "hidden until"
 471       */
 472      function get_hidden() {
 473          return $this->hidden;
 474      }
 475  
 476      /**
 477       * Set a grade object hidden status
 478       *
 479       * @param int $hidden 0 means visiable, 1 means hidden always, a timestamp means "hidden until"
 480       * @param bool $cascade Ignored
 481       */
 482      function set_hidden($hidden, $cascade=false) {
 483          $this->hidden = $hidden;
 484          $this->update();
 485      }
 486  
 487      /**
 488       * Returns whether the grade object can control the visibility of the grades.
 489       *
 490       * @return bool
 491       */
 492      public function can_control_visibility() {
 493          return true;
 494      }
 495  }