pax_global_header00006660000000000000000000000064127763726700014533gustar00rootroot0000000000000052 comment=bae283f0cdf2d73f2d0e947724e46ca2ba565066 php-db-dataobject-1.11.5/000077500000000000000000000000001277637267000150705ustar00rootroot00000000000000php-db-dataobject-1.11.5/DB_DataObject-1.11.5/000077500000000000000000000000001277637267000201565ustar00rootroot00000000000000php-db-dataobject-1.11.5/DB_DataObject-1.11.5/DB/000077500000000000000000000000001277637267000204435ustar00rootroot00000000000000php-db-dataobject-1.11.5/DB_DataObject-1.11.5/DB/DataObject.php000077500000000000000000005301601277637267000231640ustar00rootroot00000000000000 * @copyright 1997-2006 The PHP Group * @license http://www.php.net/license/3_01.txt PHP License 3.01 * @version CVS: $Id: DataObject.php 336751 2015-05-12 04:39:50Z alan_k $ * @link http://pear.php.net/package/DB_DataObject */ /* =========================================================================== * * !!!!!!!!!!!!! W A R N I N G !!!!!!!!!!! * * THIS MAY SEGFAULT PHP IF YOU ARE USING THE ZEND OPTIMIZER (to fix it, * just add "define('DB_DATAOBJECT_NO_OVERLOAD',true);" before you include * this file. reducing the optimization level may also solve the segfault. * =========================================================================== */ /** * The main "DB_DataObject" class is really a base class for your own tables classes * * // Set up the class by creating an ini file (refer to the manual for more details * [DB_DataObject] * database = mysql:/username:password@host/database * schema_location = /home/myapplication/database * class_location = /home/myapplication/DBTables/ * clase_prefix = DBTables_ * * * //Start and initialize...................... - dont forget the & * $config = parse_ini_file('example.ini',true); * $options = &PEAR::getStaticProperty('DB_DataObject','options'); * $options = $config['DB_DataObject']; * * // example of a class (that does not use the 'auto generated tables data') * class mytable extends DB_DataObject { * // mandatory - set the table * var $_database_dsn = "mysql://username:password@localhost/database"; * var $__table = "mytable"; * function table() { * return array( * 'id' => 1, // integer or number * 'name' => 2, // string * ); * } * function keys() { * return array('id'); * } * } * * // use in the application * * * Simple get one row * * $instance = new mytable; * $instance->get("id",12); * echo $instance->somedata; * * * Get multiple rows * * $instance = new mytable; * $instance->whereAdd("ID > 12"); * $instance->whereAdd("ID < 14"); * $instance->find(); * while ($instance->fetch()) { * echo $instance->somedata; * } /** * Needed classes * - we use getStaticProperty from PEAR pretty extensively (cant remove it ATM) */ require_once 'PEAR.php'; /** * We are duping fetchmode constants to be compatible with * both DB and MDB2 */ define('DB_DATAOBJECT_FETCHMODE_ORDERED',1); define('DB_DATAOBJECT_FETCHMODE_ASSOC',2); /** * these are constants for the get_table array * user to determine what type of escaping is required around the object vars. */ define('DB_DATAOBJECT_INT', 1); // does not require '' define('DB_DATAOBJECT_STR', 2); // requires '' define('DB_DATAOBJECT_DATE', 4); // is date #TODO define('DB_DATAOBJECT_TIME', 8); // is time #TODO define('DB_DATAOBJECT_BOOL', 16); // is boolean #TODO define('DB_DATAOBJECT_TXT', 32); // is long text #TODO define('DB_DATAOBJECT_BLOB', 64); // is blob type define('DB_DATAOBJECT_NOTNULL', 128); // not null col. define('DB_DATAOBJECT_MYSQLTIMESTAMP' , 256); // mysql timestamps (ignored by update/insert) /* * Define this before you include DataObjects.php to disable overload - if it segfaults due to Zend optimizer.. */ //define('DB_DATAOBJECT_NO_OVERLOAD',true) /** * Theses are the standard error codes, most methods will fail silently - and return false * to access the error message either use $table->_lastError * or $last_error = PEAR::getStaticProperty('DB_DataObject','lastError'); * the code is $last_error->code, and the message is $last_error->message (a standard PEAR error) */ define('DB_DATAOBJECT_ERROR_INVALIDARGS', -1); // wrong args to function define('DB_DATAOBJECT_ERROR_NODATA', -2); // no data available define('DB_DATAOBJECT_ERROR_INVALIDCONFIG', -3); // something wrong with the config define('DB_DATAOBJECT_ERROR_NOCLASS', -4); // no class exists define('DB_DATAOBJECT_ERROR_INVALID_CALL' ,-7); // overlad getter/setter failure /** * Used in methods like delete() and count() to specify that the method should * build the condition only out of the whereAdd's and not the object parameters. */ define('DB_DATAOBJECT_WHEREADD_ONLY', true); /** * * storage for connection and result objects, * it is done this way so that print_r()'ing the is smaller, and * it reduces the memory size of the object. * -- future versions may use $this->_connection = & PEAR object.. * although will need speed tests to see how this affects it. * - includes sub arrays * - connections = md5 sum mapp to pear db object * - results = [id] => map to pear db object * - resultseq = sequence id for results & results field * - resultfields = [id] => list of fields return from query (for use with toArray()) * - ini = mapping of database to ini file results * - links = mapping of database to links file * - lasterror = pear error objects for last error event. * - config = aliased view of PEAR::getStaticPropery('DB_DataObject','options') * done for performance. * - array of loaded classes by autoload method - to stop it doing file access request over and over again! */ $GLOBALS['_DB_DATAOBJECT']['RESULTS'] = array(); $GLOBALS['_DB_DATAOBJECT']['RESULTSEQ'] = 1; $GLOBALS['_DB_DATAOBJECT']['RESULTFIELDS'] = array(); $GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'] = array(); $GLOBALS['_DB_DATAOBJECT']['INI'] = array(); $GLOBALS['_DB_DATAOBJECT']['LINKS'] = array(); $GLOBALS['_DB_DATAOBJECT']['SEQUENCE'] = array(); $GLOBALS['_DB_DATAOBJECT']['LASTERROR'] = null; $GLOBALS['_DB_DATAOBJECT']['CONFIG'] = array(); $GLOBALS['_DB_DATAOBJECT']['CACHE'] = array(); $GLOBALS['_DB_DATAOBJECT']['OVERLOADED'] = false; $GLOBALS['_DB_DATAOBJECT']['QUERYENDTIME'] = 0; // this will be horrifically slow!!!! // these two are BC/FC handlers for call in PHP4/5 if (!defined('DB_DATAOBJECT_NO_OVERLOAD')) { class DB_DataObject_Overload { function __call($method,$args) { $return = null; $this->_call($method,$args,$return); return $return; } function __sleep() { return array_keys(get_object_vars($this)) ; } } } else { class DB_DataObject_Overload {} } /* * * @package DB_DataObject * @author Alan Knowles * @since PHP 4.0 */ class DB_DataObject extends DB_DataObject_Overload { /** * The Version - use this to check feature changes * * @access private * @var string */ var $_DB_DataObject_version = "1.11.3"; /** * The Database table (used by table extends) * * @access private * @var string */ var $__table = ''; // database table /** * The Number of rows returned from a query * * @access public * @var int */ var $N = 0; // Number of rows returned from a query /* ============================================================= */ /* Major Public Methods */ /* (designed to be optionally then called with parent::method()) */ /* ============================================================= */ /** * Get a result using key, value. * * for example * $object->get("ID",1234); * Returns Number of rows located (usually 1) for success, * and puts all the table columns into this classes variables * * see the fetch example on how to extend this. * * if no value is entered, it is assumed that $key is a value * and get will then use the first key in keys() * to obtain the key. * * @param string $k column * @param string $v value * @access public * @return int No. of rows */ function get($k = null, $v = null) { global $_DB_DATAOBJECT; if (empty($_DB_DATAOBJECT['CONFIG'])) { DB_DataObject::_loadConfig(); } $keys = array(); if ($v === null) { $v = $k; $keys = $this->keys(); if (!$keys) { $this->raiseError("No Keys available for {$this->tableName()}", DB_DATAOBJECT_ERROR_INVALIDCONFIG); return false; } $k = $keys[0]; } if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { $this->debug("$k $v " .print_r($keys,true), "GET"); } if ($v === null) { $this->raiseError("No Value specified for get", DB_DATAOBJECT_ERROR_INVALIDARGS); return false; } $this->$k = $v; return $this->find(1); } /** * Get the value of the primary id * * While I normally use 'id' as the PRIMARY KEY value, some database use * {table}_id as the column name. * * To save a bit of typing, * * $id = $do->pid(); * * @return the id */ function pid() { $keys = $this->keys(); if (!$keys) { $this->raiseError("No Keys available for {$this->tableName()}", DB_DATAOBJECT_ERROR_INVALIDCONFIG); return false; } $k = $keys[0]; if (empty($this->$k)) { // we do not $this->raiseError("pid() called on Object where primary key value not available", DB_DATAOBJECT_ERROR_NODATA); return false; } return $this->$k; } /** * build the basic select query. * * @access private */ function _build_select() { global $_DB_DATAOBJECT; $quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']); if ($quoteIdentifiers) { $this->_connect(); $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; } $tn = ($quoteIdentifiers ? $DB->quoteIdentifier($this->tableName()) : $this->tableName()) ; if (!empty($this->_query['derive_table']) && !empty($this->_query['derive_select']) ) { // this is a derived select.. // not much support in the api yet.. $sql = 'SELECT ' . $this->_query['derive_select'] .' FROM ( SELECT'. $this->_query['data_select'] . " \n" . " FROM $tn " . $this->_query['useindex'] . " \n" . $this->_join . " \n" . $this->_query['condition'] . " \n" . $this->_query['group_by'] . " \n" . $this->_query['having'] . " \n" . ') ' . $this->_query['derive_table']; return $sql; } $sql = 'SELECT ' . $this->_query['data_select'] . " \n" . " FROM $tn " . $this->_query['useindex'] . " \n" . $this->_join . " \n" . $this->_query['condition'] . " \n" . $this->_query['group_by'] . " \n" . $this->_query['having'] . " \n"; return $sql; } /** * find results, either normal or crosstable * * for example * * $object = new mytable(); * $object->ID = 1; * $object->find(); * * * will set $object->N to number of rows, and expects next command to fetch rows * will return $object->N * * if an error occurs $object->N will be set to false and return value will also be false; * if numRows is not supported it will * * * @param boolean $n Fetch first result * @access public * @return mixed (number of rows returned, or true if numRows fetching is not supported) */ function find($n = false) { global $_DB_DATAOBJECT; if ($this->_query === false) { $this->raiseError( "You cannot do two queries on the same object (copy it before finding)", DB_DATAOBJECT_ERROR_INVALIDARGS); return false; } if (empty($_DB_DATAOBJECT['CONFIG'])) { DB_DataObject::_loadConfig(); } if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { $this->debug($n, "find",1); } if (!strlen($this->tableName())) { // xdebug can backtrace this! trigger_error("NO \$__table SPECIFIED in class definition",E_USER_ERROR); } $this->N = 0; $query_before = $this->_query; $this->_build_condition($this->table()) ; $this->_connect(); $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; $sql = $this->_build_select(); foreach ($this->_query['unions'] as $union_ar) { $sql .= $union_ar[1] . $union_ar[0]->_build_select() . " \n"; } $sql .= $this->_query['order_by'] . " \n"; /* We are checking for method modifyLimitQuery as it is PEAR DB specific */ if ((!isset($_DB_DATAOBJECT['CONFIG']['db_driver'])) || ($_DB_DATAOBJECT['CONFIG']['db_driver'] == 'DB')) { /* PEAR DB specific */ if (isset($this->_query['limit_start']) && strlen($this->_query['limit_start'] . $this->_query['limit_count'])) { $sql = $DB->modifyLimitQuery($sql,$this->_query['limit_start'], $this->_query['limit_count']); } } else { /* theoretically MDB2! */ if (isset($this->_query['limit_start']) && strlen($this->_query['limit_start'] . $this->_query['limit_count'])) { $DB->setLimit($this->_query['limit_count'],$this->_query['limit_start']); } } $err = $this->_query($sql); if (is_a($err,'PEAR_Error')) { return false; } if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { $this->debug("CHECK autofetchd $n", "find", 1); } // find(true) $ret = $this->N; if (!$ret && !empty($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid])) { // clear up memory if nothing found!? unset($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid]); } if ($n && $this->N > 0 ) { if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { $this->debug("ABOUT TO AUTOFETCH", "find", 1); } $fs = $this->fetch(); // if fetch returns false (eg. failed), then the backend doesnt support numRows (eg. ret=true) // - hence find() also returns false.. $ret = ($ret === true) ? $fs : $ret; } if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { $this->debug("DONE", "find", 1); } $this->_query = $query_before; return $ret; } /** * fetches next row into this objects var's * * returns 1 on success 0 on failure * * * * Example * $object = new mytable(); * $object->name = "fred"; * $object->find(); * $store = array(); * while ($object->fetch()) { * echo $this->ID; * $store[] = $object; // builds an array of object lines. * } * * to add features to a fetch * function fetch () { * $ret = parent::fetch(); * $this->date_formated = date('dmY',$this->date); * return $ret; * } * * @access public * @return boolean on success */ function fetch() { global $_DB_DATAOBJECT; if (empty($_DB_DATAOBJECT['CONFIG'])) { DB_DataObject::_loadConfig(); } if (empty($this->N)) { if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { $this->debug("No data returned from FIND (eg. N is 0)","FETCH", 3); } return false; } if (empty($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid]) || !is_object($result = $_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid])) { if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { $this->debug('fetched on object after fetch completed (no results found)'); } return false; } $array = $result->fetchRow(DB_DATAOBJECT_FETCHMODE_ASSOC); if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { $this->debug(serialize($array),"FETCH"); } // fetched after last row.. if ($array === null) { if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { $t= explode(' ',microtime()); $this->debug("Last Data Fetch'ed after " . ($t[0]+$t[1]- $_DB_DATAOBJECT['QUERYENDTIME'] ) . " seconds", "FETCH", 1); } // reduce the memory usage a bit... (but leave the id in, so count() works ok on it) unset($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid]); // we need to keep a copy of resultfields locally so toArray() still works // however we dont want to keep it in the global cache.. if (!empty($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid])) { $this->_resultFields = $_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid]; unset($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid]); } // this is probably end of data!! //DB_DataObject::raiseError("fetch: no data returned", DB_DATAOBJECT_ERROR_NODATA); return false; } // make sure resultFields is always empty.. $this->_resultFields = false; if (!isset($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid])) { // note: we dont declare this to keep the print_r size down. $_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid]= array_flip(array_keys($array)); } $replace = array('.', ' '); foreach($array as $k=>$v) { // use strpos as str_replace is slow. $kk = (strpos($k, '.') === false && strpos($k, ' ') === false) ? $k : str_replace($replace, '_', $k); if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { $this->debug("$kk = ". $array[$k], "fetchrow LINE", 3); } $this->$kk = $array[$k]; } // set link flag $this->_link_loaded=false; if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { $this->debug("{$this->tableName()} DONE", "fetchrow",2); } if (($this->_query !== false) && empty($_DB_DATAOBJECT['CONFIG']['keep_query_after_fetch'])) { $this->_query = false; } return true; } /** * fetches all results as an array, * * return format is dependant on args. * if selectAdd() has not been called on the object, then it will add the correct columns to the query. * * A) Array of values (eg. a list of 'id') * * $x = DB_DataObject::factory('mytable'); * $x->whereAdd('something = 1') * $ar = $x->fetchAll('id'); * -- returns array(1,2,3,4,5) * * B) Array of values (not from table) * * $x = DB_DataObject::factory('mytable'); * $x->whereAdd('something = 1'); * $x->selectAdd(); * $x->selectAdd('distinct(group_id) as group_id'); * $ar = $x->fetchAll('group_id'); * -- returns array(1,2,3,4,5) * * * C) A key=>value associative array * * $x = DB_DataObject::factory('mytable'); * $x->whereAdd('something = 1') * $ar = $x->fetchAll('id','name'); * -- returns array(1=>'fred',2=>'blogs',3=> ....... * * D) array of objects * $x = DB_DataObject::factory('mytable'); * $x->whereAdd('something = 1'); * $ar = $x->fetchAll(); * * E) array of arrays (for example) * $x = DB_DataObject::factory('mytable'); * $x->whereAdd('something = 1'); * $ar = $x->fetchAll(false,false,'toArray'); * * * @param string|false $k key * @param string|false $v value * @param string|false $method method to call on each result to get array value (eg. 'toArray') * @access public * @return array format dependant on arguments, may be empty */ function fetchAll($k= false, $v = false, $method = false) { // should it even do this!!!?!? if ($k !== false && ( // only do this is we have not been explicit.. empty($this->_query['data_select']) || ($this->_query['data_select'] == '*') ) ) { $this->selectAdd(); $this->selectAdd($k); if ($v !== false) { $this->selectAdd($v); } } $this->find(); $ret = array(); while ($this->fetch()) { if ($v !== false) { $ret[$this->$k] = $this->$v; continue; } $ret[] = $k === false ? ($method == false ? clone($this) : $this->$method()) : $this->$k; } return $ret; } /** * Adds a condition to the WHERE statement, defaults to AND * * $object->whereAdd(); //reset or cleaer ewhwer * $object->whereAdd("ID > 20"); * $object->whereAdd("age > 20","OR"); * * @param string $cond condition * @param string $logic optional logic "OR" (defaults to "AND") * @access public * @return string|PEAR::Error - previous condition or Error when invalid args found */ function whereAdd($cond = false, $logic = 'AND') { // for PHP5.2.3 - there is a bug with setting array properties of an object. $_query = $this->_query; if (!isset($this->_query) || ($_query === false)) { return $this->raiseError( "You cannot do two queries on the same object (clone it before finding)", DB_DATAOBJECT_ERROR_INVALIDARGS); } if ($cond === false) { $r = $this->_query['condition']; $_query['condition'] = ''; $this->_query = $_query; return preg_replace('/^\s+WHERE\s+/','',$r); } // check input...= 0 or ' ' == error! if (!trim($cond)) { return $this->raiseError("WhereAdd: No Valid Arguments", DB_DATAOBJECT_ERROR_INVALIDARGS); } $r = $_query['condition']; if ($_query['condition']) { $_query['condition'] .= " {$logic} ( {$cond} )"; $this->_query = $_query; return $r; } $_query['condition'] = " WHERE ( {$cond} ) "; $this->_query = $_query; return $r; } /** * Adds a 'IN' condition to the WHERE statement * * $object->whereAddIn('id', $array, 'int'); //minimal usage * $object->whereAddIn('price', $array, 'float', 'OR'); // cast to float, and call whereAdd with 'OR' * $object->whereAddIn('name', $array, 'string'); // quote strings * * @param string $key key column to match * @param array $list list of values to match * @param string $type string|int|integer|float|bool cast to type. * @param string $logic optional logic to call whereAdd with eg. "OR" (defaults to "AND") * @access public * @return string|PEAR::Error - previous condition or Error when invalid args found */ function whereAddIn($key, $list, $type, $logic = 'AND') { $not = ''; if ($key[0] == '!') { $not = 'NOT '; $key = substr($key, 1); } // fix type for short entry. $type = $type == 'int' ? 'integer' : $type; if ($type == 'string') { $this->_connect(); } $ar = array(); foreach($list as $k) { settype($k, $type); $ar[] = $type == 'string' ? $this->_quote($k) : $k; } if (!$ar) { return $not ? $this->_query['condition'] : $this->whereAdd("1=0"); } return $this->whereAdd("$key $not IN (". implode(',', $ar). ')', $logic ); } /** * Adds a order by condition * * $object->orderBy(); //clears order by * $object->orderBy("ID"); * $object->orderBy("ID,age"); * * @param string $order Order * @access public * @return none|PEAR::Error - invalid args only */ function orderBy($order = false) { if ($this->_query === false) { $this->raiseError( "You cannot do two queries on the same object (copy it before finding)", DB_DATAOBJECT_ERROR_INVALIDARGS); return false; } if ($order === false) { $this->_query['order_by'] = ''; return; } // check input...= 0 or ' ' == error! if (!trim($order)) { return $this->raiseError("orderBy: No Valid Arguments", DB_DATAOBJECT_ERROR_INVALIDARGS); } if (!$this->_query['order_by']) { $this->_query['order_by'] = " ORDER BY {$order} "; return; } $this->_query['order_by'] .= " , {$order}"; } /** * Adds a group by condition * * $object->groupBy(); //reset the grouping * $object->groupBy("ID DESC"); * $object->groupBy("ID,age"); * * @param string $group Grouping * @access public * @return none|PEAR::Error - invalid args only */ function groupBy($group = false) { if ($this->_query === false) { $this->raiseError( "You cannot do two queries on the same object (copy it before finding)", DB_DATAOBJECT_ERROR_INVALIDARGS); return false; } if ($group === false) { $this->_query['group_by'] = ''; return; } // check input...= 0 or ' ' == error! if (!trim($group)) { return $this->raiseError("groupBy: No Valid Arguments", DB_DATAOBJECT_ERROR_INVALIDARGS); } if (!$this->_query['group_by']) { $this->_query['group_by'] = " GROUP BY {$group} "; return; } $this->_query['group_by'] .= " , {$group}"; } /** * Adds a having clause * * $object->having(); //reset the grouping * $object->having("sum(value) > 0 "); * * @param string $having condition * @access public * @return none|PEAR::Error - invalid args only */ function having($having = false) { if ($this->_query === false) { $this->raiseError( "You cannot do two queries on the same object (copy it before finding)", DB_DATAOBJECT_ERROR_INVALIDARGS); return false; } if ($having === false) { $this->_query['having'] = ''; return; } // check input...= 0 or ' ' == error! if (!trim($having)) { return $this->raiseError("Having: No Valid Arguments", DB_DATAOBJECT_ERROR_INVALIDARGS); } if (!$this->_query['having']) { $this->_query['having'] = " HAVING {$having} "; return; } $this->_query['having'] .= " AND {$having}"; } /** * Adds a using Index * * $object->useIndex(); //reset the use Index * $object->useIndex("some_index"); * * Note do not put unfiltered user input into theis method. * This is mysql specific at present? - might need altering to support other databases. * * @param string|array $index index or indexes to use. * @access public * @return none|PEAR::Error - invalid args only */ function useIndex($index = false) { if ($this->_query === false) { $this->raiseError( "You cannot do two queries on the same object (copy it before finding)", DB_DATAOBJECT_ERROR_INVALIDARGS); return false; } if ($index=== false) { $this->_query['useindex'] = ''; return; } // check input...= 0 or ' ' == error! if ((is_string($index) && !trim($index)) || (is_array($index) && !count($index)) ) { return $this->raiseError("Having: No Valid Arguments", DB_DATAOBJECT_ERROR_INVALIDARGS); } $index = is_array($index) ? implode(', ', $index) : $index; if (!$this->_query['useindex']) { $this->_query['useindex'] = " USE INDEX ({$index}) "; return; } $this->_query['useindex'] = substr($this->_query['useindex'],0, -2) . ", {$index}) "; } /** * Sets the Limit * * $boject->limit(); // clear limit * $object->limit(12); * $object->limit(12,10); * * Note this will emit an error on databases other than mysql/postgress * as there is no 'clean way' to implement it. - you should consider refering to * your database manual to decide how you want to implement it. * * @param string $a limit start (or number), or blank to reset * @param string $b number * @access public * @return none|PEAR::Error - invalid args only */ function limit($a = null, $b = null) { if ($this->_query === false) { $this->raiseError( "You cannot do two queries on the same object (copy it before finding)", DB_DATAOBJECT_ERROR_INVALIDARGS); return false; } if ($a === null) { $this->_query['limit_start'] = ''; $this->_query['limit_count'] = ''; return; } // check input...= 0 or ' ' == error! if ((!is_int($a) && ((string)((int)$a) !== (string)$a)) || (($b !== null) && (!is_int($b) && ((string)((int)$b) !== (string)$b)))) { return $this->raiseError("limit: No Valid Arguments", DB_DATAOBJECT_ERROR_INVALIDARGS); } global $_DB_DATAOBJECT; $this->_connect(); $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; $this->_query['limit_start'] = ($b == null) ? 0 : (int)$a; $this->_query['limit_count'] = ($b == null) ? (int)$a : (int)$b; } /** * Adds a select columns * * $object->selectAdd(); // resets select to nothing! * $object->selectAdd("*"); // default select * $object->selectAdd("unixtime(DATE) as udate"); * $object->selectAdd("DATE"); * * to prepend distict: * $object->selectAdd('distinct ' . $object->selectAdd()); * * @param string $k * @access public * @return mixed null or old string if you reset it. */ function selectAdd($k = null) { if ($this->_query === false) { $this->raiseError( "You cannot do two queries on the same object (copy it before finding)", DB_DATAOBJECT_ERROR_INVALIDARGS); return false; } if ($k === null) { $old = $this->_query['data_select']; $this->_query['data_select'] = ''; return $old; } // check input...= 0 or ' ' == error! if (!trim($k)) { return $this->raiseError("selectAdd: No Valid Arguments", DB_DATAOBJECT_ERROR_INVALIDARGS); } if ($this->_query['data_select']) { $this->_query['data_select'] .= ', '; } $this->_query['data_select'] .= " $k "; } /** * Adds multiple Columns or objects to select with formating. * * $object->selectAs(null); // adds "table.colnameA as colnameA,table.colnameB as colnameB,......" * // note with null it will also clear the '*' default select * $object->selectAs(array('a','b'),'%s_x'); // adds "a as a_x, b as b_x" * $object->selectAs(array('a','b'),'ddd_%s','ccc'); // adds "ccc.a as ddd_a, ccc.b as ddd_b" * $object->selectAdd($object,'prefix_%s'); // calls $object->get_table and adds it all as * objectTableName.colnameA as prefix_colnameA * * @param array|object|null the array or object to take column names from. * @param string format in sprintf format (use %s for the colname) * @param string table name eg. if you have joinAdd'd or send $from as an array. * @access public * @return void */ function selectAs($from = null,$format = '%s',$tableName=false) { global $_DB_DATAOBJECT; if ($this->_query === false) { $this->raiseError( "You cannot do two queries on the same object (copy it before finding)", DB_DATAOBJECT_ERROR_INVALIDARGS); return false; } if ($from === null) { // blank the '*' $this->selectAdd(); $from = $this; } $table = $this->tableName(); if (is_object($from)) { $table = $from->tableName(); $from = array_keys($from->table()); } if ($tableName !== false) { $table = $tableName; } $s = '%s'; if (!empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers'])) { $this->_connect(); $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; $s = $DB->quoteIdentifier($s); $format = $DB->quoteIdentifier($format); } foreach ($from as $k) { $this->selectAdd(sprintf("{$s}.{$s} as {$format}",$table,$k,$k)); } $this->_query['data_select'] .= "\n"; } /** * Insert the current objects variables into the database * * Returns the ID of the inserted element (if auto increment or sequences are used.) * * for example * * Designed to be extended * * $object = new mytable(); * $object->name = "fred"; * echo $object->insert(); * * @access public * @return mixed false on failure, int when auto increment or sequence used, otherwise true on success */ function insert() { global $_DB_DATAOBJECT; // we need to write to the connection (For nextid) - so us the real // one not, a copyied on (as ret-by-ref fails with overload!) if (!isset($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) { $this->_connect(); } $quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']); $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; $items = $this->table(); if (!$items) { $this->raiseError("insert:No table definition for {$this->tableName()}", DB_DATAOBJECT_ERROR_INVALIDCONFIG); return false; } $options = $_DB_DATAOBJECT['CONFIG']; $datasaved = 1; $leftq = ''; $rightq = ''; $seqKeys = isset($_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()]) ? $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()] : $this->sequenceKey(); $key = isset($seqKeys[0]) ? $seqKeys[0] : false; $useNative = isset($seqKeys[1]) ? $seqKeys[1] : false; $seq = isset($seqKeys[2]) ? $seqKeys[2] : false; $dbtype = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn["phptype"]; // nativeSequences or Sequences.. // big check for using sequences if (($key !== false) && !$useNative) { if (!$seq) { $keyvalue = $DB->nextId($this->tableName()); } else { $f = $DB->getOption('seqname_format'); $DB->setOption('seqname_format','%s'); $keyvalue = $DB->nextId($seq); $DB->setOption('seqname_format',$f); } if (PEAR::isError($keyvalue)) { $this->raiseError($keyvalue->toString(), DB_DATAOBJECT_ERROR_INVALIDCONFIG); return false; } $this->$key = $keyvalue; } // if we haven't set disable_null_strings to "full" $ignore_null = !isset($options['disable_null_strings']) || !is_string($options['disable_null_strings']) || strtolower($options['disable_null_strings']) !== 'full' ; foreach($items as $k => $v) { // if we are using autoincrement - skip the column... if ($key && ($k == $key) && $useNative) { continue; } // Ignore INTEGERS which aren't set to a value - or empty string.. if ( (!isset($this->$k) || ($v == 1 && $this->$k === '')) && $ignore_null ) { continue; } // dont insert data into mysql timestamps // use query() if you really want to do this!!!! if ($v & DB_DATAOBJECT_MYSQLTIMESTAMP) { continue; } if ($leftq) { $leftq .= ', '; $rightq .= ', '; } $leftq .= ($quoteIdentifiers ? ($DB->quoteIdentifier($k) . ' ') : "$k "); if (is_object($this->$k) && is_a($this->$k,'DB_DataObject_Cast')) { $value = $this->$k->toString($v,$DB); if (PEAR::isError($value)) { $this->raiseError($value->toString() ,DB_DATAOBJECT_ERROR_INVALIDARGS); return false; } $rightq .= $value; continue; } if (!($v & DB_DATAOBJECT_NOTNULL) && DB_DataObject::_is_null($this,$k)) { $rightq .= " NULL "; continue; } // DATE is empty... on a col. that can be null.. // note: this may be usefull for time as well.. if (!$this->$k && (($v & DB_DATAOBJECT_DATE) || ($v & DB_DATAOBJECT_TIME)) && !($v & DB_DATAOBJECT_NOTNULL)) { $rightq .= " NULL "; continue; } if ($v & DB_DATAOBJECT_STR) { $rightq .= $this->_quote((string) ( ($v & DB_DATAOBJECT_BOOL) ? // this is thanks to the braindead idea of postgres to // use t/f for boolean. (($this->$k === 'f') ? 0 : (int)(bool) $this->$k) : $this->$k )) . " "; continue; } if (is_numeric($this->$k)) { $rightq .=" {$this->$k} "; continue; } /* flag up string values - only at debug level... !!!??? */ if (is_object($this->$k) || is_array($this->$k)) { $this->debug('ODD DATA: ' .$k . ' ' . print_r($this->$k,true),'ERROR'); } // at present we only cast to integers // - V2 may store additional data about float/int $rightq .= ' ' . intval($this->$k) . ' '; } // not sure why we let empty insert here.. - I guess to generate a blank row.. if ($leftq || $useNative) { $table = ($quoteIdentifiers ? $DB->quoteIdentifier($this->tableName()) : $this->tableName()); if (($dbtype == 'pgsql') && empty($leftq)) { $r = $this->_query("INSERT INTO {$table} DEFAULT VALUES"); } else { $r = $this->_query("INSERT INTO {$table} ($leftq) VALUES ($rightq) "); } if (PEAR::isError($r)) { $this->raiseError($r); return false; } if ($r < 1) { return 0; } // now do we have an integer key! if ($key && $useNative) { switch ($dbtype) { case 'mysql': case 'mysqli': $method = "{$dbtype}_insert_id"; $this->$key = $method( $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->connection ); break; case 'mssql': // note this is not really thread safe - you should wrapp it with // transactions = eg. // $db->query('BEGIN'); // $db->insert(); // $db->query('COMMIT'); $db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver']; $method = ($db_driver == 'DB') ? 'getOne' : 'queryOne'; $mssql_key = $DB->$method("SELECT @@IDENTITY"); if (PEAR::isError($mssql_key)) { $this->raiseError($mssql_key); return false; } $this->$key = $mssql_key; break; case 'pgsql': if (!$seq) { $seq = $DB->getSequenceName(strtolower($this->tableName())); } $db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver']; $method = ($db_driver == 'DB') ? 'getOne' : 'queryOne'; $pgsql_key = $DB->$method("SELECT currval('".$seq . "')"); if (PEAR::isError($pgsql_key)) { $this->raiseError($pgsql_key); return false; } $this->$key = $pgsql_key; break; case 'ifx': $this->$key = array_shift ( ifx_fetch_row ( ifx_query( "select DBINFO('sqlca.sqlerrd1') FROM systables where tabid=1", $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->connection, IFX_SCROLL ), "FIRST" ) ); break; } } if (isset($_DB_DATAOBJECT['CACHE'][strtolower(get_class($this))])) { $this->_clear_cache(); } if ($key) { return $this->$key; } return true; } $this->raiseError("insert: No Data specifed for query", DB_DATAOBJECT_ERROR_NODATA); return false; } /** * Updates current objects variables into the database * uses the keys() to decide how to update * Returns the true on success * * for example * * $object = DB_DataObject::factory('mytable'); * $object->get("ID",234); * $object->email="testing@test.com"; * if(!$object->update()) * echo "UPDATE FAILED"; * * to only update changed items : * $dataobject->get(132); * $original = $dataobject; // clone/copy it.. * $dataobject->setFrom($_POST); * if ($dataobject->validate()) { * $dataobject->update($original); * } // otherwise an error... * * performing global updates: * $object = DB_DataObject::factory('mytable'); * $object->status = "dead"; * $object->whereAdd('age > 150'); * $object->update(DB_DATAOBJECT_WHEREADD_ONLY); * * @param object dataobject (optional) | DB_DATAOBJECT_WHEREADD_ONLY - used to only update changed items. * @access public * @return int rows affected or false on failure */ function update($dataObject = false) { global $_DB_DATAOBJECT; // connect will load the config! $this->_connect(); $original_query = $this->_query; $items = $this->table(); // only apply update against sequence key if it is set????? $seq = $this->sequenceKey(); if ($seq[0] !== false) { $keys = array($seq[0]); if (!isset($this->{$keys[0]}) && $dataObject !== true) { $this->raiseError("update: trying to perform an update without the key set, and argument to update is not DB_DATAOBJECT_WHEREADD_ONLY ". print_r(array('seq' => $seq , 'keys'=>$keys), true), DB_DATAOBJECT_ERROR_INVALIDARGS); return false; } } else { $keys = $this->keys(); } if (!$items) { $this->raiseError("update:No table definition for {$this->tableName()}", DB_DATAOBJECT_ERROR_INVALIDCONFIG); return false; } $datasaved = 1; $settings = ''; $this->_connect(); $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; $dbtype = $DB->dsn["phptype"]; $quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']); $options = $_DB_DATAOBJECT['CONFIG']; $ignore_null = !isset($options['disable_null_strings']) || !is_string($options['disable_null_strings']) || strtolower($options['disable_null_strings']) !== 'full' ; foreach($items as $k => $v) { // I think this is ignoring empty vlalues if ((!isset($this->$k) || ($v == 1 && $this->$k === '')) && $ignore_null ) { continue; } // ignore stuff thats // dont write things that havent changed.. if (($dataObject !== false) && isset($dataObject->$k) && ($dataObject->$k === $this->$k)) { continue; } // - dont write keys to left.!!! if (in_array($k,$keys)) { continue; } // dont insert data into mysql timestamps // use query() if you really want to do this!!!! if ($v & DB_DATAOBJECT_MYSQLTIMESTAMP) { continue; } if ($settings) { $settings .= ', '; } $kSql = ($quoteIdentifiers ? $DB->quoteIdentifier($k) : $k); if (is_object($this->$k) && is_a($this->$k,'DB_DataObject_Cast')) { $value = $this->$k->toString($v,$DB); if (PEAR::isError($value)) { $this->raiseError($value->getMessage() ,DB_DATAOBJECT_ERROR_INVALIDARG); return false; } $settings .= "$kSql = $value "; continue; } // special values ... at least null is handled... if (!($v & DB_DATAOBJECT_NOTNULL) && DB_DataObject::_is_null($this,$k)) { $settings .= "$kSql = NULL "; continue; } // DATE is empty... on a col. that can be null.. // note: this may be usefull for time as well.. if (!$this->$k && (($v & DB_DATAOBJECT_DATE) || ($v & DB_DATAOBJECT_TIME)) && !($v & DB_DATAOBJECT_NOTNULL)) { $settings .= "$kSql = NULL "; continue; } if ($v & DB_DATAOBJECT_STR) { $settings .= "$kSql = ". $this->_quote((string) ( ($v & DB_DATAOBJECT_BOOL) ? // this is thanks to the braindead idea of postgres to // use t/f for boolean. (($this->$k === 'f') ? 0 : (int)(bool) $this->$k) : $this->$k )) . ' '; continue; } if (is_numeric($this->$k)) { $settings .= "$kSql = {$this->$k} "; continue; } // at present we only cast to integers // - V2 may store additional data about float/int $settings .= "$kSql = " . intval($this->$k) . ' '; } if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { $this->debug("got keys as ".serialize($keys),3); } if ($dataObject !== true) { $this->_build_condition($items,$keys); } else { // prevent wiping out of data! if (empty($this->_query['condition'])) { $this->raiseError("update: global table update not available do \$do->whereAdd('1=1'); if you really want to do that. ", DB_DATAOBJECT_ERROR_INVALIDARGS); return false; } } // echo " $settings, $this->condition "; if ($settings && isset($this->_query) && $this->_query['condition']) { $table = ($quoteIdentifiers ? $DB->quoteIdentifier($this->tableName()) : $this->tableName()); $r = $this->_query("UPDATE {$table} SET {$settings} {$this->_query['condition']} "); // restore original query conditions. $this->_query = $original_query; if (PEAR::isError($r)) { $this->raiseError($r); return false; } if ($r < 1) { return 0; } $this->_clear_cache(); return $r; } // restore original query conditions. $this->_query = $original_query; // if you manually specified a dataobject, and there where no changes - then it's ok.. if ($dataObject !== false) { return true; } $this->raiseError( "update: No Data specifed for query $settings , {$this->_query['condition']}", DB_DATAOBJECT_ERROR_NODATA); return false; } /** * Deletes items from table which match current objects variables * * Returns the true on success * * for example * * Designed to be extended * * $object = new mytable(); * $object->ID=123; * echo $object->delete(); // builds a conditon * * $object = new mytable(); * $object->whereAdd('age > 12'); * $object->limit(1); * $object->orderBy('age DESC'); * $object->delete(true); // dont use object vars, use the conditions, limit and order. * * @param bool $useWhere (optional) If DB_DATAOBJECT_WHEREADD_ONLY is passed in then * we will build the condition only using the whereAdd's. Default is to * build the condition only using the object parameters. * * @access public * @return mixed Int (No. of rows affected) on success, false on failure, 0 on no data affected */ function delete($useWhere = false) { global $_DB_DATAOBJECT; // connect will load the config! $this->_connect(); $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; $quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']); $extra_cond = ' ' . (isset($this->_query['order_by']) ? $this->_query['order_by'] : ''); if (!$useWhere) { $keys = $this->keys(); $this->_query = array(); // as it's probably unset! $this->_query['condition'] = ''; // default behaviour not to use where condition $this->_build_condition($this->table(),$keys); // if primary keys are not set then use data from rest of object. if (!$this->_query['condition']) { $this->_build_condition($this->table(),array(),$keys); } $extra_cond = ''; } // don't delete without a condition if (($this->_query !== false) && $this->_query['condition']) { $table = ($quoteIdentifiers ? $DB->quoteIdentifier($this->tableName()) : $this->tableName()); $sql = "DELETE "; // using a joined delete. - with useWhere.. $sql .= (!empty($this->_join) && $useWhere) ? "{$table} FROM {$table} {$this->_join} " : "FROM {$table} "; $sql .= $this->_query['condition']. $extra_cond; // add limit.. if (isset($this->_query['limit_start']) && strlen($this->_query['limit_start'] . $this->_query['limit_count'])) { if (!isset($_DB_DATAOBJECT['CONFIG']['db_driver']) || ($_DB_DATAOBJECT['CONFIG']['db_driver'] == 'DB')) { // pear DB $sql = $DB->modifyLimitQuery($sql,$this->_query['limit_start'], $this->_query['limit_count']); } else { // MDB2 $DB->setLimit( $this->_query['limit_count'],$this->_query['limit_start']); } } $r = $this->_query($sql); if (PEAR::isError($r)) { $this->raiseError($r); return false; } if ($r < 1) { return 0; } $this->_clear_cache(); return $r; } else { $this->raiseError("delete: No condition specifed for query", DB_DATAOBJECT_ERROR_NODATA); return false; } } /** * fetches a specific row into this object variables * * Not recommended - better to use fetch() * * Returens true on success * * @param int $row row * @access public * @return boolean true on success */ function fetchRow($row = null) { global $_DB_DATAOBJECT; if (empty($_DB_DATAOBJECT['CONFIG'])) { $this->_loadConfig(); } if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { $this->debug("{$this->tableName()} $row of {$this->N}", "fetchrow",3); } if (!$this->tableName()) { $this->raiseError("fetchrow: No table", DB_DATAOBJECT_ERROR_INVALIDCONFIG); return false; } if ($row === null) { $this->raiseError("fetchrow: No row specified", DB_DATAOBJECT_ERROR_INVALIDARGS); return false; } if (!$this->N) { $this->raiseError("fetchrow: No results avaiable", DB_DATAOBJECT_ERROR_NODATA); return false; } if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { $this->debug("{$this->tableName()} $row of {$this->N}", "fetchrow",3); } $result = $_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid]; $array = $result->fetchrow(DB_DATAOBJECT_FETCHMODE_ASSOC,$row); if (!is_array($array)) { $this->raiseError("fetchrow: No results available", DB_DATAOBJECT_ERROR_NODATA); return false; } $replace = array('.', ' '); foreach($array as $k => $v) { // use strpos as str_replace is slow. $kk = (strpos($k, '.') === false && strpos($k, ' ') === false) ? $k : str_replace($replace, '_', $k); if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { $this->debug("$kk = ". $array[$k], "fetchrow LINE", 3); } $this->$kk = $array[$k]; } if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { $this->debug("{$this->tableName()} DONE", "fetchrow", 3); } return true; } /** * Find the number of results from a simple query * * for example * * $object = new mytable(); * $object->name = "fred"; * echo $object->count(); * echo $object->count(true); // dont use object vars. * echo $object->count('distinct mycol'); count distinct mycol. * echo $object->count('distinct mycol',true); // dont use object vars. * echo $object->count('distinct'); // count distinct id (eg. the primary key) * * * @param bool|string (optional) * (true|false => see below not on whereAddonly) * (string) * "DISTINCT" => does a distinct count on the tables 'key' column * otherwise => normally it counts primary keys - you can use * this to do things like $do->count('distinct mycol'); * * @param bool $whereAddOnly (optional) If DB_DATAOBJECT_WHEREADD_ONLY is passed in then * we will build the condition only using the whereAdd's. Default is to * build the condition using the object parameters as well. * * @access public * @return int */ function count($countWhat = false,$whereAddOnly = false) { global $_DB_DATAOBJECT; if (is_bool($countWhat)) { $whereAddOnly = $countWhat; } $t = clone($this); $items = $t->table(); $quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']); if (!isset($t->_query)) { $this->raiseError( "You cannot do run count after you have run fetch()", DB_DATAOBJECT_ERROR_INVALIDARGS); return false; } $this->_connect(); $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; if (!$whereAddOnly && $items) { $t->_build_condition($items); } $keys = $this->keys(); if (empty($keys[0]) && (!is_string($countWhat) || (strtoupper($countWhat) == 'DISTINCT'))) { $this->raiseError( "You cannot do run count without keys - use \$do->count('id'), or use \$do->count('distinct id')';", DB_DATAOBJECT_ERROR_INVALIDARGS,PEAR_ERROR_DIE); return false; } $table = ($quoteIdentifiers ? $DB->quoteIdentifier($this->tableName()) : $this->tableName()); $key_col = empty($keys[0]) ? '' : (($quoteIdentifiers ? $DB->quoteIdentifier($keys[0]) : $keys[0])); $as = ($quoteIdentifiers ? $DB->quoteIdentifier('DATAOBJECT_NUM') : 'DATAOBJECT_NUM'); // support distinct on default keys. $countWhat = (strtoupper($countWhat) == 'DISTINCT') ? "DISTINCT {$table}.{$key_col}" : $countWhat; $countWhat = is_string($countWhat) ? $countWhat : "{$table}.{$key_col}"; $r = $t->_query( "SELECT count({$countWhat}) as $as FROM $table {$t->_join} {$t->_query['condition']}"); if (PEAR::isError($r)) { return false; } $result = $_DB_DATAOBJECT['RESULTS'][$t->_DB_resultid]; $l = $result->fetchRow(DB_DATAOBJECT_FETCHMODE_ORDERED); // free the results - essential on oracle. $t->free(); if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { $this->debug('Count returned '. $l[0] ,1); } return (int) $l[0]; } /** * sends raw query to database * * Since _query has to be a private 'non overwriteable method', this is a relay * * @param string $string SQL Query * @access public * @return void or DB_Error */ function query($string) { return $this->_query($string); } /** * an escape wrapper around DB->escapeSimple() * can be used when adding manual queries or clauses * eg. * $object->query("select * from xyz where abc like '". $object->escape($_GET['name']) . "'"); * * @param string $string value to be escaped * @param bool $likeEscape escapes % and _ as well. - so like queries can be protected. * @access public * @return string */ function escape($string, $likeEscape=false) { global $_DB_DATAOBJECT; $this->_connect(); $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; // mdb2 uses escape... $dd = empty($_DB_DATAOBJECT['CONFIG']['db_driver']) ? 'DB' : $_DB_DATAOBJECT['CONFIG']['db_driver']; $ret = ($dd == 'DB') ? $DB->escapeSimple($string) : $DB->escape($string); if ($likeEscape) { $ret = str_replace(array('_','%'), array('\_','\%'), $ret); } return $ret; } /* ==================================================== */ /* Major Private Vars */ /* ==================================================== */ /** * The Database connection dsn (as described in the PEAR DB) * only used really if you are writing a very simple application/test.. * try not to use this - it is better stored in configuration files.. * * @access private * @var string */ var $_database_dsn = ''; /** * The Database connection id (md5 sum of databasedsn) * * @access private * @var string */ var $_database_dsn_md5 = ''; /** * The Database name * created in __connection * * @access private * @var string */ var $_database = ''; /** * The QUERY rules * This replaces alot of the private variables * used to build a query, it is unset after find() is run. * * * * @access private * @var array */ var $_query = array( 'condition' => '', // the WHERE condition 'group_by' => '', // the GROUP BY condition 'order_by' => '', // the ORDER BY condition 'having' => '', // the HAVING condition 'useindex' => '', // the USE INDEX condition 'limit_start' => '', // the LIMIT condition 'limit_count' => '', // the LIMIT condition 'data_select' => '*', // the columns to be SELECTed 'unions' => array(), // the added unions, 'derive_table' => '', // derived table name (BETA) 'derive_select' => '', // derived table select (BETA) ); /** * Database result id (references global $_DB_DataObject[results] * * @access private * @var integer */ var $_DB_resultid; /** * ResultFields - on the last call to fetch(), resultfields is sent here, * so we can clean up the memory. * * @access public * @var array */ var $_resultFields = false; /* ============================================================== */ /* Table definition layer (started of very private but 'came out'*/ /* ============================================================== */ /** * Autoload or manually load the table definitions * * * usage : * DB_DataObject::databaseStructure( 'databasename', * parse_ini_file('mydb.ini',true), * parse_ini_file('mydb.link.ini',true)); * * obviously you dont have to use ini files.. (just return array similar to ini files..) * * It should append to the table structure array * * * @param optional string name of database to assign / read * @param optional array structure of database, and keys * @param optional array table links * * @access public * @return true or PEAR:error on wrong paramenters.. or false if no file exists.. * or the array(tablename => array(column_name=>type)) if called with 1 argument.. (databasename) */ function databaseStructure() { global $_DB_DATAOBJECT; // Assignment code if ($args = func_get_args()) { if (count($args) == 1) { // this returns all the tables and their structure.. if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { $this->debug("Loading Generator as databaseStructure called with args",1); } $x = new DB_DataObject; $x->_database = $args[0]; $this->_connect(); $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; $tables = $DB->getListOf('tables'); class_exists('DB_DataObject_Generator') ? '' : require_once 'DB/DataObject/Generator.php'; foreach($tables as $table) { $y = new DB_DataObject_Generator; $y->fillTableSchema($x->_database,$table); } return $_DB_DATAOBJECT['INI'][$x->_database]; } else { $_DB_DATAOBJECT['INI'][$args[0]] = isset($_DB_DATAOBJECT['INI'][$args[0]]) ? $_DB_DATAOBJECT['INI'][$args[0]] + $args[1] : $args[1]; if (isset($args[1])) { $_DB_DATAOBJECT['LINKS'][$args[0]] = isset($_DB_DATAOBJECT['LINKS'][$args[0]]) ? $_DB_DATAOBJECT['LINKS'][$args[0]] + $args[2] : $args[2]; } return true; } } if (!$this->_database) { $this->_connect(); } // if this table is already loaded this table.. if (!empty($_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()])) { return true; } // initialize the ini data.. if empt.. if (empty($_DB_DATAOBJECT['INI'][$this->_database])) { $_DB_DATAOBJECT['INI'][$this->_database] = array(); } if (empty($_DB_DATAOBJECT['CONFIG'])) { DB_DataObject::_loadConfig(); } // we do not have the data for this table yet... // if we are configured to use the proxy.. if ( !empty($_DB_DATAOBJECT['CONFIG']['proxy']) ) { if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { $this->debug("Loading Generator to fetch Schema",1); } class_exists('DB_DataObject_Generator') ? '' : require_once 'DB/DataObject/Generator.php'; $x = new DB_DataObject_Generator; $x->fillTableSchema($this->_database,$this->tableName()); return true; } // if you supply this with arguments, then it will take those // as the database and links array... $schemas = isset($_DB_DATAOBJECT['CONFIG']['schema_location']) ? array("{$_DB_DATAOBJECT['CONFIG']['schema_location']}/{$this->_database}.ini") : array() ; if (isset($_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"])) { $schemas = is_array($_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"]) ? $_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"] : explode(PATH_SEPARATOR,$_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"]); } $_DB_DATAOBJECT['INI'][$this->_database] = array(); foreach ($schemas as $ini) { if (file_exists($ini) && is_file($ini)) { $_DB_DATAOBJECT['INI'][$this->_database] = array_merge( $_DB_DATAOBJECT['INI'][$this->_database], parse_ini_file($ini, true) ); if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { if (!is_readable ($ini)) { $this->debug("ini file is not readable: $ini","databaseStructure",1); } else { $this->debug("Loaded ini file: $ini","databaseStructure",1); } } } else { if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { $this->debug("Missing ini file: $ini","databaseStructure",1); } } } // are table name lowecased.. if (!empty($_DB_DATAOBJECT['CONFIG']['portability']) && $_DB_DATAOBJECT['CONFIG']['portability'] & 1) { foreach($_DB_DATAOBJECT['INI'][$this->_database] as $k=>$v) { // results in duplicate cols.. but not a big issue.. $_DB_DATAOBJECT['INI'][$this->_database][strtolower($k)] = $v; } } // now have we loaded the structure.. if (!empty($_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()])) { return true; } // - if not try building it.. if (!empty($_DB_DATAOBJECT['CONFIG']['proxy'])) { class_exists('DB_DataObject_Generator') ? '' : require_once 'DB/DataObject/Generator.php'; $x = new DB_DataObject_Generator; $x->fillTableSchema($this->_database,$this->tableName()); // should this fail!!!??? return true; } $this->debug("Cant find database schema: {$this->_database}/{$this->tableName()} \n". "in links file data: " . print_r($_DB_DATAOBJECT['INI'],true),"databaseStructure",5); // we have to die here!! - it causes chaos if we dont (including looping forever!) $this->raiseError( "Unable to load schema for database and table (turn debugging up to 5 for full error message)", DB_DATAOBJECT_ERROR_INVALIDARGS, PEAR_ERROR_DIE); return false; } /** * Return or assign the name of the current table * * * @param string optinal table name to set * @access public * @return string The name of the current table */ function tableName() { global $_DB_DATAOBJECT; $args = func_get_args(); if (count($args)) { $this->__table = $args[0]; } if (empty($this->__table)) { return ''; } if (!empty($_DB_DATAOBJECT['CONFIG']['portability']) && $_DB_DATAOBJECT['CONFIG']['portability'] & 1) { return strtolower($this->__table); } return $this->__table; } /** * Return or assign the name of the current database * * @param string optional database name to set * @access public * @return string The name of the current database */ function database() { $args = func_get_args(); if (count($args)) { $this->_database = $args[0]; } else { $this->_connect(); } return $this->_database; } /** * get/set an associative array of table columns * * @access public * @param array key=>type array * @return array (associative) */ function table() { // for temporary storage of database fields.. // note this is not declared as we dont want to bloat the print_r output $args = func_get_args(); if (count($args)) { $this->_database_fields = $args[0]; } if (isset($this->_database_fields)) { return $this->_database_fields; } global $_DB_DATAOBJECT; if (!isset($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) { $this->_connect(); } if (isset($_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()])) { return $_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()]; } $this->databaseStructure(); $ret = array(); if (isset($_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()])) { $ret = $_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()]; } return $ret; } /** * get/set an array of table primary keys * * set usage: $do->keys('id','code'); * * This is defined in the table definition if it gets it wrong, * or you do not want to use ini tables, you can override this. * @param string optional set the key * @param * optional set more keys * @access public * @return array */ function keys() { // for temporary storage of database fields.. // note this is not declared as we dont want to bloat the print_r output $args = func_get_args(); if (count($args)) { $this->_database_keys = $args; } if (isset($this->_database_keys)) { return $this->_database_keys; } global $_DB_DATAOBJECT; if (!isset($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) { $this->_connect(); } if (isset($_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()."__keys"])) { return array_keys($_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()."__keys"]); } $this->databaseStructure(); if (isset($_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()."__keys"])) { return array_keys($_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()."__keys"]); } return array(); } /** * get/set an sequence key * * by default it returns the first key from keys() * set usage: $do->sequenceKey('id',true); * * override this to return array(false,false) if table has no real sequence key. * * @param string optional the key sequence/autoinc. key * @param boolean optional use native increment. default false * @param false|string optional native sequence name * @access public * @return array (column,use_native,sequence_name) */ function sequenceKey() { global $_DB_DATAOBJECT; // call setting if (!$this->_database) { $this->_connect(); } if (!isset($_DB_DATAOBJECT['SEQUENCE'][$this->_database])) { $_DB_DATAOBJECT['SEQUENCE'][$this->_database] = array(); } $args = func_get_args(); if (count($args)) { $args[1] = isset($args[1]) ? $args[1] : false; $args[2] = isset($args[2]) ? $args[2] : false; $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()] = $args; } if (isset($_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()])) { return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()]; } // end call setting (eg. $do->sequenceKeys(a,b,c); ) $keys = $this->keys(); if (!$keys) { return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()] = array(false,false,false); } $table = $this->table(); $dbtype = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype']; $usekey = $keys[0]; $seqname = false; if (!empty($_DB_DATAOBJECT['CONFIG']['sequence_'.$this->tableName()])) { $seqname = $_DB_DATAOBJECT['CONFIG']['sequence_'.$this->tableName()]; if (strpos($seqname,':') !== false) { list($usekey,$seqname) = explode(':',$seqname); } } // if the key is not an integer - then it's not a sequence or native if (empty($table[$usekey]) || !($table[$usekey] & DB_DATAOBJECT_INT)) { return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()] = array(false,false,false); } if (!empty($_DB_DATAOBJECT['CONFIG']['ignore_sequence_keys'])) { $ignore = $_DB_DATAOBJECT['CONFIG']['ignore_sequence_keys']; if (is_string($ignore) && (strtoupper($ignore) == 'ALL')) { return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()] = array(false,false,$seqname); } if (is_string($ignore)) { $ignore = $_DB_DATAOBJECT['CONFIG']['ignore_sequence_keys'] = explode(',',$ignore); } if (in_array($this->tableName(),$ignore)) { return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()] = array(false,false,$seqname); } } $realkeys = $_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()."__keys"]; // if you are using an old ini file - go back to old behaviour... if (is_numeric($realkeys[$usekey])) { $realkeys[$usekey] = 'N'; } // multiple unique primary keys without a native sequence... if (($realkeys[$usekey] == 'K') && (count($keys) > 1)) { return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()] = array(false,false,$seqname); } // use native sequence keys... // technically postgres native here... // we need to get the new improved tabledata sorted out first. // support named sequence keys.. - currently postgres only.. if ( in_array($dbtype , array('pgsql')) && ($table[$usekey] & DB_DATAOBJECT_INT) && isset($realkeys[$usekey]) && strlen($realkeys[$usekey]) > 1) { return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()] = array($usekey,true, $realkeys[$usekey]); } if ( in_array($dbtype , array('pgsql', 'mysql', 'mysqli', 'mssql', 'ifx')) && ($table[$usekey] & DB_DATAOBJECT_INT) && isset($realkeys[$usekey]) && ($realkeys[$usekey] == 'N') ) { return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()] = array($usekey,true,$seqname); } // if not a native autoinc, and we have not assumed all primary keys are sequence if (($realkeys[$usekey] != 'N') && !empty($_DB_DATAOBJECT['CONFIG']['dont_use_pear_sequences'])) { return array(false,false,false); } // I assume it's going to try and be a nextval DB sequence.. (not native) return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()] = array($usekey,false,$seqname); } /* =========================================================== */ /* Major Private Methods - the core part! */ /* =========================================================== */ /** * clear the cache values for this class - normally done on insert/update etc. * * @access private * @return void */ function _clear_cache() { global $_DB_DATAOBJECT; $class = strtolower(get_class($this)); if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { $this->debug("Clearing Cache for ".$class,1); } if (!empty($_DB_DATAOBJECT['CACHE'][$class])) { unset($_DB_DATAOBJECT['CACHE'][$class]); } } /** * backend wrapper for quoting, as MDB2 and DB do it differently... * * @access private * @return string quoted */ function _quote($str) { global $_DB_DATAOBJECT; return (empty($_DB_DATAOBJECT['CONFIG']['db_driver']) || ($_DB_DATAOBJECT['CONFIG']['db_driver'] == 'DB')) ? $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->quoteSmart($str) : $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->quote($str); } /** * connects to the database * * * TODO: tidy this up - This has grown to support a number of connection options like * a) dynamic changing of ini file to change which database to connect to * b) multi data via the table_{$table} = dsn ini option * c) session based storage. * * @access private * @return true | PEAR::error */ function _connect() { global $_DB_DATAOBJECT; if (empty($_DB_DATAOBJECT['CONFIG'])) { $this->_loadConfig(); } // Set database driver for reference $db_driver = empty($_DB_DATAOBJECT['CONFIG']['db_driver']) ? 'DB' : $_DB_DATAOBJECT['CONFIG']['db_driver']; // is it already connected ? if ($this->_database_dsn_md5 && !empty($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) { // connection is an error... if (PEAR::isError($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) { return $this->raiseError( $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->message, $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->code, PEAR_ERROR_DIE ); } if (empty($this->_database)) { $this->_database = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['database']; $hasGetDatabase = method_exists($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5], 'getDatabase'); $this->_database = ($db_driver != 'DB' && $hasGetDatabase) ? $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->getDatabase() : $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['database']; if (($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype'] == 'sqlite') && is_file($this->_database)) { $this->_database = basename($this->_database); } if ($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype'] == 'ibase') { $this->_database = substr(basename($this->_database), 0, -4); } } // theoretically we have a md5, it's listed in connections and it's not an error. // so everything is ok! return true; } // it's not currently connected! // try and work out what to use for the dsn ! $options= $_DB_DATAOBJECT['CONFIG']; // if the databse dsn dis defined in the object.. $dsn = isset($this->_database_dsn) ? $this->_database_dsn : null; if (!$dsn) { if (!$this->_database && !strlen($this->tableName())) { $this->_database = isset($options["table_{$this->tableName()}"]) ? $options["table_{$this->tableName()}"] : null; } if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { $this->debug("Checking for database specific ini ('{$this->_database}') : database_{$this->_database} in options","CONNECT"); } if ($this->_database && !empty($options["database_{$this->_database}"])) { $dsn = $options["database_{$this->_database}"]; } else if (!empty($options['database'])) { $dsn = $options['database']; } } // if still no database... if (!$dsn) { return $this->raiseError( "No database name / dsn found anywhere", DB_DATAOBJECT_ERROR_INVALIDCONFIG, PEAR_ERROR_DIE ); } if (is_string($dsn)) { $this->_database_dsn_md5 = md5($dsn); } else { /// support array based dsn's $this->_database_dsn_md5 = md5(serialize($dsn)); } if (!empty($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) { if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { $this->debug("USING CACHED CONNECTION", "CONNECT",3); } if (!$this->_database) { $hasGetDatabase = method_exists($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5], 'getDatabase'); $this->_database = ($db_driver != 'DB' && $hasGetDatabase) ? $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->getDatabase() : $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['database']; if (($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype'] == 'sqlite') && is_file($this->_database)) { $this->_database = basename($this->_database); } } return true; } if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { $this->debug("NEW CONNECTION TP DATABASE :" .$this->_database , "CONNECT",3); /* actualy make a connection */ $this->debug(print_r($dsn,true) ." {$this->_database_dsn_md5}", "CONNECT",3); } // Note this is verbose deliberatly! if ($db_driver == 'DB') { /* PEAR DB connect */ // this allows the setings of compatibility on DB $db_options = PEAR::getStaticProperty('DB','options'); require_once 'DB.php'; if ($db_options) { $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5] = DB::connect($dsn,$db_options); } else { $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5] = DB::connect($dsn); } } else { /* assumption is MDB2 */ require_once 'MDB2.php'; // this allows the setings of compatibility on MDB2 $db_options = PEAR::getStaticProperty('MDB2','options'); $db_options = is_array($db_options) ? $db_options : array(); $db_options['portability'] = isset($db_options['portability'] ) ? $db_options['portability'] : MDB2_PORTABILITY_ALL ^ MDB2_PORTABILITY_FIX_CASE; $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5] = MDB2::connect($dsn,$db_options); } if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { $this->debug(print_r($_DB_DATAOBJECT['CONNECTIONS'],true), "CONNECT",5); } if (PEAR::isError($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) { $this->debug($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->toString(), "CONNECT FAILED",5); return $this->raiseError( "Connect failed, turn on debugging to 5 see why", $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->code, PEAR_ERROR_DIE ); } if (empty($this->_database)) { $hasGetDatabase = method_exists($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5], 'getDatabase'); $this->_database = ($db_driver != 'DB' && $hasGetDatabase) ? $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->getDatabase() : $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['database']; if (($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype'] == 'sqlite') && is_file($this->_database)) { $this->_database = basename($this->_database); } } // Oracle need to optimize for portibility - not sure exactly what this does though :) return true; } /** * sends query to database - this is the private one that must work * - internal functions use this rather than $this->query() * * @param string $string * @access private * @return mixed none or PEAR_Error */ function _query($string) { global $_DB_DATAOBJECT; $this->_connect(); $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; $options = $_DB_DATAOBJECT['CONFIG']; $_DB_driver = empty($_DB_DATAOBJECT['CONFIG']['db_driver']) ? 'DB': $_DB_DATAOBJECT['CONFIG']['db_driver']; if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { $this->debug($string,$log="QUERY"); } if ( strtoupper($string) == 'BEGIN' || strtoupper($string) == 'START TRANSACTION' ) { if ($_DB_driver == 'DB') { $DB->autoCommit(false); $DB->simpleQuery('BEGIN'); } else { $DB->beginTransaction(); } return true; } if (strtoupper($string) == 'COMMIT') { $res = $DB->commit(); if ($_DB_driver == 'DB') { $DB->autoCommit(true); } return $res; } if (strtoupper($string) == 'ROLLBACK') { $DB->rollback(); if ($_DB_driver == 'DB') { $DB->autoCommit(true); } return true; } if (!empty($options['debug_ignore_updates']) && (strtolower(substr(trim($string), 0, 6)) != 'select') && (strtolower(substr(trim($string), 0, 4)) != 'show') && (strtolower(substr(trim($string), 0, 8)) != 'describe')) { $this->debug('Disabling Update as you are in debug mode'); return $this->raiseError("Disabling Update as you are in debug mode", null) ; } //if (@$_DB_DATAOBJECT['CONFIG']['debug'] > 1) { // this will only work when PEAR:DB supports it. //$this->debug($DB->getAll('explain ' .$string,DB_DATAOBJECT_FETCHMODE_ASSOC), $log="sql",2); //} // some sim $t= explode(' ',microtime()); $_DB_DATAOBJECT['QUERYENDTIME'] = $time = $t[0]+$t[1]; for ($tries = 0;$tries < 3;$tries++) { if ($_DB_driver == 'DB') { $result = $DB->query($string); } else { switch (strtolower(substr(trim($string),0,6))) { case 'insert': case 'update': case 'delete': $result = $DB->exec($string); break; default: $result = $DB->query($string); break; } } // see if we got a failure.. - try again a few times.. if (!is_object($result) || !is_a($result,'PEAR_Error')) { break; } if ($result->getCode() != -14) { // *DB_ERROR_NODBSELECTED break; // not a connection error.. } sleep(1); // wait before retyring.. $DB->connect($DB->dsn); } if (is_object($result) && is_a($result,'PEAR_Error')) { if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { $this->debug($result->toString(), "Query Error",1 ); } $this->N = false; return $this->raiseError($result); } if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { $t= explode(' ',microtime()); $_DB_DATAOBJECT['QUERYENDTIME'] = $t[0]+$t[1]; $this->debug('QUERY DONE IN '.($t[0]+$t[1]-$time)." seconds", 'query',1); } switch (strtolower(substr(trim($string),0,6))) { case 'insert': case 'update': case 'delete': if ($_DB_driver == 'DB') { // pear DB specific return $DB->affectedRows(); } return $result; } if (is_object($result)) { // lets hope that copying the result object is OK! $_DB_resultid = $GLOBALS['_DB_DATAOBJECT']['RESULTSEQ']++; $_DB_DATAOBJECT['RESULTS'][$_DB_resultid] = $result; $this->_DB_resultid = $_DB_resultid; } $this->N = 0; if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { $this->debug(serialize($result), 'RESULT',5); } if (method_exists($result, 'numRows')) { if ($_DB_driver == 'DB') { $DB->expectError(DB_ERROR_UNSUPPORTED); } else { $DB->expectError(MDB2_ERROR_UNSUPPORTED); } $this->N = $result->numRows(); //var_dump($this->N); if (is_object($this->N) && is_a($this->N,'PEAR_Error')) { $this->N = true; } $DB->popExpect(); } } /** * Builds the WHERE based on the values of of this object * * @param mixed $keys * @param array $filter (used by update to only uses keys in this filter list). * @param array $negative_filter (used by delete to prevent deleting using the keys mentioned..) * @access private * @return string */ function _build_condition($keys, $filter = array(),$negative_filter=array()) { global $_DB_DATAOBJECT; $this->_connect(); $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; $quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']); $options = $_DB_DATAOBJECT['CONFIG']; // if we dont have query vars.. - reset them. if ($this->_query === false) { $x = new DB_DataObject; $this->_query= $x->_query; } foreach($keys as $k => $v) { // index keys is an indexed array /* these filter checks are a bit suspicious.. - need to check that update really wants to work this way */ if ($filter) { if (!in_array($k, $filter)) { continue; } } if ($negative_filter) { if (in_array($k, $negative_filter)) { continue; } } if (!isset($this->$k)) { continue; } $kSql = $quoteIdentifiers ? ( $DB->quoteIdentifier($this->tableName()) . '.' . $DB->quoteIdentifier($k) ) : "{$this->tableName()}.{$k}"; if (is_object($this->$k) && is_a($this->$k,'DB_DataObject_Cast')) { $dbtype = $DB->dsn["phptype"]; $value = $this->$k->toString($v,$DB); if (PEAR::isError($value)) { $this->raiseError($value->getMessage() ,DB_DATAOBJECT_ERROR_INVALIDARG); return false; } if ((strtolower($value) === 'null') && !($v & DB_DATAOBJECT_NOTNULL)) { $this->whereAdd(" $kSql IS NULL"); continue; } $this->whereAdd(" $kSql = $value"); continue; } if (!($v & DB_DATAOBJECT_NOTNULL) && DB_DataObject::_is_null($this,$k)) { $this->whereAdd(" $kSql IS NULL"); continue; } if ($v & DB_DATAOBJECT_STR) { $this->whereAdd(" $kSql = " . $this->_quote((string) ( ($v & DB_DATAOBJECT_BOOL) ? // this is thanks to the braindead idea of postgres to // use t/f for boolean. (($this->$k === 'f') ? 0 : (int)(bool) $this->$k) : $this->$k )) ); continue; } if (is_numeric($this->$k)) { $this->whereAdd(" $kSql = {$this->$k}"); continue; } /* this is probably an error condition! */ $this->whereAdd(" $kSql = ".intval($this->$k)); } } /** * classic factory method for loading a table class * usage: $do = DB_DataObject::factory('person') * WARNING - this may emit a include error if the file does not exist.. * use @ to silence it (if you are sure it is acceptable) * eg. $do = @DB_DataObject::factory('person') * * table name can bedatabasename/table * - and allow modular dataobjects to be written.. * (this also helps proxy creation) * * Experimental Support for Multi-Database factory eg. mydatabase.mytable * * * @param string $table tablename (use blank to create a new instance of the same class.) * @access private * @return DataObject|PEAR_Error */ static function factory($table = '') { global $_DB_DATAOBJECT; // multi-database support.. - experimental. $database = ''; if (strpos( $table,'/') !== false ) { list($database,$table) = explode('.',$table, 2); } if (empty($_DB_DATAOBJECT['CONFIG'])) { DB_DataObject::_loadConfig(); } // no configuration available for database if (!empty($database) && empty($_DB_DATAOBJECT['CONFIG']['database_'.$database])) { $do = new DB_DataObject(); $do->raiseError( "unable to find database_{$database} in Configuration, It is required for factory with database" , 0, PEAR_ERROR_DIE ); } /* if ($table === '') { if (is_a($this,'DB_DataObject') && strlen($this->tableName())) { $table = $this->tableName(); } else { return DB_DataObject::raiseError( "factory did not recieve a table name", DB_DATAOBJECT_ERROR_INVALIDARGS); } } */ // does this need multi db support?? $cp = isset($_DB_DATAOBJECT['CONFIG']['class_prefix']) ? explode(PATH_SEPARATOR, $_DB_DATAOBJECT['CONFIG']['class_prefix']) : ''; //print_r($cp); // multiprefix support. $tbl = preg_replace('/[^A-Z0-9]/i','_',ucfirst($table)); if (is_array($cp)) { $class = array(); foreach($cp as $cpr) { $ce = substr(phpversion(),0,1) > 4 ? class_exists($cpr . $tbl,false) : class_exists($cpr . $tbl); if ($ce) { $class = $cpr . $tbl; break; } $class[] = $cpr . $tbl; } } else { $class = $tbl; $ce = substr(phpversion(),0,1) > 4 ? class_exists($class,false) : class_exists($class); } $rclass = $ce ? $class : DB_DataObject::_autoloadClass($class, $table); // proxy = full|light if (!$rclass && isset($_DB_DATAOBJECT['CONFIG']['proxy'])) { DB_DataObject::debug("FAILED TO Autoload $database.$table - using proxy.","FACTORY",1); $proxyMethod = 'getProxy'.$_DB_DATAOBJECT['CONFIG']['proxy']; // if you have loaded (some other way) - dont try and load it again.. class_exists('DB_DataObject_Generator') ? '' : require_once 'DB/DataObject/Generator.php'; $d = new DB_DataObject; $d->__table = $table; $ret = $d->_connect(); if (is_object($ret) && is_a($ret, 'PEAR_Error')) { return $ret; } $x = new DB_DataObject_Generator; return $x->$proxyMethod( $d->_database, $table); } if (!$rclass || !class_exists($rclass)) { $dor = new DB_DataObject(); return $dor->raiseError( "factory could not find class " . (is_array($class) ? implode(PATH_SEPARATOR, $class) : $class ). "from $table", DB_DATAOBJECT_ERROR_INVALIDCONFIG); } $ret = new $rclass(); if (!empty($database)) { DB_DataObject::debug("Setting database to $database","FACTORY",1); $ret->database($database); } return $ret; } /** * autoload Class * * @param string|array $class Class * @param string $table Table trying to load. * @access private * @return string classname on Success */ function _autoloadClass($class, $table=false) { global $_DB_DATAOBJECT; if (empty($_DB_DATAOBJECT['CONFIG'])) { DB_DataObject::_loadConfig(); } $class_prefix = empty($_DB_DATAOBJECT['CONFIG']['class_prefix']) ? '' : $_DB_DATAOBJECT['CONFIG']['class_prefix']; $table = $table ? $table : substr($class,strlen($class_prefix)); // only include the file if it exists - and barf badly if it has parse errors :) if (!empty($_DB_DATAOBJECT['CONFIG']['proxy']) || empty($_DB_DATAOBJECT['CONFIG']['class_location'])) { return false; } // support for: // class_location = mydir/ => maps to mydir/Tablename.php // class_location = mydir/myfile_%s.php => maps to mydir/myfile_Tablename // with directory sepr // class_location = mydir/:mydir2/: => tries all of thes locations. $cl = $_DB_DATAOBJECT['CONFIG']['class_location']; switch (true) { case (strpos($cl ,'%s') !== false): $file = sprintf($cl , preg_replace('/[^A-Z0-9]/i','_',ucfirst($table))); break; case (strpos($cl , PATH_SEPARATOR) !== false): $file = array(); foreach(explode(PATH_SEPARATOR, $cl ) as $p) { $file[] = $p .'/'.preg_replace('/[^A-Z0-9]/i','_',ucfirst($table)).".php"; } break; default: $file = $cl .'/'.preg_replace('/[^A-Z0-9]/i','_',ucfirst($table)).".php"; break; } $cls = is_array($class) ? $class : array($class); if (is_array($file) || !file_exists($file)) { $found = false; $file = is_array($file) ? $file : array($file); $search = implode(PATH_SEPARATOR, $file); foreach($file as $f) { foreach(explode(PATH_SEPARATOR, '' . PATH_SEPARATOR . ini_get('include_path')) as $p) { $ff = empty($p) ? $f : "$p/$f"; if (file_exists($ff)) { $file = $ff; $found = true; break; } } if ($found) { break; } } if (!$found) { $dor = new DB_DataObject(); $dor->raiseError( "autoload:Could not find class " . implode(',', $cls) . " using class_location value :" . $search . " using include_path value :" . ini_get('include_path'), DB_DATAOBJECT_ERROR_INVALIDCONFIG); return false; } } include_once $file; $ce = false; foreach($cls as $c) { $ce = substr(phpversion(),0,1) > 4 ? class_exists($c,false) : class_exists($c); if ($ce) { $class = $c; break; } } if (!$ce) { $dor = new DB_DataObject(); $dor->raiseError( "autoload:Could not autoload " . implode(',', $cls) , DB_DATAOBJECT_ERROR_INVALIDCONFIG); return false; } return $class; } /** * Have the links been loaded? * if they have it contains a array of those variables. * * @access private * @var boolean | array */ var $_link_loaded = false; /** * Get the links associate array as defined by the links.ini file. * * * Experimental... - * Should look a bit like * [local_col_name] => "related_tablename:related_col_name" * * @param array $new_links optional - force update of the links for this table * You probably want to restore it to it's original state after, * as modifying here does it for the whole PHP request. * * @return array|null * array = if there are links defined for this table. * empty array - if there is a links.ini file, but no links on this table * false - if no links.ini exists for this database (hence try auto_links). * @access public * @see DB_DataObject::getLinks(), DB_DataObject::getLink() */ function links() { global $_DB_DATAOBJECT; if (empty($_DB_DATAOBJECT['CONFIG'])) { $this->_loadConfig(); } // have to connect.. -> otherwise things break later. $this->_connect(); // alias for shorter code.. $lcfg = &$_DB_DATAOBJECT['LINKS']; $cfg = $_DB_DATAOBJECT['CONFIG']; if ($args = func_get_args()) { // an associative array was specified, that updates the current // schema... - be careful doing this if (empty( $lcfg[$this->_database])) { $lcfg[$this->_database] = array(); } $lcfg[$this->_database][$this->tableName()] = $args[0]; } // loaded and available. if (isset($lcfg[$this->_database][$this->tableName()])) { return $lcfg[$this->_database][$this->tableName()]; } // loaded if (isset($lcfg[$this->_database])) { // either no file, or empty.. return $lcfg[$this->_database] === false ? null : array(); } // links are same place as schema by default. $schemas = isset($cfg['schema_location']) ? array("{$cfg['schema_location']}/{$this->_database}.ini") : array() ; // if ini_* is set look there instead. // and support multiple locations. if (isset($cfg["ini_{$this->_database}"])) { $schemas = is_array($cfg["ini_{$this->_database}"]) ? $cfg["ini_{$this->_database}"] : explode(PATH_SEPARATOR,$cfg["ini_{$this->_database}"]); } // default to not available. $lcfg[$this->_database] = false; foreach ($schemas as $ini) { $links = isset($cfg["links_{$this->_database}"]) ? $cfg["links_{$this->_database}"] : str_replace('.ini','.links.ini',$ini); // file really exists.. if (!file_exists($links) || !is_file($links)) { if (!empty($cfg['debug'])) { $this->debug("Missing links.ini file: $links","links",1); } continue; } // set to empty array - as we have at least one file now.. $lcfg[$this->_database] = empty($lcfg[$this->_database]) ? array() : $lcfg[$this->_database]; // merge schema file into lcfg.. $lcfg[$this->_database] = array_merge( $lcfg[$this->_database], parse_ini_file($links, true) ); if (!empty($cfg['debug'])) { $this->debug("Loaded links.ini file: $links","links",1); } } if (!empty($_DB_DATAOBJECT['CONFIG']['portability']) && $_DB_DATAOBJECT['CONFIG']['portability'] & 1) { foreach($lcfg[$this->_database] as $k=>$v) { $nk = strtolower($k); // results in duplicate cols.. but not a big issue.. $lcfg[$this->_database][$nk] = isset($lcfg[$this->_database][$nk]) ? $lcfg[$this->_database][$nk] : array(); foreach($v as $kk =>$vv) { //var_Dump($vv);exit; $vv =explode(':', $vv); $vv[0] = strtolower($vv[0]); $lcfg[$this->_database][$nk][$kk] = implode(':', $vv); } } } //echo '
';print_r($lcfg);exit;
        
        // if there is no link data at all on the file!
        // we return null.
        if ($lcfg[$this->_database] === false) {
            return null;
        }
        
        if (isset($lcfg[$this->_database][$this->tableName()])) {
            return $lcfg[$this->_database][$this->tableName()];
        }
        
        return array();
    }
    
    
    /**
     * generic getter/setter for links
     *
     * This is the new 'recommended' way to get get/set linked objects.
     * must be used with links.ini
     *
     * usage:
     *  get:
     *  $obj = $do->link('company_id');
     *  $obj = $do->link(array('local_col', 'linktable:linked_col'));
     *  
     *  set:
     *  $do->link('company_id',0);
     *  $do->link('company_id',$obj);
     *  $do->link('company_id', array($obj));
     *
     *  example function
     *
     *  function company() {
     *     $this->link(array('company_id','company:id'), func_get_args());
     *   }
     *
     * 
     *
     * @param  mixed $link_spec              link specification (normally a string)
     *                                       uses similar rules to  joinAdd() array argument.
     * @param  mixed $set_value (optional)   int, DataObject, or array('set')
     * @author Alan Knowles
     * @access public
     * @return mixed true or false on setting, object on getting
     */
    function link($field, $set_args = array())
    {
        require_once 'DB/DataObject/Links.php';
        $l = new DB_DataObject_Links($this);
        return  $l->link($field,$set_args) ;
        
    }
    
      /**
     * load related objects
     *
     * Generally not recommended to use this.
     * The generator should support creating getter_setter methods which are better suited.
     *
     * Relies on  .links.ini
     *
     * Sets properties on the calling dataobject  you can change what
     * object vars the links are stored in by  changeing the format parameter
     *
     *
     * @param  string format (default _%s) where %s is the table name.
     * @author Tim White 
     * @access public
     * @return boolean , true on success
     */
    function getLinks($format = '_%s')
    {
        require_once 'DB/DataObject/Links.php';
         $l = new DB_DataObject_Links($this);
        return $l->applyLinks($format);
           
    }

    /**
     * deprecited : @use link() 
     */
    function getLink($row, $table = null, $link = false)
    {
        require_once 'DB/DataObject/Links.php';
        $l = new DB_DataObject_Links($this);
        return $l->getLink($row, $table === null ? false: $table, $link);
         
        
    }

    /**
     * getLinkArray
     * Fetch an array of related objects. This should be used in conjunction with a .links.ini file configuration (see the introduction on linking for details on this).
     * You may also use this with all parameters to specify, the column and related table.
     * This is highly dependant on naming columns 'correctly' :)
     * using colname = xxxxx_yyyyyy
     * xxxxxx = related table; (yyyyy = user defined..)
     * looks up table xxxxx, for value id=$this->xxxxx
     * stores it in $this->_xxxxx_yyyyy
     *
     * @access public
     * @param string $column - either column or column.xxxxx
     * @param string $table - name of table to look up value in
     * @return array - array of results (empty array on failure)
     * 
     * Example - Getting the related objects
     * 
     * $person = new DataObjects_Person;
     * $person->get(12);
     * $children = $person->getLinkArray('children');
     * 
     * echo 'There are ', count($children), ' descendant(s):
'; * foreach ($children as $child) { * echo $child->name, '
'; * } * */ function getLinkArray($row, $table = null) { require_once 'DB/DataObject/Links.php'; $l = new DB_DataObject_Links($this); return $l->getLinkArray($row, $table === null ? false: $table); } /** * unionAdd - adds another dataobject to this, building a unioned query. * * usage: * $doTable1 = DB_DataObject::factory("table1"); * $doTable2 = DB_DataObject::factory("table2"); * * $doTable1->selectAdd(); * $doTable1->selectAdd("col1,col2"); * $doTable1->whereAdd("col1 > 100"); * $doTable1->orderBy("col1"); * * $doTable2->selectAdd(); * $doTable2->selectAdd("col1, col2"); * $doTable2->whereAdd("col2 = 'v'"); * * $doTable1->unionAdd($doTable2); * $doTable1->find(); * * Note: this model may be a better way to implement joinAdd?, eg. do the building in find? * * * @param $obj object|false the union object or false to reset * @param optional $is_all string 'ALL' to do all. * @returns $obj object|array the added object, or old list if reset. */ function unionAdd($obj,$is_all= '') { if ($obj === false) { $ret = $this->_query['unions']; $this->_query['unions'] = array(); return $ret; } $this->_query['unions'][] = array($obj, 'UNION ' . $is_all . ' ') ; return $obj; } /** * The JOIN condition * * @access private * @var string */ var $_join = ''; /** * joinAdd - adds another dataobject to this, building a joined query. * * example (requires links.ini to be set up correctly) * // get all the images for product 24 * $i = new DataObject_Image(); * $pi = new DataObjects_Product_image(); * $pi->product_id = 24; // set the product id to 24 * $i->joinAdd($pi); // add the product_image connectoin * $i->find(); * while ($i->fetch()) { * // do stuff * } * // an example with 2 joins * // get all the images linked with products or productgroups * $i = new DataObject_Image(); * $pi = new DataObject_Product_image(); * $pgi = new DataObject_Productgroup_image(); * $i->joinAdd($pi); * $i->joinAdd($pgi); * $i->find(); * while ($i->fetch()) { * // do stuff * } * * * @param optional $obj object |array the joining object (no value resets the join) * If you use an array here it should be in the format: * array('local_column','remotetable:remote_column'); * if remotetable does not have a definition, you should * use @ to hide the include error message.. * array('local_column', $dataobject , 'remote_column'); * if array has 3 args, then second is assumed to be the linked dataobject. * * @param optional $joinType string | array * 'LEFT'|'INNER'|'RIGHT'|'' Inner is default, '' indicates * just select ... from a,b,c with no join and * links are added as where items. * * If second Argument is array, it is assumed to be an associative * array with arguments matching below = eg. * 'joinType' => 'INNER', * 'joinAs' => '...' * 'joinCol' => .... * 'useWhereAsOn' => false, * * @param optional $joinAs string if you want to select the table as anther name * useful when you want to select multiple columsn * from a secondary table. * @param optional $joinCol string The column on This objects table to match (needed * if this table links to the child object in * multiple places eg. * user->friend (is a link to another user) * user->mother (is a link to another user..) * * optional 'useWhereAsOn' bool default false; * convert the where argments from the object being added * into ON arguments. * * * @return none * @access public * @author Stijn de Reede */ function joinAdd($obj = false, $joinType='INNER', $joinAs=false, $joinCol=false) { global $_DB_DATAOBJECT; if ($obj === false) { $this->_join = ''; return; } //echo '
'; print_r(func_get_args());
        $useWhereAsOn = false;
        // support for 2nd argument as an array of options
        if (is_array($joinType)) {
            // new options can now go in here... (dont forget to document them)
            $useWhereAsOn = !empty($joinType['useWhereAsOn']);
            $joinCol      = isset($joinType['joinCol'])  ? $joinType['joinCol']  : $joinCol;
            $joinAs       = isset($joinType['joinAs'])   ? $joinType['joinAs']   : $joinAs;
            $joinType     = isset($joinType['joinType']) ? $joinType['joinType'] : 'INNER';
        }
        // support for array as first argument 
        // this assumes that you dont have a links.ini for the specified table.
        // and it doesnt exist as am extended dataobject!! - experimental.
        
        $ofield = false; // object field
        $tfield = false; // this field
        $toTable = false;
        if (is_array($obj)) {
            $tfield = $obj[0];
            
            if (count($obj) == 3) {
                $ofield = $obj[2];
                $obj = $obj[1];
            } else {
                list($toTable,$ofield) = explode(':',$obj[1]);
            
                $obj = is_string($toTable) ? DB_DataObject::factory($toTable) : $toTable;
            
                if (!$obj || !is_object($obj) || is_a($obj,'PEAR_Error')) {
                    $obj = new DB_DataObject;
                    $obj->__table = $toTable;
                }
                $obj->_connect();
            }
            // set the table items to nothing.. - eg. do not try and match
            // things in the child table...???
            $items = array();
        }
        
        if (!is_object($obj) || !is_a($obj,'DB_DataObject')) {
            return $this->raiseError("joinAdd: called without an object", DB_DATAOBJECT_ERROR_NODATA,PEAR_ERROR_DIE);
        }
        /*  make sure $this->_database is set.  */
        $this->_connect();
        $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
       

        /// CHANGED 26 JUN 2009 - we prefer links from our local table over the remote one.
        
        /* otherwise see if there are any links from this table to the obj. */
        //print_r($this->links());
        if (($ofield === false) && ($links = $this->links())) {
            // this enables for support for arrays of links in ini file.
            // link contains this_column[] =  linked_table:linked_column
            // or standard way.
            // link contains this_column =  linked_table:linked_column
            foreach ($links as $k => $linkVar) {
            
                if (!is_array($linkVar)) {
                    $linkVar  = array($linkVar);
                }
                foreach($linkVar as $v) {

                    
                    
                    /* link contains {this column} = {linked table}:{linked column} */
                    $ar = explode(':', $v);
                    // Feature Request #4266 - Allow joins with multiple keys
                    if (strpos($k, ',') !== false) {
                        $k = explode(',', $k);
                    }
                    if (strpos($ar[1], ',') !== false) {
                        $ar[1] = explode(',', $ar[1]);
                    }

                    if ($ar[0] != $obj->tableName()) {
                        continue;
                    }
                    if ($joinCol !== false) {
                        if ($k == $joinCol) {
                            // got it!?
                            $tfield = $k;
                            $ofield = $ar[1];
                            break;
                        } 
                        continue;
                        
                    } 
                    $tfield = $k;
                    $ofield = $ar[1];
                    break;
                        
                }
            }
        }
         /* look up the links for obj table */
        //print_r($obj->links());
        if (!$ofield && ($olinks = $obj->links())) {
            
            foreach ($olinks as $k => $linkVar) {
                /* link contains {this column} = array ( {linked table}:{linked column} )*/
                if (!is_array($linkVar)) {
                    $linkVar  = array($linkVar);
                }
                foreach($linkVar as $v) {
                    
                    /* link contains {this column} = {linked table}:{linked column} */
                    $ar = explode(':', $v);
                    
                    // Feature Request #4266 - Allow joins with multiple keys
                    $links_key_array = strpos($k,',');
                    if ($links_key_array !== false) {
                        $k = explode(',', $k);
                    }
                    
                    $ar_array = strpos($ar[1],',');
                    if ($ar_array !== false) {
                        $ar[1] = explode(',', $ar[1]);
                    }
                 
                    if ($ar[0] != $this->tableName()) {
                        continue;
                    }
                    
                    // you have explictly specified the column
                    // and the col is listed here..
                    // not sure if 1:1 table could cause probs here..
                    
                    if ($joinCol !== false) {
                        $this->raiseError( 
                            "joinAdd: You cannot target a join column in the " .
                            "'link from' table ({$obj->tableName()}). " . 
                            "Either remove the fourth argument to joinAdd() ".
                            "({$joinCol}), or alter your links.ini file.",
                            DB_DATAOBJECT_ERROR_NODATA);
                        return false;
                    }
                
                    $ofield = $k;
                    $tfield = $ar[1];
                    break;
                    
                }
            }
        }

        // finally if these two table have column names that match do a join by default on them

        if (($ofield === false) && $joinCol) {
            $ofield = $joinCol;
            $tfield = $joinCol;

        }
        /* did I find a conneciton between them? */

        if ($ofield === false) {
            $this->raiseError(
                "joinAdd: {$obj->tableName()} has no link with {$this->tableName()}",
                DB_DATAOBJECT_ERROR_NODATA);
            return false;
        }
        $joinType = strtoupper($joinType);
        
        // we default to joining as the same name (this is remvoed later..)
        
        if ($joinAs === false) {
            $joinAs = $obj->tableName();
        }
        
        $quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
        $options = $_DB_DATAOBJECT['CONFIG'];
        
        // not sure  how portable adding database prefixes is..
        $objTable = $quoteIdentifiers ? 
                $DB->quoteIdentifier($obj->tableName()) : 
                 $obj->tableName() ;
                
        $dbPrefix  = '';
        if (strlen($obj->_database) && in_array($DB->dsn['phptype'],array('mysql','mysqli'))) {
            $dbPrefix = ($quoteIdentifiers
                         ? $DB->quoteIdentifier($obj->_database)
                         : $obj->_database) . '.';    
        }
        
        // if they are the same, then dont add a prefix...                
        if ($obj->_database == $this->_database) {
           $dbPrefix = '';
        }
        // as far as we know only mysql supports database prefixes..
        // prefixing the database name is now the default behaviour,
        // as it enables joining mutiple columns from multiple databases...
         
            // prefix database (quoted if neccessary..)
        $objTable = $dbPrefix . $objTable;
       
        $cond = '';

        // if obj only a dataobject - eg. no extended class has been defined..
        // it obvioulsy cant work out what child elements might exist...
        // until we get on the fly querying of tables..
        // note: we have already checked that it is_a(db_dataobject earlier)
        if ( strtolower(get_class($obj)) != 'db_dataobject') {
                 
            // now add where conditions for anything that is set in the object 
        
        
        
            $items = $obj->table();
            // will return an array if no items..
            
            // only fail if we where expecting it to work (eg. not joined on a array)
             
            if (!$items) {
                $this->raiseError(
                    "joinAdd: No table definition for {$obj->tableName()}", 
                    DB_DATAOBJECT_ERROR_INVALIDCONFIG);
                return false;
            }
            
            $ignore_null = !isset($options['disable_null_strings'])
                    || !is_string($options['disable_null_strings'])
                    || strtolower($options['disable_null_strings']) !== 'full' ;
            

            foreach($items as $k => $v) {
                if (!isset($obj->$k) && $ignore_null) {
                    continue;
                }
                
                $kSql = ($quoteIdentifiers ? $DB->quoteIdentifier($k) : $k);
                
                if (DB_DataObject::_is_null($obj,$k)) {
                	$obj->whereAdd("{$joinAs}.{$kSql} IS NULL");
                	continue;
                }
                
                if ($v & DB_DATAOBJECT_STR) {
                    $obj->whereAdd("{$joinAs}.{$kSql} = " . $this->_quote((string) (
                            ($v & DB_DATAOBJECT_BOOL) ? 
                                // this is thanks to the braindead idea of postgres to 
                                // use t/f for boolean.
                                (($obj->$k === 'f') ? 0 : (int)(bool) $obj->$k) :  
                                $obj->$k
                        )));
                    continue;
                }
                if (is_numeric($obj->$k)) {
                    $obj->whereAdd("{$joinAs}.{$kSql} = {$obj->$k}");
                    continue;
                }
                            
                if (is_object($obj->$k) && is_a($obj->$k,'DB_DataObject_Cast')) {
                    $value = $obj->$k->toString($v,$DB);
                    if (PEAR::isError($value)) {
                        $this->raiseError($value->getMessage() ,DB_DATAOBJECT_ERROR_INVALIDARG);
                        return false;
                    } 
                    $obj->whereAdd("{$joinAs}.{$kSql} = $value");
                    continue;
                }
                
                
                /* this is probably an error condition! */
                $obj->whereAdd("{$joinAs}.{$kSql} = 0");
            }
            if ($this->_query === false) {
                $this->raiseError(
                    "joinAdd can not be run from a object that has had a query run on it,
                    clone the object or create a new one and use setFrom()", 
                    DB_DATAOBJECT_ERROR_INVALIDARGS);
                return false;
            }
        }

        // and finally merge the whereAdd from the child..
        if ($obj->_query['condition']) {
            $cond = preg_replace('/^\sWHERE/i','',$obj->_query['condition']);

            if (!$useWhereAsOn) {
                $this->whereAdd($cond);
            }
        }
    
        
        
        
        // nested (join of joined objects..)
        $appendJoin = '';
        if ($obj->_join) {
            // postgres allows nested queries, with ()'s
            // not sure what the results are with other databases..
            // may be unpredictable..
            if (in_array($DB->dsn["phptype"],array('pgsql'))) {
                $objTable = "($objTable {$obj->_join})";
            } else {
                $appendJoin = $obj->_join;
            }
        }
        
  
        // fix for #2216
        // add the joinee object's conditions to the ON clause instead of the WHERE clause
        if ($useWhereAsOn && strlen($cond)) {
            $appendJoin = ' AND ' . $cond . ' ' . $appendJoin;
        }
               
        
        
        $table = $this->tableName();
        
        if ($quoteIdentifiers) {
            $joinAs   = $DB->quoteIdentifier($joinAs);
            $table    = $DB->quoteIdentifier($table);     
            $ofield   = (is_array($ofield)) ? array_map(array($DB, 'quoteIdentifier'), $ofield) : $DB->quoteIdentifier($ofield);
            $tfield   = (is_array($tfield)) ? array_map(array($DB, 'quoteIdentifier'), $tfield) : $DB->quoteIdentifier($tfield); 
        }
        // add database prefix if they are different databases
       
        
        $fullJoinAs = '';
        $addJoinAs  = ($quoteIdentifiers ? $DB->quoteIdentifier($obj->tableName()) : $obj->tableName()) != $joinAs;
        if ($addJoinAs) {
            // join table a AS b - is only supported by a few databases and is probably not needed
            // , however since it makes the whole Statement alot clearer we are leaving it in
            // for those databases.
            $fullJoinAs = in_array($DB->dsn["phptype"],array('mysql','mysqli','pgsql')) ? "AS {$joinAs}" :  $joinAs;
        } else {
            // if 
            $joinAs = $dbPrefix . $joinAs;
        }
        
        
        switch ($joinType) {
            case 'INNER':
            case 'LEFT': 
            case 'RIGHT': // others??? .. cross, left outer, right outer, natural..?
                
                // Feature Request #4266 - Allow joins with multiple keys
                $jadd = "\n {$joinType} JOIN {$objTable} {$fullJoinAs}";
                //$this->_join .= "\n {$joinType} JOIN {$objTable} {$fullJoinAs}";
                if (is_array($ofield)) {
                	$key_count = count($ofield);
                    for($i = 0; $i < $key_count; $i++) {
                    	if ($i == 0) {
                    		$jadd .= " ON ({$joinAs}.{$ofield[$i]}={$table}.{$tfield[$i]}) ";
                    	}
                    	else {
                    		$jadd .= " AND {$joinAs}.{$ofield[$i]}={$table}.{$tfield[$i]} ";
                    	}
                    }
                    $jadd .= ' ' . $appendJoin . ' ';
                } else {
	                $jadd .= " ON ({$joinAs}.{$ofield}={$table}.{$tfield}) {$appendJoin} ";
                }
                // jadd avaliable for debugging join build.
                //echo $jadd ."\n";
                $this->_join .= $jadd;
                break;
                
            case '': // this is just a standard multitable select..
                $this->_join .= "\n , {$objTable} {$fullJoinAs} {$appendJoin}";
                $this->whereAdd("{$joinAs}.{$ofield}={$table}.{$tfield}");
        }
         
         
        return true;

    }

    /**
     * autoJoin - using the links.ini file, it builds a query with all the joins 
     * usage: 
     * $x = DB_DataObject::factory('mytable');
     * $x->autoJoin();
     * $x->get(123); 
     *   will result in all of the joined data being added to the fetched object..
     * 
     * $x = DB_DataObject::factory('mytable');
     * $x->autoJoin();
     * $ar = $x->fetchAll();
     *   will result in an array containing all the data from the table, and any joined tables..
     * 
     * $x = DB_DataObject::factory('mytable');
     * $jdata = $x->autoJoin();
     * $x->selectAdd(); //reset..
     * foreach($_REQUEST['requested_cols'] as $c) {
     *    if (!isset($jdata[$c])) continue; // ignore columns not available..
     *    $x->selectAdd( $jdata[$c] . ' as ' . $c);
     * }
     * $ar = $x->fetchAll(); 
     *   will result in only the columns requested being fetched...
     *
     *
     *
     * @param     array     Configuration
     *          exclude  Array of columns to exclude from results (eg. modified_by_id)
     *          links    The equivilant links.ini data for this table eg.
     *                    array( 'person_id' => 'person:id', .... )
     *          include  Array of columns to include
     *          distinct Array of distinct columns.
     *          
     * @return   array      info about joins
     *                      cols => map of resulting {joined_tablename}.{joined_table_column_name}
     *                      join_names => map of resulting {join_name_as}.{joined_table_column_name}
     *                      count => the column to count on.
     * @access   public
     */
    function autoJoin($cfg = array())
    {
        global $_DB_DATAOBJECT;
        //var_Dump($cfg);exit;
        $pre_links = $this->links();
        if (!empty($cfg['links'])) {
            $this->links(array_merge( $pre_links , $cfg['links']));
        }
        $map = $this->links( );
        
        $this->databaseStructure();
        $dbstructure = $_DB_DATAOBJECT['INI'][$this->_database];
        //print_r($map);
        $tabdef = $this->table();
         
        // we need this as normally it's only cleared by an empty selectAs call.
       
        
        $keys = array_keys($tabdef);
        if (!empty($cfg['exclude'])) {
            $keys = array_intersect($keys, array_diff($keys, $cfg['exclude'])); 
        }
        if (!empty($cfg['include'])) {
            
            $keys =  array_intersect($keys,  $cfg['include']); 
        }
        
        $selectAs = array();
        
        if (!empty($keys)) {
            $selectAs = array(array( $keys , '%s', false));
        }
        
        $ret = array(
            'cols' => array(),
            'join_names' => array(),
            'count' => false,
        );
        
        
        
        $has_distinct = false;
        if (!empty($cfg['distinct']) && $keys) {
            
            // reset the columsn?
            $cols = array();
            
             //echo '
' ;print_r($xx);exit;
            foreach($keys as $c) {
                //var_dump($c);
                
                if (  $cfg['distinct'] == $c) {
                    $has_distinct = 'DISTINCT( ' . $this->tableName() .'.'. $c .') as ' . $c;
                    $ret['count'] =  'DISTINCT  ' . $this->tableName() .'.'. $c .'';
                    continue;
                }
                // cols is in our filtered keys...
                $cols = $c;
                
            }
            // apply our filtered version, which excludes the distinct column.
            
            $selectAs = empty($cols) ?  array() : array(array(array(  $cols) , '%s', false)) ;
            
            
            
        } 
                
        foreach($keys as $k) {
            $ret['cols'][$k] = $this->tableName(). '.' . $k;
        }
        
         
        
        foreach($map as $ocl=>$info) {
            
            list($tab,$col) = explode(':', $info);
            // what about multiple joins on the same table!!!
            
            // if links point to a table that does not exist - ignore.
            if (!isset($dbstructure[$tab])) {
                continue;
            }
            
            $xx = DB_DataObject::factory($tab);
            if (!is_object($xx) || !is_a($xx, 'DB_DataObject')) {
                continue;
            }
            // skip columns that are excluded.
            
            // we ignore include here... - as
             
            // this is borked ... for multiple jions..
            $this->joinAdd($xx, 'LEFT', 'join_'.$ocl.'_'. $col, $ocl);
            
            if (!empty($cfg['exclude']) && in_array($ocl, $cfg['exclude'])) {
                continue;
            }
            
            $tabdef = $xx->table();
            $table = $xx->tableName();
            
            $keys = array_keys($tabdef);
            
            
            if (!empty($cfg['exclude'])) {
                $keys = array_intersect($keys, array_diff($keys, $cfg['exclude']));
                
                foreach($keys as $k) {
                    if (in_array($ocl.'_'.$k, $cfg['exclude'])) {
                        $keys = array_diff($keys, $k); // removes the k..
                    }
                }
                
            }
            
            if (!empty($cfg['include'])) {
                // include will basically be BASECOLNAME_joinedcolname
                $nkeys = array();
                foreach($keys as $k) {
                    if (in_array( sprintf($ocl.'_%s', $k), $cfg['include'])) {
                        $nkeys[] = $k;
                    }
                }
                $keys = $nkeys;
            }
            
            if (empty($keys)) {
                continue;
            }
            // got distinct, and not yet found it..
            if (!$has_distinct && !empty($cfg['distinct']))  {
                $cols = array();
                foreach($keys as $c) {
                    $tn = sprintf($ocl.'_%s', $c);
                      
                    if ( $tn == $cfg['distinct']) {
                        
                        $has_distinct = 'DISTINCT( ' . 'join_'.$ocl.'_'.$col.'.'.$c .')  as ' . $tn ;
                        $ret['count'] =  'DISTINCT  join_'.$ocl.'_'.$col.'.'.$c;
                       // var_dump($this->countWhat );
                        continue;
                    }
                    $cols[] = $c;
                     
                }
                
                if (!empty($cols)) {
                    $selectAs[] = array($cols, $ocl.'_%s', 'join_'.$ocl.'_'. $col);
                }
                
            } else {
                $selectAs[] = array($keys, $ocl.'_%s', 'join_'.$ocl.'_'. $col);
            }
              
            foreach($keys as $k) {
                $ret['cols'][sprintf('%s_%s', $ocl, $k)] = $tab.'.'.$k;
                $ret['join_names'][sprintf('%s_%s', $ocl, $k)] = sprintf('join_%s_%s.%s',$ocl, $col, $k);
            }
             
        }
        
        // fill in the select details..
        $this->selectAdd(); 
        
        if ($has_distinct) {
            $this->selectAdd($has_distinct);
        }
       
        foreach($selectAs as $ar) {            
            $this->selectAs($ar[0], $ar[1], $ar[2]);
        }
        // restore links..
        $this->links( $pre_links );
        
        return $ret;
        
    }
    
    /**
     * Factory method for calling DB_DataObject_Cast
     *
     * if used with 1 argument DB_DataObject_Cast::sql($value) is called
     * 
     * if used with 2 arguments DB_DataObject_Cast::$value($callvalue) is called
     * valid first arguments are: blob, string, date, sql
     * 
     * eg. $member->updated = $member->sqlValue('NOW()');
     * 
     * 
     * might handle more arguments for escaping later...
     * 
     *
     * @param string $value (or type if used with 2 arguments)
     * @param string $callvalue (optional) used with date/null etc..
     */
    
    function sqlValue($value)
    {
        $method = 'sql';
        if (func_num_args() == 2) {
            $method = $value;
            $value = func_get_arg(1);
        }
        require_once 'DB/DataObject/Cast.php';
        return call_user_func(array('DB_DataObject_Cast', $method), $value);
        
    }
    
    
    /**
     * Copies items that are in the table definitions from an
     * array or object into the current object
     * will not override key values.
     *
     *
     * @param    array | object  $from
     * @param    string  $format eg. map xxxx_name to $object->name using 'xxxx_%s' (defaults to %s - eg. name -> $object->name
     * @param    boolean  $skipEmpty (dont assign empty values if a column is empty (eg. '' / 0 etc...)
     * @access   public
     * @return   true on success or array of key=>setValue error message
     */
    function setFrom($from, $format = '%s', $skipEmpty=false)
    {
        global $_DB_DATAOBJECT;
        $keys  = $this->keys();
        $items = $this->table();
        
        if (!$items) {
            $this->raiseError(
                "setFrom:Could not find table definition for {$this->tableName()}", 
                DB_DATAOBJECT_ERROR_INVALIDCONFIG);
            return;
        }
        $overload_return = array();
        foreach (array_keys($items) as $k) {
            if (in_array($k,$keys)) {
                continue; // dont overwrite keys
            }
            if (!$k) {
                continue; // ignore empty keys!!! what
            }
            
            $chk = is_object($from) &&  
                (version_compare(phpversion(), "5.1.0" , ">=") ? 
                    property_exists($from, sprintf($format,$k)) :  // php5.1
                    array_key_exists( sprintf($format,$k), get_class_vars($from)) //older
                );
            // if from has property ($format($k)      
            if ($chk) {
                $kk = (strtolower($k) == 'from') ? '_from' : $k;
                if (method_exists($this,'set'.$kk)) {
                    $ret = $this->{'set'.$kk}($from->{sprintf($format,$k)});
                    if (is_string($ret)) {
                        $overload_return[$k] = $ret;
                    }
                    continue;
                }
                $this->$k = $from->{sprintf($format,$k)};
                continue;
            }
            
            if (is_object($from)) {
                continue;
            }
            
            if (empty($from[sprintf($format,$k)]) && $skipEmpty) {
                continue;
            }
            
            if (!isset($from[sprintf($format,$k)]) && !DB_DataObject::_is_null($from, sprintf($format,$k))) {
                continue;
            }
           
            $kk = (strtolower($k) == 'from') ? '_from' : $k;
            if (method_exists($this,'set'. $kk)) {
                $ret =  $this->{'set'.$kk}($from[sprintf($format,$k)]);
                if (is_string($ret)) {
                    $overload_return[$k] = $ret;
                }
                continue;
            }
            $val = $from[sprintf($format,$k)];
            if (is_a($val, 'DB_DataObject_Cast')) {
                $this->$k = $val;
                continue;
            }
            if (is_object($val) || is_array($val)) {
                continue;
            }
            $ret = $this->fromValue($k,$val);
            if ($ret !== true)  {
                $overload_return[$k] = 'Not A Valid Value';
            }
            //$this->$k = $from[sprintf($format,$k)];
        }
        if ($overload_return) {
            return $overload_return;
        }
        return true;
    }

    /**
     * Returns an associative array from the current data
     * (kind of oblivates the idea behind DataObjects, but
     * is usefull if you use it with things like QuickForms.
     *
     * you can use the format to return things like user[key]
     * by sending it $object->toArray('user[%s]')
     *
     * will also return links converted to arrays.
     *
     * @param   string  sprintf format for array
     * @param   bool||number    [true = elemnts that have a value set],
     *                          [false = table + returned colums] ,
     *                          [0 = returned columsn only]
     *
     * @access   public
     * @return   array of key => value for row
     */

    function toArray($format = '%s', $hideEmpty = false) 
    {
        global $_DB_DATAOBJECT;
        
        // we use false to ignore sprintf.. (speed up..)
        $format = $format == '%s' ? false : $format;
        
        $ret = array();
        $rf = ($this->_resultFields !== false) ? $this->_resultFields : 
                (isset($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid]) ?
                 $_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid] : false);
        
        $ar = ($rf !== false) ?
            (($hideEmpty === 0) ? $rf : array_merge($rf, $this->table())) :
            $this->table();

        foreach($ar as $k=>$v) {
             
            if (!isset($this->$k)) {
                if (!$hideEmpty) {
                    $ret[$format === false ? $k : sprintf($format,$k)] = '';
                }
                continue;
            }
            // call the overloaded getXXXX() method. - except getLink and getLinks
            if (method_exists($this,'get'.$k) && !in_array(strtolower($k),array('links','link'))) {
                $ret[$format === false ? $k : sprintf($format,$k)] = $this->{'get'.$k}();
                continue;
            }
            // should this call toValue() ???
            $ret[$format === false ? $k : sprintf($format,$k)] = $this->$k;
        }
        if (!$this->_link_loaded) {
            return $ret;
        }
        foreach($this->_link_loaded as $k) {
            $ret[$format === false ? $k : sprintf($format,$k)] = $this->$k->toArray();
        
        }
        
        return $ret;
    }

    /**
     * validate the values of the object (usually prior to inserting/updating..)
     *
     * Note: This was always intended as a simple validation routine.
     * It lacks understanding of field length, whether you are inserting or updating (and hence null key values)
     *
     * This should be moved to another class: DB_DataObject_Validate 
     *      FEEL FREE TO SEND ME YOUR VERSION FOR CONSIDERATION!!!
     *
     * Usage:
     * if (is_array($ret = $obj->validate())) { ... there are problems with the data ... }
     *
     * Logic:
     *   - defaults to only testing strings/numbers if numbers or strings are the correct type and null values are correct
     *   - validate Column methods : "validate{ROWNAME}()"  are called if they are defined.
     *            These methods should return 
     *                  true = everything ok
     *                  false|object = something is wrong!
     * 
     *   - This method loads and uses the PEAR Validate Class.
     *
     *
     * @access  public
     * @return  array of validation results (where key=>value, value=false|object if it failed) or true (if they all succeeded)
     */
    function validate()
    {
        global $_DB_DATAOBJECT;
        require_once 'Validate.php';
        $table = $this->table();
        $ret   = array();
        $seq   = $this->sequenceKey();
        $options = $_DB_DATAOBJECT['CONFIG'];
        foreach($table as $key => $val) {
            
            
            // call user defined validation always...
            $method = "Validate" . ucfirst($key);
            if (method_exists($this, $method)) {
                $ret[$key] = $this->$method();
                continue;
            }
            
            // if not null - and it's not set.......
            
            if ($val & DB_DATAOBJECT_NOTNULL && DB_DataObject::_is_null($this, $key)) {
                // dont check empty sequence key values..
                if (($key == $seq[0]) && ($seq[1] == true)) {
                    continue;
                }
                $ret[$key] = false;
                continue;
            }
            
            
             if (DB_DataObject::_is_null($this, $key)) {
                if ($val & DB_DATAOBJECT_NOTNULL) {
                    $this->debug("'null' field used for '$key', but it is defined as NOT NULL", 'VALIDATION', 4);
                    $ret[$key] = false;
                    continue;
                }
                continue;
            }

            // ignore things that are not set. ?
           
            if (!isset($this->$key)) {
                continue;
            }
            
            // if the string is empty.. assume it is ok..
            if (!is_object($this->$key) && !is_array($this->$key) && !strlen((string) $this->$key)) {
                continue;
            }
            
            // dont try and validate cast objects - assume they are problably ok..
            if (is_object($this->$key) && is_a($this->$key,'DB_DataObject_Cast')) {
                continue;
            }
            // at this point if you have set something to an object, and it's not expected
            // the Validate will probably break!!... - rightly so! (your design is broken, 
            // so issuing a runtime error like PEAR_Error is probably not appropriate..
            
            switch (true) {
                // todo: date time.....
                case  ($val & DB_DATAOBJECT_STR):
                    $ret[$key] = Validate::string($this->$key, VALIDATE_PUNCTUATION . VALIDATE_NAME);
                    continue;
                case  ($val & DB_DATAOBJECT_INT):
                    $ret[$key] = Validate::number($this->$key, array('decimal'=>'.'));
                    continue;
            }
        }
        // if any of the results are false or an object (eg. PEAR_Error).. then return the array..
        foreach ($ret as $key => $val) {
            if ($val !== true) {
                return $ret;
            }
        }
        return true; // everything is OK.
    }

    /**
     * Gets the DB object related to an object - so you can use funky peardb stuf with it :)
     *
     * @access public
     * @return object The DB connection
     */
    function getDatabaseConnection()
    {
        global $_DB_DATAOBJECT;

        if (($e = $this->_connect()) !== true) {
            return $e;
        }
        if (!isset($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
            $r = false;
            return $r;
        }
        return $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
    }
 
 
    /**
     * Gets the DB result object related to the objects active query
     *  - so you can use funky pear stuff with it - like pager for example.. :)
     *
     * @access public
     * @return object The DB result object
     */
     
    function getDatabaseResult()
    {
        global $_DB_DATAOBJECT;
        $this->_connect();
        if (!isset($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid])) {
            $r = false;
            return $r;
        }
        return $_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid];
    }

    /**
     * Overload Extension support
     *  - enables setCOLNAME/getCOLNAME
     *  if you define a set/get method for the item it will be called.
     * otherwise it will just return/set the value.
     * NOTE this currently means that a few Names are NO-NO's 
     * eg. links,link,linksarray, from, Databaseconnection,databaseresult
     *
     * note 
     *  - set is automatically called by setFrom.
     *   - get is automatically called by toArray()
     *  
     * setters return true on success. = strings on failure
     * getters return the value!
     *
     * this fires off trigger_error - if any problems.. pear_error, 
     * has problems with 4.3.2RC2 here
     *
     * @access public
     * @return true?
     * @see overload
     */

    
    function _call($method,$params,&$return) {
        
        //$this->debug("ATTEMPTING OVERLOAD? $method");
        // ignore constructors : - mm
        if (strtolower($method) == strtolower(get_class($this))) {
            return true;
        }
        $type = strtolower(substr($method,0,3));
        $class = get_class($this);
        if (($type != 'set') && ($type != 'get')) {
            return false;
        }
         
        
        
        // deal with naming conflick of setFrom = this is messy ATM!
        
        if (strtolower($method) == 'set_from') {
            $return = $this->toValue('from',isset($params[0]) ? $params[0] : null);
            return  true;
        }
        
        $element = substr($method,3);
        
        // dont you just love php's case insensitivity!!!!
        
        $array =  array_keys(get_class_vars($class));
        /* php5 version which segfaults on 5.0.3 */
        if (class_exists('ReflectionClass')) {
            $reflection = new ReflectionClass($class);
            $array = array_keys($reflection->getdefaultProperties());
        }
        
        if (!in_array($element,$array)) {
            // munge case
            foreach($array as $k) {
                $case[strtolower($k)] = $k;
            }
            if ((substr(phpversion(),0,1) == 5) && isset($case[strtolower($element)])) {
                trigger_error("PHP5 set/get calls should match the case of the variable",E_USER_WARNING);
                $element = strtolower($element);
            }
            
            // does it really exist?
            if (!isset($case[$element])) {
                return false;            
            }
            // use the mundged case
            $element = $case[$element]; // real case !
        }
        
        
        if ($type == 'get') {
            $return = $this->toValue($element,isset($params[0]) ? $params[0] : null);
            return true;
        }
        
        
        $return = $this->fromValue($element, $params[0]);
         
        return true;
            
          
    }
        
    
    /**
    * standard set* implementation.
    *
    * takes data and uses it to set dates/strings etc.
    * normally called from __call..  
    *
    * Current supports
    *   date      = using (standard time format, or unixtimestamp).... so you could create a method :
    *               function setLastread($string) { $this->fromValue('lastread',strtotime($string)); }
    *
    *   time      = using strtotime 
    *   datetime  = using  same as date - accepts iso standard or unixtimestamp.
    *   string    = typecast only..
    * 
    * TODO: add formater:: eg. d/m/Y for date! ???
    *
    * @param   string       column of database
    * @param   mixed        value to assign
    *
    * @return   true| false     (False on error)
    * @access   public 
    * @see      DB_DataObject::_call
    */
  
    
    function fromValue($col,$value) 
    {
        global $_DB_DATAOBJECT;
        $options = $_DB_DATAOBJECT['CONFIG'];
        $cols = $this->table();
        // dont know anything about this col..
        if (!isset($cols[$col]) || is_a($value, 'DB_DataObject_Cast')) {
            $this->$col = $value;
            return true;
        }
        //echo "FROM VALUE $col, {$cols[$col]}, $value\n";
        switch (true) {
            // set to null and column is can be null...
            case ((!($cols[$col] & DB_DATAOBJECT_NOTNULL)) && DB_DataObject::_is_null($value, false)):
            case (is_object($value) && is_a($value,'DB_DataObject_Cast')): 
                $this->$col = $value;
                return true;
                
            // fail on setting null on a not null field..
            case (($cols[$col] & DB_DATAOBJECT_NOTNULL) && DB_DataObject::_is_null($value,false)):

                return false;
        
            case (($cols[$col] & DB_DATAOBJECT_DATE) &&  ($cols[$col] & DB_DATAOBJECT_TIME)):
                // empty values get set to '' (which is inserted/updated as NULl
                if (!$value) {
                    $this->$col = '';
                }
            
                if (is_numeric($value)) {
                    $this->$col = date('Y-m-d H:i:s', $value);
                    return true;
                }
              
                // eak... - no way to validate date time otherwise...
                $this->$col = (string) $value;
                return true;
            
            case ($cols[$col] & DB_DATAOBJECT_DATE):
                // empty values get set to '' (which is inserted/updated as NULl
                 
                if (!$value) {
                    $this->$col = '';
                    return true; 
                }
            
                if (is_numeric($value)) {
                    $this->$col = date('Y-m-d',$value);
                    return true;
                }
                 
                // try date!!!!
                require_once 'Date.php';
                $x = new Date($value);
                $this->$col = $x->format("%Y-%m-%d");
                return true;
            
            case ($cols[$col] & DB_DATAOBJECT_TIME):
                // empty values get set to '' (which is inserted/updated as NULl
                if (!$value) {
                    $this->$col = '';
                }
            
                $guess = strtotime($value);
                if ($guess != -1) {
                     $this->$col = date('H:i:s', $guess);
                    return $return = true;
                }
                // otherwise an error in type...
                return false;
            
            case ($cols[$col] & DB_DATAOBJECT_STR):
                
                $this->$col = (string) $value;
                return true;
                
            // todo : floats numerics and ints...
            default:
                $this->$col = $value;
                return true;
        }
    
    
    
    }
     /**
    * standard get* implementation.
    *
    *  with formaters..
    * supported formaters:  
    *   date/time : %d/%m/%Y (eg. php strftime) or pear::Date 
    *   numbers   : %02d (eg. sprintf)
    *  NOTE you will get unexpected results with times like 0000-00-00 !!!
    *
    *
    * 
    * @param   string       column of database
    * @param   format       foramt
    *
    * @return   true     Description
    * @access   public 
    * @see      DB_DataObject::_call(),strftime(),Date::format()
    */
    function toValue($col,$format = null) 
    {
        if (is_null($format)) {
            return $this->$col;
        }
        $cols = $this->table();
        switch (true) {
            case (($cols[$col] & DB_DATAOBJECT_DATE) &&  ($cols[$col] & DB_DATAOBJECT_TIME)):
                if (!$this->$col) {
                    return '';
                }
                $guess = strtotime($this->$col);
                if ($guess != -1) {
                    return strftime($format, $guess);
                }
                // eak... - no way to validate date time otherwise...
                return $this->$col;
            case ($cols[$col] & DB_DATAOBJECT_DATE):
                if (!$this->$col) {
                    return '';
                } 
                $guess = strtotime($this->$col);
                if ($guess != -1) {
                    return strftime($format,$guess);
                }
                // try date!!!!
                require_once 'Date.php';
                $x = new Date($this->$col);
                return $x->format($format);
                
            case ($cols[$col] & DB_DATAOBJECT_TIME):
                if (!$this->$col) {
                    return '';
                }
                $guess = strtotime($this->$col);
                if ($guess > -1) {
                    return strftime($format, $guess);
                }
                // otherwise an error in type...
                return $this->$col;
                
            case ($cols[$col] &  DB_DATAOBJECT_MYSQLTIMESTAMP):
                if (!$this->$col) {
                    return '';
                }
                require_once 'Date.php';
                
                $x = new Date($this->$col);
                
                return $x->format($format);
            
             
            case ($cols[$col] &  DB_DATAOBJECT_BOOL):
                
                if ($cols[$col] &  DB_DATAOBJECT_STR) {
                    // it's a 't'/'f' !
                    return ($this->$col === 't');
                }
                return (bool) $this->$col;
            
               
            default:
                return sprintf($format,$this->col);
        }
            

    }
    
    
    /* ----------------------- Debugger ------------------ */

    /**
     * Debugger. - use this in your extended classes to output debugging information.
     *
     * Uses DB_DataObject::DebugLevel(x) to turn it on
     *
     * @param    string $message - message to output
     * @param    string $logtype - bold at start
     * @param    string $level   - output level
     * @access   public
     * @return   none
     */
    function debug($message, $logtype = 0, $level = 1)
    {
        global $_DB_DATAOBJECT;

        if (empty($_DB_DATAOBJECT['CONFIG']['debug'])  || 
            (is_numeric($_DB_DATAOBJECT['CONFIG']['debug']) &&  $_DB_DATAOBJECT['CONFIG']['debug'] < $level)) {
            return;
        }
        // this is a bit flaky due to php's wonderfull class passing around crap..
        // but it's about as good as it gets..
        $class = (isset($this) && is_a($this,'DB_DataObject')) ? get_class($this) : 'DB_DataObject';
        
        if (!is_string($message)) {
            $message = print_r($message,true);
        }
        if (!is_numeric( $_DB_DATAOBJECT['CONFIG']['debug']) && is_callable( $_DB_DATAOBJECT['CONFIG']['debug'])) {
            return call_user_func($_DB_DATAOBJECT['CONFIG']['debug'], $class, $message, $logtype, $level);
        }
        
        if (!ini_get('html_errors')) {
            echo "$class   : $logtype       : $message\n";
            flush();
            return;
        }
        if (!is_string($message)) {
            $message = print_r($message,true);
        }
        $colorize = ($logtype == 'ERROR') ? '' : '';
        echo "{$colorize}$class: $logtype: ". nl2br(htmlspecialchars($message)) . "
\n"; } /** * sets and returns debug level * eg. DB_DataObject::debugLevel(4); * * @param int $v level * @access public * @return none */ static function debugLevel($v = null) { global $_DB_DATAOBJECT; if (empty($_DB_DATAOBJECT['CONFIG'])) { DB_DataObject::_loadConfig(); } if ($v !== null) { $r = isset($_DB_DATAOBJECT['CONFIG']['debug']) ? $_DB_DATAOBJECT['CONFIG']['debug'] : 0; $_DB_DATAOBJECT['CONFIG']['debug'] = $v; return $r; } return isset($_DB_DATAOBJECT['CONFIG']['debug']) ? $_DB_DATAOBJECT['CONFIG']['debug'] : 0; } /** * Last Error that has occured * - use $this->_lastError or * $last_error = PEAR::getStaticProperty('DB_DataObject','lastError'); * * @access public * @var object PEAR_Error (or false) */ var $_lastError = false; /** * Default error handling is to create a pear error, but never return it. * if you need to handle errors you should look at setting the PEAR_Error callback * this is due to the fact it would wreck havoc on the internal methods! * * @param int $message message * @param int $type type * @param int $behaviour behaviour (die or continue!); * @access public * @return error object */ function raiseError($message, $type = null, $behaviour = null) { global $_DB_DATAOBJECT; if ($behaviour == PEAR_ERROR_DIE && !empty($_DB_DATAOBJECT['CONFIG']['dont_die'])) { $behaviour = null; } $error = &PEAR::getStaticProperty('DB_DataObject','lastError'); // no checks for production here?....... - we log errors before we throw them. DB_DataObject::debug($message,'ERROR',1); if (PEAR::isError($message)) { $error = $message; } else { require_once 'DB/DataObject/Error.php'; $dor = new PEAR(); $error = $dor->raiseError($message, $type, $behaviour, $opts=null, $userinfo=null, 'DB_DataObject_Error' ); } // this will never work totally with PHP's object model. // as this is passed on static calls (like staticGet in our case) $_DB_DATAOBJECT['LASTERROR'] = $error; if (isset($this) && is_object($this) && is_subclass_of($this,'db_dataobject')) { $this->_lastError = $error; } return $error; } /** * Define the global $_DB_DATAOBJECT['CONFIG'] as an alias to PEAR::getStaticProperty('DB_DataObject','options'); * * After Profiling DB_DataObject, I discoved that the debug calls where taking * considerable time (well 0.1 ms), so this should stop those calls happening. as * all calls to debug are wrapped with direct variable queries rather than actually calling the funciton * THIS STILL NEEDS FURTHER INVESTIGATION * * @access public * @return object an error object */ function _loadConfig() { global $_DB_DATAOBJECT; $_DB_DATAOBJECT['CONFIG'] = &PEAR::getStaticProperty('DB_DataObject','options'); } /** * Free global arrays associated with this object. * * * @access public * @return none */ function free() { global $_DB_DATAOBJECT; if (isset($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid])) { unset($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid]); } if (isset($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid])) { unset($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid]); } // clear the staticGet cache as well. $this->_clear_cache(); // this is a huge bug in DB! if (isset($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) { $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->num_rows = array(); } if (is_array($this->_link_loaded)) { foreach ($this->_link_loaded as $do) { if ( !empty($this->{$do}) && is_object($this->{$do}) && method_exists($this->{$do}, 'free') ) { $this->{$do}->free(); } } } } /** * Evaluate whether or not a value is set to null, taking the 'disable_null_strings' option into account. * If the value is a string set to "null" and the "disable_null_strings" option is not set to * true, then the value is considered to be null. * If the value is actually a PHP NULL value, and "disable_null_strings" has been set to * the value "full", then it will also be considered null. - this can not differenticate between not set * * * @param object|array $obj_or_ar * @param string|false $prop prperty * @access private * @return bool object */ function _is_null($obj_or_ar , $prop) { global $_DB_DATAOBJECT; $isset = $prop === false ? isset($obj_or_ar) : (is_array($obj_or_ar) ? isset($obj_or_ar[$prop]) : isset($obj_or_ar->$prop)); $value = $isset ? ($prop === false ? $obj_or_ar : (is_array($obj_or_ar) ? $obj_or_ar[$prop] : $obj_or_ar->$prop)) : null; $options = $_DB_DATAOBJECT['CONFIG']; $null_strings = !isset($options['disable_null_strings']) || $options['disable_null_strings'] === false; $crazy_null = isset($options['disable_null_strings']) && is_string($options['disable_null_strings']) && strtolower($options['disable_null_strings'] === 'full'); if ( $null_strings && $isset && is_string($value) && (strtolower($value) === 'null') ) { return true; } if ( $crazy_null && !$isset ) { return true; } return false; } /** * (deprecated - use ::get / and your own caching method) */ static function staticGet($class, $k, $v = null) { $lclass = strtolower($class); global $_DB_DATAOBJECT; if (empty($_DB_DATAOBJECT['CONFIG'])) { DB_DataObject::_loadConfig(); } $key = "$k:$v"; if ($v === null) { $key = $k; } if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { DB_DataObject::debug("$class $key","STATIC GET - TRY CACHE"); } if (!empty($_DB_DATAOBJECT['CACHE'][$lclass][$key])) { return $_DB_DATAOBJECT['CACHE'][$lclass][$key]; } if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { DB_DataObject::debug("$class $key","STATIC GET - NOT IN CACHE"); } $obj = DB_DataObject::factory(substr($class,strlen($_DB_DATAOBJECT['CONFIG']['class_prefix']))); if (PEAR::isError($obj)) { $dor = new DB_DataObject(); $dor->raiseError("could not autoload $class", DB_DATAOBJECT_ERROR_NOCLASS); $r = false; return $r; } if (!isset($_DB_DATAOBJECT['CACHE'][$lclass])) { $_DB_DATAOBJECT['CACHE'][$lclass] = array(); } if (!$obj->get($k,$v)) { $dor = new DB_DataObject(); $dor->raiseError("No Data return from get $k $v", DB_DATAOBJECT_ERROR_NODATA); $r = false; return $r; } $_DB_DATAOBJECT['CACHE'][$lclass][$key] = $obj; return $_DB_DATAOBJECT['CACHE'][$lclass][$key]; } /** * autoload Class relating to a table * (deprecited - use ::factory) * * @param string $table table * @access private * @return string classname on Success */ function staticAutoloadTable($table) { global $_DB_DATAOBJECT; if (empty($_DB_DATAOBJECT['CONFIG'])) { DB_DataObject::_loadConfig(); } $p = isset($_DB_DATAOBJECT['CONFIG']['class_prefix']) ? $_DB_DATAOBJECT['CONFIG']['class_prefix'] : ''; $class = $p . preg_replace('/[^A-Z0-9]/i','_',ucfirst($table)); $ce = substr(phpversion(),0,1) > 4 ? class_exists($class,false) : class_exists($class); $class = $ce ? $class : DB_DataObject::_autoloadClass($class); return $class; } /* ---- LEGACY BC METHODS - NOT DOCUMENTED - See Documentation on New Methods. ---*/ function _get_table() { return $this->table(); } function _get_keys() { return $this->keys(); } } // technially 4.3.2RC1 was broken!! // looks like 4.3.3 may have problems too.... if (!defined('DB_DATAOBJECT_NO_OVERLOAD')) { if ((phpversion() != '4.3.2-RC1') && (version_compare( phpversion(), "4.3.1") > 0)) { if (version_compare( phpversion(), "5") < 0) { overload('DB_DataObject'); } $GLOBALS['_DB_DATAOBJECT']['OVERLOADED'] = true; } } php-db-dataobject-1.11.5/DB_DataObject-1.11.5/DB/DataObject/000077500000000000000000000000001277637267000224435ustar00rootroot00000000000000php-db-dataobject-1.11.5/DB_DataObject-1.11.5/DB/DataObject/Cast.php000077500000000000000000000360551277637267000240620ustar00rootroot00000000000000 * @copyright 1997-2008 The PHP Group * @license http://www.php.net/license/3_01.txt PHP License 3.01 * @version CVS: $Id: Cast.php 287158 2009-08-12 13:58:31Z alan_k $ * @link http://pear.php.net/package/DB_DataObject */ /** * * Common usages: * // blobs * $data = DB_DataObject_Cast::blob($somefile); * $data = DB_DataObject_Cast::string($somefile); * $dataObject->someblobfield = $data * * // dates? * $d1 = new DB_DataObject_Cast::date('12/12/2000'); * $d2 = new DB_DataObject_Cast::date(2000,12,30); * $d3 = new DB_DataObject_Cast::date($d1->year, $d1->month+30, $d1->day+30); * * // time, datetime.. ????????? * * // raw sql???? * $data = DB_DataObject_Cast::sql('cast("123123",datetime)'); * $data = DB_DataObject_Cast::sql('NULL'); * * // int's/string etc. are proably pretty pointless..!!!! * * * inside DB_DataObject, * if (is_a($v,'db_dataobject_class')) { * $value .= $v->toString(DB_DATAOBJECT_INT,'mysql'); * } * * * * */ class DB_DataObject_Cast { /** * Type of data Stored in the object.. * * @var string (date|blob|.....?) * @access public */ var $type; /** * Data For date representation * * @var int day/month/year * @access public */ var $day; var $month; var $year; /** * Generic Data.. * * @var string * @access public */ var $value; /** * Blob consructor * * create a Cast object from some raw data.. (binary) * * * @param string (with binary data!) * * @return object DB_DataObject_Cast * @access public */ function blob($value) { $r = new DB_DataObject_Cast; $r->type = 'blob'; $r->value = $value; return $r; } /** * String consructor (actually use if for ints and everything else!!! * * create a Cast object from some string (not binary) * * * @param string (with binary data!) * * @return object DB_DataObject_Cast * @access public */ function string($value) { $r = new DB_DataObject_Cast; $r->type = 'string'; $r->value = $value; return $r; } /** * SQL constructor (for raw SQL insert) * * create a Cast object from some sql * * @param string (with binary data!) * * @return object DB_DataObject_Cast * @access public */ function sql($value) { $r = new DB_DataObject_Cast; $r->type = 'sql'; $r->value = $value; return $r; } /** * Date Constructor * * create a Cast object from some string (not binary) * NO VALIDATION DONE, although some crappy re-calcing done! * * @param vargs... accepts * dd/mm * dd/mm/yyyy * yyyy-mm * yyyy-mm-dd * array(yyyy,dd) * array(yyyy,dd,mm) * * * * @return object DB_DataObject_Cast * @access public */ function date() { $args = func_get_args(); switch(count($args)) { case 0: // no args = today! $bits = explode('-',date('Y-m-d')); break; case 1: // one arg = a string if (strpos($args[0],'/') !== false) { $bits = array_reverse(explode('/',$args[0])); } else { $bits = explode('-',$args[0]); } break; default: // 2 or more.. $bits = $args; } if (count($bits) == 1) { // if YYYY set day = 1st.. $bits[] = 1; } if (count($bits) == 2) { // if YYYY-DD set day = 1st.. $bits[] = 1; } // if year < 1970 we cant use system tools to check it... // so we make a few best gueses.... // basically do date calculations for the year 2000!!! // fix me if anyone has more time... if (($bits[0] < 1975) || ($bits[0] > 2030)) { $oldyear = $bits[0]; $bits = explode('-',date('Y-m-d',mktime(1,1,1,$bits[1],$bits[2],2000))); $bits[0] = ($bits[0] - 2000) + $oldyear; } else { // now mktime $bits = explode('-',date('Y-m-d',mktime(1,1,1,$bits[1],$bits[2],$bits[0]))); } $r = new DB_DataObject_Cast; $r->type = 'date'; list($r->year,$r->month,$r->day) = $bits; return $r; } /** * Data For time representation ** does not handle timezones!! * * @var int hour/minute/second * @access public */ var $hour; var $minute; var $second; /** * DateTime Constructor * * create a Cast object from a Date/Time * Maybe should accept a Date object.! * NO VALIDATION DONE, although some crappy re-calcing done! * * @param vargs... accepts * noargs (now) * yyyy-mm-dd HH:MM:SS (Iso) * array(yyyy,mm,dd,HH,MM,SS) * * * @return object DB_DataObject_Cast * @access public * @author therion 5 at hotmail */ function dateTime() { $args = func_get_args(); switch(count($args)) { case 0: // no args = now! $datetime = date('Y-m-d G:i:s', mktime()); case 1: // continue on from 0 args. if (!isset($datetime)) { $datetime = $args[0]; } $parts = explode(' ', $datetime); $bits = explode('-', $parts[0]); $bits = array_merge($bits, explode(':', $parts[1])); break; default: // 2 or more.. $bits = $args; } if (count($bits) != 6) { // PEAR ERROR? return false; } $r = DB_DataObject_Cast::date($bits[0], $bits[1], $bits[2]); if (!$r) { return $r; // pass thru error (False) - doesnt happen at present! } // change the type! $r->type = 'datetime'; // should we mathematically sort this out.. // (or just assume that no-one's dumb enough to enter 26:90:90 as a time! $r->hour = $bits[3]; $r->minute = $bits[4]; $r->second = $bits[5]; return $r; } /** * time Constructor * * create a Cast object from a Date/Time * Maybe should accept a Date object.! * NO VALIDATION DONE, and no-recalcing done! * * @param vargs... accepts * noargs (now) * HH:MM:SS (Iso) * array(HH,MM,SS) * * * @return object DB_DataObject_Cast * @access public * @author therion 5 at hotmail */ function time() { $args = func_get_args(); switch (count($args)) { case 0: // no args = now! $time = date('G:i:s', mktime()); case 1: // continue on from 0 args. if (!isset($time)) { $time = $args[0]; } $bits = explode(':', $time); break; default: // 2 or more.. $bits = $args; } if (count($bits) != 3) { return false; } // now take data from bits into object fields $r = new DB_DataObject_Cast; $r->type = 'time'; $r->hour = $bits[0]; $r->minute = $bits[1]; $r->second = $bits[2]; return $r; } /** * get the string to use in the SQL statement for this... * * * @param int $to Type (DB_DATAOBJECT_* * @param object $db DB Connection Object * * * @return string * @access public */ function toString($to=false,$db) { // if $this->type is not set, we are in serious trouble!!!! // values for to: $method = 'toStringFrom'.$this->type; return $this->$method($to,$db); } /** * get the string to use in the SQL statement from a blob of binary data * ** Suppots only blob->postgres::bytea * * @param int $to Type (DB_DATAOBJECT_* * @param object $db DB Connection Object * * * @return string * @access public */ function toStringFromBlob($to,$db) { // first weed out invalid casts.. // in blobs can only be cast to blobs.! // perhaps we should support TEXT fields??? if (!($to & DB_DATAOBJECT_BLOB)) { return PEAR::raiseError('Invalid Cast from a DB_DataObject_Cast::blob to something other than a blob!'); } switch ($db->dsn["phptype"]) { case 'pgsql': return "'".pg_escape_bytea($this->value)."'::bytea"; case 'mysql': return "'".mysql_real_escape_string($this->value,$db->connection)."'"; case 'mysqli': // this is funny - the parameter order is reversed ;) return "'".mysqli_real_escape_string($db->connection, $this->value)."'"; case 'sqlite': // this is funny - the parameter order is reversed ;) return "'".sqlite_escape_string($this->value)."'"; case 'mssql': if(is_numeric($this->value)) { return $this->value; } $unpacked = unpack('H*hex', $this->value); return '0x' . $unpacked['hex']; default: return PEAR::raiseError("DB_DataObject_Cast cant handle blobs for Database:{$db->dsn['phptype']} Yet"); } } /** * get the string to use in the SQL statement for a blob from a string! * ** Suppots only string->postgres::bytea * * * @param int $to Type (DB_DATAOBJECT_* * @param object $db DB Connection Object * * * @return string * @access public */ function toStringFromString($to,$db) { // first weed out invalid casts.. // in blobs can only be cast to blobs.! // perhaps we should support TEXT fields??? // // $to == a string field which is the default type (0) // so we do not test it here. - we assume that number fields // will accept a string?? - which is stretching it a bit ... // should probaly add that test as some point. switch ($db->dsn['phptype']) { case 'pgsql': return "'".pg_escape_string($this->value)."'::bytea"; case 'mysql': return "'".mysql_real_escape_string($this->value,$db->connection)."'"; case 'mysqli': return "'".mysqli_real_escape_string($db->connection, $this->value)."'"; case 'mssql': // copied from the old DB mssql code...?? not sure how safe this is. return "'" . str_replace( array("'", "\\\r\n", "\\\n"), array("''", "\\\\\r\n\r\n", "\\\\\n\n"), $this->value ) . "'"; default: return PEAR::raiseError("DB_DataObject_Cast cant handle blobs for Database:{$db->dsn['phptype']} Yet"); } } /** * get the string to use in the SQL statement for a date * * * * @param int $to Type (DB_DATAOBJECT_* * @param object $db DB Connection Object * * * @return string * @access public */ function toStringFromDate($to,$db) { // first weed out invalid casts.. // in blobs can only be cast to blobs.! // perhaps we should support TEXT fields??? // if (($to !== false) && !($to & DB_DATAOBJECT_DATE)) { return PEAR::raiseError('Invalid Cast from a DB_DataObject_Cast::string to something other than a date!'. ' (why not just use native features)'); } return "'{$this->year}-{$this->month}-{$this->day}'"; } /** * get the string to use in the SQL statement for a datetime * * * * @param int $to Type (DB_DATAOBJECT_* * @param object $db DB Connection Object * * * @return string * @access public * @author therion 5 at hotmail */ function toStringFromDateTime($to,$db) { // first weed out invalid casts.. // in blobs can only be cast to blobs.! // perhaps we should support TEXT fields??? if (($to !== false) && !($to & (DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME))) { return PEAR::raiseError('Invalid Cast from a ' . ' DB_DataObject_Cast::dateTime to something other than a datetime!' . ' (try using native features)'); } return "'{$this->year}-{$this->month}-{$this->day} {$this->hour}:{$this->minute}:{$this->second}'"; } /** * get the string to use in the SQL statement for a time * * * * @param int $to Type (DB_DATAOBJECT_* * @param object $db DB Connection Object * * * @return string * @access public * @author therion 5 at hotmail */ function toStringFromTime($to,$db) { // first weed out invalid casts.. // in blobs can only be cast to blobs.! // perhaps we should support TEXT fields??? if (($to !== false) && !($to & DB_DATAOBJECT_TIME)) { return PEAR::raiseError('Invalid Cast from a' . ' DB_DataObject_Cast::time to something other than a time!'. ' (try using native features)'); } return "'{$this->hour}:{$this->minute}:{$this->second}'"; } /** * get the string to use in the SQL statement for a raw sql statement. * * @param int $to Type (DB_DATAOBJECT_* * @param object $db DB Connection Object * * * @return string * @access public */ function toStringFromSql($to,$db) { return $this->value; } } php-db-dataobject-1.11.5/DB_DataObject-1.11.5/DB/DataObject/Error.php000077500000000000000000000033131277637267000242500ustar00rootroot00000000000000 * @copyright 1997-2006 The PHP Group * @license http://www.php.net/license/3_01.txt PHP License 3.01 * @version CVS: $Id: Error.php 287158 2009-08-12 13:58:31Z alan_k $ * @link http://pear.php.net/package/DB_DataObject */ class DB_DataObject_Error extends PEAR_Error { /** * DB_DataObject_Error constructor. * * @param mixed $code DB error code, or string with error message. * @param integer $mode what "error mode" to operate in * @param integer $level what error level to use for $mode & PEAR_ERROR_TRIGGER * @param mixed $debuginfo additional debug info, such as the last query * * @access public * * @see PEAR_Error */ function DB_DataObject_Error($message = '', $code = DB_ERROR, $mode = PEAR_ERROR_RETURN, $level = E_USER_NOTICE) { $this->PEAR_Error('DB_DataObject Error: ' . $message, $code, $mode, $level); } // todo : - support code -> message handling, and translated error messages... } php-db-dataobject-1.11.5/DB_DataObject-1.11.5/DB/DataObject/Generator.php000077500000000000000000001731751277637267000251230ustar00rootroot00000000000000 * @copyright 1997-2006 The PHP Group * @license http://www.php.net/license/3_01.txt PHP License 3.01 * @version CVS: $Id: Generator.php 336719 2015-05-05 10:37:33Z alan_k $ * @link http://pear.php.net/package/DB_DataObject */ /* * Security Notes: * This class uses eval to create classes on the fly. * The table name and database name are used to check the database before writing the * class definitions, we now check for quotes and semi-colon's in both variables * so I cant see how it would be possible to generate code even if * for some crazy reason you took the classname and table name from User Input. * * If you consider that wrong, or can prove it.. let me know! */ /** * * Config _$ptions * [DB_DataObject] * ; optional default = DB/DataObject.php * extends_location = * ; optional default = DB_DataObject * extends = * ; alter the extends field when updating a class (defaults to only replacing DB_DataObject) * generator_class_rewrite = ANY|specific_name // default is DB_DataObject * */ /** * Needed classes * We lazy load here, due to problems with the tests not setting up include path correctly. * FIXME! */ class_exists('DB_DataObject') ? '' : require_once 'DB/DataObject.php'; //require_once('Config.php'); /** * Generator class * * @package DB_DataObject */ class DB_DataObject_Generator extends DB_DataObject { /* =========================================================== */ /* Utility functions - for building db config files */ /* =========================================================== */ /** * Array of table names * * @var array * @access private */ var $tables; /** * associative array table -> array of table row objects * * @var array * @access private */ var $_definitions; /** * active table being output * * @var string * @access private */ var $table; // active tablename /** * links (generated) * * @var array * @access private */ var $_fkeys; // active tablename /** * The 'starter' = call this to start the process * * @access public * @return none */ function start() { $options = &PEAR::getStaticProperty('DB_DataObject','options'); $db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver']; $databases = array(); foreach($options as $k=>$v) { if (substr($k,0,9) == 'database_') { $databases[substr($k,9)] = $v; } } if (isset($options['database'])) { if ($db_driver == 'DB') { require_once 'DB.php'; $dsn = DB::parseDSN($options['database']); } else { require_once 'MDB2.php'; $dsn = MDB2::parseDSN($options['database']); } if (!isset($database[$dsn['database']])) { $databases[$dsn['database']] = $options['database']; } } foreach($databases as $databasename => $database) { if (!$database) { continue; } $this->debug("CREATING FOR $databasename\n"); $class = get_class($this); $t = new $class; $t->_database_dsn = $database; $t->_database = $databasename; if ($db_driver == 'DB') { require_once 'DB.php'; $dsn = DB::parseDSN($database); } else { require_once 'MDB2.php'; $dsn = MDB2::parseDSN($database); } if (($dsn['phptype'] == 'sqlite') && is_file($databasename)) { $t->_database = basename($t->_database); } $t->_createTableList(); $t->_createForiegnKeys(); foreach(get_class_methods($class) as $method) { if (substr($method,0,8 ) != 'generate') { continue; } $this->debug("calling $method"); $t->$method(); } } $this->debug("DONE\n\n"); } /** * Output File was config object, now just string * Used to generate the Tables * * @var string outputbuffer for table definitions * @access private */ var $_newConfig; /** * Build a list of tables; * and store it in $this->tables and $this->_definitions[tablename]; * * @access private * @return none */ function _createTableList() { $this->_connect(); $options = &PEAR::getStaticProperty('DB_DataObject','options'); $__DB= &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5]; $db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver']; $is_MDB2 = ($db_driver != 'DB') ? true : false; if (is_object($__DB) && is_a($__DB , 'PEAR_Error')) { return PEAR::raiseError($__DB->toString(), null, PEAR_ERROR_DIE); } if (!$is_MDB2) { // try getting a list of schema tables first. (postgres) $__DB->expectError(DB_ERROR_UNSUPPORTED); $this->tables = $__DB->getListOf('schema.tables'); $__DB->popExpect(); } else { /** * set portability and some modules to fetch the informations */ $db_options = PEAR::getStaticProperty('MDB2','options'); if (empty($db_options)) { $__DB->setOption('portability', MDB2_PORTABILITY_ALL ^ MDB2_PORTABILITY_FIX_CASE); } $__DB->loadModule('Manager'); $__DB->loadModule('Reverse'); } if ((empty($this->tables) || (is_object($this->tables) && is_a($this->tables , 'PEAR_Error')))) { //if that fails fall back to clasic tables list. if (!$is_MDB2) { // try getting a list of schema tables first. (postgres) $__DB->expectError(DB_ERROR_UNSUPPORTED); $this->tables = $__DB->getListOf('tables'); $__DB->popExpect(); } else { $this->tables = $__DB->manager->listTables(); $sequences = $__DB->manager->listSequences(); foreach ($sequences as $k => $v) { $this->tables[] = $__DB->getSequenceName($v); } } } if (is_object($this->tables) && is_a($this->tables , 'PEAR_Error')) { return PEAR::raiseError($this->tables->toString(), null, PEAR_ERROR_DIE); } // build views as well if asked to. if (!empty($options['build_views'])) { if (!$is_MDB2) { $views = $__DB->getListOf(is_string($options['build_views']) ? $options['build_views'] : 'views'); } else { $views = $__DB->manager->listViews(); } if (is_object($views) && is_a($views,'PEAR_Error')) { return PEAR::raiseError( 'Error getting Views (check the PEAR bug database for the fix to DB), ' . $views->toString(), null, PEAR_ERROR_DIE ); } $this->tables = array_merge ($this->tables, $views); } // declare a temporary table to be filled with matching tables names $tmp_table = array(); foreach($this->tables as $table) { if (isset($options['generator_include_regex']) && !preg_match($options['generator_include_regex'],$table)) { $this->debug("SKIPPING (generator_include_regex) : $table"); continue; } if (isset($options['generator_exclude_regex']) && preg_match($options['generator_exclude_regex'],$table)) { continue; } $strip = empty($options['generator_strip_schema']) ? false : $options['generator_strip_schema']; $strip = is_numeric($strip) ? (bool) $strip : $strip; $strip = (is_string($strip) && strtolower($strip) == 'true') ? true : $strip; // postgres strip the schema bit from the if (!empty($strip) ) { if (!is_string($strip) || preg_match($strip, $table)) { $bits = explode('.', $table,2); $table = $bits[0]; if (count($bits) > 1) { $table = $bits[1]; } } } $this->debug("EXTRACTING : $table"); $quotedTable = !empty($options['quote_identifiers_tableinfo']) ? $__DB->quoteIdentifier($table) : $table; if (!$is_MDB2) { $defs = $__DB->tableInfo($quotedTable); } else { $defs = $__DB->reverse->tableInfo($quotedTable); // rename the length value, so it matches db's return. } if (is_object($defs) && is_a($defs,'PEAR_Error')) { // running in debug mode should pick this up as a big warning.. $this->debug("Error reading tableInfo: $table"); $this->raiseError('Error reading tableInfo, '. $defs->toString()); continue; } // cast all definitions to objects - as we deal with that better. foreach($defs as $def) { if (!is_array($def)) { continue; } // rename the length value, so it matches db's return. if (isset($def['length']) && !isset($def['len'])) { $def['len'] = $def['length']; } $this->_definitions[$table][] = (object) $def; } // we find a matching table, just store it into a temporary array $tmp_table[] = $table; } // the temporary table array is now the right one (tables names matching // with regex expressions have been removed) $this->tables = $tmp_table; //print_r($this->_definitions); } /** * Auto generation of table data. * * it will output to db_oo_{database} the table definitions * * @access private * @return none */ function generateDefinitions() { $this->debug("Generating Definitions file: "); if (!$this->tables) { $this->debug("-- NO TABLES -- \n"); return; } $options = &PEAR::getStaticProperty('DB_DataObject','options'); //$this->_newConfig = new Config('IniFile'); $this->_newConfig = ''; foreach($this->tables as $this->table) { $this->_generateDefinitionsTable(); } $this->_connect(); // dont generate a schema if location is not set // it's created on the fly! if (empty($options['schema_location']) && empty($options["ini_{$this->_database}"]) ) { return; } if (!empty($options['generator_no_ini'])) { // built in ini files.. return; } $base = @$options['schema_location']; if (isset($options["ini_{$this->_database}"])) { $file = $options["ini_{$this->_database}"]; } else { $file = "{$base}/{$this->_database}.ini"; } if (!file_exists(dirname($file))) { require_once 'System.php'; System::mkdir(array('-p','-m',0755,dirname($file))); } $this->debug("Writing ini as {$file}\n"); //touch($file); $tmpname = tempnam(session_save_path(),'DataObject_'); //print_r($this->_newConfig); $fh = fopen($tmpname,'w'); if (!$fh) { return PEAR::raiseError( "Failed to create temporary file: $tmpname\n". "make sure session.save_path is set and is writable\n" ,null, PEAR_ERROR_DIE); } fwrite($fh,$this->_newConfig); fclose($fh); $perms = file_exists($file) ? fileperms($file) : 0755; // windows can fail doing this. - not a perfect solution but otherwise it's getting really kludgy.. if (!@rename($tmpname, $file)) { unlink($file); rename($tmpname, $file); } chmod($file,$perms); //$ret = $this->_newConfig->writeInput($file,false); //if (PEAR::isError($ret) ) { // return PEAR::raiseError($ret->message,null,PEAR_ERROR_DIE); // } } /** * create the data for Foreign Keys (for links.ini) * Currenly only works with mysql / mysqli / posgtreas * to use, you must set option: generate_links=true * * @author Pascal Sch�ni */ function _createForiegnKeys() { $options = PEAR::getStaticProperty('DB_DataObject','options'); if (empty($options['generate_links'])) { return false; } $__DB = &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5]; if (!in_array($__DB->phptype, array('mysql', 'mysqli', 'pgsql'))) { echo "WARNING: cant handle non-mysql and pgsql introspection for defaults."; return; // cant handle non-mysql introspection for defaults. } $this->debug("generateForeignKeys: Start"); $DB = $this->getDatabaseConnection(); $fk = array(); switch ($DB->phptype) { case 'pgsql': foreach($this->tables as $this->table) { $quotedTable = !empty($options['quote_identifiers_tableinfo']) ? $DB->quoteIdentifier($table) : $this->table; $res =& $DB->query("SELECT pg_catalog.pg_get_constraintdef(r.oid, true) AS condef FROM pg_catalog.pg_constraint r, pg_catalog.pg_class c WHERE c.oid=r.conrelid AND r.contype = 'f' AND c.relname = '" . $quotedTable . "'"); if (PEAR::isError($res)) { die($res->getMessage()); } while ($row = $res->fetchRow(DB_FETCHMODE_ASSOC)) { $treffer = array(); // this only picks up one of these.. see this for why: http://pear.php.net/bugs/bug.php?id=17049 preg_match( "/FOREIGN KEY \((\w*)\) REFERENCES (\w*)\((\w*)\)/i", $row['condef'], $treffer); if (!count($treffer)) { continue; } $fk[$this->table][$treffer[1]] = $treffer[2] . ":" . $treffer[3]; } } break; case 'mysql': case 'mysqli': default: foreach($this->tables as $this->table) { $quotedTable = !empty($options['quote_identifiers_tableinfo']) ? $DB->quoteIdentifier($table) : $this->table; $res =& $DB->query('SHOW CREATE TABLE ' . $quotedTable ); if (PEAR::isError($res)) { die($res->getMessage()); } $text = $res->fetchRow(DB_FETCHMODE_DEFAULT, 0); $treffer = array(); // Extract FOREIGN KEYS preg_match_all( "/FOREIGN KEY \(`(\w*)`\) REFERENCES `(\w*)` \(`(\w*)`\)/i", $text[1], $treffer, PREG_SET_ORDER); if (!count($treffer)) { continue; } foreach($treffer as $i=> $tref) { $fk[$this->table][$tref[1]] = $tref[2] . ":" . $tref[3]; } } } $this->_fkeys = $fk; } /** * generate Foreign Keys (for links.ini) * Currenly only works with mysql / mysqli * to use, you must set option: generate_links=true * * @author Pascal Sch�ni */ function generateForeignKeys() { $options = PEAR::getStaticProperty('DB_DataObject','options'); if (empty($options['generate_links'])) { return false; } $__DB = &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5]; if (!in_array($__DB->phptype, array('mysql', 'mysqli', 'pgsql'))) { echo "WARNING: cant handle non-mysql and pgsql introspection for defaults."; return; // cant handle non-mysql introspection for defaults. } $this->debug("generateForeignKeys: Start"); $fk = $this->_fkeys; $links_ini = ""; foreach($fk as $table => $details) { $links_ini .= "[$table]\n"; foreach ($details as $col => $ref) { $links_ini .= "$col = $ref\n"; } $links_ini .= "\n"; } // dont generate a schema if location is not set // it's created on the fly! $options = PEAR::getStaticProperty('DB_DataObject','options'); if (!empty($options['schema_location'])) { $file = "{$options['schema_location']}/{$this->_database}.links.ini"; } elseif (isset($options["ini_{$this->_database}"])) { $file = preg_replace('/\.ini/','.links.ini',$options["ini_{$this->_database}"]); } else { $this->debug("generateForeignKeys: SKIP - schema_location or ini_{database} was not set"); return; } if (!file_exists(dirname($file))) { mkdir(dirname($file),0755, true); } $this->debug("Writing ini as {$file}\n"); //touch($file); // not sure why this is needed? $tmpname = tempnam(session_save_path(),'DataObject_'); $fh = fopen($tmpname,'w'); if (!$fh) { return PEAR::raiseError( "Failed to create temporary file: $tmpname\n". "make sure session.save_path is set and is writable\n" ,null, PEAR_ERROR_DIE); } fwrite($fh,$links_ini); fclose($fh); $perms = file_exists($file) ? fileperms($file) : 0755; // windows can fail doing this. - not a perfect solution but otherwise it's getting really kludgy.. if (!@rename($tmpname, $file)) { unlink($file); rename($tmpname, $file); } chmod($file, $perms); } /** * The table geneation part * * @access private * @return tabledef and keys array. */ function _generateDefinitionsTable() { global $_DB_DATAOBJECT; $options = PEAR::getStaticProperty('DB_DataObject','options'); $defs = $this->_definitions[$this->table]; $this->_newConfig .= "\n[{$this->table}]\n"; $keys_out = "\n[{$this->table}__keys]\n"; $keys_out_primary = ''; $keys_out_secondary = ''; if (@$_DB_DATAOBJECT['CONFIG']['debug'] > 2) { echo "TABLE STRUCTURE FOR {$this->table}\n"; print_r($defs); } $DB = $this->getDatabaseConnection(); $dbtype = $DB->phptype; $ret = array( 'table' => array(), 'keys' => array(), ); $ret_keys_primary = array(); $ret_keys_secondary = array(); foreach($defs as $t) { $n=0; $write_ini = true; switch (strtoupper($t->type)) { case 'INT': case 'INT2': // postgres case 'INT4': // postgres case 'INT8': // postgres case 'SERIAL4': // postgres case 'SERIAL8': // postgres case 'INTEGER': case 'TINYINT': case 'SMALLINT': case 'MEDIUMINT': case 'BIGINT': $type = DB_DATAOBJECT_INT; if ($t->len == 1) { $type += DB_DATAOBJECT_BOOL; } break; case 'REAL': case 'DOUBLE': case 'DOUBLE PRECISION': // double precision (firebird) case 'FLOAT': case 'FLOAT4': // real (postgres) case 'FLOAT8': // double precision (postgres) case 'DECIMAL': case 'MONEY': // mssql and maybe others case 'NUMERIC': case 'NUMBER': // oci8 $type = DB_DATAOBJECT_INT; // should really by FLOAT!!! / MONEY... break; case 'YEAR': $type = DB_DATAOBJECT_INT; break; case 'BIT': case 'BOOL': case 'BOOLEAN': $type = DB_DATAOBJECT_BOOL; // postgres needs to quote '0' if ($dbtype == 'pgsql') { $type += DB_DATAOBJECT_STR; } break; case 'STRING': case 'CHAR': case 'VARCHAR': case 'VARCHAR2': case 'TINYTEXT': case 'ENUM': case 'SET': // not really but oh well case 'POINT': // mysql geometry stuff - not really string - but will do.. case 'TIMESTAMPTZ': // postgres case 'BPCHAR': // postgres case 'INTERVAL': // postgres (eg. '12 days') case 'CIDR': // postgres IP net spec case 'INET': // postgres IP case 'MACADDR': // postgress network Mac address. case 'INTEGER[]': // postgres type case 'BOOLEAN[]': // postgres type $type = DB_DATAOBJECT_STR; break; case 'TEXT': case 'MEDIUMTEXT': case 'LONGTEXT': case '_TEXT': //postgres (?? view ??) $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_TXT; break; case 'DATE': $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE; break; case 'TIME': $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_TIME; break; case 'DATETIME': $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME; break; case 'TIMESTAMP': // do other databases use this??? $type = ($dbtype == 'mysql') ? DB_DATAOBJECT_MYSQLTIMESTAMP : DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME; break; case 'BLOB': /// these should really be ignored!!!??? case 'TINYBLOB': case 'MEDIUMBLOB': case 'LONGBLOB': case 'CLOB': // oracle character lob support case 'BYTEA': // postgres blob support.. $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_BLOB; break; default: echo "*****************************************************************\n". "** WARNING UNKNOWN TYPE **\n". "** Found column '{$t->name}', of type '{$t->type}' **\n". "** Please submit a bug, describe what type you expect this **\n". "** column to be **\n". "** ---------POSSIBLE FIX / WORKAROUND -------------------------**\n". "** Try using MDB2 as the backend - eg set the config option **\n". "** db_driver = MDB2 **\n". "*****************************************************************\n"; $write_ini = false; break; } if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $t->name)) { echo "*****************************************************************\n". "** WARNING COLUMN NAME UNUSABLE **\n". "** Found column '{$t->name}', of type '{$t->type}' **\n". "** Since this column name can't be converted to a php variable **\n". "** name, and the whole idea of mapping would result in a mess **\n". "** This column has been ignored... **\n". "*****************************************************************\n"; continue; } if (!strlen(trim($t->name))) { continue; // is this a bug? } if (preg_match('/not[ _]null/i',$t->flags)) { $type += DB_DATAOBJECT_NOTNULL; } if (in_array($t->name,array('null','yes','no','true','false'))) { echo "*****************************************************************\n". "** WARNING **\n". "** Found column '{$t->name}', which is invalid in an .ini file **\n". "** This line will not be writen to the file - you will have **\n". "** define the keys()/method manually. **\n". "*****************************************************************\n"; $write_ini = false; } else { $this->_newConfig .= "{$t->name} = $type\n"; } $ret['table'][$t->name] = $type; // i've no idea if this will work well on other databases? // only use primary key or nextval(), cause the setFrom blocks you setting all key items... // if no keys exist fall back to using unique //echo "\n{$t->name} => {$t->flags}\n"; $secondary_key_match = isset($options['generator_secondary_key_match']) ? $options['generator_secondary_key_match'] : 'primary|unique'; $m = array(); if (preg_match('/(auto_increment|nextval\(([^)]*))/i',rawurldecode($t->flags),$m) || (isset($t->autoincrement) && ($t->autoincrement === true))) { $sn = 'N'; if ($DB->phptype == 'pgsql' && !empty($m[2])) { $sn = preg_replace('/[("]+/','', $m[2]); //echo urldecode($t->flags) . "\n" ; } // native sequences = 2 if ($write_ini) { $keys_out_primary .= "{$t->name} = $sn\n"; } $ret_keys_primary[$t->name] = $sn; } else if ($secondary_key_match && preg_match('/('.$secondary_key_match.')/i',$t->flags)) { // keys.. = 1 $key_type = 'K'; if (!preg_match("/(primary)/i",$t->flags)) { $key_type = 'U'; } if ($write_ini) { $keys_out_secondary .= "{$t->name} = {$key_type}\n"; } $ret_keys_secondary[$t->name] = $key_type; } } $this->_newConfig .= $keys_out . (empty($keys_out_primary) ? $keys_out_secondary : $keys_out_primary); $ret['keys'] = empty($keys_out_primary) ? $ret_keys_secondary : $ret_keys_primary; if (@$_DB_DATAOBJECT['CONFIG']['debug'] > 2) { print_r(array("dump for {$this->table}", $ret)); } return $ret; } /** * Convert a table name into a class name -> override this if you want a different mapping * * @access public * @return string class name; */ function getClassNameFromTableName($table) { $options = &PEAR::getStaticProperty('DB_DataObject','options'); $class_prefix = empty($options['class_prefix']) ? '' : $options['class_prefix']; return $class_prefix.preg_replace('/[^A-Z0-9]/i','_',ucfirst(trim($this->table))); } /** * Convert a table name into a file name -> override this if you want a different mapping * * @access public * @return string file name; */ function getFileNameFromTableName($table) { $options = &PEAR::getStaticProperty('DB_DataObject','options'); $base = $options['class_location']; if (strpos($base,'%s') !== false) { $base = dirname($base); } if (!file_exists($base)) { require_once 'System.php'; System::mkdir(array('-p',$base)); } if (strpos($options['class_location'],'%s') !== false) { $outfilename = sprintf($options['class_location'], preg_replace('/[^A-Z0-9]/i','_',ucfirst($this->table))); } else { $outfilename = "{$base}/".preg_replace('/[^A-Z0-9]/i','_',ucfirst($this->table)).".php"; } return $outfilename; } /** * Convert a column name into a method name (usually prefixed by get/set/validateXXXXX) * * @access public * @return string method name; */ function getMethodNameFromColumnName($col) { return ucfirst($col); } /* * building the class files * for each of the tables output a file! */ function generateClasses() { //echo "Generating Class files: \n"; $options = &PEAR::getStaticProperty('DB_DataObject','options'); $this->_extends = empty($options['extends']) ? $this->_extends : $options['extends']; $this->_extendsFile = !isset($options['extends_location']) ? $this->_extendsFile : $options['extends_location']; foreach($this->tables as $this->table) { $this->table = trim($this->table); $this->classname = $this->getClassNameFromTableName($this->table); $i = ''; $outfilename = $this->getFileNameFromTableName($this->table); $oldcontents = ''; if (file_exists($outfilename)) { // file_get_contents??? $oldcontents = implode('',file($outfilename)); } $out = $this->_generateClassTable($oldcontents); $this->debug( "writing $this->classname\n"); $tmpname = tempnam(session_save_path(),'DataObject_'); $fh = fopen($tmpname, "w"); if (!$fh) { return PEAR::raiseError( "Failed to create temporary file: $tmpname\n". "make sure session.save_path is set and is writable\n" ,null, PEAR_ERROR_DIE); } fputs($fh,$out); fclose($fh); $perms = file_exists($outfilename) ? fileperms($outfilename) : 0755; // windows can fail doing this. - not a perfect solution but otherwise it's getting really kludgy.. if (!@rename($tmpname, $outfilename)) { unlink($outfilename); rename($tmpname, $outfilename); } chmod($outfilename, $perms); } //echo $out; } /** * class being extended (can be overridden by [DB_DataObject] extends=xxxx * * @var string * @access private */ var $_extends = 'DB_DataObject'; /** * line to use for require('DB/DataObject.php'); * * @var string * @access private */ var $_extendsFile = "DB/DataObject.php"; /** * class being generated * * @var string * @access private */ var $_className; /** * The table class geneation part - single file. * * @access private * @return none */ function _generateClassTable($input = '') { // title = expand me! $foot = ""; $head = "table}\n"; $head .= $this->derivedHookPageLevelDocBlock(); $head .= " */\n"; $head .= $this->derivedHookExtendsDocBlock(); // requires - if you set extends_location = (blank) then no require line will be set // this can be used if you have an autoloader if (!empty($this->_extendsFile)) { $head .= "require_once '{$this->_extendsFile}';\n\n"; } // add dummy class header in... // class $head .= $this->derivedHookClassDocBlock(); $head .= "class {$this->classname} extends {$this->_extends} \n{"; $body = "\n ###START_AUTOCODE\n"; $body .= " /* the code below is auto generated do not remove the above tag */\n\n"; // table $p = str_repeat(' ',max(2, (18 - strlen($this->table)))) ; $options = &PEAR::getStaticProperty('DB_DataObject','options'); $var = (substr(phpversion(),0,1) > 4) ? 'public' : 'var'; $var = !empty($options['generator_var_keyword']) ? $options['generator_var_keyword'] : $var; $body .= " {$var} \$__table = '{$this->table}'; {$p}// table name\n"; // if we are using the option database_{databasename} = dsn // then we should add var $_database = here // as database names may not always match.. if (empty($GLOBALS['_DB_DATAOBJECT']['CONFIG'])) { DB_DataObject::_loadConfig(); } // Only include the $_database property if the omit_database_var is unset or false if (isset($options["database_{$this->_database}"]) && empty($GLOBALS['_DB_DATAOBJECT']['CONFIG']['generator_omit_database_var'])) { $p = str_repeat(' ', max(2, (16 - strlen($this->_database)))); $body .= " {$var} \$_database = '{$this->_database}'; {$p}// database name (used with database_{*} config)\n"; } if (!empty($options['generator_novars'])) { $var = '//'.$var; } $defs = $this->_definitions[$this->table]; // show nice information! $connections = array(); $sets = array(); foreach($defs as $t) { if (!strlen(trim($t->name))) { continue; } if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $t->name)) { echo "*****************************************************************\n". "** WARNING COLUMN NAME UNUSABLE **\n". "** Found column '{$t->name}', of type '{$t->type}' **\n". "** Since this column name can't be converted to a php variable **\n". "** name, and the whole idea of mapping would result in a mess **\n". "** This column has been ignored... **\n". "*****************************************************************\n"; continue; } $pad = str_repeat(' ',max(2, (30 - strlen($t->name)))); $length = empty($t->len) ? '' : '('.$t->len.')'; $flags = strlen($t->flags) ? (' '. trim($t->flags)) : ''; $body .=" {$var} \${$t->name}; {$pad}// {$t->type}{$length}{$flags}\n"; // can not do set as PEAR::DB table info doesnt support it. //if (substr($t->Type,0,3) == "set") // $sets[$t->Field] = "array".substr($t->Type,3); $body .= $this->derivedHookVar($t,strlen($p)); } $body .= $this->derivedHookPostVar($defs); // THIS IS TOTALLY BORKED old FC creation // IT WILL BE REMOVED!!!!! in DataObjects 1.6 // grep -r __clone * to find all it's uses // and replace them with $x = clone($y); // due to the change in the PHP5 clone design. $static = 'static'; if ( substr(phpversion(),0,1) < 5) { $body .= "\n"; $body .= " /* ZE2 compatibility trick*/\n"; $body .= " function __clone() { return \$this;}\n"; } // depricated - in here for BC... if (!empty($options['static_get'])) { // simple creation tools ! (static stuff!) $body .= "\n"; $body .= " /* Static get */\n"; $body .= " $static function staticGet(\$k,\$v=NULL) { " . "return DB_DataObject::staticGet('{$this->classname}',\$k,\$v = null); }\n"; } // generate getter and setter methods $body .= $this->_generateGetters($input); $body .= $this->_generateSetters($input); $body .= $this->_generateLinkMethods($input); /* theoretically there is scope here to introduce 'list' methods based up 'xxxx_up' column!!! for heiracitcal trees.. */ // set methods //foreach ($sets as $k=>$v) { // $kk = strtoupper($k); // $body .=" function getSets{$k}() { return {$v}; }\n"; //} if (!empty($options['generator_no_ini'])) { $def = $this->_generateDefinitionsTable(); // simplify this!? $body .= $this->_generateTableFunction($def['table']); $body .= $this->_generateKeysFunction($def['keys']); $body .= $this->_generateSequenceKeyFunction($def); $body .= $this->_generateDefaultsFunction($this->table, $def['table']); } else if (!empty($options['generator_add_defaults'])) { // I dont really like doing it this way (adding another option) // but it helps on older projects. $def = $this->_generateDefinitionsTable(); // simplify this!? $body .= $this->_generateDefaultsFunction($this->table,$def['table']); } $body .= $this->derivedHookFunctions($input); $body .= "\n /* the code above is auto generated do not remove the tag below */"; $body .= "\n ###END_AUTOCODE\n"; // stubs.. if (!empty($options['generator_add_validate_stubs'])) { foreach($defs as $t) { if (!strlen(trim($t->name))) { continue; } $validate_fname = 'validate' . $this->getMethodNameFromColumnName($t->name); // dont re-add it.. if (preg_match('/\s+function\s+' . $validate_fname . '\s*\(/i', $input)) { continue; } $body .= "\n function {$validate_fname}()\n {\n return false;\n }\n"; } } $foot .= "}\n"; $full = $head . $body . $foot; if (!$input) { return $full; } if (!preg_match('/(\n|\r\n)\s*###START_AUTOCODE(\n|\r\n)/s',$input)) { return $full; } if (!preg_match('/(\n|\r\n)\s*###END_AUTOCODE(\n|\r\n)/s',$input)) { return $full; } /* this will only replace extends DB_DataObject by default, unless use set generator_class_rewrite to ANY or a name*/ $class_rewrite = 'DB_DataObject'; $options = &PEAR::getStaticProperty('DB_DataObject','options'); if (empty($options['generator_class_rewrite']) || !($class_rewrite = $options['generator_class_rewrite'])) { $class_rewrite = 'DB_DataObject'; } if ($class_rewrite == 'ANY') { $class_rewrite = '[a-z_]+'; } $input = preg_replace( '/(\n|\r\n)class\s*[a-z0-9_]+\s*extends\s*' .$class_rewrite . '\s*(\n|\r\n)\{(\n|\r\n)/si', "\nclass {$this->classname} extends {$this->_extends} \n{\n", $input); $ret = preg_replace( '/(\n|\r\n)\s*###START_AUTOCODE(\n|\r\n).*(\n|\r\n)\s*###END_AUTOCODE(\n|\r\n)/s', $body,$input); if (!strlen($ret)) { return PEAR::raiseError( "PREG_REPLACE failed to replace body, - you probably need to set these in your php.ini\n". "pcre.backtrack_limit=1000000\n". "pcre.recursion_limit=1000000\n" ,null, PEAR_ERROR_DIE); } return $ret; } /** * hook to add extra methods to all classes * * called once for each class, use with $this->table and * $this->_definitions[$this->table], to get data out of the current table, * use it to add extra methods to the default classes. * * @access public * @return string added to class eg. functions. */ function derivedHookFunctions($input = "") { // This is so derived generator classes can generate functions // It MUST NOT be changed here!!! return ""; } /** * hook for var lines * called each time a var line is generated, override to add extra var * lines * * @param object t containing type,len,flags etc. from tableInfo call * @param int padding number of spaces * @access public * @return string added to class eg. functions. */ function derivedHookVar(&$t,$padding) { // This is so derived generator classes can generate variabels // It MUST NOT be changed here!!! return ""; } /** * hook for after var lines ( * called at the end of the output of var line have generated, override to add extra var * lines * * @param array cols containing array of objects with type,len,flags etc. from tableInfo call * @access public * @return string added to class eg. functions. */ function derivedHookPostVar($t) { // This is so derived generator classes can generate variabels // It MUST NOT be changed here!!! return ""; } /** * hook to add extra page-level (in terms of phpDocumentor) DocBlock * * called once for each class, use it add extra page-level docs * @access public * @return string added to class eg. functions. */ function derivedHookPageLevelDocBlock() { return ''; } /** * hook to add extra doc block (in terms of phpDocumentor) to extend string * * called once for each class, use it add extra comments to extends * string (require_once...) * @access public * @return string added to class eg. functions. */ function derivedHookExtendsDocBlock() { return ''; } /** * hook to add extra class level DocBlock (in terms of phpDocumentor) * * called once for each class, use it add extra comments to class * string (require_once...) * @access public * @return string added to class eg. functions. */ function derivedHookClassDocBlock() { return ''; } /** /** * getProxyFull - create a class definition on the fly and instantate it.. * * similar to generated files - but also evals the class definitoin code. * * * @param string database name * @param string table name of table to create proxy for. * * * @return object Instance of class. or PEAR Error * @access public */ function getProxyFull($database,$table) { if ($err = $this->fillTableSchema($database,$table)) { return $err; } $options = &PEAR::getStaticProperty('DB_DataObject','options'); $class_prefix = empty($options['class_prefix']) ? '' : $options['class_prefix']; $this->_extends = empty($options['extends']) ? $this->_extends : $options['extends']; $this->_extendsFile = !isset($options['extends_location']) ? $this->_extendsFile : $options['extends_location']; $classname = $this->classname = $this->getClassNameFromTableName($this->table); $out = $this->_generateClassTable(); //echo $out; eval('?>'.$out); return new $classname; } /** * fillTableSchema - set the database schema on the fly * * * * @param string database name * @param string table name of table to create schema info for * * @return none | PEAR::error() * @access public */ function fillTableSchema($database,$table) { global $_DB_DATAOBJECT; // a little bit of sanity testing. if ((false !== strpos($database,"'")) || (false !== strpos($database,";"))) { return PEAR::raiseError("Error: Database name contains a quote or semi-colon", null, PEAR_ERROR_DIE); } $this->_database = $database; $this->_connect(); $table = trim($table); // a little bit of sanity testing. if ((false !== strpos($table,"'")) || (false !== strpos($table,";"))) { return PEAR::raiseError("Error: Table contains a quote or semi-colon", null, PEAR_ERROR_DIE); } $__DB= &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5]; $options = PEAR::getStaticProperty('DB_DataObject','options'); $db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver']; $is_MDB2 = ($db_driver != 'DB') ? true : false; if (!$is_MDB2) { // try getting a list of schema tables first. (postgres) $__DB->expectError(DB_ERROR_UNSUPPORTED); $this->tables = $__DB->getListOf('schema.tables'); $__DB->popExpect(); } else { /** * set portability and some modules to fetch the informations */ $__DB->setOption('portability', MDB2_PORTABILITY_ALL ^ MDB2_PORTABILITY_FIX_CASE); $__DB->loadModule('Manager'); $__DB->loadModule('Reverse'); } $quotedTable = !empty($options['quote_identifiers_tableinfo']) ? $__DB->quoteIdentifier($table) : $table; if (!$is_MDB2) { $defs = $__DB->tableInfo($quotedTable); } else { $defs = $__DB->reverse->tableInfo($quotedTable); if (PEAR::isError($defs)) { return $defs; } foreach ($defs as $k => $v) { if (!isset($defs[$k]['length'])) { continue; } $defs[$k]['len'] = $defs[$k]['length']; } } if (PEAR::isError($defs)) { return $defs; } if (@$_DB_DATAOBJECT['CONFIG']['debug'] > 2) { $this->debug("getting def for $database/$table",'fillTable'); $this->debug(print_r($defs,true),'defs'); } // cast all definitions to objects - as we deal with that better. foreach($defs as $def) { if (is_array($def)) { $this->_definitions[$table][] = (object) $def; } } $this->table = trim($table); $ret = $this->_generateDefinitionsTable(); $_DB_DATAOBJECT['INI'][$database][$table] = $ret['table']; $_DB_DATAOBJECT['INI'][$database][$table.'__keys'] = $ret['keys']; return false; } /** * Generate getter methods for class definition * * @param string $input Existing class contents * @return string * @access public */ function _generateGetters($input) { $options = &PEAR::getStaticProperty('DB_DataObject','options'); $getters = ''; // only generate if option is set to true if (empty($options['generate_getters'])) { return ''; } // remove auto-generated code from input to be able to check if the method exists outside of the auto-code $input = preg_replace('/(\n|\r\n)\s*###START_AUTOCODE(\n|\r\n).*(\n|\r\n)\s*###END_AUTOCODE(\n|\r\n)/s', '', $input); $getters .= "\n\n"; $defs = $this->_definitions[$this->table]; // loop through properties and create getter methods foreach ($defs = $defs as $t) { // build mehtod name $methodName = 'get' . $this->getMethodNameFromColumnName($t->name); if (!strlen(trim($t->name)) || preg_match("/function[\s]+[&]?$methodName\(/i", $input)) { continue; } $getters .= " /**\n"; $getters .= " * Getter for \${$t->name}\n"; $getters .= " *\n"; $getters .= (stristr($t->flags, 'multiple_key')) ? " * @return object\n" : " * @return {$t->type}\n"; $getters .= " * @access public\n"; $getters .= " */\n"; $getters .= (substr(phpversion(),0,1) > 4) ? ' public ' : ' '; $getters .= "function $methodName() {\n"; $getters .= " return \$this->{$t->name};\n"; $getters .= " }\n\n"; } return $getters; } /** * Generate link setter/getter methods for class definition * * @param string Existing class contents * @return string * @access public */ function _generateLinkMethods($input) { $options = &PEAR::getStaticProperty('DB_DataObject','options'); $setters = ''; // only generate if option is set to true // generate_link_methods true:: if (empty($options['generate_link_methods'])) { //echo "skip lm? - not set"; return ''; } if (empty($this->_fkeys)) { // echo "skip lm? - fkyes empty"; return ''; } if (empty($this->_fkeys[$this->table])) { //echo "skip lm? - no fkeys for {$this->table}"; return ''; } // remove auto-generated code from input to be able to check if the method exists outside of the auto-code $input = preg_replace('/(\n|\r\n)\s*###START_AUTOCODE(\n|\r\n).*(\n|\r\n)\s*###END_AUTOCODE(\n|\r\n)/s', '', $input); $setters .= "\n"; $defs = $this->_fkeys[$this->table]; // $fk[$this->table][$tref[1]] = $tref[2] . ":" . $tref[3]; // loop through properties and create setter methods foreach ($defs as $k => $info) { // build mehtod name $methodName = is_callable($options['generate_link_methods']) ? $options['generate_link_methods']($k) : $k; if (!strlen(trim($k)) || preg_match("/function[\s]+[&]?$methodName\(/i", $input)) { continue; } $setters .= " /**\n"; $setters .= " * Getter / Setter for \${$k}\n"; $setters .= " *\n"; $setters .= " * @param mixed (optional) value to assign\n"; $setters .= " * @access public\n"; $setters .= " */\n"; $setters .= (substr(phpversion(),0,1) > 4) ? ' public ' : ' '; $setters .= "function $methodName() {\n"; $setters .= " return \$this->link('$k', func_get_args());\n"; $setters .= " }\n\n"; } return $setters; } /** * Generate setter methods for class definition * * @param string Existing class contents * @return string * @access public */ function _generateSetters($input) { $options = &PEAR::getStaticProperty('DB_DataObject','options'); $setters = ''; // only generate if option is set to true if (empty($options['generate_setters'])) { return ''; } // remove auto-generated code from input to be able to check if the method exists outside of the auto-code $input = preg_replace('/(\n|\r\n)\s*###START_AUTOCODE(\n|\r\n).*(\n|\r\n)\s*###END_AUTOCODE(\n|\r\n)/s', '', $input); $setters .= "\n"; $defs = $this->_definitions[$this->table]; // loop through properties and create setter methods foreach ($defs = $defs as $t) { // build mehtod name $methodName = 'set' . $this->getMethodNameFromColumnName($t->name); if (!strlen(trim($t->name)) || preg_match("/function[\s]+[&]?$methodName\(/i", $input)) { continue; } $setters .= " /**\n"; $setters .= " * Setter for \${$t->name}\n"; $setters .= " *\n"; $setters .= " * @param mixed input value\n"; $setters .= " * @access public\n"; $setters .= " */\n"; $setters .= (substr(phpversion(),0,1) > 4) ? ' public ' : ' '; $setters .= "function $methodName(\$value) {\n"; $setters .= " \$this->{$t->name} = \$value;\n"; $setters .= " }\n\n"; } return $setters; } /** * Generate table Function - used when generator_no_ini is set. * * @param array table array. * @return string * @access public */ function _generateTableFunction($def) { $defines = explode(',','INT,STR,DATE,TIME,BOOL,TXT,BLOB,NOTNULL,MYSQLTIMESTAMP'); $ret = "\n" . " function table()\n" . " {\n" . " return array(\n"; foreach($def as $k=>$v) { $str = '0'; foreach($defines as $dn) { if ($v & constant('DB_DATAOBJECT_' . $dn)) { $str .= ' + DB_DATAOBJECT_' . $dn; } } if (strlen($str) > 1) { $str = substr($str,3); // strip the 0 + } // hopefully addslashes is good enough here!!! $ret .= ' \''.addslashes($k).'\' => ' . $str . ",\n"; } return $ret . " );\n" . " }\n"; } /** * Generate keys Function - used generator_no_ini is set. * * @param array keys array. * @return string * @access public */ function _generateKeysFunction($def) { $ret = "\n" . " function keys()\n" . " {\n" . " return array("; foreach($def as $k=>$type) { // hopefully addslashes is good enough here!!! $ret .= '\''.addslashes($k).'\', '; } $ret = preg_replace('#, $#', '', $ret); return $ret . ");\n" . " }\n"; } /** * Generate sequenceKey Function - used generator_no_ini is set. * * @param array table and key definition. * @return string * @access public */ function _generateSequenceKeyFunction($def) { //print_r($def); // DB_DataObject::debugLevel(5); global $_DB_DATAOBJECT; // print_r($def); $dbtype = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype']; $realkeys = $def['keys']; $keys = array_keys($realkeys); $usekey = isset($keys[0]) ? $keys[0] : false; $table = $def['table']; $seqname = false; $ar = array(false,false,false); if ($usekey !== false) { if (!empty($_DB_DATAOBJECT['CONFIG']['sequence_'.$this->__table])) { $usekey = $_DB_DATAOBJECT['CONFIG']['sequence_'.$this->__table]; if (strpos($usekey,':') !== false) { list($usekey,$seqname) = explode(':',$usekey); } } if (in_array($dbtype , array( 'mysql', 'mysqli', 'mssql', 'ifx')) && ($table[$usekey] & DB_DATAOBJECT_INT) && isset($realkeys[$usekey]) && ($realkeys[$usekey] == 'N') ) { // use native sequence keys. $ar = array($usekey,true,$seqname); } else { // use generated sequence keys.. if ($table[$usekey] & DB_DATAOBJECT_INT) { $ar = array($usekey,false,$seqname); } } } $ret = "\n" . " function sequenceKey() // keyname, use native, native name\n" . " {\n" . " return array("; foreach($ar as $v) { switch (gettype($v)) { case 'boolean': $ret .= ($v ? 'true' : 'false') . ', '; break; case 'string': $ret .= "'" . $v . "', "; break; default: // eak $ret .= "null, "; } } $ret = preg_replace('#, $#', '', $ret); return $ret . ");\n" . " }\n"; } /** * Generate defaults Function - used generator_add_defaults or generator_no_ini is set. * Only supports mysql and mysqli ... welcome ideas for more.. * * * @param array table and key definition. * @return string * @access public */ function _generateDefaultsFunction($table,$defs) { $__DB= &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5]; if (!in_array($__DB->phptype, array('mysql','mysqli'))) { return; // cant handle non-mysql introspection for defaults. } $options = PEAR::getStaticProperty('DB_DataObject','options'); $db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver']; $method = $db_driver == 'DB' ? 'getAll' : 'queryAll'; $res = $__DB->$method('DESCRIBE ' . $table,DB_FETCHMODE_ASSOC); $defaults = array(); foreach($res as $ar) { // this is initially very dumb... -> and it may mess up.. $type = $defs[$ar['Field']]; switch (true) { case (is_null( $ar['Default'])): $defaults[$ar['Field']] = 'null'; break; case ($type & DB_DATAOBJECT_DATE): case ($type & DB_DATAOBJECT_TIME): case ($type & DB_DATAOBJECT_MYSQLTIMESTAMP): // not supported yet.. break; case ($type & DB_DATAOBJECT_BOOL): $defaults[$ar['Field']] = (int)(boolean) $ar['Default']; break; case ($type & DB_DATAOBJECT_STR): $defaults[$ar['Field']] = "'" . addslashes($ar['Default']) . "'"; break; default: // hopefully eveything else... - numbers etc. if (!strlen($ar['Default'])) { continue; } if (is_numeric($ar['Default'])) { $defaults[$ar['Field']] = $ar['Default']; } break; } //var_dump(array($ar['Field'], $ar['Default'], $defaults[$ar['Field']])); } if (empty($defaults)) { return; } $ret = "\n" . " function defaults() // column default values \n" . " {\n" . " return array(\n"; foreach($defaults as $k=>$v) { $ret .= ' \''.addslashes($k).'\' => ' . $v . ",\n"; } return $ret . " );\n" . " }\n"; } } php-db-dataobject-1.11.5/DB_DataObject-1.11.5/DB/DataObject/Links.php000066400000000000000000000346651277637267000242520ustar00rootroot00000000000000 * @copyright 1997-2006 The PHP Group * @license http://www.php.net/license/3_01.txt PHP License 3.01 * @version : FIXME * @link http://pear.php.net/package/DB_DataObject */ /** * * Example of how this could be used.. * * The lind method are now in here. * * Currenly only supports existing methods, and new 'link()' method * */ /** * Links class * * @package DB_DataObject */ class DB_DataObject_Links { /** * @property {DB_DataObject} do DataObject to apply this to. */ var $do = false; /** * @property {Array|String} load What to load, 'all' or an array of properties. (default all) */ var $load = 'all'; /** * @property {String|Boolean} scanf use part of column name as resulting * property name. (default false) */ var $scanf = false; /** * @property {String|Boolean} printf use column name as sprintf for resulting property name.. * (default %s_link if apply is true, otherwise it is %s) */ var $printf = false; /** * @property {Boolean} cached cache the result, so future queries will use cache rather * than running the expensive sql query. */ var $cached = false; /** * @property {Boolean} apply apply the result to this object, (default true) */ var $apply = true; //------------------------- RETURN ------------------------------------ /** * @property {Array} links key value associative array of links. */ var $links; /** * Constructor * -- good ole style.. * @param {DB_DataObject} do DataObject to apply to. * @param {Array} cfg Configuration (basically properties of this object) */ function DB_DataObject_Links($do,$cfg= array()) { // check if do is set!!!? $this->do = $do; foreach($cfg as $k=>$v) { $this->$k = $v; } } /** * return name from related object * * The relies on a .links.ini file, unless you specify the arguments. * * you can also use $this->getLink('thisColumnName','otherTable','otherTableColumnName') * * * @param string $field|array either row or row.xxxxx or links spec. * @param string|DB_DataObject $table (optional) name of table to look up value in * @param string $link (optional) name of column in other table to match * @author Tim White * @access public * @return mixed object on success false on failure or '0' when not linked */ function getLink($field, $table= false, $link='') { static $cache = array(); // GUESS THE LINKED TABLE.. (if found - recursevly call self) if ($table == false) { $info = $this->linkInfo($field); if ($info) { return $this->getLink($field, $info[0], $link === false ? $info[1] : $link ); } // no links defined.. - use borked BC method... // use the old _ method - this shouldnt happen if called via getLinks() if (!($p = strpos($field, '_'))) { return false; } $table = substr($field, 0, $p); return $this->getLink($field, $table); } $tn = is_string($table) ? $table : $table->tableName(); if (!isset($this->do->$field)) { $this->do->raiseError("getLink: row not set $field", DB_DATAOBJECT_ERROR_NODATA); return false; } // check to see if we know anything about this table.. if (empty($this->do->$field) || $this->do->$field < 0) { return 0; // no record. } if ($this->cached && isset($cache[$tn.':'. $link .':'. $this->do->$field])) { return $cache[$tn.':'. $link .':'. $this->do->$field]; } $obj = is_string($table) ? $this->do->factory($tn) : $table;; if (!is_a($obj,'DB_DataObject')) { $this->do->raiseError( "getLink:Could not find class for row $field, table $tn", DB_DATAOBJECT_ERROR_INVALIDCONFIG); return false; } // -1 or 0 -- no referenced record.. $ret = false; if ($link) { if ($obj->get($link, $this->do->$field)) { $ret = $obj; } // this really only happens when no link config is set (old BC stuff) } else if ($obj->get($this->do->$field)) { $ret= $obj; } if ($this->cached) { $cache[$tn.':'. $link .':'. $this->do->$field] = $ret; } return $ret; } /** * get link information for a field or field specification * * alll link (and join methods accept the 'link' info ) in various ways * string : 'field' = which field to get (uses ???.links.ini to work out what) * array(2) : 'field', 'table:remote_col' << just like the links.ini def. * array(3) : 'field', $dataobject, 'remote_col' (handy for joinAdd to do nested joins.) * * @param string|array $field or link spec to use. * @return (false|array) array of dataobject and linked field or false. * * */ function linkInfo($field) { if (is_array($field)) { if (count($field) == 3) { // array with 3 args: // local_col , dataobject, remote_col return array( $field[1], $field[2], $field[0] ); } list($table,$link) = explode(':', $field[1]); return array( $this->do->factory($table), $link, $field[0] ); } // work out the link.. (classic way) $links = $this->do->links(); if (empty($links) || !is_array($links)) { return false; } if (!isset($links[$field])) { return false; } list($table,$link) = explode(':', $links[$field]); //??? needed??? if ($p = strpos($field,".")) { $field = substr($field,0,$p); } return array( $this->do->factory($table), $link, $field ); } /** * a generic geter/setter provider.. * * provides a generic getter setter for the referenced object * eg. * $link->link('company_id') returns getLink for the object * if nothing is linked (it will return an empty dataObject) * $link->link('company_id', array(1)) - just sets the * * also array as the field speck supports * $link->link(array('company_id', 'company:id')) * * * @param string|array $field the field to fetch or link spec. * @params array $args the arguments sent to the getter setter * @return mixed true of false on set, the object on getter. * */ function link($field, $args = array()) { $info = $this->linkInfo($field); if (!$info) { $this->do->raiseError( "getLink:Could not find link for row $field", DB_DATAOBJECT_ERROR_INVALIDCONFIG); return false; } $field = $info[2]; if (empty($args)) { // either an empty array or really empty.... if (!isset($this->do->$field)) { return $info[0]; // empty dataobject. } $ret = $this->getLink($field); // nothing linked -- return new object.. return ($ret === 0) ? $info[0] : $ret; } $assign = is_array($args) ? $args[0] : $args; // otherwise it's a set call.. if (!is_a($assign , 'DB_DataObject')) { if (is_numeric($assign) && is_integer($assign * 1)) { if ($assign > 0) { if (!$info) { return false; } // check that record exists.. if (!$info[0]->get($info[1], $assign )) { return false; } } $this->do->$field = $assign ; return true; } return false; } // otherwise we are assigning it ... $this->do->$field = $assign->{$info[1]}; return true; } /** * load related objects * * Generally not recommended to use this. * The generator should support creating getter_setter methods which are better suited. * * Relies on .links.ini * * Sets properties on the calling dataobject you can change what * object vars the links are stored in by changeing the format parameter * * * @param string format (default _%s) where %s is the table name. * @author Tim White * @access public * @return boolean , true on success */ function applyLinks($format = '_%s') { // get table will load the options. if ($this->do->_link_loaded) { return true; } $this->do->_link_loaded = false; $cols = $this->do->table(); $links = $this->do->links(); $loaded = array(); if ($links) { foreach($links as $key => $match) { list($table,$link) = explode(':', $match); $k = sprintf($format, str_replace('.', '_', $key)); // makes sure that '.' is the end of the key; if ($p = strpos($key,'.')) { $key = substr($key, 0, $p); } $this->do->$k = $this->getLink($key, $table, $link); if (is_object($this->do->$k)) { $loaded[] = $k; } } $this->do->_link_loaded = $loaded; return true; } // this is the autonaming stuff.. // it sends the column name down to getLink and lets that sort it out.. // if there is a links file then it is not used! // IT IS DEPRECATED!!!! - DO NOT USE if (!is_null($links)) { return false; } foreach (array_keys($cols) as $key) { if (!($p = strpos($key, '_'))) { continue; } // does the table exist. $k =sprintf($format, $key); $this->do->$k = $this->getLink($key); if (is_object($this->do->$k)) { $loaded[] = $k; } } $this->do->_link_loaded = $loaded; return true; } /** * getLinkArray * Fetch an array of related objects. This should be used in conjunction with a * .links.ini file configuration (see the introduction on linking for details on this). * * You may also use this with all parameters to specify, the column and related table. * * @access public * @param string $field- either column or column.xxxxx * @param string $table (optional) name of table to look up value in * @param string $fkey (optional) fetchall key see DB_DataObject::fetchAll() * @param string $fval (optional)fetchall val DB_DataObject::fetchAll() * @param string $fval (optional) fetchall method DB_DataObject::fetchAll() * @return array - array of results (empty array on failure) * * Example - Getting the related objects * * $person = new DataObjects_Person; * $person->get(12); * $children = $person->getLinkArray('children'); * * echo 'There are ', count($children), ' descendant(s):
'; * foreach ($children as $child) { * echo $child->name, '
'; * } * */ function getLinkArray($field, $table = null, $fkey = false, $fval = false, $fmethod = false) { $ret = array(); if (!$table) { $links = $this->do->links(); if (is_array($links)) { if (!isset($links[$field])) { // failed.. return $ret; } list($table,$link) = explode(':',$links[$field]); return $this->getLinkArray($field,$table); } if (!($p = strpos($field,'_'))) { return $ret; } return $this->getLinkArray($field,substr($field,0,$p)); } $c = $this->do->factory($table); if (!is_object($c) || !is_a($c,'DB_DataObject')) { $this->do->raiseError( "getLinkArray:Could not find class for row $field, table $table", DB_DATAOBJECT_ERROR_INVALIDCONFIG ); return $ret; } // if the user defined method list exists - use it... if (method_exists($c, 'listFind')) { $c->listFind($this->id); while ($c->fetch()) { $ret[] = clone($c); } return $ret; } return $c->fetchAll($fkey, $fval, $fmethod); } }php-db-dataobject-1.11.5/DB_DataObject-1.11.5/DB/DataObject/createTables.php000077500000000000000000000047341277637267000255650ustar00rootroot00000000000000#!/usr/bin/php -q // +----------------------------------------------------------------------+ // // $Id: createTables.php 277015 2009-03-12 05:51:03Z alan_k $ // // since this version doesnt use overload, // and I assume anyone using custom generators should add this.. define('DB_DATAOBJECT_NO_OVERLOAD',1); //require_once 'DB/DataObject/Generator.php'; require_once 'DB/DataObject/Generator.php'; if (php_sapi_name() != 'cli') { PEAR::raiseError("\nERROR: You must turn use the cli sapi to run this", null, PEAR_ERROR_DIE); } if (!ini_get('register_argc_argv')) { PEAR::raiseError("\nERROR: You must turn register_argc_argv On in you php.ini file for this to work\neg.\n\nregister_argc_argv = On\n\n", null, PEAR_ERROR_DIE); exit; } if (!@$_SERVER['argv'][1]) { PEAR::raiseError("\nERROR: createTable.php usage:\n\n" .$_SERVER['argv'][0] . " example.ini\n\n", null, PEAR_ERROR_DIE); exit; } $config = parse_ini_file($_SERVER['argv'][1], true); foreach($config as $class=>$values) { $options = &PEAR::getStaticProperty($class,'options'); $options = $values; } $options = &PEAR::getStaticProperty('DB_DataObject','options'); if (empty($options)) { PEAR::raiseError("\nERROR: could not read ini file\n\n", null, PEAR_ERROR_DIE); exit; } set_time_limit(0); // use debug level from file if set.. DB_DataObject::debugLevel(isset($options['debug']) ? $options['debug'] : 1); $generator = new DB_DataObject_Generator; $generator->start(); php-db-dataobject-1.11.5/DB_DataObject-1.11.5/docs/000077500000000000000000000000001277637267000211065ustar00rootroot00000000000000php-db-dataobject-1.11.5/DB_DataObject-1.11.5/docs/LICENCE.txt000066400000000000000000000062221277637267000227130ustar00rootroot00000000000000-------------------------------------------------------------------- The PHP License, version 3.01 Copyright (c) 1999 - 2012 The PHP Group. All rights reserved. -------------------------------------------------------------------- Redistribution and use in source and binary forms, with or without modification, is permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The name "PHP" must not be used to endorse or promote products derived from this software without prior written permission. For written permission, please contact group@php.net. 4. Products derived from this software may not be called "PHP", nor may "PHP" appear in their name, without prior written permission from group@php.net. You may indicate that your software works in conjunction with PHP by saying "Foo for PHP" instead of calling it "PHP Foo" or "phpfoo" 5. The PHP Group may publish revised and/or new versions of the license from time to time. Each version will be given a distinguishing version number. Once covered code has been published under a particular version of the license, you may always continue to use it under the terms of that version. You may also choose to use such covered code under the terms of any subsequent version of the license published by the PHP Group. No one other than the PHP Group has the right to modify the terms applicable to covered code created under this License. 6. Redistributions of any form whatsoever must retain the following acknowledgment: "This product includes PHP software, freely available from ". THIS SOFTWARE IS PROVIDED BY THE PHP DEVELOPMENT TEAM ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE PHP DEVELOPMENT TEAM OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------- This software consists of voluntary contributions made by many individuals on behalf of the PHP Group. The PHP Group can be contacted via Email at group@php.net. For more information on the PHP Group and the PHP project, please see . PHP includes the Zend Engine, freely available at . php-db-dataobject-1.11.5/DB_DataObject-1.11.5/docs/example.ini000066400000000000000000000204651277637267000232510ustar00rootroot00000000000000; Example INI File [DB] ;portability = 15 ; if you are working with oracle (or plan to) - this will do things ; like force lowercase table names (see the PEAR DB manual [DB_DataObject] database = mysql://username:password@localhost/database ; the default database dsn see pear spec for more details schema_location = /home/me/Projects/myapplication/DataObjects ;directory where the database ini files are.. ; ini_{databaseName} = /path/to/ini/file.ini:/path/to_another_ini/file.ini ; you can also configure manually the ini_file location ; (and the links is expected to be in the ; same location) for each database by using the syntax ; use a PATH_SEPERATOR to load multiple files. ; BEST USED WITH database_{databasename} = dsn... ; links_{databaseName} = /path/to/databases/links.ini ; allows you to specify a specific links.ini file for a database ; or project. class_location = /home/me/Projects/myapplication/DataObjects ; directory where the Table classes are.. ; you can also use the format ; /home/me/Projects/myapplication/DataObjects_%s.php ; and %s gets replaced with the tablename. ; to use multiple search paths use the PATH_SEPERATOR require_prefix = DataObjects/ ; prefix for the 'require statement' class_prefix = DataObjects_ ; Prefix Mapping of table name to PHP Class ; to use multiple prefixes seperate them with PATH_SEPERATOR ;table_example = example ; use database example for table example ;database_example = mysql://username:password@host/database ; use this dsn for database example ;db_driver = DB ; can be set to MDB2 or DB ; uses MDB2 backend rather than PEAR DB ;disable_null_strings = false ; DataObjects will convert the text value 'null' to NULL when building queries ; this may cause problems! Setting to true will turn off this feature. ; you can use DB_DataObject_Cast::SQL('NULL'); in where you have to turn this off. ; ; can also be set to 'full' however - this may delete data quietly if properties are ; not fetched and are set *** Highly recommended not to use this.. ; the generator settings (optional) ;extends_location = ; string added to php file for 'include('DB/DataObject');' ; usefull if you want to add an extra layer - eg. a table level auth scheme.. ; optional :: default = DB/DataObject.php ; if set to empty, then no 'require_once' line will be added - usefull for ; autoloading. ;extends = ; string used in Table_XXX extends YYYY { ; usefull if you want to add an extra layer - eg. a table level auth scheme.. ; optional :: default = DB_DataObject ;generator_class_rewrite = ; alter the extends field when updating a class (defaults to only replacing DB_DataObject) ; use ANY to will replace all extends with either DB_DataObject or the value of extends (above) ; use specific_name to replace 'specific_name' with either DB_DataObject or the value of extends (above) ;generator_novars = ; (True) prevents writing of private/var's so you can overload get/set ; note: this has the downside of making code less clear... (alot of magic!!) ;generator_var_keyword = 'var' ; var|public - (or private if you want to break things) ; The variable prefix that is used when class properties are created ; the default is public for PHP5, and var for PHP4 ;generator_add_validate_stubs = ; (True) will insert / (or add to existing files) stubs for validate methods ;generator_no_ini = ; (True) will generate the methods table() ,keys(), sequenceKeys() and defaults() ; methods in the generated classes ; and not generate any ini file to describe the table. ;generator_add_defaults = ; (True) will generate defaults() method in generated classes ; returning an array of the defaults column values for the table. ;generator_omit_database_var ; (false) will prevent generation of $database line in generated files ;generate_links = false ; (true) will create the links.ini from the table definition ; Only available with mysql / mysqli / postgres at present. ;generate_link_methods = false ; (true) will create the wrappers around link() ; => function($k) { return $k; } // to munge the column name into a method name. ; Only available with mysql / mysqli / postgres at present. ;generate_setters = false ; (true) will generate setXXXX() methods for you. ;generate_getters = false ; (true) will generate getXXXX() methods for you. debug = 0 ; default debugging level debug_ignore_updates = 0 ; behaviour changed in > 0.10 so that you have to manually turn of updates ; rather than it automatically being done by setting debug to > 1 dont_die = 0 ; if you set this to true or 1, then dataobjects will not die ; on major errors like database connection etc. or programming errors ; - you probably want to do this on a live server - and ; write a pear error handler.. - to send you an email! ;quote_identifiers = 0 ; Quote table and column names when building queries - this relies on the ; quoteIdentifier method being available in PEAR::DB (it should be introduced in 1.5 final or 1.6) ;quote_identifiers_tableinfo = 0 ; Quote table and column names when generator sends tableinfo to request schema ; Note - this can be on for most databases, but must be off for Oracle (see bug #10112) ;keep_query_after_fetch = 0 ; From 1.4 on the query building will be cleared after a fetch(1.5), or find(1.4) ; To disable this behavior set this to 1 dont_use_pear_sequences = 0 ; If a table does not have a Native (autoincrement etc.) - and it has a primary ; key, it is assumed that the table should use pear's getNextID to ; emulate sequences.. - if this is not required set this to 1 ;generator_include_regex = /^FOO_.*/i ;generator_exclude_regex = /^tmp_.*/i ; include and exclude tables from being read by the generator build_views = 0 ;for postgres, you can build dataobjects for views as well ; you can set this to 'schema.views' to extract views with schema information ; I believe it postgres also supports updating on views (nice feature) ; *** NOTE *** You will have to manually define keys() / sequenceKeys() ; As the generator can not recognize these automatically ;generator_strip_schema = 1 ; postgres has a wierd concept of schema's which end up prefixed to ; the list of tables. - this makes a mess of class/schema generation ; setting this to 1, makes the generator strip the schema from the table name. ; now supports regex (if you set it to a regex it will strip schema of matched names) ; for example '/^public\./' ;generator_secondary_key_match = primary|unique ; set to false to prevent generation of any unique or primary keys that are not autoincrement/nextval() ; portability = 0 ; similar to DB's portability setting, ; currently it only lowercases the tablename when you call tableName() ; ---------- DEPRECIATED -------------- ;ignore_sequence_keys = ; THIS IS DEPRECIATED NOW - define the method sequenceKey() to return array(false,false) ; to stop useage of sequence keys. ; see sequenceKey Manual page for more details. ;sequence_{tablename} = {column} ; THIS IS DEPRECIATED NOW - define the method sequenceKey() to return array(false,false) ; specify which column is autoincremented/nextval'd ;sequence_{tablename} = {column}:{sequence_name} ; THIS IS DEPRECIATED NOW - define the method sequenceKey() to return array(false,false) ; specify which column is nextval'd, and the sequence name to use for the column. php-db-dataobject-1.11.5/DB_DataObject-1.11.5/scripts/000077500000000000000000000000001277637267000216455ustar00rootroot00000000000000php-db-dataobject-1.11.5/DB_DataObject-1.11.5/scripts/DB_DataObject_createTables.bat000066400000000000000000000010471277637267000273620ustar00rootroot00000000000000@ECHO OFF REM $Id: DB_DataObject_createTables.bat,v 1.1 2003-09-08 20:43:31 arnaud Exp $ REM BATCH FILE TO EXECUTE PEAR::DB_DATAOBJECT createTables.php script IF '%1' == '' ( ECHO ************************************************** ECHO * Please pass the name of the ini file ECHO * to process at the only parameter ECHO * ECHO * e.g.: DB_DataObject_createTables my_ini_file.ini ECHO ************************************************** GOTO :EOF ) %PHP_PEAR_PHP_BIN% %PHP_PEAR_INSTALL_DIR%\DB\DataObject\createTables.php %1php-db-dataobject-1.11.5/package.xml000066400000000000000000002057261277637267000172210ustar00rootroot00000000000000 DB_DataObject pear.php.net An SQL Builder, Object Interface to Database Tables DataObject performs 2 tasks: 1. Builds SQL statements based on the objects vars and the builder methods. 2. acts as a datastore for a table row. The core class is designed to be extended for each of your tables so that you put the data logic inside the data classes. included is a Generator to make your configuration files and your base classes. Alan Knowles alan_k alan@roojs.com yes 2015-11-10 1.11.5 1.9.0 stable stable PHP License 3.01 Minor feature and fix release. Changes #---- - force usage of tableName - rather than direct access to __table #---- - autoJoin: check joined tables against database structure Bugs #20536 - generator spaces after dataobject variables Feature requests #20540 - support empty extends_location so that no require_once is generated by Generator 5.0 1.4.0b1 Date pear.php.net 1.4.3 DB pear.php.net 1.7.0 MDB2 pear.php.net 2.0.0RC1 Validate pear.php.net 0.1.1 2015-04-08 1.11.4 1.9.0 stable stable PHP License 3.01 Minor feature and fix release. (Basically a yearly release now..) #20291 Ignored in update $dao->field = 0 (fix bad check for null) #20149 - correct free for linked objects / more detailed error reporting on update error #19962 - Cannot insert or update when part of a reference in multi-key case is NULL by vtamma #19973 - accept START TRANSACTION as an alias for BEGIN - suuport for useindex (mysql only) and #19922 - pear error not being set in static property. - fix static infront of depricated staticget() method and fix call to PEAR:: static calls. - Fix some static errors that are now fatal unfortunatly... - fix building of autojoin when using distinct columns 2014-04-22 1.11.3 1.9.0 stable stable PHP License 3.01 Minor feature and fix release. (Basically a yearly release now..) #19922 - fix for pear error not being set in static property. #19973 - accept START TRANSACTION as an alias for BEGIN #19962 - Cannot insert or update when part of a reference in multi-key case is NULL by vtamma #20149 - correct free for linked objects / more detailed error reporting on update error - fix building of autojoin when using distinct columns 2013-04-05 1.11.2 1.9.0 stable stable PHP License 3.01 (re-release of 1.11.0 - to fix archive tar issue. / fix dependancy) Minor feature and fix release. (Basically a yearly release now..) Dependancies MDB2 and DB are now optional dependancies, although one of them is required. AutoJoin - improvements - support sqlValue and DB_DataObject_Cast values in setFrom() - correct filtering - diff and intersect... so additional cols do not get injected by exclude.. - autoJoin( "exclude" => array( "col1", "col2") ) should now exclude the columns from selectAdd/As - correctly handle include in autojoin - based on derived column names - autojoin now supports include and distinct, and returns count as the column to count on + handles Closure on debugging output of config Other Changes - add limited support for building derived table queries - which can speed up mysql queries hugely when using distinct/limit Removed / changed / depricated - remove PHP4 support - Make staticGet depricated in generator and main code (move to end of file with rest of depricated stuff.. Minor fixes - fix signature on depricated staticGet method - fix typo in links code DO::getLinks() - fix is_a syntax error - RTFM might be a good idea - check return value from generator - remove blob test from stringtostring code.. Bug Fixes #19697 - str_replace only if needed to reduce memory - and move staticAutoloadTAble to depricated section #19637 - strip support with regex broke boolean settings #19640 - indentation not correct on generator #19500 - use same way that pear db mssql driver quotes for regular strings (tidy up) #19500 - support for mssql in DataObject Cast #19505 - simplify the proxy loading code detection and usage. #19479 - simpler test should do the job #19479 - link loaded test was incorrect #19475 - comment out a few echos that got left in 2013-04-03 1.10.0 1.9.0 stable stable PHP License 3.01 Minor feature and fix release. #----- - Fix transactions - query(BEGIN) did not actually run when called. #----- - Refactor Link and join code - link code moved to DB_DataObject_Link, new option generate_link_methods, new method link() - make the links option work with autojoin, add setter feature to links() - support links option on autojoin - add support for excluded columns on autoJoin - more detailed check on assigning numbers in link() - new link syntax in generated methods - make links() support same array syntax as joinAdd(), and change links() to support input as func_get_args() for the second argument, cleans up wrapper code.. - support joinAdd(array("local_col", $dataObject, "remote_col"), "LEFT"), extra debugging on count(), and case sensitive checking on numRows (not sure if that was needed..) #----- - remove notes relating to MDB as they are not relivant anymore #----- - SVN/git has new directory layout #----- - Add pid() - a quick way to get the value from the primary "id" column of the database #----- - add sqlValue() - a factory method to create a DB_DataObject_Cast Object #----- - nice warning on failed to write to temporary file #----- - fixes to correctly support portability in selectAs() #----- - fixes to correctly support portability in joinAdd #----- - lower case links and ini during load, when portability set, fix handling in joinAdd #----- - add experimental support for posgresql introspected native keys #----- - move assignment to error in raiseError after it actually happens, this should ensure _lastError gets set correctly as the aliased setting does not appear to work very well.. #----- - find() will return false when an error occurs (rather than 0) #18995 - Remove error_reporting settings from tests (closes bug #18995). #18931 - typos in comments - rasmus would appreciate this one... (depreciated to depricated) #----- - support ini_database in foreign key generation, and regex matching on strip schema #----- - document change to generator_strip_schema which now supports regexp to strip from matching schemas #----- - add support for portability in tableName() - most references to tablename will now call tablename, so that code working on mysql can work on postgres, where tables are mixed case in mysql. #----- - document build_views change #----- - support _TEXT type (postgres view returns this.) #----- - support build_views=schema.views for building dataobjects from views with schemas #18767 - use argv0 for the usage line, and ensure that is is run from cli sapi 2011-08-29 1.9.6 1.9.0 stable stable PHP License 3.01 Small fix release. #18749 - Fix is_a() for 5.3.7+ due to php-core stupidity #18602 - optimize fetch - thanks to shadesofgraylin #18605 - optimize sprintf in toArray() #18446 - use table() method rather than accessing static global. #----- - toArray support for only returning returned columns use "0" as the second parameter #----- - allow tableless dataobject connect() call to work #----- - disable overload using DB_DATAOBJECT_NO_OVERLOAD on PHP5 - Im supprised no one ever noticed this - overload is EVIL ;) #----- - Add autoJoin method, which will build a full join with all related tables. #16867 - fix doc comments - change to public methods for the ones that are supposed to be overridden #17637 - fix detection of non-existant links files resulting in defaulting to autolinks 2010-07-07 1.9.5 1.9.0 stable stable PHP License 3.01 Small fix release. fix bug #17554 - update when id=0 (or primary key is 0) - thanks to the persistance of Jacek Pawlowski 2010-06-21 1.9.4 1.9.0 stable stable PHP License 3.01 Bug Fixes #17508 - whereAddIn used wrong method to quote strings #6202 - better docs on getLinkArray - needs documentation as well really.. #17343 - tidy up padding code, - fix alignnment of comments in generated files (Original Patch by madsliejensen) #17289 - fix sequence_<table> = XXXXX (madsliejensen) #17157 - use property_exists in newer versions of php. New Features #7590 - union support - thanks to Ian Carmichael #----- - whereAdd in support for negative (prefix column with '!') and empty arrays forces 1=0 query. #13798 - add hookpostvar for adding things like varchar len data etc.. as per req #16267 - Linking Multiple foreign keys - supports links.ini id[] = table1:col1 id[] =... #17049 - generate links.ini for postgres - by madsliejensen 2010-01-15 1.9.3 1.9.0 stable stable PHP License 3.01 Fix broken release (1.9.1 and 1.9.2) fetchAll() / whereAddIn() Release version. This version adds two new utility methods, that can speed up usage, fetchAll() - does the $do->find() / while($do->fetch()) $ar[] = clone($do) loop, and more. whereAddIn() - allows you to quickly build IN queries, with correct escaping. 2009-12-03 1.9.0 1.9.0 stable stable PHP License 3.01 Modular Release version. This version was designed to support multiple locations of DataObjects for use with modular applications. - NOTE the change to ini_DATABASENAME may fixes the previous behaviour to match the documented behaviour - however this may break existing installations. New Features: #------- class_location accepts multiple locations with PATH_SEPARATOR #------- class_prefix accepts multiple prefixes with PATH_SEPARATOR #------- ini_DATABASENAME merges all found ini files in paths (seperated by PATH_SEPARATOR) previous behaviour was just to read the first. - this applies to matching links.ini files. #12550 - disable_null_strings=full -- see the bug report for details - it is highly recommended not to use this feature, as it may result in data being deleted from your database if not used carefully. Notes: #------- staticGet now marked depreciated. #------- links_DATABASENAME does not support multiple paths #------- staticAutoloadTable (depreciated) and staticGet (depreciated) do not support this feature 2009-08-12 1.8.12 1.8.6 stable stable PHP License 3.01 Licence upgrade Release #----- - Changed Error and Cast Licence to 3.01 to keep Debian/Ubuntu happy. 2009-07-16 1.8.11 1.8.6 stable stable PHP License 3.01 Licence upgrade Release #----- - Licence changed to 3.01 to keep Debian/Ubuntu happy. #----- - Change order of preference for links in JoinAdd - prefer called table links over jointo table. #----- - Retry support for connection failure on queries #----- - Factory now uses '/' for database/table seperator (experimental) #----- - Fix INSERT on Postresql with empty data. 2009-03-19 1.8.10 1.8.6 stable stable PHP License 3.01 Annual Release - Minor Bugfixs / Extra Features #----- - Fix factory error with database name not found #----- - Change factory multi-database call to use '/' rather than '.' so that it can work with postgres (also syncs with what the original notes said) 2009-03-12 1.8.9 1.8.6 stable stable PHP License Annual Release - Minor Bugfixs / Extra Features #15066 - limited support for POINT (at least generator creates something) #14976 - add option generator_omit_database_var to prevent creation of $database lines (Troy Anderson) #13995 - incorrect test in sequence key for postgres #14252 - doc comments on delete() corrected to say return value is no. of rows affected on success ###### - ensure that tableinfo uses quote_identifiers_tableinfo ###### - add sqlite support to Cast blobs ###### - old skipEmpty check broke formating on setFrom ###### - remove previous checks on database dsn lookup - move it to factory. ###### - Fix count check for no keys. - By Jeffrey Pfau ###### - Add multidatabase factory() support - eg. DB_DataObject::factory("mydatabase.mytable"); ###### - Added checks to ensure that database names match requested ones. #13773 - delete(true) honours join conditions #13788 - test for error before re-writing definition table for MDB2 in Generator #13789 - honour user specified MDB2 options in Generator #13790 - support CLOB oracle #12993 - add new option generator_secondary_key_match - set to false, to disable key matching for secondary key types 2008-01-30 1.8.8 1.8.6 stable stable PHP License Minor Bugfix / Feature Release Pre-chinese new year release (with lots of 8's...) Bug Fixes #12556 - Generator - pregmatch appears broken for replacing class name on updating files #10112 - additional config option quote_identifiers_tableinfo for use with tableinfo on generator Fixes issues with Oracle and quoting identifiers. #11773 - problems with object->array[prop] setting on PHP5.2.3 ** this may also have been fixed twice by fix for #11775 #12858 - MDB2 and postgres missing db_driver variable #9902 - default portability options for MDB2 to not fix case. #9658 - postgres DECIMAL using DB backend incorrectly flagged as unknown - changed warning to suggest using MDB2 #11091 - count attempted to read quote identifiers before config was loaded #11804 - insert with postgres/msql return wrong error message #9364 - fix postgres native sequences with MDB #11775 - unsetting of $this->_query causes overload setters/getters to handle later uses of _query and break due to lack of array accessor support #11634 - Windows fails to rename files if they exist - affecting Generator (try and fix it so unix is still atomic) 2007-07-12 1.8.7 1.8.6 stable stable PHP License Minor Bugfix Release Bug Fixes: #11586 - missing default value in Generator for class_prefix caused warning #11584 - typo in Generator caused permissions to be incorrectly set on ini file. 2007-07-10 1.8.6 1.8.6 stable stable PHP License Minor Bugfix and Minor Feature addition release Bug Fixes: #11483 - show an error if pcre limits are not correctly set and generator tries to generate empty files #11528 - sending like queries to escape does not escape % and _ - added additional parameter to escape : [bool $likeEscape] is optional and can be used to force escaping of these characters #10573 - fix permissions on generated files #10573 - write Generated files to temp directory, then rename into place. - fixing locking issues #10531 - correct fix for bug #9628 (support MDB2 for defaults()) #10454 - mssql uses wrong variable name for options. #10175 - MDB2 does not return length value #9573 - enable disable_null_strings to turn of "null"-> NULL replacements #9625 - MDB2 support for MSSQL sequencies #9644 - incorrect constant in foriegn key generation. #9628 - support MDB2 for defaults() #9839 - Invalid column names can not be supported, so warning is generated #9834 - SQL2003 standards on Joins - adds brackets around ON() #4266 - minor fix to appendJoin code #9755 - generator showed php error if connection failed #9730 - quote identifiers on composite joins * feature request #4266 - joins with multiple keys #2216 - JOIN ON conversion of Where condition into ON arguments - patch by David Sanders #---- add in docbook hooks to generator - as requested/coded by Vyacheslav Iutin 2006-12-15 1.8.5 1.8.5 stable stable PHP License Minor Bugfix and Minor Feature addition release * #8876 - call free() after count - hopefully this will not break anything! * #8736 - auto_increment check for MDB2 backend * #8628 - tableInfo fix for MDB2 * #7792 - add integer[] and boolean[] support * #7702 - memory leaks on getLinks() , free() is called within getLinks(), - you still need to call free() after doing a getLink() - as I suspect automagically doing that would cause problems.. * #7409 - ibase support for filename based database names - not sure if this works, but will ask reporter to test it.. * #7353 - postgres incorrect testing of t/f for booleans * #7114 = generator of sequence keys and defaults using generator_no_ini * #3773 - change last_value to currval - need to confirm this is the correct fix. * #3152 - joinAdd requires join column to be in links.ini file Feature Requests: * Feature Request #4266 - Allow joins with multiple keys Minor Changes: * dont flush on debugging html output * links.ini code generation based on schema (mysql[i] only) - option:generate_links=true - thanks to Pacal Schoni * add warning about unknown types in generator. * only prefix database name, in joins when the two objects are of different databases * nicer error message for !isreadable - thx to Euan Maxwell * use class exists loader, so that relative test relative paths in tests work, (also means that autoload may work...) * remove pass by reference - why on earth that was there in the first place is totally beyond me... * give derivedHookFunctions the ability to use the original input data * add methods getClassNameFromTableName(), getFileNameFromTableName(), getMethodNameFromColumnName(), which allow you to set up your own mapping system by overriding them in your extended generator.. it however needs some support in dataobjects so currently flagged as EXPERIMENTAL 1.8.4 1.8.4 stable stable 2006-03-06 PHP License Minor Bugfix and Minor Feature addition release Bugs ---- #7006 - fix links due to removal of databaseStructure call in 1.8.3, added call to connect(); #7012 - joinAdd fixed due to checking of wrong variable when buidling query on mysql. Features -------- - Add support for defaults() generation in createTables/Generator, when using the flag generator_add_defaults = yes or generator_no_ini = yes a method defaults() is created in the table class, which contains the default values for the table generated from the database. Note: EXPERIMENTAL and only works with Mysql[i] at present. 1.8.3 1.8.3 stable stable 2006-03-03 PHP License Minor Bugfix and Minor Feature addition release Bugs ---- - Remove Warning if class_prefix is not set, and allow it not to be set. - joinAdd() support for DB_DataObject::cast() objects (Daniel Braga) - MDB2 fixes for undefined constants (bate) - #6772 - Support for FLOAT4 type in postgres #6953 - Changed memory allocation of stored column values for SQL return when Fetching last item of an object. Fixing problem of missing columns on Last fetch from Join being broken when used with toArray(). #6928 - Defaulting to prefixing database name in joins for Mysql and Mysqli backends, so multiple joins across multiple databases work. Features -------- - Add support for option 'generator_no_ini => true', which generates class files with schema defined inside the table(), keys() and sequenceKeys() (based on work and suggestions by Didler Galland and Justin Patrin) - SQL output for debugging now has line breaks to make it easier to read. - SelectAs uses quote itendifiers for target format. 1.8.2 1.8.2 stable stable 2004-01-21 PHP License Minor Bugfix release Bugs ---- #6543 - Generator broke due to MDB changes. 1.8.1 1.8.1 stable stable 2006-01-20 PHP License Bugs ---- #6153 - dont call autoload on PHP5 - kludgy wrapper around class_exists() #6458 - clear staticGet cache when calling free() #6442 - validation not working correctly on DB_DataObject_Cast Objects Changes/New Features -------------------- * fix warning if setFrom called with object.. * make createTables honour debug level in ini file. * Added support in generator, for MDB2 backend to generate the class files (bate) 1.8.0 1.8.0 stable stable 2005-12-28 PHP License Major Bug Fix & Feature release. Bugs: #4871 - remove warning when value is not found in table #4834 - case sensitive checks for db_dataobject_cast fail #4894 - Validate methods can return PEAR Errors, or even messages now.. - only true indicates success #4938 - support MONEY type for MSSQL #4968 - count() will not be affected by changing default db fetchmode #5036 - make sure DB is loaded before using it #5264 - add optional dep on MDB #5416 - use tablea AS tableb only for postgress/mysql - oracle doesnt need the AS bit - and I guess a few others too.. available and first fetch fails conditions #5614 - joinAdd check to see if object being joined is really a dataobject #5065 - count returns number cast to an integer (even though database may return a string) #5800 - generator will continue creating other tables if we find one that fails... #5881 - wrap conditions with extra brackets remaining checks in validate #5393 - flag up unique only keys as "U", rather than "K" (or "N" for native) to allow anything calling keys() to find more detailed information - should have no effect on the dataobject class, as only sequence keys looks for "K" Feature Requests: #5146 - option: generator_var_keyword, to specify if you want to use var or public Other Changes default behaviour.. so it shouldnt really have affected anyone.. amazed no one picked this up before.. * make WHERE stripping better = well spotted Justin * catch errors returned from nextID - like permission denied etc., add warnings in debug mode if we are using objects or arrays for some reason, fix error messages * correct the dependancy on packages * the dont die here causes a recursive error and segfaults everything if it doesnt return.. * use is_a, rather than PEAR, as one day we may remove that dep... * fix PHP4.4* issues with returning by reference.. * make debugging schema loading errors a bit easier * test for bug #5931 added to test 91 * update($dataobject), change check for changes to strict checking, so that 0001 -> 00001 works, along with many other situations.., remove extra ; from code * fix null detection on postgres, - patch by Alistair MacDonald * free result columns as well as results after fetching all rows. 1.7.15 1.7.15 stable stable 2005-07-07 PHP License Minor Bugfix release * fix bug #4760 - DB_DataObject_Cast blobs broke in last release 1.7.14 1.7.14 stable stable 2004-06-25 PHP License Bugfix release (Mostly) Special thanks to Dan Rossi for the MDB Backend work on this release. Key changes: - SQLlite support for files. - MDB Backend support (experimental) use: db_driver = MDB as a config option. NOTE: the generator will probably not work. - toArray() has an extra argument (bool)hideEmpty, which prevents it returning key/value pair when the value is empty - DB_DataObject_Cast object was redesigned to enable better quoting of blobs - PostgresSQL schema for tablenames can be removed with the generator_strip_schema = 1 - Fetch return value and object->N are now "true", rather than 1 when numRows is not supported, (also document it on find()) Bugs Fixed. * fix bug #4672 - query(COMMIT) relays return value from DB->commit() * fix bug #4535 - fix update/select etc. for postgres with boolean values. * fix bug #4601 - wrong method used to test if file for sqlite * fix bug #4193 - sqlite names causing invalid .ini files * fix bug #4518 - escape and transactions fixed for MDB backend * fix bug #4431 - add hideEmpty to toArray() - not sure if I really like this.. - adding args to methods, the root of all evil ;) * fix bug #4383 - generator getListOf() call now expects pear errors * fix bug #4235 #4203 - schema mess with postgres. generator_strip_schema=1 will strip the schema part from the name of the table. * fix bug #4168 - if connection test is sucessfull, dont try and connect.. (performance improvement) * fix bug #4040 - prevent toArray from calling getLink and getLinks * fix bug #4019 - createTables uses sets up options for DB compatibility mode * fix bug #4000 - better checking for php 4.3.10 * fix bug #3944 - support for informix autoincrements * fix bug #4182 - better string and blob support on mysql in cast * fix bug #4135 - boolean values borked in condition building.. 1.7.13 1.7.13 stable stable 2005-03-24 PHP License Minor Bugfix release #3943 - postgres booleans broke again due to last fix for mysqli. 1.7.12 1.7.12 stable stable 2004-03-23 PHP License Daily Bugfix release. ;) #3898 - mysqli quote bug fixed. (Greg Beaver) #------ provisional support for mysql blobs in Cast object (Antony Jankelowitz) 1.7.11 1.7.11 stable stable 2005-03-22 PHP License Minor Bugfix release. #3898 - fix count , which got broken by previous distinct argument changes #------ support $x IS NOT NULL using $obj->value = DB_DataObject_Cast::sql("NOT NULL"); 1.7.10 1.7.10 stable stable 2005-03-16 PHP License Minor Bugfix release. #3829 - joinAdd fix for database prefixing (yet again) 1.7.9 1.7.9 stable stable 2005-03-12 PHP License Minor Bugfix release. #3764 - toValue(), and hence toArray() cast booleans to true|false. (previous fix was buggy) 1.7.8 1.7.8 stable stable 2005-03-12 PHP License Minor Bugfix release. #3764 - toValue(), and hence toArray() cast booleans to true|false. #3763 - createTables.php defines NO_OVERLOAD to prevent warnings on PHP4.3.10. #3768 - Hopefully last fix to joinAdd() / database prefixing and quoteIdentifiers #3761 - Fixed DB_DataObject::datetime() bad code. #3783 - count('distinct') builds the query distinct [primary_key], should help in some situations using joinAdd + group By etc. 1.7.7 1.7.7 stable stable 2005-03-07 PHP License Minor bug fix release - join add with table fixes broke for mysql with last release Bug Fixes: #---- - Joins incorrectly prefect database name for mysql/mysqli (Thanks to Mark Mitchenall for spotting this) #---- used wrong variable for checking prefixes. (Stephane Gully) 1.7.6 1.7.6 stable stable 2005-03-05 PHP License Nothing like a big release to create bugs.. Note: the last release also included method creation for setters and getters, which got missed out from the changelog. Bug Fixes: #3708 - Generator did not clear last files contents when generating new files. #3706 - Joins broke on everything except mysql (database prefix now only added to mysql) 1.7.5 1.7.5 stable stable 2005-03-02 PHP License A few releases where skipped, this should be the amalgamated upgrade! Note: php4.3.10 will exit with an error if you dont define NO_OVERLOAD, rather than segfaulting.. Generator is buggy with 5.0.3 - upgrade DB or PHP.. Bug Fixes: #3584 - Cast now supports time and datetime, (theiron) #3645 - PHP4 clone FC hack now calls $obj->__clone() in a similar way to PHP5 #2928 - lastError contains the last error when query bugs out now. #2674 - column names null / true /false etc. now halt createtables #2778 - more informative warning shown when postgres bug is found. #---- - experimental multidatabase join support #2739 - sleep is relayed into __call - bug in PHP5 #2565 - oci8 NUMBER type recoginzed #---- - new option generate_add_validate_stubs = 1, creates validate stubs. #---- - more comprehensive limit support (Lutz Thomas) #---- - allow access to columns starting with _ eg. set_xxx() & get_xxx() (Sandro) #---- - Generator write E_STRICT code for PHP5 now #2441 - Debugging code removed. #---- - multiple HAVING calls now supported. #2468 - remove warning hider. #2318 - update($do) when no changes detected returns true. #2319 - joinAdd does not use AS when table and target have same name. #---- - Dia diagram generation tools available in CVS (build diagrams from dataobjects) #2267 - additional debugging information available at connect time 1.7.2 1.7.2 stable stable 2004-08-28 PHP License Minor bug fix / Minor feature addition release Bug Fixes: #1835 - more fixes to ensure that joinAdd works correctly. #2106 - Update with old object having no changes does not raise an error #---- - fixed problem of call() code producing parse error on PHP5.0.1 #2205 - fixed case sensitivity of PHP5 call() #---- - fixed undefined index in delete after a fetch() Features Added: #2086 - Support for buliding dataobjects for Views (use option build_view=1) #2240 - factory() called on a dataobject returns a new instance of that object. 1.7.1 1.7.1 stable stable 2004-08-09 PHP License Minor bug fix release Bug Fixes: #2088 - class_prefix may be left out. #------ php4.2 compatibility should work as advertised in last realease #------ Generator didnt have DB loaded due to lazy load changes. 1.7.0 1.7.0 stable stable 2004-06-03 PHP License Major bug fix release, Minor features added. Bug Fixes: #1559 - __FUNCTION__ remove, so dependancy on php 4.2 may still be valid. #1595 - regex include/exclude working correctly (Thanks to Stephane Gully) #1664 - configuration line links_{databasename} now available #1726 - enable orderby and limit can be used with delete() #1697 - prevent generator from adding an extra line created after definition. #1704 - update only adds null when the column is not marked 'not null' #1778 - toArray() returns a merge of defined table and actual results #1828 - Generator will die, if tableinfo is not supported. #1833 - links() now returns an empty array (no links) or null (no links.ini) so it can be used generically with : $ar = (array) $do->links(); #1835 - getLinks, getLink etc. now use ->links(), so you can define your own links() to return table links, rather than use links.ini files. #1923 - removed all @ silencers, and replace with empty/isset. #1935 - selectAs() - removed double quoteIdentifiers #2068 - workaround for DB bug, postgres nextval() being rawurlencoded #1980 - logic error when using quoteIdentifiers Changes: * DebugLevel(1) now displays native errors from database when queries fail. * Result Field names now stored seperately from Result Object * free() method added, to clean up memory instantly, if you are doing alot of queries. = Cleans Result Fields, Result Object, NumRows returned in Connection object. * include path is used to do file hunting if factory fails to find file. * DB is lazy loaded on connection.b 1.6.1 1.6.1 stable stable 2004-06-03 PHP License Minor Bug Fix * fixed count() breaks when countWhat used. 1.6.0 1.6.0 stable stable 2004-04-24 PHP License Minor Feature Release and many bug fixes.. * DB_DataObject_Error added to enable testing for dataobject errors * Date fixes - needs latest date package. * Generator fails with clear message if backend doesnt support tableInfo * added cidr, inet, macaddr for postgres * new options to filter generation of classes generator_include_regex = /foo_.*/i generator_exclude_regex = /tmp_.*/i * result column list is now stored for all queries (in the result object) so toArray() allways relates to the query. * php version correted in package file. * getLinks called before other database call fixed. * Updating without changing things, no longer raises a pear error, it just returns 0 (eg. no rows affected) * now Depends on 1.4 DB - escapeSimple is now used * extra option to disable assumtions to use pear db's nextID dont_use_pear_sequences = true * DebugLevel($val) if $val is not an integer - it is assumed to be a callback. * fixed clearing of data results after last fetch (reduces memory consumption when doing many queries) * strtolower any class name tests. * count accepts 1 or 2 arguments $countWhat or $whereAddonly, or $countWhat and $whereAddonly eg. $countWhat = "distinct id" eg. $countWhat = true (means $whereAdd = true) * selectAdd() returns existing value, so you can modify it. * most debugging & raiseError calls are dynamic, so you can overload them. * numbers allowed in tablenames. * ini filename uses database_{projectname} => {projectname}.ini so database names are portable, and multiple projects are theoretically feasible. * __clone is replaced with clone() for PHP5 compaitiblity. * use PEAR::getStaticProperty("DB","options") for oracle portablity settings. * null checked against not null spec. And probably a few more - as it's been quite a while since the last release.. 1.5.3 1.5.3 stable stable 2004-01-29 PHP License Bug fix release. Changes: - fixed toValue() ** previously did not work correclty.. ** getXXX($format) correctly relays into toValue - see strftime / Data::format or sprintf for formats, depending on column type. Fixes * fix bug #630 - correct handling of YEAR (it's an int) * fix bug #630 - mysql timestamps are ingored for updated and insert (use query() if you want to change them manually. * fix bug #620 - limit accepts floats and doubles, as long as they are really integers.. 1.5.2 1.5.2 stable stable 2004-01-24 PHP License Bug fix release. Changes: - removed automatic strtotime() in setters and setFrom ** this is closer to the previous behaviour (it caused too many problems with strange dates ** if you set a date/time column with a number, it assumes it is a unixtimestamp -- enables you to write setters like function setSome_Date($value) { $this->fromValue('some_date',strtotime($value)); } Fixes * $DB_DataObject->getDatabaseConnection() will return a pear error if dont_die is set, so it can be used to test connections. * note changed on databaseStructure, it can be called statically and dynamically. * fix bug #612 databaseStructure('databasename') will output the ini file structure array , with __keys * fix bug #611 corrected comment about NO_OVERLOAD * fix bug #603 to prevent clearing of queries after fetch, use the option keep_query_after_fetch = 1 * fix bug #359 (again) - _link_loaded array should only contain links that where sucessfully loaded 1.5.1 1.5.1 stable stable 2004-01-22 PHP License Bug fix release. Fixes * fixed fromValue had problems with null and DataOBject_Cast objects * NOT NULL checking enabled in fromValue (eg. setters) * fix bug #605 - ability to specify sequence names option sequence_{tablename} = {column} // default {tablename}_seq option sequence_{tablename} = {column}:{sequence_name} * Dependancy on Date added. (it is used by the setter/getters) 1.5 1.5 stable stable 2004-01-20 PHP License New Features / Fixes * proxy and auto schema building - you can use dataobjects wihout running create tables. - schema is created on the fly if no schema file exists - using config option proxy=full, a class is generated on the fly to represent the tables. * toValue and fromValue added as getter and setter providers - they include date formating tools - eg. $do->getBirthDay('%d/%m/%Y') will work with overload and PEAR::Date to provide formated dates. (works with date/time and datetime) - set*() will also call strtotime and PEAR::Date to try and read dates.\ setFrom will relay into fromValue() - hence auto fixing date entries * make native key recognision a little better - changes ini file format (key=N|K) - native or key. - BC protected... - ignores it when it finds a number.. (Fixes bug #532) * allow multiple updates on the same object - without adding lots of id=.... on the end.. * transactions support works transparently by sending $do->query('BEGIN'), $do->query('COMMIT'),$do->query('ROLLBACK') will automatically relay into the DB methods. * re-add support for sequence_{tablename} - prefered method is still overriding sequenceKey, and returning correct values. Bug Fixes * fix bug #595 - replace non letters in class names with underscore.. * fix bug #532 - better recoginsion of sequence keys * fix bug #528/#476 - typo fix (E_NOTICE error on $valu) * fix bug #473 - query can return a DB_Error * fix bug #501 - autoload/factory now uses full path (not require path) * fix bug #507 - sequences broken 1.4 1.4 stable stable 2003-12-20 PHP License 1.3 1.3 stable stable 2003-11-14 PHP License New Features - Inserts and updates do not use keys in the data creation part of the query (Markus Wolff) - MsSQL native get_last_insert ID using SELECT @@IDENTITY ** you may want to consider using this with transactions eg. $dataobject->query('BEGIN') and $dataobject->query('COMMIT') to ensure thread safety. (Markus Wolff) - DB_DataObject_Cast - a experimental generic casting feature for assigning variables * Common usages: // blobs $dataObject->someblobfield = DB_DataObject_Cast::blob(file_get_contents('xxx.jpg')); $dataObject->someblobfield = DB_DataObject_Cast::string('xxxxx'); // dates? $dataObject->at_date = DB_DataObject_Cast::date('12/12/2000'); //Human $dataObject->at_date = DB_DataObject_Cast::date('2000-12-12'); //ISO $dataObject->at_date = DB_DataObject_Cast::date(2000,12,12); //YYYY,dd,mm $d2 = DB_DataObject_Cast::date(); // today! // some addition $dataObject->expires = DB_DataObject_Cast::date($d1->year, $d1->month+30, $d1->day+30); // raw sql???? $dataObject->maxids = DB_DataObject_Cast::sql('max(id) > 12'); $dataObject->emptyfield = DB_DataObject_Cast::sql('NULL'); You can extend this object to provide your own datatypes.. - PHP5 Compatibility Return values should work with PHP5beta2 Overload (__call) should work with PHP5beta2 - table() and keys() Allow setting of data with a value (so you can use dataobjects without extended instances.) - factory/ getLinks and autoloading Will use the full path location in config to check if the file exists, after that it is included without warnings turned off.. (autoloading check to see if class exists first - should be faster) Fixes: - Validate is an optional dependency (Arnaud Limbourg) - Bug #228, BOOL type on postgresql correctly quoted. - Bug #133, No error is raised when calling fetch with no data returned, just produces a debug warning now - Bug #172,Turning of Overload If you define DB_DATAOBJECT_NO_OVERLOAD to any value, then dataobjects will not be overloaded, This is usefull when working with ZendOptimizer, that may segfault with certain optimization levels 1.2 1.2 stable stable 2003-08-07 PHP License New Features: - A Number of the private methods have been made Public, changes: - _get_table() = table() - _get_keys() = keys() additions - tableName() = returns or assign the table name - database() = returns or assign the database name changes by Markus Wolff POSSIBLE BC IMPLICATIONS: If you define _get_table or _get_keys manually (rather than using the ini file) you should rename your methods table() and keys() Fixes: - Generator is now more tollerant of editors that alter the spaces before the autocode markers ### (Andreas Ljunggren) 1.1 1.1 stable stable 2003-08-07 PHP License Bug Fixes - limit argument testing now accepts "10" (a string with an integer in it, as a valid argument) (Demian Turner) New Features - JoinAdd has additional Argument $joinCol, so you can specify multiple targets links of the same table, eg. user->friend, user->parent both connect to the user table.. (Andy Crain) 1.0.2 1.0.2 stable stable 2003-07-31 PHP License Minor Bugfix Release - Fix static Get call, case sensitivity properly. 1.0.1 1.0.1 stable stable 2003-07-18 PHP License Minor Bugfix Release - Fix Select defaults back to * after an fetch - Fix Update with NULL values 1.0 1.0 stable stable 2003-07-16 PHP License - Gold 1.0 Release.. - final stable release in this series. Future versions (eg. 1.1.*(dev)->1.2.*(release)) series will be generally API compatible, however is likely to use MDB rather than DB. - fix bug #23983 - argument checking on whereAdd, orderBy, limit and a few others (Mike Carter) - fix bug with cache clearing not working with staticGet and update/insert - null comparison for $object->var = 'null' will generate IS NULL query - null insert/update works when using 'null' as a value. - getLinks returns true on success - getLinks now has a formatter eg. '_%s' is default to allow user to define the format of variables to assign child objects to. - generator uses UNIQUE only if no primary/autoincrement type field is found. - added support for $object->having('sum(value) > 10'); - fetch will remove some query lines (so if you want to see how the query was built you will have to print_r() the object after find(), and before fetch(), or just use debugLevel(1) - overload calls from setFrom and toArray do method checks, as overload is still proving to be a little unstable especially with Zend Encoder. (Note: if DataObjects causes crashes , remove the overload code at the bottom of DataObjects.php) 0.19 0.19 stable stable 2003-05-22 PHP License - fix bug #23712 - deleting (and probably updating as well) an object while being fetched. (Michael McCarthy for spotting this) - fix generator overwriting code when file was edited in a Windows editor (Robert Janeczek) - validation will not produce errors on empty numeric data by default. - overload extension enabled to relay setFrom and toArray calls to get*, set* methods, which are predefined as getXXX = returns $object->XXX setXXX($V) = returns true (or an error string if you create your own) (only affects php-4.3.2RC2 or later..) - selectAs($dataobject,'prefix_%s') to help control the way join queries return their data * doing selectAs() - clears the select and sets it to table.id as id ... etc. - joinAdd has 2 extra optional arguments $dataObject->joinAdd($otherDataObject, $joinType, $joinAs) existing behaviour (INNER Joins) is default (eg. no value) $joinType can be: INNER,LEFT,RIGHT = eg. INNER JOIN ... etc. '' = just added with a , and the link conditions are added with a WHERE statement. $joinAs can be used to name the table differently in the join eg. SELECT .. FROM person INNER JOIN address as homeaddress ...... - setFrom has an extra optional parameter for formating: eg. $object->setFrom($_POST,'prefix_%s') would map prefix_name to $object->name the default is '%s' which is the same as the previous behaviour - added option dont_die (default no) to ini file default (no) means that DataObjects will emit a PEAR_ERROR_DIE on configuration and programming errors true or 1 means that you can write a PEAR::error handler to catch the errors and display a nice it's not working message 0.18 0.18 stable stable 2003-05-17 PHP License - add table prefix to autobuilt queries (assist in ambigous querys for joins) (Daniel Von Fange) - change update and delete to return the number of rows affected or false on failure - various code tidy ups - standards etc. (Arnaud Limbourg) - fix generator for postgres and mdb - use unique keys and look for 'nextval' as keys. - fix no key insert bug (G Moxley Kempster) - fix empty table bug in generator (Robert Janeczek) - staticAutoloadTable(), now handles parse errors in included files alot cleaner - added dependancies on PHP4.3, DB 1.3 and Validate (although Validate is optional) - added table prefix to count query (Richard Wallace) 0.17 0.17 stable stable 2003-03-12 PHP License - Usual post feature bug fix release - fix loading of links.ini for joinAdd, getLinks etc. (Geoff Hopson) 0.16 0.16 stable stable 2003-03-06 PHP License - replaced config storage - should improve performance - removed production option (not required due to improved config storage) - bugfix : added checks for is_array on ignore_sequence_keys (Hermen Heinen) - add toArray($format) which supports sprintf to enable easy integration with Quickforms etc. (Demian Turner) - add optional dataobject as variable to update(), so you can update will only create an update based on what has changed. (Geoff Hopson) - add join condition to count() (Daniel Von Fange) - modify limit() to work with postgresql and make it emit a fatal error if you try this on databases that do not directly support limit queries. (Geoff Hopson) * I'm open on better ideas for this - probably wrapping the find() select call 0.15 0.15 stable stable 2003-02-13 PHP License - modified find(true), so that it calls fetch rather than fetchrow so that overriding fetch will affect get() calls. (probably depreciates fetchrow now .. shout if anyone is using it) - added escape method as a quasi quote wrapper into pear::DB - useful for building your own queries - eg. like requests.. (does not add quotes) - fixed bug with delete not using primary key if it was set. (Ian Eure) 0.14 0.14 stable stable 2003-02-06 PHP License - added get affected rows to update/insert/delete they will return false if no data is affected and set the Objects lastError value. Note: databases that do not support affectedRows(), will always return false. 0.13 0.13 stable stable 2003-01-23 PHP License - bug fix release - debug_ignore_updates worked in reverse.. (Alexander Mazurov) 0.12 0.12 stable stable 2003-01-22 PHP License - Usual post release bug fix. - fixes *.links.ini incorrectly using *.links (Damian Turner) - Adds the ability to get PEAR Connection and result objects getDatabaseConnection() and getDatabaseResult() (jason rust) 0.11 0.11 stable stable 2003-01-21 PHP License - changed connecition and result caching to use global private variable, rather than getStaticProperty - results objects are not stored on insert/update/delete (speed increase on large inserts can be as much as 10x faster) - bug fix - Checks that primary keys are integers (Anthony Juou) - typo on No data returned warning - unit tests in CVS - new configuration option for whether to use insert id's and sequences. - Generator no creates boolean bit (mitchell perilstein) 0.10 0.10 stable stable 2002-12-09 PHP License - bug fix - Generator incorrectly set _database, causing generation of database.ini files to fail - change addslashes to PEAR::DB::quote() - generator now only replaces DB_DataObjects with extends variable if set, you can use generator_rewrite_class to set what is a valid replacement. 0.9 0.9 stable stable 2002-12-06 PHP License - bug fix release - dsn was not set correctly 0.8 0.8 stable stable 2002-09-21 PHP License - changed to stable status as it appears to be pretty stable now. - update and insert now return FALSE on error conditions (Peter Gebauer) which can be checked using === FALSE, and the error obtained using $object->_lastError - delete will only use primary keys if they are set, otherwise it will use all object variables to build the condition. (Daniel Ferreira) - Added derivedHookFunctions(), and derivedHookVar() to enable extensions to generator to make user defined methods. (Michael Lund Rasmussen) - support for INT4, BPCHAR, TIMESTAMPTZ and postres Primary keys (Xavier) - better PEAR CS complience (tabs and TRUE -> true), removed short open tags in generator (Thomas Volkmar Worm) - experimental addJoin method (Stijn de Reede) - Allow active options array (eg. dynamic changing of database config) (Sergey Lipnevich) 0.6 0.6 beta beta 2002-09-10 PHP License - bug fix release on insert (broke due to compatibility fixes) 0.5 0.5 beta beta 2002-09-07 PHP License - fixed database.links.ini file loading in wrong location (Jens Fischer) - fixed type on extends (Jens Fischer) - added __clone() support to pre- php5 classes - added multiple linked table support 0.4 0.4 beta beta 2002-08-21 PHP License - Bug fix release - createTables used wrong argv 0.3 0.3 beta beta 2002-08-16 PHP License - Fixed Globals requirement in createTables, 0.2 0.2 beta beta 2002-07-12 PHP License Removed list method, First upload as pear package. - Documentation now in PEAR Manual 0.1 0.1 beta beta 2002-06-25 PHP License PEARified