adodb-active-record.inc.php 26.3 KB
Newer Older
Penny Leach's avatar
Penny Leach committed
1
2
3
<?php
/*

4
@version   v5.20.14  06-Jan-2019
5
6
@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
@copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
7
  Latest version is available at http://adodb.org/
8
9
10

  Released under both BSD license and Lesser GPL library license.
  Whenever there is any discrepancy between the two licenses,
Penny Leach's avatar
Penny Leach committed
11
  the BSD license will take precedence.
12

Penny Leach's avatar
Penny Leach committed
13
  Active Record implementation. Superset of Zend Framework's.
14

15
  Version 0.92
16
17

  See http://www-128.ibm.com/developerworks/java/library/j-cb03076/?ca=dgr-lnxw01ActiveRecord
Penny Leach's avatar
Penny Leach committed
18
19
20
  	for info on Ruby on Rails Active Record implementation
*/

21

Penny Leach's avatar
Penny Leach committed
22
23
global $_ADODB_ACTIVE_DBS;
global $ADODB_ACTIVE_CACHESECS; // set to true to enable caching of metadata such as field info
24
25
global $ACTIVE_RECORD_SAFETY; // set to false to disable safety checks
global $ADODB_ACTIVE_DEFVALS; // use default values of table definition when creating new active record.
Penny Leach's avatar
Penny Leach committed
26
27
28

// array of ADODB_Active_DB's, indexed by ADODB_Active_Record->_dbat
$_ADODB_ACTIVE_DBS = array();
29
30
31
$ACTIVE_RECORD_SAFETY = true;
$ADODB_ACTIVE_DEFVALS = false;
$ADODB_ACTIVE_CACHESECS = 0;
Penny Leach's avatar
Penny Leach committed
32
33
34
35
36
37
38
39
40
41
42

class ADODB_Active_DB {
	var $db; // ADOConnection
	var $tables; // assoc array of ADODB_Active_Table objects, indexed by tablename
}

class ADODB_Active_Table {
	var $name; // table name
	var $flds; // assoc array of adofieldobjs, indexed by fieldname
	var $keys; // assoc array of primary keys, indexed by fieldname
	var $_created; // only used when stored as a cached file
43
44
	var $_belongsTo = array();
	var $_hasMany = array();
Penny Leach's avatar
Penny Leach committed
45
46
}

47
48
// $db = database connection
// $index = name of index - can be associative, for an example see
49
//    http://phplens.com/lens/lensforum/msgs.php?id=17790
Penny Leach's avatar
Penny Leach committed
50
// returns index into $_ADODB_ACTIVE_DBS
51
function ADODB_SetDatabaseAdapter(&$db, $index=false)
Penny Leach's avatar
Penny Leach committed
52
53
{
	global $_ADODB_ACTIVE_DBS;
54

Penny Leach's avatar
Penny Leach committed
55
		foreach($_ADODB_ACTIVE_DBS as $k => $d) {
56
			if (PHP_VERSION >= 5) {
57
58
59
				if ($d->db === $db) {
					return $k;
				}
60
			} else {
61
				if ($d->db->_connectionID === $db->_connectionID && $db->database == $d->db->database) {
62
					return $k;
63
				}
64
			}
Penny Leach's avatar
Penny Leach committed
65
		}
66

Penny Leach's avatar
Penny Leach committed
67
		$obj = new ADODB_Active_DB();
68
		$obj->db = $db;
Penny Leach's avatar
Penny Leach committed
69
		$obj->tables = array();
70

71
72
73
		if ($index == false) {
			$index = sizeof($_ADODB_ACTIVE_DBS);
		}
74

75
		$_ADODB_ACTIVE_DBS[$index] = $obj;
76

Penny Leach's avatar
Penny Leach committed
77
78
79
80
81
		return sizeof($_ADODB_ACTIVE_DBS)-1;
}


class ADODB_Active_Record {
82
83
	static $_changeNames = true; // dynamically pluralize table names
	static $_quoteNames = false;
84
85

	static $_foreignSuffix = '_id'; //
Penny Leach's avatar
Penny Leach committed
86
87
88
89
90
91
92
	var $_dbat; // associative index pointing to ADODB_Active_DB eg. $ADODB_Active_DBS[_dbat]
	var $_table; // tablename, if set in class definition then use it as table name
	var $_tableat; // associative index pointing to ADODB_Active_Table, eg $ADODB_Active_DBS[_dbat]->tables[$this->_tableat]
	var $_where; // where clause set in Load()
	var $_saved = false; // indicates whether data is already inserted.
	var $_lasterr = false; // last error message
	var $_original = false; // the original values loaded or inserted, refreshed on update
93
94
95

