Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

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