myThis =& $myThis; } public function isInited($mode = -1) {return true;} public function onSet($node, $value, $myFields, $myRow) {} public function onUnset($node, $isAttribute, $myRow) {} public function setEntries($entries, $myFields, $numRows) {} public function Output($asText = false) { if (!function_exists('json_encode')) return false; if (!isset($this->myThis) && !is_object($this->myThis)) return false; return json_encode($this->myThis->getEntries()); } } class xmlRenderer implements tableRenderer { public $isInited = false; private $myDoc = null; private $myRow = null; private $myRowNum = 0; private $myRoot = 'docRoot'; private $myRowName = 'entryRow'; /** * Initializes a SimpleXMLElement object * * @return void * @author Matthieu Lalonde **/ public function __construct() {} public function isInited($mode = -1) { if ($mode == false) { $this->isInited = false; } return $this->isInited; } /** * undocumented function * * @return void * @author Matthieu Lalonde **/ public function _init($myThis) { $this->myRow = null; $this->myRowNum = null; if (!isset($myThis->rootXPath)) { $this->myRowName = $myThis->tableName; $this->myRoot = 'appData'; } else { $xpathParts = explode('/', removeLeadingSlash($myThis->rootXPath)); $this->myRoot = $xpathParts[0]; $this->myRowName = $xpathParts[1]; } $myRoot = $this->myRoot; $myXML = << <${myRoot}/> XML; $this->myDoc = new SimpleXMLElement($myXML); $this->isInited = true; return true; } /** * onSet whenever the table class sets an overloads * * @return void * @author Matthieu Lalonde * @param Takes the name of the node as a string and a mixed value **/ public function onSet($node, $value, $myFields, $myRow) { if (!$this->isInited()) { return null; } if (!isset($this->myDoc->{$this->myRowName}[$myRow])) { if (version_compare(phpversion(), '5.1.3') == -1) { throw new appException('The XML Renderer requires PHP 5.1.3 or above to function properly', MSG_FATAL); } else { $myXMlEntry = $this->myDoc->addChild($this->myRowName); } } else { $myXMlEntry = $this->myDoc->{$this->myRowName}[$myRow]; } if ($value == 'NOW()') $value = dbAbstract::getNOW(); $fieldName = self::cleanXMLTag($node); if (!tableAbstract::isAttribute($node, $myFields)) { $myXMlEntry->$fieldName = $value; } else { $myXMlEntry[$fieldName] = $value; } $this->myRowNum = $myRow; return true; } /** * onUnset whenever the table class unsets an overloads * * @return void * @author Matthieu Lalonde * @param Takes the name of the node as a string **/ public function onUnset($node, $isAttribute, $myRow) { if (!$this->isInited) return null; if (!isset($this->myDoc->{$myRowName[$myRow]})) false; if (!$isAttribute) { unset($this->myDoc->{$myRowName[$myRow]}); } elseif ($isAttribute && isset($this->myDoc->{$myRowName[$myRow]}[$node])) { unset($this->myDoc->{$myRowName[$myRow]}[$node]); } return false; } /** * Takes an array of entries and populates the XML with it * * @param array $entries * @param array $myFields: list of data fields * @return void * @author Matthieu Lalonde */ public function setEntries($entries, $myFields, $numRows) { for ($i = 0;$i < $numRows;$i++) { if (!isset($entries[$i])) { foreach ($myFields as $myField => $fieldInfo) { if (!array_key_exists($myField, $entries)) { $this->onSet($myField, '', $myFields, 0); } } foreach ($entries as $entryNode => $entryValue) { $this->onSet($entryNode, $entryValue, $myFields, 0); } } else { foreach ($myFields as $myField => $fieldInfo) { if (!array_key_exists($myField, $entries[$i])) { $this->onSet($myField, '', $myFields, $i); } } foreach ($entries[$i] as $entryNode => $entryValue) { $this->onSet($entryNode, $entryValue, $myFields, $i); } } } } final public function Output($asText = false) { if (!$this->isInited()) return false; return self::outputXML($this->myDoc, $asText); } final private static function outputXML($myXMLDoc, $asText = null) { if ($asText) { $xmlDoc = new DOMDocument; $xmlDoc->formatOutput = true; $xmlDoc->preserveWhiteSpace = false; $xmlDoc->loadXML($myXMLDoc->asXML()); return $xmlDoc->saveXML(); } return $myXMLDoc; } final private static function cleanXMLTag(&$tag) { $tag = trim($tag); if (preg_match('/^[0-9](.*)/', $tag)) { $tag = '_' . $tag; } return $tag; } final private static function getBaseDoc() { return << XML; } } /** * Models **/ class activeRecordTest extends tableAbstract { public $rootXPath = '/appData/pageData'; function __construct(tableRenderer &$renderer = null) { $this->_init(__CLASS__, $renderer); } protected function onCreate() { $tableName = $this->myName; $tableSQL = <<fetchRow(HP('limit', 1)) > 0) return true; else return false; } protected function validate_login($data) { return false; /* if (rand(0, 1) == 0) return true; else return false;*/ } protected function validate_password($data) { return false; /* if (rand(0, 1) == 0) return true; else return false;*/ } protected function validate_email($data) { return false; /* if (rand(0, 1) == 0) return true; else return false;*/ } } // END table model Pages /** * tableAbstract is a simple active record abstraction layer * * @package SmurfCMS * @author Matthieu Lalonde */ class tableAbstract { /* Public */ /* Protected */ /** * List of the table's fields name * * @var array * @access protected */ protected $myFields = array(); /** * Table's fields definition * * @var array * @access protected */ protected $myFieldsDef = array(); /** * List of private fields * * @var array * @access protected */ protected $privateFields = array('id', 'dateCreated', 'dateUpdated'); /** * Table name from the model * * @var string * @access protected */ protected $tableName = ''; /** * Table's name in the database * * @var string * @access protected */ protected $myName = ''; /** * ID of the row in myEntries * * @var int * @access protected */ protected $myRowID = null; /** * Table & Overload entries * * @var array * @access protected */ protected $myEntries = null; /** * Number of rows from the last query * * @var int * @access protected */ protected $numRows = 0; /* Private */ /** * Last table query conditions (WHERE...) * * @var string * @access private */ private $lastDBConditions = null; /** * Last query parameters * * @var array * @access private */ private $queryParams = null; /** * Table renderer object * * @var object * @access private */ private $myRenderer = null; /** * List of all the table's queries * and whether or not they failed. * * @var array * @access private */ private $queriesLog = array(); /** * Name of the default renderer * * @var string * @access private */ private $defaultRenderer = 'defaultRenderer'; /** * List of default query conditions * * @var array * @access private */ private static $queryConditions= array( 'conditions' => '1', // WHERE $conditions 'limit' => null, 'orderBy' => 'id', 'order' => 'ASC', // Either nothing or DESC 'types' => false, // Usually internal! 'mode' => MDB2_FETCHMODE_ASSOC,// Shouldn't change for overload!!! ); /** | Basic object methods | **/ /** * __construct() initilizes the table. * A note of warning: this can be overloaded by a table child! * * @param (string) $tableName: name of this table * @param (string) $tableRenderer: default table renderer object (optional) * @return (bool) true on success * @author Matthieu Lalonde */ public function __construct($tableName, tableRenderer &$renderer = null) { if(get_class($this) == __CLASS__) { throw new fatalException('You cannot instanciate this class, please use a model!'); return null; } else { return $this->_init($tableName, $renderer); } } /** * __destruct() disconnects the database and resets the query * * @return void * @author Matthieu Lalonde */ final public function __destruct() { $dbAbstract = Singleton::getInstance('dbAbstract'); $dbAbstract->disconnectDB(); $this->resetQuery(); } /** * _init() initialize the table by fetching the fields * and manipulatin the table (creation as necessary) * * @param (string) $tableName: name of this table * @param (string) $tableRenderer: default table renderer object (optional) * @return (bool) true on success * @author Matthieu Lalonde */ final public function _init($tableName, tableRenderer &$renderer = null) { $dbAbstract = Singleton::getInstance('dbAbstract'); $this->tableName = strtolower($tableName); $this->myName = addTrailingChar(dbAbstract::$tablePrefix . strtolower($tableName), 's'); $this->setRenderer($renderer); $tempFields = $dbAbstract->fetchTableFields($this->myName); if ($tempFields === false) { // The table does not exist, let's try and create it first. if ($this->createTable(/*true to drop first!*/)) { $tempFields = $dbAbstract->fetchTableFields($this->myName); } if ($tempFields === false) { die('cannot init!'); //$msgController = msgController(); //$msgController->appendMsg(new fatalError('Cannot initialize table `'.$this->myName.'`!', ERROR_CODE_DB)); return false; } } foreach ($tempFields as $field) { $myTmpField = $field['Field']; $this->myFields[] = $myTmpField; $this->myFieldsDef[$myTmpField] = $field; } return true; } // END _init() /** | Class Accessor Methods | **/ /** * getLastQuery returns the last database query to be executed (failure or not) * * @return string * @author Matthieu Lalonde */ final public function getLastQuery() { $dbAbstract = Singleton::getInstance('dbAbstract'); return $dbAbstract->mdb2->last_query; } /** * getEntries returns the current overload entries * * @return array * @author Matthieu Lalonde */ final public function getEntries() { return $this->myEntries; } /** * getMyfields returns the name of the table's fields * * @return array * @author Matthieu Lalonde */ final public function getMyfields() { return $this->myFields; } /** * getMyTableName returns the table's short name * * @return sting * @author Matthieu Lalonde */ final public function getMyTableName() { return $this->tableName; } /** * getMyName returns the table's full name * * @return string * @author Matthieu Lalonde */ final public function getMyName() { return $this->myName; } /** * getNumRow returns the current number of rows * * @return int * @author Matthieu Lalonde */ final public function getNumRow() { return $this->numRows; } /** * getQueryLog return an array containing a log of the queries * * @return (array) array(query, (bool)result) * @author Matthieu Lalonde */ final public function getQueryLog() { return $this->queriesLog; } /** * getRowID returns the current row number of myEntries * * @return void * @author Matthieu Lalonde */ final public function getRowID() { return $this->myRowID; } /** * setRowID affects a row number to myEntries * * @param (int) $id: row number * @return (bool) true on success * @author Matthieu Lalonde */ final public function setRowID($id) { if (!isset($id) || !is_numeric($id)) { $this->myRowID = false; return false; } if ($id > $this->numRows) {return false;} $this->myRowID = $id; return true; } /** * Resets the current table state * Deletes all information about the current query! * Allows for the same object to be used multiple times * * @return void * @author Matthieu Lalonde */ final public function resetQuery() { if ($this->myRenderer) { $this->myRenderer->_init($this); } $this->myRowID = null; $this->myEntries = null; $this->queryParams = null; $this->numRows = 0; $this->lastDBConditions = ''; //$this->queriesLog[] = 'Reseted query!'; } /** * getMyRenderer returns the current renderer of the default render is there is one * * @return void * @author Matthieu Lalonde */ final public function getMyRenderer() { if (!interface_exists('tableRenderer', true)) return false; if (!$this->myRenderer) { if (empty($this->defaultRenderer) || !class_exists($this->defaultRenderer)) { return false; } $this->myRenderer = new $this->defaultRenderer; } return $this->myRenderer; } /** * setRenderer allows the table class to receive a new renderer * * @param tableRenderer $renderer * @return void * @author Matthieu Lalonde */ final public function setRenderer(tableRenderer &$renderer) { $this->myRenderer = $renderer; $this->myRenderer->_init($this); } /** | Class Rendering Methods | **/ /** * render renders all of the overloads with the external renderer * * @param (bool) $asText: force the renderer to output text if it can. * @return (mix) Renderer output * @author Matthieu Lalonde */ final public function render($asText = false) { try { return $this->getMyRenderer()->Output($asText); } catch (Exception $e) { throw new fatalException('Unable to load default render!'); } return null; } /** | Class Overload Methods | **/ /** * __set() overload. Stores the passed information in the myEntries named array * * @param (string) $name: field name * @param (string) $value: field to insert * @return void * @author Matthieu Lalonde */ final public function __set($name, $value) { return $this->_handleMyEntries($name, $value, 'set'); } // TODO This needs to handle "RoR like" A.R. methods final public function __call($name, $arguments) {} /** * __get() overload queries the myEntries array for a field of $name * * @param (string) $name: name of the node. but be a valid fields or __get returns false * @return false if anything fails or the entry data * @author Matthieu Lalonde */ final public function __get($name) { return $this->_handleMyEntries($name, null, 'get'); } /** * __toString is delegated to the rendering functions * * @author Matthieu Lalonde */ final public function __toString() { return $this->render(true); } /** * __isset() overload queries the myEntries array in * search of a field and returns whether or not it exists * * @param (string) $name: must be a valid field name * @return true is $name isset in myEntries, false in any other case * @author Matthieu Lalonde */ final public function __isset($name) { return $this->_handleMyEntries($name, null, 'isset'); } /** * __unset() overload unsets the specified entry from myEntries * * @param (string) $name: name of the field * @return void * @author Matthieu Lalonde */ final public function __unset($name) { return $this->_handleMyEntries($name, null, 'unset'); } /** * handles all of the queries to myEntries from the overloads. * * @param (string) $name: name of the field * @param (string) $value: node value (optional) * @param (string) $mode: overload mode * @return Multiple possibilities, false as default and failure * @author Matthieu Lalonde */ final private function _handleMyEntries($name, $value = '', $mode = 'set') { if (($mode == 'set' ||is_array($this->myEntries)) && in_array($name, $this->myFields)) { $myRowID = $this->myRowID; if (!isset($myRowID)) {$myRowID = 0;} if ($mode == 'set' || isset($this->myEntries[$myRowID])) { if ($mode == 'set' || array_key_exists($name, $this->myEntries[$myRowID])) { switch ($mode) { case 'set': if (!in_array($name, $this->privateFields)) { // Call the renderer's onSet event if (isset($this->myRenderer)) { $this->myRenderer->onSet($name, $value, $this->myFieldsDef, $myRowID); } $this->myRowID = $myRowID; $this->myEntries[$myRowID][$name] = $value; return true; } else { throw new Exception('Cannot access private field!'); return false; } break; case 'get': if (isset($this->myEntries[$myRowID][$name])) { return $this->myEntries[$myRowID][$name]; } break; case 'isset': return isset($this->myEntries[$myRowID][$name]); break; case 'unset': if (isset($this->myRenderer)) { // Call the renderer's onUnset event $this->myRenderer->onUnset($name, self::isAttribute($name, $this->myFieldsDef), $myRowID); } unset($this->myEntries[$myRowID][$name]); break; } } } } return false; } // END _handleMyEntries() /** | Class Table Manipulation Methods | **/ /** * createTable attempts to create the table in the database if the table's child * provides an sql query for it * * @param (bool) $dropTable: whether or not to drop the table first, mainly unsed internally atm * @return (bool) * @author Matthieu Lalonde */ /** * onCreate() is to be overloaded by the table's child. * If it returns it's creation sql query, the table will be created as necessary. */ protected function onCreate() {return null;} final private function createTable($dropTable = false) { $createTableQuery = $this->onCreate(); if ($dropTable == true) { $createTableQuery = 'DROP TABLE `' . $this->tableName . '`; ' . "\n\n" . $createTableQuery; } if (!empty($createTableQuery)) { $dbAbstract = Singleton::getInstance('dbAbstract'); $dbAbstract->mdb2->exec($createTableQuery); $this->queriesLog[] = array($dbAbstract->mdb2->last_query, PEAR::isError($dbAbstract->mdb2)); if (PEAR::isError($dbAbstract->mdb2)) { throw new fatalException('Cannot create table `' . $his->tableName . '`'); return false; } return true; } return false; } // END createTableQuery() /** * deleteData deletes the content of myEntries in the database * according to the selected row or for all entries with the last query. * A word of warning, this will reset the query with resetQuery() and * reinit the renderer if one is available! * * @return affected rows if all goes well, false if anything else * @author Matthieu Lalonde */ // onDelete() is to be overloaded by the table's child for data validation if necessary. protected function onDelete() {} final public function deleteData() { $doNotDelete = $this->onDelete(); if (!$doNotDelete && is_array($this->myEntries)) { $dbAbstract = Singleton::getInstance('dbAbstract'); if (isset($this->myRowID) && $this->numRows > 0) { $myQ = 'DELETE FROM `' . $this->myName . '` WHERE `id` = ' . $this->myEntries[$this->myRowID]['id'] . ';'; } elseif (!empty($this->lastDBConditions) && $this->numRows > 0) { $myQ = 'DELETE FROM `' . $this->myName . '` WHERE ' . $this->lastDBConditions . ';'; } else { return false; } $dbAbstract->mdb2->setLimit(count($this->myEntries)); $affected =& $dbAbstract->mdb2->exec($myQ); $this->queriesLog[] = array($dbAbstract->mdb2->last_query, PEAR::isError($affected)); // Always check that result is not an error if (PEAR::isError($affected)) { throw new appException('Error deleting'); return false; } $this->resetQuery(); if ($this->myRenderer) { $this->myRenderer->isInited(false); } return $affected; } else { return $doNotDelete; } return null; } // END deleteData() /** * saveData saves (update or insert) the content of myEntries to the database * according to the selected row or for all entries with the last query. * * @return void * @author Matthieu Lalonde */ // onSave() is to be overloaded by the table's child for data validation if necessary. protected function onSave() {return null;} final public function saveData() { $isNew = false; $doNotSave = $this->onSave(); if (is_array($this->myEntries) && !$doNotSave) { for($i=(count($this->myEntries) - 1);$i>=0;$i--) { if ($doNotSave) break; foreach($this->myFieldsDef as $myKey => $myFieldDef) { if ($doNotSave) break; if (!is_numeric($myKey) && !in_array($myKey, $this->privateFields) && method_exists($this, ($meth = 'validate_' . $myKey))) { if (!isset($this->myEntries[$i][$myKey])) { $myValBefore = null; $myVal = null; } else { $myVal = $this->myEntries[$i][$myKey]; $myValBefore = $this->myEntries[$i][$myKey]; } $doNotSave = $this->$meth($myVal); if ($myVal != $myValBefore) { $this->myEntries[$i][$myKey] = $myVal; } } } } if (!$doNotSave) { $dbAbstract = Singleton::getInstance('dbAbstract'); if (is_numeric($this->myRowID) && $this->numRows > 0 && isset($this->myEntries[$this->myRowID]['id'])) { $myDate = $this->setDefaultFields('update'); $myEntry = array_merge($myDate, $this->myEntries[$this->myRowID]); $myQ[] = 'UPDATE `' . $this->myName . '` SET ' . self::mysqlUpdateValues($myEntry, $this->myFieldsDef) . ' WHERE `id` = ' . $this->myEntries[$this->myRowID]['id'] .';'; } elseif (!empty($this->lastDBConditions) && $this->numRows > 0 && isset($this->myEntries[0]['id'])) { $myQ = array(); foreach ($this->myEntries as $myEntry) { $myDate = $this->setDefaultFields('update'); $myEntry = array_merge($myDate, $myEntry); //if (!isset($myEntry['id'])) continue; $myQ[] = 'UPDATE `' . $this->myName . '` SET ' . self::mysqlUpdateValues($myEntry, $this->myFieldsDef) . ' WHERE `id` = ' . $myEntry['id'] . ';'; } } else { $isNew = true; $myQ = array(); foreach ($this->myEntries as $myEntry) { if (!array_key_exists('id', $myEntry)) { $myEntry = array_merge(array('id' => ''), $myEntry); } $myDate = $this->setDefaultFields('create'); $myEntry = array_merge($myDate, $myEntry); $myQData = self::getInsertFields($myEntry, $this->myFieldsDef); if (empty($myQData['queryKeys']) || empty($myQData['queryValues'])) { continue; } $myQ[] .='INSERT INTO `' . $this->myName . '` (' . $myQData['queryKeys'] . ') ' . 'VALUES (' . $myQData['queryValues'] . ');'; } } if (!isset($myQ[0])) { return false; } foreach ($myQ as $myQuery) { if (!$isNew) $dbAbstract->mdb2->setLimit(1); $affected =& $dbAbstract->mdb2->exec($myQuery); $lastQuery = $dbAbstract->mdb2->last_query; $this->queriesLog[] = array($lastQuery, PEAR::isError($affected)); if (PEAR::isError($affected)) break; } // Always check that result is not an error if (PEAR::isError($affected)) { throw new appException('Cannot save to table ' . $affected->getMessage() . $lastQuery); return false; } if ($isNew) $this->myEntries[0]['id'] = $dbAbstract->mdb2->lastInsertID(); $this->numRows = count($this->myEntries); return $affected; } } return false; } // END saveData(); final private function setDefaultFields($mode) { $myKey = ''; switch ($mode) { case 'create': $myKey = 'dateCreated'; break; case 'update': $myKey = 'dateUpdated'; break; default: return array(); break; } if (!array_key_exists($myKey, $this->myFieldsDef)) { return array(); } return array($myKey => dbAbstract::getNOW()); } /** * _query generates and fetches a selection query on the table * * @param array $fieldName: specify a list of fields to fetch (option, default to all) * @param array $myConditions: an array containing the search conditions of the query * @return mixed: number of fetched rows on success, false on failure * @author Matthieu Lalonde */ final private function _query($fieldName = '*', $myConditions = null) { $dbAbstract = Singleton::getInstance('dbAbstract'); $myConditions = self::validateQueryConditions($myConditions); if (!$dbAbstract->isReady()) { return null; } // Check for ordering $queryOrder = !empty($myConditions['orderBy']) ? ' ORDER BY `' . $myConditions['orderBy'] . '` ' . $myConditions['order'] : ''; // Build the mySQL query $mySQL = 'SELECT '. self::fieldsToCSV($fieldName) .' FROM `' . $this->myName .'` WHERE '. $myConditions['conditions'] . $queryOrder .';'; // Set limit if (isset($myConditions['limit']) && is_numeric($myConditions['limit']) && $myConditions['limit'] >= 0) { $dbAbstract->mdb2->setLimit($myConditions['limit']); } $res =& $dbAbstract->mdb2->query($mySQL); $this->lastDBConditions = $myConditions['conditions']; $this->queriesLog[] = array($dbAbstract->mdb2->last_query, PEAR::isError($res)); try { if (PEAR::isError($res)) { throw new Exception($res->getMessage() .'Unable to query from database table "'.$this->myName.'"', MSG_ERROR); } if (isset($myConditions['types']) && is_array($myConditions['types'])) { $res->setResultTypes($myConditions['limit']); } if (isset($myConditions['limit']) && $myConditions['limit'] == 1) { $return = $res->fetchRow($myConditions['mode']); } else { $return = $res->fetchAll($myConditions['mode']); } // Free the result! $this->numRows = count($return); $res->free(); if ($this->numRows > 0) { if (is_object($this->myRenderer)) { $this->myRenderer->setEntries($return, $this->myFieldsDef, $this->numRows); } if ($this->numRows == 1) { $this->myRowID = 0; } else { $this->myRowID = null; } $this->myEntries = $return; return $this->numRows; } else { throw new alertException('No result for: ' . $this->getLastQuery() . "\n", MSG_ALERT);//'call_back_error_string'); return false; } } catch (Exception $e) { // This really shouldn't happen throw new appException($e->getMessage(), $e->getCode()); return false; } } // End _query() /** * validateQueryConditions makes sure all the required * query conditions are listed in the query parameters * * @param array $myConditions: valid set of condition * @param string $array * @return array * @author Matthieu Lalonde */ final private static function validateQueryConditions(array $myConditions = null) { $queryConditions = self::$queryConditions; if (is_array($myConditions)) { foreach ($queryConditions as $myKey => $myVal) { if (array_key_exists($myKey, $myConditions) && !empty($myConditions[$myKey])) { $queryConditions[$myKey] = $myConditions[$myKey]; } } } // Override, otherwise... bad for overload!!! $queryConditions['mode'] = MDB2_FETCHMODE_ASSOC; return $queryConditions; } // END validateQueryConditions() /** * fetch, fetchRow(s) allow querying the table * and handle prepartion of the query parameters * * @param array $searchParams: contains a field/match named array, if 'QRelation' is set, the query will use OR instead of AND, if 'QReversed' is set, the search wil be reverse * @param array $queryParams: optional named array containing query parameters (see the validation function) * @return result of _query() * @author Matthieu Lalonde */ final public function fetchRow($searchParams, $queryParams = null) {return $this->fetch($searchParams, $queryParams);} final public function fetchRows($searchParams, $queryParams = null) {return $this->fetch($searchParams, $queryParams);} final public function fetch($searchParams, $queryParams = null) { // First reset the query! $this->resetQuery(); if (!is_array($searchParams) && !is_array($queryParams)) { $res = $this->fetchAll(); } else { $singleArray = false; if (!is_array($queryParams)) { $singleArray = true; $queryParams = $searchParams; } $queryFields = '*'; if (isset($searchParams['fieldName'])) { if (in_array($searchParams['fieldName'], $this->myFields)) { $queryFields = $searchParams['fieldName']; } unset($searchParams['fieldName']); unset($queryParams['fieldName']); } $or = false; if (isset($searchParams['QRelation'])) { $or = true; unset($searchParams['QRelation']); unset($queryParams['QRelation']); } $not = false; if (isset($searchParams['QReversed'])) { $not = true; unset($searchParams['QReversed']); unset($queryParams['QReversed']); } if (!isset($queryParams['conditions']) && isset($searchParams)) { if ($singleArray) { foreach (self::$queryConditions as $key => $value) { if (isset($searchParams[$key])) unset($searchParams[$key]); } } $queryParams['conditions'] = $this->mysqlArrayToWHERE($searchParams, $or, $not); } $res = $this->_query($queryFields, $queryParams); if (!$res) { $this->queryParams = null; } else { $this->queryParams = $queryParams; } } return $res; } // END fetch() /** * fetchAll fetches all entries for the table * * @return _query() result * @author Matthieu Lalonde */ final public function fetchAll() { // First reset the query! $this->resetQuery(); return $this->_query(); } /** | Class Utilities Methods | **/ /** * mysqlArrayToWHERE creates an escaped string for mysql search queries * * @param array $mArray * @param bool $or: if true use OR instead of AND * @param bool $not: if true reverse the search * @return string * @author Matthieu Lalonde */ final private function mysqlArrayToWHERE($mArray, $or = false, $not = false) { $sFields = ''; $i = 0; if (!$or) $relation = 'AND'; else $relation = 'OR'; foreach($mArray as $key => $val) { if (is_numeric($key) || !in_array($key, $this->myFields)) unset($mArray[$key]); } if (empty($mArray)) return false; while (list($paramKey, $value) = each($mArray)) { if (dbAbstract::isLike($this->myFieldsDef[$paramKey]['Type'])) { $entryRelation = ($not == true ? 'NOT ' : '') . 'LIKE'; } else { $entryRelation = ($not == true ? '!' : '') . '='; } $sFields.= '`' . $paramKey . '` '.$entryRelation.' \'' . $value . '\''; if(++$i < count($mArray)) { $sFields.= ' ' . $relation . ' '; } } return $sFields; } /** * mysqlUpdateValues creates an escaped string for mysql update queries * * @param array $mArray: named array containing the update data * @param array $types: used for quoting * @return string * @author Matthieu Lalonde */ final private static function mysqlUpdateValues($mArray, $types) { $sValues = ""; $i = 0; unset($mArray['id']); $count = count($mArray); while (list($key, $value) = each($mArray)) { $sValues .= "$key = '" . dbAbstract::escapeString($value) . "'"; if(++$i < $count) { $sValues .= ", "; } } return $sValues; } /** * fieldsToCSV returns a CSV formated string * * @param (array) $mArray: contains a series of field names * @return string * @author Jérémie Lauzon, Matthieu Lalonde */ final private static function fieldsToCSV($mArray) { $sFields = ""; $i = 0; if (!is_array($mArray)) { return $mArray; } while (list($key, $value) = each($mArray)) { $value = preg_replace('/![A-Za-z0-9_-]/', '', $value); $sFields .= '`' . $value . '`'; if(++$i < count($mArray)) { $sFields .= ", "; } } return $sFields; } /** * valuesToCSV returns a CSV formated string of escaped values * * @param (array) $mArray: contains a series of field names * @param (array) $types: contains the type for each field for quoting purposes * @return string * @author Jérémie Lauzon, Matthieu Lalonde */ final private static function valuesToCSV($mArray, $types) { $sValues = ""; $i = 0; if (!array_key_exists('id', $mArray)) { $mArray = array_merge(array('id' => ''), $mArray); } foreach ($mArray as $key => $value) { $quotes = ($value == 'NOW()' ? '' : '\''); $sValues .= $quotes . dbAbstract::escapeString($value) . $quotes; if(++$i < count($mArray)) { $sValues .= ", "; } } return $sValues; } /** * getInsertFields returns an array of prepared data * to use for a insertion query * * @param array $recordValues * @param array or string $types * @return array array('queryKeys' => fieldsToCSV($recordKeys), 'queryValues' => valuesToCSV($recordValues, $types)); * @author Matthieu Lalonde */ final private static function getInsertFields(array $recordValues, $types) { $recordKeys = array(); foreach ($recordValues as $recordKey => $recordValue) { $recordKeys[] = $recordKey; } return array('queryKeys' => self::fieldsToCSV($recordKeys), 'queryValues' => self::valuesToCSV($recordValues, $types)); } /** * isAttribute returns whether or not a field is an attribute * * @param string $fieldName * @return bool * @author Matthieu Lalonde */ final public function isAttribute($fieldName, $fieldsDef) { if (in_array($fieldName, array('id', 'uid')) || !empty($fieldsDef[$fieldName]['Key']) || (isset($fieldsDef[$fieldName]) && preg_match('/(enum\((.*)\))/', $fieldsDef[$fieldName]['Type']))) { return true; } return false; } } // END class tableAbstract /** * Load the database config **/ global $dsn; initDBConfig(); function initDBConfig() { global $dsn; include HELPERS . 'dbConf.inc.php'; if (!isset($dsn) || empty($dsn)) { throw new fatalException('Could not find database configuration file!'); } } /** * dbAbstract: handles database connection * * @package SmurfCMS * @author Matthieu Lalonde **/ class dbAbstract { /** * Table prefix in the database, if you need one * * @var string * @access public */ public static $tablePrefix = 'xcms_'; /** * Database object * * @var MDB2 object * @access public */ public $mdb2; /** * MDB2 options * * @var array * @access private */ private $dsnOptions = array('debug' => 2); /** * Database connection options * * @var array * @access private */ private $dsn = array(); /** * state of the database connection * * @var string * @access private */ private $dbState = false; /** * Don't allow the construct to be overwritten * * @return void * @author Matthieu Lalonde * @access public */ final public function __construct() {} /** * __destruct makes sure the database is disconnected first * * @return void * @author Matthieu Lalonde * @access private */ final public function __destruct() { $dbAbstract = Singleton::getInstance('dbAbstract'); $dbAbstract->disconnectDB(); } // End __destruct() /** * allows for easy access to the mdb2 methods * * @param string $name: method name * @param string $params: method params * @return void * @author Matthieu Lalonde * @access public */ final public function __call($name, $params) { $dbAbstract = Singleton::getInstance('dbAbstract'); if (method_exists($dbAbstract->mdb2, $name)) { return call_user_func(array($dbAbstract->mdb2, $name), $params); } return false; } /** * disconnectDB closes the database connection and reset the state * * @return void * @author Matthieu Lalonde * @access public */ final public function disconnectDB() { $dbAbstract = Singleton::getInstance('dbAbstract'); if (is_object($dbAbstract->mdb2) && !(PEAR::isError($dbAbstract->mdb2))) { $dbAbstract->mdb2->disconnect(); } $dbAbstract->mdb2 = null; $dbAbstract->dbState = false; } // END disconnectDB /** * resetConnection attempts to reset the database's connection * * @return bool: true on success * @author Matthieu Lalonde * @access public */ final public function resetConnection() { $dbAbstract = Singleton::getInstance('dbAbstract'); $dbAbstract->disconnectDB(); return $dbAbstract->_init(); } // END resetConnection /** * _init starts up a connection to the database * * @return bool: true on success * @author Matthieu Lalonde * @access public */ final public function _init() { global $dsn; $dbAbstract = Singleton::getInstance('dbAbstract'); $dbAbstract->dsn = $dsn; $dbAbstract->mdb2 =& MDB2::connect($dbAbstract->dsn, $dbAbstract->dsnOptions); if (PEAR::isError($dbAbstract->mdb2)) { throw new appException( 'Error connecting to the database with error: '. $dbAbstract->mdb2->getMessage() . ' on ' . $dbAbstract->mdb2->getUserinfo() ); return false; } else { $dbAbstract->mdb2->setOption('portability', MDB2_PORTABILITY_ALL ^ MDB2_PORTABILITY_FIX_CASE); $dbAbstract->dbState = true; return true; } } // End Init /** ** Acessors **/ /** * isReady checks if the database is read or can be initialized. * in the later case, an attempt will be made to init the database * * @return bool: state of the database * @author Matthieu Lalonde * @access public */ final public function isReady($noInit = false) { $dbAbstract = Singleton::getInstance('dbAbstract'); $msgController = msgController(); if (!$noInit && !$dbAbstract->dbState && $msgController->msgCount(false, false, ERROR_CODE_DB) == 0) { return $dbAbstract->_init(); } return $dbAbstract->dbState; } /** | Utilities | **/ /** * fetchTableFields fetch a litle of the table's fields * * @param string $tableName: valid table name * @return array, false on failure * @author Matthieu Lalonde * @access public */ final public function fetchTableFields($tableName) { $dbAbstract = Singleton::getInstance('dbAbstract'); $sql = 'SHOW COLUMNS FROM `'. $tableName .'`;'; $fields = Array(); if (!$dbAbstract->isReady()) { return false; } $res =& $dbAbstract->mdb2->query($sql); if (PEAR::isError($res)) { return false; } $return = $res->fetchAll(MDB2_FETCHMODE_ASSOC); // Free the result! $res->free(); if (count($return) == 1) { return $return[0]; } else if (count($return) > 1) { return $return; } return false; } /** * isLike returns true is a type uses a 'LIKE' syntax rather than '=' * * @param string $type * @return bool * @author Matthieu Lalonde */ final static function isLike($type) { if (preg_match('/(text)|(varchar)|(blob)/', $type)) { return true; } return false; } /** * getNOW returns a mySQL NOW() DATETIME stamp * * @return void * @author Matthieu Lalonde */ final public static function getNOW() { return date("Y-m-d H:i:s"); } /** * getNever returns a mySQL formatted _never_ DATETIME stamp * * @return void * @author Matthieu Lalonde */ final static function getNever() { return '0000-00-00 00:00:00'; } /** * escapeString escapes an array or a string of data. * * @param mixed $data * @param mixed $type * @param bool $trim * @return $data * @author Matthieu Lalonde */ final static function escapeString(&$data, $trim = false) { $dbAbstract = Singleton::getInstance('dbAbstract'); if (!is_array($data)) { if ($data == 'NOW()') return self::getNOW(); $data = mysql_real_escape_string($data); if ($trim) { $data = trim($data); } } else { foreach($data as $key => $val) { if ($val == 'NOW()') { $data[$key] = self::getNOW(); } else { if ($trim) { $data[$key] = trim($val); } $data[$key] = mysql_real_escape_string($val); } } } return $data; } } ?>
fetchRows(H('fieldName', '*'));