	var $foreignName; // CFR: class name when in a relationship

96
97
	var $lockMode = ' for update '; // you might want to change to

98
99
100
	static function UseDefaultValues($bool=null)
	{
	global $ADODB_ACTIVE_DEFVALS;
101
102
103
		if (isset($bool)) {
			$ADODB_ACTIVE_DEFVALS = $bool;
		}
104
105
106
		return $ADODB_ACTIVE_DEFVALS;
	}

Penny Leach's avatar
Penny Leach committed
107
	// should be static
108
	static function SetDatabaseAdapter(&$db, $index=false)
Penny Leach's avatar
Penny Leach committed
109
	{
110
		return ADODB_SetDatabaseAdapter($db, $index);
Penny Leach's avatar
Penny Leach committed
111
	}
112
113


114
	public function __set($name, $value)
Penny Leach's avatar
Penny Leach committed
115
	{
116
117
		$name = str_replace(' ', '_', $name);
		$this->$name = $value;
Penny Leach's avatar
Penny Leach committed
118
	}
119

Penny Leach's avatar
Penny Leach committed
120
121
122
123
	// php5 constructor
	function __construct($table = false, $pkeyarr=false, $db=false)
	{
	global $ADODB_ASSOC_CASE,$_ADODB_ACTIVE_DBS;
124

Penny Leach's avatar
Penny Leach committed
125
126
127
128
		if ($db == false && is_object($pkeyarr)) {
			$db = $pkeyarr;
			$pkeyarr = false;
		}
129
130

		if (!$table) {
131
132
133
			if (!empty($this->_table)) {
				$table = $this->_table;
			}
Penny Leach's avatar
Penny Leach committed
134
135
			else $table = $this->_pluralize(get_class($this));
		}
136
		$this->foreignName = strtolower(get_class($this)); // CFR: default foreign name
Penny Leach's avatar
Penny Leach committed
137
138
		if ($db) {
			$this->_dbat = ADODB_Active_Record::SetDatabaseAdapter($db);
139
		} else if (!isset($this->_dbat)) {
140
141
142
143
144
145
			if (sizeof($_ADODB_ACTIVE_DBS) == 0) {
				$this->Error(
					"No database connection set; use ADOdb_Active_Record::SetDatabaseAdapter(\$db)",
					'ADODB_Active_Record::__constructor'
				);
			}
146
147
148
149
			end($_ADODB_ACTIVE_DBS);
			$this->_dbat = key($_ADODB_ACTIVE_DBS);
		}

Penny Leach's avatar
Penny Leach committed
150
151
		$this->_table = $table;
		$this->_tableat = $table; # reserved for setting the assoc value to a non-table name, eg. the sql string in future
152

Penny Leach's avatar
Penny Leach committed
153
154
		$this->UpdateActiveTable($pkeyarr);
	}
155

Penny Leach's avatar
Penny Leach committed
156
157
158
159
160
	function __wakeup()
	{
  		$class = get_class($this);
  		new $class;
	}
161

Penny Leach's avatar
Penny Leach committed
162
163
	function _pluralize($table)
	{
164
165
166
		if (!ADODB_Active_Record::$_changeNames) {
			return $table;
		}
167

Penny Leach's avatar
Penny Leach committed
168
169
170
171
172
173
		$ut = strtoupper($table);
		$len = strlen($table);
		$lastc = $ut[$len-1];
		$lastc2 = substr($ut,$len-2);
		switch ($lastc) {
		case 'S':
174
			return $table.'es';
Penny Leach's avatar
Penny Leach committed
175
176
		case 'Y':
			return substr($table,0,$len-1).'ies';
177
		case 'X':
Penny Leach's avatar
Penny Leach committed
178
			return $table.'es';
179
		case 'H':
180
			if ($lastc2 == 'CH' || $lastc2 == 'SH') {
Penny Leach's avatar
Penny Leach committed
181
				return $table.'es';
182
			}
Penny Leach's avatar
Penny Leach committed
183
184
185
186
		default:
			return $table.'s';
		}
	}
187

188
189
190
191
	// CFR Lamest singular inflector ever - @todo Make it real!
	// Note: There is an assumption here...and it is that the argument's length >= 4
	function _singularize($tables)
	{
192

193
194
195
		if (!ADODB_Active_Record::$_changeNames) {
			return $table;
		}
196

197
198
		$ut = strtoupper($tables);
		$len = strlen($tables);
199
		if($ut[$len-1] != 'S') {
200
			return $tables; // I know...forget oxen
201
202
		}
		if($ut[$len-2] != 'E') {
203
			return substr($tables, 0, $len-1);
204
205
		}
		switch($ut[$len-3]) {
206
207
208
209
210
211
			case 'S':
			case 'X':
				return substr($tables, 0, $len-2);
			case 'I':
				return substr($tables, 0, $len-3) . 'y';
			case 'H';
212
				if($ut[$len-4] == 'C' || $ut[$len-4] == 'S') {
213
					return substr($tables, 0, $len-2);
214
				}
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
			default:
				return substr($tables, 0, $len-1); // ?
		}
	}

