X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=extlib%2FDB%2FDataObject.php;h=1a7b34665d174224ef50528dd4355b9e348cbdf7;hb=64ac344efab356ffc9debe2ace001ebeb91b7c6f;hp=0c6a13dc28f7cec5c702d2ad09fb41704bc6b018;hpb=8b5bcae58fba59734a566315f04499fcc1433c75;p=quix0rs-gnu-social.git diff --git a/extlib/DB/DataObject.php b/extlib/DB/DataObject.php index 0c6a13dc28..1a7b34665d 100644 --- a/extlib/DB/DataObject.php +++ b/extlib/DB/DataObject.php @@ -2,11 +2,11 @@ /** * Object Based Database Query Builder and data store * - * PHP versions 4 and 5 + * For PHP versions 4,5 and 6 * - * LICENSE: This source file is subject to version 3.0 of the PHP license + * LICENSE: This source file is subject to version 3.01 of the PHP license * that is available through the world-wide-web at the following URI: - * http://www.php.net/license/3_0.txt. If you did not receive a copy of + * http://www.php.net/license/3_01.txt. If you did not receive a copy of * the PHP License and are unable to obtain it through the web, please * send a note to license@php.net so we can mail you a copy immediately. * @@ -14,8 +14,8 @@ * @package DB_DataObject * @author Alan Knowles * @copyright 1997-2006 The PHP Group - * @license http://www.php.net/license/3_0.txt PHP License 3.0 - * @version CVS: $Id: DataObject.php,v 1.439 2008/01/30 02:14:06 alan_k Exp $ + * @license http://www.php.net/license/3_01.txt PHP License 3.01 + * @version CVS: $Id: DataObject.php 320069 2011-11-28 04:34:08Z alan_k $ * @link http://pear.php.net/package/DB_DataObject */ @@ -176,10 +176,11 @@ $GLOBALS['_DB_DATAOBJECT']['QUERYENDTIME'] = 0; // this will be horrifically slow!!!! -// NOTE: Overload SEGFAULTS ON PHP4 + Zend Optimizer (see define before..) // these two are BC/FC handlers for call in PHP4/5 -if ( substr(phpversion(),0,1) == 5) { + +if (!defined('DB_DATAOBJECT_NO_OVERLOAD')) { + class DB_DataObject_Overload { function __call($method,$args) @@ -194,27 +195,10 @@ if ( substr(phpversion(),0,1) == 5) { } } } else { - if (version_compare(phpversion(),'4.3.10','eq') && !defined('DB_DATAOBJECT_NO_OVERLOAD')) { - trigger_error( - "overload does not work with PHP4.3.10, either upgrade - (snaps.php.net) or more recent version - or define DB_DATAOBJECT_NO_OVERLOAD as per the manual. - ",E_USER_ERROR); - } - - if (!function_exists('clone')) { - // emulate clone - as per php_compact, slow but really the correct behaviour.. - eval('function clone($t) { $r = $t; if (method_exists($r,"__clone")) { $r->__clone(); } return $r; }'); - } - eval(' - class DB_DataObject_Overload { - function __call($method,$args,&$return) { - return $this->_call($method,$args,$return); - } - } - '); + class DB_DataObject_Overload {} } + @@ -235,7 +219,7 @@ class DB_DataObject extends DB_DataObject_Overload * @access private * @var string */ - var $_DB_DataObject_version = "1.8.8"; + var $_DB_DataObject_version = "1.11.3"; /** * The Database table (used by table extends) @@ -290,7 +274,7 @@ class DB_DataObject extends DB_DataObject_Overload $v = $k; $keys = $this->keys(); if (!$keys) { - $this->raiseError("No Keys available for {$this->__table}", DB_DATAOBJECT_ERROR_INVALIDCONFIG); + $this->raiseError("No Keys available for {$this->tableName()}", DB_DATAOBJECT_ERROR_INVALIDCONFIG); return false; } $k = $keys[0]; @@ -306,68 +290,88 @@ class DB_DataObject extends DB_DataObject_Overload $this->$k = $v; return $this->find(1); } - + /** - * An autoloading, caching static get method using key, value (based on get) + * Get the value of the primary id * - * Usage: - * $object = DB_DataObject::staticGet("DbTable_mytable",12); - * or - * $object = DB_DataObject::staticGet("DbTable_mytable","name","fred"); + * While I normally use 'id' as the PRIMARY KEY value, some database use + * {table}_id as the column name. * - * or write it into your extended class: - * function &staticGet($k,$v=NULL) { return DB_DataObject::staticGet("This_Class",$k,$v); } + * To save a bit of typing, * - * @param string $class class name - * @param string $k column (or value if using keys) - * @param string $v value (optional) - * @access public - * @return object + * $id = $do->pid(); + * + * @return the id */ - function &staticGet($class, $k, $v = null) + function pid() { - $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]; + $keys = $this->keys(); + if (!$keys) { + $this->raiseError("No Keys available for {$this->tableName()}", + DB_DATAOBJECT_ERROR_INVALIDCONFIG); + return false; } - if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { - DB_DataObject::debug("$class $key","STATIC GET - NOT IN CACHE"); + $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; + } + - $obj = DB_DataObject::factory(substr($class,strlen($_DB_DATAOBJECT['CONFIG']['class_prefix']))); - if (PEAR::isError($obj)) { - DB_DataObject::raiseError("could not autoload $class", DB_DATAOBJECT_ERROR_NOCLASS); - $r = false; - return $r; - } - - if (!isset($_DB_DATAOBJECT['CACHE'][$lclass])) { - $_DB_DATAOBJECT['CACHE'][$lclass] = array(); + + /** + * 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]; } - if (!$obj->get($k,$v)) { - DB_DataObject::raiseError("No Data return from get $k $v", DB_DATAOBJECT_ERROR_NODATA); + $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; + - $r = false; - return $r; } - $_DB_DATAOBJECT['CACHE'][$lclass][$key] = $obj; - return $_DB_DATAOBJECT['CACHE'][$lclass][$key]; + + + + $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 * @@ -381,6 +385,10 @@ class DB_DataObject extends DB_DataObject_Overload * 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) @@ -410,20 +418,21 @@ class DB_DataObject extends DB_DataObject_Overload $query_before = $this->_query; $this->_build_condition($this->table()) ; - $quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']); + $this->_connect(); - $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; + $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; - /* We are checking for method modifyLimitQuery as it is PEAR DB specific */ - $sql = 'SELECT ' . - $this->_query['data_select'] . " \n" . - ' FROM ' . ($quoteIdentifiers ? $DB->quoteIdentifier($this->__table) : $this->__table) . " \n" . - $this->_join . " \n" . - $this->_query['condition'] . " \n" . - $this->_query['group_by'] . " \n" . - $this->_query['having'] . " \n" . - $this->_query['order_by'] . " \n"; + $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 */ @@ -439,7 +448,10 @@ class DB_DataObject extends DB_DataObject_Overload } - $this->_query($sql); + $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); @@ -511,7 +523,7 @@ class DB_DataObject extends DB_DataObject_Overload } if (empty($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid]) || - !is_object($result = &$_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)'); @@ -556,10 +568,12 @@ class DB_DataObject extends DB_DataObject_Overload // 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) { - $kk = str_replace(".", "_", $k); - $kk = str_replace(" ", "_", $kk); + // 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); } @@ -569,7 +583,7 @@ class DB_DataObject extends DB_DataObject_Overload // set link flag $this->_link_loaded=false; if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { - $this->debug("{$this->__table} DONE", "fetchrow",2); + $this->debug("{$this->tableName()} DONE", "fetchrow",2); } if (($this->_query !== false) && empty($_DB_DATAOBJECT['CONFIG']['keep_query_after_fetch'])) { $this->_query = false; @@ -577,6 +591,85 @@ class DB_DataObject extends DB_DataObject_Overload 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 * @@ -621,6 +714,48 @@ class DB_DataObject extends DB_DataObject_Overload 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 * @@ -727,6 +862,43 @@ class DB_DataObject extends DB_DataObject_Overload $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 * @@ -764,7 +936,7 @@ class DB_DataObject extends DB_DataObject_Overload } global $_DB_DATAOBJECT; $this->_connect(); - $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; + $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; @@ -844,9 +1016,9 @@ class DB_DataObject extends DB_DataObject_Overload } - $table = $this->__table; + $table = $this->tableName(); if (is_object($from)) { - $table = $from->__table; + $table = $from->tableName(); $from = array_keys($from->table()); } @@ -856,7 +1028,7 @@ class DB_DataObject extends DB_DataObject_Overload $s = '%s'; if (!empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers'])) { $this->_connect(); - $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; + $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; $s = $DB->quoteIdentifier($s); $format = $DB->quoteIdentifier($format); } @@ -894,25 +1066,24 @@ class DB_DataObject extends DB_DataObject_Overload $quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']); - $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; + $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; - $items = isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table]) ? - $_DB_DATAOBJECT['INI'][$this->_database][$this->__table] : $this->table(); + $items = $this->table(); if (!$items) { - $this->raiseError("insert:No table definition for {$this->__table}", + $this->raiseError("insert:No table definition for {$this->tableName()}", DB_DATAOBJECT_ERROR_INVALIDCONFIG); return false; } - $options = &$_DB_DATAOBJECT['CONFIG']; + $options = $_DB_DATAOBJECT['CONFIG']; $datasaved = 1; $leftq = ''; $rightq = ''; - $seqKeys = isset($_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table]) ? - $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] : + $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; @@ -929,7 +1100,7 @@ class DB_DataObject extends DB_DataObject_Overload if (($key !== false) && !$useNative) { if (!$seq) { - $keyvalue = $DB->nextId($this->__table); + $keyvalue = $DB->nextId($this->tableName()); } else { $f = $DB->getOption('seqname_format'); $DB->setOption('seqname_format','%s'); @@ -942,9 +1113,13 @@ class DB_DataObject extends DB_DataObject_Overload } $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... @@ -952,8 +1127,11 @@ class DB_DataObject extends DB_DataObject_Overload continue; } - - if (!isset($this->$k)) { + + // 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 @@ -969,7 +1147,7 @@ class DB_DataObject extends DB_DataObject_Overload $leftq .= ($quoteIdentifiers ? ($DB->quoteIdentifier($k) . ' ') : "$k "); - if (is_a($this->$k,'DB_DataObject_Cast')) { + 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); @@ -980,8 +1158,7 @@ class DB_DataObject extends DB_DataObject_Overload } - - if (!isset($options['disable_null_strings']) && is_string($this->$k) && (strtolower($this->$k) === 'null') && !($v & DB_DATAOBJECT_NOTNULL)) { + if (!($v & DB_DATAOBJECT_NOTNULL) && DB_DataObject::_is_null($this,$k)) { $rightq .= " NULL "; continue; } @@ -1025,9 +1202,15 @@ class DB_DataObject extends DB_DataObject_Overload if ($leftq || $useNative) { - $table = ($quoteIdentifiers ? $DB->quoteIdentifier($this->__table) : $this->__table); + $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) "); + } - $r = $this->_query("INSERT INTO {$table} ($leftq) VALUES ($rightq) "); @@ -1071,7 +1254,7 @@ class DB_DataObject extends DB_DataObject_Overload case 'pgsql': if (!$seq) { - $seq = $DB->getSequenceName(strtolower($this->__table)); + $seq = $DB->getSequenceName(strtolower($this->tableName())); } $db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver']; $method = ($db_driver == 'DB') ? 'getOne' : 'queryOne'; @@ -1154,19 +1337,18 @@ class DB_DataObject extends DB_DataObject_Overload $original_query = $this->_query; - $items = isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table]) ? - $_DB_DATAOBJECT['INI'][$this->_database][$this->__table] : $this->table(); + $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 (empty($this->{$keys[0]}) && $dataObject !== true) { + 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 - ", DB_DATAOBJECT_ERROR_INVALIDARGS); + ". print_r(array('seq' => $seq , 'keys'=>$keys), true), DB_DATAOBJECT_ERROR_INVALIDARGS); return false; } } else { @@ -1175,22 +1357,31 @@ class DB_DataObject extends DB_DataObject_Overload if (!$items) { - $this->raiseError("update:No table definition for {$this->__table}", DB_DATAOBJECT_ERROR_INVALIDCONFIG); + $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]; + $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) { - if (!isset($this->$k)) { - continue; + + // I think this is ignoring empty vlalues + if ((!isset($this->$k) || ($v == 1 && $this->$k === '')) + && $ignore_null + ) { + continue; } // ignore stuff thats @@ -1217,7 +1408,7 @@ class DB_DataObject extends DB_DataObject_Overload $kSql = ($quoteIdentifiers ? $DB->quoteIdentifier($k) : $k); - if (is_a($this->$k,'DB_DataObject_Cast')) { + 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); @@ -1228,7 +1419,7 @@ class DB_DataObject extends DB_DataObject_Overload } // special values ... at least null is handled... - if (!isset($options['disable_null_strings']) && (strtolower($this->$k) === 'null') && !($v & DB_DATAOBJECT_NOTNULL)) { + if (!($v & DB_DATAOBJECT_NOTNULL) && DB_DataObject::_is_null($this,$k)) { $settings .= "$kSql = NULL "; continue; } @@ -1261,7 +1452,7 @@ class DB_DataObject extends DB_DataObject_Overload // - 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); @@ -1283,7 +1474,7 @@ class DB_DataObject extends DB_DataObject_Overload // echo " $settings, $this->condition "; if ($settings && isset($this->_query) && $this->_query['condition']) { - $table = ($quoteIdentifiers ? $DB->quoteIdentifier($this->__table) : $this->__table); + $table = ($quoteIdentifiers ? $DB->quoteIdentifier($this->tableName()) : $this->tableName()); $r = $this->_query("UPDATE {$table} SET {$settings} {$this->_query['condition']} "); @@ -1339,14 +1530,14 @@ class DB_DataObject extends DB_DataObject_Overload * build the condition only using the object parameters. * * @access public - * @return mixed True on success, false on failure, 0 on no data affected + * @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]; + $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'] : ''); @@ -1368,8 +1559,14 @@ class DB_DataObject extends DB_DataObject_Overload // don't delete without a condition if (($this->_query !== false) && $this->_query['condition']) { - $table = ($quoteIdentifiers ? $DB->quoteIdentifier($this->__table) : $this->__table); - $sql = "DELETE FROM {$table} {$this->_query['condition']}{$extra_cond}"; + $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.. @@ -1424,9 +1621,9 @@ class DB_DataObject extends DB_DataObject_Overload $this->_loadConfig(); } if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { - $this->debug("{$this->__table} $row of {$this->N}", "fetchrow",3); + $this->debug("{$this->tableName()} $row of {$this->N}", "fetchrow",3); } - if (!$this->__table) { + if (!$this->tableName()) { $this->raiseError("fetchrow: No table", DB_DATAOBJECT_ERROR_INVALIDCONFIG); return false; } @@ -1439,19 +1636,22 @@ class DB_DataObject extends DB_DataObject_Overload return false; } if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { - $this->debug("{$this->__table} $row of {$this->N}", "fetchrow",3); + $this->debug("{$this->tableName()} $row of {$this->N}", "fetchrow",3); } - $result = &$_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid]; + $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) { - $kk = str_replace(".", "_", $k); + // 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); } @@ -1459,7 +1659,7 @@ class DB_DataObject extends DB_DataObject_Overload } if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { - $this->debug("{$this->__table} DONE", "fetchrow", 3); + $this->debug("{$this->tableName()} DONE", "fetchrow", 3); } return true; } @@ -1513,7 +1713,7 @@ class DB_DataObject extends DB_DataObject_Overload return false; } $this->_connect(); - $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; + $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; if (!$whereAddOnly && $items) { @@ -1521,15 +1721,15 @@ class DB_DataObject extends DB_DataObject_Overload } $keys = $this->keys(); - if (!$keys[0] && !is_string($countWhat)) { + if (empty($keys[0]) && (!is_string($countWhat) || (strtoupper($countWhat) == 'DISTINCT'))) { $this->raiseError( - "You cannot do run count without keys - use \$do->keys('id');", + "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->__table) : $this->__table); - $key_col = ($quoteIdentifiers ? $DB->quoteIdentifier($keys[0]) : $keys[0]); + $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. @@ -1545,11 +1745,13 @@ class DB_DataObject extends DB_DataObject_Overload return false; } - $result = &$_DB_DATAOBJECT['RESULTS'][$t->_DB_resultid]; + $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]; } @@ -1583,7 +1785,7 @@ class DB_DataObject extends DB_DataObject_Overload { global $_DB_DATAOBJECT; $this->_connect(); - $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; + $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); @@ -1642,9 +1844,13 @@ class DB_DataObject extends DB_DataObject_Overload '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) ); @@ -1713,7 +1919,7 @@ class DB_DataObject extends DB_DataObject_Overload $x = new DB_DataObject; $x->_database = $args[0]; $this->_connect(); - $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; + $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; $tables = $DB->getListOf('tables'); class_exists('DB_DataObject_Generator') ? '' : @@ -1744,32 +1950,41 @@ class DB_DataObject extends DB_DataObject_Overload $this->_connect(); } - // loaded already? - if (!empty($_DB_DATAOBJECT['INI'][$this->_database])) { - - // database loaded - but this is table is not available.. - if ( - empty($_DB_DATAOBJECT['INI'][$this->_database][$this->__table]) - && !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->__table); - } + + // 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... @@ -1784,10 +1999,15 @@ class DB_DataObject extends DB_DataObject_Overload } - + $_DB_DATAOBJECT['INI'][$this->_database] = array(); foreach ($schemas as $ini) { if (file_exists($ini) && is_file($ini)) { - $_DB_DATAOBJECT['INI'][$this->_database] = parse_ini_file($ini, true); + + $_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); @@ -1802,9 +2022,18 @@ class DB_DataObject extends DB_DataObject_Overload } } + // 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->__table])) { + if (!empty($_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()])) { return true; } // - if not try building it.. @@ -1813,11 +2042,11 @@ class DB_DataObject extends DB_DataObject_Overload require_once 'DB/DataObject/Generator.php'; $x = new DB_DataObject_Generator; - $x->fillTableSchema($this->_database,$this->__table); + $x->fillTableSchema($this->_database,$this->tableName()); // should this fail!!!??? return true; } - $this->debug("Cant find database schema: {$this->_database}/{$this->__table} \n". + $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); @@ -1839,10 +2068,14 @@ class DB_DataObject extends DB_DataObject_Overload */ function tableName() { + global $_DB_DATAOBJECT; $args = func_get_args(); if (count($args)) { $this->__table = $args[0]; } + if (!empty($_DB_DATAOBJECT['CONFIG']['portability']) && $_DB_DATAOBJECT['CONFIG']['portability'] & 1) { + return strtolower($this->__table); + } return $this->__table; } @@ -1858,7 +2091,10 @@ class DB_DataObject extends DB_DataObject_Overload $args = func_get_args(); if (count($args)) { $this->_database = $args[0]; + } else { + $this->_connect(); } + return $this->_database; } @@ -1887,17 +2123,17 @@ class DB_DataObject extends DB_DataObject_Overload if (!isset($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) { $this->_connect(); } - - if (isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table])) { - return $_DB_DATAOBJECT['INI'][$this->_database][$this->__table]; + + 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->__table])) { - $ret = $_DB_DATAOBJECT['INI'][$this->_database][$this->__table]; + if (isset($_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()])) { + $ret = $_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()]; } return $ret; @@ -1912,7 +2148,7 @@ class DB_DataObject extends DB_DataObject_Overload * 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 private + * @access public * @return array */ function keys() @@ -1931,13 +2167,13 @@ class DB_DataObject extends DB_DataObject_Overload if (!isset($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) { $this->_connect(); } - if (isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table."__keys"])) { - return array_keys($_DB_DATAOBJECT['INI'][$this->_database][$this->__table."__keys"]); + 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->__table."__keys"])) { - return array_keys($_DB_DATAOBJECT['INI'][$this->_database][$this->__table."__keys"]); + if (isset($_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()."__keys"])) { + return array_keys($_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()."__keys"]); } return array(); } @@ -1952,7 +2188,7 @@ class DB_DataObject extends DB_DataObject_Overload * @param string optional the key sequence/autoinc. key * @param boolean optional use native increment. default false * @param false|string optional native sequence name - * @access private + * @access public * @return array (column,use_native,sequence_name) */ function sequenceKey() @@ -1973,10 +2209,10 @@ class DB_DataObject extends DB_DataObject_Overload 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->__table] = $args; + $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()] = $args; } - if (isset($_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table])) { - return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table]; + 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); ) @@ -1985,13 +2221,12 @@ class DB_DataObject extends DB_DataObject_Overload $keys = $this->keys(); if (!$keys) { - return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] + return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()] = array(false,false,false); } - $table = isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table]) ? - $_DB_DATAOBJECT['INI'][$this->_database][$this->__table] : $this->table(); + $table = $this->table(); $dbtype = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype']; @@ -2001,35 +2236,35 @@ class DB_DataObject extends DB_DataObject_Overload $seqname = 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 (!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->__table] = array(false,false,false); + 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->__table] = array(false,false,$seqname); + 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->__table,$ignore)) { - return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] = array(false,false,$seqname); + 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->__table."__keys"]; + $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])) { @@ -2038,25 +2273,38 @@ class DB_DataObject extends DB_DataObject_Overload // multiple unique primary keys without a native sequence... if (($realkeys[$usekey] == 'K') && (count($keys) > 1)) { - return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] = array(false,false,$seqname); + 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. - if ( in_array($dbtype , array('psql', 'mysql', 'mysqli', 'mssql', 'ifx')) && + // 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->__table] = array($usekey,true,$seqname); + 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->__table] = array($usekey,false,$seqname); + return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()] = array($usekey,false,$seqname); } @@ -2125,10 +2373,13 @@ class DB_DataObject extends DB_DataObject_Overload $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 ? - + $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, @@ -2137,7 +2388,7 @@ class DB_DataObject extends DB_DataObject_Overload } - if (!$this->_database) { + 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'); @@ -2165,22 +2416,23 @@ class DB_DataObject extends DB_DataObject_Overload // it's not currently connected! // try and work out what to use for the dsn ! - $options= &$_DB_DATAOBJECT['CONFIG']; + $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) { - $this->_database = isset($options["table_{$this->__table}"]) ? $options["table_{$this->__table}"] : null; + if (!$this->_database && !empty($this->__table)) { + $this->_database = isset($options["table_{$this->tableName()}"]) ? $options["table_{$this->tableName()}"] : null; } if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { - $this->debug("Checking for database database_{$this->_database} in options","CONNECT"); + $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']; + } } @@ -2205,6 +2457,9 @@ class DB_DataObject extends DB_DataObject_Overload 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'); @@ -2221,7 +2476,7 @@ class DB_DataObject extends DB_DataObject_Overload return true; } if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { - $this->debug("NEW CONNECTION", "CONNECT",3); + $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); } @@ -2236,11 +2491,11 @@ class DB_DataObject extends DB_DataObject_Overload $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); + $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5] = DB::connect($dsn,$db_options); } else { - $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5] = &DB::connect($dsn); + $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5] = DB::connect($dsn); } - + } else { /* assumption is MDB2 */ require_once 'MDB2.php'; @@ -2249,13 +2504,13 @@ class DB_DataObject extends DB_DataObject_Overload $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); + $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5] = MDB2::connect($dsn,$db_options); } if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { - $this->debug(serialize($_DB_DATAOBJECT['CONNECTIONS']), "CONNECT",5); + $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); @@ -2265,8 +2520,8 @@ class DB_DataObject extends DB_DataObject_Overload ); } - - if (!$this->_database) { + + if (empty($this->_database)) { $hasGetDatabase = method_exists($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5], 'getDatabase'); $this->_database = ($db_driver != 'DB' && $hasGetDatabase) @@ -2282,7 +2537,6 @@ class DB_DataObject extends DB_DataObject_Overload } // Oracle need to optimize for portibility - not sure exactly what this does though :) - $c = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; return true; } @@ -2301,9 +2555,9 @@ class DB_DataObject extends DB_DataObject_Overload $this->_connect(); - $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; + $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; - $options = &$_DB_DATAOBJECT['CONFIG']; + $options = $_DB_DATAOBJECT['CONFIG']; $_DB_driver = empty($_DB_DATAOBJECT['CONFIG']['db_driver']) ? 'DB': $_DB_DATAOBJECT['CONFIG']['db_driver']; @@ -2313,15 +2567,19 @@ class DB_DataObject extends DB_DataObject_Overload } - if (strtoupper($string) == 'BEGIN') { + if ( + strtoupper($string) == 'BEGIN' || + strtoupper($string) == 'START TRANSACTION' + ) { if ($_DB_driver == 'DB') { $DB->autoCommit(false); + $DB->simpleQuery('BEGIN'); } else { $DB->beginTransaction(); } - // db backend adds begin anyway from now on.. return true; } + if (strtoupper($string) == 'COMMIT') { $res = $DB->commit(); if ($_DB_driver == 'DB') { @@ -2357,43 +2615,44 @@ class DB_DataObject extends DB_DataObject_Overload $t= explode(' ',microtime()); $_DB_DATAOBJECT['QUERYENDTIME'] = $time = $t[0]+$t[1]; - - do { - if ($_DB_driver == 'DB') { - $result = $DB->query($string); - } else { - switch (strtolower(substr(trim($string),0,6))) { + for ($tries = 0;$tries < 3;$tries++) { - case 'insert': - case 'update': - case 'delete': - $result = $DB->exec($string); - break; - - default: - $result = $DB->query($string); - break; + 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); } + - // try to reconnect, at most 3 times - $again = false; - if (is_a($result, 'PEAR_Error') - AND $result->getCode() == DB_ERROR_NODBSELECTED - AND $cpt++<3) { - $DB->disconnect(); - sleep(1); - $DB->connect($DB->dsn); - $again = true; - } - - } while ($again); - - if (is_a($result,'PEAR_Error')) { + 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'])) { @@ -2422,14 +2681,17 @@ class DB_DataObject extends DB_DataObject_Overload if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { $this->debug(serialize($result), 'RESULT',5); } - if (method_exists($result, 'numrows')) { + if (method_exists($result, 'numRows')) { if ($_DB_driver == 'DB') { $DB->expectError(DB_ERROR_UNSUPPORTED); } else { $DB->expectError(MDB2_ERROR_UNSUPPORTED); } - $this->N = $result->numrows(); - if (is_a($this->N,'PEAR_Error')) { + + $this->N = $result->numRows(); + //var_dump($this->N); + + if (is_object($this->N) && is_a($this->N,'PEAR_Error')) { $this->N = true; } $DB->popExpect(); @@ -2449,7 +2711,7 @@ class DB_DataObject extends DB_DataObject_Overload { global $_DB_DATAOBJECT; $this->_connect(); - $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; + $DB = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; $quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']); $options = $_DB_DATAOBJECT['CONFIG']; @@ -2459,7 +2721,8 @@ class DB_DataObject extends DB_DataObject_Overload $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.. @@ -2480,12 +2743,12 @@ class DB_DataObject extends DB_DataObject_Overload } $kSql = $quoteIdentifiers - ? ( $DB->quoteIdentifier($this->__table) . '.' . $DB->quoteIdentifier($k) ) - : "{$this->__table}.{$k}"; + ? ( $DB->quoteIdentifier($this->tableName()) . '.' . $DB->quoteIdentifier($k) ) + : "{$this->tableName()}.{$k}"; - if (is_a($this->$k,'DB_DataObject_Cast')) { + 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)) { @@ -2500,7 +2763,7 @@ class DB_DataObject extends DB_DataObject_Overload continue; } - if (!isset($options['disable_null_strings']) && (strtolower($this->$k) === 'null') && !($v & DB_DATAOBJECT_NOTNULL)) { + if (!($v & DB_DATAOBJECT_NOTNULL) && DB_DataObject::_is_null($this,$k)) { $this->whereAdd(" $kSql IS NULL"); continue; } @@ -2525,28 +2788,6 @@ class DB_DataObject extends DB_DataObject_Overload } } - /** - * autoload Class relating to a table - * (depreciated - 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; - } /** @@ -2556,11 +2797,13 @@ class DB_DataObject extends DB_DataObject_Overload * use @ to silence it (if you are sure it is acceptable) * eg. $do = @DB_DataObject::factory('person') * - * table name will eventually be databasename/table + * 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 @@ -2568,15 +2811,35 @@ class DB_DataObject extends DB_DataObject_Overload - function factory($table = '') { + 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->__table)) { - $table = $this->__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", @@ -2584,24 +2847,49 @@ class DB_DataObject extends DB_DataObject_Overload } } + */ + // does this need multi db support?? + $cp = isset($_DB_DATAOBJECT['CONFIG']['class_prefix']) ? + explode(PATH_SEPARATOR, $_DB_DATAOBJECT['CONFIG']['class_prefix']) : ''; - $p = isset($_DB_DATAOBJECT['CONFIG']['class_prefix']) ? - $_DB_DATAOBJECT['CONFIG']['class_prefix'] : ''; - $class = $p . preg_replace('/[^A-Z0-9]/i','_',ucfirst($table)); + //print_r($cp); - $ce = substr(phpversion(),0,1) > 4 ? class_exists($class,false) : class_exists($class); - $class = $ce ? $class : DB_DataObject::_autoloadClass($class); + // 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 (!$class && isset($_DB_DATAOBJECT['CONFIG']['proxy'])) { + 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; - if (is_a($ret = $d->_connect(), 'PEAR_Error')) { + + $ret = $d->_connect(); + if (is_object($ret) && is_a($ret, 'PEAR_Error')) { return $ret; } @@ -2609,22 +2897,32 @@ class DB_DataObject extends DB_DataObject_Overload return $x->$proxyMethod( $d->_database, $table); } - if (!$class) { - return DB_DataObject::raiseError( - "factory could not find class $class from $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); } - - return new $class; + + $ret = new $rclass(); + + if (!empty($database)) { + DB_DataObject::debug("Setting database to $database","FACTORY",1); + $ret->database($database); + } + return $ret; } /** * autoload Class * - * @param string $class Class + * @param string|array $class Class + * @param string $table Table trying to load. * @access private * @return string classname on Success */ - function _autoloadClass($class) + function _autoloadClass($class, $table=false) { global $_DB_DATAOBJECT; @@ -2634,32 +2932,63 @@ class DB_DataObject extends DB_DataObject_Overload $class_prefix = empty($_DB_DATAOBJECT['CONFIG']['class_prefix']) ? '' : $_DB_DATAOBJECT['CONFIG']['class_prefix']; - $table = substr($class,strlen($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']; - if (strpos($_DB_DATAOBJECT['CONFIG']['class_location'],'%s') !== false) { - $file = sprintf($_DB_DATAOBJECT['CONFIG']['class_location'], preg_replace('/[^A-Z0-9]/i','_',ucfirst($table))); - } else { - $file = $_DB_DATAOBJECT['CONFIG']['class_location'].'/'.preg_replace('/[^A-Z0-9]/i','_',ucfirst($table)).".php"; + 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; } - if (!file_exists($file)) { + $cls = is_array($class) ? $class : array($class); + + if (is_array($file) || !file_exists($file)) { $found = false; - foreach(explode(PATH_SEPARATOR, ini_get('include_path')) as $p) { - if (file_exists("$p/$file")) { - $file = "$p/$file"; - $found = true; + + $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) { - DB_DataObject::raiseError( - "autoload:Could not find class {$class} using class_location value", + $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; } @@ -2667,12 +2996,19 @@ class DB_DataObject extends DB_DataObject_Overload include_once $file; - - $ce = substr(phpversion(),0,1) > 4 ? class_exists($class,false) : class_exists($class); - + + $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) { - DB_DataObject::raiseError( - "autoload:Could not autoload {$class}", + $dor = new DB_DataObject(); + $dor->raiseError( + "autoload:Could not autoload " . implode(',', $cls) , DB_DATAOBJECT_ERROR_INVALIDCONFIG); return false; } @@ -2698,11 +3034,14 @@ class DB_DataObject extends DB_DataObject_Overload * 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 - * null - if no links.ini exists for this database (hence try auto_links). + * false - if no links.ini exists for this database (hence try auto_links). * @access public * @see DB_DataObject::getLinks(), DB_DataObject::getLink() */ @@ -2716,237 +3055,190 @@ class DB_DataObject extends DB_DataObject_Overload // have to connect.. -> otherwise things break later. $this->_connect(); - if (isset($_DB_DATAOBJECT['LINKS'][$this->_database][$this->__table])) { - return $_DB_DATAOBJECT['LINKS'][$this->_database][$this->__table]; + // 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(); } - - - - - // attempt to load links file here.. - - if (!isset($_DB_DATAOBJECT['LINKS'][$this->_database])) { - $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}"]); + // 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); + } - - foreach ($schemas as $ini) { + } + + if (!empty($_DB_DATAOBJECT['CONFIG']['portability']) && $_DB_DATAOBJECT['CONFIG']['portability'] & 1) { + foreach($lcfg[$this->_database] as $k=>$v) { - $links = - isset($_DB_DATAOBJECT['CONFIG']["links_{$this->_database}"]) ? - $_DB_DATAOBJECT['CONFIG']["links_{$this->_database}"] : - str_replace('.ini','.links.ini',$ini); - - if (empty($_DB_DATAOBJECT['LINKS'][$this->_database]) && file_exists($links) && is_file($links)) { - /* not sure why $links = ... here - TODO check if that works */ - $_DB_DATAOBJECT['LINKS'][$this->_database] = parse_ini_file($links, true); - if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { - $this->debug("Loaded links.ini file: $links","links",1); - } - } else { - if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { - $this->debug("Missing links.ini file: $links","links",1); - } + $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 (!isset($_DB_DATAOBJECT['LINKS'][$this->_database])) {
+        if ($lcfg[$this->_database] === false) {
             return null;
         }
         
