Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

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

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