	function hasMany($foreignRef, $foreignKey = false, $foreignClass = 'ADODB_Active_Record')
	{
		$ar = new $foreignClass($foreignRef);
		$ar->foreignName = $foreignRef;
		$ar->UpdateActiveTable();
		$ar->foreignKey = ($foreignKey) ? $foreignKey : $foreignRef.ADODB_Active_Record::$_foreignSuffix;
		$table =& $this->TableInfo();
		$table->_hasMany[$foreignRef] = $ar;
	#	$this->$foreignRef = $this->_hasMany[$foreignRef]; // WATCHME Removed assignment by ref. to please __get()
	}
230

231
232
233
234
235
236
	// use when you don't want ADOdb to auto-pluralize tablename
	static function TableHasMany($table, $foreignRef, $foreignKey = false, $foreignClass = 'ADODB_Active_Record')
	{
		$ar = new ADODB_Active_Record($table);
		$ar->hasMany($foreignRef, $foreignKey, $foreignClass);
	}
237

238
239
240
	// use when you don't want ADOdb to auto-pluralize tablename
	static function TableKeyHasMany($table, $tablePKey, $foreignRef, $foreignKey = false, $foreignClass = 'ADODB_Active_Record')
	{
241
242
243
		if (!is_array($tablePKey)) {
			$tablePKey = array($tablePKey);
		}
244
245
246
		$ar = new ADODB_Active_Record($table,$tablePKey);
		$ar->hasMany($foreignRef, $foreignKey, $foreignClass);
	}
247
248


249
250
251
252
253
254
255
	// use when you want ADOdb to auto-pluralize tablename for you. Note that the class must already be defined.
	// e.g. class Person will generate relationship for table Persons
	static function ClassHasMany($parentclass, $foreignRef, $foreignKey = false, $foreignClass = 'ADODB_Active_Record')
	{
		$ar = new $parentclass();
		$ar->hasMany($foreignRef, $foreignKey, $foreignClass);
	}
256

257
258
259
260
261
262
263
264
265
266

	function belongsTo($foreignRef,$foreignKey=false, $parentKey='', $parentClass = 'ADODB_Active_Record')
	{
		global $inflector;

		$ar = new $parentClass($this->_pluralize($foreignRef));
		$ar->foreignName = $foreignRef;
		$ar->parentKey = $parentKey;
		$ar->UpdateActiveTable();
		$ar->foreignKey = ($foreignKey) ? $foreignKey : $foreignRef.ADODB_Active_Record::$_foreignSuffix;
267

268
269
270
271
		$table =& $this->TableInfo();
		$table->_belongsTo[$foreignRef] = $ar;
	#	$this->$foreignRef = $this->_belongsTo[$foreignRef];
	}
272

273
274
275
276
277
	static function ClassBelongsTo($class, $foreignRef, $foreignKey=false, $parentKey='', $parentClass = 'ADODB_Active_Record')
	{
		$ar = new $class();
		$ar->belongsTo($foreignRef, $foreignKey, $parentKey, $parentClass);
	}
278

279
280
281
282
283
	static function TableBelongsTo($table, $foreignRef, $foreignKey=false, $parentKey='', $parentClass = 'ADODB_Active_Record')
	{
		$ar = new ADOdb_Active_Record($table);
		$ar->belongsTo($foreignRef, $foreignKey, $parentKey, $parentClass);
	}
284

285
286
	static function TableKeyBelongsTo($table, $tablePKey, $foreignRef, $foreignKey=false, $parentKey='', $parentClass = 'ADODB_Active_Record')
	{
287
288
289
		if (!is_array($tablePKey)) {
			$tablePKey = array($tablePKey);
		}
290
291
292
293
294
295
296
		$ar = new ADOdb_Active_Record($table, $tablePKey);
		$ar->belongsTo($foreignRef, $foreignKey, $parentKey, $parentClass);
	}