-        if (isset($_DB_DATAOBJECT['LINKS'][$this->_database][$this->__table])) {
-            return $_DB_DATAOBJECT['LINKS'][$this->_database][$this->__table];
+        if (isset($lcfg[$this->_database][$this->tableName()])) {
+            return $lcfg[$this->_database][$this->tableName()];
         }
         
         return array();
     }
+    
+    
     /**
-     * load related objects
+     * generic getter/setter for links
      *
-     * There are two ways to use this, one is to set up a .links.ini file
-     * into a static property named .links and specifies the table joins,
-     * the other highly dependent 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
-     * you can change what object vars the links are stored in by 
-     * changeing the format parameter
+     * 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));
      *
-     * @param  string format (default _%s) where %s is the table name.
-     * @author Tim White 
+     *  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 boolean , true on success
+     * @return mixed true or false on setting, object on getting
      */
-    function getLinks($format = '_%s')
+    function link($field, $set_args = array())
     {
-         
-        // get table will load the options.
-        if ($this->_link_loaded) {
-            return true;
-        }
-        $this->_link_loaded = false;
-        $cols  = $this->table();
-        $links = $this->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->$k = $this->getLink($key, $table, $link);
-                
-                if (is_object($this->$k)) {
-                    $loaded[] = $k; 
-                }
-            }
-            $this->_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 DEPRECIATED!!!! - USE 
-        if (!is_null($links)) {    
-            return false;
-        }
-        
+        require_once 'DB/DataObject/Links.php';
+        $l = new DB_DataObject_Links($this);
+        return  $l->link($field,$set_args) ;
         
