DB_DataObject
[ class tree: DB_DataObject ] [ index: DB_DataObject ] [ all elements ]

Source for file DataObject.php

Documentation is available at DataObject.php

  1. <?php
  2. /**
  3.  * Object Based Database Query Builder and data store
  4.  *
  5.  * For PHP versions 4,5 and 6
  6.  *
  7.  * LICENSE: This source file is subject to version 3.01 of the PHP license
  8.  * that is available through the world-wide-web at the following URI:
  9.  * http://www.php.net/license/3_01.txt.  If you did not receive a copy of
  10.  * the PHP License and are unable to obtain it through the web, please
  11.  * send a note to license@php.net so we can mail you a copy immediately.
  12.  *
  13.  * @category   Database
  14.  * @package    DB_DataObject
  15.  * @author     Alan Knowles <alan@akbkhome.com>
  16.  * @copyright  1997-2006 The PHP Group
  17.  * @license    http://www.php.net/license/3_01.txt  PHP License 3.01
  18.  * @version    CVS: $Id: DataObject.php 329992 2013-04-03 11:38:43Z alan_k $
  19.  * @link       http://pear.php.net/package/DB_DataObject
  20.  */
  21.   
  22.  
  23. /* =========================================================================== 
  24.  *
  25.  *    !!!!!!!!!!!!!               W A R N I N G                !!!!!!!!!!!
  26.  *
  27.  *  THIS MAY SEGFAULT PHP IF YOU ARE USING THE ZEND OPTIMIZER (to fix it, 
  28.  *  just add "define('DB_DATAOBJECT_NO_OVERLOAD',true);" before you include 
  29.  *  this file. reducing the optimization level may also solve the segfault.
  30.  *  ===========================================================================
  31.  */
  32.  
  33. /**
  34.  * The main "DB_DataObject" class is really a base class for your own tables classes
  35.  *
  36.  * // Set up the class by creating an ini file (refer to the manual for more details
  37.  * [DB_DataObject]
  38.  * database         = mysql:/username:password@host/database
  39.  * schema_location = /home/myapplication/database
  40.  * class_location  = /home/myapplication/DBTables/
  41.  * clase_prefix    = DBTables_
  42.  *
  43.  *
  44.  * //Start and initialize...................... - dont forget the &
  45.  * $config = parse_ini_file('example.ini',true);
  46.  * $options = &PEAR::getStaticProperty('DB_DataObject','options');
  47.  * $options = $config['DB_DataObject'];
  48.  *
  49.  * // example of a class (that does not use the 'auto generated tables data')
  50.  * class mytable extends DB_DataObject {
  51.  *     // mandatory - set the table
  52.  *     var $_database_dsn = "mysql://username:password@localhost/database";
  53.  *     var $__table = "mytable";
  54.  *     function table() {
  55.  *         return array(
  56.  *             'id' => 1, // integer or number
  57.  *             'name' => 2, // string
  58.  *        );
  59.  *     }
  60.  *     function keys() {
  61.  *         return array('id');
  62.  *     }
  63.  * }
  64.  *
  65.  * // use in the application
  66.  *
  67.  *
  68.  * Simple get one row
  69.  *
  70.  * $instance = new mytable;
  71.  * $instance->get("id",12);
  72.  * echo $instance->somedata;
  73.  *
  74.  *
  75.  * Get multiple rows
  76.  *
  77.  * $instance = new mytable;
  78.  * $instance->whereAdd("ID > 12");
  79.  * $instance->whereAdd("ID < 14");
  80.  * $instance->find();
  81.  * while ($instance->fetch()) {
  82.  *     echo $instance->somedata;
  83.  * }
  84.  
  85.  
  86. /**
  87.  * Needed classes
  88.  * - we use getStaticProperty from PEAR pretty extensively (cant remove it ATM)
  89.  */
  90.  
  91. require_once 'PEAR.php';
  92.  
  93. /**
  94.  * We are duping fetchmode constants to be compatible with
  95.  * both DB and MDB2
  96.  */
  97. define('DB_DATAOBJECT_FETCHMODE_ORDERED',1)
  98. define('DB_DATAOBJECT_FETCHMODE_ASSOC',2);
  99.  
  100.  
  101.  
  102.  
  103.  
  104. /**
  105.  * these are constants for the get_table array
  106.  * user to determine what type of escaping is required around the object vars.
  107.  */
  108. define('DB_DATAOBJECT_INT',  1);  // does not require ''
  109. define('DB_DATAOBJECT_STR',  2);  // requires ''
  110.  
  111. define('DB_DATAOBJECT_DATE'4);  // is date #TODO
  112. define('DB_DATAOBJECT_TIME'8);  // is time #TODO
  113. define('DB_DATAOBJECT_BOOL'16)// is boolean #TODO
  114. define('DB_DATAOBJECT_TXT',  32)// is long text #TODO
  115. define('DB_DATAOBJECT_BLOB'64)// is blob type
  116.  
  117.  
  118. define('DB_DATAOBJECT_NOTNULL'128);           // not null col.
  119. define('DB_DATAOBJECT_MYSQLTIMESTAMP'   256);           // mysql timestamps (ignored by update/insert)
  120. /*
  121.  * Define this before you include DataObjects.php to  disable overload - if it segfaults due to Zend optimizer..
  122.  */
  123. //define('DB_DATAOBJECT_NO_OVERLOAD',true)  
  124.  
  125.  
  126. /**
  127.  * Theses are the standard error codes, most methods will fail silently - and return false
  128.  * to access the error message either use $table->_lastError
  129.  * or $last_error = PEAR::getStaticProperty('DB_DataObject','lastError');
  130.  * the code is $last_error->code, and the message is $last_error->message (a standard PEAR error)
  131.  */
  132.  
  133. define('DB_DATAOBJECT_ERROR_INVALIDARGS',   -1);  // wrong args to function
  134. define('DB_DATAOBJECT_ERROR_NODATA',        -2);  // no data available
  135. define('DB_DATAOBJECT_ERROR_INVALIDCONFIG'-3);  // something wrong with the config
  136. define('DB_DATAOBJECT_ERROR_NOCLASS',       -4);  // no class exists
  137. define('DB_DATAOBJECT_ERROR_INVALID_CALL'  ,-7);  // overlad getter/setter failure
  138.  
  139. /**
  140.  * Used in methods like delete() and count() to specify that the method should
  141.  * build the condition only out of the whereAdd's and not the object parameters.
  142.  */
  143. define('DB_DATAOBJECT_WHEREADD_ONLY'true);
  144.  
  145. /**
  146.  *
  147.  * storage for connection and result objects,
  148.  * it is done this way so that print_r()'ing the is smaller, and
  149.  * it reduces the memory size of the object.
  150.  * -- future versions may use $this->_connection = & PEAR object..
  151.  *   although will need speed tests to see how this affects it.
  152.  * - includes sub arrays
  153.  *   - connections = md5 sum mapp to pear db object
  154.  *   - results     = [id] => map to pear db object
  155.  *   - resultseq   = sequence id for results & results field
  156.  *   - resultfields = [id] => list of fields return from query (for use with toArray())
  157.  *   - ini         = mapping of database to ini file results
  158.  *   - links       = mapping of database to links file
  159.  *   - lasterror   = pear error objects for last error event.
  160.  *   - config      = aliased view of PEAR::getStaticPropery('DB_DataObject','options') * done for performance.
  161.  *   - array of loaded classes by autoload method - to stop it doing file access request over and over again!
  162.  */
  163. $GLOBALS['_DB_DATAOBJECT']['RESULTS']   = array();
  164. $GLOBALS['_DB_DATAOBJECT']['RESULTSEQ'= 1;
  165. $GLOBALS['_DB_DATAOBJECT']['RESULTFIELDS'= array();
  166. $GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'= array();
  167. $GLOBALS['_DB_DATAOBJECT']['INI'= array();
  168. $GLOBALS['_DB_DATAOBJECT']['LINKS'= array();
  169. $GLOBALS['_DB_DATAOBJECT']['SEQUENCE'= array();
  170. $GLOBALS['_DB_DATAOBJECT']['LASTERROR'= null;
  171. $GLOBALS['_DB_DATAOBJECT']['CONFIG'= array();
  172. $GLOBALS['_DB_DATAOBJECT']['CACHE'= array();
  173. $GLOBALS['_DB_DATAOBJECT']['OVERLOADED'= false;
  174. $GLOBALS['_DB_DATAOBJECT']['QUERYENDTIME'= 0;
  175.  
  176.  
  177.  
  178. // this will be horrifically slow!!!!
  179. // these two are BC/FC handlers for call in PHP4/5
  180.  
  181.  
  182. if (!defined('DB_DATAOBJECT_NO_OVERLOAD')) {
  183.     
  184.     class DB_DataObject_Overload 
  185.     {
  186.         function __call($method,$args
  187.         {
  188.             $return = null;
  189.             $this->_call($method,$args,$return);
  190.             return $return;
  191.         }
  192.         function __sleep(
  193.         {
  194.             return array_keys(get_object_vars($this)) 
  195.         }
  196.     }
  197. else {
  198.     class DB_DataObject_Overload {}
  199. }
  200.  
  201.  
  202.     
  203.  
  204.  
  205.  
  206.  
  207.  /*
  208.  *
  209.  * @package  DB_DataObject
  210.  * @author   Alan Knowles <alan@akbkhome.com>
  211.  * @since    PHP 4.0
  212.  */
  213.  
  214. class DB_DataObject extends DB_DataObject_Overload
  215. {
  216.    /**
  217.     * The Version - use this to check feature changes
  218.     *
  219.     * @access   private
  220.     * @var      string 
  221.     */
  222.     var $_DB_DataObject_version "1.11.0";
  223.  
  224.     /**
  225.      * The Database table (used by table extends)
  226.      *
  227.      * @access  private
  228.      * @var     string 
  229.      */
  230.     var $__table '';  // database table
  231.  
  232.     /**
  233.      * The Number of rows returned from a query
  234.      *
  235.      * @access  public
  236.      * @var     int 
  237.      */
  238.     var $N = 0;  // Number of rows returned from a query
  239.  
  240.     /* ============================================================= */
  241.     /*                      Major Public Methods                     */
  242.     /* (designed to be optionally then called with parent::method()) */
  243.     /* ============================================================= */
  244.  
  245.  
  246.     /**
  247.      * Get a result using key, value.
  248.      *
  249.      * for example
  250.      * $object->get("ID",1234);
  251.      * Returns Number of rows located (usually 1) for success,
  252.      * and puts all the table columns into this classes variables
  253.      *
  254.      * see the fetch example on how to extend this.
  255.      *
  256.      * if no value is entered, it is assumed that $key is a value
  257.      * and get will then use the first key in keys()
  258.      * to obtain the key.
  259.      *
  260.      * @param   string  $k column
  261.      * @param   string  $v value
  262.      * @access  public
  263.      * @return  int     No. of rows
  264.      */
  265.     function get($k = null$v = null)
  266.     {
  267.         global $_DB_DATAOBJECT;
  268.         if (empty($_DB_DATAOBJECT['CONFIG'])) {
  269.             DB_DataObject::_loadConfig();
  270.         }
  271.         $keys = array();
  272.         
  273.         if ($v === null{
  274.             $v $k;
  275.             $keys $this->keys();
  276.             if (!$keys{
  277.                 $this->raiseError("No Keys available for {$this->tableName()}"DB_DATAOBJECT_ERROR_INVALIDCONFIG);
  278.                 return false;
  279.             }
  280.             $k $keys[0];
  281.         }
  282.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  283.             $this->debug("$k $v " .print_r($keys,true)"GET");
  284.         }
  285.         
  286.         if ($v === null{
  287.             $this->raiseError("No Value specified for get"DB_DATAOBJECT_ERROR_INVALIDARGS);
  288.             return false;
  289.         }
  290.         $this->$k $v;
  291.         return $this->find(1);
  292.     }
  293.     
  294.     /**
  295.      * Get the value of the primary id
  296.      *
  297.      * While I normally use 'id' as the PRIMARY KEY value, some database use
  298.      * {table}_id as the column name.
  299.      *
  300.      * To save a bit of typing,
  301.      *
  302.      * $id = $do->pid();
  303.      *
  304.      * @return the id
  305.      */
  306.     function pid()
  307.     {
  308.         $keys $this->keys();
  309.         if (!$keys{
  310.             $this->raiseError("No Keys available for {$this->tableName()}",
  311.                             DB_DATAOBJECT_ERROR_INVALIDCONFIG);
  312.             return false;
  313.         }
  314.         $k $keys[0];
  315.         if (empty($this->$k)) // we do not 
  316.             $this->raiseError("pid() called on Object where primary key value not available",
  317.                             DB_DATAOBJECT_ERROR_NODATA);
  318.             return false;
  319.         }
  320.         return $this->$k;
  321.     }
  322.     
  323.  
  324.  
  325.     /**
  326.      * build the basic select query.
  327.      * 
  328.      * @access private
  329.      */
  330.     
  331.     function _build_select()
  332.     {
  333.         global $_DB_DATAOBJECT;
  334.         $quoteIdentifiers !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
  335.         if ($quoteIdentifiers{
  336.             $this->_connect();
  337.             $DB $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  338.         }
  339.         $tn ($quoteIdentifiers $DB->quoteIdentifier($this->tableName()) $this->tableName()) ;
  340.         if (!empty($this->_query['derive_table']&& !empty($this->_query['derive_select']) ) {
  341.             
  342.             // this is a derived select..
  343.             // not much support in the api yet..
  344.             
  345.              $sql 'SELECT ' .
  346.                $this->_query['derive_select']
  347.                .' FROM ( SELECT'.
  348.                     $this->_query['data_select'" \n" .
  349.                     " FROM   $tn \n" .
  350.                     $this->_join " \n" .
  351.                     $this->_query['condition'" \n" .
  352.                     $this->_query['group_by'" \n" .
  353.                     $this->_query['having'" \n" .
  354.                 ') ' $this->_query['derive_table'];
  355.                      
  356.             return $sql;
  357.             
  358.             
  359.         }
  360.         
  361.        
  362.         
  363.         $sql 'SELECT ' .
  364.             $this->_query['data_select'" \n" .
  365.             " FROM   $tn \n" .
  366.             $this->_join " \n" .
  367.             $this->_query['condition'" \n" .
  368.             $this->_query['group_by'" \n" .
  369.             $this->_query['having'" \n";
  370.                  
  371.         return $sql;
  372.     }
  373.  
  374.      
  375.     /**
  376.      * find results, either normal or crosstable
  377.      *
  378.      * for example
  379.      *
  380.      * $object = new mytable();
  381.      * $object->ID = 1;
  382.      * $object->find();
  383.      *
  384.      *
  385.      * will set $object->N to number of rows, and expects next command to fetch rows
  386.      * will return $object->N
  387.      *
  388.      * if an error occurs $object->N will be set to false and return value will also be false;
  389.      * if numRows is not supported it will
  390.      * 
  391.      *
  392.      * @param   boolean $n Fetch first result
  393.      * @access  public
  394.      * @return  mixed (number of rows returned, or true if numRows fetching is not supported)
  395.      */
  396.     function find($n = false)
  397.     {
  398.         global $_DB_DATAOBJECT;
  399.         if ($this->_query === false{
  400.             $this->raiseError(
  401.                 "You cannot do two queries on the same object (copy it before finding)"
  402.                 DB_DATAOBJECT_ERROR_INVALIDARGS);
  403.             return false;
  404.         }
  405.         
  406.         if (empty($_DB_DATAOBJECT['CONFIG'])) {
  407.             DB_DataObject::_loadConfig();
  408.         }
  409.  
  410.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  411.             $this->debug($n"find",1);
  412.         }
  413.         if (!$this->__table{
  414.             // xdebug can backtrace this!
  415.             trigger_error("NO \$__table SPECIFIED in class definition",E_USER_ERROR);
  416.         }
  417.         $this->= 0;
  418.         $query_before $this->_query;
  419.         $this->_build_condition($this->table()) ;
  420.         
  421.        
  422.         $this->_connect();
  423.         $DB $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  424.        
  425.         
  426.         $sql $this->_build_select();
  427.         
  428.         foreach ($this->_query['unions'as $union_ar{  
  429.             $sql .=   $union_ar[1.   $union_ar[0]->_build_select(" \n";
  430.         }
  431.         
  432.         $sql .=  $this->_query['order_by']  " \n";
  433.         
  434.         
  435.         /* We are checking for method modifyLimitQuery as it is PEAR DB specific */
  436.         if ((!isset($_DB_DATAOBJECT['CONFIG']['db_driver'])) || 
  437.             ($_DB_DATAOBJECT['CONFIG']['db_driver'== 'DB')) {
  438.             /* PEAR DB specific */
  439.         
  440.             if (isset($this->_query['limit_start']&& strlen($this->_query['limit_start'$this->_query['limit_count'])) {
  441.                 $sql $DB->modifyLimitQuery($sql,$this->_query['limit_start']$this->_query['limit_count']);
  442.             }
  443.         else {
  444.             /* theoretically MDB2! */
  445.             if (isset($this->_query['limit_start']&& strlen($this->_query['limit_start'$this->_query['limit_count'])) {
  446.                 $DB->setLimit($this->_query['limit_count'],$this->_query['limit_start']);
  447.             }
  448.         }
  449.         
  450.         
  451.         $err $this->_query($sql);
  452.         if (is_a($err,'PEAR_Error')) {
  453.             return false;
  454.         }
  455.         
  456.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  457.             $this->debug("CHECK autofetchd $n""find"1);
  458.         }
  459.         
  460.         // find(true)
  461.         
  462.         $ret $this->N;
  463.         if (!$ret && !empty($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid])) {     
  464.             // clear up memory if nothing found!?
  465.             unset($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid]);
  466.         }
  467.         
  468.         if ($n && $this->> 0 {
  469.             if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  470.                 $this->debug("ABOUT TO AUTOFETCH""find"1);
  471.             }
  472.             $fs $this->fetch();
  473.             // if fetch returns false (eg. failed), then the backend doesnt support numRows (eg. ret=true)
  474.             // - hence find() also returns false..
  475.             $ret ($ret === true$fs $ret;
  476.         }
  477.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  478.             $this->debug("DONE""find"1);
  479.         }
  480.         $this->_query $query_before;
  481.         return $ret;
  482.     }
  483.  
  484.     /**
  485.      * fetches next row into this objects var's
  486.      *
  487.      * returns 1 on success 0 on failure
  488.      *
  489.      *
  490.      *
  491.      * Example
  492.      * $object = new mytable();
  493.      * $object->name = "fred";
  494.      * $object->find();
  495.      * $store = array();
  496.      * while ($object->fetch()) {
  497.      *   echo $this->ID;
  498.      *   $store[] = $object; // builds an array of object lines.
  499.      * }
  500.      *
  501.      * to add features to a fetch
  502.      * function fetch () {
  503.      *    $ret = parent::fetch();
  504.      *    $this->date_formated = date('dmY',$this->date);
  505.      *    return $ret;
  506.      * }
  507.      *
  508.      * @access  public
  509.      * @return  boolean on success
  510.      */
  511.     function fetch()
  512.     {
  513.  
  514.         global $_DB_DATAOBJECT;
  515.         if (empty($_DB_DATAOBJECT['CONFIG'])) {
  516.             DB_DataObject::_loadConfig();
  517.         }
  518.         if (empty($this->N)) {
  519.             if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  520.                 $this->debug("No data returned from FIND (eg. N is 0)","FETCH"3);
  521.             }
  522.             return false;
  523.         }
  524.         
  525.         if (empty($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid]|| 
  526.             !is_object($result $_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid])) 
  527.         {
  528.             if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  529.                 $this->debug('fetched on object after fetch completed (no results found)');
  530.             }
  531.             return false;
  532.         }
  533.         
  534.         
  535.         $array $result->fetchRow(DB_DATAOBJECT_FETCHMODE_ASSOC);
  536.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  537.             $this->debug(serialize($array),"FETCH");
  538.         }
  539.         
  540.         // fetched after last row..
  541.         if ($array === null{
  542.             if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  543.                 $texplode(' ',microtime());
  544.             
  545.                 $this->debug("Last Data Fetch'ed after " 
  546.                         ($t[0]+$t[1]$_DB_DATAOBJECT['QUERYENDTIME']  
  547.                         " seconds",
  548.                     "FETCH"1);
  549.             }
  550.             // reduce the memory usage a bit... (but leave the id in, so count() works ok on it)
  551.             unset($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid]);
  552.             
  553.             // we need to keep a copy of resultfields locally so toArray() still works
  554.             // however we dont want to keep it in the global cache..
  555.             
  556.             if (!empty($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid])) {
  557.                 $this->_resultFields $_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid];
  558.                 unset($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid]);
  559.             }
  560.             // this is probably end of data!!
  561.             //DB_DataObject::raiseError("fetch: no data returned", DB_DATAOBJECT_ERROR_NODATA);
  562.             return false;
  563.         }
  564.         // make sure resultFields is always empty..
  565.         $this->_resultFields = false;
  566.         
  567.         if (!isset($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid])) {
  568.             // note: we dont declare this to keep the print_r size down.
  569.             $_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid]array_flip(array_keys($array));
  570.         }
  571.         $replace = array('.'' ');
  572.         foreach($array as $k=>$v{
  573.             // use strpos as str_replace is slow.
  574.             $kk =  (strpos($k'.'=== false && strpos($k' '=== false?
  575.                 $k str_replace($replace'_'$k);
  576.                 
  577.             if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  578.                 $this->debug("$kk = ". $array[$k]"fetchrow LINE"3);
  579.             }
  580.             $this->$kk $array[$k];
  581.         }
  582.         
  583.         // set link flag
  584.         $this->_link_loaded=false;
  585.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  586.             $this->debug("{$this->tableName()} DONE""fetchrow",2);
  587.         }
  588.         if (($this->_query !== false&&  empty($_DB_DATAOBJECT['CONFIG']['keep_query_after_fetch'])) {
  589.             $this->_query = false;
  590.         }
  591.         return true;
  592.     }
  593.  
  594.     
  595.      /**
  596.      * fetches all results as an array,
  597.      *
  598.      * return format is dependant on args.
  599.      * if selectAdd() has not been called on the object, then it will add the correct columns to the query.
  600.      * 
  601.      * A) Array of values (eg. a list of 'id')
  602.      *
  603.      * $x = DB_DataObject::factory('mytable');
  604.      * $x->whereAdd('something = 1')
  605.      * $ar = $x->fetchAll('id');
  606.      * -- returns array(1,2,3,4,5)
  607.      *
  608.      * B) Array of values (not from table)
  609.      *
  610.      * $x = DB_DataObject::factory('mytable');
  611.      * $x->whereAdd('something = 1');
  612.      * $x->selectAdd();
  613.      * $x->selectAdd('distinct(group_id) as group_id');
  614.      * $ar = $x->fetchAll('group_id');
  615.      * -- returns array(1,2,3,4,5)
  616.      *
  617.      * C) A key=>value associative array
  618.      *
  619.      * $x = DB_DataObject::factory('mytable');
  620.      * $x->whereAdd('something = 1')
  621.      * $ar = $x->fetchAll('id','name');
  622.      * -- returns array(1=>'fred',2=>'blogs',3=> .......
  623.      *
  624.      * D) array of objects
  625.      * $x = DB_DataObject::factory('mytable');
  626.      * $x->whereAdd('something = 1');
  627.      * $ar = $x->fetchAll();
  628.      *
  629.      * E) array of arrays (for example)
  630.      * $x = DB_DataObject::factory('mytable');
  631.      * $x->whereAdd('something = 1');
  632.      * $ar = $x->fetchAll(false,false,'toArray');
  633.      *
  634.      *
  635.      * @param    string|false $k key
  636.      * @param    string|false $v value
  637.      * @param    string|false $method method to call on each result to get array value (eg. 'toArray')
  638.      * @access  public
  639.      * @return  array  format dependant on arguments, may be empty
  640.      */
  641.     function fetchAll($k= false$v = false$method = false)  
  642.     {
  643.         // should it even do this!!!?!?
  644.         if ($k !== false && 
  645.                 (   // only do this is we have not been explicit..
  646.                     empty($this->_query['data_select']|| 
  647.                     ($this->_query['data_select'== '*')
  648.                 )
  649.             {
  650.             $this->selectAdd();
  651.             $this->selectAdd($k);
  652.             if ($v !== false{
  653.                 $this->selectAdd($v);
  654.             }
  655.         }
  656.         
  657.         $this->find();
  658.         $ret = array();
  659.         while ($this->fetch()) {
  660.             if ($v !== false{
  661.                 $ret[$this->$k$this->$v;
  662.                 continue;
  663.             }
  664.             $ret[$k === false ? 
  665.                 ($method == false ? clone($this)  $this->$method())
  666.                 : $this->$k;
  667.         }
  668.         return $ret;
  669.          
  670.     }
  671.     
  672.     
  673.     /**
  674.      * Adds a condition to the WHERE statement, defaults to AND
  675.      *
  676.      * $object->whereAdd(); //reset or cleaer ewhwer
  677.      * $object->whereAdd("ID > 20");
  678.      * $object->whereAdd("age > 20","OR");
  679.      *
  680.      * @param    string  $cond  condition
  681.      * @param    string  $logic optional logic "OR" (defaults to "AND")
  682.      * @access   public
  683.      * @return   string|PEAR::Error- previous condition or Error when invalid args found
  684.      */
  685.     function whereAdd($cond = false$logic 'AND')
  686.     {
  687.         // for PHP5.2.3 - there is a bug with setting array properties of an object.
  688.         $_query $this->_query;
  689.          
  690.         if (!isset($this->_query|| ($_query === false)) {
  691.             return $this->raiseError(
  692.                 "You cannot do two queries on the same object (clone it before finding)"
  693.                 DB_DATAOBJECT_ERROR_INVALIDARGS);
  694.         }
  695.         
  696.         if ($cond === false{
  697.             $r $this->_query['condition'];
  698.             $_query['condition''';
  699.             $this->_query $_query;
  700.             return preg_replace('/^\s+WHERE\s+/','',$r);
  701.         }
  702.         // check input...= 0 or '   ' == error!
  703.         if (!trim($cond)) {
  704.             return $this->raiseError("WhereAdd: No Valid Arguments"DB_DATAOBJECT_ERROR_INVALIDARGS);
  705.         }
  706.         $r $_query['condition'];
  707.         if ($_query['condition']{
  708.             $_query['condition'.= " {$logic} ( {$cond} )";
  709.             $this->_query $_query;
  710.             return $r;
  711.         }
  712.         $_query['condition'= " WHERE ( {$cond} ) ";
  713.         $this->_query $_query;
  714.         return $r;
  715.     }
  716.  
  717.     /**
  718.     * Adds a 'IN' condition to the WHERE statement
  719.     *
  720.     * $object->whereAddIn('id', $array, 'int'); //minimal usage
  721.     * $object->whereAddIn('price', $array, 'float', 'OR');  // cast to float, and call whereAdd with 'OR'
  722.     * $object->whereAddIn('name', $array, 'string');  // quote strings
  723.     *
  724.     * @param    string  $key  key column to match
  725.     * @param    array  $list  list of values to match
  726.     * @param    string  $type  string|int|integer|float|bool  cast to type.
  727.     * @param    string  $logic optional logic to call whereAdd with eg. "OR" (defaults to "AND")
  728.     * @access   public
  729.     * @return   string|PEAR::Error- previous condition or Error when invalid args found
  730.     */
  731.     function whereAddIn($key$list$type$logic 'AND'
  732.     {
  733.         $not '';
  734.         if ($key[0== '!'{
  735.             $not 'NOT ';
  736.             $key substr($key1);
  737.         }
  738.         // fix type for short entry. 
  739.         $type $type == 'int' 'integer' $type
  740.  
  741.         if ($type == 'string'{
  742.             $this->_connect();
  743.         }
  744.  
  745.         $ar = array();
  746.         foreach($list as $k{
  747.             settype($k$type);
  748.             $ar[$type == 'string' $this->_quote($k$k;
  749.         }
  750.       
  751.         if (!$ar{
  752.             return $not $this->_query['condition'$this->whereAdd("1=0");
  753.         }
  754.         return $this->whereAdd("$key $not IN (". implode(','$ar)')'$logic );    
  755.     }
  756.  
  757.     
  758.     
  759.     /**
  760.      * Adds a order by condition
  761.      *
  762.      * $object->orderBy(); //clears order by
  763.      * $object->orderBy("ID");
  764.      * $object->orderBy("ID,age");
  765.      *
  766.      * @param  string $order  Order
  767.      * @access public
  768.      * @return none|PEAR::Error- invalid args only
  769.      */
  770.     function orderBy($order = false)
  771.     {
  772.         if ($this->_query === false{
  773.             $this->raiseError(
  774.                 "You cannot do two queries on the same object (copy it before finding)"
  775.                 DB_DATAOBJECT_ERROR_INVALIDARGS);
  776.             return false;
  777.         }
  778.         if ($order === false{
  779.             $this->_query['order_by''';
  780.             return;
  781.         }
  782.         // check input...= 0 or '    ' == error!
  783.         if (!trim($order)) {
  784.             return $this->raiseError("orderBy: No Valid Arguments"DB_DATAOBJECT_ERROR_INVALIDARGS);
  785.         }
  786.         
  787.         if (!$this->_query['order_by']{
  788.             $this->_query['order_by'= " ORDER BY {$order} ";
  789.             return;
  790.         }
  791.         $this->_query['order_by'.= " , {$order}";
  792.     }
  793.  
  794.     /**
  795.      * Adds a group by condition
  796.      *
  797.      * $object->groupBy(); //reset the grouping
  798.      * $object->groupBy("ID DESC");
  799.      * $object->groupBy("ID,age");
  800.      *
  801.      * @param  string  $group  Grouping
  802.      * @access public
  803.      * @return none|PEAR::Error- invalid args only
  804.      */
  805.     function groupBy($group = false)
  806.     {
  807.         if ($this->_query === false{
  808.             $this->raiseError(
  809.                 "You cannot do two queries on the same object (copy it before finding)"
  810.                 DB_DATAOBJECT_ERROR_INVALIDARGS);
  811.             return false;
  812.         }
  813.         if ($group === false{
  814.             $this->_query['group_by''';
  815.             return;
  816.         }
  817.         // check input...= 0 or '    ' == error!
  818.         if (!trim($group)) {
  819.             return $this->raiseError("groupBy: No Valid Arguments"DB_DATAOBJECT_ERROR_INVALIDARGS);
  820.         }
  821.         
  822.         
  823.         if (!$this->_query['group_by']{
  824.             $this->_query['group_by'= " GROUP BY {$group} ";
  825.             return;
  826.         }
  827.         $this->_query['group_by'.= " , {$group}";
  828.     }
  829.  
  830.     /**
  831.      * Adds a having clause
  832.      *
  833.      * $object->having(); //reset the grouping
  834.      * $object->having("sum(value) > 0 ");
  835.      *
  836.      * @param  string  $having  condition
  837.      * @access public
  838.      * @return none|PEAR::Error- invalid args only
  839.      */
  840.     function having($having = false)
  841.     {
  842.         if ($this->_query === false{
  843.             $this->raiseError(
  844.                 "You cannot do two queries on the same object (copy it before finding)"
  845.                 DB_DATAOBJECT_ERROR_INVALIDARGS);
  846.             return false;
  847.         }
  848.         if ($having === false{
  849.             $this->_query['having''';
  850.             return;
  851.         }
  852.         // check input...= 0 or '    ' == error!
  853.         if (!trim($having)) {
  854.             return $this->raiseError("Having: No Valid Arguments"DB_DATAOBJECT_ERROR_INVALIDARGS);
  855.         }
  856.         
  857.         
  858.         if (!$this->_query['having']{
  859.             $this->_query['having'= " HAVING {$having} ";
  860.             return;
  861.         }
  862.         $this->_query['having'.= " AND {$having}";
  863.     }
  864.  
  865.     /**
  866.      * Sets the Limit
  867.      *
  868.      * $boject->limit(); // clear limit
  869.      * $object->limit(12);
  870.      * $object->limit(12,10);
  871.      *
  872.      * Note this will emit an error on databases other than mysql/postgress
  873.      * as there is no 'clean way' to implement it. - you should consider refering to
  874.      * your database manual to decide how you want to implement it.
  875.      *
  876.      * @param  string $a  limit start (or number), or blank to reset
  877.      * @param  string $b  number
  878.      * @access public
  879.      * @return none|PEAR::Error- invalid args only
  880.      */
  881.     function limit($a = null$b = null)
  882.     {
  883.         if ($this->_query === false{
  884.             $this->raiseError(
  885.                 "You cannot do two queries on the same object (copy it before finding)"
  886.                 DB_DATAOBJECT_ERROR_INVALIDARGS);
  887.             return false;
  888.         }
  889.         
  890.         if ($a === null{
  891.            $this->_query['limit_start''';
  892.            $this->_query['limit_count''';
  893.            return;
  894.         }
  895.         // check input...= 0 or '    ' == error!
  896.         if ((!is_int($a&& ((string)((int)$a!== (string)$a)) 
  897.             || (($b !== null&& (!is_int($b&& ((string)((int)$b!== (string)$b)))) {
  898.             return $this->raiseError("limit: No Valid Arguments"DB_DATAOBJECT_ERROR_INVALIDARGS);
  899.         }
  900.         global $_DB_DATAOBJECT;
  901.         $this->_connect();
  902.         $DB $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  903.         
  904.         $this->_query['limit_start'($b == null? 0 : (int)$a;
  905.         $this->_query['limit_count'($b == null? (int)$a : (int)$b;
  906.         
  907.     }
  908.  
  909.     /**
  910.      * Adds a select columns
  911.      *
  912.      * $object->selectAdd(); // resets select to nothing!
  913.      * $object->selectAdd("*"); // default select
  914.      * $object->selectAdd("unixtime(DATE) as udate");
  915.      * $object->selectAdd("DATE");
  916.      *
  917.      * to prepend distict:
  918.      * $object->selectAdd('distinct ' . $object->selectAdd());
  919.      *
  920.      * @param  string  $k 
  921.      * @access public
  922.      * @return mixed null or old string if you reset it.
  923.      */
  924.     function selectAdd($k = null)
  925.     {
  926.         if ($this->_query === false{
  927.             $this->raiseError(
  928.                 "You cannot do two queries on the same object (copy it before finding)"
  929.                 DB_DATAOBJECT_ERROR_INVALIDARGS);
  930.             return false;
  931.         }
  932.         if ($k === null{
  933.             $old $this->_query['data_select'];
  934.             $this->_query['data_select''';
  935.             return $old;
  936.         }
  937.         
  938.         // check input...= 0 or '    ' == error!
  939.         if (!trim($k)) {
  940.             return $this->raiseError("selectAdd: No Valid Arguments"DB_DATAOBJECT_ERROR_INVALIDARGS);
  941.         }
  942.         
  943.         if ($this->_query['data_select']{
  944.             $this->_query['data_select'.= ', ';
  945.         }
  946.         $this->_query['data_select'.= " $k ";
  947.     }
  948.     /**
  949.      * Adds multiple Columns or objects to select with formating.
  950.      *
  951.      * $object->selectAs(null); // adds "table.colnameA as colnameA,table.colnameB as colnameB,......"
  952.      *                      // note with null it will also clear the '*' default select
  953.      * $object->selectAs(array('a','b'),'%s_x'); // adds "a as a_x, b as b_x"
  954.      * $object->selectAs(array('a','b'),'ddd_%s','ccc'); // adds "ccc.a as ddd_a, ccc.b as ddd_b"
  955.      * $object->selectAdd($object,'prefix_%s'); // calls $object->get_table and adds it all as
  956.      *                  objectTableName.colnameA as prefix_colnameA
  957.      *
  958.      * @param  array|object|nullthe array or object to take column names from.
  959.      * @param  string           format in sprintf format (use %s for the colname)
  960.      * @param  string           table name eg. if you have joinAdd'd or send $from as an array.
  961.      * @access public
  962.      * @return void 
  963.      */
  964.     function selectAs($from = null,$format '%s',$tableName=false)
  965.     {
  966.         global $_DB_DATAOBJECT;
  967.         
  968.         if ($this->_query === false{
  969.             $this->raiseError(
  970.                 "You cannot do two queries on the same object (copy it before finding)"
  971.                 DB_DATAOBJECT_ERROR_INVALIDARGS);
  972.             return false;
  973.         }
  974.         
  975.         if ($from === null{
  976.             // blank the '*' 
  977.             $this->selectAdd();
  978.             $from $this;
  979.         }
  980.         
  981.         
  982.         $table $this->tableName();
  983.         if (is_object($from)) {
  984.             $table $from->tableName();
  985.             $from array_keys($from->table());
  986.         }
  987.         
  988.         if ($tableName !== false{
  989.             $table $tableName;
  990.         }
  991.         $s '%s';
  992.         if (!empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers'])) {
  993.             $this->_connect();
  994.             $DB $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  995.             $s      $DB->quoteIdentifier($s);
  996.             $format $DB->quoteIdentifier($format)
  997.         }
  998.         foreach ($from as $k{
  999.             $this->selectAdd(sprintf("{$s}.{$s} as {$format}",$table,$k,$k));
  1000.         }
  1001.         $this->_query['data_select'.= "\n";
  1002.     }
  1003.     /**
  1004.      * Insert the current objects variables into the database
  1005.      *
  1006.      * Returns the ID of the inserted element (if auto increment or sequences are used.)
  1007.      *
  1008.      * for example
  1009.      *
  1010.      * Designed to be extended
  1011.      *
  1012.      * $object = new mytable();
  1013.      * $object->name = "fred";
  1014.      * echo $object->insert();
  1015.      *
  1016.      * @access public
  1017.      * @return mixed false on failure, int when auto increment or sequence used, otherwise true on success
  1018.      */
  1019.     function insert()
  1020.     {
  1021.         global $_DB_DATAOBJECT;
  1022.         
  1023.         // we need to write to the connection (For nextid) - so us the real
  1024.         // one not, a copyied on (as ret-by-ref fails with overload!)
  1025.         
  1026.         if (!isset($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
  1027.             $this->_connect();
  1028.         }
  1029.         
  1030.         $quoteIdentifiers  !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
  1031.         
  1032.         $DB $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  1033.          
  1034.         $items $this->table();
  1035.             
  1036.         if (!$items{
  1037.             $this->raiseError("insert:No table definition for {$this->tableName()}",
  1038.                 DB_DATAOBJECT_ERROR_INVALIDCONFIG);
  1039.             return false;
  1040.         }
  1041.         $options $_DB_DATAOBJECT['CONFIG'];
  1042.  
  1043.  
  1044.         $datasaved = 1;
  1045.         $leftq     '';
  1046.         $rightq    '';
  1047.      
  1048.         $seqKeys   = isset($_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()]?
  1049.                         $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()
  1050.                         $this->sequenceKey();
  1051.         
  1052.         $key       = isset($seqKeys[0]$seqKeys[0: false;
  1053.         $useNative = isset($seqKeys[1]$seqKeys[1: false;
  1054.         $seq       = isset($seqKeys[2]$seqKeys[2: false;
  1055.         
  1056.         $dbtype    $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn["phptype"];
  1057.         
  1058.          
  1059.         // nativeSequences or Sequences..     
  1060.  
  1061.         // big check for using sequences
  1062.         
  1063.         if (($key !== false&& !$useNative
  1064.         
  1065.             if (!$seq{
  1066.                 $keyvalue =  $DB->nextId($this->tableName());
  1067.             else {
  1068.                 $f $DB->getOption('seqname_format');
  1069.                 $DB->setOption('seqname_format','%s');
  1070.                 $keyvalue =  $DB->nextId($seq);
  1071.                 $DB->setOption('seqname_format',$f);
  1072.             }
  1073.             if (PEAR::isError($keyvalue)) {
  1074.                 $this->raiseError($keyvalue->toString()DB_DATAOBJECT_ERROR_INVALIDCONFIG);
  1075.                 return false;
  1076.             }
  1077.             $this->$key $keyvalue;
  1078.         }
  1079.         
  1080.         // if we haven't set disable_null_strings to "full"
  1081.         $ignore_null !isset($options['disable_null_strings'])
  1082.                     || !is_string($options['disable_null_strings'])
  1083.                     || strtolower($options['disable_null_strings']!== 'full' ;
  1084.                     
  1085.              
  1086.         foreach($items as $k => $v{
  1087.             
  1088.             // if we are using autoincrement - skip the column...
  1089.             if ($key && ($k == $key&& $useNative{
  1090.                 continue;
  1091.             }
  1092.         
  1093.             
  1094.            
  1095.            
  1096.             // Ignore variables which aren't set to a value
  1097.             if !isset($this->$k&& $ignore_null{
  1098.                 continue;
  1099.             }
  1100.             // dont insert data into mysql timestamps 
  1101.             // use query() if you really want to do this!!!!
  1102.             if ($v DB_DATAOBJECT_MYSQLTIMESTAMP{
  1103.                 continue;
  1104.             }
  1105.             
  1106.             if ($leftq{
  1107.                 $leftq  .= ', ';
  1108.                 $rightq .= ', ';
  1109.             }
  1110.             
  1111.             $leftq .= ($quoteIdentifiers ($DB->quoteIdentifier($k' ')  : "$k ");
  1112.             
  1113.             if (is_object($this->$k&& is_a($this->$k,'DB_DataObject_Cast')) {
  1114.                 $value $this->$k->toString($v,$DB);
  1115.                 if (PEAR::isError($value)) {
  1116.                     $this->raiseError($value->toString(,DB_DATAOBJECT_ERROR_INVALIDARGS);
  1117.                     return false;
  1118.                 }
  1119.                 $rightq .=  $value;
  1120.                 continue;
  1121.             }
  1122.             
  1123.             
  1124.             if (!($v DB_DATAOBJECT_NOTNULL&& DB_DataObject::_is_null($this,$k)) {
  1125.                 $rightq .= " NULL ";
  1126.                 continue;
  1127.             }
  1128.             // DATE is empty... on a col. that can be null.. 
  1129.             // note: this may be usefull for time as well..
  1130.             if (!$this->$k && 
  1131.                     (($v DB_DATAOBJECT_DATE|| ($v DB_DATAOBJECT_TIME)) && 
  1132.                     !($v DB_DATAOBJECT_NOTNULL)) {
  1133.                     
  1134.                 $rightq .= " NULL ";
  1135.                 continue;
  1136.             }
  1137.               
  1138.             
  1139.             if ($v DB_DATAOBJECT_STR{
  1140.                 $rightq .= $this->_quote((string) (
  1141.                         ($v DB_DATAOBJECT_BOOL
  1142.                             // this is thanks to the braindead idea of postgres to 
  1143.                             // use t/f for boolean.
  1144.                             (($this->$k === 'f'? 0 : (int)(bool) $this->$k:  
  1145.                             $this->$k
  1146.                     )) " ";
  1147.                 continue;
  1148.             }
  1149.             if (is_numeric($this->$k)) {
  1150.                 $rightq .=" {$this->$k} ";
  1151.                 continue;
  1152.             }
  1153.             /* flag up string values - only at debug level... !!!??? */
  1154.             if (is_object($this->$k|| is_array($this->$k)) {
  1155.                 $this->debug('ODD DATA: ' .$k ' ' .  print_r($this->$k,true),'ERROR');
  1156.             }
  1157.             
  1158.             // at present we only cast to integers
  1159.             // - V2 may store additional data about float/int
  1160.             $rightq .= ' ' intval($this->$k' ';
  1161.  
  1162.         }
  1163.         
  1164.         // not sure why we let empty insert here.. - I guess to generate a blank row..
  1165.         
  1166.         
  1167.         if ($leftq || $useNative{
  1168.             $table ($quoteIdentifiers $DB->quoteIdentifier($this->tableName())    $this->tableName());
  1169.             
  1170.             
  1171.             if (($dbtype == 'pgsql'&& empty($leftq)) {
  1172.                 $r $this->_query("INSERT INTO {$table} DEFAULT VALUES");
  1173.             else {
  1174.                $r $this->_query("INSERT INTO {$table} ($leftq) VALUES ($rightq");
  1175.             }
  1176.             
  1177.  
  1178.             
  1179.             
  1180.             if (PEAR::isError($r)) {
  1181.                 $this->raiseError($r);
  1182.                 return false;
  1183.             }
  1184.             
  1185.             if ($r < 1{
  1186.                 return 0;
  1187.             }
  1188.             
  1189.             
  1190.             // now do we have an integer key!
  1191.             
  1192.             if ($key && $useNative{
  1193.                 switch ($dbtype{
  1194.                     case 'mysql':
  1195.                     case 'mysqli':
  1196.                         $method = "{$dbtype}_insert_id";
  1197.                         $this->$key $method(
  1198.                             $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->connection
  1199.                         );
  1200.                         break;
  1201.                     
  1202.                     case 'mssql':
  1203.                         // note this is not really thread safe - you should wrapp it with 
  1204.                         // transactions = eg.
  1205.                         // $db->query('BEGIN');
  1206.                         // $db->insert();
  1207.                         // $db->query('COMMIT');
  1208.                         $db_driver = empty($options['db_driver']'DB' $options['db_driver'];
  1209.                         $method ($db_driver  == 'DB''getOne' 'queryOne';
  1210.                         $mssql_key $DB->$method("SELECT @@IDENTITY");
  1211.                         if (PEAR::isError($mssql_key)) {
  1212.                             $this->raiseError($mssql_key);
  1213.                             return false;
  1214.                         }
  1215.                         $this->$key $mssql_key;
  1216.                         break; 
  1217.                         
  1218.                     case 'pgsql':
  1219.                         if (!$seq{
  1220.                             $seq $DB->getSequenceName(strtolower($this->tableName()));
  1221.                         }
  1222.                         $db_driver = empty($options['db_driver']'DB' $options['db_driver'];
  1223.                         $method ($db_driver  == 'DB''getOne' 'queryOne';
  1224.                         $pgsql_key $DB->$method("SELECT currval('".$seq "')")
  1225.  
  1226.  
  1227.                         if (PEAR::isError($pgsql_key)) {
  1228.                             $this->raiseError($pgsql_key);
  1229.                             return false;
  1230.                         }
  1231.                         $this->$key $pgsql_key;
  1232.                         break;
  1233.                     
  1234.                     case 'ifx':
  1235.                         $this->$key array_shift (
  1236.                             ifx_fetch_row (
  1237.                                 ifx_query(
  1238.                                     "select DBINFO('sqlca.sqlerrd1') FROM systables where tabid=1",
  1239.                                     $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->connection,
  1240.                                     IFX_SCROLL
  1241.                                 )
  1242.                                 "FIRST"
  1243.                             )
  1244.                         )
  1245.                         break;
  1246.                     
  1247.                 }
  1248.                         
  1249.             }
  1250.  
  1251.             if (isset($_DB_DATAOBJECT['CACHE'][strtolower(get_class($this))])) {
  1252.                 $this->_clear_cache();
  1253.             }
  1254.             if ($key{
  1255.                 return $this->$key;
  1256.             }
  1257.             return true;
  1258.         }
  1259.         $this->raiseError("insert: No Data specifed for query"DB_DATAOBJECT_ERROR_NODATA);
  1260.         return false;
  1261.     }
  1262.  
  1263.     /**
  1264.      * Updates  current objects variables into the database
  1265.      * uses the keys() to decide how to update
  1266.      * Returns the  true on success
  1267.      *
  1268.      * for example
  1269.      *
  1270.      * $object = DB_DataObject::factory('mytable');
  1271.      * $object->get("ID",234);
  1272.      * $object->email="testing@test.com";
  1273.      * if(!$object->update())
  1274.      *   echo "UPDATE FAILED";
  1275.      *
  1276.      * to only update changed items :
  1277.      * $dataobject->get(132);
  1278.      * $original = $dataobject; // clone/copy it..
  1279.      * $dataobject->setFrom($_POST);
  1280.      * if ($dataobject->validate()) {
  1281.      *    $dataobject->update($original);
  1282.      * } // otherwise an error...
  1283.      *
  1284.      * performing global updates:
  1285.      * $object = DB_DataObject::factory('mytable');
  1286.      * $object->status = "dead";
  1287.      * $object->whereAdd('age > 150');
  1288.      * $object->update(DB_DATAOBJECT_WHEREADD_ONLY);
  1289.      *
  1290.      * @param object dataobject (optional) | DB_DATAOBJECT_WHEREADD_ONLY - used to only update changed items.
  1291.      * @access public
  1292.      * @return  int rows affected or false on failure
  1293.      */
  1294.     function update($dataObject = false)
  1295.     {
  1296.         global $_DB_DATAOBJECT;
  1297.         // connect will load the config!
  1298.         $this->_connect();
  1299.         
  1300.         
  1301.         $original_query =  $this->_query;
  1302.         
  1303.         $items $this->table();
  1304.         
  1305.         // only apply update against sequence key if it is set?????
  1306.         
  1307.         $seq    $this->sequenceKey();
  1308.         if ($seq[0!== false{
  1309.             $keys = array($seq[0]);
  1310.             if (!isset($this->{$keys[0]}&& $dataObject !== true{
  1311.                 $this->raiseError("update: trying to perform an update without 
  1312.                         the key set, and argument to update is not 
  1313.                         DB_DATAOBJECT_WHEREADD_ONLY
  1314.                     "DB_DATAOBJECT_ERROR_INVALIDARGS);
  1315.                 return false;  
  1316.             }
  1317.         else {
  1318.             $keys $this->keys();
  1319.         }
  1320.         
  1321.          
  1322.         if (!$items{
  1323.             $this->raiseError("update:No table definition for {$this->tableName()}"DB_DATAOBJECT_ERROR_INVALIDCONFIG);
  1324.             return false;
  1325.         }
  1326.         $datasaved = 1;
  1327.         $settings  '';
  1328.         $this->_connect();
  1329.         
  1330.         $DB            $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  1331.         $dbtype        $DB->dsn["phptype"];
  1332.         $quoteIdentifiers !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
  1333.         $options $_DB_DATAOBJECT['CONFIG'];
  1334.         
  1335.         
  1336.         $ignore_null !isset($options['disable_null_strings'])
  1337.                     || !is_string($options['disable_null_strings'])
  1338.                     || strtolower($options['disable_null_strings']!== 'full' ;
  1339.                     
  1340.         
  1341.         foreach($items as $k => $v{
  1342.             
  1343.             if (!isset($this->$k&& $ignore_null{
  1344.                 continue;
  1345.             }
  1346.             // ignore stuff thats 
  1347.           
  1348.             // dont write things that havent changed..
  1349.             if (($dataObject !== false&& isset($dataObject->$k&& ($dataObject->$k === $this->$k)) {
  1350.                 continue;
  1351.             }
  1352.             
  1353.             // - dont write keys to left.!!!
  1354.             if (in_array($k,$keys)) {
  1355.                 continue;
  1356.             }
  1357.             
  1358.              // dont insert data into mysql timestamps 
  1359.             // use query() if you really want to do this!!!!
  1360.             if ($v DB_DATAOBJECT_MYSQLTIMESTAMP{
  1361.                 continue;
  1362.             }
  1363.             
  1364.             
  1365.             if ($settings)  {
  1366.                 $settings .= ', ';
  1367.             }
  1368.             
  1369.             $kSql ($quoteIdentifiers $DB->quoteIdentifier($k$k);
  1370.             
  1371.             if (is_object($this->$k&& is_a($this->$k,'DB_DataObject_Cast')) {
  1372.                 $value $this->$k->toString($v,$DB);
  1373.                 if (PEAR::isError($value)) {
  1374.                     $this->raiseError($value->getMessage(,DB_DATAOBJECT_ERROR_INVALIDARG);
  1375.                     return false;
  1376.                 }
  1377.                 $settings .= "$kSql = $value ";
  1378.                 continue;
  1379.             }
  1380.             
  1381.             // special values ... at least null is handled...
  1382.             if (!($v DB_DATAOBJECT_NOTNULL&& DB_DataObject::_is_null($this,$k)) {
  1383.                 $settings .= "$kSql = NULL ";
  1384.                 continue;
  1385.             }
  1386.             // DATE is empty... on a col. that can be null.. 
  1387.             // note: this may be usefull for time as well..
  1388.             if (!$this->$k && 
  1389.                     (($v DB_DATAOBJECT_DATE|| ($v DB_DATAOBJECT_TIME)) && 
  1390.                     !($v DB_DATAOBJECT_NOTNULL)) {
  1391.                     
  1392.                 $settings .= "$kSql = NULL ";
  1393.                 continue;
  1394.             }
  1395.             
  1396.  
  1397.             if ($v DB_DATAOBJECT_STR{
  1398.                 $settings .= "$kSql = ". $this->_quote((string) (
  1399.                         ($v DB_DATAOBJECT_BOOL
  1400.                             // this is thanks to the braindead idea of postgres to 
  1401.                             // use t/f for boolean.
  1402.                             (($this->$k === 'f'? 0 : (int)(bool) $this->$k:  
  1403.                             $this->$k
  1404.                     )) ' ';
  1405.                 continue;
  1406.             }
  1407.             if (is_numeric($this->$k)) {
  1408.                 $settings .= "$kSql = {$this->$k} ";
  1409.                 continue;
  1410.             }
  1411.             // at present we only cast to integers
  1412.             // - V2 may store additional data about float/int
  1413.             $settings .= "$kSql = " . intval($this->$k' ';
  1414.         }
  1415.  
  1416.         
  1417.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  1418.             $this->debug("got keys as ".serialize($keys),3);
  1419.         }
  1420.         if ($dataObject !== true{
  1421.             $this->_build_condition($items,$keys);
  1422.         else {
  1423.             // prevent wiping out of data!
  1424.             if (empty($this->_query['condition'])) {
  1425.                  $this->raiseError("update: global table update not available
  1426.                         do \$do->whereAdd('1=1'); if you really want to do that.
  1427.                     "DB_DATAOBJECT_ERROR_INVALIDARGS);
  1428.                 return false;
  1429.             }
  1430.         }
  1431.         
  1432.         
  1433.         
  1434.         //  echo " $settings, $this->condition ";
  1435.         if ($settings && isset($this->_query&& $this->_query['condition']{
  1436.             
  1437.             $table ($quoteIdentifiers $DB->quoteIdentifier($this->tableName()) $this->tableName());
  1438.         
  1439.             $r $this->_query("UPDATE  {$table}  SET {$settings} {$this->_query['condition']} ");
  1440.             
  1441.             // restore original query conditions.
  1442.             $this->_query = $original_query;
  1443.             
  1444.             if (PEAR::isError($r)) {
  1445.                 $this->raiseError($r);
  1446.                 return false;
  1447.             }
  1448.             if ($r < 1{
  1449.                 return 0;
  1450.             }
  1451.  
  1452.             $this->_clear_cache();
  1453.             return $r;
  1454.         }
  1455.         // restore original query conditions.
  1456.         $this->_query = $original_query;
  1457.         
  1458.         // if you manually specified a dataobject, and there where no changes - then it's ok..
  1459.         if ($dataObject !== false{
  1460.             return true;
  1461.         }
  1462.         
  1463.         $this->raiseError(
  1464.             "update: No Data specifed for query $settings , {$this->_query['condition']}"
  1465.             DB_DATAOBJECT_ERROR_NODATA);
  1466.         return false;
  1467.     }
  1468.  
  1469.     /**
  1470.      * Deletes items from table which match current objects variables
  1471.      *
  1472.      * Returns the true on success
  1473.      *
  1474.      * for example
  1475.      *
  1476.      * Designed to be extended
  1477.      *
  1478.      * $object = new mytable();
  1479.      * $object->ID=123;
  1480.      * echo $object->delete(); // builds a conditon
  1481.      *
  1482.      * $object = new mytable();
  1483.      * $object->whereAdd('age > 12');
  1484.      * $object->limit(1);
  1485.      * $object->orderBy('age DESC');
  1486.      * $object->delete(true); // dont use object vars, use the conditions, limit and order.
  1487.      *
  1488.      * @param bool $useWhere (optional) If DB_DATAOBJECT_WHEREADD_ONLY is passed in then
  1489.      *              we will build the condition only using the whereAdd's.  Default is to
  1490.      *              build the condition only using the object parameters.
  1491.      *
  1492.      * @access public
  1493.      * @return mixed Int (No. of rows affected) on success, false on failure, 0 on no data affected
  1494.      */
  1495.     function delete($useWhere = false)
  1496.     {
  1497.         global $_DB_DATAOBJECT;
  1498.         // connect will load the config!
  1499.         $this->_connect();
  1500.         $DB $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  1501.         $quoteIdentifiers  !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
  1502.         
  1503.         $extra_cond ' ' (isset($this->_query['order_by']$this->_query['order_by''')
  1504.         
  1505.         if (!$useWhere{
  1506.  
  1507.             $keys $this->keys();
  1508.             $this->_query = array()// as it's probably unset!
  1509.             $this->_query['condition'''// default behaviour not to use where condition
  1510.             $this->_build_condition($this->table(),$keys);
  1511.             // if primary keys are not set then use data from rest of object.
  1512.             if (!$this->_query['condition']{
  1513.                 $this->_build_condition($this->table(),array(),$keys);
  1514.             }
  1515.             $extra_cond '';
  1516.         
  1517.             
  1518.  
  1519.         // don't delete without a condition
  1520.         if (($this->_query !== false&& $this->_query['condition']{
  1521.         
  1522.             $table ($quoteIdentifiers $DB->quoteIdentifier($this->tableName()) $this->tableName());
  1523.             $sql "DELETE ";
  1524.             // using a joined delete. - with useWhere..
  1525.             $sql .= (!empty($this->_join&& $useWhere
  1526.                 "{$table} FROM {$table} {$this->_join} " : 
  1527.                 "FROM {$table} ";
  1528.                 
  1529.             $sql .= $this->_query['condition']$extra_cond;
  1530.             
  1531.             // add limit..
  1532.             
  1533.             if (isset($this->_query['limit_start']&& strlen($this->_query['limit_start'$this->_query['limit_count'])) {
  1534.                 
  1535.                 if (!isset($_DB_DATAOBJECT['CONFIG']['db_driver']||  
  1536.                     ($_DB_DATAOBJECT['CONFIG']['db_driver'== 'DB')) {
  1537.                     // pear DB 
  1538.                     $sql $DB->modifyLimitQuery($sql,$this->_query['limit_start']$this->_query['limit_count']);
  1539.                     
  1540.                 else {
  1541.                     // MDB2
  1542.                     $DB->setLimit$this->_query['limit_count'],$this->_query['limit_start']);
  1543.                 }
  1544.                     
  1545.             }
  1546.             
  1547.             
  1548.             $r $this->_query($sql);
  1549.             
  1550.             
  1551.             if (PEAR::isError($r)) {
  1552.                 $this->raiseError($r);
  1553.                 return false;
  1554.             }
  1555.             if ($r < 1{
  1556.                 return 0;
  1557.             }
  1558.             $this->_clear_cache();
  1559.             return $r;
  1560.         else {
  1561.             $this->raiseError("delete: No condition specifed for query"DB_DATAOBJECT_ERROR_NODATA);
  1562.             return false;
  1563.         }
  1564.     }
  1565.  
  1566.     /**
  1567.      * fetches a specific row into this object variables
  1568.      *
  1569.      * Not recommended - better to use fetch()
  1570.      *
  1571.      * Returens true on success
  1572.      *
  1573.      * @param  int   $row  row
  1574.      * @access public
  1575.      * @return boolean true on success
  1576.      */
  1577.     function fetchRow($row = null)
  1578.     {
  1579.         global $_DB_DATAOBJECT;
  1580.         if (empty($_DB_DATAOBJECT['CONFIG'])) {
  1581.             $this->_loadConfig();
  1582.         }
  1583.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  1584.             $this->debug("{$this->tableName()} $row of {$this->N}""fetchrow",3);
  1585.         }
  1586.         if (!$this->tableName()) {
  1587.             $this->raiseError("fetchrow: No table"DB_DATAOBJECT_ERROR_INVALIDCONFIG);
  1588.             return false;
  1589.         }
  1590.         if ($row === null{
  1591.             $this->raiseError("fetchrow: No row specified"DB_DATAOBJECT_ERROR_INVALIDARGS);
  1592.             return false;
  1593.         }
  1594.         if (!$this->N{
  1595.             $this->raiseError("fetchrow: No results avaiable"DB_DATAOBJECT_ERROR_NODATA);
  1596.             return false;
  1597.         }
  1598.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  1599.             $this->debug("{$this->tableName()} $row of {$this->N}""fetchrow",3);
  1600.         }
  1601.  
  1602.  
  1603.         $result $_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid];
  1604.         $array  $result->fetchrow(DB_DATAOBJECT_FETCHMODE_ASSOC,$row);
  1605.         if (!is_array($array)) {
  1606.             $this->raiseError("fetchrow: No results available"DB_DATAOBJECT_ERROR_NODATA);
  1607.             return false;
  1608.         }
  1609.         $replace = array('.'' ');
  1610.         foreach($array as $k => $v{
  1611.             // use strpos as str_replace is slow.
  1612.             $kk =  (strpos($k'.'=== false && strpos($k' '=== false?
  1613.                 $k str_replace($replace'_'$k);
  1614.             
  1615.             if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  1616.                 $this->debug("$kk = ". $array[$k]"fetchrow LINE"3);
  1617.             }
  1618.             $this->$kk $array[$k];
  1619.         }
  1620.  
  1621.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  1622.             $this->debug("{$this->tableName()} DONE""fetchrow"3);
  1623.         }
  1624.         return true;
  1625.     }
  1626.  
  1627.     /**
  1628.      * Find the number of results from a simple query
  1629.      *
  1630.      * for example
  1631.      *
  1632.      * $object = new mytable();
  1633.      * $object->name = "fred";
  1634.      * echo $object->count();
  1635.      * echo $object->count(true);  // dont use object vars.
  1636.      * echo $object->count('distinct mycol');   count distinct mycol.
  1637.      * echo $object->count('distinct mycol',true); // dont use object vars.
  1638.      * echo $object->count('distinct');      // count distinct id (eg. the primary key)
  1639.      *
  1640.      *
  1641.      * @param bool|string (optional)
  1642.      *                   (true|false => see below not on whereAddonly)
  1643.      *                   (string)
  1644.      *                       "DISTINCT" => does a distinct count on the tables 'key' column
  1645.      *                       otherwise  => normally it counts primary keys - you can use
  1646.      *                                     this to do things like $do->count('distinct mycol');
  1647.      *                  
  1648.      * @param bool      $whereAddOnly (optional) If DB_DATAOBJECT_WHEREADD_ONLY is passed in then
  1649.      *                   we will build the condition only using the whereAdd's.  Default is to
  1650.      *                   build the condition using the object parameters as well.
  1651.      *                  
  1652.      * @access public
  1653.      * @return int 
  1654.      */
  1655.     function count($countWhat = false,$whereAddOnly = false)
  1656.     {
  1657.         global $_DB_DATAOBJECT;
  1658.         
  1659.         if (is_bool($countWhat)) {
  1660.             $whereAddOnly $countWhat;
  1661.         }
  1662.         
  1663.         $t = clone($this);
  1664.         $items   $t->table();
  1665.         
  1666.         $quoteIdentifiers !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
  1667.         
  1668.         
  1669.         if (!isset($t->_query)) {
  1670.             $this->raiseError(
  1671.                 "You cannot do run count after you have run fetch()"
  1672.                 DB_DATAOBJECT_ERROR_INVALIDARGS);
  1673.             return false;
  1674.         }
  1675.         $this->_connect();
  1676.         $DB $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  1677.        
  1678.  
  1679.         if (!$whereAddOnly && $items)  {
  1680.             $t->_build_condition($items);
  1681.         }
  1682.         $keys $this->keys();
  1683.  
  1684.         if (empty($keys[0]&& (!is_string($countWhat|| (strtoupper($countWhat== 'DISTINCT'))) {
  1685.             $this->raiseError(
  1686.                 "You cannot do run count without keys - use \$do->count('id'), or use \$do->count('distinct id')';"
  1687.                 DB_DATAOBJECT_ERROR_INVALIDARGS,PEAR_ERROR_DIE);
  1688.             return false;
  1689.             
  1690.         }
  1691.         $table   ($quoteIdentifiers $DB->quoteIdentifier($this->tableName()) $this->tableName());
  1692.         $key_col = empty($keys[0]'' (($quoteIdentifiers $DB->quoteIdentifier($keys[0]$keys[0]));
  1693.         $as      ($quoteIdentifiers $DB->quoteIdentifier('DATAOBJECT_NUM''DATAOBJECT_NUM');
  1694.         
  1695.         // support distinct on default keys.
  1696.         $countWhat (strtoupper($countWhat== 'DISTINCT'
  1697.             "DISTINCT {$table}.{$key_col}" : $countWhat;
  1698.         
  1699.         $countWhat is_string($countWhat$countWhat : "{$table}.{$key_col}";
  1700.         
  1701.         $r $t->_query(
  1702.             "SELECT count({$countWhat}) as $as
  1703.                 FROM $table {$t->_join} {$t->_query['condition']}");
  1704.         if (PEAR::isError($r)) {
  1705.             return false;
  1706.         }
  1707.          
  1708.         $result  $_DB_DATAOBJECT['RESULTS'][$t->_DB_resultid];
  1709.         $l $result->fetchRow(DB_DATAOBJECT_FETCHMODE_ORDERED);
  1710.         // free the results - essential on oracle.
  1711.         $t->free();
  1712.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  1713.             $this->debug('Count returned '$l[0,1);
  1714.         }
  1715.         return (int) $l[0];
  1716.     }
  1717.  
  1718.     /**
  1719.      * sends raw query to database
  1720.      *
  1721.      * Since _query has to be a private 'non overwriteable method', this is a relay
  1722.      *
  1723.      * @param  string  $string  SQL Query
  1724.      * @access public
  1725.      * @return void or DB_Error
  1726.      */
  1727.     function query($string)
  1728.     {
  1729.         return $this->_query($string);
  1730.     }
  1731.  
  1732.  
  1733.     /**
  1734.      * an escape wrapper around DB->escapeSimple()
  1735.      * can be used when adding manual queries or clauses
  1736.      * eg.
  1737.      * $object->query("select * from xyz where abc like '". $object->escape($_GET['name']) . "'");
  1738.      *
  1739.      * @param  string  $string  value to be escaped
  1740.      * @param  bool $likeEscape  escapes % and _ as well. - so like queries can be protected.
  1741.      * @access public
  1742.      * @return string 
  1743.      */
  1744.     function escape($string$likeEscape=false)
  1745.     {
  1746.         global $_DB_DATAOBJECT;
  1747.         $this->_connect();
  1748.         $DB $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  1749.         // mdb2 uses escape...
  1750.         $dd = empty($_DB_DATAOBJECT['CONFIG']['db_driver']'DB' $_DB_DATAOBJECT['CONFIG']['db_driver'];
  1751.         $ret ($dd == 'DB'$DB->escapeSimple($string$DB->escape($string);
  1752.         if ($likeEscape{
  1753.             $ret str_replace(array('_','%')array('\_','\%')$ret);
  1754.         }
  1755.         return $ret;
  1756.         
  1757.     }
  1758.  
  1759.     /* ==================================================== */
  1760.     /*        Major Private Vars                            */
  1761.     /* ==================================================== */
  1762.  
  1763.     /**
  1764.      * The Database connection dsn (as described in the PEAR DB)
  1765.      * only used really if you are writing a very simple application/test..
  1766.      * try not to use this - it is better stored in configuration files..
  1767.      *
  1768.      * @access  private
  1769.      * @var     string 
  1770.      */
  1771.     var $_database_dsn '';
  1772.  
  1773.     /**
  1774.      * The Database connection id (md5 sum of databasedsn)
  1775.      *
  1776.      * @access  private
  1777.      * @var     string 
  1778.      */
  1779.     var $_database_dsn_md5 '';
  1780.  
  1781.     /**
  1782.      * The Database name
  1783.      * created in __connection
  1784.      *
  1785.      * @access  private
  1786.      * @var  string 
  1787.      */
  1788.     var $_database '';
  1789.  
  1790.     
  1791.     
  1792.     /**
  1793.      * The QUERY rules
  1794.      * This replaces alot of the private variables
  1795.      * used to build a query, it is unset after find() is run.
  1796.      * 
  1797.      *
  1798.      *
  1799.      * @access  private
  1800.      * @var     array 
  1801.      */
  1802.     var $_query = array(
  1803.         'condition'   => ''// the WHERE condition
  1804.         'group_by'    => ''// the GROUP BY condition
  1805.         'order_by'    => ''// the ORDER BY condition
  1806.         'having'      => ''// the HAVING condition
  1807.         'limit_start' => ''// the LIMIT condition
  1808.         'limit_count' => ''// the LIMIT condition
  1809.         'data_select' => '*'// the columns to be SELECTed
  1810.         'unions'      => array()// the added unions,
  1811.         'derive_table' => ''// derived table name (BETA)
  1812.         'derive_select' => ''// derived table select (BETA)
  1813.     );
  1814.         
  1815.     
  1816.   
  1817.  
  1818.     /**
  1819.      * Database result id (references global $_DB_DataObject[results]
  1820.      *
  1821.      * @access  private
  1822.      * @var     integer 
  1823.      */
  1824.     var $_DB_resultid;
  1825.      
  1826.      /**
  1827.      * ResultFields - on the last call to fetch(), resultfields is sent here,
  1828.      * so we can clean up the memory.
  1829.      *
  1830.      * @access  public
  1831.      * @var     array 
  1832.      */
  1833.     var $_resultFields = false; 
  1834.  
  1835.  
  1836.     /* ============================================================== */
  1837.     /*  Table definition layer (started of very private but 'came out'*/
  1838.     /* ============================================================== */
  1839.  
  1840.     /**
  1841.      * Autoload or manually load the table definitions
  1842.      *
  1843.      *
  1844.      * usage :
  1845.      * DB_DataObject::databaseStructure(  'databasename',
  1846.      *                                    parse_ini_file('mydb.ini',true),
  1847.      *                                    parse_ini_file('mydb.link.ini',true));
  1848.      *
  1849.      * obviously you dont have to use ini files.. (just return array similar to ini files..)
  1850.      *  
  1851.      * It should append to the table structure array
  1852.      *
  1853.      *     
  1854.      * @param optional string  name of database to assign / read
  1855.      * @param optional array   structure of database, and keys
  1856.      * @param optional array  table links
  1857.      *
  1858.      * @access public
  1859.      * @return true or PEAR:error on wrong paramenters.. or false if no file exists..
  1860.      *               or the array(tablename => array(column_name=>type)) if called with 1 argument.. (databasename)
  1861.      */
  1862.     function databaseStructure()
  1863.     {
  1864.  
  1865.         global $_DB_DATAOBJECT;
  1866.         
  1867.         // Assignment code 
  1868.         
  1869.         if ($args func_get_args()) {
  1870.         
  1871.             if (count($args== 1{
  1872.                 
  1873.                 // this returns all the tables and their structure..
  1874.                 if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  1875.                     $this->debug("Loading Generator as databaseStructure called with args",1);
  1876.                 }
  1877.                 
  1878.                 $x = new DB_DataObject;
  1879.                 $x->_database = $args[0];
  1880.                 $this->_connect();
  1881.                 $DB $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  1882.        
  1883.                 $tables $DB->getListOf('tables');
  1884.                 class_exists('DB_DataObject_Generator''' 
  1885.                     require_once 'DB/DataObject/Generator.php';
  1886.                     
  1887.                 foreach($tables as $table{
  1888.                     $y = new DB_DataObject_Generator;
  1889.                     $y->fillTableSchema($x->_database,$table);
  1890.                 }
  1891.                 return $_DB_DATAOBJECT['INI'][$x->_database];            
  1892.             else {
  1893.         
  1894.                 $_DB_DATAOBJECT['INI'][$args[0]] = isset($_DB_DATAOBJECT['INI'][$args[0]]?
  1895.                     $_DB_DATAOBJECT['INI'][$args[0]] $args[1$args[1];
  1896.                 
  1897.                 if (isset($args[1])) {
  1898.                     $_DB_DATAOBJECT['LINKS'][$args[0]] = isset($_DB_DATAOBJECT['LINKS'][$args[0]]?
  1899.                         $_DB_DATAOBJECT['LINKS'][$args[0]] $args[2$args[2];
  1900.                 }
  1901.                 return true;
  1902.             }
  1903.           
  1904.         }
  1905.         
  1906.         
  1907.         
  1908.         if (!$this->_database{
  1909.             $this->_connect();
  1910.         }
  1911.         
  1912.         
  1913.         // if this table is already loaded this table..
  1914.         if (!empty($_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()])) {
  1915.             return true;
  1916.         }
  1917.         
  1918.         // initialize the ini data.. if empt..
  1919.         if (empty($_DB_DATAOBJECT['INI'][$this->_database])) {
  1920.             $_DB_DATAOBJECT['INI'][$this->_database= array();
  1921.         }
  1922.          
  1923.         if (empty($_DB_DATAOBJECT['CONFIG'])) {
  1924.             DB_DataObject::_loadConfig();
  1925.         }
  1926.         
  1927.         // we do not have the data for this table yet...
  1928.         
  1929.         // if we are configured to use the proxy..
  1930.         
  1931.         if !empty($_DB_DATAOBJECT['CONFIG']['proxy']) ) {
  1932.             if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  1933.                 $this->debug("Loading Generator to fetch Schema",1);
  1934.             }
  1935.             class_exists('DB_DataObject_Generator''' 
  1936.                 require_once 'DB/DataObject/Generator.php';
  1937.                 
  1938.             
  1939.             $x = new DB_DataObject_Generator;
  1940.             $x->fillTableSchema($this->_database,$this->tableName());
  1941.             return true;
  1942.         }
  1943.             
  1944.              
  1945.        
  1946.         
  1947.         // if you supply this with arguments, then it will take those
  1948.         // as the database and links array...
  1949.          
  1950.         $schemas = isset($_DB_DATAOBJECT['CONFIG']['schema_location']?
  1951.             array("{$_DB_DATAOBJECT['CONFIG']['schema_location']}/{$this->_database}.ini":
  1952.             array(;
  1953.                  
  1954.         if (isset($_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"])) {
  1955.             $schemas is_array($_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"]?
  1956.                 $_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}":
  1957.                 explode(PATH_SEPARATOR,$_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"]);
  1958.         }
  1959.                     
  1960.          
  1961.         $_DB_DATAOBJECT['INI'][$this->_database= array();
  1962.         foreach ($schemas as $ini{
  1963.              if (file_exists($ini&& is_file($ini)) {
  1964.                 
  1965.                 $_DB_DATAOBJECT['INI'][$this->_databasearray_merge(
  1966.                     $_DB_DATAOBJECT['INI'][$this->_database],
  1967.                     parse_ini_file($initrue)
  1968.                 );
  1969.                     
  1970.                 if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) 
  1971.                     if (!is_readable ($ini)) {
  1972.                         $this->debug("ini file is not readable: $ini","databaseStructure",1);
  1973.                     else {
  1974.                         $this->debug("Loaded ini file: $ini","databaseStructure",1);
  1975.                     }
  1976.                 }
  1977.             else {
  1978.                 if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  1979.                     $this->debug("Missing ini file: $ini","databaseStructure",1);
  1980.                 }
  1981.             }
  1982.              
  1983.         }
  1984.         // are table name lowecased..
  1985.         if (!empty($_DB_DATAOBJECT['CONFIG']['portability']&& $_DB_DATAOBJECT['CONFIG']['portability'1{
  1986.             foreach($_DB_DATAOBJECT['INI'][$this->_databaseas $k=>$v{
  1987.                 // results in duplicate cols.. but not a big issue..
  1988.                 $_DB_DATAOBJECT['INI'][$this->_database][strtolower($k)$v;
  1989.             }
  1990.         }
  1991.         
  1992.         
  1993.         // now have we loaded the structure.. 
  1994.         
  1995.         if (!empty($_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()])) {
  1996.             return true;
  1997.         }
  1998.         // - if not try building it..
  1999.         if (!empty($_DB_DATAOBJECT['CONFIG']['proxy'])) {
  2000.             class_exists('DB_DataObject_Generator''' 
  2001.                 require_once 'DB/DataObject/Generator.php';
  2002.                 
  2003.             $x = new DB_DataObject_Generator;
  2004.             $x->fillTableSchema($this->_database,$this->tableName());
  2005.             // should this fail!!!???
  2006.             return true;
  2007.         }
  2008.         $this->debug("Cant find database schema: {$this->_database}/{$this->tableName()} \n".
  2009.                     "in links file data: " print_r($_DB_DATAOBJECT['INI'],true),"databaseStructure",5);
  2010.         // we have to die here!! - it causes chaos if we dont (including looping forever!)
  2011.         $this->raiseError"Unable to load schema for database and table (turn debugging up to 5 for full error message)"DB_DATAOBJECT_ERROR_INVALIDARGSPEAR_ERROR_DIE);
  2012.         return false;
  2013.         
  2014.          
  2015.     }
  2016.  
  2017.  
  2018.  
  2019.  
  2020.     /**
  2021.      * Return or assign the name of the current table
  2022.      *
  2023.      *
  2024.      * @param   string optinal table name to set
  2025.      * @access public
  2026.      * @return string The name of the current table
  2027.      */
  2028.     function tableName()
  2029.     {
  2030.         global $_DB_DATAOBJECT;
  2031.         $args func_get_args();
  2032.         if (count($args)) {
  2033.             $this->__table = $args[0];
  2034.         }
  2035.         if (!empty($_DB_DATAOBJECT['CONFIG']['portability']&& $_DB_DATAOBJECT['CONFIG']['portability'1{
  2036.             return strtolower($this->__table);
  2037.         }
  2038.         return $this->__table;
  2039.     }
  2040.     
  2041.     /**
  2042.      * Return or assign the name of the current database
  2043.      *
  2044.      * @param   string optional database name to set
  2045.      * @access public
  2046.      * @return string The name of the current database
  2047.      */
  2048.     function database()
  2049.     {
  2050.         $args func_get_args();
  2051.         if (count($args)) {
  2052.             $this->_database = $args[0];
  2053.         else {
  2054.             $this->_connect();
  2055.         }
  2056.         
  2057.         return $this->_database;
  2058.     }
  2059.   
  2060.     /**
  2061.      * get/set an associative array of table columns
  2062.      *
  2063.      * @access public
  2064.      * @param  array key=>type array
  2065.      * @return array (associative)
  2066.      */
  2067.     function table()
  2068.     {
  2069.         
  2070.         // for temporary storage of database fields..
  2071.         // note this is not declared as we dont want to bloat the print_r output
  2072.         $args func_get_args();
  2073.         if (count($args)) {
  2074.             $this->_database_fields = $args[0];
  2075.         }
  2076.         if (isset($this->_database_fields)) {
  2077.             return $this->_database_fields;
  2078.         }
  2079.         
  2080.         
  2081.         global $_DB_DATAOBJECT;
  2082.         if (!isset($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
  2083.             $this->_connect();
  2084.         }
  2085.           
  2086.         if (isset($_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()])) {
  2087.             return $_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()];
  2088.         }
  2089.         
  2090.         $this->databaseStructure();
  2091.  
  2092.         
  2093.         $ret = array();
  2094.         if (isset($_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()])) {
  2095.             $ret =  $_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()];
  2096.         }
  2097.         
  2098.         return $ret;
  2099.     }
  2100.  
  2101.     /**
  2102.      * get/set an  array of table primary keys
  2103.      *
  2104.      * set usage: $do->keys('id','code');
  2105.      *
  2106.      * This is defined in the table definition if it gets it wrong,
  2107.      * or you do not want to use ini tables, you can override this.
  2108.      * @param  string optional set the key
  2109.      * @param    optional  set more keys
  2110.      * @access public
  2111.      * @return array 
  2112.      */
  2113.     function keys()
  2114.     {
  2115.         // for temporary storage of database fields..
  2116.         // note this is not declared as we dont want to bloat the print_r output
  2117.         $args func_get_args();
  2118.         if (count($args)) {
  2119.             $this->_database_keys = $args;
  2120.         }
  2121.         if (isset($this->_database_keys)) {
  2122.             return $this->_database_keys;
  2123.         }
  2124.         
  2125.         global $_DB_DATAOBJECT;
  2126.         if (!isset($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
  2127.             $this->_connect();
  2128.         }
  2129.         if (isset($_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()."__keys"])) {
  2130.             return array_keys($_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()."__keys"]);
  2131.         }
  2132.         $this->databaseStructure();
  2133.         
  2134.         if (isset($_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()."__keys"])) {
  2135.             return array_keys($_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()."__keys"]);
  2136.         }
  2137.         return array();
  2138.     }
  2139.     /**
  2140.      * get/set an  sequence key
  2141.      *
  2142.      * by default it returns the first key from keys()
  2143.      * set usage: $do->sequenceKey('id',true);
  2144.      *
  2145.      * override this to return array(false,false) if table has no real sequence key.
  2146.      *
  2147.      * @param  string  optional the key sequence/autoinc. key
  2148.      * @param  boolean optional use native increment. default false
  2149.      * @param  false|stringoptional native sequence name
  2150.      * @access public
  2151.      * @return array (column,use_native,sequence_name)
  2152.      */
  2153.     function sequenceKey()
  2154.     {
  2155.         global $_DB_DATAOBJECT;
  2156.           
  2157.         // call setting
  2158.         if (!$this->_database{
  2159.             $this->_connect();
  2160.         }
  2161.         
  2162.         if (!isset($_DB_DATAOBJECT['SEQUENCE'][$this->_database])) {
  2163.             $_DB_DATAOBJECT['SEQUENCE'][$this->_database= array();
  2164.         }
  2165.  
  2166.         
  2167.         $args func_get_args();
  2168.         if (count($args)) {
  2169.             $args[1= isset($args[1]$args[1: false;
  2170.             $args[2= isset($args[2]$args[2: false;
  2171.             $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()$args;
  2172.         }
  2173.         if (isset($_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()])) {
  2174.             return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()];
  2175.         }
  2176.         // end call setting (eg. $do->sequenceKeys(a,b,c); )
  2177.         
  2178.        
  2179.         
  2180.         
  2181.         $keys $this->keys();
  2182.         if (!$keys{
  2183.             return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()
  2184.                 = array(false,false,false);
  2185.         }
  2186.  
  2187.  
  2188.         $table =  $this->table();
  2189.        
  2190.         $dbtype    $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype'];
  2191.         
  2192.         $usekey $keys[0];
  2193.         
  2194.         
  2195.         
  2196.         $seqname = false;
  2197.         
  2198.         if (!empty($_DB_DATAOBJECT['CONFIG']['sequence_'.$this->tableName()])) {
  2199.             $seqname $_DB_DATAOBJECT['CONFIG']['sequence_'.$this->tableName()];
  2200.             if (strpos($seqname,':'!== false{
  2201.                 list($usekey,$seqnameexplode(':',$seqname);
  2202.             }
  2203.         }  
  2204.         
  2205.         
  2206.         // if the key is not an integer - then it's not a sequence or native
  2207.         if (empty($table[$usekey]|| !($table[$usekeyDB_DATAOBJECT_INT)) {
  2208.                 return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()= array(false,false,false);
  2209.         }
  2210.         
  2211.         
  2212.         if (!empty($_DB_DATAOBJECT['CONFIG']['ignore_sequence_keys'])) {
  2213.             $ignore =  $_DB_DATAOBJECT['CONFIG']['ignore_sequence_keys'];
  2214.             if (is_string($ignore&& (strtoupper($ignore== 'ALL')) {
  2215.                 return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()= array(false,false,$seqname);
  2216.             }
  2217.             if (is_string($ignore)) {
  2218.                 $ignore $_DB_DATAOBJECT['CONFIG']['ignore_sequence_keys'explode(',',$ignore);
  2219.             }
  2220.             if (in_array($this->tableName(),$ignore)) {
  2221.                 return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()= array(false,false,$seqname);
  2222.             }
  2223.         }
  2224.         
  2225.         
  2226.         $realkeys $_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()."__keys"];
  2227.         
  2228.         // if you are using an old ini file - go back to old behaviour...
  2229.         if (is_numeric($realkeys[$usekey])) {
  2230.             $realkeys[$usekey'N';
  2231.         }
  2232.         
  2233.         // multiple unique primary keys without a native sequence...
  2234.         if (($realkeys[$usekey== 'K'&& (count($keys> 1)) {
  2235.             return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()= array(false,false,$seqname);
  2236.         }
  2237.         // use native sequence keys...
  2238.         // technically postgres native here...
  2239.         // we need to get the new improved tabledata sorted out first.
  2240.         
  2241.         // support named sequence keys.. - currently postgres only..
  2242.         
  2243.         if (    in_array($dbtype array('pgsql')) &&
  2244.                 ($table[$usekeyDB_DATAOBJECT_INT&& 
  2245.                 isset($realkeys[$usekey]&& strlen($realkeys[$usekey]> 1{
  2246.             return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()= array($usekey,true$realkeys[$usekey]);
  2247.         }
  2248.         
  2249.         if (    in_array($dbtype array('pgsql''mysql''mysqli''mssql''ifx')) && 
  2250.                 ($table[$usekeyDB_DATAOBJECT_INT&& 
  2251.                 isset($realkeys[$usekey]&& ($realkeys[$usekey== 'N')
  2252.                 {
  2253.             return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()= array($usekey,true,$seqname);
  2254.         }
  2255.         
  2256.         
  2257.         // if not a native autoinc, and we have not assumed all primary keys are sequence
  2258.         if (($realkeys[$usekey!= 'N'&& 
  2259.             !empty($_DB_DATAOBJECT['CONFIG']['dont_use_pear_sequences'])) {
  2260.             return array(false,false,false);
  2261.         }
  2262.         
  2263.         
  2264.         
  2265.         // I assume it's going to try and be a nextval DB sequence.. (not native)
  2266.         return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->tableName()= array($usekey,false,$seqname);
  2267.     }
  2268.     
  2269.     
  2270.     
  2271.     /* =========================================================== */
  2272.     /*  Major Private Methods - the core part!              */
  2273.     /* =========================================================== */
  2274.  
  2275.  
  2276.     
  2277.     /**
  2278.      * clear the cache values for this class  - normally done on insert/update etc.
  2279.      *
  2280.      * @access private
  2281.      * @return void 
  2282.      */
  2283.     function _clear_cache()
  2284.     {
  2285.         global $_DB_DATAOBJECT;
  2286.         
  2287.         $class strtolower(get_class($this));
  2288.         
  2289.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  2290.             $this->debug("Clearing Cache for ".$class,1);
  2291.         }
  2292.         
  2293.         if (!empty($_DB_DATAOBJECT['CACHE'][$class])) {
  2294.             unset($_DB_DATAOBJECT['CACHE'][$class]);
  2295.         }
  2296.     }
  2297.  
  2298.     
  2299.     /**
  2300.      * backend wrapper for quoting, as MDB2 and DB do it differently...
  2301.      *
  2302.      * @access private
  2303.      * @return string quoted
  2304.      */
  2305.     
  2306.     function _quote($str
  2307.     {
  2308.         global $_DB_DATAOBJECT;
  2309.         return (empty($_DB_DATAOBJECT['CONFIG']['db_driver']|| 
  2310.                 ($_DB_DATAOBJECT['CONFIG']['db_driver'== 'DB'))
  2311.             ? $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->quoteSmart($str)
  2312.             : $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->quote($str);
  2313.     }
  2314.     
  2315.     
  2316.     /**
  2317.      * connects to the database
  2318.      *
  2319.      *
  2320.      * TODO: tidy this up - This has grown to support a number of connection options like
  2321.      *  a) dynamic changing of ini file to change which database to connect to
  2322.      *  b) multi data via the table_{$table} = dsn ini option
  2323.      *  c) session based storage.
  2324.      *
  2325.      * @access private
  2326.      * @return true | PEAR::error
  2327.      */
  2328.     function _connect()
  2329.     {
  2330.         global $_DB_DATAOBJECT;
  2331.         if (empty($_DB_DATAOBJECT['CONFIG'])) {
  2332.             $this->_loadConfig();
  2333.         }
  2334.         // Set database driver for reference 
  2335.         $db_driver = empty($_DB_DATAOBJECT['CONFIG']['db_driver']
  2336.                 'DB' $_DB_DATAOBJECT['CONFIG']['db_driver'];
  2337.         
  2338.         // is it already connected ?    
  2339.         if ($this->_database_dsn_md5 && !empty($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
  2340.             
  2341.             // connection is an error...
  2342.             if (PEAR::isError($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
  2343.                 return $this->raiseError(
  2344.                         $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->message,
  2345.                         $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->codePEAR_ERROR_DIE
  2346.                 );
  2347.                  
  2348.             }
  2349.  
  2350.             if (empty($this->_database)) {
  2351.                 $this->_database = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['database'];
  2352.                 $hasGetDatabase method_exists($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]'getDatabase');
  2353.                 
  2354.                 $this->_database = ($db_driver != 'DB' && $hasGetDatabase)  
  2355.                         ? $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->getDatabase(
  2356.                         : $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['database'];
  2357.  
  2358.                 
  2359.                 
  2360.                 if (($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype'== 'sqlite'
  2361.                     && is_file($this->_database))  {
  2362.                     $this->_database = basename($this->_database);
  2363.                 }
  2364.                 if ($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype'== 'ibase')  {
  2365.                     $this->_database = substr(basename($this->_database)0-4);
  2366.                 }
  2367.                 
  2368.             }
  2369.             // theoretically we have a md5, it's listed in connections and it's not an error.
  2370.             // so everything is ok!
  2371.             return true;
  2372.             
  2373.         }
  2374.  
  2375.         // it's not currently connected!
  2376.         // try and work out what to use for the dsn !
  2377.  
  2378.         $options$_DB_DATAOBJECT['CONFIG'];
  2379.         // if the databse dsn dis defined in the object..
  2380.         $dsn = isset($this->_database_dsn$this->_database_dsn : null;
  2381.         
  2382.         if (!$dsn{
  2383.             if (!$this->_database && !empty($this->__table)) {
  2384.                 $this->_database = isset($options["table_{$this->tableName()}"]$options["table_{$this->tableName()}": null;
  2385.             }
  2386.             if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  2387.                 $this->debug("Checking for database specific ini ('{$this->_database}') : database_{$this->_database} in options","CONNECT");
  2388.             }
  2389.             
  2390.             if ($this->_database && !empty($options["database_{$this->_database}"]))  {
  2391.                 $dsn $options["database_{$this->_database}"];
  2392.             else if (!empty($options['database'])) {
  2393.                 $dsn $options['database'];
  2394.                   
  2395.             }
  2396.         }
  2397.         
  2398.         // if still no database...
  2399.         if (!$dsn{
  2400.             return $this->raiseError(
  2401.                 "No database name / dsn found anywhere",
  2402.                 DB_DATAOBJECT_ERROR_INVALIDCONFIGPEAR_ERROR_DIE
  2403.             );
  2404.                  
  2405.         }
  2406.         
  2407.         
  2408.         if (is_string($dsn)) {
  2409.             $this->_database_dsn_md5 = md5($dsn);
  2410.         else {
  2411.             /// support array based dsn's
  2412.             $this->_database_dsn_md5 = md5(serialize($dsn));
  2413.         }
  2414.  
  2415.         if (!empty($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
  2416.             if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  2417.                 $this->debug("USING CACHED CONNECTION""CONNECT",3);
  2418.             }
  2419.             
  2420.             
  2421.             
  2422.             if (!$this->_database{
  2423.  
  2424.                 $hasGetDatabase method_exists($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]'getDatabase');
  2425.                 $this->_database = ($db_driver != 'DB' && $hasGetDatabase)  
  2426.                         ? $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->getDatabase(
  2427.                         : $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['database'];
  2428.                 
  2429.                 if (($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype'== 'sqlite'
  2430.                     && is_file($this->_database)) 
  2431.                 {
  2432.                     $this->_database = basename($this->_database);
  2433.                 }
  2434.             }
  2435.             return true;
  2436.         }
  2437.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  2438.             $this->debug("NEW CONNECTION TP DATABASE :" .$this->_database "CONNECT",3);
  2439.             /* actualy make a connection */
  2440.             $this->debug(print_r($dsn,true." {$this->_database_dsn_md5}""CONNECT",3);
  2441.         }
  2442.         
  2443.         // Note this is verbose deliberatly! 
  2444.         
  2445.         if ($db_driver == 'DB'{
  2446.             
  2447.             /* PEAR DB connect */
  2448.             
  2449.             // this allows the setings of compatibility on DB 
  2450.             $db_options = PEAR::getStaticProperty('DB','options');
  2451.             require_once 'DB.php';
  2452.             if ($db_options{
  2453.                 $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5= DB::connect($dsn,$db_options);
  2454.             else {
  2455.                 $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5= DB::connect($dsn);
  2456.             }
  2457.             
  2458.         else {
  2459.             /* assumption is MDB2 */
  2460.             require_once 'MDB2.php';
  2461.             // this allows the setings of compatibility on MDB2 
  2462.             $db_options = PEAR::getStaticProperty('MDB2','options');
  2463.             $db_options is_array($db_options$db_options : array();
  2464.             $db_options['portability'= isset($db_options['portability')
  2465.                 ? $db_options['portability']  : MDB2_PORTABILITY_ALL ^ MDB2_PORTABILITY_FIX_CASE;
  2466.             $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5= MDB2::connect($dsn,$db_options);
  2467.             
  2468.         }
  2469.         
  2470.         
  2471.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  2472.             $this->debug(print_r($_DB_DATAOBJECT['CONNECTIONS'],true)"CONNECT",5);
  2473.         }
  2474.         if (PEAR::isError($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
  2475.             $this->debug($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->toString()"CONNECT FAILED",5);
  2476.             return $this->raiseError(
  2477.                     "Connect failed, turn on debugging to 5 see why",
  2478.                         $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->codePEAR_ERROR_DIE
  2479.             );
  2480.  
  2481.         }
  2482.          
  2483.         if (empty($this->_database)) {
  2484.             $hasGetDatabase method_exists($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]'getDatabase');
  2485.             
  2486.             $this->_database = ($db_driver != 'DB' && $hasGetDatabase)  
  2487.                     ? $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->getDatabase(
  2488.                     : $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['database'];
  2489.  
  2490.  
  2491.             if (($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype'== 'sqlite'
  2492.                 && is_file($this->_database)) 
  2493.             {
  2494.                 $this->_database = basename($this->_database);
  2495.             }
  2496.         }
  2497.         
  2498.         // Oracle need to optimize for portibility - not sure exactly what this does though :)
  2499.          
  2500.         return true;
  2501.     }
  2502.  
  2503.     /**
  2504.      * sends query to database - this is the private one that must work
  2505.      *   - internal functions use this rather than $this->query()
  2506.      *
  2507.      * @param  string  $string 
  2508.      * @access private
  2509.      * @return mixed none or PEAR_Error
  2510.      */
  2511.     function _query($string)
  2512.     {
  2513.         global $_DB_DATAOBJECT;
  2514.         $this->_connect();
  2515.         
  2516.  
  2517.         $DB $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  2518.  
  2519.         $options $_DB_DATAOBJECT['CONFIG'];
  2520.         
  2521.         $_DB_driver = empty($_DB_DATAOBJECT['CONFIG']['db_driver']
  2522.                     'DB':  $_DB_DATAOBJECT['CONFIG']['db_driver'];
  2523.         
  2524.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  2525.             $this->debug($string,$log="QUERY");
  2526.             
  2527.         }
  2528.         
  2529.         if (strtoupper($string== 'BEGIN'{
  2530.             if ($_DB_driver == 'DB'{
  2531.                 $DB->autoCommit(false);
  2532.                 $DB->simpleQuery('BEGIN');
  2533.             else {
  2534.                 $DB->beginTransaction();
  2535.             }
  2536.             return true;
  2537.         }
  2538.         if (strtoupper($string== 'COMMIT'{
  2539.             $res $DB->commit();
  2540.             if ($_DB_driver == 'DB'{
  2541.                 $DB->autoCommit(true);
  2542.             }
  2543.             return $res;
  2544.         }
  2545.         
  2546.         if (strtoupper($string== 'ROLLBACK'{
  2547.             $DB->rollback();
  2548.             if ($_DB_driver == 'DB'{
  2549.                 $DB->autoCommit(true);
  2550.             }
  2551.             return true;
  2552.         }
  2553.         
  2554.  
  2555.         if (!empty($options['debug_ignore_updates']&&
  2556.             (strtolower(substr(trim($string)06)) != 'select'&&
  2557.             (strtolower(substr(trim($string)04)) != 'show'&&
  2558.             (strtolower(substr(trim($string)08)) != 'describe')) {
  2559.  
  2560.             $this->debug('Disabling Update as you are in debug mode');
  2561.             return $this->raiseError("Disabling Update as you are in debug mode"null;
  2562.  
  2563.         }
  2564.         //if (@$_DB_DATAOBJECT['CONFIG']['debug'] > 1) {
  2565.             // this will only work when PEAR:DB supports it.
  2566.             //$this->debug($DB->getAll('explain ' .$string,DB_DATAOBJECT_FETCHMODE_ASSOC), $log="sql",2);
  2567.         //}
  2568.         
  2569.         // some sim
  2570.         $texplode(' ',microtime());
  2571.         $_DB_DATAOBJECT['QUERYENDTIME'$time $t[0]+$t[1];
  2572.          
  2573.         
  2574.         for ($tries = 0;$tries < 3;$tries++{
  2575.             
  2576.             if ($_DB_driver == 'DB'{
  2577.                 
  2578.                 $result $DB->query($string);
  2579.             else {
  2580.                 switch (strtolower(substr(trim($string),0,6))) {
  2581.                 
  2582.                     case 'insert':
  2583.                     case 'update':
  2584.                     case 'delete':
  2585.                         $result $DB->exec($string);
  2586.                         break;
  2587.                         
  2588.                     default:
  2589.                         $result $DB->query($string);
  2590.                         break;
  2591.                 }
  2592.             }
  2593.             
  2594.             // see if we got a failure.. - try again a few times..
  2595.             if (!is_object($result|| !is_a($result,'PEAR_Error')) {
  2596.                 break;
  2597.             }
  2598.             if ($result->getCode(!= -14{  // *DB_ERROR_NODBSELECTED
  2599.                 break; // not a connection error..
  2600.             }
  2601.             sleep(1)// wait before retyring..
  2602.             $DB->connect($DB->dsn);
  2603.         }
  2604.        
  2605.  
  2606.         if (is_object($result&& is_a($result,'PEAR_Error')) {
  2607.             if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) 
  2608.                 $this->debug($result->toString()"Query Error",);
  2609.             }
  2610.             $this->N = false;
  2611.             return $this->raiseError($result);
  2612.         }
  2613.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  2614.             $texplode(' ',microtime());
  2615.             $_DB_DATAOBJECT['QUERYENDTIME'$t[0]+$t[1];
  2616.             $this->debug('QUERY DONE IN  '.($t[0]+$t[1]-$time)." seconds"'query',1);
  2617.         }
  2618.         switch (strtolower(substr(trim($string),0,6))) {
  2619.             case 'insert':
  2620.             case 'update':
  2621.             case 'delete':
  2622.                 if ($_DB_driver == 'DB'{
  2623.                     // pear DB specific
  2624.                     return $DB->affectedRows()
  2625.                 }
  2626.                 return $result;
  2627.         }
  2628.         if (is_object($result)) {
  2629.             // lets hope that copying the result object is OK!
  2630.             
  2631.             $_DB_resultid  $GLOBALS['_DB_DATAOBJECT']['RESULTSEQ']++;
  2632.             $_DB_DATAOBJECT['RESULTS'][$_DB_resultid$result
  2633.             $this->_DB_resultid = $_DB_resultid;
  2634.         }
  2635.         $this->N = 0;
  2636.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  2637.             $this->debug(serialize($result)'RESULT',5);
  2638.         }
  2639.         if (method_exists($result'numRows')) {
  2640.             if ($_DB_driver == 'DB'{
  2641.                 $DB->expectError(DB_ERROR_UNSUPPORTED);
  2642.             else {
  2643.                 $DB->expectError(MDB2_ERROR_UNSUPPORTED);
  2644.             }
  2645.             
  2646.             $this->N = $result->numRows();
  2647.             //var_dump($this->N);
  2648.             
  2649.             if (is_object($this->N&& is_a($this->N,'PEAR_Error')) {
  2650.                 $this->N = true;
  2651.             }
  2652.             $DB->popExpect();
  2653.         }
  2654.     }
  2655.  
  2656.     /**
  2657.      * Builds the WHERE based on the values of of this object
  2658.      *
  2659.      * @param   mixed   $keys 
  2660.      * @param   array   $filter (used by update to only uses keys in this filter list).
  2661.      * @param   array   $negative_filter (used by delete to prevent deleting using the keys mentioned..)
  2662.      * @access  private
  2663.      * @return  string 
  2664.      */
  2665.     function _build_condition($keys$filter = array(),$negative_filter=array())
  2666.     {
  2667.         global $_DB_DATAOBJECT;
  2668.         $this->_connect();
  2669.         $DB $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  2670.        
  2671.         $quoteIdentifiers  !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
  2672.         $options $_DB_DATAOBJECT['CONFIG'];
  2673.         
  2674.         // if we dont have query vars.. - reset them.
  2675.         if ($this->_query === false{
  2676.             $x = new DB_DataObject;
  2677.             $this->_query= $x->_query;
  2678.         }
  2679.        
  2680.                     
  2681.         foreach($keys as $k => $v{
  2682.             // index keys is an indexed array
  2683.             /* these filter checks are a bit suspicious..
  2684.                 - need to check that update really wants to work this way */
  2685.  
  2686.             if ($filter{
  2687.                 if (!in_array($k$filter)) {
  2688.                     continue;
  2689.                 }
  2690.             }
  2691.             if ($negative_filter{
  2692.                 if (in_array($k$negative_filter)) {
  2693.                     continue;
  2694.                 }
  2695.             }
  2696.             if (!isset($this->$k)) {
  2697.                 continue;
  2698.             }
  2699.             
  2700.             $kSql $quoteIdentifiers 
  2701.                 ? $DB->quoteIdentifier($this->tableName()) '.' $DB->quoteIdentifier($k) )  
  2702.                 : "{$this->tableName()}.{$k}";
  2703.              
  2704.              
  2705.             
  2706.             if (is_object($this->$k&& is_a($this->$k,'DB_DataObject_Cast')) {
  2707.                 $dbtype $DB->dsn["phptype"];
  2708.                 $value $this->$k->toString($v,$DB);
  2709.                 if (PEAR::isError($value)) {
  2710.                     $this->raiseError($value->getMessage(,DB_DATAOBJECT_ERROR_INVALIDARG);
  2711.                     return false;
  2712.                 }
  2713.                 if ((strtolower($value=== 'null'&& !($v DB_DATAOBJECT_NOTNULL)) {
  2714.                     $this->whereAdd(" $kSql IS NULL");
  2715.                     continue;
  2716.                 }
  2717.                 $this->whereAdd(" $kSql = $value");
  2718.                 continue;
  2719.             }
  2720.             
  2721.             if (!($v DB_DATAOBJECT_NOTNULL&& DB_DataObject::_is_null($this,$k)) {
  2722.                 $this->whereAdd(" $kSql  IS NULL");
  2723.                 continue;
  2724.             }
  2725.             
  2726.  
  2727.             if ($v DB_DATAOBJECT_STR{
  2728.                 $this->whereAdd(" $kSql  = " . $this->_quote((string) (
  2729.                         ($v DB_DATAOBJECT_BOOL
  2730.                             // this is thanks to the braindead idea of postgres to 
  2731.                             // use t/f for boolean.
  2732.                             (($this->$k === 'f'? 0 : (int)(bool) $this->$k:  
  2733.                             $this->$k
  2734.                     )) );
  2735.                 continue;
  2736.             }
  2737.             if (is_numeric($this->$k)) {
  2738.                 $this->whereAdd(" $kSql = {$this->$k}");
  2739.                 continue;
  2740.             }
  2741.             /* this is probably an error condition! */
  2742.             $this->whereAdd(" $kSql = ".intval($this->$k));
  2743.         }
  2744.     }
  2745.  
  2746.     
  2747.     
  2748.      /**
  2749.      * classic factory method for loading a table class
  2750.      * usage: $do = DB_DataObject::factory('person')
  2751.      * WARNING - this may emit a include error if the file does not exist..
  2752.      * use @ to silence it (if you are sure it is acceptable)
  2753.      * eg. $do = @DB_DataObject::factory('person')
  2754.      *
  2755.      * table name can bedatabasename/table
  2756.      * - and allow modular dataobjects to be written..
  2757.      * (this also helps proxy creation)
  2758.      *
  2759.      * Experimental Support for Multi-Database factory eg. mydatabase.mytable
  2760.      * 
  2761.      * 
  2762.      * @param  string  $table  tablename (use blank to create a new instance of the same class.)
  2763.      * @access private
  2764.      * @return DataObject|PEAR_Error
  2765.      */
  2766.     
  2767.     
  2768.  
  2769.     function factory($table '')
  2770.     {
  2771.         global $_DB_DATAOBJECT;
  2772.         
  2773.         
  2774.         // multi-database support.. - experimental.
  2775.         $database '';
  2776.        
  2777.         if (strpos$table,'/'!== false {
  2778.             list($database,$tableexplode('.',$table2);
  2779.           
  2780.         }
  2781.          
  2782.         if (empty($_DB_DATAOBJECT['CONFIG'])) {
  2783.             DB_DataObject::_loadConfig();
  2784.         }
  2785.         // no configuration available for database
  2786.         if (!empty($database&& empty($_DB_DATAOBJECT['CONFIG']['database_'.$database])) {
  2787.                 return DB_DataObject::raiseError(
  2788.                     "unable to find database_{$database} in Configuration, It is required for factory with database"
  2789.                     0PEAR_ERROR_DIE );   
  2790.        }
  2791.         
  2792.        
  2793.         
  2794.         if ($table === ''{
  2795.             if (is_a($this,'DB_DataObject'&& strlen($this->tableName())) {
  2796.                 $table $this->tableName();
  2797.             else {
  2798.                 return DB_DataObject::raiseError(
  2799.                     "factory did not recieve a table name",
  2800.                     DB_DATAOBJECT_ERROR_INVALIDARGS);
  2801.             }
  2802.         }
  2803.         
  2804.         // does this need multi db support??
  2805.         $cp = isset($_DB_DATAOBJECT['CONFIG']['class_prefix']?
  2806.             explode(PATH_SEPARATOR$_DB_DATAOBJECT['CONFIG']['class_prefix']'';
  2807.         
  2808.         //print_r($cp);
  2809.         
  2810.         // multiprefix support.
  2811.         $tbl preg_replace('/[^A-Z0-9]/i','_',ucfirst($table));
  2812.         if (is_array($cp)) {
  2813.             $class = array();
  2814.             foreach($cp as $cpr{
  2815.                 $ce substr(phpversion(),0,1> 4 ? class_exists($cpr $tbl,falseclass_exists($cpr $tbl);
  2816.                 if ($ce{
  2817.                     $class $cpr $tbl;
  2818.                     break;
  2819.                 }
  2820.                 $class[$cpr $tbl;
  2821.             }
  2822.         else {
  2823.             $class $tbl;
  2824.             $ce substr(phpversion(),0,1> 4 ? class_exists($class,falseclass_exists($class);
  2825.         }
  2826.         
  2827.         
  2828.         $rclass $ce $class  : DB_DataObject::_autoloadClass($class$table);
  2829.         // proxy = full|light
  2830.         if (!$rclass && isset($_DB_DATAOBJECT['CONFIG']['proxy'])) 
  2831.         
  2832.             DB_DataObject::debug("FAILED TO Autoload  $database.$table - using proxy.","FACTORY",1);
  2833.         
  2834.         
  2835.             $proxyMethod 'getProxy'.$_DB_DATAOBJECT['CONFIG']['proxy'];
  2836.             // if you have loaded (some other way) - dont try and load it again..
  2837.             class_exists('DB_DataObject_Generator''' 
  2838.                     require_once 'DB/DataObject/Generator.php';
  2839.             
  2840.             $d = new DB_DataObject;
  2841.            
  2842.             $d->__table = $table;
  2843.             
  2844.             $ret $d->_connect();
  2845.             if (is_object($ret&& is_a($ret'PEAR_Error')) {
  2846.                 return $ret;
  2847.             }
  2848.             
  2849.             $x = new DB_DataObject_Generator;
  2850.             return $x->$proxyMethod$d->_database$table);
  2851.         }
  2852.         
  2853.         if (!$rclass || !class_exists($rclass)) {
  2854.             return DB_DataObject::raiseError(
  2855.                 "factory could not find class " 
  2856.                 (is_array($classimplode(PATH_SEPARATOR$class)  $class  )
  2857.                 "from $table",
  2858.                 DB_DATAOBJECT_ERROR_INVALIDCONFIG);
  2859.         }
  2860.  
  2861.         $ret = new $rclass();
  2862.  
  2863.         if (!empty($database)) {
  2864.             DB_DataObject::debug("Setting database to $database","FACTORY",1);
  2865.             $ret->database($database);
  2866.         }
  2867.         return $ret;
  2868.     }
  2869.     /**
  2870.      * autoload Class
  2871.      *
  2872.      * @param  string|array $class  Class
  2873.      * @param  string  $table  Table trying to load.
  2874.      * @access private
  2875.      * @return string classname on Success
  2876.      */
  2877.     function _autoloadClass($class$table=false)
  2878.     {
  2879.         global $_DB_DATAOBJECT;
  2880.         
  2881.         if (empty($_DB_DATAOBJECT['CONFIG'])) {
  2882.             DB_DataObject::_loadConfig();
  2883.         }
  2884.         $class_prefix = empty($_DB_DATAOBJECT['CONFIG']['class_prefix']
  2885.                 '' $_DB_DATAOBJECT['CONFIG']['class_prefix'];
  2886.                 
  2887.         $table   $table $table substr($class,strlen($class_prefix));
  2888.  
  2889.         // only include the file if it exists - and barf badly if it has parse errors :)
  2890.         if (!empty($_DB_DATAOBJECT['CONFIG']['proxy']|| empty($_DB_DATAOBJECT['CONFIG']['class_location'])) {
  2891.             return false;
  2892.         }
  2893.         // support for:
  2894.         // class_location = mydir/ => maps to mydir/Tablename.php
  2895.         // class_location = mydir/myfile_%s.php => maps to mydir/myfile_Tablename
  2896.         // with directory sepr
  2897.         // class_location = mydir/:mydir2/: => tries all of thes locations.
  2898.         $cl $_DB_DATAOBJECT['CONFIG']['class_location'];
  2899.         
  2900.         
  2901.         switch (true{
  2902.             case (strpos($cl ,'%s'!== false):
  2903.                 $file sprintf($cl preg_replace('/[^A-Z0-9]/i','_',ucfirst($table)));
  2904.                 break;
  2905.                 
  2906.             case (strpos($cl PATH_SEPARATOR!== false):
  2907.                 $file = array();
  2908.                 foreach(explode(PATH_SEPARATOR$cl as $p{
  2909.                     $file[=  $p .'/'.preg_replace('/[^A-Z0-9]/i','_',ucfirst($table)).".php";
  2910.                 }
  2911.                 break;
  2912.             default:
  2913.                 $file $cl .'/'.preg_replace('/[^A-Z0-9]/i','_',ucfirst($table)).".php";
  2914.                 break;
  2915.         }
  2916.         
  2917.         $cls is_array($class$class : array($class);
  2918.         
  2919.         if (is_array($file|| !file_exists($file)) {
  2920.             $found = false;
  2921.             
  2922.             $file is_array($file$file : array($file);
  2923.             $search implode(PATH_SEPARATOR$file);
  2924.             foreach($file as $f{
  2925.                 foreach(explode(PATH_SEPARATOR'' . PATH_SEPARATOR . ini_get('include_path')) as $p{
  2926.                     $ff = empty($p$f : "$p/$f";
  2927.  
  2928.                     if (file_exists($ff)) {
  2929.                         $file $ff;
  2930.                         $found = true;
  2931.                         break;
  2932.                     }
  2933.                 }
  2934.                 if ($found{
  2935.                     break;
  2936.                 }
  2937.             }
  2938.             if (!$found{
  2939.                 DB_DataObject::raiseError(
  2940.                     "autoload:Could not find class " implode(','$cls.
  2941.                     " using class_location value :" $search .
  2942.                     " using include_path value :" ini_get('include_path')
  2943.                     DB_DATAOBJECT_ERROR_INVALIDCONFIG);
  2944.                 return false;
  2945.             }
  2946.         }
  2947.         
  2948.         include_once $file;
  2949.         
  2950.        
  2951.         $ce = false;
  2952.         foreach($cls as $c{
  2953.             $ce substr(phpversion(),0,1> 4 ? class_exists($c,falseclass_exists($c);
  2954.             if ($ce{
  2955.                 $class $c;
  2956.                 break;
  2957.             }
  2958.         }
  2959.         if (!$ce{
  2960.             DB_DataObject::raiseError(
  2961.                 "autoload:Could not autoload " implode(','$cls
  2962.                 DB_DATAOBJECT_ERROR_INVALIDCONFIG);
  2963.             return false;
  2964.         }
  2965.         return $class;
  2966.     }
  2967.     
  2968.     
  2969.     
  2970.     /**
  2971.      * Have the links been loaded?
  2972.      * if they have it contains a array of those variables.
  2973.      *
  2974.      * @access  private
  2975.      * @var     boolean | array
  2976.      */
  2977.     var $_link_loaded = false;
  2978.     
  2979.     /**
  2980.     * Get the links associate array  as defined by the links.ini file.
  2981.     * 
  2982.     *
  2983.     * Experimental... -
  2984.     * Should look a bit like
  2985.     *       [local_col_name] => "related_tablename:related_col_name"
  2986.     * 
  2987.     * @param    array $new_links optional - force update of the links for this table
  2988.     *                You probably want to restore it to it's original state after,
  2989.     *                as modifying here does it for the whole PHP request.
  2990.     * 
  2991.     * @return   array|null
  2992.     *            array       = if there are links defined for this table.
  2993.     *            empty array - if there is a links.ini file, but no links on this table
  2994.     *            false       - if no links.ini exists for this database (hence try auto_links).
  2995.     * @access   public
  2996.     * @see      DB_DataObject::getLinks(), DB_DataObject::getLink()
  2997.     */
  2998.     
  2999.     function links()
  3000.     {
  3001.         global $_DB_DATAOBJECT;
  3002.         if (empty($_DB_DATAOBJECT['CONFIG'])) {
  3003.             $this->_loadConfig();
  3004.         }
  3005.         // have to connect.. -> otherwise things break later.
  3006.         $this->_connect();
  3007.         
  3008.         // alias for shorter code..
  3009.         $lcfg  &$_DB_DATAOBJECT['LINKS'];
  3010.         $cfg   =  $_DB_DATAOBJECT['CONFIG'];
  3011.  
  3012.         if ($args func_get_args()) {
  3013.             // an associative array was specified, that updates the current
  3014.             // schema... - be careful doing this
  3015.             if (empty$lcfg[$this->_database])) {
  3016.                 $lcfg[$this->_database= array();
  3017.             }
  3018.             $lcfg[$this->_database][$this->tableName()$args[0];
  3019.             
  3020.         }
  3021.         // loaded and available.
  3022.         if (isset($lcfg[$this->_database][$this->tableName()])) {
  3023.             return $lcfg[$this->_database][$this->tableName()];
  3024.         }
  3025.  
  3026.         // loaded 
  3027.         if (isset($lcfg[$this->_database])) {
  3028.             // either no file, or empty..
  3029.             return $lcfg[$this->_database=== false ? null : array();
  3030.         }
  3031.         
  3032.         // links are same place as schema by default.
  3033.         $schemas = isset($cfg['schema_location']?
  3034.             array("{$cfg['schema_location']}/{$this->_database}.ini":
  3035.             array(;
  3036.  
  3037.         // if ini_* is set look there instead.
  3038.         // and support multiple locations.                 
  3039.         if (isset($cfg["ini_{$this->_database}"])) {
  3040.             $schemas is_array($cfg["ini_{$this->_database}"]?
  3041.                 $cfg["ini_{$this->_database}":
  3042.                 explode(PATH_SEPARATOR,$cfg["ini_{$this->_database}"]);
  3043.         }
  3044.                         
  3045.         // default to not available.
  3046.         $lcfg[$this->_database= false;
  3047.  
  3048.         foreach ($schemas as $ini{
  3049.                 
  3050.             $links = isset($cfg["links_{$this->_database}"]?
  3051.                     $cfg["links_{$this->_database}":
  3052.                     str_replace('.ini','.links.ini',$ini);
  3053.             
  3054.             // file really exists..
  3055.             if (!file_exists($links|| !is_file($links)) {
  3056.                 if (!empty($cfg['debug'])) {
  3057.                     $this->debug("Missing links.ini file: $links","links",1);
  3058.                 }
  3059.                 continue;
  3060.             }
  3061.  
  3062.             // set to empty array - as we have at least one file now..
  3063.             $lcfg[$this->_database= empty($lcfg[$this->_database]? array($lcfg[$this->_database];
  3064.  
  3065.             // merge schema file into lcfg..
  3066.             $lcfg[$this->_databasearray_merge(
  3067.                 $lcfg[$this->_database],
  3068.                 parse_ini_file($linkstrue)
  3069.             );
  3070.  
  3071.                         
  3072.             if (!empty($cfg['debug'])) {
  3073.                 $this->debug("Loaded links.ini file: $links","links",1);
  3074.             }
  3075.              
  3076.         }
  3077.         
  3078.         if (!empty($_DB_DATAOBJECT['CONFIG']['portability']&& $_DB_DATAOBJECT['CONFIG']['portability'1{
  3079.             foreach($lcfg[$this->_databaseas $k=>$v{
  3080.                 
  3081.                 $nk strtolower($k);
  3082.                 // results in duplicate cols.. but not a big issue..
  3083.                 $lcfg[$this->_database][$nk= isset($lcfg[$this->_database][$nk])
  3084.                     ? $lcfg[$this->_database][$nk]  : array();
  3085.                 
  3086.                 foreach($v as $kk =>$vv{
  3087.                     //var_Dump($vv);exit;
  3088.                     $vv =explode(':'$vv);
  3089.                     $vv[0strtolower($vv[0]);
  3090.                     $lcfg[$this->_database][$nk][$kkimplode(':'$vv);
  3091.                 }
  3092.                 
  3093.                 
  3094.             }
  3095.         }
  3096.         //echo '<PRE>';print_r($lcfg);exit;
  3097.         
  3098.         // if there is no link data at all on the file!
  3099.         // we return null.
  3100.         if ($lcfg[$this->_database=== false{
  3101.             return null;
  3102.         }
  3103.         
  3104.         if (isset($lcfg[$this->_database][$this->tableName()])) {
  3105.             return $lcfg[$this->_database][$this->tableName()];
  3106.         }
  3107.         
  3108.         return array();
  3109.     }
  3110.     
  3111.     
  3112.     /**
  3113.      * generic getter/setter for links
  3114.      *
  3115.      * This is the new 'recommended' way to get get/set linked objects.
  3116.      * must be used with links.ini
  3117.      *
  3118.      * usage:
  3119.      *  get:
  3120.      *  $obj = $do->link('company_id');
  3121.      *  $obj = $do->link(array('local_col', 'linktable:linked_col'));
  3122.      *  
  3123.      *  set:
  3124.      *  $do->link('company_id',0);
  3125.      *  $do->link('company_id',$obj);
  3126.      *  $do->link('company_id', array($obj));
  3127.      *
  3128.      *  example function
  3129.      *
  3130.      *  function company() {
  3131.      *     $this->link(array('company_id','company:id'), func_get_args());
  3132.      *   }
  3133.      *
  3134.      * 
  3135.      *
  3136.      * @param  mixed $link_spec              link specification (normally a string)
  3137.      *                                        uses similar rules to  joinAdd() array argument.
  3138.      * @param  mixed $set_value (optional)   int, DataObject, or array('set')
  3139.      * @author Alan Knowles
  3140.      * @access public
  3141.      * @return mixed true or false on setting, object on getting
  3142.      */
  3143.     function link($field$set_args = array())
  3144.     {
  3145.         require_once 'DB/DataObject/Links.php';
  3146.         $l = new DB_DataObject_Links($this);
  3147.         return  $l->link($field,$set_args;
  3148.         
  3149.     }
  3150.     
  3151.       /**
  3152.      * load related objects
  3153.      *
  3154.      * Generally not recommended to use this.
  3155.      * The generator should support creating getter_setter methods which are better suited.
  3156.      *
  3157.      * Relies on  <dbname>.links.ini
  3158.      *
  3159.      * Sets properties on the calling dataobject  you can change what
  3160.      * object vars the links are stored in by  changeing the format parameter
  3161.      *
  3162.      *
  3163.      * @param  string format (default _%s) where %s is the table name.
  3164.      * @author Tim White <tim@cyface.com>
  3165.      * @access public
  3166.      * @return boolean , true on success
  3167.      */
  3168.     function getLinks($format '_%s')
  3169.     {
  3170.         require_once 'DB/DataObject/Links.php';
  3171.          $l = new DB_DataObject_Links($this);
  3172.         return $l->applyLinks($format);
  3173.            
  3174.     }
  3175.  
  3176.     /**
  3177.      * deprecited : @use link()
  3178.      */
  3179.     function getLink($row$table = null$link = false)
  3180.     {
  3181.         require_once 'DB/DataObject/Links.php';
  3182.         $l = new DB_DataObject_Links($this);
  3183.         return $l->getLink($row$table === null ? false: $table$link);
  3184.          
  3185.         
  3186.     }
  3187.  
  3188.     /**
  3189.      * getLinkArray
  3190.      * Fetch an array of related objects. This should be used in conjunction with a <dbname>.links.ini file configuration (see the introduction on linking for details on this).
  3191.      * You may also use this with all parameters to specify, the column and related table.
  3192.      * This is highly dependant on naming columns 'correctly' :)
  3193.      * using colname = xxxxx_yyyyyy
  3194.      * xxxxxx = related table; (yyyyy = user defined..)
  3195.      * looks up table xxxxx, for value id=$this->xxxxx
  3196.      * stores it in $this->_xxxxx_yyyyy
  3197.      *
  3198.      * @access public
  3199.      * @param string $column - either column or column.xxxxx
  3200.      * @param string $table - name of table to look up value in
  3201.      * @return array - array of results (empty array on failure)
  3202.      * 
  3203.      *  Example - Getting the related objects
  3204.      * 
  3205.      *  $person = new DataObjects_Person;
  3206.      *  $person->get(12);
  3207.      *  $children = $person->getLinkArray('children');
  3208.      * 
  3209.      *  echo 'There are ', count($children), ' descendant(s):<br />';
  3210.      *  foreach ($children as $child) {
  3211.      *      echo $child->name, '<br />';
  3212.      *  }
  3213.      * 
  3214.      */
  3215.     function getLinkArray($row$table = null)
  3216.     {
  3217.         require_once 'DB/DataObject/Links.php';
  3218.         $l = new DB_DataObject_Links($this);
  3219.         return $l->getLinkArray($row$table === null ? false: $table);
  3220.      
  3221.     }
  3222.  
  3223.      /**
  3224.      * unionAdd - adds another dataobject to this, building a unioned query.
  3225.      *
  3226.      * usage:
  3227.      * $doTable1 = DB_DataObject::factory("table1");
  3228.      * $doTable2 = DB_DataObject::factory("table2");
  3229.      * 
  3230.      * $doTable1->selectAdd();
  3231.      * $doTable1->selectAdd("col1,col2");
  3232.      * $doTable1->whereAdd("col1 > 100");
  3233.      * $doTable1->orderBy("col1");
  3234.      *
  3235.      * $doTable2->selectAdd();
  3236.      * $doTable2->selectAdd("col1, col2");
  3237.      * $doTable2->whereAdd("col2 = 'v'");
  3238.      * 
  3239.      * $doTable1->unionAdd($doTable2);
  3240.      * $doTable1->find();
  3241.       * 
  3242.      * Note: this model may be a better way to implement joinAdd?, eg. do the building in find?
  3243.      * 
  3244.      * 
  3245.      * @param             $obj       object|false the union object or false to reset
  3246.      * @param    optional $is_all    string 'ALL' to do all.
  3247.      * @returns           $obj       object|array the added object, or old list if reset.
  3248.      */
  3249.     
  3250.     function unionAdd($obj,$is_all'')
  3251.     {
  3252.         if ($obj === false{
  3253.             $ret $this->_query['unions'];
  3254.             $this->_query['unions'= array();
  3255.             return $ret;
  3256.         }
  3257.         $this->_query['unions'][= array($obj'UNION ' $is_all ' ';
  3258.         return $obj;
  3259.     }
  3260.  
  3261.     
  3262.     
  3263.     /**
  3264.      * The JOIN condition
  3265.      *
  3266.      * @access  private
  3267.      * @var     string 
  3268.      */
  3269.     var $_join '';
  3270.  
  3271.     /**
  3272.      * joinAdd - adds another dataobject to this, building a joined query.
  3273.      *
  3274.      * example (requires links.ini to be set up correctly)
  3275.      * // get all the images for product 24
  3276.      * $i = new DataObject_Image();
  3277.      * $pi = new DataObjects_Product_image();
  3278.      * $pi->product_id = 24; // set the product id to 24
  3279.      * $i->joinAdd($pi); // add the product_image connectoin
  3280.      * $i->find();
  3281.      * while ($i->fetch()) {
  3282.      *     // do stuff
  3283.      * }
  3284.      * // an example with 2 joins
  3285.      * // get all the images linked with products or productgroups
  3286.      * $i = new DataObject_Image();
  3287.      * $pi = new DataObject_Product_image();
  3288.      * $pgi = new DataObject_Productgroup_image();
  3289.      * $i->joinAdd($pi);
  3290.      * $i->joinAdd($pgi);
  3291.      * $i->find();
  3292.      * while ($i->fetch()) {
  3293.      *     // do stuff
  3294.      * }
  3295.      *
  3296.      *
  3297.      * @param    optional $obj       object |array    the joining object (no value resets the join)
  3298.      *                                           If you use an array here it should be in the format:
  3299.      *                                           array('local_column','remotetable:remote_column');
  3300.      *                                              if remotetable does not have a definition, you should
  3301.      *                                              use @ to hide the include error message..
  3302.      *                                           array('local_column',  $dataobject , 'remote_column');
  3303.      *                                              if array has 3 args, then second is assumed to be the linked dataobject.
  3304.      *
  3305.      * @param    optional $joinType  string | array
  3306.      *                                           'LEFT'|'INNER'|'RIGHT'|'' Inner is default, '' indicates
  3307.      *                                           just select ... from a,b,c with no join and
  3308.      *                                           links are added as where items.
  3309.      *                                          
  3310.      *                                           If second Argument is array, it is assumed to be an associative
  3311.      *                                           array with arguments matching below = eg.
  3312.      *                                           'joinType' => 'INNER',
  3313.      *                                           'joinAs' => '...'
  3314.      *                                           'joinCol' => ....
  3315.      *                                           'useWhereAsOn' => false,
  3316.      *
  3317.      * @param    optional $joinAs    string     if you want to select the table as anther name
  3318.      *                                           useful when you want to select multiple columsn
  3319.      *                                           from a secondary table.
  3320.      
  3321.      * @param    optional $joinCol   string     The column on This objects table to match (needed
  3322.      *                                           if this table links to the child object in
  3323.      *                                           multiple places eg.
  3324.      *                                           user->friend (is a link to another user)
  3325.      *                                           user->mother (is a link to another user..)
  3326.      *
  3327.      *            optional 'useWhereAsOn' bool   default false;
  3328.      *                                           convert the where argments from the object being added
  3329.      *                                           into ON arguments.
  3330.      * 
  3331.      * 
  3332.      * @return   none 
  3333.      * @access   public
  3334.      * @author   Stijn de Reede      <sjr@gmx.co.uk>
  3335.      */
  3336.     function joinAdd($obj = false$joinType='INNER'$joinAs=false$joinCol=false)
  3337.     {
  3338.         global $_DB_DATAOBJECT;
  3339.         if ($obj === false{
  3340.             $this->_join = '';
  3341.             return;
  3342.         }
  3343.          
  3344.         //echo '<PRE>'; print_r(func_get_args());
  3345.         $useWhereAsOn = false;
  3346.         // support for 2nd argument as an array of options
  3347.         if (is_array($joinType)) {
  3348.             // new options can now go in here... (dont forget to document them)
  3349.             $useWhereAsOn !empty($joinType['useWhereAsOn']);
  3350.             $joinCol      = isset($joinType['joinCol'])  $joinType['joinCol']  $joinCol;
  3351.             $joinAs       = isset($joinType['joinAs'])   $joinType['joinAs']   $joinAs;
  3352.             $joinType     = isset($joinType['joinType']$joinType['joinType''INNER';
  3353.         }
  3354.         // support for array as first argument 
  3355.         // this assumes that you dont have a links.ini for the specified table.
  3356.         // and it doesnt exist as am extended dataobject!! - experimental.
  3357.         
  3358.         $ofield = false; // object field
  3359.         $tfield = false; // this field
  3360.         $toTable = false;
  3361.         if (is_array($obj)) {
  3362.             $tfield $obj[0];
  3363.             
  3364.             if (count($obj== 3{
  3365.                 $ofield $obj[2];
  3366.                 $obj $obj[1];
  3367.             else {
  3368.                 list($toTable,$ofieldexplode(':',$obj[1]);
  3369.             
  3370.                 $obj is_string($toTable? DB_DataObject::factory($toTable$toTable;
  3371.             
  3372.                 if (!$obj || !is_object($obj|| is_a($obj,'PEAR_Error')) {
  3373.                     $obj = new DB_DataObject;
  3374.                     $obj->__table = $toTable;
  3375.                 }
  3376.                 $obj->_connect();
  3377.             }
  3378.             // set the table items to nothing.. - eg. do not try and match
  3379.             // things in the child table...???
  3380.             $items = array();
  3381.         }
  3382.         
  3383.         if (!is_object($obj|| !is_a($obj,'DB_DataObject')) {
  3384.             return $this->raiseError("joinAdd: called without an object"DB_DATAOBJECT_ERROR_NODATA,PEAR_ERROR_DIE);
  3385.         }
  3386.         /*  make sure $this->_database is set.  */
  3387.         $this->_connect();
  3388.         $DB $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  3389.        
  3390.  
  3391.         /// CHANGED 26 JUN 2009 - we prefer links from our local table over the remote one.
  3392.         
  3393.         /* otherwise see if there are any links from this table to the obj. */
  3394.         //print_r($this->links());
  3395.         if (($ofield === false&& ($links $this->links())) {
  3396.             // this enables for support for arrays of links in ini file.
  3397.             // link contains this_column[] =  linked_table:linked_column
  3398.             // or standard way.
  3399.             // link contains this_column =  linked_table:linked_column
  3400.             foreach ($links as $k => $linkVar{
  3401.             
  3402.                 if (!is_array($linkVar)) {
  3403.                     $linkVar  = array($linkVar);
  3404.                 }
  3405.                 foreach($linkVar as $v{
  3406.  
  3407.                     
  3408.                     
  3409.                     /* link contains {this column} = {linked table}:{linked column} */
  3410.                     $ar explode(':'$v);
  3411.                     // Feature Request #4266 - Allow joins with multiple keys
  3412.                     if (strpos($k','!== false{
  3413.                         $k explode(','$k);
  3414.                     }
  3415.                     if (strpos($ar[1]','!== false{
  3416.                         $ar[1explode(','$ar[1]);
  3417.                     }
  3418.  
  3419.                     if ($ar[0!= $obj->tableName()) {
  3420.                         continue;
  3421.                     }
  3422.                     if ($joinCol !== false{
  3423.                         if ($k == $joinCol{
  3424.                             // got it!?
  3425.                             $tfield $k;
  3426.                             $ofield $ar[1];
  3427.                             break;
  3428.                         
  3429.                         continue;
  3430.                         
  3431.                     
  3432.                     $tfield $k;
  3433.                     $ofield $ar[1];
  3434.                     break;
  3435.                         
  3436.                 }
  3437.             }
  3438.         }
  3439.          /* look up the links for obj table */
  3440.         //print_r($obj->links());
  3441.         if (!$ofield && ($olinks $obj->links())) {
  3442.             
  3443.             foreach ($olinks as $k => $linkVar{
  3444.                 /* link contains {this column} = array ( {linked table}:{linked column} )*/
  3445.                 if (!is_array($linkVar)) {
  3446.                     $linkVar  = array($linkVar);
  3447.                 }
  3448.                 foreach($linkVar as $v{
  3449.                     
  3450.                     /* link contains {this column} = {linked table}:{linked column} */
  3451.                     $ar explode(':'$v);
  3452.                     
  3453.                     // Feature Request #4266 - Allow joins with multiple keys
  3454.                     $links_key_array strpos($k,',');
  3455.                     if ($links_key_array !== false{
  3456.                         $k explode(','$k);
  3457.                     }
  3458.                     
  3459.                     $ar_array strpos($ar[1],',');
  3460.                     if ($ar_array !== false{
  3461.                         $ar[1explode(','$ar[1]);
  3462.                     }
  3463.                  
  3464.                     if ($ar[0!= $this->tableName()) {
  3465.                         continue;
  3466.                     }
  3467.                     
  3468.                     // you have explictly specified the column
  3469.                     // and the col is listed here..
  3470.                     // not sure if 1:1 table could cause probs here..
  3471.                     
  3472.                     if ($joinCol !== false{
  3473.                         $this->raiseError
  3474.                             "joinAdd: You cannot target a join column in the " .
  3475.                             "'link from' table ({$obj->__table}). " . 
  3476.                             "Either remove the fourth argument to joinAdd() ".
  3477.                             "({$joinCol}), or alter your links.ini file.",
  3478.                             DB_DATAOBJECT_ERROR_NODATA);
  3479.                         return false;
  3480.                     }
  3481.                 
  3482.                     $ofield $k;
  3483.                     $tfield $ar[1];
  3484.                     break;
  3485.                     
  3486.                 }
  3487.             }
  3488.         }
  3489.  
  3490.         // finally if these two table have column names that match do a join by default on them
  3491.  
  3492.         if (($ofield === false&& $joinCol{
  3493.             $ofield $joinCol;
  3494.             $tfield $joinCol;
  3495.  
  3496.         }
  3497.         /* did I find a conneciton between them? */
  3498.  
  3499.         if ($ofield === false{
  3500.             $this->raiseError(
  3501.                 "joinAdd: {$obj->tableName()} has no link with {$this->tableName()}",
  3502.                 DB_DATAOBJECT_ERROR_NODATA);
  3503.             return false;
  3504.         }
  3505.         $joinType strtoupper($joinType);
  3506.         
  3507.         // we default to joining as the same name (this is remvoed later..)
  3508.         
  3509.         if ($joinAs === false{
  3510.             $joinAs $obj->tableName();
  3511.         }
  3512.         
  3513.         $quoteIdentifiers !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
  3514.         $options $_DB_DATAOBJECT['CONFIG'];
  3515.         
  3516.         // not sure  how portable adding database prefixes is..
  3517.         $objTable $quoteIdentifiers 
  3518.                 $DB->quoteIdentifier($obj->tableName()) 
  3519.                  $obj->tableName(;
  3520.                 
  3521.         $dbPrefix  '';
  3522.         if (strlen($obj->_database&& in_array($DB->dsn['phptype'],array('mysql','mysqli'))) {
  3523.             $dbPrefix ($quoteIdentifiers
  3524.                          ? $DB->quoteIdentifier($obj->_database)
  3525.                          : $obj->_database'.';    
  3526.         }
  3527.         
  3528.         // if they are the same, then dont add a prefix...                
  3529.         if ($obj->_database == $this->_database{
  3530.            $dbPrefix '';
  3531.         }
  3532.         // as far as we know only mysql supports database prefixes..
  3533.         // prefixing the database name is now the default behaviour,
  3534.         // as it enables joining mutiple columns from multiple databases...
  3535.          
  3536.             // prefix database (quoted if neccessary..)
  3537.         $objTable $dbPrefix $objTable;
  3538.        
  3539.         $cond '';
  3540.  
  3541.         // if obj only a dataobject - eg. no extended class has been defined..
  3542.         // it obvioulsy cant work out what child elements might exist...
  3543.         // until we get on the fly querying of tables..
  3544.         // note: we have already checked that it is_a(db_dataobject earlier)
  3545.         if strtolower(get_class($obj)) != 'db_dataobject'{
  3546.                  
  3547.             // now add where conditions for anything that is set in the object 
  3548.         
  3549.         
  3550.         
  3551.             $items $obj->table();
  3552.             // will return an array if no items..
  3553.             
  3554.             // only fail if we where expecting it to work (eg. not joined on a array)
  3555.              
  3556.             if (!$items{
  3557.                 $this->raiseError(
  3558.                     "joinAdd: No table definition for {$obj->__table}"
  3559.                     DB_DATAOBJECT_ERROR_INVALIDCONFIG);
  3560.                 return false;
  3561.             }
  3562.             
  3563.             $ignore_null !isset($options['disable_null_strings'])
  3564.                     || !is_string($options['disable_null_strings'])
  3565.                     || strtolower($options['disable_null_strings']!== 'full' ;
  3566.             
  3567.  
  3568.             foreach($items as $k => $v{
  3569.                 if (!isset($obj->$k&& $ignore_null{
  3570.                     continue;
  3571.                 }
  3572.                 
  3573.                 $kSql ($quoteIdentifiers $DB->quoteIdentifier($k$k);
  3574.                 
  3575.                 if (DB_DataObject::_is_null($obj,$k)) {
  3576.                     $obj->whereAdd("{$joinAs}.{$kSql} IS NULL");
  3577.                     continue;
  3578.                 }
  3579.                 
  3580.                 if ($v DB_DATAOBJECT_STR{
  3581.                     $obj->whereAdd("{$joinAs}.{$kSql} = " . $this->_quote((string) (
  3582.                             ($v DB_DATAOBJECT_BOOL
  3583.                                 // this is thanks to the braindead idea of postgres to 
  3584.                                 // use t/f for boolean.
  3585.                                 (($obj->$k === 'f'? 0 : (int)(bool) $obj->$k:  
  3586.                                 $obj->$k
  3587.                         )));
  3588.                     continue;
  3589.                 }
  3590.                 if (is_numeric($obj->$k)) {
  3591.                     $obj->whereAdd("{$joinAs}.{$kSql} = {$obj->$k}");
  3592.                     continue;
  3593.                 }
  3594.                             
  3595.                 if (is_object($obj->$k&& is_a($obj->$k,'DB_DataObject_Cast')) {
  3596.                     $value $obj->$k->toString($v,$DB);
  3597.                     if (PEAR::isError($value)) {
  3598.                         $this->raiseError($value->getMessage(,DB_DATAOBJECT_ERROR_INVALIDARG);
  3599.                         return false;
  3600.                     
  3601.                     $obj->whereAdd("{$joinAs}.{$kSql} = $value");
  3602.                     continue;
  3603.                 }
  3604.                 
  3605.                 
  3606.                 /* this is probably an error condition! */
  3607.                 $obj->whereAdd("{$joinAs}.{$kSql} = 0");
  3608.             }
  3609.             if ($this->_query === false{
  3610.                 $this->raiseError(
  3611.                     "joinAdd can not be run from a object that has had a query run on it,
  3612.                     clone the object or create a new one and use setFrom()"
  3613.                     DB_DATAOBJECT_ERROR_INVALIDARGS);
  3614.                 return false;
  3615.             }
  3616.         }
  3617.  
  3618.         // and finally merge the whereAdd from the child..
  3619.         if ($obj->_query['condition']{
  3620.             $cond preg_replace('/^\sWHERE/i','',$obj->_query['condition']);
  3621.  
  3622.             if (!$useWhereAsOn{
  3623.                 $this->whereAdd($cond);
  3624.             }
  3625.         }
  3626.     
  3627.         
  3628.         
  3629.         
  3630.         // nested (join of joined objects..)
  3631.         $appendJoin '';
  3632.         if ($obj->_join{
  3633.             // postgres allows nested queries, with ()'s
  3634.             // not sure what the results are with other databases..
  3635.             // may be unpredictable..
  3636.             if (in_array($DB->dsn["phptype"],array('pgsql'))) {
  3637.                 $objTable = "($objTable {$obj->_join})";
  3638.             else {
  3639.                 $appendJoin $obj->_join;
  3640.             }
  3641.         }
  3642.         
  3643.   
  3644.         // fix for #2216
  3645.         // add the joinee object's conditions to the ON clause instead of the WHERE clause
  3646.         if ($useWhereAsOn && strlen($cond)) {
  3647.             $appendJoin ' AND ' $cond ' ' $appendJoin;
  3648.         }
  3649.                
  3650.         
  3651.         
  3652.         $table $this->tableName();
  3653.         
  3654.         if ($quoteIdentifiers{
  3655.             $joinAs   $DB->quoteIdentifier($joinAs);
  3656.             $table    $DB->quoteIdentifier($table);     
  3657.             $ofield   (is_array($ofield)) array_map(array($DB'quoteIdentifier')$ofield$DB->quoteIdentifier($ofield);
  3658.             $tfield   (is_array($tfield)) array_map(array($DB'quoteIdentifier')$tfield$DB->quoteIdentifier($tfield)
  3659.         }
  3660.         // add database prefix if they are different databases
  3661.        
  3662.         
  3663.         $fullJoinAs '';
  3664.         $addJoinAs  ($quoteIdentifiers $DB->quoteIdentifier($obj->tableName()) $obj->tableName()) != $joinAs;
  3665.         if ($addJoinAs{
  3666.             // join table a AS b - is only supported by a few databases and is probably not needed
  3667.             // , however since it makes the whole Statement alot clearer we are leaving it in
  3668.             // for those databases.
  3669.             $fullJoinAs in_array($DB->dsn["phptype"],array('mysql','mysqli','pgsql')) ? "AS {$joinAs}" :  $joinAs;
  3670.         else {
  3671.             // if 
  3672.             $joinAs $dbPrefix $joinAs;
  3673.         }
  3674.         
  3675.         
  3676.         switch ($joinType{
  3677.             case 'INNER':
  3678.             case 'LEFT'
  3679.             case 'RIGHT'// others??? .. cross, left outer, right outer, natural..?
  3680.                 
  3681.                 // Feature Request #4266 - Allow joins with multiple keys
  3682.                 $jadd = "\n {$joinType} JOIN {$objTable} {$fullJoinAs}";
  3683.                 //$this->_join .= "\n {$joinType} JOIN {$objTable} {$fullJoinAs}";
  3684.                 if (is_array($ofield)) {
  3685.                     $key_count count($ofield);
  3686.                     for($i = 0; $i $key_count$i++{
  3687.                         if ($i == 0{
  3688.                             $jadd .= " ON ({$joinAs}.{$ofield[$i]}={$table}.{$tfield[$i]}";
  3689.                         }
  3690.                         else {
  3691.                             $jadd .= " AND {$joinAs}.{$ofield[$i]}={$table}.{$tfield[$i]} ";
  3692.                         }
  3693.                     }
  3694.                     $jadd .= ' ' $appendJoin ' ';
  3695.                 else {
  3696.                     $jadd .= " ON ({$joinAs}.{$ofield}={$table}.{$tfield}) {$appendJoin} ";
  3697.                 }
  3698.                 // jadd avaliable for debugging join build.
  3699.                 //echo $jadd ."\n";
  3700.                 $this->_join .= $jadd;
  3701.                 break;
  3702.                 
  3703.             case ''// this is just a standard multitable select..
  3704.                 $this->_join .= "\n , {$objTable} {$fullJoinAs} {$appendJoin}";
  3705.                 $this->whereAdd("{$joinAs}.{$ofield}={$table}.{$tfield}");
  3706.         }
  3707.          
  3708.          
  3709.         return true;
  3710.  
  3711.     }
  3712.  
  3713.     /**
  3714.      * autoJoin - using the links.ini file, it builds a query with all the joins
  3715.      * usage:
  3716.      * $x = DB_DataObject::factory('mytable');
  3717.      * $x->autoJoin();
  3718.      * $x->get(123);
  3719.      *   will result in all of the joined data being added to the fetched object..
  3720.      * 
  3721.      * $x = DB_DataObject::factory('mytable');
  3722.      * $x->autoJoin();
  3723.      * $ar = $x->fetchAll();
  3724.      *   will result in an array containing all the data from the table, and any joined tables..
  3725.      * 
  3726.      * $x = DB_DataObject::factory('mytable');
  3727.      * $jdata = $x->autoJoin();
  3728.      * $x->selectAdd(); //reset..
  3729.      * foreach($_REQUEST['requested_cols'] as $c) {
  3730.      *    if (!isset($jdata[$c])) continue; // ignore columns not available..
  3731.      *    $x->selectAdd( $jdata[$c] . ' as ' . $c);
  3732.      * }
  3733.      * $ar = $x->fetchAll();
  3734.      *   will result in only the columns requested being fetched...
  3735.      *
  3736.      *
  3737.      *
  3738.      * @param     array     Configuration
  3739.      *           exclude  Array of columns to exclude from results (eg. modified_by_id)
  3740.      *           links    The equivilant links.ini data for this table eg.
  3741.      *                     array( 'person_id' => 'person:id', .... )
  3742.      *           include  Array of columns to include
  3743.      *           distinct Array of distinct columns.
  3744.      *          
  3745.      * @return   array      info about joins
  3746.      *                       cols => map of resulting {joined_tablename}.{joined_table_column_name}
  3747.      *                       join_names => map of resulting {join_name_as}.{joined_table_column_name}
  3748.      *                       count => the column to count on.
  3749.      * @access   public
  3750.      */
  3751.     function autoJoin($cfg = array())
  3752.     {
  3753.         //var_Dump($cfg);exit;
  3754.         $pre_links $this->links();
  3755.         if (!empty($cfg['links'])) {
  3756.             $this->links(array_merge$pre_links $cfg['links']));
  3757.         }
  3758.         $map $this->links);
  3759.         
  3760.         
  3761.         //print_r($map);
  3762.         $tabdef $this->table();
  3763.          
  3764.         // we need this as normally it's only cleared by an empty selectAs call.
  3765.        
  3766.         
  3767.         $keys array_keys($tabdef);
  3768.         if (!empty($cfg['exclude'])) {
  3769.             $keys array_intersect($keysarray_diff($keys$cfg['exclude']))
  3770.         }
  3771.         if (!empty($cfg['include'])) {
  3772.             
  3773.             $keys =  array_intersect($keys,  $cfg['include'])
  3774.         }
  3775.         
  3776.         $selectAs = array();
  3777.         
  3778.         if (!empty($keys)) {
  3779.             $selectAs = array(array$keys '%s'false));
  3780.         }
  3781.         
  3782.         $ret = array(
  3783.             'cols' => array(),
  3784.             'join_names' => array(),
  3785.             'count' => false,
  3786.         );
  3787.         
  3788.         
  3789.         
  3790.         $has_distinct = false;
  3791.         if (!empty($cfg['distinct']&& $keys{
  3792.             
  3793.             // reset the columsn?
  3794.             $cols = array();
  3795.             
  3796.              //echo '<PRE>' ;print_r($xx);exit;
  3797.             foreach($keys as $c{
  3798.                 //var_dump($c);
  3799.                 
  3800.                 if (  $cfg['distinct'== $c{
  3801.                     $has_distinct 'DISTINCT( ' $this->tableName(.'.'$c .') as ' $c;
  3802.                     $ret['count'=  'DISTINCT  ' $this->tableName(.'.'$c .'';
  3803.                     continue;
  3804.                 }
  3805.                 // cols is in our filtered keys...
  3806.                 $cols $c;
  3807.                 
  3808.             }
  3809.             // apply our filtered version, which excludes the distinct column.
  3810.             
  3811.             $selectAs = empty($cols?  array(: array(array(  $cols '%s'false)) ;
  3812.             
  3813.             
  3814.             
  3815.         
  3816.                 
  3817.         foreach($keys as $k{
  3818.             $ret['cols'][$k$this->tableName()'.' $k;
  3819.         }
  3820.         
  3821.          
  3822.         
  3823.         foreach($map as $ocl=>$info{
  3824.             
  3825.             list($tab,$colexplode(':'$info);
  3826.             // what about multiple joins on the same table!!!
  3827.             $xx = DB_DataObject::factory($tab);
  3828.             if (!is_object($xx|| !is_a($xx'DB_DataObject')) {
  3829.                 continue;
  3830.             }
  3831.             // skip columns that are excluded.
  3832.             
  3833.             // we ignore include here... - as
  3834.              
  3835.             // this is borked ... for multiple jions..
  3836.             $this->joinAdd($xx'LEFT''join_'.$ocl.'_'$col$ocl);
  3837.             
  3838.             if (!empty($cfg['exclude']&& in_array($ocl$cfg['exclude'])) {
  3839.                 continue;
  3840.             }
  3841.             
  3842.             $tabdef $xx->table();
  3843.             $table $xx->tableName();
  3844.             
  3845.             $keys array_keys($tabdef);
  3846.             
  3847.             
  3848.             if (!empty($cfg['exclude'])) {
  3849.                 $keys array_intersect($keysarray_diff($keys$cfg['exclude']));
  3850.                 
  3851.                 foreach($keys as $k{
  3852.                     if (in_array($ocl.'_'.$k$cfg['exclude'])) {
  3853.                         $keys array_diff($keys$k)// removes the k..
  3854.                     }
  3855.                 }
  3856.                 
  3857.             }
  3858.             
  3859.             if (!empty($cfg['include'])) {
  3860.                 // include will basically be BASECOLNAME_joinedcolname
  3861.                 $nkeys = array();
  3862.                 foreach($keys as $k{
  3863.                     if (in_arraysprintf($ocl.'_%s'$k)$cfg['include'])) {
  3864.                         $nkeys[$k;
  3865.                     }
  3866.                 }
  3867.                 $keys $nkeys;
  3868.             }
  3869.             
  3870.             if (empty($keys)) {
  3871.                 continue;
  3872.             }
  3873.             // got distinct, and not yet found it..
  3874.             if (!$has_distinct && !empty($cfg['distinct']))  {
  3875.                 $cols = array();
  3876.                 foreach($keys as $c{
  3877.                     $tn sprintf($ocl.'_%s'$c);
  3878.                       
  3879.                     if $tn == $cfg['distinct']{
  3880.                         
  3881.                         $has_distinct 'DISTINCT( ' 'join_'.$ocl.'_'.$col.'.'.$c .')  as ' $tn ;
  3882.                         $ret['count'=  'DISTINCT  join_'.$ocl.'_'.$col.'.'.$c;
  3883.                        // var_dump($this->countWhat );
  3884.                         continue;
  3885.                     }
  3886.                     $cols[$c;
  3887.                      
  3888.                 }
  3889.                 
  3890.                 if (!empty($cols)) {
  3891.                     $selectAs[= array($cols$ocl.'_%s''join_'.$ocl.'_'$col);
  3892.                 }
  3893.                 
  3894.             else {
  3895.                 $selectAs[= array($keys$ocl.'_%s''join_'.$ocl.'_'$col);
  3896.             }
  3897.               
  3898.             foreach($keys as $k{
  3899.                 $ret['cols'][sprintf('%s_%s'$ocl$k)$tab.'.'.$k;
  3900.                 $ret['join_names'][sprintf('%s_%s'$ocl$k)sprintf('join_%s_%s.%s',$ocl$col$k);
  3901.             }
  3902.              
  3903.         }
  3904.         
  3905.         // fill in the select details..
  3906.         $this->selectAdd()
  3907.         
  3908.         if ($has_distinct{
  3909.             $this->selectAdd($has_distinct);
  3910.         }
  3911.          
  3912.         foreach($selectAs as $ar{
  3913.             
  3914.             $this->selectAs($ar[0]$ar[1]$ar[2]);
  3915.         }
  3916.         // restore links..
  3917.         $this->links$pre_links );
  3918.         
  3919.         return $ret;
  3920.         
  3921.     }
  3922.     
  3923.     /**
  3924.      * Factory method for calling DB_DataObject_Cast
  3925.      *
  3926.      * if used with 1 argument DB_DataObject_Cast::sql($value) is called
  3927.      * 
  3928.      * if used with 2 arguments DB_DataObject_Cast::$value($callvalue) is called
  3929.      * valid first arguments are: blob, string, date, sql
  3930.      * 
  3931.      * eg. $member->updated = $member->sqlValue('NOW()');
  3932.      * 
  3933.      * 
  3934.      * might handle more arguments for escaping later...
  3935.      * 
  3936.      *
  3937.      * @param string $value (or type if used with 2 arguments)
  3938.      * @param string $callvalue (optional) used with date/null etc..
  3939.      */
  3940.     
  3941.     function sqlValue($value)
  3942.     {
  3943.         $method 'sql';
  3944.         if (func_num_args(== 2{
  3945.             $method $value;
  3946.             $value func_get_arg(1);
  3947.         }
  3948.         require_once 'DB/DataObject/Cast.php';
  3949.         return call_user_func(array('DB_DataObject_Cast'$method)$value);
  3950.         
  3951.     }
  3952.     
  3953.     
  3954.     /**
  3955.      * Copies items that are in the table definitions from an
  3956.      * array or object into the current object
  3957.      * will not override key values.
  3958.      *
  3959.      *
  3960.      * @param    array | object  $from
  3961.      * @param    string  $format eg. map xxxx_name to $object->name using 'xxxx_%s' (defaults to %s - eg. name -> $object->name
  3962.      * @param    boolean  $skipEmpty (dont assign empty values if a column is empty (eg. '' / 0 etc...)
  3963.      * @access   public
  3964.      * @return   true on success or array of key=>setValue error message
  3965.      */
  3966.     function setFrom($from$format '%s'$skipEmpty=false)
  3967.     {
  3968.         global $_DB_DATAOBJECT;
  3969.         $keys  $this->keys();
  3970.         $items $this->table();
  3971.         if (!$items{
  3972.             $this->raiseError(
  3973.                 "setFrom:Could not find table definition for {$this->tableName()}"
  3974.                 DB_DATAOBJECT_ERROR_INVALIDCONFIG);
  3975.             return;
  3976.         }
  3977.         $overload_return = array();
  3978.         foreach (array_keys($itemsas $k{
  3979.             if (in_array($k,$keys)) {
  3980.                 continue; // dont overwrite keys
  3981.             }
  3982.             if (!$k{
  3983.                 continue; // ignore empty keys!!! what
  3984.             }
  3985.             
  3986.             $chk is_object($from&&  
  3987.                 (version_compare(phpversion()"5.1.0" ">="
  3988.                     property_exists($fromsprintf($format,$k)) :  // php5.1
  3989.                     array_key_existssprintf($format,$k)get_class_vars($from)) //older
  3990.                 );
  3991.             // if from has property ($format($k)      
  3992.             if ($chk{
  3993.                 $kk (strtolower($k== 'from''_from' $k;
  3994.                 if (method_exists($this,'set'.$kk)) {
  3995.                     $ret $this->{'set'.$kk}($from->{sprintf($format,$k)});
  3996.                     if (is_string($ret)) {
  3997.                         $overload_return[$k$ret;
  3998.                     }
  3999.                     continue;
  4000.                 }
  4001.                 $this->$k $from->{sprintf($format,$k)};
  4002.                 continue;
  4003.             }
  4004.             
  4005.             if (is_object($from)) {
  4006.                 continue;
  4007.             }
  4008.             
  4009.             if (empty($from[sprintf($format,$k)]&& $skipEmpty{
  4010.                 continue;
  4011.             }
  4012.             
  4013.             if (!isset($from[sprintf($format,$k)]&& !DB_DataObject::_is_null($fromsprintf($format,$k))) {
  4014.                 continue;
  4015.             }
  4016.            
  4017.             $kk (strtolower($k== 'from''_from' $k;
  4018.             if (method_exists($this,'set'$kk)) {
  4019.                 $ret =  $this->{'set'.$kk}($from[sprintf($format,$k)]);
  4020.                 if (is_string($ret)) {
  4021.                     $overload_return[$k$ret;
  4022.                 }
  4023.                 continue;
  4024.             }
  4025.             $val $from[sprintf($format,$k)];
  4026.             if (is_a($val'DB_DataObject_Cast')) {
  4027.                 $this->$k $val;
  4028.                 continue;
  4029.             }
  4030.             if (is_object($val|| is_array($val)) {
  4031.                 continue;
  4032.             }
  4033.             $ret $this->fromValue($k,$val);
  4034.             if ($ret !== true)  {
  4035.                 $overload_return[$k'Not A Valid Value';
  4036.             }
  4037.             //$this->$k = $from[sprintf($format,$k)];
  4038.         }
  4039.         if ($overload_return{
  4040.             return $overload_return;
  4041.         }
  4042.         return true;
  4043.     }
  4044.  
  4045.     /**
  4046.      * Returns an associative array from the current data
  4047.      * (kind of oblivates the idea behind DataObjects, but
  4048.      * is usefull if you use it with things like QuickForms.
  4049.      *
  4050.      * you can use the format to return things like user[key]
  4051.      * by sending it $object->toArray('user[%s]')
  4052.      *
  4053.      * will also return links converted to arrays.
  4054.      *
  4055.      * @param   string  sprintf format for array
  4056.      * @param   bool||number   [true = elemnts that have a value set],
  4057.      *                           [false = table + returned colums] ,
  4058.      *                           [0 = returned columsn only]
  4059.      *
  4060.      * @access   public
  4061.      * @return   array of key => value for row
  4062.      */
  4063.  
  4064.     function toArray($format '%s'$hideEmpty = false
  4065.     {
  4066.         global $_DB_DATAOBJECT;
  4067.         
  4068.         // we use false to ignore sprintf.. (speed up..)
  4069.         $format $format == '%s' ? false : $format;
  4070.         
  4071.         $ret = array();
  4072.         $rf ($this->_resultFields !== false$this->_resultFields : 
  4073.                 (isset($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid]?
  4074.                  $_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid: false);
  4075.         
  4076.         $ar ($rf !== false?
  4077.             (($hideEmpty === 0$rf array_merge($rf$this->table())) :
  4078.             $this->table();
  4079.  
  4080.         foreach($ar as $k=>$v{
  4081.              
  4082.             if (!isset($this->$k)) {
  4083.                 if (!$hideEmpty{
  4084.                     $ret[$format === false ? $k sprintf($format,$k)'';
  4085.                 }
  4086.                 continue;
  4087.             }
  4088.             // call the overloaded getXXXX() method. - except getLink and getLinks
  4089.             if (method_exists($this,'get'.$k&& !in_array(strtolower($k),array('links','link'))) {
  4090.                 $ret[$format === false ? $k sprintf($format,$k)$this->{'get'.$k}();
  4091.                 continue;
  4092.             }
  4093.             // should this call toValue() ???
  4094.             $ret[$format === false ? $k sprintf($format,$k)$this->$k;
  4095.         }
  4096.         if (!$this->_link_loaded{
  4097.             return $ret;
  4098.         }
  4099.         foreach($this->_link_loaded as $k{
  4100.             $ret[$format === false ? $k sprintf($format,$k)$this->$k->toArray();
  4101.         
  4102.         }
  4103.         
  4104.         return $ret;
  4105.     }
  4106.  
  4107.     /**
  4108.      * validate the values of the object (usually prior to inserting/updating..)
  4109.      *
  4110.      * Note: This was always intended as a simple validation routine.
  4111.      * It lacks understanding of field length, whether you are inserting or updating (and hence null key values)
  4112.      *
  4113.      * This should be moved to another class: DB_DataObject_Validate
  4114.      *      FEEL FREE TO SEND ME YOUR VERSION FOR CONSIDERATION!!!
  4115.      *
  4116.      * Usage:
  4117.      * if (is_array($ret = $obj->validate())) { ... there are problems with the data ... }
  4118.      *
  4119.      * Logic:
  4120.      *   - defaults to only testing strings/numbers if numbers or strings are the correct type and null values are correct
  4121.      *   - validate Column methods : "validate{ROWNAME}()"  are called if they are defined.
  4122.      *            These methods should return
  4123.      *                  true = everything ok
  4124.      *                  false|object = something is wrong!
  4125.      * 
  4126.      *   - This method loads and uses the PEAR Validate Class.
  4127.      *
  4128.      *
  4129.      * @access  public
  4130.      * @return  array of validation results (where key=>value, value=false|object if it failed) or true (if they all succeeded)
  4131.      */
  4132.     function validate()
  4133.     {
  4134.         global $_DB_DATAOBJECT;
  4135.         require_once 'Validate.php';
  4136.         $table $this->table();
  4137.         $ret   = array();
  4138.         $seq   $this->sequenceKey();
  4139.         $options $_DB_DATAOBJECT['CONFIG'];
  4140.         foreach($table as $key => $val{
  4141.             
  4142.             
  4143.             // call user defined validation always...
  4144.             $method "Validate" ucfirst($key);
  4145.             if (method_exists($this$method)) {
  4146.                 $ret[$key$this->$method();
  4147.                 continue;
  4148.             }
  4149.             
  4150.             // if not null - and it's not set.......
  4151.             
  4152.             if ($val DB_DATAOBJECT_NOTNULL && DB_DataObject::_is_null($this$key)) {
  4153.                 // dont check empty sequence key values..
  4154.                 if (($key == $seq[0]&& ($seq[1== true)) {
  4155.                     continue;
  4156.                 }
  4157.                 $ret[$key= false;
  4158.                 continue;
  4159.             }
  4160.             
  4161.             
  4162.              if (DB_DataObject::_is_null($this$key)) {
  4163.                 if ($val DB_DATAOBJECT_NOTNULL{
  4164.                     $this->debug("'null' field used for '$key', but it is defined as NOT NULL"'VALIDATION'4);
  4165.                     $ret[$key= false;
  4166.                     continue;
  4167.                 }
  4168.                 continue;
  4169.             }
  4170.  
  4171.             // ignore things that are not set. ?
  4172.            
  4173.             if (!isset($this->$key)) {
  4174.                 continue;
  4175.             }
  4176.             
  4177.             // if the string is empty.. assume it is ok..
  4178.             if (!is_object($this->$key&& !is_array($this->$key&& !strlen((string) $this->$key)) {
  4179.                 continue;
  4180.             }
  4181.             
  4182.             // dont try and validate cast objects - assume they are problably ok..
  4183.             if (is_object($this->$key&& is_a($this->$key,'DB_DataObject_Cast')) {
  4184.                 continue;
  4185.             }
  4186.             // at this point if you have set something to an object, and it's not expected
  4187.             // the Validate will probably break!!... - rightly so! (your design is broken, 
  4188.             // so issuing a runtime error like PEAR_Error is probably not appropriate..
  4189.             
  4190.             switch (true{
  4191.                 // todo: date time.....
  4192.                 case  ($val DB_DATAOBJECT_STR):
  4193.                     $ret[$key= Validate::string($this->$keyVALIDATE_PUNCTUATION . VALIDATE_NAME);
  4194.                     continue;
  4195.                 case  ($val DB_DATAOBJECT_INT):
  4196.                     $ret[$key= Validate::number($this->$keyarray('decimal'=>'.'));
  4197.                     continue;
  4198.             }
  4199.         }
  4200.         // if any of the results are false or an object (eg. PEAR_Error).. then return the array..
  4201.         foreach ($ret as $key => $val{
  4202.             if ($val !== true{
  4203.                 return $ret;
  4204.             }
  4205.         }
  4206.         return true; // everything is OK.
  4207.     }
  4208.  
  4209.     /**
  4210.      * Gets the DB object related to an object - so you can use funky peardb stuf with it :)
  4211.      *
  4212.      * @access public
  4213.      * @return object The DB connection
  4214.      */
  4215.     function getDatabaseConnection()
  4216.     {
  4217.         global $_DB_DATAOBJECT;
  4218.  
  4219.         if (($e $this->_connect()) !== true{
  4220.             return $e;
  4221.         }
  4222.         if (!isset($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
  4223.             $r = false;
  4224.             return $r;
  4225.         }
  4226.         return $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  4227.     }
  4228.  
  4229.  
  4230.     /**
  4231.      * Gets the DB result object related to the objects active query
  4232.      *  - so you can use funky pear stuff with it - like pager for example.. :)
  4233.      *
  4234.      * @access public
  4235.      * @return object The DB result object
  4236.      */
  4237.      
  4238.     function getDatabaseResult()
  4239.     {
  4240.         global $_DB_DATAOBJECT;
  4241.         $this->_connect();
  4242.         if (!isset($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid])) {
  4243.             $r = false;
  4244.             return $r;
  4245.         }
  4246.         return $_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid];
  4247.     }
  4248.  
  4249.     /**
  4250.      * Overload Extension support
  4251.      *  - enables setCOLNAME/getCOLNAME
  4252.      *  if you define a set/get method for the item it will be called.
  4253.      * otherwise it will just return/set the value.
  4254.      * NOTE this currently means that a few Names are NO-NO's
  4255.      * eg. links,link,linksarray, from, Databaseconnection,databaseresult
  4256.      *
  4257.      * note
  4258.      *  - set is automatically called by setFrom.
  4259.      *   - get is automatically called by toArray()
  4260.      *  
  4261.      * setters return true on success. = strings on failure
  4262.      * getters return the value!
  4263.      *
  4264.      * this fires off trigger_error - if any problems.. pear_error,
  4265.      * has problems with 4.3.2RC2 here
  4266.      *
  4267.      * @access public
  4268.      * @return true? 
  4269.      * @see overload
  4270.      */
  4271.  
  4272.     
  4273.     function _call($method,$params,&$return{
  4274.         
  4275.         //$this->debug("ATTEMPTING OVERLOAD? $method");
  4276.         // ignore constructors : - mm
  4277.         if (strtolower($method== strtolower(get_class($this))) {
  4278.             return true;
  4279.         }
  4280.         $type strtolower(substr($method,0,3));
  4281.         $class get_class($this);
  4282.         if (($type != 'set'&& ($type != 'get')) {
  4283.             return false;
  4284.         }
  4285.          
  4286.         
  4287.         
  4288.         // deal with naming conflick of setFrom = this is messy ATM!
  4289.         
  4290.         if (strtolower($method== 'set_from'{
  4291.             $return $this->toValue('from',isset($params[0]$params[0: null);
  4292.             return  true;
  4293.         }
  4294.         
  4295.         $element substr($method,3);
  4296.         
  4297.         // dont you just love php's case insensitivity!!!!
  4298.         
  4299.         $array =  array_keys(get_class_vars($class));
  4300.         /* php5 version which segfaults on 5.0.3 */
  4301.         if (class_exists('ReflectionClass')) {
  4302.             $reflection = new ReflectionClass($class);
  4303.             $array array_keys($reflection->getdefaultProperties());
  4304.         }
  4305.         
  4306.         if (!in_array($element,$array)) {
  4307.             // munge case
  4308.             foreach($array as $k{
  4309.                 $case[strtolower($k)$k;
  4310.             }
  4311.             if ((substr(phpversion(),0,1== 5&& isset($case[strtolower($element)])) {
  4312.                 trigger_error("PHP5 set/get calls should match the case of the variable",E_USER_WARNING);
  4313.                 $element strtolower($element);
  4314.             }
  4315.             
  4316.             // does it really exist?
  4317.             if (!isset($case[$element])) {
  4318.                 return false;            
  4319.             }
  4320.             // use the mundged case
  4321.             $element $case[$element]// real case !
  4322.         }
  4323.         
  4324.         
  4325.         if ($type == 'get'{
  4326.             $return $this->toValue($element,isset($params[0]$params[0: null);
  4327.             return true;
  4328.         }
  4329.         
  4330.         
  4331.         $return $this->fromValue($element$params[0]);
  4332.          
  4333.         return true;
  4334.             
  4335.           
  4336.     }
  4337.         
  4338.     
  4339.     /**
  4340.     * standard set* implementation.
  4341.     *
  4342.     * takes data and uses it to set dates/strings etc.
  4343.     * normally called from __call..
  4344.     *
  4345.     * Current supports
  4346.     *   date      = using (standard time format, or unixtimestamp).... so you could create a method :
  4347.     *               function setLastread($string) { $this->fromValue('lastread',strtotime($string)); }
  4348.     *
  4349.     *   time      = using strtotime
  4350.     *   datetime  = using  same as date - accepts iso standard or unixtimestamp.
  4351.     *   string    = typecast only..
  4352.     * 
  4353.     * TODO: add formater:: eg. d/m/Y for date! ???
  4354.     *
  4355.     * @param   string       column of database
  4356.     * @param   mixed        value to assign
  4357.     *
  4358.     * @return   true|false     (False on error)
  4359.     * @access   public
  4360.     * @see      DB_DataObject::_call
  4361.     */
  4362.   
  4363.     
  4364.     function fromValue($col,$value
  4365.     {
  4366.         global $_DB_DATAOBJECT;
  4367.         $options $_DB_DATAOBJECT['CONFIG'];
  4368.         $cols $this->table();
  4369.         // dont know anything about this col..
  4370.         if (!isset($cols[$col]|| is_a($value'DB_DataObject_Cast')) {
  4371.             $this->$col $value;
  4372.             return true;
  4373.         }
  4374.         //echo "FROM VALUE $col, {$cols[$col]}, $value\n";
  4375.         switch (true{
  4376.             // set to null and column is can be null...
  4377.             case ((!($cols[$colDB_DATAOBJECT_NOTNULL)) && DB_DataObject::_is_null($valuefalse)):
  4378.             case (is_object($value&& is_a($value,'DB_DataObject_Cast'))
  4379.                 $this->$col $value;
  4380.                 return true;
  4381.                 
  4382.             // fail on setting null on a not null field..
  4383.             case (($cols[$colDB_DATAOBJECT_NOTNULL&& DB_DataObject::_is_null($value,false)):
  4384.  
  4385.                 return false;
  4386.         
  4387.             case (($cols[$colDB_DATAOBJECT_DATE&&  ($cols[$colDB_DATAOBJECT_TIME)):
  4388.                 // empty values get set to '' (which is inserted/updated as NULl
  4389.                 if (!$value{
  4390.                     $this->$col '';
  4391.                 }
  4392.             
  4393.                 if (is_numeric($value)) {
  4394.                     $this->$col date('Y-m-d H:i:s'$value);
  4395.                     return true;
  4396.                 }
  4397.               
  4398.                 // eak... - no way to validate date time otherwise...
  4399.                 $this->$col = (string) $value;
  4400.                 return true;
  4401.             
  4402.             case ($cols[$colDB_DATAOBJECT_DATE):
  4403.                 // empty values get set to '' (which is inserted/updated as NULl
  4404.                  
  4405.                 if (!$value{
  4406.                     $this->$col '';
  4407.                     return true; 
  4408.                 }
  4409.             
  4410.                 if (is_numeric($value)) {
  4411.                     $this->$col date('Y-m-d',$value);
  4412.                     return true;
  4413.                 }
  4414.                  
  4415.                 // try date!!!!
  4416.                 require_once 'Date.php';
  4417.                 $x = new Date($value);
  4418.                 $this->$col $x->format("%Y-%m-%d");
  4419.                 return true;
  4420.             
  4421.             case ($cols[$colDB_DATAOBJECT_TIME):
  4422.                 // empty values get set to '' (which is inserted/updated as NULl
  4423.                 if (!$value{
  4424.                     $this->$col '';
  4425.                 }
  4426.             
  4427.                 $guess strtotime($value);
  4428.                 if ($guess != -1{
  4429.                      $this->$col date('H:i:s'$guess);
  4430.                     return $return = true;
  4431.                 }
  4432.                 // otherwise an error in type...
  4433.                 return false;
  4434.             
  4435.             case ($cols[$colDB_DATAOBJECT_STR):
  4436.                 
  4437.                 $this->$col = (string) $value;
  4438.                 return true;
  4439.                 
  4440.             // todo : floats numerics and ints...
  4441.             default:
  4442.                 $this->$col $value;
  4443.                 return true;
  4444.         }
  4445.     
  4446.     
  4447.     
  4448.     }
  4449.      /**
  4450.     * standard get* implementation.
  4451.     *
  4452.     *  with formaters..
  4453.     * supported formaters:
  4454.     *   date/time : %d/%m/%Y (eg. php strftime) or pear::Date
  4455.     *   numbers   : %02d (eg. sprintf)
  4456.     *  NOTE you will get unexpected results with times like 0000-00-00 !!!
  4457.     *
  4458.     *
  4459.     * 
  4460.     * @param   string       column of database
  4461.     * @param   format       foramt
  4462.     *
  4463.     * @return   true     Description
  4464.     * @access   public
  4465.     * @see      DB_DataObject::_call(),strftime(),Date::format()
  4466.     */
  4467.     function toValue($col,$format = null
  4468.     {
  4469.         if (is_null($format)) {
  4470.             return $this->$col;
  4471.         }
  4472.         $cols $this->table();
  4473.         switch (true{
  4474.             case (($cols[$colDB_DATAOBJECT_DATE&&  ($cols[$colDB_DATAOBJECT_TIME)):
  4475.                 if (!$this->$col{
  4476.                     return '';
  4477.                 }
  4478.                 $guess strtotime($this->$col);
  4479.                 if ($guess != -1{
  4480.                     return strftime($format$guess);
  4481.                 }
  4482.                 // eak... - no way to validate date time otherwise...
  4483.                 return $this->$col;
  4484.             case ($cols[$colDB_DATAOBJECT_DATE):
  4485.                 if (!$this->$col{
  4486.                     return '';
  4487.                 
  4488.                 $guess strtotime($this->$col);
  4489.                 if ($guess != -1{
  4490.                     return strftime($format,$guess);
  4491.                 }
  4492.                 // try date!!!!
  4493.                 require_once 'Date.php';
  4494.                 $x = new Date($this->$col);
  4495.                 return $x->format($format);
  4496.                 
  4497.             case ($cols[$colDB_DATAOBJECT_TIME):
  4498.                 if (!$this->$col{
  4499.                     return '';
  4500.                 }
  4501.                 $guess strtotime($this->$col);
  4502.                 if ($guess > -1{
  4503.                     return strftime($format$guess);
  4504.                 }
  4505.                 // otherwise an error in type...
  4506.                 return $this->$col;
  4507.                 
  4508.             case ($cols[$col&  DB_DATAOBJECT_MYSQLTIMESTAMP):
  4509.                 if (!$this->$col{
  4510.                     return '';
  4511.                 }
  4512.                 require_once 'Date.php';
  4513.                 
  4514.                 $x = new Date($this->$col);
  4515.                 
  4516.                 return $x->format($format);
  4517.             
  4518.              
  4519.             case ($cols[$col&  DB_DATAOBJECT_BOOL):
  4520.                 
  4521.                 if ($cols[$col&  DB_DATAOBJECT_STR{
  4522.                     // it's a 't'/'f' !
  4523.                     return ($this->$col === 't');
  4524.                 }
  4525.                 return (bool) $this->$col;
  4526.             
  4527.                
  4528.             default:
  4529.                 return sprintf($format,$this->col);
  4530.         }
  4531.             
  4532.  
  4533.     }
  4534.     
  4535.     
  4536.     /* ----------------------- Debugger ------------------ */
  4537.  
  4538.     /**
  4539.      * Debugger. - use this in your extended classes to output debugging information.
  4540.      *
  4541.      * Uses DB_DataObject::DebugLevel(x) to turn it on
  4542.      *
  4543.      * @param    string $message - message to output
  4544.      * @param    string $logtype - bold at start
  4545.      * @param    string $level   - output level
  4546.      * @access   public
  4547.      * @return   none 
  4548.      */
  4549.     function debug($message$logtype = 0$level = 1)
  4550.     {
  4551.         global $_DB_DATAOBJECT;
  4552.  
  4553.         if (empty($_DB_DATAOBJECT['CONFIG']['debug'])  || 
  4554.             (is_numeric($_DB_DATAOBJECT['CONFIG']['debug']&&  $_DB_DATAOBJECT['CONFIG']['debug'$level)) {
  4555.             return;
  4556.         }
  4557.         // this is a bit flaky due to php's wonderfull class passing around crap..
  4558.