	/**
	 * __get Access properties - used for lazy loading
297
298
	 *
	 * @param mixed $name
299
300
301
302
303
304
305
	 * @access protected
	 * @return mixed
	 */
	 function __get($name)
	{
		return $this->LoadRelations($name, '', -1, -1);
	}
306

307
	/**
308
	 * @param string $name
309
310
311
312
313
314
315
316
317
	 * @param string $whereOrderBy : eg. ' AND field1 = value ORDER BY field2'
	 * @param offset
	 * @param limit
	 * @return mixed
	 */
	function LoadRelations($name, $whereOrderBy='', $offset=-1,$limit=-1)
	{
		$extras = array();
		$table = $this->TableInfo();
318
319
320
321
322
323
		if ($limit >= 0) {
			$extras['limit'] = $limit;
		}
		if ($offset >= 0) {
			$extras['offset'] = $offset;
		}
324

325
326
327
328
329
330
331
		if (strlen($whereOrderBy)) {
			if (!preg_match('/^[ \n\r]*AND/i', $whereOrderBy)) {
				if (!preg_match('/^[ \n\r]*ORDER[ \n\r]/i', $whereOrderBy)) {
					$whereOrderBy = 'AND ' . $whereOrderBy;
				}
			}
		}
332

333
		if(!empty($table->_belongsTo[$name])) {
334
335
			$obj = $table->_belongsTo[$name];
			$columnName = $obj->foreignKey;
336
			if(empty($this->$columnName)) {
337
				$this->$name = null;
338
339
340
341
342
343
344
345
			}
			else {
				if ($obj->parentKey) {
					$key = $obj->parentKey;
				}
				else {
					$key = reset($table->keys);
				}
346

347
348
349
350
351
352
353
				$arrayOfOne = $obj->Find($key.'='.$this->$columnName.' '.$whereOrderBy,false,false,$extras);
				if ($arrayOfOne) {
					$this->$name = $arrayOfOne[0];
					return $arrayOfOne[0];
				}
			}
		}
354
		if(!empty($table->_hasMany[$name])) {
355
356
357
358
359
360
361
362
			$obj = $table->_hasMany[$name];
			$key = reset($table->keys);
			$id = @$this->$key;
			if (!is_numeric($id)) {
				$db = $this->DB();
				$id = $db->qstr($id);
			}
			$objs = $obj->Find($obj->foreignKey.'='.$id. ' '.$whereOrderBy,false,false,$extras);
363
364
365
			if (!$objs) {
				$objs = array();
			}
366
367
368
			$this->$name = $objs;
			return $objs;
		}
369

370
371
		return array();
	}
Penny Leach's avatar
Penny Leach committed
372
	//////////////////////////////////
373

Penny Leach's avatar
Penny Leach committed
374
375
376
377
	// update metadata
	function UpdateActiveTable($pkeys=false,$forceUpdate=false)
	{
	global $ADODB_ASSOC_CASE,$_ADODB_ACTIVE_DBS , $ADODB_CACHE_DIR, $ADODB_ACTIVE_CACHESECS;
378
379
380
	global $ADODB_ACTIVE_DEFVALS,$ADODB_FETCH_MODE;

		$activedb = $_ADODB_ACTIVE_DBS[$this->_dbat];
Penny Leach's avatar
Penny Leach committed
381
382
383
384
385

		$table = $this->_table;
		$tables = $activedb->tables;
		$tableat = $this->_tableat;
		if (!$forceUpdate && !empty($tables[$tableat])) {
386
387
388

			$acttab = $tables[$tableat];
			foreach($acttab->flds as $name => $fld) {
389
390
391
392
393
394
				if ($ADODB_ACTIVE_DEFVALS && isset($fld->default_value)) {
					$this->$name = $fld->default_value;
				}
				else {
					$this->$name = null;
				}
395
			}
Penny Leach's avatar
Penny Leach committed
396
397
			return;
		}
398
		$db = $activedb->db;
Penny Leach's avatar
Penny Leach committed
399
400
401
402
403
404
		$fname = $ADODB_CACHE_DIR . '/adodb_' . $db->databaseType . '_active_'. $table . '.cache';
		if (!$forceUpdate && $ADODB_ACTIVE_CACHESECS && $ADODB_CACHE_DIR && file_exists($fname)) {
			$fp = fopen($fname,'r');
			@flock($fp, LOCK_SH);
			$acttab = unserialize(fread($fp,100000));
			fclose($fp);
405
			if ($acttab->_created + $ADODB_ACTIVE_CACHESECS - (abs(rand()) % 16) > time()) {
Penny Leach's avatar
Penny Leach committed
406
407
				// abs(rand()) randomizes deletion, reducing contention to delete/refresh file
				// ideally, you should cache at least 32 secs
408

409
				foreach($acttab->flds as $name => $fld) {
410
					if ($ADODB_ACTIVE_DEFVALS && isset($fld->default_value)) {
411
						$this->$name = $fld->default_value;
412
413
					}
					else {
414
						$this->$name = null;
415
					}
416
				}
417

Penny Leach's avatar
Penny Leach committed
418
				$activedb->tables[$table] = $acttab;
419

Penny Leach's avatar
Penny Leach committed
420
421
422
423
424
425
426
427
				//if ($db->debug) ADOConnection::outp("Reading cached active record file: $fname");
			  	return;
			} else if ($db->debug) {
				ADOConnection::outp("Refreshing cached active record file: $fname");
			}
		}
		$activetab = new ADODB_Active_Table();
		$activetab->name = $table;
428

429
430
		$save = $ADODB_FETCH_MODE;
		$ADODB_FETCH_MODE = ADODB_FETCH_ASSOC;
431
432
433
		if ($db->fetchMode !== false) {
			$savem = $db->SetFetchMode(false);
		}
434

Penny Leach's avatar
Penny Leach committed
435
		$cols = $db->MetaColumns($table);
436

437
438
439
		if (isset($savem)) {
			$db->SetFetchMode($savem);
		}
440
		$ADODB_FETCH_MODE = $save;
441

Penny Leach's avatar
Penny Leach committed
442
		if (!$cols) {
443
			$this->Error("Invalid table name: $table",'UpdateActiveTable');
Penny Leach's avatar
Penny Leach committed
444
445
446
447
448
449
450
			return false;
		}
		$fld = reset($cols);
		if (!$pkeys) {
			if (isset($fld->primary_key)) {
				$pkeys = array();
				foreach($cols as $name => $fld) {
451
452
453
					if (!empty($fld->primary_key)) {
						$pkeys[] = $name;
					}
Penny Leach's avatar
Penny Leach committed
454
				}
455
			} else
Penny Leach's avatar
Penny Leach committed
456
457
458
459
460
461
				$pkeys = $this->GetPrimaryKeys($db, $table);
		}
		if (empty($pkeys)) {
			$this->Error("No primary key found for table $table",'UpdateActiveTable');
			return false;
		}
462

Penny Leach's avatar
Penny Leach committed
463
464
		$attr = array();
		$keys = array();
465

Penny Leach's avatar
Penny Leach committed
466
467
468
469
		switch($ADODB_ASSOC_CASE) {
		case 0:
			foreach($cols as $name => $fldobj) {
				$name = strtolower($name);
470
471
472
473
				if ($ADODB_ACTIVE_DEFVALS && isset($fldobj->default_value)) {
					$this->$name = $fldobj->default_value;
				}
				else {
474
					$this->$name = null;
475
				}
Penny Leach's avatar
Penny Leach committed
476
477
478
479
480
481
				$attr[$name] = $fldobj;
			}
			foreach($pkeys as $k => $name) {
				$keys[strtolower($name)] = strtolower($name);
			}
			break;
482
483

		case 1:
Penny Leach's avatar
Penny Leach committed
484
485
			foreach($cols as $name => $fldobj) {
				$name = strtoupper($name);
486

487
488
489
490
				if ($ADODB_ACTIVE_DEFVALS && isset($fldobj->default_value)) {
					$this->$name = $fldobj->default_value;
				}
				else {
491
					$this->$name = null;
492
				}
Penny Leach's avatar
Penny Leach committed
493
494
				$attr[$name] = $fldobj;
			}
495

Penny Leach's avatar
Penny Leach committed
496
497
498
499
500
501
			foreach($pkeys as $k => $name) {
				$keys[strtoupper($name)] = strtoupper($name);
			}
			break;
		default:
			foreach($cols as $name => $fldobj) {
502
				$name = ($fldobj->name);
503

504
505
506
507
				if ($ADODB_ACTIVE_DEFVALS && isset($fldobj->default_value)) {
					$this->$name = $fldobj->default_value;
				}
				else {
508
					$this->$name = null;
509
				}
Penny Leach's avatar
Penny Leach committed
510
511
512
				$attr[$name] = $fldobj;
			}
			foreach($pkeys as $k => $name) {
513
				$keys[$name] = $cols[$name]->name;
Penny Leach's avatar
Penny Leach committed
514
515
516
			}
			break;
		}
517

Penny Leach's avatar
Penny Leach committed
518
519
520
521
522
523
		$activetab->keys = $keys;
		$activetab->flds = $attr;

		if ($ADODB_ACTIVE_CACHESECS && $ADODB_CACHE_DIR) {
			$activetab->_created = time();
			$s = serialize($activetab);
524
525
526
			if (!function_exists('adodb_write_file')) {
				include(ADODB_DIR.'/adodb-csvlib.inc.php');
			}
Penny Leach's avatar
Penny Leach committed
527
528
			adodb_write_file($fname,$s);
		}
529
530
		if (isset($activedb->tables[$table])) {
			$oldtab = $activedb->tables[$table];
531

532
533
534
535
			if ($oldtab) {
				$activetab->_belongsTo = $oldtab->_belongsTo;
				$activetab->_hasMany = $oldtab->_hasMany;
			}
536
		}
Penny Leach's avatar
Penny Leach committed
537
538
		$activedb->tables[$table] = $activetab;
	}
539

Penny Leach's avatar
Penny Leach committed
540
541
542
543
	function GetPrimaryKeys(&$db, $table)
	{
		return $db->MetaPrimaryKeys($table);
	}
544
545

