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