Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

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