-        foreach (array_keys($cols) as $key) {
-            if (!($p = strpos($key, '_'))) {
-                continue;
-            }
-            // does the table exist.
-            $k =sprintf($format, $key);
-            $this->$k = $this->getLink($key);
-            if (is_object($this->$k)) {
-                $loaded[] = $k; 
-            }
-        }
-        $this->_link_loaded = $loaded;
-        return true;
     }
-
-    /**
-     * return name from related object
+    
+      /**
+     * load related objects
      *
-     * There are two ways to use this, one is to set up a .links.ini file
-     * into a static property named .links and specifies the table joins,
-     * the other is highly dependant on naming columns 'correctly' :)
+     * Generally not recommended to use this.
+     * The generator should support creating getter_setter methods which are better suited.
      *
-     * NOTE: the naming convention is depreciated!!! - use links.ini
+     * Relies on  .links.ini
      *
-     * 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
-     *
-     * you can also use $this->getLink('thisColumnName','otherTable','otherTableColumnName')
+     * Sets properties on the calling dataobject  you can change what
+     * object vars the links are stored in by  changeing the format parameter
      *
      *
-     * @param string $row    either row or row.xxxxx
-     * @param string $table  name of table to look up value in
-     * @param string $link   name of column in other table to match
+     * @param  string format (default _%s) where %s is the table name.
      * @author Tim White 
      * @access public
-     * @return mixed object on success
+     * @return boolean , true on success
      */
