Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

Differences Between: [Versions 310 and 311] [Versions 311 and 400] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403] [Versions 39 and 311]

   1  <?php
   2  /*
   3  
   4  @version   v5.21.0  2021-02-27
   5  @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
   6  @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   7    Latest version is available at https://adodb.org/
   8  
   9    Released under both BSD license and Lesser GPL library license.
  10    Whenever there is any discrepancy between the two licenses,
  11    the BSD license will take precedence.
  12  
  13    Active Record implementation. Superset of Zend Framework's.
  14  
  15    This is "Active Record eXtended" to support JOIN, WORK and LAZY mode by Chris Ravenscroft  chris#voilaweb.com
  16  
  17    Version 0.9
  18  
  19    See http://www-128.ibm.com/developerworks/java/library/j-cb03076/?ca=dgr-lnxw01ActiveRecord
  20    	 for info on Ruby on Rails Active Record implementation
  21  */
  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  }