	// error handler for both PHP4+5.
Penny Leach's avatar
Penny Leach committed
546
547
548
	function Error($err,$fn)
	{
	global $_ADODB_ACTIVE_DBS;
549

Penny Leach's avatar
Penny Leach committed
550
551
		$fn = get_class($this).'::'.$fn;
		$this->_lasterr = $fn.': '.$err;
552

553
554
555
		if ($this->_dbat < 0) {
			$db = false;
		}
Penny Leach's avatar
Penny Leach committed
556
557
		else {
			$activedb = $_ADODB_ACTIVE_DBS[$this->_dbat];
558
			$db = $activedb->db;
Penny Leach's avatar
Penny Leach committed
559
		}
560
561

		if (function_exists('adodb_throw')) {
562
563
564
565
566
567
568
569
570
571
572
			if (!$db) {
				adodb_throw('ADOdb_Active_Record', $fn, -1, $err, 0, 0, false);
			}
			else {
				adodb_throw($db->databaseType, $fn, -1, $err, 0, 0, $db);
			}
		} else {
			if (!$db || $db->debug) {
				ADOConnection::outp($this->_lasterr);
			}
		}
573

Penny Leach's avatar
Penny Leach committed
574
	}
575

Penny Leach's avatar
Penny Leach committed
576
577
578
579
	// return last error message
	function ErrorMsg()
	{
		if (!function_exists('adodb_throw')) {
580
581
582
583
584
585
			if ($this->_dbat < 0) {
				$db = false;
			}
			else {
				$db = $this->DB();
			}
586

Penny Leach's avatar
Penny Leach committed
587
			// last error could be database error too
588
589
590
			if ($db && $db->ErrorMsg()) {
				return $db->ErrorMsg();
			}
Penny Leach's avatar
Penny Leach committed
591
592
593
		}
		return $this->_lasterr;
	}
594
595