-    function getLink($row, $table = null, $link = false)
+    function getLinks($format = '_%s')
     {
-        
-        
-        // GUESS THE LINKED TABLE.. (if found - recursevly call self)
-        
-        if ($table === null) {
-            $links = $this->links();
-            
-            if (is_array($links)) {
-            
-                if ($links[$row]) {
-                    list($table,$link) = explode(':', $links[$row]);
-                    if ($p = strpos($row,".")) {
-                        $row = substr($row,0,$p);
-                    }
-                    return $this->getLink($row,$table,$link);
-                    
-                } 
-                
-                $this->raiseError(
-                    "getLink: $row is not defined as a link (normally this is ok)", 
-                    DB_DATAOBJECT_ERROR_NODATA);
-                    
-                $r = false;
-                return $r;// technically a possible error condition?
-
-            }  
-            // use the old _ method - this shouldnt happen if called via getLinks()
-            if (!($p = strpos($row, '_'))) {
-                $r = null;
-                return $r; 
-            }
-            $table = substr($row, 0, $p);
-            return $this->getLink($row, $table);
-            
+        require_once 'DB/DataObject/Links.php';
+         $l = new DB_DataObject_Links($this);
+        return $l->applyLinks($format);
+           
+    }
 
-        }
-        
-        
-        
-        if (!isset($this->$row)) {
-            $this->raiseError("getLink: row not set $row", DB_DATAOBJECT_ERROR_NODATA);
-            return false;
-        }
-        
-        // check to see if we know anything about this table..
-        
-        $obj = $this->factory($table);
-        
-        if (!is_a($obj,'DB_DataObject')) {
-            $this->raiseError(
-                "getLink:Could not find class for row $row, table $table", 
-                DB_DATAOBJECT_ERROR_INVALIDCONFIG);
-            return false;
-        }
-        if ($link) {
-            if ($obj->get($link, $this->$row)) {
-                $obj->free();
-                return $obj;
-            } 
-            return  false;
-        }
-        
-        if ($obj->get($this->$row)) {
-            $obj->free();
-            return $obj;
-        }
-        return false;
+    /**
+     * 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);
+         
         
     }
 
     /**
-     * IS THIS SUPPORTED/USED ANYMORE???? 
-     *return a list of options for a linked table
-     *
+     * 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..)
@@ -2954,51 +3246,70 @@ class DB_DataObject extends DB_DataObject_Overload
      * stores it in $this->_xxxxx_yyyyy
      *
      * @access public
-     * @return array of results (empty array on failure)
+     * @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) + function getLinkArray($row, $table = null) { - - $ret = array(); - if (!$table) { - $links = $this->links(); - - if (is_array($links)) { - if (!isset($links[$row])) { - // failed.. - return $ret; - } - list($table,$link) = explode(':',$links[$row]); - } else { - if (!($p = strpos($row,'_'))) { - return $ret; - } - $table = substr($row,0,$p); - } - } - - $c = $this->factory($table); - - if (!is_a($c,'DB_DataObject')) { - $this->raiseError( - "getLinkArray:Could not find class for row $row, table $table", - DB_DATAOBJECT_ERROR_INVALIDCONFIG - ); - return $ret; - } + require_once 'DB/DataObject/Links.php'; + $l = new DB_DataObject_Links($this); + return $l->getLinkArray($row, $table === null ? false: $table); + + } - // if the user defined method list exists - use it... - if (method_exists($c, 'listFind')) { - $c->listFind($this->id); - } else { - $c->find(); - } - while ($c->fetch()) { - $ret[] = $c; + /** + * 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; } - return $ret; + $this->_query['unions'][] = array($obj, 'UNION ' . $is_all . ' ') ; + return $obj; } + + /** * The JOIN condition * @@ -3036,9 +3347,10 @@ class DB_DataObject extends DB_DataObject_Overload * @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.. - * + * 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 @@ -3079,7 +3391,7 @@ class DB_DataObject extends DB_DataObject_Overload return; } - + //echo '
'; print_r(func_get_args());
         $useWhereAsOn = false;
         // support for 2nd argument as an array of options
         if (is_array($joinType)) {
@@ -3098,14 +3410,21 @@ class DB_DataObject extends DB_DataObject_Overload
         $toTable = false;
         if (is_array($obj)) {
             $tfield = $obj[0];
-            list($toTable,$ofield) = explode(':',$obj[1]);
-            $obj = DB_DataObject::factory($toTable);
             
-            if (!$obj || is_a($obj,'PEAR_Error')) {
-                $obj = new DB_DataObject;
-                $obj->__table = $toTable;
+            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();
             }
-            $obj->_connect();
             // set the table items to nothing.. - eg. do not try and match
             // things in the child table...???
             $items = array();
@@ -3116,32 +3435,85 @@ class DB_DataObject extends DB_DataObject_Overload
         }
         /*  make sure $this->_database is set.  */
         $this->_connect();