$myTest->fetchRow(HA(H('fieldName', 'email'), H('login', 'xsmurf')));

// Use of a custom model method
echo 'User xsmurf ' . (($iExists = $myTest->loginExists('xsmurf')) ? 'exists' : 'doesn\'t exist') . "\n";

if ($myTest->fetchAll() == 0 || !$iExists)
{
	$myTest->email		= 'xsmurf@smurfturf.net';
	$myTest->password	= sha1('xsmurf');
	$myTest->login		= 'xsmurf';
	
	if ($myTest->saveData())
	{
		echo 'Saved data 1' . "\n";
	} else {
		echo 'Failed to save 1' . "\n";
	}
}

if ($myTest->fetchRows(H('fieldName', 'id'), H('limit', 6)) <= 5) {
	$myTest->resetQuery(); // Force reset the query we want to create a new entry but had a valid query!
	$myTest->login		= 'fubar'.date('u');
	$myTest->email		= 'fubar@'.microtime().'.net';
	$myTest->password	= sha1('fubar');
	
	if ($myTest->saveData())
	{
		echo 'Saved data 2' . "\n";
	} else {
		echo 'Failed to save 2' . "\n";
	}
	
	/* Update the newly inserted row */
	
	$myTest->login		= 'fubar'.microtime();
	$myTest->email		= 'fubar@'.microtime().'.net';
	$myTest->password	= sha1('fubar');
	
	if ($myTest->saveData())
	{
		echo 'Updated data 2' . "\n";
	} else {
		echo 'Updated to save 2' . "\n";
	}
}

