Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.

Differences Between: [Versions 310 and 402] [Versions 311 and 402] [Versions 39 and 402] [Versions 400 and 402] [Versions 401 and 402]

   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                  throw new \moodle_exception('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       * @param bool $isbulkupdate If bulk grade update is happening.
 242       * @return bool success
 243       */
 244      public function update($source = null, $isbulkupdate = false) {
 245          global $USER, $CFG, $DB;
 246  
 247          if (empty($this->id)) {
 248              debugging('Can not update grade object, no id!');
 249              return false;
 250          }
 251  
 252          $data = $this->get_record_data();
 253  
 254          $DB->update_record($this->table, $data);
 255  
 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);
 265          }
 266  
 267          $this->notify_changed(false, $isbulkupdate);
 268  
 269          $this->update_feedback_files($historyid);
 270  
 271          return true;
 272      }
 273  
 274      /**
 275       * Deletes this object from the database.
 276       *
 277       * @param string $source From where was the object deleted (mod/forum, manual, etc.)
 278       * @return bool success
 279       */
 280      public function delete($source=null) {
 281          global $USER, $CFG, $DB;
 282  
 283          if (empty($this->id)) {
 284              debugging('Can not delete grade object, no id!');
 285              return false;
 286          }
 287  
 288          $data = $this->get_record_data();
 289  
 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);
 300              }
 301  
 302              $this->notify_changed(true);
 303  
 304              $this->delete_feedback_files();
 305  
 306              return true;
 307          } else {
 308              return false;
 309          }
 310      }
 311  
 312      /**
 313       * Returns object with fields and values that are defined in database
 314       *
 315       * @return stdClass
 316       */
 317      public function get_record_data() {
 318          $data = new stdClass();
 319  
 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;
 326                  }
 327              }
 328          }
 329          return $data;
 330      }
 331  
 332      /**
 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.
 336       *
 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
 340       */
 341      public function insert($source = null, $isbulkupdate = false) {
 342          global $USER, $CFG, $DB;
 343  
 344          if (!empty($this->id)) {
 345              debugging("Grade object already exists!");
 346              return false;
 347          }
 348  
 349          $data = $this->get_record_data();
 350  
 351          $this->id = $DB->insert_record($this->table, $data);
 352  
 353          // set all object properties from real db data
 354          $this->update_from_db();
 355  
 356          $data = $this->get_record_data();
 357  
 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);
 367          }
 368  
 369          $this->notify_changed(false, $isbulkupdate);
 370  
 371          $this->add_feedback_files($historyid);
 372  
 373          return $this->id;
 374      }
 375  
 376      /**
 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.
 381       *
 382       * @return bool True if successful
 383       */
 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;
 388          }
 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;
 393          }
 394  
 395          grade_object::set_properties($this, $params);
 396  
 397          return true;
 398      }
 399  
 400      /**
 401       * Given an associated array or object, cycles through each key/variable
 402       * and assigns the value to the corresponding variable in this object.
 403       *
 404       * @param grade_object $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
 407       */
 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;
 413              }
 414          }
 415      }
 416  
 417      /**
 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.
 421       *
 422       * @param bool $deleted
 423       */
 424      protected function notify_changed($deleted) {
 425      }
 426  
 427      /**
 428       * Handles adding feedback files in the gradebook.
 429       *
 430       * @param int|null $historyid
 431       */
 432      protected function add_feedback_files(int $historyid = null) {
 433      }
 434  
 435      /**
 436       * Handles updating feedback files in the gradebook.
 437       *
 438       * @param int|null $historyid
 439       */
 440      protected function update_feedback_files(int $historyid = null) {
 441      }
 442  
 443      /**
 444       * Handles deleting feedback files in the gradebook.
 445       */
 446      protected function delete_feedback_files() {
 447      }
 448  
 449      /**
 450       * Returns the current hidden state of this grade_item
 451       *
 452       * This depends on the grade object hidden setting and the current time if hidden is set to a "hidden until" timestamp
 453       *
 454       * @return bool Current hidden state
 455       */
 456      function is_hidden() {
 457          return ($this->hidden == 1 or ($this->hidden != 0 and $this->hidden > time()));
 458      }
 459  
 460      /**
 461       * Check grade object hidden status
 462       *
 463       * @return bool True if a "hidden until" timestamp is set, false if grade object is set to always visible or always hidden.
 464       */
 465      function is_hiddenuntil() {
 466          return $this->hidden > 1;
 467      }
 468  
 469      /**
 470       * Check a grade item hidden status.
 471       *
 472       * @return int 0 means visible, 1 hidden always, a timestamp means "hidden until"
 473       */
 474      function get_hidden() {
 475          return $this->hidden;
 476      }
 477  
 478      /**
 479       * Set a grade object hidden status
 480       *
 481       * @param int $hidden 0 means visiable, 1 means hidden always, a timestamp means "hidden until"
 482       * @param bool $cascade Ignored
 483       */
 484      function set_hidden($hidden, $cascade=false) {
 485          $this->hidden = $hidden;
 486          $this->update();
 487      }
 488  
 489      /**
 490       * Returns whether the grade object can control the visibility of the grades.
 491       *
 492       * @return bool
 493       */
 494      public function can_control_visibility() {
 495          return true;
 496      }
 497  }