-        $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
+        $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 => $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);
+            foreach ($olinks as $k => $linkVar) {
+                /* link contains {this column} = array ( {linked table}:{linked column} )*/
+                if (!is_array($linkVar)) {
+                    $linkVar  = array($linkVar);
                 }
-                
-                $ar_array = strpos($ar[1],',');
-                if ($ar_array !== false) {
-                    $ar[1] = explode(',', $ar[1]);
-                }
-             
-                if ($ar[0] == $this->__table) {
+                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..
@@ -3160,41 +3532,11 @@ class DB_DataObject extends DB_DataObject_Overload
                     $ofield = $k;
                     $tfield = $ar[1];
                     break;
+                    
                 }
             }
         }
 
-        /* otherwise see if there are any links from this table to the obj. */
-        //print_r($this->links());
-        if (($ofield === false) && ($links = $this->links())) {
-            foreach ($links as $k => $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->__table) {
-                    if ($joinCol !== false) {
-                        if ($k == $joinCol) {
-                            $tfield = $k;
-                            $ofield = $ar[1];
-                            break;
-                        } else {
-                            continue;
-                        }
-                    } else {
-                        $tfield = $k;
-                        $ofield = $ar[1];
-                        break;
-                    }
-                }
-            }
-        }
         // finally if these two table have column names that match do a join by default on them
 
         if (($ofield === false) && $joinCol) {
@@ -3206,7 +3548,7 @@ class DB_DataObject extends DB_DataObject_Overload
 
         if ($ofield === false) {
             $this->raiseError(
-                "joinAdd: {$obj->__table} has no link with {$this->__table}",
+                "joinAdd: {$obj->tableName()} has no link with {$this->tableName()}",
                 DB_DATAOBJECT_ERROR_NODATA);
             return false;
         }
@@ -3215,7 +3557,7 @@ class DB_DataObject extends DB_DataObject_Overload
         // we default to joining as the same name (this is remvoed later..)
         
         if ($joinAs === false) {
-            $joinAs = $obj->__table;
+            $joinAs = $obj->tableName();
         }
         
         $quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
@@ -3223,8 +3565,8 @@ class DB_DataObject extends DB_DataObject_Overload
         
         // not sure  how portable adding database prefixes is..
         $objTable = $quoteIdentifiers ? 
-                $DB->quoteIdentifier($obj->__table) : 
-                 $obj->__table ;
+                $DB->quoteIdentifier($obj->tableName()) : 
+                 $obj->tableName() ;
                 
         $dbPrefix  = '';
         if (strlen($obj->_database) && in_array($DB->dsn['phptype'],array('mysql','mysqli'))) {
@@ -3267,14 +3609,23 @@ class DB_DataObject extends DB_DataObject_Overload
                     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)) {
+                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) (
@@ -3291,19 +3642,14 @@ class DB_DataObject extends DB_DataObject_Overload
                     continue;
                 }
                             