if ($myTest->fetchRows(HP('fieldName', 'id', 'QReversed', 'true', 'login', 'xsmurf')) >= 5)
{
	if ($myTest->deleteData())
	{
		echo 'Deleted data 1' . "\n";
	} else {
		echo 'Failed to delete 1' . "\n";
	}
}

if ($myTest->fetchAll() > 0)
{
	for ($i = 0;$i < $myTest->getNumRow();$i++)
	{
		$myTest->setRowID($i);
		echo $i . ': ' . $myTest->login . ' > ' . $myTest->email . "\n";
	}
	
	if ($myTest->getNumRow() > 1 && $myTest->setRowID($myTest->getNumRow() - 1)) {
		$myTest->email = 'last@cool.net';
		$myTest->saveData();
	}
	
	if ($myTest->fetchRows(HP('fieldName', 'id', 'QReversed', 'true', 'login', 'xsmurf')) >= 3)
	{
		for ($i = 0;$i < $myTest->getNumRow();$i++)
		{
			$myTest->setRowID($i);
			$myTest->email = 'last@ofit.' . microtime();
		}
		
		$myTest->setRowID(false); // We want to update all of the entries so we have to unset our row pointer!
		$myTest->saveData();
	}
}

$myTest->fetchRows(HP('fieldName', '*'));

?>
tableRenderer generated XML
'; ?>
render(true));

?>
jsonRenderer output
'; ?>
setRenderer(new jsonRenderer);
echo str_replace('},{', "},\n{", $myTest->render());

$time = timer($begin);

?>
Queries executed in ' . $time . ' sec.'; echo '

Query log


'; ?>
getQueryLog() as $query)
{
	echo '';
	echo ($query[1]) ? 'Failed' : ' Passed';
	echo ': ';
	echo $query[0] . "\n";
}

?>
Query usage sources
'; $file = file_get_contents(__FILE__); $res = substr($file, strpos($file, '
'));
$res = substr($res, 0, strrpos($res, '
')); highlight_string($res); echo '...'; $exp = explode("\n", $file); $lines = count($exp); echo <<

In $lines lines of code


Code coverage

XHTML; // Hash Pair function HP() { $args = func_get_args(); if (!is_array($args)) return $args; $myArgs = array(); for ($i=0;$i $val) { $myArgs[$key] = $val; } } return $myArgs; } // END HA() // Hash map entry function H($node, $val) {return array($node => $val);} ?>