Merge branch 'MDL-47200-master' of git://github.com/jleyva/moodle
[moodle.git] / lib / adodb / adodb-active-recordx.inc.php
1 <?php
2 /*
4 @version V5.19  23-Apr-2014  (c) 2000-2014 John Lim (jlim#natsoft.com). All rights reserved.
5   Latest version is available at http://adodb.sourceforge.net
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.
11   Active Record implementation. Superset of Zend Framework's.
13   This is "Active Record eXtended" to support JOIN, WORK and LAZY mode by Chris Ravenscroft  chris#voilaweb.com
15   Version 0.9
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;
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                 }
76                 $obj = new ADODB_Active_DB();
77                 $obj->db = $db;
78                 $obj->tables = array();
80                 $_ADODB_ACTIVE_DBS[] = $obj;
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         }
115         public function __set($name, $value)
116         {
117                 $name = str_replace(' ', '_', $name);
118                 $this->$name = $value;
119         }
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;
135                 if ($db == false && is_object($pkeyarr)) {
136                         $db = $pkeyarr;
137                         $pkeyarr = false;
138                 }
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;
164                 if ($this->_dbat < 0) $this->Error("No database connection set; use ADOdb_Active_Record::SetDatabaseAdapter(\$db)",'ADODB_Active_Record::__constructor');
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         }
182         function __wakeup()
183         {
184                 $class = get_class($this);
185                 new $class;
186         }
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         }
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         {
252                 if (!ADODB_Active_Record::$_changeNames) return $table;
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;
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();
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;
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';
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         //////////////////////////////////
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                 }
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;
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;
437                 $save = $ADODB_FETCH_MODE;
438                 $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC;
439                 if ($db->fetchMode !== false) $savem = $db->SetFetchMode(false);
441                 $cols = $db->MetaColumns($table);
443                 if (isset($savem)) $db->SetFetchMode($savem);
444                 $ADODB_FETCH_MODE = $save;
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                 }
465                 $attr = array();
466                 $keys = array();
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;
483                 case 1:
484                         foreach($cols as $name => $fldobj) {
485                                 $name = strtoupper($name);
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                         }
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);
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                 }
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];
527                         if ($oldtab) $activetab->_belongsTo = $oldtab->_belongsTo;
528                         if ($oldtab) $activetab->_hasMany = $oldtab->_hasMany;
529                 }
530                 $activedb->tables[$table] = $activetab;
531         }
533         function GetPrimaryKeys(&$db, $table)
534         {
535                 return $db->MetaPrimaryKeys($table);
536         }
538         // error handler for both PHP4+5.
539         function Error($err,$fn)
540         {
541         global $_ADODB_ACTIVE_DBS;
543                 $fn = get_class($this).'::'.$fn;
544                 $this->_lasterr = $fn.': '.$err;
546                 if ($this->_dbat < 0) $db = false;
547                 else {
548                         $activedb = $_ADODB_ACTIVE_DBS[$this->_dbat];
549                         $db = $activedb->db;
550                 }
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);
558         }
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();
567                         // last error could be database error too
568                         if ($db && $db->ErrorMsg()) return $db->ErrorMsg();
569                 }
570                 return $this->_lasterr;
571         }
573         function ErrorNo()
574         {
575                 if ($this->_dbat < 0) return -9999; // no database connection...
576                 $db = $this->DB();
578                 return (int) $db->ErrorNo();
579         }
582         // retrieve ADOConnection from _ADODB_Active_DBs
583         function DB()
584         {
585         global $_ADODB_ACTIVE_DBS;
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         }
597         // retrieve ADODB_Active_Table
598         function &TableInfo()
599         {
600         global $_ADODB_ACTIVE_DBS;
602                 $activedb = $_ADODB_ACTIVE_DBS[$this->_dbat];
603                 $table = $activedb->tables[$this->_tableat];
604                 return $table;
605         }
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         }
619         // set a numeric array (using natural table field ordering) as object properties
620         function Set(&$row)
621         {
622         global $ACTIVE_RECORD_SAFETY;
624                 $db = $this->DB();
626                 if (!$row) {
627                         $this->_saved = false;
628                         return false;
629                 }
631                 $this->_saved = true;
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         }
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;
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         }
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';
714                 case 'C':
715                 case 'X':
716                         if (is_null($val)) return 'null';
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         }
729         // generate where clause for an UPDATE/SELECT
730         function GenWhere(&$db, &$table)
731         {
732                 $keys = $table->keys;
733                 $parr = array();
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         }
745         //------------------------------------------------------------ Public functions below
747         function Load($where=null,$bindarr=false)
748         {
749                 $db = $this->DB(); if (!$db) return false;
750                 $this->_where = $where;
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';
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;
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                 }
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;
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');
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         }
882         // false on error
883         function Save()
884         {
885                 if ($this->_saved) $ok = $this->Update();
886                 else $ok = $this->Insert();
888                 return $ok;
889         }
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();
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                 }
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);
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                 }
945                 $this->_original = $valarr;
946                 return !empty($ok);
947         }
949         function Delete()
950         {
951                 $db = $this->DB(); if (!$db) return false;
952                 $table = $this->TableInfo();
954                 $where = $this->GenWhere($db,$table);
955                 $sql = 'DELETE FROM '.$this->_table.' WHERE '.$where;
956                 $ok = $db->Execute($sql);
958                 return $ok ? true : false;
959         }
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         }
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;
989                 $db = $this->DB(); if (!$db) return false;
990                 $table = $this->TableInfo();
992                 $pkey = $table->keys;
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                 }
1014                 if (!is_array($pkey)) $pkey = array($pkey);
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);
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                         }
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();
1052                 $where = $this->GenWhere($db, $table);
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;
1068                         if (isset($table->keys[$name])) {
1069                                 continue;
1070                         }
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                         }
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                 }
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         }
1101         function GetAttributeNames()
1102         {
1103                 $table = $this->TableInfo();
1104                 if (!$table) return false;
1105                 return array_keys($table->flds);
1106         }
1108 };
1110 function adodb_GetActiveRecordsClass(&$db, $class, $tableObj,$whereOrderBy,$bindarr, $primkeyArr,
1111                         $extra, $relations)
1113         global $_ADODB_ACTIVE_DBS;
1115                 if (empty($extra['loading'])) $extra['loading'] = ADODB_LAZY_AR;
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');
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);
1196                 $db->SetFetchMode($save);
1198                 $false = false;
1200                 if ($rows === false) {
1201                         return $false;
1202                 }
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) {
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                                 }
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;