-                if (is_a($obj->$k,'DB_DataObject_Cast')) {
+                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;
-                    }
-                    if (!isset($options['disable_null_strings']) && strtolower($value) === 'null') {
-                        $obj->whereAdd("{$joinAs}.{$kSql} IS NULL");
-                        continue;
-                    } else {
-                        $obj->whereAdd("{$joinAs}.{$kSql} = $value");
-                        continue;
-                    }
+                    } 
+                    $obj->whereAdd("{$joinAs}.{$kSql} = $value");
+                    continue;
                 }
                 
                 
@@ -3353,7 +3699,7 @@ class DB_DataObject extends DB_DataObject_Overload
                
         
         
-        $table = $this->__table;
+        $table = $this->tableName();
         
         if ($quoteIdentifiers) {
             $joinAs   = $DB->quoteIdentifier($joinAs);
@@ -3365,7 +3711,7 @@ class DB_DataObject extends DB_DataObject_Overload
        
         
         $fullJoinAs = '';
-        $addJoinAs  = ($quoteIdentifiers ? $DB->quoteIdentifier($obj->__table) : $obj->__table) != $joinAs;
+        $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
@@ -3383,22 +3729,25 @@ class DB_DataObject extends DB_DataObject_Overload
             case 'RIGHT': // others??? .. cross, left outer, right outer, natural..?
                 
                 // Feature Request #4266 - Allow joins with multiple keys
-                $this->_join .= "\n {$joinType} JOIN {$objTable} {$fullJoinAs}";
+                $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) {
-                    		$this->_join .= " ON ({$joinAs}.{$ofield[$i]}={$table}.{$tfield[$i]}) ";
+                    		$jadd .= " ON ({$joinAs}.{$ofield[$i]}={$table}.{$tfield[$i]}) ";
                     	}
                     	else {
-                    		$this->_join .= " AND {$joinAs}.{$ofield[$i]}={$table}.{$tfield[$i]} ";
+                    		$jadd .= " AND {$joinAs}.{$ofield[$i]}={$table}.{$tfield[$i]} ";
                     	}
                     }
