Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.

/**
 * Definition of a grade object class for grade item, grade category etc to inherit from
 *
 * @package   core_grades
 * @category  grade
 * @copyright 2006 Nicolas Connault
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */

defined('MOODLE_INTERNAL') || die();

/**
 * An abstract object that holds methods and attributes common to all grade_* objects defined here.
 *
 * @package   core_grades
 * @category  grade
 * @copyright 2006 Nicolas Connault
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
abstract class grade_object {
    /**
     * The database table this grade object is stored in
     * @var string $table
     */
    public $table;

    /**
     * Array of required table fields, must start with 'id'.
     * @var array $required_fields
     */
    public $required_fields = array('id', 'timecreated', 'timemodified', 'hidden');

    /**
     * Array of optional fields with default values - usually long text information that is not always needed.
     * If you want to create an instance without optional fields use: new grade_object($only_required_fields, false);
     * @var array $optional_fields
     */
    public $optional_fields = array();

    /**
     * The PK.
     * @var int $id
     */
    public $id;

    /**
     * The first time this grade_object was created.
     * @var int $timecreated
     */
    public $timecreated;

    /**
     * The last time this grade_object was modified.
     * @var int $timemodified
     */
    public $timemodified;

    /**
     * 0 if visible, 1 always hidden or date not visible until
     * @var int $hidden
     */
    var $hidden = 0;

    /**
     * Constructor. Optionally (and by default) attempts to fetch corresponding row from the database
     *
     * @param array $params An array with required parameters for this grade object.
     * @param bool $fetch Whether to fetch corresponding row from the database or not,
     *        optional fields might not be defined if false used
     */
    public function __construct($params=NULL, $fetch=true) {
        if (!empty($params) and (is_array($params) or is_object($params))) {
            if ($fetch) {
                if ($data = $this->fetch($params)) {
                    grade_object::set_properties($this, $data);
                } else {
                    grade_object::set_properties($this, $this->optional_fields);//apply defaults for optional fields
                    grade_object::set_properties($this, $params);
                }

            } else {
                grade_object::set_properties($this, $params);
            }

        } else {
            grade_object::set_properties($this, $this->optional_fields);//apply defaults for optional fields
        }
    }

    /**
     * Makes sure all the optional fields are loaded.
     *
     * If id present, meaning the instance exists in the database, then data will be fetched from the database.
     * Defaults are used for new instances.
     */
    public function load_optional_fields() {
        global $DB;
        foreach ($this->optional_fields as $field=>$default) {
            if (property_exists($this, $field)) {
                continue;
            }
            if (empty($this->id)) {
                $this->$field = $default;
            } else {
                $this->$field = $DB->get_field($this->table, $field, array('id', $this->id));
            }
        }
    }

    /**
     * Finds and returns a grade_object instance based on params.
     *
     * @static
     * @abstract
     * @param array $params associative arrays varname=>value
     * @return object grade_object instance or false if none found.
     */
    public static function fetch($params) {
        throw new coding_exception('fetch() method needs to be overridden in each subclass of grade_object');
    }

    /**
     * Finds and returns all grade_object instances based on $params.
     *
     * @static
     * @abstract
     * @throws coding_exception Throws a coding exception if fetch_all() has not been overriden by the grade object subclass
     * @param array $params Associative arrays varname=>value
     * @return array|bool Array of grade_object instances or false if none found.
     */
    public static function fetch_all($params) {
        throw new coding_exception('fetch_all() method needs to be overridden in each subclass of grade_object');
    }

    /**
     * Factory method which uses the parameters to retrieve matching instances from the database
     *
     * @param string $table The table to retrieve from
     * @param string $classname The name of the class to instantiate
     * @param array $params An array of conditions like $fieldname => $fieldvalue
     * @return mixed An object instance or false if not found
     */
    protected static function fetch_helper($table, $classname, $params) {
        if ($instances = grade_object::fetch_all_helper($table, $classname, $params)) {
            if (count($instances) > 1) {
                // we should not tolerate any errors here - problems might appear later
                print_error('morethanonerecordinfetch','debug');
            }
            return reset($instances);
        } else {
            return false;
        }
    }

    /**
     * Factory method which uses the parameters to retrieve all matching instances from the database
     *
     * @param string $table The table to retrieve from
     * @param string $classname The name of the class to instantiate
     * @param array $params An array of conditions like $fieldname => $fieldvalue
     * @return array|bool Array of object instances or false if not found
     */
    public static function fetch_all_helper($table, $classname, $params) {
        global $DB; // Need to introspect DB here.

        $instance = new $classname();

        $classvars = (array)$instance;
        $params    = (array)$params;

        $wheresql = array();
        $newparams = array();

        $columns = $DB->get_columns($table); // Cached, no worries.

        foreach ($params as $var=>$value) {
            if (!in_array($var, $instance->required_fields) and !array_key_exists($var, $instance->optional_fields)) {
                continue;
            }
            if (!array_key_exists($var, $columns)) {
                continue;
            }
            if (is_null($value)) {
                $wheresql[] = " $var IS NULL ";
            } else {
                if ($columns[$var]->meta_type === 'X') {
                    // We have a text/clob column, use the cross-db method for its comparison.
                    $wheresql[] = ' ' . $DB->sql_compare_text($var) . ' = ' . $DB->sql_compare_text('?') . ' ';
                } else {
                    // Other columns (varchar, integers...).
                    $wheresql[] = " $var = ? ";
                }
                $newparams[] = $value;
            }
        }

        if (empty($wheresql)) {
            $wheresql = '';
        } else {
            $wheresql = implode("AND", $wheresql);
        }

        global $DB;
        $rs = $DB->get_recordset_select($table, $wheresql, $newparams);
        //returning false rather than empty array if nothing found
        if (!$rs->valid()) {
            $rs->close();
            return false;
        }

        $result = array();
        foreach($rs as $data) {
            $instance = new $classname();
            grade_object::set_properties($instance, $data);
            $result[$instance->id] = $instance;
        }
        $rs->close();
        return $result;
    }

    /**
     * Updates this object in the Database, based on its object variables. ID must be set.
     *
     * @param string $source from where was the object updated (mod/forum, manual, etc.)
> * @param bool $isbulkupdate If bulk grade update is happening.
* @return bool success */
< public function update($source=null) {
> public function update($source = null, $isbulkupdate = false) {
global $USER, $CFG, $DB; if (empty($this->id)) { debugging('Can not update grade object, no id!'); return false; } $data = $this->get_record_data(); $DB->update_record($this->table, $data); $historyid = null; if (empty($CFG->disablegradehistory)) { unset($data->timecreated); $data->action = GRADE_HISTORY_UPDATE; $data->oldid = $this->id; $data->source = $source; $data->timemodified = time(); $data->loggeduser = $USER->id; $historyid = $DB->insert_record($this->table.'_history', $data); }
< $this->notify_changed(false);
> $this->notify_changed(false, $isbulkupdate);
$this->update_feedback_files($historyid); return true; } /** * Deletes this object from the database. * * @param string $source From where was the object deleted (mod/forum, manual, etc.) * @return bool success */ public function delete($source=null) { global $USER, $CFG, $DB; if (empty($this->id)) { debugging('Can not delete grade object, no id!'); return false; } $data = $this->get_record_data(); if ($DB->delete_records($this->table, array('id'=>$this->id))) { if (empty($CFG->disablegradehistory)) { unset($data->id); unset($data->timecreated); $data->action = GRADE_HISTORY_DELETE; $data->oldid = $this->id; $data->source = $source; $data->timemodified = time(); $data->loggeduser = $USER->id; $DB->insert_record($this->table.'_history', $data); } $this->notify_changed(true); $this->delete_feedback_files(); return true; } else { return false; } } /** * Returns object with fields and values that are defined in database * * @return stdClass */ public function get_record_data() { $data = new stdClass(); foreach ($this as $var=>$value) { if (in_array($var, $this->required_fields) or array_key_exists($var, $this->optional_fields)) { if (is_object($value) or is_array($value)) { debugging("Incorrect property '$var' found when inserting grade object"); } else { $data->$var = $value; } } } return $data; } /** * Records this object in the Database, sets its id to the returned value, and returns that value. * If successful this function also fetches the new object data from database and stores it * in object properties. * * @param string $source From where was the object inserted (mod/forum, manual, etc.)
> * @param string $isbulkupdate If bulk grade update is happening.
* @return int The new grade object ID if successful, false otherwise */
< public function insert($source=null) {
> public function insert($source = null, $isbulkupdate = false) {
global $USER, $CFG, $DB; if (!empty($this->id)) { debugging("Grade object already exists!"); return false; } $data = $this->get_record_data(); $this->id = $DB->insert_record($this->table, $data); // set all object properties from real db data $this->update_from_db(); $data = $this->get_record_data(); $historyid = null; if (empty($CFG->disablegradehistory)) { unset($data->timecreated); $data->action = GRADE_HISTORY_INSERT; $data->oldid = $this->id; $data->source = $source; $data->timemodified = time(); $data->loggeduser = $USER->id; $historyid = $DB->insert_record($this->table.'_history', $data); }
< $this->notify_changed(false);
> $this->notify_changed(false, $isbulkupdate);
$this->add_feedback_files($historyid); return $this->id; } /** * Using this object's id field, fetches the matching record in the DB, and looks at * each variable in turn. If the DB has different data, the db's data is used to update * the object. This is different from the update() function, which acts on the DB record * based on the object. * * @return bool True if successful */ public function update_from_db() { if (empty($this->id)) { 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."); return false; } global $DB; if (!$params = $DB->get_record($this->table, array('id' => $this->id))) { debugging("Object with this id:{$this->id} does not exist in table:{$this->table}, can not update from db!"); return false; } grade_object::set_properties($this, $params); return true; } /** * Given an associated array or object, cycles through each key/variable * and assigns the value to the corresponding variable in this object. * * @param stdClass $instance The object to set the properties on * @param array $params An array of properties to set like $propertyname => $propertyvalue * @return array|stdClass Either an associative array or an object containing property name, property value pairs */ public static function set_properties(&$instance, $params) { $params = (array) $params; foreach ($params as $var => $value) { if (in_array($var, $instance->required_fields) or array_key_exists($var, $instance->optional_fields)) { $instance->$var = $value; } } } /** * Called immediately after the object data has been inserted, updated, or * deleted in the database. Default does nothing, can be overridden to * hook in special behaviour. * * @param bool $deleted */ protected function notify_changed($deleted) { } /** * Handles adding feedback files in the gradebook. * * @param int|null $historyid */ protected function add_feedback_files(int $historyid = null) { } /** * Handles updating feedback files in the gradebook. * * @param int|null $historyid */ protected function update_feedback_files(int $historyid = null) { } /** * Handles deleting feedback files in the gradebook. */ protected function delete_feedback_files() { } /** * Returns the current hidden state of this grade_item * * This depends on the grade object hidden setting and the current time if hidden is set to a "hidden until" timestamp * * @return bool Current hidden state */ function is_hidden() { return ($this->hidden == 1 or ($this->hidden != 0 and $this->hidden > time())); } /** * Check grade object hidden status * * @return bool True if a "hidden until" timestamp is set, false if grade object is set to always visible or always hidden. */ function is_hiddenuntil() { return $this->hidden > 1; } /** * Check a grade item hidden status. * * @return int 0 means visible, 1 hidden always, a timestamp means "hidden until" */ function get_hidden() { return $this->hidden; } /** * Set a grade object hidden status * * @param int $hidden 0 means visiable, 1 means hidden always, a timestamp means "hidden until" * @param bool $cascade Ignored */ function set_hidden($hidden, $cascade=false) { $this->hidden = $hidden; $this->update(); } /** * Returns whether the grade object can control the visibility of the grades. * * @return bool */ public function can_control_visibility() { return true; } }