	function ErrorNo()
596
	{
597
598
599
		if ($this->_dbat < 0) {
			return -9999; // no database connection...
		}
600
		$db = $this->DB();
601

602
603
604
605
		return (int) $db->ErrorNo();
	}


Penny Leach's avatar
Penny Leach committed
606
	// retrieve ADOConnection from _ADODB_Active_DBs
607
	function DB()
Penny Leach's avatar
Penny Leach committed
608
609
	{
	global $_ADODB_ACTIVE_DBS;
610

Penny Leach's avatar
Penny Leach committed
611
612
613
614
615
616
		if ($this->_dbat < 0) {
			$false = false;
			$this->Error("No database connection set: use ADOdb_Active_Record::SetDatabaseAdaptor(\$db)", "DB");
			return $false;
		}
		$activedb = $_ADODB_ACTIVE_DBS[$this->_dbat];
617
		$db = $activedb->db;
Penny Leach's avatar
Penny Leach committed
618
619
		return $db;
	}
620

Penny Leach's avatar
Penny Leach committed
621
622
623
624
625
	// retrieve ADODB_Active_Table
	function &TableInfo()
	{
	global $_ADODB_ACTIVE_DBS;
		$activedb = $_ADODB_ACTIVE_DBS[$this->_dbat];
626
		$table = $activedb->tables[$this->_tableat];
Penny Leach's avatar
Penny Leach committed
627
628
		return $table;
	}
629
630


631
632
633
634
	// I have an ON INSERT trigger on a table that sets other columns in the table.
	// So, I find that for myTable, I want to reload an active record after saving it. -- Malcolm Cook
	function Reload()
	{
635
636
637
638
639
		$db = $this->DB();
		if (!$db) {
			return false;
		}
		$table = $this->TableInfo();
640
641
642
643
		$where = $this->GenWhere($db, $table);
		return($this->Load($where));
	}

644

Penny Leach's avatar
Penny Leach committed
645
646
647
	// set a numeric array (using natural table field ordering) as object properties
	function Set(&$row)
	{
648
	global $ACTIVE_RECORD_SAFETY;
649

650
		$db = $this->DB();
651

Penny Leach's avatar
Penny Leach committed
652
		if (!$row) {
653
			$this->_saved = false;
Penny Leach's avatar
Penny Leach committed
654
655
			return false;
		}
656

Penny Leach's avatar
Penny Leach committed
657
		$this->_saved = true;
658

659
660
		$table = $this->TableInfo();
		if ($ACTIVE_RECORD_SAFETY && sizeof($table->flds) != sizeof($row)) {
661
662
663
664
665
666
667
668
669
670
			# <AP>
			$bad_size = TRUE;
			if (sizeof($row) == 2 * sizeof($table->flds)) {
				// Only keep string keys
				$keys = array_filter(array_keys($row), 'is_string');
				if (sizeof($keys) == sizeof($table->flds)) {
					$bad_size = FALSE;
				}
			}
			if ($bad_size) {
Penny Leach's avatar
Penny Leach committed
671
672
673
			$this->Error("Table structure of $this->_table has changed","Load");
			return false;
		}
674
			# </AP>
675
		}
676
		else
677
			$keys = array_keys($row);
678

679
680
681
		# <AP>
		reset($keys);
		$this->_original = array();
Penny Leach's avatar
Penny Leach committed
682
		foreach($table->flds as $name=>$fld) {
683
			$value = $row[current($keys)];
684
			$this->$name = $value;
685
686
			$this->_original[] = $value;
			next($keys);
Penny Leach's avatar
Penny Leach committed
687
		}
688

689
		# </AP>
Penny Leach's avatar
Penny Leach committed
690
691
		return true;
	}
692

Penny Leach's avatar
Penny Leach committed
693
694
695
	// get last inserted id for INSERT
	function LastInsertID(&$db,$fieldname)
	{
696
		if ($db->hasInsertID) {
Penny Leach's avatar
Penny Leach committed
697
			$val = $db->Insert_ID($this->_table,$fieldname);
698
699
		}
		else {
Penny Leach's avatar
Penny Leach committed
700
			$val = false;
701
		}
702

Penny Leach's avatar
Penny Leach committed
703
704
705
706
707
708
		if (is_null($val) || $val === false) {
			// this might not work reliably in multi-user environment
			return $db->GetOne("select max(".$fieldname.") from ".$this->_table);
		}
		return $val;
	}
709

Penny Leach's avatar
Penny Leach committed
710
711
712
713
	// quote data in where clause
	function doquote(&$db, $val,$t)
	{
		switch($t) {
714
		case 'L':
715
716
717
			if (strpos($db->databaseType,'postgres') !== false) {
				return $db->qstr($val);
			}
718
		case 'D':
Penny Leach's avatar
Penny Leach committed
719
		case 'T':
720
721
722
			if (empty($val)) {
				return 'null';
			}
723
		case 'B':
724
		case 'N':
Penny Leach's avatar
Penny Leach committed
725
726
		case 'C':
		case 'X':
727
728
729
			if (is_null($val)) {
				return 'null';
			}
730
731

			if (strlen($val)>0 &&
732
733
				(strncmp($val,"'",1) != 0 || substr($val,strlen($val)-1,1) != "'")
			) {
Penny Leach's avatar
Penny Leach committed
734
735
736
737
738
739
740
741
				return $db->qstr($val);
				break;
			}
		default:
			return $val;
			break;
		}
	}
742

Penny Leach's avatar
Penny Leach committed
743
744
745
746
747
	// generate where clause for an UPDATE/SELECT
	function GenWhere(&$db, &$table)
	{
		$keys = $table->keys;
		$parr = array();
748

Penny Leach's avatar
Penny Leach committed
749
750
751
752
753
754
755
756
		foreach($keys as $k) {
			$f = $table->flds[$k];
			if ($f) {
				$parr[] = $k.' = '.$this->doquote($db,$this->$k,$db->MetaType($f->type));
			}
		}
		return implode(' and ', $parr);
	}
757
758


759
760
	function _QName($n,$db=false)
	{
761
762
763
764
765
766
767
768
769
		if (!ADODB_Active_Record::$_quoteNames) {
			return $n;
		}
		if (!$db) {
			$db = $this->DB();
			if (!$db) {
				return false;
			}
		}
770
771
		return $db->nameQuote.$n.$db->nameQuote;
	}
772

Penny Leach's avatar
Penny Leach committed
773
	//------------------------------------------------------------ Public functions below
774

775
	function Load($where=null,$bindarr=false, $lock = false)
Penny Leach's avatar
Penny Leach committed
776
	{
777
	global $ADODB_FETCH_MODE;
778

779
780
781
782
		$db = $this->DB();
		if (!$db) {
			return false;
		}
Penny Leach's avatar
Penny Leach committed
783
		$this->_where = $where;
784

785
786
		$save = $ADODB_FETCH_MODE;
		$ADODB_FETCH_MODE = ADODB_FETCH_NUM;
787
788
789
		if ($db->fetchMode !== false) {
			$savem = $db->SetFetchMode(false);
		}
790

791
		$qry = "select * from ".$this->_table;
792

793
794
795
		if($where) {
			$qry .= ' WHERE '.$where;
		}
796
797
798
		if ($lock) {
			$qry .= $this->lockMode;
		}
799

800
		$row = $db->GetRow($qry,$bindarr);
801

802
803
804
		if (isset($savem)) {
			$db->SetFetchMode($savem);
		}
805
		$ADODB_FETCH_MODE = $save;
806

Penny Leach's avatar
Penny Leach committed
807
808
		return $this->Set($row);
	}
809

810
811
812
813
	function LoadLocked($where=null, $bindarr=false)
	{
		$this->Load($where,$bindarr,true);
	}
814

815
816
817
818
	# useful for multiple record inserts
	# see http://phplens.com/lens/lensforum/msgs.php?id=17795
	function Reset()
	{
819
820
821
822
823
824
825
826
827
828
829
830
831
		$this->_where=null;
		$this->_saved = false;
		$this->_lasterr = false;
		$this->_original = false;
		$vars=get_object_vars($this);
		foreach($vars as $k=>$v){
			if(substr($k,0,1)!=='_'){
				$this->{$k}=null;
			}
		}
		$this->foreignName=strtolower(get_class($this));
		return true;
	}
832

Penny Leach's avatar
Penny Leach committed
833
834
835
	// false on error
	function Save()
	{
836
837
838
839
840
841
		if ($this->_saved) {
			$ok = $this->Update();
		}
		else {
			$ok = $this->Insert();
		}
842

Penny Leach's avatar
Penny Leach committed
843
844
		return $ok;
	}
845
846


Penny Leach's avatar
Penny Leach committed
847
848
849
	// false on error
	function Insert()
	{
850
851
852
853
		$db = $this->DB();
		if (!$db) {
			return false;
		}
Penny Leach's avatar
Penny Leach committed
854
		$cnt = 0;
855
		$table = $this->TableInfo();
856

Penny Leach's avatar
Penny Leach committed
857
858
859
860
861
862
		$valarr = array();
		$names = array();
		$valstr = array();

		foreach($table->flds as $name=>$fld) {
			$val = $this->$name;
863
			if(!is_array($val) || !is_null($val) || !array_key_exists($name, $table->keys)) {
Penny Leach's avatar
Penny Leach committed
864
				$valarr[] = $val;
865
				$names[] = $this->_QName($name,$db);
Penny Leach's avatar
Penny Leach committed
866
867
868
869
				$valstr[] = $db->Param($cnt);
				$cnt += 1;
			}
		}
870

Penny Leach's avatar
Penny Leach committed
871
872
873
874
875
876
877
878
879
880
		if (empty($names)){
			foreach($table->flds as $name=>$fld) {
				$valarr[] = null;
				$names[] = $name;
				$valstr[] = $db->Param($cnt);
				$cnt += 1;
			}
		}
		$sql = 'INSERT INTO '.$this->_table."(".implode(',',$names).') VALUES ('.implode(',',$valstr).')';
		$ok = $db->Execute($sql,$valarr);
881

Penny Leach's avatar
Penny Leach committed
882
883
884
885
886
887
888
889
890
891
892
893
894
895
		if ($ok) {
			$this->_saved = true;
			$autoinc = false;
			foreach($table->keys as $k) {
				if (is_null($this->$k)) {
					$autoinc = true;
					break;
				}
			}
			if ($autoinc && sizeof($table->keys) == 1) {
				$k = reset($table->keys);
				$this->$k = $this->LastInsertID($db,$k);
			}
		}
896

Penny Leach's avatar
Penny Leach committed
897
898
899
		$this->_original = $valarr;
		return !empty($ok);
	}
900

Penny Leach's avatar
Penny Leach committed
901
902
	function Delete()
	{
903
904
905
906
		$db = $this->DB();
		if (!$db) {
			return false;
		}
907
		$table = $this->TableInfo();
908

Penny Leach's avatar
Penny Leach committed
909
910
911
		$where = $this->GenWhere($db,$table);
		$sql = 'DELETE FROM '.$this->_table.' WHERE '.$where;
		$ok = $db->Execute($sql);
912

Penny Leach's avatar
Penny Leach committed
913
914
		return $ok ? true : false;
	}
915

Penny Leach's avatar
Penny Leach committed
916
	// returns an array of active record objects
917
	function Find($whereOrderBy,$bindarr=false,$pkeysArr=false,$extra=array())
Penny Leach's avatar
Penny Leach committed
918
	{
919
920
921
922
		$db = $this->DB();
		if (!$db || empty($this->_table)) {
			return false;
		}
923
		$arr = $db->GetActiveRecordsClass(get_class($this),$this->_table, $whereOrderBy,$bindarr,$pkeysArr,$extra);
Penny Leach's avatar
Penny Leach committed
924
925
		return $arr;
	}
926

Penny Leach's avatar
Penny Leach committed
927
928
929
930
	// returns 0 on error, 1 on update, 2 on insert
	function Replace()
	{
	global $ADODB_ASSOC_CASE;
931

932
933
934
935
		$db = $this->DB();
		if (!$db) {
			return false;
		}
936
		$table = $this->TableInfo();
937

Penny Leach's avatar
Penny Leach committed
938
		$pkey = $table->keys;
939

Penny Leach's avatar
Penny Leach committed
940
941
942
943
944
		foreach($table->flds as $name=>$fld) {
			$val = $this->$name;
			/*
			if (is_null($val)) {
				if (isset($fld->not_null) && $fld->not_null) {
945
946
947
					if (isset($fld->default_value) && strlen($fld->default_value)) {
						continue;
					}
Penny Leach's avatar
Penny Leach committed
948
949
950
951
952
953
954
					else {
						$this->Error("Cannot update null into $name","Replace");
						return false;
					}
				}
			}*/
			if (is_null($val) && !empty($fld->auto_increment)) {
955
956
				continue;
			}
957

958
959
960
			if (is_array($val)) {
				continue;
			}
961

Penny Leach's avatar
Penny Leach committed
962
963
964
965
			$t = $db->MetaType($fld->type);
			$arr[$name] = $this->doquote($db,$val,$t);
			$valarr[] = $val;
		}
966

967
968
969
		if (!is_array($pkey)) {
			$pkey = array($pkey);
		}
970

971
		if ($ADODB_ASSOC_CASE == 0) {
Penny Leach's avatar
Penny Leach committed
972
973
			foreach($pkey as $k => $v)
				$pkey[$k] = strtolower($v);
974
975
976
		}
		elseif ($ADODB_ASSOC_CASE == 1) {
			foreach($pkey as $k => $v) {
Penny Leach's avatar
Penny Leach committed
977
				$pkey[$k] = strtoupper($v);
978
979
			}
		}
980

Penny Leach's avatar
Penny Leach committed
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
		$ok = $db->Replace($this->_table,$arr,$pkey);
		if ($ok) {
			$this->_saved = true; // 1= update 2=insert
			if ($ok == 2) {
				$autoinc = false;
				foreach($table->keys as $k) {
					if (is_null($this->$k)) {
						$autoinc = true;
						break;
					}
				}
				if ($autoinc && sizeof($table->keys) == 1) {
					$k = reset($table->keys);
					$this->$k = $this->LastInsertID($db,$k);
				}
			}
997

998
			$this->_original = $valarr;
999
		}
Penny Leach's avatar
Penny Leach committed
1000
1001
1002
1003
1004
1005
		return $ok;
	}