-                    $this->_join .= ' ' . $appendJoin . ' ';
+                    $jadd .= ' ' . $appendJoin . ' ';
                 } else {
-	                $this->_join .= " ON ({$joinAs}.{$ofield}={$table}.{$tfield}) {$appendJoin} ";
+	                $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..
@@ -3411,6 +3760,246 @@ class DB_DataObject extends DB_DataObject_Overload
 
     }
 
+    /**
+     * 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())
+    {
+        //var_Dump($cfg);exit;
+        $pre_links = $this->links();
+        if (!empty($cfg['links'])) {
+            $this->links(array_merge( $pre_links , $cfg['links']));
+        }
+        $map = $this->links( );
+        
+        
+        //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!!!
+            $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
@@ -3428,9 +4017,10 @@ class DB_DataObject extends DB_DataObject_Overload
         global $_DB_DATAOBJECT;
         $keys  = $this->keys();
         $items = $this->table();
+        
         if (!$items) {
             $this->raiseError(
-                "setFrom:Could not find table definition for {$this->__table}", 
+                "setFrom:Could not find table definition for {$this->tableName()}", 
                 DB_DATAOBJECT_ERROR_INVALIDCONFIG);
             return;
         }
@@ -3442,7 +4032,14 @@ class DB_DataObject extends DB_DataObject_Overload
             if (!$k) {
                 continue; // ignore empty keys!!! what
             }
-            if (is_object($from) && isset($from->{sprintf($format,$k)})) {
+            
+            $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)});
@@ -3459,11 +4056,11 @@ class DB_DataObject extends DB_DataObject_Overload
                 continue;
             }
             
-            if (empty($from[$k]) && $skipEmpty) {
+            if (empty($from[sprintf($format,$k)]) && $skipEmpty) {
                 continue;
             }
             
-            if (!isset($from[sprintf($format,$k)])) {
+            if (!isset($from[sprintf($format,$k)]) && !DB_DataObject::_is_null($from, sprintf($format,$k))) {
                 continue;
             }
            
@@ -3475,13 +4072,15 @@ class DB_DataObject extends DB_DataObject_Overload
                 }
                 continue;
             }
-            if (is_object($from[sprintf($format,$k)])) {
+            $val = $from[sprintf($format,$k)];
+            if (is_a($val, 'DB_DataObject_Cast')) {
+                $this->$k = $val;
                 continue;
             }
-            if (is_array($from[sprintf($format,$k)])) {
+            if (is_object($val) || is_array($val)) {
                 continue;
             }
-            $ret = $this->fromValue($k,$from[sprintf($format,$k)]);
+            $ret = $this->fromValue($k,$val);
             if ($ret !== true)  {
                 $overload_return[$k] = 'Not A Valid Value';
             }
@@ -3504,7 +4103,9 @@ class DB_DataObject extends DB_DataObject_Overload
      * will also return links converted to arrays.
      *
      * @param   string  sprintf format for array
-     * @param   bool    empty only return elemnts that have a value set.
+     * @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
@@ -3513,34 +4114,40 @@ class DB_DataObject extends DB_DataObject_Overload
     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);
+                (isset($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid]) ?
+                 $_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid] : false);
+        
         $ar = ($rf !== false) ?
-            array_merge($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid],$this->table()) :
+            (($hideEmpty === 0) ? $rf : array_merge($rf, $this->table())) :
             $this->table();
 
         foreach($ar as $k=>$v) {
              
             if (!isset($this->$k)) {
                 if (!$hideEmpty) {
-                    $ret[sprintf($format,$k)] = '';
+                    $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[sprintf($format,$k)] = $this->{'get'.$k}();
+                $ret[$format === false ? $k : sprintf($format,$k)] = $this->{'get'.$k}();
                 continue;
             }
             // should this call toValue() ???
-            $ret[sprintf($format,$k)] = $this->$k;
+            $ret[$format === false ? $k : sprintf($format,$k)] = $this->$k;
         }
         if (!$this->_link_loaded) {
             return $ret;
         }
         foreach($this->_link_loaded as $k) {
-            $ret[sprintf($format,$k)] = $this->$k->toArray();
+            $ret[$format === false ? $k : sprintf($format,$k)] = $this->$k->toArray();
         
         }
         
@@ -3592,7 +4199,7 @@ class DB_DataObject extends DB_DataObject_Overload
             
             // if not null - and it's not set.......
             
-            if (!isset($this->$key) && ($val & DB_DATAOBJECT_NOTNULL)) {
+            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;
@@ -3602,7 +4209,7 @@ class DB_DataObject extends DB_DataObject_Overload
             }
             
             
-            if (!isset($options['disable_null_strings']) && is_string($this->$key) && (strtolower($this->$key) == 'null')) {
+             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;
@@ -3655,7 +4262,7 @@ class DB_DataObject extends DB_DataObject_Overload
      * @access public
      * @return object The DB connection
      */
