You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
394 lines
13 KiB
394 lines
13 KiB
2 years ago
|
<?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/>.
|
||
|
|
||
|
/**
|
||
|
* Course completion critieria aggregation
|
||
|
*
|
||
|
* @package core_completion
|
||
|
* @category completion
|
||
|
* @copyright 2009 Catalyst IT Ltd
|
||
|
* @author Aaron Barnes <aaronb@catalyst.net.nz>
|
||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||
|
*/
|
||
|
|
||
|
defined('MOODLE_INTERNAL') || die();
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Trigger for the new data_object api.
|
||
|
*
|
||
|
* See data_object::__constructor
|
||
|
*/
|
||
|
define('DATA_OBJECT_FETCH_BY_KEY', 2);
|
||
|
|
||
|
/**
|
||
|
* A data abstraction object that holds methods and attributes
|
||
|
*
|
||
|
* @package core_completion
|
||
|
* @category completion
|
||
|
* @copyright 2009 Catalyst IT Ltd
|
||
|
* @author Aaron Barnes <aaronb@catalyst.net.nz>
|
||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||
|
*/
|
||
|
abstract class data_object {
|
||
|
|
||
|
/* @var string Table that the class maps to in the database */
|
||
|
public $table;
|
||
|
|
||
|
/* @var array Array of required table fields, must start with 'id'. */
|
||
|
public $required_fields = array('id');
|
||
|
|
||
|
/**
|
||
|
* 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 data_object($only_required_fields, false);
|
||
|
* @var array
|
||
|
*/
|
||
|
public $optional_fields = array();
|
||
|
|
||
|
/* @var Array of unique fields, used in where clauses and constructor */
|
||
|
public $unique_fields = array();
|
||
|
|
||
|
/* @var int The primary key */
|
||
|
public $id;
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Constructor. Optionally (and by default) attempts to fetch corresponding row from DB.
|
||
|
*
|
||
|
* If $fetch is not false, there are a few different things that can happen:
|
||
|
* - true:
|
||
|
* load corresponding row from the database, using $params as the WHERE clause
|
||
|
*
|
||
|
* - DATA_OBJECT_FETCH_BY_KEY:
|
||
|
* load corresponding row from the database, using only the $id in the WHERE clause (if set),
|
||
|
* otherwise using the columns listed in $this->unique_fields.
|
||
|
*
|
||
|
* - array():
|
||
|
* load corresponding row from the database, using the columns listed in this array
|
||
|
* in the WHERE clause
|
||
|
*
|
||
|
* @param array $params required parameters and their values for this data object
|
||
|
* @param mixed $fetch if false, do not attempt to fetch from the database, otherwise see notes
|
||
|
*/
|
||
|
public function __construct($params = null, $fetch = true) {
|
||
|
|
||
|
if (is_object($params)) {
|
||
|
throw new coding_exception('data_object params should be in the form of an array, not an object');
|
||
|
}
|
||
|
|
||
|
// If no params given, apply defaults for optional fields
|
||
|
if (empty($params) || !is_array($params)) {
|
||
|
self::set_properties($this, $this->optional_fields);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// If fetch is false, do not load from database
|
||
|
if ($fetch === false) {
|
||
|
self::set_properties($this, $params);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Compose where clause only from fields in unique_fields
|
||
|
if ($fetch === DATA_OBJECT_FETCH_BY_KEY && !empty($this->unique_fields)) {
|
||
|
if (empty($params['id'])) {
|
||
|
$where = array_intersect_key($params, array_flip($this->unique_fields));
|
||
|
}
|
||
|
else {
|
||
|
$where = array('id' => $params['id']);
|
||
|
}
|
||
|
// Compose where clause from given field names
|
||
|
} else if (is_array($fetch) && !empty($fetch)) {
|
||
|
$where = array_intersect_key($params, array_flip($fetch));
|
||
|
// Use entire params array for where clause
|
||
|
} else {
|
||
|
$where = $params;
|
||
|
}
|
||
|
|
||
|
// Attempt to load from database
|
||
|
if ($data = $this->fetch($where)) {
|
||
|
// Apply data from database, then data sent to constructor
|
||
|
self::set_properties($this, $data);
|
||
|
self::set_properties($this, $params);
|
||
|
} else {
|
||
|
// Apply defaults for optional fields, then data from constructor
|
||
|
self::set_properties($this, $this->optional_fields);
|
||
|
self::set_properties($this, $params);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Makes sure all the optional fields are loaded.
|
||
|
*
|
||
|
* If id present (==instance exists in db) fetches data from db.
|
||
|
* 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 data_object instance based on params.
|
||
|
*
|
||
|
* This function MUST be overridden by all deriving classes.
|
||
|
*
|
||
|
* @param array $params associative arrays varname => value
|
||
|
* @throws coding_exception This function MUST be overridden
|
||
|
* @return data_object instance of data_object or false if none found.
|
||
|
*/
|
||
|
public static function fetch($params) {
|
||
|
throw new coding_exception('fetch() method needs to be overridden in each subclass of data_object');
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Finds and returns all data_object instances based on params.
|
||
|
*
|
||
|
* This function MUST be overridden by all deriving classes.
|
||
|
*
|
||
|
* @param array $params associative arrays varname => value
|
||
|
* @throws coding_exception This function MUST be overridden
|
||
|
* @return array array of data_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 data_object');
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Factory method - uses the parameters to retrieve matching instance from the DB.
|
||
|
*
|
||
|
* @final
|
||
|
* @param string $table The table name to fetch from
|
||
|
* @param string $classname The class that you want the result instantiated as
|
||
|
* @param array $params Any params required to select the desired row
|
||
|
* @return object Instance of $classname or false.
|
||
|
*/
|
||
|
protected static function fetch_helper($table, $classname, $params) {
|
||
|
if ($instances = self::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 - uses the parameters to retrieve all matching instances from the DB.
|
||
|
*
|
||
|
* @final
|
||
|
* @param string $table The table name to fetch from
|
||
|
* @param string $classname The class that you want the result instantiated as
|
||
|
* @param array $params Any params required to select the desired row
|
||
|
* @return mixed array of object instances or false if not found
|
||
|
*/
|
||
|
public static function fetch_all_helper($table, $classname, $params) {
|
||
|
$instance = new $classname();
|
||
|
|
||
|
$classvars = (array)$instance;
|
||
|
$params = (array)$params;
|
||
|
|
||
|
$wheresql = array();
|
||
|
|
||
|
$dbparams = array();
|
||
|
foreach ($params as $var=>$value) {
|
||
|
if (!in_array($var, $instance->required_fields) and !array_key_exists($var, $instance->optional_fields)) {
|
||
|
continue;
|
||
|
}
|
||
|
if (is_null($value)) {
|
||
|
$wheresql[] = " $var IS NULL ";
|
||
|
} else {
|
||
|
$wheresql[] = " $var = ? ";
|
||
|
$dbparams[] = $value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (empty($wheresql)) {
|
||
|
$wheresql = '';
|
||
|
} else {
|
||
|
$wheresql = implode("AND", $wheresql);
|
||
|
}
|
||
|
|
||
|
global $DB;
|
||
|
if ($datas = $DB->get_records_select($table, $wheresql, $dbparams)) {
|
||
|
|
||
|
$result = array();
|
||
|
foreach($datas as $data) {
|
||
|
$instance = new $classname();
|
||
|
self::set_properties($instance, $data);
|
||
|
$result[$instance->id] = $instance;
|
||
|
}
|
||
|
return $result;
|
||
|
|
||
|
} else {
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Updates this object in the Database, based on its object variables. ID must be set.
|
||
|
*
|
||
|
* @return bool success
|
||
|
*/
|
||
|
public function update() {
|
||
|
global $DB;
|
||
|
|
||
|
if (empty($this->id)) {
|
||
|
debugging('Can not update data object, no id!');
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
$data = $this->get_record_data();
|
||
|
|
||
|
$DB->update_record($this->table, $data);
|
||
|
|
||
|
$this->notify_changed(false);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Deletes this object from the database.
|
||
|
*
|
||
|
* @return bool success
|
||
|
*/
|
||
|
public function delete() {
|
||
|
global $DB;
|
||
|
|
||
|
if (empty($this->id)) {
|
||
|
debugging('Can not delete data object, no id!');
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
$data = $this->get_record_data();
|
||
|
|
||
|
if ($DB->delete_records($this->table, array('id'=>$this->id))) {
|
||
|
$this->notify_changed(true);
|
||
|
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 data 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.
|
||
|
*
|
||
|
* @return int PK ID if successful, false otherwise
|
||
|
*/
|
||
|
public function insert() {
|
||
|
global $DB;
|
||
|
|
||
|
if (!empty($this->id)) {
|
||
|
debugging("Data 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();
|
||
|
|
||
|
$this->notify_changed(false);
|
||
|
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 for success, false otherwise.
|
||
|
*/
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
self::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.
|
||
|
*
|
||
|
* @final
|
||
|
* @param data_object $instance
|
||
|
* @param array $params
|
||
|
*/
|
||
|
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 Set this to true if it has been deleted.
|
||
|
*/
|
||
|
public function notify_changed($deleted) {
|
||
|
}
|
||
|
}
|