array(TYPE, REQUIRED, VERIFY METHOD (:self for self-named method), RELATION => array(FILE, CLASS IN FILE, ALTERNATE FIELD NAME)) * @var array */ protected $fields = array(); /** * The table name the API maps objects for * @var string */ protected $table = '___UNDEFINED___'; /** * The database table prefix * @var string */ protected $prefix = ''; /** * Values array: sanitized and verified field values * @var array */ public $values = array(); /** * Fields that were set by the client * @var array */ private $setfields = array(); /** * WHERE condition * @var string */ private $condition = ''; /** * The object table row; a fetched row that represents this instance * @var array */ public $objdata = array(); /** * Insert ID from the insert() command * @var integer */ public $insertid = 0; /** * Error queue that builds up errors * @var ApiException */ private $exception = null; // ################################################################### /** * Constructor: cannot instantiate class directly */ public function __construct() { BSApp::RequiredModules(array('Db', 'Input')); } // ################################################################### /** * Adds an error into the error queue that is then hit * * @param Exception Error message */ protected function _error(Exception $e) { if ($this->exception == null) { $this->exception = new ApiException(); } $this->exception->addException($e); } // ################################################################### /** * This simply throws the ApiException if it exists, which inside holds * all of the individual and specific errors */ private function _processErrorQueue() { if ($this->exception) { throw $this->exception; } } // ################################################################### /** * Returns the list of exceptions contained in the ApiException * * @return array Array of errors */ public function isValid() { if ($this->exception == null) { return array(); } return $this->exception->getExceptions(); } // ################################################################### /** * Sets a value, sanitizes it, and verifies it * * @param string Field name * @param mixed Value * @param bool Do clean? * @param bool Do verify? */ public function set($field, $value, $doclean = true, $doverify = true) { if (!isset($this->fields["$field"])) { throw new Exception('Field "' . $field . '" is not valid'); } $this->values["$field"] = ($doclean ? BSApp::Registry()->getType('Input')->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(new Exception(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 * * @param mixed String with WHERE condition; array of fields to use for WHERE builder */ public function setCondition($condition = '') { if (is_array($condition) AND sizeof($condition) > 0) { $this->condition = ''; foreach ($condition AS $field) { if (!$this->values["$field"]) { throw new Exception('The specified field "' . $field . '" for the condition could not be found as it is not set'); } $condbits[] = "$field = " . $this->_prepareFieldForSql($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"]) { throw new Exception('Cannot determine condition from the REQ_AUTO field because it is not set'); } $this->condition = "$name = " . $this->_prepareFieldForSql($name); } } if ($this->condition == '') { throw new Exception('No REQ_AUTO fields are present and therefore the condition cannot be created'); } } } // ################################################################### /** * Fetches a record based on the condition * * @param bool Run pre_fetch()? * @param bool Run post_fetch()? * * @return boolean Whether or not the row was successfully fetched */ public function fetch($doPre = true, $doPost = true) { if ($this->condition == '') { $this->setCondition(); } // reset the error queue due to any validation errors caused by fetchable fields $this->errors = null; $this->_runActionMethod('pre_fetch', $doPre); $result = BSApp::Registry()->getType('Db')->queryFirst("SELECT * FROM {$this->prefix}{$this->table} WHERE {$this->condition}"); if (!$result) { return false; } $this->_runActionMethod('post_fetch', $doPost); $this->objdata = $result; return true; } // ################################################################### /** * Inserts a record in the database * * @param bool Run pre_insert()? * @param bool Run post_insert()? */ public function insert($doPre = true, $doPost = true) { $this->verify(); $this->_processErrorQueue(); $this->_runActionMethod('pre_insert', $doPre); foreach ($this->setfields AS $field) { $fields[] = $field; $values[] = $this->_prepareFieldForSql($field); } BSApp::Registry()->getType('Db')->query("INSERT INTO {$this->prefix}{$this->table} (" . implode(',', $fields) . ") VALUES (" . implode(',', $values) . ")"); if (BSApp::Registry()->getType('DbPostgreSql')) { foreach ($this->fields AS $field => $info) { if ($info[F_REQ] == REQ_AUTO) { $autofield = $field; break; } } $this->insertid = BSApp::Registry()->getType('Db')->insertId($this->prefix . $this->table, $autofield); } else { $this->insertid = BSApp::Registry()->getType('Db')->insertId(); } $this->_runActionMethod('post_insert', $doPost); } // ################################################################### /** * Updates a record in the database using the data in $vaues * * @param bool Run pre_update()? * @param bool Run post_update()? */ public function update($doPre = true, $doPost = true) { if ($this->condition == '') { $this->setCondition(); } $this->_processErrorQueue(); $this->_runActionMethod('pre_update', $doPre); foreach ($this->setfields AS $field) { $updates[] = "$field = " . $this->_prepareFieldForSql($field); } $updates = implode(', ', $updates); BSApp::Registry()->getType('Db')->query("UPDATE {$this->prefix}{$this->table} SET $updates WHERE {$this->condition}"); $this->_runActionMethod('post_update', $doPost); } // ################################################################### /** * Deletes a record * * @param bool Run pre_remove()? * @param bool Run post_remove()? */ public function remove($doPre = true, $doPost = true) { if ($this->condition == '') { $this->setCondition(); } $this->fetch(); $this->_runActionMethod('pre_remove', $doPre); BSApp::Registry()->getType('Db')->query("DELETE FROM {$this->prefix}{$this->table} WHERE {$this->condition}"); $this->_runActionMethod('post_remove', $doPost); } // ################################################################### /** * Verifies that all required fields are set */ private function verify() { foreach ($this->fields AS $name => $options) { if ($options[F_REQ] == REQ_YES) { if (!isset($this->values["$name"])) { $this->_error(new Exception(sprintf(_('The required field "%1$s" was not set'), $name), $name)); } } else if ($options[F_REQ] == REQ_SET) { $this->{"set_$name"}(); } } } // ################################################################### /** * Runs a pre- or post-action method for database commands * * @param string Action to run * @param bool Actually run it? */ private function _runActionMethod($method, $doRun) { if (!$doRun) { return; } $actmethod = (method_exists($this, $method) ? $this->$method() : ''); } // ################################################################### /** * Prepares a value for use in a SQL query; it encases and escapes * strings and string-like values * * @param string Field name * * @return string Prepared value entry */ private function _prepareFieldForSql($name) { $type = $this->fields["$name"][F_TYPE]; if ($type == TYPE_NONE OR $type == TYPE_STR OR $type == TYPE_STRUN) { return "'" . BSApp::Registry()->getType('Db')->escapeString($this->values["$name"]) . "'"; } else if ($type == TYPE_BOOL) { return ($this->values["$name"] == true ? "'1'" : "'0'"); } else if ($type == TYPE_BIN) { return "'" . BSApp::Registry()->getType('Db')->escapeBinary($this->values["$name"]) . "'"; } else { return $this->values["$name"]; } } // ################################################################### /** * Verify field: not a zero value */ protected function verify_nozero($field) { if ($this->values["$field"] == 0) { return new Exception(sprintf(_('The field "%1$s" cannot be zero'), $field), $field); } return true; } // ################################################################### /** * Verify field: not empty */ protected function verify_noempty($field) { if (empty($this->values["$field"])) { return new Exception(sprintf(_('The field "%1$s" cannot be empty'), $field), $field); } return true; } // ################################################################### /** * Verify field: unique */ protected function verify_unique($field) { $res = BSApp::Registry()->getType('Db')->queryFirst("SELECT $field FROM {$this->prefix}{$this->table} WHERE $field = " . $this->_prepareFieldForSql($field) . (empty($this->condition) ? "" : " AND !({$this->condition})")); if ($res) { return new Exception(sprintf(_('The "%1$s" field must contain a unique value'), $field), $field); } return true; } } /** * API Exception * * This class is an exception container that can be used to store a series * of exceptions that can be thrown as one * * @author rsesek * @copyright Copyright (c)2002 - 2007, Blue Static * @package ISSO * */ class ApiException extends Exception { /** * Array of exceptions * @var array */ private $exceptions = array(); // ################################################################### /** * Constructor */ public function __construct() { parent::__construct(_('An error occurred while processing the API data.')); } // ################################################################### /** * Adds an exception to the collection * * @param Exception $e */ public function addException(Exception $e) { $this->exceptions[] = $e; } // ################################################################### /** * Returns an array of all the exceptions in the collection * * @return array */ public function getExceptions() { return $this->exceptions; } } ?>