-    function &getDatabaseConnection()
+    function getDatabaseConnection()
     {
         global $_DB_DATAOBJECT;
 
@@ -3678,7 +4285,7 @@ class DB_DataObject extends DB_DataObject_Overload
      * @return object The DB result object
      */
      
-    function &getDatabaseResult()
+    function getDatabaseResult()
     {
         global $_DB_DATAOBJECT;
         $this->_connect();
@@ -3810,20 +4417,21 @@ class DB_DataObject extends DB_DataObject_Overload
         $options = $_DB_DATAOBJECT['CONFIG'];
         $cols = $this->table();
         // dont know anything about this col..
-        if (!isset($cols[$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 (!isset($options['disable_null_strings']) && (strtolower($value) == 'null') && (!($cols[$col] & DB_DATAOBJECT_NOTNULL))):
+            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 (!isset($options['disable_null_strings']) && (strtolower($value) == 'null') && ($cols[$col] & DB_DATAOBJECT_NOTNULL)):
+            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)):
@@ -4027,7 +4635,7 @@ class DB_DataObject extends DB_DataObject_Overload
      * @access  public
      * @return  none
      */
-    function debugLevel($v = null)
+    static function debugLevel($v = null)
     {
         global $_DB_DATAOBJECT;
         if (empty($_DB_DATAOBJECT['CONFIG'])) {
@@ -4044,7 +4652,7 @@ class DB_DataObject extends DB_DataObject_Overload
     /**
      * Last Error that has occured
      * - use $this->_lastError or
-     * $last_error = &PEAR::getStaticProperty('DB_DataObject','lastError');
+     * $last_error = PEAR::getStaticProperty('DB_DataObject','lastError');
      *
      * @access  public
      * @var     object PEAR_Error (or false)
@@ -4069,17 +4677,10 @@ class DB_DataObject extends DB_DataObject_Overload
         if ($behaviour == PEAR_ERROR_DIE && !empty($_DB_DATAOBJECT['CONFIG']['dont_die'])) {
             $behaviour = null;
         }
+        
         $error = &PEAR::getStaticProperty('DB_DataObject','lastError');
         
-        // this will never work totally with PHP's object model.
-        // as this is passed on static calls (like staticGet in our case)
-
-        if (isset($this) && is_object($this) && is_subclass_of($this,'db_dataobject')) {
-            $this->_lastError = $error;
-        }
-
-        $_DB_DATAOBJECT['LASTERROR'] = $error;
-
+      
         // no checks for production here?....... - we log  errors before we throw them.
         DB_DataObject::debug($message,'ERROR',1);
         
@@ -4088,10 +4689,19 @@ class DB_DataObject extends DB_DataObject_Overload
             $error = $message;
         } else {
             require_once 'DB/DataObject/Error.php';
-            $error = PEAR::raiseError($message, $type, $behaviour,
+            $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;
     }
@@ -4138,9 +4748,143 @@ class DB_DataObject extends DB_DataObject_Overload
         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. ---*/
     
@@ -4163,3 +4907,4 @@ if (!defined('DB_DATAOBJECT_NO_OVERLOAD')) {
     }
 }
 
+