apppath */ define('F_RELATION_FILE', 0); /** * Relation index for class name */ define('F_RELATION_CLASS', 1); /** * Relation index for field-link alternate name */ define('F_RELATION_ALTFIELD', 2); } /** * Abstract API * * Abstract class that is used as an API base for most common database interaction * schemes. Creates a simple structure that holds data and can update, delete, and * insert. * * @author Blue Static * @copyright Copyright ©2002 - [#]year[#], Blue Static * @version $Revision$ * @package ISSO * */ class API { /** * Registry object * @var object * @access protected */ var $registry = null; /** * Fields: used for verification and sanitization * NAME => array(TYPE, REQUIRED, VERIFY METHOD (:self for self-named method), RELATION => array(FILE, CLASS IN FILE, ALTERNATE FIELD NAME)) * @var array * @access protected */ var $fields = array(); /** * Values array: sanitized and verified field values * @var array * @access public */ var $values = array(); /** * Fields that were manually set with set(), not by using set_existing() * @var array * @access private */ var $setfields = array(); /** * An array of all of the processed relations on an object * @var array * @access public */ var $relations = array(); /** * WHERE condition * @var string * @access private */ var $condition = ''; /** * The object table row; a fetched row that represents this instance * @var array * @access public */ var $objdata = array(); /** * Insert ID from the insert() command * @var integer * @access public */ var $insertid = 0; /** * Pre- and post-action method stoppers * @var array * @access public */ var $norunners = array(); /** * The relations to execute on * @var array * @access public */ var $dorelations = array('fetch'); /** * Error list that has been generated * @var array * @access private */ var $errors = array(); // ################################################################### /** * Constructor: cannot instantiate class directly */ function __construct(&$registry) { if (!is_subclass_of($this, 'API')) { trigger_error('Cannot instantiate the API module directly', E_USER_ERROR); } if (!is_object($registry)) { trigger_error('The passed registry is not an object', E_USER_ERROR); } $this->registry =& $registry; } // ################################################################### /** * (PHP 4) Constructor */ function API(&$registry) { $this->__construct($registry); } // ################################################################### /** * Constructs an error for the error handler to receive * * @access protected * * @param string Error message */ function error($message) { $this->errors[] = $message; // we want to explicitly specify silence if (APIError() == 'silent') { return; } if (!is_callable(APIError())) { trigger_error('No APIError() handler has been set', E_USER_WARNING); } call_user_func(APIError(), $message); } // ################################################################### /** * Returns the error list. This is because we don't want people mucking * with the error system. It will return an empty array if there are * no errors. * * @access public * * @return array Array of errors */ function check_errors() { if (sizeof($this->errors) < 1) { return array(); } return $this->errors; } // ################################################################### /** * Sets a value, sanitizes it, and verifies it * * @access public * * @param string Field name * @param mixed Value * @param bool Do clean? * @param bool Do verify? */ function set($field, $value, $doclean = true, $doverify = true) { if (!isset($this->fields["$field"])) { trigger_error('Field `' . $field . '` is not valid', E_USER_WARNING); return; } $this->values["$field"] = ($doclean ? $this->registry->clean($value, $this->fields["$field"][F_TYPE]) : $value); $this->setfields["$field"] = $field; if (isset($this->fields["$field"][F_VERIFY]) AND $doverify) { if ($this->fields["$field"][F_VERIFY] == ':self') { $verify = $this->{"verify_$field"}($field); } else { $verify = $this->{$this->fields["$field"][F_VERIFY]}($field); } if ($verify !== true) { if ($verify === false) { $this->error(sprintf(_('Validation of %1$s failed'), $field)); } else { $this->error($verify); } } } } // ################################################################### /** * Sets the condition to use in the WHERE clause; if not passed, then * it calculates it from the REQ_AUTO field * * @access public * * @param mixed String with WHERE condition; array of fields to use for WHERE builder */ function set_condition($condition = '') { if (is_array($condition) AND sizeof($condition) > 0) { $this->condition = ''; foreach ($condition AS $field) { if (!$this->values["$field"]) { trigger_error('The specified field `' . $field . '` for the condition could not be found as it is not set', E_USER_WARNING); continue; } $condbits[] = "$field = " . $this->prepare_field_for_sql($field); } $this->condition = implode(' AND ', $condbits); } else if ($condition != '') { $this->condition = $condition; } else { foreach ($this->fields AS $name => $options) { if ($options[F_REQ] == REQ_AUTO) { if (!$this->values["$name"]) { trigger_error('Cannot determine condition from the REQ_AUTO field because it is not set', E_USER_WARNING); continue; } $this->condition = "$name = " . $this->prepare_field_for_sql($name); } } if ($this->condition == '') { trigger_error('No REQ_AUTO fields are present and therefore the condition cannot be created', E_USER_WARNING); } } } // ################################################################### /** * Sets existing data into $values where it's not already present * * @access public */ function set_existing() { static $run; if ($run) { return; } $this->fetch(); foreach ($this->objdata AS $field => $value) { if (!isset($this->values["$field"])) { $this->values["$field"] = $value; } } $run = true; } // ################################################################### /** * Fetches a record based on the condition * * @param bool Populate $this->values[] with data, along with $this->objdata[] ? * * @access public */ function fetch($populate = false) { if ($this->condition == '') { trigger_error('Condition is empty: cannot fetch', E_USER_ERROR); } $this->run_action_method('pre_fetch'); $result = $this->registry->modules[ISSO_DB_LAYER]->query_first("SELECT * FROM {$this->prefix}{$this->table} WHERE {$this->condition}"); if (!$result) { $this->error(_('No records were returned')); return; } $this->run_action_method('post_fetch'); $this->objdata = $result; if ($populate) { foreach ($this->objdata AS $key => $value) { if (!isset($this->values["$key"])) { $this->values["$key"] = $value; } } } $this->call_relations('fetch'); } // ################################################################### /** * Inserts a record in the database * * @access public */ function insert() { $this->verify(); $this->run_action_method('pre_insert'); foreach ($this->setfields AS $field) { $fields[] = $field; $values[] = $this->prepare_field_for_sql($field); } $this->registry->modules[ISSO_DB_LAYER]->query("INSERT INTO {$this->prefix}{$this->table} (" . implode(',', $fields) . ") VALUES (" . implode(',', $values) . ")"); if (strcasecmp(ISSO_DB_LAYER, 'DB_PostgreSQL') == 0) { foreach ($this->fields AS $field => $info) { if ($info[F_REQ] == REQ_AUTO) { $autofield = $field; break; } } $this->insertid = $this->registry->modules[ISSO_DB_LAYER]->insert_id($this->prefix . $this->table, $autofield); } else { $this->insertid = $this->registry->modules[ISSO_DB_LAYER]->insert_id(); } $this->run_action_method('post_insert'); } // ################################################################### /** * Updates a record in the database using the data in $vaues * * @access public */ function update() { if ($this->condition == '') { trigger_error('Condition is empty: cannot update', E_USER_ERROR); } $this->run_action_method('pre_update'); foreach ($this->setfields AS $field) { $updates[] = "$field = " . $this->prepare_field_for_sql($field); } $updates = implode(', ', $updates); $this->registry->modules[ISSO_DB_LAYER]->query("UPDATE {$this->prefix}{$this->table} SET $updates WHERE {$this->condition}"); $this->run_action_method('post_update'); } // ################################################################### /** * Deletes a record * * @access public * * @param bool Run API->set_existing()? */ function delete($runset = true) { if ($this->condition == '') { trigger_error('Condition is empty: cannot delete', E_USER_ERROR); } if ($runset) { $this->set_existing(); } $this->run_action_method('pre_delete'); $this->registry->modules[ISSO_DB_LAYER]->query("DELETE FROM {$this->prefix}{$this->table} WHERE {$this->condition}"); $this->run_action_method('post_delete'); } // ################################################################### /** * Verifies that all required fields are set * * @access private */ function verify() { foreach ($this->fields AS $name => $options) { if ($options[F_REQ] == REQ_YES) { if (!isset($this->values["$name"])) { $this->error(sprintf(_('Required field %1$s was not set'), $name)); } } else if ($options[F_REQ] == REQ_SET) { $this->{"set_$name"}(); } } } // ################################################################### /** * Runs a pre- or post-action method for database commands * * @access private * * @param string Action to run */ function run_action_method($method) { if (in_array($method, $this->norunners)) { return; } $actmethod = (method_exists($this, $method) ? $this->$method() : ''); } // ################################################################### /** * Determines if it's safe to run a relation; if so, it will return * the WHERE SQL clause * * @access public * * @param string Operation to run */ function call_relations($method) { if (!is_array($this->dorelations) OR !in_array($method, $this->dorelations)) { return; } foreach ($this->fields AS $field => $info) { $value = (isset($this->values["$field"]) ? $this->values["$field"] : $this->objdata["$field"]); if ($value == null OR !is_array($info[F_RELATION])) { continue; } if (!file_exists($this->registry->getAppPath() . $info[F_RELATION][F_RELATION_FILE])) { trigger_error("Could not load the relation file for field '$field'"); } require_once($this->registry->getAppPath() . $info[F_RELATION][F_RELATION_FILE]); $this->relations["$field"] = new $info[F_RELATION][F_RELATION_CLASS]($this->registry); $this->relations["$field"]->set(($info[F_RELATION][F_RELATION_ALTFIELD] ? $info[F_RELATION][F_RELATION_ALTFIELD] : $field), $value); $this->relations["$field"]->set_condition(); $this->relations["$field"]->$method(); } } // ################################################################### /** * Prepares a value for use in a SQL query; it encases and escapes * strings and string-like values * * @access private * * @param string Field name * * @return string Prepared value entry */ function prepare_field_for_sql($name) { $type = $this->fields["$name"][F_TYPE]; if ($type == TYPE_NOCLEAN OR $type == TYPE_STR OR $type == TYPE_STRUN) { return "'" . $this->registry->db->escape_string($this->values["$name"]) . "'"; } else if ($type == TYPE_BOOL) { return ($this->values["$name"] == true ? "'1'" : "'0'"); } else if ($type == TYPE_BIN) { return "'" . $this->registry->db->escape_binary($this->values["$name"]) . "'"; } else { return $this->values["$name"]; } } // ################################################################### /** * Verify field: not a zero value * * @access protected */ function verify_nozero($field) { if ($this->values["$field"] == 0) { return sprintf(_('The field "%1$s" cannot be zero'), $field); } return true; } // ################################################################### /** * Verify field: not empty * * @access protected */ function verify_noempty($field) { if (empty($this->values["$field"])) { return sprintf(_('The field "%1$s" cannot be empty'), $field); } return true; } } // ################################################################### /** * Setter and getter method for the API error reporting system. Passing * a name will cause the set, no arguments will cause the get. * * @access public * * @param mixed Method name in callable form * * @return mixed Method name in callable form */ function APIError($new = null) { static $caller, $prev; if ($new === -1) { $caller = $prev; } else if ($new !== null) { $prev = $caller; $caller = $new; } return $caller; } /*=====================================================================*\ || ################################################################### || # $HeadURL$ || # $Id$ || ################################################################### \*=====================================================================*/ ?>