MDL-47200 WebServices: Add core_files_get_files to the mob app service
[moodle.git] / lib / adodb / adodb-active-recordx.inc.php
1 <?php
2 /*
4 @version V5.06 29 Sept 2008   (c) 2000-2012 John Lim (jlim#natsoft.com). All rights reserved.
5   Latest version is available at http://adodb.sourceforge.net
6  
7   Released under both BSD license and Lesser GPL library license. 
8   Whenever there is any discrepancy between the two licenses, 
9   the BSD license will take precedence.
10   
11   Active Record implementation. Superset of Zend Framework's.
12   
13   This is "Active Record eXtended" to support JOIN, WORK and LAZY mode by Chris Ravenscroft  chris#voilaweb.com 
14   
15   Version 0.9
16   
17   See http://www-128.ibm.com/developerworks/java/library/j-cb03076/?ca=dgr-lnxw01ActiveRecord 
18         for info on Ruby on Rails Active Record implementation
19 */
22         // CFR: Active Records Definitions
23 define('ADODB_JOIN_AR', 0x01);
24 define('ADODB_WORK_AR', 0x02);
25 define('ADODB_LAZY_AR', 0x03);
28 global $_ADODB_ACTIVE_DBS;
29 global $ADODB_ACTIVE_CACHESECS; // set to true to enable caching of metadata such as field info
30 global $ACTIVE_RECORD_SAFETY; // set to false to disable safety checks
31 global $ADODB_ACTIVE_DEFVALS; // use default values of table definition when creating new active record.
33 // array of ADODB_Active_DB's, indexed by ADODB_Active_Record->_dbat
34 $_ADODB_ACTIVE_DBS = array();
35 $ACTIVE_RECORD_SAFETY = true; // CFR: disabled while playing with relations
36 $ADODB_ACTIVE_DEFVALS = false;
38 class ADODB_Active_DB {
39         var $db; // ADOConnection
40         var $tables; // assoc array of ADODB_Active_Table objects, indexed by tablename
41 }
43 class ADODB_Active_Table {
44         var $name; // table name
45         var $flds; // assoc array of adofieldobjs, indexed by fieldname
46         var $keys; // assoc array of primary keys, indexed by fieldname
47         var $_created; // only used when stored as a cached file
48         var $_belongsTo = array();
49         var $_hasMany = array();
50         var $_colsCount; // total columns count, including relations
52         function updateColsCount()
53         {
54                 $this->_colsCount = sizeof($this->flds);
55                 foreach($this->_belongsTo as $foreignTable)
56                         $this->_colsCount += sizeof($foreignTable->TableInfo()->flds);
57                 foreach($this->_hasMany as $foreignTable)
58                         $this->_colsCount += sizeof($foreignTable->TableInfo()->flds);
59         }
60 }
62 // returns index into $_ADODB_ACTIVE_DBS
63 function ADODB_SetDatabaseAdapter(&$db)
64 {
65         global $_ADODB_ACTIVE_DBS;
66         
67                 foreach($_ADODB_ACTIVE_DBS as $k => $d) {
68                         if (PHP_VERSION >= 5) {
69                                 if ($d->db === $db) return $k;
70                         } else {
71                                 if ($d->db->_connectionID === $db->_connectionID && $db->database == $d->db->database) 
72                                         return $k;
73                         }
74                 }
75                 
76                 $obj = new ADODB_Active_DB();
77                 $obj->db = $db;
78                 $obj->tables = array();
79                 
80                 $_ADODB_ACTIVE_DBS[] = $obj;
81                 
82                 return sizeof($_ADODB_ACTIVE_DBS)-1;
83 }
86 class ADODB_Active_Record {
87         static $_changeNames = true; // dynamically pluralize table names
88         static $_foreignSuffix = '_id'; // 
89         var $_dbat; // associative index pointing to ADODB_Active_DB eg. $ADODB_Active_DBS[_dbat]
90         var $_table; // tablename, if set in class definition then use it as table name
91         var $_sTable; // singularized table name
92         var $_pTable; // pluralized table name
93         var $_tableat; // associative index pointing to ADODB_Active_Table, eg $ADODB_Active_DBS[_dbat]->tables[$this->_tableat]
94         var $_where; // where clause set in Load()
95         var $_saved = false; // indicates whether data is already inserted.
96         var $_lasterr = false; // last error message
97         var $_original = false; // the original values loaded or inserted, refreshed on update
99         var $foreignName; // CFR: class name when in a relationship
101         static function UseDefaultValues($bool=null)
102         {
103         global $ADODB_ACTIVE_DEFVALS;
104                 if (isset($bool)) $ADODB_ACTIVE_DEFVALS = $bool;
105                 return $ADODB_ACTIVE_DEFVALS;
106         }
108         // should be static
109         static function SetDatabaseAdapter(&$db) 
110         {
111                 return ADODB_SetDatabaseAdapter($db);
112         }
113         
114         
115         public function __set($name, $value)
116         {
117                 $name = str_replace(' ', '_', $name);
118                 $this->$name = $value;
119         }
120         
121         // php5 constructor
122         // Note: if $table is defined, then we will use it as our table name
123         // Otherwise we will use our classname...
124         // In our database, table names are pluralized (because there can be
125         // more than one row!)
126         // Similarly, if $table is defined here, it has to be plural form.
127         //
128         // $options is an array that allows us to tweak the constructor's behaviour
129         // if $options['refresh'] is true, we re-scan our metadata information
130         // if $options['new'] is true, we forget all relations
131         function __construct($table = false, $pkeyarr=false, $db=false, $options=array())
132         {
133         global $ADODB_ASSOC_CASE,$_ADODB_ACTIVE_DBS;
134         
135                 if ($db == false && is_object($pkeyarr)) {
136                         $db = $pkeyarr;
137                         $pkeyarr = false;
138                 }
139                 
140                 if($table)
141                 {
142                         // table argument exists. It is expected to be
143                         // already plural form.
144                         $this->_pTable = $table;
145                         $this->_sTable = $this->_singularize($this->_pTable);
146                 }
147                 else
148                 {
149                         // We will use current classname as table name.
150                         // We need to pluralize it for the real table name.
151                         $this->_sTable = strtolower(get_class($this));
152                         $this->_pTable = $this->_pluralize($this->_sTable);
153                 }
154                 $this->_table = &$this->_pTable;
156                 $this->foreignName = $this->_sTable; // CFR: default foreign name (singular)
158                 if ($db) {
159                         $this->_dbat = ADODB_Active_Record::SetDatabaseAdapter($db);
160                 } else
161                         $this->_dbat = sizeof($_ADODB_ACTIVE_DBS)-1;
162                 
163                 
164                 if ($this->_dbat < 0) $this->Error("No database connection set; use ADOdb_Active_Record::SetDatabaseAdapter(\$db)",'ADODB_Active_Record::__constructor');
165                 
166                 $this->_tableat = $this->_table; # reserved for setting the assoc value to a non-table name, eg. the sql string in future
168                 // CFR: Just added this option because UpdateActiveTable() can refresh its information
169                 // but there was no way to ask it to do that.
170                 $forceUpdate = (isset($options['refresh']) && true === $options['refresh']);
171                 $this->UpdateActiveTable($pkeyarr, $forceUpdate);
172                 if(isset($options['new']) && true === $options['new'])
173                 {
174                         $table =& $this->TableInfo();
175                         unset($table->_hasMany);
176                         unset($table->_belongsTo);
177                         $table->_hasMany = array();
178                         $table->_belongsTo = array();
179                 }
180         }
181         
182         function __wakeup()
183         {
184                 $class = get_class($this);
185                 new $class;
186         }
187         
188         // CFR: Constants found in Rails
189         static $IrregularP = array(
190                 'PERSON'    => 'people',
191                 'MAN'       => 'men',
192                 'WOMAN'     => 'women',
193                 'CHILD'     => 'children',
194                 'COW'       => 'kine',
195         );
197         static $IrregularS = array(
198                 'PEOPLE'    => 'PERSON',
199                 'MEN'       => 'man',
200                 'WOMEN'     => 'woman',
201                 'CHILDREN'  => 'child',
202                 'KINE'      => 'cow',
203         );
205         static $WeIsI = array(
206                 'EQUIPMENT' => true,
207                 'INFORMATION'   => true,
208                 'RICE'      => true,
209                 'MONEY'     => true,
210                 'SPECIES'   => true,
211                 'SERIES'    => true,
212                 'FISH'      => true,
213                 'SHEEP'     => true,
214         );
216         function _pluralize($table)
217         {
218                 if (!ADODB_Active_Record::$_changeNames) return $table;
220                 $ut = strtoupper($table);
221                 if(isset(self::$WeIsI[$ut]))
222                 {
223                         return $table;
224                 }
225                 if(isset(self::$IrregularP[$ut]))
226                 {
227                         return self::$IrregularP[$ut];
228                 }
229                 $len = strlen($table);
230                 $lastc = $ut[$len-1];
231                 $lastc2 = substr($ut,$len-2);
232                 switch ($lastc) {
233                 case 'S':
234                         return $table.'es';     
235                 case 'Y':
236                         return substr($table,0,$len-1).'ies';
237                 case 'X':       
238                         return $table.'es';
239                 case 'H': 
240                         if ($lastc2 == 'CH' || $lastc2 == 'SH')
241                                 return $table.'es';
242                 default:
243                         return $table.'s';
244                 }
245         }
246         
247         // CFR Lamest singular inflector ever - @todo Make it real!
248         // Note: There is an assumption here...and it is that the argument's length >= 4
249         function _singularize($table)
250         {
251         
252                 if (!ADODB_Active_Record::$_changeNames) return $table;
253         
254                 $ut = strtoupper($table);
255                 if(isset(self::$WeIsI[$ut]))
256                 {
257                         return $table;
258                 }
259                 if(isset(self::$IrregularS[$ut]))
260                 {
261                         return self::$IrregularS[$ut];
262                 }
263                 $len = strlen($table);
264                 if($ut[$len-1] != 'S')
265                         return $table; // I know...forget oxen
266                 if($ut[$len-2] != 'E')
267                         return substr($table, 0, $len-1);
268                 switch($ut[$len-3])
269                 {
270                         case 'S':
271                         case 'X':
272                                 return substr($table, 0, $len-2);
273                         case 'I':
274                                 return substr($table, 0, $len-3) . 'y';
275                         case 'H';
276                                 if($ut[$len-4] == 'C' || $ut[$len-4] == 'S')
277                                         return substr($table, 0, $len-2);
278                         default:
279                                 return substr($table, 0, $len-1); // ?
280                 }
281         }
283         /*
284          * ar->foreignName will contain the name of the tables associated with this table because
285          * these other tables' rows may also be referenced by this table using theirname_id or the provided
286          * foreign keys (this index name is stored in ar->foreignKey)
287          *
288          * this-table.id = other-table-#1.this-table_id
289          *               = other-table-#2.this-table_id
290          */
291         function hasMany($foreignRef,$foreignKey=false)
292         {
293                 $ar = new ADODB_Active_Record($foreignRef);
294                 $ar->foreignName = $foreignRef;
295                 $ar->UpdateActiveTable();
296                 $ar->foreignKey = ($foreignKey) ? $foreignKey : strtolower(get_class($this)) . self::$_foreignSuffix;
298                 $table =& $this->TableInfo();
299                 if(!isset($table->_hasMany[$foreignRef]))
300                 {
301                         $table->_hasMany[$foreignRef] = $ar;
302                         $table->updateColsCount();
303                 }
304 # @todo Can I make this guy be lazy?
305                 $this->$foreignRef = $table->_hasMany[$foreignRef]; // WATCHME Removed assignment by ref. to please __get()
306         }
308         /**
309          * ar->foreignName will contain the name of the tables associated with this table because
310          * this table's rows may also be referenced by those tables using thistable_id or the provided
311          * foreign keys (this index name is stored in ar->foreignKey)
312          *
313          * this-table.other-table_id = other-table.id
314          */
315         function belongsTo($foreignRef,$foreignKey=false)
316         {
317                 global $inflector;
319                 $ar = new ADODB_Active_Record($this->_pluralize($foreignRef));
320                 $ar->foreignName = $foreignRef;
321                 $ar->UpdateActiveTable();
322                 $ar->foreignKey = ($foreignKey) ? $foreignKey : $ar->foreignName . self::$_foreignSuffix;
323                 
324                 $table =& $this->TableInfo();
325                 if(!isset($table->_belongsTo[$foreignRef]))
326                 {
327                         $table->_belongsTo[$foreignRef] = $ar;
328                         $table->updateColsCount();
329                 }
330                 $this->$foreignRef = $table->_belongsTo[$foreignRef];
331         }
333         /**
334          * __get Access properties - used for lazy loading
335          * 
336          * @param mixed $name 
337          * @access protected
338          * @return void
339          */
340         function __get($name)
341         {
342                 return $this->LoadRelations($name, '', -1. -1);
343         }
345         function LoadRelations($name, $whereOrderBy, $offset=-1, $limit=-1)
346         {
347                 $extras = array();
348                 if($offset >= 0) $extras['offset'] = $offset;
349                 if($limit >= 0) $extras['limit'] = $limit;
350                 $table =& $this->TableInfo();
351                 
352                 if (strlen($whereOrderBy)) 
353                         if (!preg_match('/^[ \n\r]*AND/i',$whereOrderBy))
354                                 if (!preg_match('/^[ \n\r]*ORDER[ \n\r]/i',$whereOrderBy))
355                                         $whereOrderBy = 'AND '.$whereOrderBy;
356                                         
357                 if(!empty($table->_belongsTo[$name]))
358                 {
359                         $obj = $table->_belongsTo[$name];
360                         $columnName = $obj->foreignKey;
361                         if(empty($this->$columnName))
362                                 $this->$name = null;
363                         else
364                         {
365                                 if(($k = reset($obj->TableInfo()->keys)))
366                                         $belongsToId = $k;
367                                 else
368                                         $belongsToId = 'id';
369                                 
370                                 $arrayOfOne =
371                                         $obj->Find(
372                                                 $belongsToId.'='.$this->$columnName.' '.$whereOrderBy, false, false, $extras);
373                                 $this->$name = $arrayOfOne[0];
374                         }
375                         return $this->$name;
376                 }
377                 if(!empty($table->_hasMany[$name]))
378                 {
379                         $obj = $table->_hasMany[$name];
380                         if(($k = reset($table->keys)))
381                                 $hasManyId   = $k;
382                         else
383                                 $hasManyId   = 'id';                    
385                         $this->$name =
386                                 $obj->Find(
387                                         $obj->foreignKey.'='.$this->$hasManyId.' '.$whereOrderBy, false, false, $extras);
388                         return $this->$name;
389                 }
390         }
391         //////////////////////////////////
392         
393         // update metadata
394         function UpdateActiveTable($pkeys=false,$forceUpdate=false)
395         {
396         global $ADODB_ASSOC_CASE,$_ADODB_ACTIVE_DBS , $ADODB_CACHE_DIR, $ADODB_ACTIVE_CACHESECS;
397         global $ADODB_ACTIVE_DEFVALS, $ADODB_FETCH_MODE;
399                 $activedb = $_ADODB_ACTIVE_DBS[$this->_dbat];
401                 $table = $this->_table;
402                 $tables = $activedb->tables;
403                 $tableat = $this->_tableat;
404                 if (!$forceUpdate && !empty($tables[$tableat])) {
406                         $tobj = $tables[$tableat];
407                         foreach($tobj->flds as $name => $fld) {
408                         if ($ADODB_ACTIVE_DEFVALS && isset($fld->default_value)) 
409                                 $this->$name = $fld->default_value;
410                         else
411                                 $this->$name = null;
412                         }
413                         return;
414                 }
415                 
416                 $db = $activedb->db;
417                 $fname = $ADODB_CACHE_DIR . '/adodb_' . $db->databaseType . '_active_'. $table . '.cache';
418                 if (!$forceUpdate && $ADODB_ACTIVE_CACHESECS && $ADODB_CACHE_DIR && file_exists($fname)) {
419                         $fp = fopen($fname,'r');
420                         @flock($fp, LOCK_SH);
421                         $acttab = unserialize(fread($fp,100000));
422                         fclose($fp);
423                         if ($acttab->_created + $ADODB_ACTIVE_CACHESECS - (abs(rand()) % 16) > time()) { 
424                                 // abs(rand()) randomizes deletion, reducing contention to delete/refresh file
425                                 // ideally, you should cache at least 32 secs
426                                 $activedb->tables[$table] = $acttab;
427                                 
428                                 //if ($db->debug) ADOConnection::outp("Reading cached active record file: $fname");
429                                 return;
430                         } else if ($db->debug) {
431                                 ADOConnection::outp("Refreshing cached active record file: $fname");
432                         }
433                 }
434                 $activetab = new ADODB_Active_Table();
435                 $activetab->name = $table;
436                 
437                 $save = $ADODB_FETCH_MODE;
438                 $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC;
439                 if ($db->fetchMode !== false) $savem = $db->SetFetchMode(false);
440                 
441                 $cols = $db->MetaColumns($table);
442                 
443                 if (isset($savem)) $db->SetFetchMode($savem);
444                 $ADODB_FETCH_MODE = $save;
445                 
446                 if (!$cols) {
447                         $this->Error("Invalid table name: $table",'UpdateActiveTable'); 
448                         return false;
449                 }
450                 $fld = reset($cols);
451                 if (!$pkeys) {
452                         if (isset($fld->primary_key)) {
453                                 $pkeys = array();
454                                 foreach($cols as $name => $fld) {
455                                         if (!empty($fld->primary_key)) $pkeys[] = $name;
456                                 }
457                         } else  
458                                 $pkeys = $this->GetPrimaryKeys($db, $table);
459                 }
460                 if (empty($pkeys)) {
461                         $this->Error("No primary key found for table $table",'UpdateActiveTable');
462                         return false;
463                 }
464                 
465                 $attr = array();
466                 $keys = array();
467                 
468                 switch($ADODB_ASSOC_CASE) {
469                 case 0:
470                         foreach($cols as $name => $fldobj) {
471                                 $name = strtolower($name);
472                 if ($ADODB_ACTIVE_DEFVALS && isset($fldobj->default_value))
473                     $this->$name = $fldobj->default_value;
474                 else
475                                         $this->$name = null;
476                                 $attr[$name] = $fldobj;
477                         }
478                         foreach($pkeys as $k => $name) {
479                                 $keys[strtolower($name)] = strtolower($name);
480                         }
481                         break;
482                         
483                 case 1: 
484                         foreach($cols as $name => $fldobj) {
485                                 $name = strtoupper($name);
486                
487                 if ($ADODB_ACTIVE_DEFVALS && isset($fldobj->default_value))
488                     $this->$name = $fldobj->default_value;
489                 else
490                                         $this->$name = null;
491                                 $attr[$name] = $fldobj;
492                         }
493                         
494                         foreach($pkeys as $k => $name) {
495                                 $keys[strtoupper($name)] = strtoupper($name);
496                         }
497                         break;
498                 default:
499                         foreach($cols as $name => $fldobj) {
500                                 $name = ($fldobj->name);
501                 
502                 if ($ADODB_ACTIVE_DEFVALS && isset($fldobj->default_value))
503                     $this->$name = $fldobj->default_value;
504                 else
505                                         $this->$name = null;
506                                 $attr[$name] = $fldobj;
507                         }
508                         foreach($pkeys as $k => $name) {
509                                 $keys[$name] = $cols[$name]->name;
510                         }
511                         break;
512                 }
513                 
514                 $activetab->keys = $keys;
515                 $activetab->flds = $attr;
516                 $activetab->updateColsCount();
518                 if ($ADODB_ACTIVE_CACHESECS && $ADODB_CACHE_DIR) {
519                         $activetab->_created = time();
520                         $s = serialize($activetab);
521                         if (!function_exists('adodb_write_file')) include(ADODB_DIR.'/adodb-csvlib.inc.php');
522                         adodb_write_file($fname,$s);
523                 }
524                 if (isset($activedb->tables[$table])) {
525                         $oldtab = $activedb->tables[$table];
526                 
527                         if ($oldtab) $activetab->_belongsTo = $oldtab->_belongsTo;
528                         if ($oldtab) $activetab->_hasMany = $oldtab->_hasMany;
529                 }
530                 $activedb->tables[$table] = $activetab;
531         }
532         
533         function GetPrimaryKeys(&$db, $table)
534         {
535                 return $db->MetaPrimaryKeys($table);
536         }
537         
538         // error handler for both PHP4+5. 
539         function Error($err,$fn)
540         {
541         global $_ADODB_ACTIVE_DBS;
542         
543                 $fn = get_class($this).'::'.$fn;
544                 $this->_lasterr = $fn.': '.$err;
545                 
546                 if ($this->_dbat < 0) $db = false;
547                 else {
548                         $activedb = $_ADODB_ACTIVE_DBS[$this->_dbat];
549                         $db = $activedb->db;
550                 }
551                 
552                 if (function_exists('adodb_throw')) {   
553                         if (!$db) adodb_throw('ADOdb_Active_Record', $fn, -1, $err, 0, 0, false);
554                         else adodb_throw($db->databaseType, $fn, -1, $err, 0, 0, $db);
555                 } else
556                         if (!$db || $db->debug) ADOConnection::outp($this->_lasterr);
557                 
558         }
559         
560         // return last error message
561         function ErrorMsg()
562         {
563                 if (!function_exists('adodb_throw')) {
564                         if ($this->_dbat < 0) $db = false;
565                         else $db = $this->DB();
566                 
567                         // last error could be database error too
568                         if ($db && $db->ErrorMsg()) return $db->ErrorMsg();
569                 }
570                 return $this->_lasterr;
571         }
572         
573         function ErrorNo() 
574         {
575                 if ($this->_dbat < 0) return -9999; // no database connection...
576                 $db = $this->DB();
577                 
578                 return (int) $db->ErrorNo();
579         }
582         // retrieve ADOConnection from _ADODB_Active_DBs
583         function DB()
584         {
585         global $_ADODB_ACTIVE_DBS;
586         
587                 if ($this->_dbat < 0) {
588                         $false = false;
589                         $this->Error("No database connection set: use ADOdb_Active_Record::SetDatabaseAdaptor(\$db)", "DB");
590                         return $false;
591                 }
592                 $activedb = $_ADODB_ACTIVE_DBS[$this->_dbat];
593                 $db = $activedb->db;
594                 return $db;
595         }
596         
597         // retrieve ADODB_Active_Table
598         function &TableInfo()
599         {
600         global $_ADODB_ACTIVE_DBS;
601         
602                 $activedb = $_ADODB_ACTIVE_DBS[$this->_dbat];
603                 $table = $activedb->tables[$this->_tableat];
604                 return $table;
605         }
606         
607         
608         // I have an ON INSERT trigger on a table that sets other columns in the table.
609         // So, I find that for myTable, I want to reload an active record after saving it. -- Malcolm Cook
610         function Reload()
611         {
612                 $db =& $this->DB(); if (!$db) return false;
613                 $table =& $this->TableInfo();
614                 $where = $this->GenWhere($db, $table);
615                 return($this->Load($where));
616         }
618         
619         // set a numeric array (using natural table field ordering) as object properties
620         function Set(&$row)
621         {
622         global $ACTIVE_RECORD_SAFETY;
623         
624                 $db = $this->DB();
625                 
626                 if (!$row) {
627                         $this->_saved = false;          
628                         return false;
629                 }
630                 
631                 $this->_saved = true;
632                 
633                 $table = $this->TableInfo();
634                 $sizeofFlds = sizeof($table->flds);
635                 $sizeofRow  = sizeof($row);
636                 if ($ACTIVE_RECORD_SAFETY && $table->_colsCount != $sizeofRow && $sizeofFlds != $sizeofRow) {
637             # <AP>
638             $bad_size = TRUE;
639         if($sizeofRow == 2 * $table->_colsCount || $sizeofRow == 2 * $sizeofFlds) {
640                 // Only keep string keys
641                 $keys = array_filter(array_keys($row), 'is_string');
642                 if (sizeof($keys) == sizeof($table->flds))
643                     $bad_size = FALSE;
644             }
645             if ($bad_size) {
646                         $this->Error("Table structure of $this->_table has changed","Load");
647                         return false;
648                 }
649             # </AP>
650                 }
651         else
652                 $keys = array_keys($row);
653         # <AP>
654         reset($keys);
655         $this->_original = array();
656                 foreach($table->flds as $name=>$fld)
657                 {
658             $value = $row[current($keys)];
659                         $this->$name = $value;
660             $this->_original[] = $value;
661             if(!next($keys)) break;
662                 }
663                 $table =& $this->TableInfo();
664                 foreach($table->_belongsTo as $foreignTable)
665                 {
666                         $ft = $foreignTable->TableInfo();
667                         $propertyName = $ft->name;
668                         foreach($ft->flds as $name=>$fld)
669                         {
670                                 $value = $row[current($keys)];
671                                 $foreignTable->$name = $value;
672                                 $foreignTable->_original[] = $value;
673                                 if(!next($keys)) break;
674                         }
675                 }
676                 foreach($table->_hasMany as $foreignTable)
677                 {
678                         $ft = $foreignTable->TableInfo();
679                         foreach($ft->flds as $name=>$fld)
680                         {
681                                 $value = $row[current($keys)];
682                                 $foreignTable->$name = $value;
683                                 $foreignTable->_original[] = $value;
684                                 if(!next($keys)) break;
685                         }
686                 }
687         # </AP>
688                 return true;
689         }
690         
691         // get last inserted id for INSERT
692         function LastInsertID(&$db,$fieldname)
693         {
694                 if ($db->hasInsertID)
695                         $val = $db->Insert_ID($this->_table,$fieldname);
696                 else
697                         $val = false;
698                         
699                 if (is_null($val) || $val === false) {
700                         // this might not work reliably in multi-user environment
701                         return $db->GetOne("select max(".$fieldname.") from ".$this->_table);
702                 }
703                 return $val;
704         }
705         
706         // quote data in where clause
707         function doquote(&$db, $val,$t)
708         {
709                 switch($t) {
710                 case 'D':
711                 case 'T':
712                         if (empty($val)) return 'null';
713                         
714                 case 'C':
715                 case 'X':
716                         if (is_null($val)) return 'null';
717                         
718                         if (strlen($val)>0 && 
719                                 (strncmp($val,"'",1) != 0 || substr($val,strlen($val)-1,1) != "'")) { 
720                                 return $db->qstr($val);
721                                 break;
722                         }
723                 default:
724                         return $val;
725                         break;
726                 }
727         }
728         
729         // generate where clause for an UPDATE/SELECT
730         function GenWhere(&$db, &$table)
731         {
732                 $keys = $table->keys;
733                 $parr = array();
734                 
735                 foreach($keys as $k) {
736                         $f = $table->flds[$k];
737                         if ($f) {
738                                 $parr[] = $k.' = '.$this->doquote($db,$this->$k,$db->MetaType($f->type));
739                         }
740                 }
741                 return implode(' and ', $parr);
742         }
743         
744         
745         //------------------------------------------------------------ Public functions below
746         
747         function Load($where=null,$bindarr=false)
748         {
749                 $db = $this->DB(); if (!$db) return false;
750                 $this->_where = $where;
751                 
752                 $save = $db->SetFetchMode(ADODB_FETCH_NUM);
753                 $qry = "select * from ".$this->_table;
754                 $table =& $this->TableInfo();
756                 if(($k = reset($table->keys)))
757                         $hasManyId   = $k;
758                 else
759                         $hasManyId   = 'id';
760                 
761                 foreach($table->_belongsTo as $foreignTable)
762                 {
763                         if(($k = reset($foreignTable->TableInfo()->keys)))
764                         {
765                                 $belongsToId = $k;
766                         }
767                         else
768                         {
769                                 $belongsToId = 'id';
770                         }
771                         $qry .= ' LEFT JOIN '.$foreignTable->_table.' ON '.
772                                 $this->_table.'.'.$foreignTable->foreignKey.'='.
773                                 $foreignTable->_table.'.'.$belongsToId;
774                 }
775                 foreach($table->_hasMany as $foreignTable)
776                 {
777                         $qry .= ' LEFT JOIN '.$foreignTable->_table.' ON '.
778                                 $this->_table.'.'.$hasManyId.'='.
779                                 $foreignTable->_table.'.'.$foreignTable->foreignKey;
780                 }
781                 if($where)
782                         $qry .= ' WHERE '.$where;
783                 
784                 // Simple case: no relations. Load row and return.
785                 if((count($table->_hasMany) + count($table->_belongsTo)) < 1)
786                 {
787                         $row = $db->GetRow($qry,$bindarr);
788                         if(!$row)
789                                 return false;
790                         $db->SetFetchMode($save);
791                         return $this->Set($row);
792                 }
793                 
794                 // More complex case when relations have to be collated
795                 $rows = $db->GetAll($qry,$bindarr);
796                 if(!$rows)
797                         return false;
798                 $db->SetFetchMode($save);
799                 if(count($rows) < 1)
800                         return false;
801                 $class = get_class($this);
802                 $isFirstRow = true;
803                 
804                 if(($k = reset($this->TableInfo()->keys)))
805                         $myId   = $k;
806                 else
807                         $myId   = 'id';
808                 $index = 0; $found = false;
809                 /** @todo Improve by storing once and for all in table metadata */
810                 /** @todo Also re-use info for hasManyId */
811                 foreach($this->TableInfo()->flds as $fld)
812                 {
813                         if($fld->name == $myId)
814                         {
815                                 $found = true;
816                                 break;
817                         }
818                         $index++;
819                 }
820                 if(!$found)
821                         $this->outp_throw("Unable to locate key $myId for $class in Load()",'Load');
822                 
823                 foreach($rows as $row)
824                 {
825                         $rowId = intval($row[$index]);
826                         if($rowId > 0)
827                         {
828                                 if($isFirstRow)
829                                 {
830                                         $isFirstRow = false;
831                                         if(!$this->Set($row))
832                                                 return false;
833                                 }
834                                 $obj = new $class($table,false,$db);
835                                 $obj->Set($row);
836                                 // TODO Copy/paste code below: bad!
837                                 if(count($table->_hasMany) > 0)
838                                 {
839                                         foreach($table->_hasMany as $foreignTable)
840                                         {
841                                                 $foreignName = $foreignTable->foreignName;
842                                                 if(!empty($obj->$foreignName))
843                                                 {
844                                                         if(!is_array($this->$foreignName))
845                                                         {
846                                                                 $foreignObj = $this->$foreignName;
847                                                                 $this->$foreignName = array(clone($foreignObj));
848                                                         }
849                                                         else
850                                                         {
851                                                                 $foreignObj = $obj->$foreignName;
852                                                                 array_push($this->$foreignName, clone($foreignObj));
853                                                         }
854                                                 }
855                                         }
856                                 }
857                                 if(count($table->_belongsTo) > 0)
858                                 {
859                                         foreach($table->_belongsTo as $foreignTable)
860                                         {
861                                                 $foreignName = $foreignTable->foreignName;
862                                                 if(!empty($obj->$foreignName))
863                                                 {
864                                                         if(!is_array($this->$foreignName))
865                                                         {
866                                                                 $foreignObj = $this->$foreignName;
867                                                                 $this->$foreignName = array(clone($foreignObj));
868                                                         }
869                                                         else
870                                                         {
871                                                                 $foreignObj = $obj->$foreignName;
872                                                                 array_push($this->$foreignName, clone($foreignObj));
873                                                         }
874                                                 }
875                                         }
876                                 }                               
877                         }
878                 }
879                 return true;
880         }
881         
882         // false on error
883         function Save()
884         {
885                 if ($this->_saved) $ok = $this->Update();
886                 else $ok = $this->Insert();
887                 
888                 return $ok;
889         }
890         
891         // CFR: Sometimes we may wish to consider that an object is not to be replaced but inserted.
892         // Sample use case: an 'undo' command object (after a delete())
893         function Dirty()
894         {
895                 $this->_saved = false;
896         }
898         // false on error
899         function Insert()
900         {
901                 $db = $this->DB(); if (!$db) return false;
902                 $cnt = 0;
903                 $table = $this->TableInfo();
904                 
905                 $valarr = array();
906                 $names = array();
907                 $valstr = array();
909                 foreach($table->flds as $name=>$fld) {
910                         $val = $this->$name;
911                         if(!is_null($val) || !array_key_exists($name, $table->keys)) {
912                                 $valarr[] = $val;
913                                 $names[] = $name;
914                                 $valstr[] = $db->Param($cnt);
915                                 $cnt += 1;
916                         }
917                 }
918                 
919                 if (empty($names)){
920                         foreach($table->flds as $name=>$fld) {
921                                 $valarr[] = null;
922                                 $names[] = $name;
923                                 $valstr[] = $db->Param($cnt);
924                                 $cnt += 1;
925                         }
926                 }
927                 $sql = 'INSERT INTO '.$this->_table."(".implode(',',$names).') VALUES ('.implode(',',$valstr).')';
928                 $ok = $db->Execute($sql,$valarr);
929                 
930                 if ($ok) {
931                         $this->_saved = true;
932                         $autoinc = false;
933                         foreach($table->keys as $k) {
934                                 if (is_null($this->$k)) {
935                                         $autoinc = true;
936                                         break;
937                                 }
938                         }
939                         if ($autoinc && sizeof($table->keys) == 1) {
940                                 $k = reset($table->keys);
941                                 $this->$k = $this->LastInsertID($db,$k);
942                         }
943                 }
944                 
945                 $this->_original = $valarr;
946                 return !empty($ok);
947         }
948         
949         function Delete()
950         {
951                 $db = $this->DB(); if (!$db) return false;
952                 $table = $this->TableInfo();
953                 
954                 $where = $this->GenWhere($db,$table);
955                 $sql = 'DELETE FROM '.$this->_table.' WHERE '.$where;
956                 $ok = $db->Execute($sql);
957                 
958                 return $ok ? true : false;
959         }
960         
961         // returns an array of active record objects
962         function Find($whereOrderBy,$bindarr=false,$pkeysArr=false,$extra=array())
963         {
964                 $db = $this->DB(); if (!$db || empty($this->_table)) return false;
965                 $table =& $this->TableInfo();
966                 $arr = $db->GetActiveRecordsClass(get_class($this),$this, $whereOrderBy,$bindarr,$pkeysArr,$extra,
967                         array('foreignName'=>$this->foreignName, 'belongsTo'=>$table->_belongsTo, 'hasMany'=>$table->_hasMany));
968                 return $arr;
969         }
970         
971         // CFR: In introduced this method to ensure that inner workings are not disturbed by
972         // subclasses...for instance when GetActiveRecordsClass invokes Find()
973         // Why am I not invoking parent::Find?
974         // Shockingly because I want to preserve PHP4 compatibility.
975         function packageFind($whereOrderBy,$bindarr=false,$pkeysArr=false,$extra=array())
976         {
977                 $db = $this->DB(); if (!$db || empty($this->_table)) return false;
978                 $table =& $this->TableInfo();
979                 $arr = $db->GetActiveRecordsClass(get_class($this),$this, $whereOrderBy,$bindarr,$pkeysArr,$extra,
980                         array('foreignName'=>$this->foreignName, 'belongsTo'=>$table->_belongsTo, 'hasMany'=>$table->_hasMany));
981                 return $arr;
982         }
984         // returns 0 on error, 1 on update, 2 on insert
985         function Replace()
986         {
987         global $ADODB_ASSOC_CASE;
988                 
989                 $db = $this->DB(); if (!$db) return false;
990                 $table = $this->TableInfo();
991                 
992                 $pkey = $table->keys;
993                 
994                 foreach($table->flds as $name=>$fld) {
995                         $val = $this->$name;
996                         /*
997                         if (is_null($val)) {
998                                 if (isset($fld->not_null) && $fld->not_null) {
999                                         if (isset($fld->default_value) && strlen($fld->default_value)) continue;
1000                                         else {
1001                                                 $this->Error("Cannot update null into $name","Replace");
1002                                                 return false;
1003                                         }
1004                                 }
1005                         }*/
1006                         if (is_null($val) && !empty($fld->auto_increment)) {
1007                 continue;
1008             }
1009                         $t = $db->MetaType($fld->type);
1010                         $arr[$name] = $this->doquote($db,$val,$t);
1011                         $valarr[] = $val;
1012                 }
1013                 
1014                 if (!is_array($pkey)) $pkey = array($pkey);
1015                 
1016                 
1017                 if ($ADODB_ASSOC_CASE == 0) 
1018                         foreach($pkey as $k => $v)
1019                                 $pkey[$k] = strtolower($v);
1020                 elseif ($ADODB_ASSOC_CASE == 1) 
1021                         foreach($pkey as $k => $v)
1022                                 $pkey[$k] = strtoupper($v);
1023                                 
1024                 $ok = $db->Replace($this->_table,$arr,$pkey);
1025                 if ($ok) {
1026                         $this->_saved = true; // 1= update 2=insert
1027                         if ($ok == 2) {
1028                                 $autoinc = false;
1029                                 foreach($table->keys as $k) {
1030                                         if (is_null($this->$k)) {
1031                                                 $autoinc = true;
1032                                                 break;
1033                                         }
1034                                 }
1035                                 if ($autoinc && sizeof($table->keys) == 1) {
1036                                         $k = reset($table->keys);
1037                                         $this->$k = $this->LastInsertID($db,$k);
1038                                 }
1039                         }
1040                         
1041                         $this->_original = $valarr;
1042                 } 
1043                 return $ok;
1044         }
1046         // returns 0 on error, 1 on update, -1 if no change in data (no update)
1047         function Update()
1048         {
1049                 $db = $this->DB(); if (!$db) return false;
1050                 $table = $this->TableInfo();
1051                 
1052                 $where = $this->GenWhere($db, $table);
1053                 
1054                 if (!$where) {
1055                         $this->error("Where missing for table $table", "Update");
1056                         return false;
1057                 }
1058                 $valarr = array(); 
1059                 $neworig = array();
1060                 $pairs = array();
1061                 $i = -1;
1062                 $cnt = 0;
1063                 foreach($table->flds as $name=>$fld) {
1064                         $i += 1;
1065                         $val = $this->$name;
1066                         $neworig[] = $val;
1067                         
1068                         if (isset($table->keys[$name])) {
1069                                 continue;
1070                         }
1071                         
1072                         if (is_null($val)) {
1073                                 if (isset($fld->not_null) && $fld->not_null) {
1074                                         if (isset($fld->default_value) && strlen($fld->default_value)) continue;
1075                                         else {
1076                                                 $this->Error("Cannot set field $name to NULL","Update");
1077                                                 return false;
1078                                         }
1079                                 }
1080                         }
1081                         
1082                         if (isset($this->_original[$i]) && $val == $this->_original[$i]) {
1083                                 continue;
1084                         }                       
1085                         $valarr[] = $val;
1086                         $pairs[] = $name.'='.$db->Param($cnt);
1087                         $cnt += 1;
1088                 }
1089                 
1090                 
1091                 if (!$cnt) return -1;
1092                 $sql = 'UPDATE '.$this->_table." SET ".implode(",",$pairs)." WHERE ".$where;
1093                 $ok = $db->Execute($sql,$valarr);
1094                 if ($ok) {
1095                         $this->_original = $neworig;
1096                         return 1;
1097                 }
1098                 return 0;
1099         }
1100         
1101         function GetAttributeNames()
1102         {
1103                 $table = $this->TableInfo();
1104                 if (!$table) return false;
1105                 return array_keys($table->flds);
1106         }
1107         
1108 };
1110 function adodb_GetActiveRecordsClass(&$db, $class, $tableObj,$whereOrderBy,$bindarr, $primkeyArr,
1111                         $extra, $relations)
1113         global $_ADODB_ACTIVE_DBS;
1114         
1115                 if (empty($extra['loading'])) $extra['loading'] = ADODB_LAZY_AR;
1116                 
1117                 $save = $db->SetFetchMode(ADODB_FETCH_NUM);
1118                 $table = &$tableObj->_table;
1119                 $tableInfo =& $tableObj->TableInfo();
1120                 if(($k = reset($tableInfo->keys)))
1121                         $myId   = $k;
1122                 else
1123                         $myId   = 'id';
1124                 $index = 0; $found = false;
1125                 /** @todo Improve by storing once and for all in table metadata */
1126                 /** @todo Also re-use info for hasManyId */
1127                 foreach($tableInfo->flds as $fld)
1128                 {
1129                         if($fld->name == $myId)
1130                         {
1131                                 $found = true;
1132                                 break;
1133                         }
1134                         $index++;
1135                 }
1136                 if(!$found)
1137                         $db->outp_throw("Unable to locate key $myId for $class in GetActiveRecordsClass()",'GetActiveRecordsClass');
1138                 
1139                 $qry = "select * from ".$table;
1140                 if(ADODB_JOIN_AR == $extra['loading'])
1141                 {
1142                         if(!empty($relations['belongsTo']))
1143                         {
1144                                 foreach($relations['belongsTo'] as $foreignTable)
1145                                 {
1146                                         if(($k = reset($foreignTable->TableInfo()->keys)))
1147                                         {
1148                                                 $belongsToId = $k;
1149                                         }
1150                                         else
1151                                         {
1152                                                 $belongsToId = 'id';
1153                                         }
1155                                         $qry .= ' LEFT JOIN '.$foreignTable->_table.' ON '.
1156                                                 $table.'.'.$foreignTable->foreignKey.'='.
1157                                                 $foreignTable->_table.'.'.$belongsToId;
1158                                 }
1159                         }
1160                         if(!empty($relations['hasMany']))
1161                         {
1162                                 if(empty($relations['foreignName']))
1163                                         $db->outp_throw("Missing foreignName is relation specification in GetActiveRecordsClass()",'GetActiveRecordsClass');
1164                                 if(($k = reset($tableInfo->keys)))
1165                                         $hasManyId   = $k;
1166                                 else
1167                                         $hasManyId   = 'id';
1169                                 foreach($relations['hasMany'] as $foreignTable)
1170                                 {
1171                                         $qry .= ' LEFT JOIN '.$foreignTable->_table.' ON '.
1172                                                 $table.'.'.$hasManyId.'='.
1173                                                 $foreignTable->_table.'.'.$foreignTable->foreignKey;
1174                                 }
1175                         }
1176                 }
1177                 if (!empty($whereOrderBy))
1178                         $qry .= ' WHERE '.$whereOrderBy;
1179                 if(isset($extra['limit']))
1180                 {
1181                         $rows = false;
1182                         if(isset($extra['offset'])) {
1183                                 $rs = $db->SelectLimit($qry, $extra['limit'], $extra['offset']);
1184                         } else {
1185                                 $rs = $db->SelectLimit($qry, $extra['limit']);
1186                         }
1187                         if ($rs) {
1188                                 while (!$rs->EOF) {
1189                                         $rows[] = $rs->fields;
1190                                         $rs->MoveNext();
1191                                 }
1192                         }
1193                 } else
1194                         $rows = $db->GetAll($qry,$bindarr);
1195                         
1196                 $db->SetFetchMode($save);
1197                 
1198                 $false = false;
1199                 
1200                 if ($rows === false) {  
1201                         return $false;
1202                 }
1203                 
1204                 
1205                 if (!isset($_ADODB_ACTIVE_DBS)) {
1206                         include(ADODB_DIR.'/adodb-active-record.inc.php');
1207                 }       
1208                 if (!class_exists($class)) {
1209                         $db->outp_throw("Unknown class $class in GetActiveRecordsClass()",'GetActiveRecordsClass');
1210                         return $false;
1211                 }
1212                 $uniqArr = array(); // CFR Keep track of records for relations
1213                 $arr = array();
1214                 // arrRef will be the structure that knows about our objects.
1215                 // It is an associative array.
1216                 // We will, however, return arr, preserving regular 0.. order so that
1217                 // obj[0] can be used by app developpers.
1218                 $arrRef = array();
1219                 $bTos = array(); // Will store belongTo's indices if any
1220                 foreach($rows as $row) {
1221                 
1222                         $obj = new $class($table,$primkeyArr,$db);
1223                         if ($obj->ErrorNo()){
1224                                 $db->_errorMsg = $obj->ErrorMsg();
1225                                 return $false;
1226                         }
1227                         $obj->Set($row);
1228                         // CFR: FIXME: Insane assumption here:
1229                         // If the first column returned is an integer, then it's a 'id' field
1230                         // And to make things a bit worse, I use intval() rather than is_int() because, in fact,
1231                         // $row[0] is not an integer.
1232                         //
1233                         // So, what does this whole block do?
1234                         // When relationships are found, we perform JOINs. This is fast. But not accurate:
1235                         // instead of returning n objects with their n' associated cousins,
1236                         // we get n*n' objects. This code fixes this.
1237                         // Note: to-many relationships mess around with the 'limit' parameter
1238                         $rowId = intval($row[$index]);
1240                         if(ADODB_WORK_AR == $extra['loading'])
1241                         {
1242                                 $arrRef[$rowId] = $obj;
1243                                 $arr[] = &$arrRef[$rowId];
1244                                 if(!isset($indices))
1245                                         $indices = $rowId;
1246                                 else
1247                                         $indices .= ','.$rowId;
1248                                 if(!empty($relations['belongsTo']))
1249                                 {
1250                                         foreach($relations['belongsTo'] as $foreignTable)
1251                                         {
1252                                                 $foreignTableRef = $foreignTable->foreignKey;
1253                                                 // First array: list of foreign ids we are looking for
1254                                                 if(empty($bTos[$foreignTableRef]))
1255                                                         $bTos[$foreignTableRef] = array();
1256                                                 // Second array: list of ids found
1257                                                 if(empty($obj->$foreignTableRef))
1258                                                         continue;
1259                                                 if(empty($bTos[$foreignTableRef][$obj->$foreignTableRef]))
1260                                                         $bTos[$foreignTableRef][$obj->$foreignTableRef] = array();
1261                                                 $bTos[$foreignTableRef][$obj->$foreignTableRef][] = $obj;
1262                                         }
1263                                 }
1264                                 continue;
1265                         }
1267                         if($rowId>0)
1268                         {
1269                                 if(ADODB_JOIN_AR == $extra['loading'])
1270                                 {
1271                                         $isNewObj = !isset($uniqArr['_'.$row[0]]); 
1272                                         if($isNewObj)
1273                                                 $uniqArr['_'.$row[0]] = $obj;
1275                                         // TODO Copy/paste code below: bad!
1276                                         if(!empty($relations['hasMany']))
1277                                         {
1278                                                 foreach($relations['hasMany'] as $foreignTable)
1279                                                 {
1280                                                         $foreignName = $foreignTable->foreignName;
1281                                                         if(!empty($obj->$foreignName))
1282                                                         {
1283                                                                 $masterObj = &$uniqArr['_'.$row[0]];
1284                                                                 // Assumption: this property exists in every object since they are instances of the same class
1285                                                                 if(!is_array($masterObj->$foreignName))
1286                                                                 {
1287                                                                         // Pluck!
1288                                                                         $foreignObj = $masterObj->$foreignName;
1289                                                                         $masterObj->$foreignName = array(clone($foreignObj));
1290                                                                 }
1291                                                                 else
1292                                                                 {
1293                                                                         // Pluck pluck!
1294                                                                         $foreignObj = $obj->$foreignName;
1295                                                                         array_push($masterObj->$foreignName, clone($foreignObj));
1296                                                                 }
1297                                                         }
1298                                                 }
1299                                         }
1300                                         if(!empty($relations['belongsTo']))
1301                                         {
1302                                                 foreach($relations['belongsTo'] as $foreignTable)
1303                                                 {
1304                                                         $foreignName = $foreignTable->foreignName;
1305                                                         if(!empty($obj->$foreignName))
1306                                                         {
1307                                                                 $masterObj = &$uniqArr['_'.$row[0]];
1308                                                                 // Assumption: this property exists in every object since they are instances of the same class
1309                                                                 if(!is_array($masterObj->$foreignName))
1310                                                                 {
1311                                                                         // Pluck!
1312                                                                         $foreignObj = $masterObj->$foreignName;
1313                                                                         $masterObj->$foreignName = array(clone($foreignObj));
1314                                                                 }
1315                                                                 else
1316                                                                 {
1317                                                                         // Pluck pluck!
1318                                                                         $foreignObj = $obj->$foreignName;
1319                                                                         array_push($masterObj->$foreignName, clone($foreignObj));
1320                                                                 }
1321                                                         }
1322                                                 }
1323                                         }
1324                                         if(!$isNewObj)
1325                                                 unset($obj); // We do not need this object itself anymore and do not want it re-added to the main array                                 
1326                                 }
1327                                 else if(ADODB_LAZY_AR == $extra['loading'])
1328                                 {
1329                                         // Lazy loading: we need to give AdoDb a hint that we have not really loaded
1330                                         // anything, all the while keeping enough information on what we wish to load.
1331                                         // Let's do this by keeping the relevant info in our relationship arrays
1332                                         // but get rid of the actual properties.
1333                                         // We will then use PHP's __get to load these properties on-demand.
1334                                         if(!empty($relations['hasMany']))
1335                                         {
1336                                                 foreach($relations['hasMany'] as $foreignTable)
1337                                                 {
1338                                                         $foreignName = $foreignTable->foreignName;
1339                                                         if(!empty($obj->$foreignName))
1340                                                         {
1341                                                                 unset($obj->$foreignName);
1342                                                         }
1343                                                 }
1344                                         }
1345                                         if(!empty($relations['belongsTo']))
1346                                         {
1347                                                 foreach($relations['belongsTo'] as $foreignTable)
1348                                                 {
1349                                                         $foreignName = $foreignTable->foreignName;
1350                                                         if(!empty($obj->$foreignName))
1351                                                         {
1352                                                                 unset($obj->$foreignName);
1353                                                         }
1354                                                 }
1355                                         }
1356                                 }
1357                         }
1359                         if(isset($obj))
1360                                 $arr[] = $obj;
1361                 }
1363                 if(ADODB_WORK_AR == $extra['loading'])
1364                 {
1365                         // The best of both worlds?
1366                         // Here, the number of queries is constant: 1 + n*relationship.
1367                         // The second query will allow us to perform a good join
1368                         // while preserving LIMIT etc.
1369                         if(!empty($relations['hasMany']))
1370                         {
1371                                 foreach($relations['hasMany'] as $foreignTable)
1372                                 {
1373                                         $foreignName = $foreignTable->foreignName;
1374                                         $className = ucfirst($foreignTable->_singularize($foreignName));
1375                                         $obj = new $className();
1376                                         $dbClassRef = $foreignTable->foreignKey;
1377                                         $objs = $obj->packageFind($dbClassRef.' IN ('.$indices.')');
1378                                         foreach($objs as $obj)
1379                                         {
1380                                                 if(!is_array($arrRef[$obj->$dbClassRef]->$foreignName))
1381                                                         $arrRef[$obj->$dbClassRef]->$foreignName = array();
1382                                                 array_push($arrRef[$obj->$dbClassRef]->$foreignName, $obj);
1383                                         }
1384                                 }
1385                                 
1386                         }
1387                         if(!empty($relations['belongsTo']))
1388                         {
1389                                 foreach($relations['belongsTo'] as $foreignTable)
1390                                 {
1391                                         $foreignTableRef = $foreignTable->foreignKey;
1392                                         if(empty($bTos[$foreignTableRef]))
1393                                                 continue;
1394                                         if(($k = reset($foreignTable->TableInfo()->keys)))
1395                                         {
1396                                                 $belongsToId = $k;
1397                                         }
1398                                         else
1399                                         {
1400                                                 $belongsToId = 'id';
1401                                         }                                               
1402                                         $origObjsArr = $bTos[$foreignTableRef];
1403                                         $bTosString = implode(',', array_keys($bTos[$foreignTableRef]));
1404                                         $foreignName = $foreignTable->foreignName;
1405                                         $className = ucfirst($foreignTable->_singularize($foreignName));
1406                                         $obj = new $className();
1407                                         $objs = $obj->packageFind($belongsToId.' IN ('.$bTosString.')');
1408                                         foreach($objs as $obj)
1409                                         {
1410                                                 foreach($origObjsArr[$obj->$belongsToId] as $idx=>$origObj)
1411                                                 {
1412                                                         $origObj->$foreignName = $obj;
1413                                                 }
1414                                         }
1415                                 }
1416                         }
1417                 }
1419                 return $arr;
1421 ?>