	// returns 0 on error, 1 on update, -1 if no change in data (no update)
	function Update()
	{
1006
1007
1008
1009
		$db = $this->DB();
		if (!$db) {
			return false;
		}
1010
		$table = $this->TableInfo();
1011

Penny Leach's avatar
Penny Leach committed
1012
		$where = $this->GenWhere($db, $table);
1013

Penny Leach's avatar
Penny Leach committed
1014
1015
1016
1017
		if (!$where) {
			$this->error("Where missing for table $table", "Update");
			return false;
		}
1018
		$valarr = array();
Penny Leach's avatar
Penny Leach committed
1019
1020
1021
1022
1023
1024
1025
1026
		$neworig = array();
		$pairs = array();
		$i = -1;
		$cnt = 0;
		foreach($table->flds as $name=>$fld) {
			$i += 1;
			$val = $this->$name;
			$neworig[] = $val;
1027

1028
			if (isset($table->keys[$name]) || is_array($val)) {
Penny Leach's avatar
Penny Leach committed
1029
				continue;
1030
			}
1031

Penny Leach's avatar
Penny Leach committed
1032
1033
			if (is_null($val)) {
				if (isset($fld->not_null) && $fld->not_null) {
1034
1035
1036
					if (isset($fld->default_value) && strlen($fld->default_value)) {
						continue;
					}
Penny Leach's avatar
Penny Leach committed
1037
1038
1039
1040
1041
1042
					else {
						$this->Error("Cannot set field $name to NULL","Update");
						return false;
					}
				}
			}
1043

1044
1045
1046
			if (isset($this->_original[$i]) && strcmp($val,$this->_original[$i]) == 0) {
				continue;
			}
1047

1048
1049
1050
			if (is_null($this->_original[$i]) && is_null($val)) {
				continue;
			}
1051

Penny Leach's avatar
Penny Leach committed
1052
			$valarr[] = $val;
1053
			$pairs[] = $this->_QName($name,$db).'='.$db->Param($cnt);
Penny Leach's avatar
Penny Leach committed
1054
1055
			$cnt += 1;
		}