[ Index ]

PHP Cross Reference of Akelos Framework

title

Body

[close]

/ -> AkActiveRecord.php (source)

   1  <?php
   2  /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
   3  
   4  // +----------------------------------------------------------------------+
   5  // | Akelos Framework - http://www.akelos.org                             |
   6  // +----------------------------------------------------------------------+
   7  // | Copyright (c) 2002-2006, Akelos Media, S.L.  & Bermi Ferrer Martinez |
   8  // | Released under the GNU Lesser General Public License, see LICENSE.txt|
   9  // +----------------------------------------------------------------------+
  10  
  11  /**
  12   * @package ActiveRecord
  13   * @subpackage Base
  14   * @component Active Record
  15   * @author Bermi Ferrer <bermi a.t akelos c.om> 2004 - 2007
  16   * @author Kaste 2007
  17   * @copyright Copyright (c) 2002-2006, Akelos Media, S.L. http://www.akelos.org
  18   * @license GNU Lesser General Public License <http://www.gnu.org/copyleft/lesser.html>
  19   */
  20  
  21  require_once (AK_LIB_DIR.DS.'AkActiveRecord'.DS.'AkAssociatedActiveRecord.php');
  22  require_once (AK_LIB_DIR.DS.'AkActiveRecord'.DS.'AkDbAdapter.php');
  23  require_once (AK_LIB_DIR.DS.'AkActiveRecord'.DS.'AkDbSchemaCache.php');
  24  /**#@+
  25  * Constants
  26  */
  27  // Akelos args is a short way to call functions that is only intended for fast prototyping
  28  defined('AK_ENABLE_AKELOS_ARGS') ? null : define('AK_ENABLE_AKELOS_ARGS', false);
  29  // Use setColumnName if available when using set('column_name', $value);
  30  defined('AK_ACTIVE_RECORD_INTERNATIONALIZE_MODELS_BY_DEFAULT') ? null : define('AK_ACTIVE_RECORD_INTERNATIONALIZE_MODELS_BY_DEFAULT', true);
  31  defined('AK_ACTIVE_RECORD_ENABLE_AUTOMATIC_SETTERS_AND_GETTERS') ? null : define('AK_ACTIVE_RECORD_ENABLE_AUTOMATIC_SETTERS_AND_GETTERS', false);
  32  defined('AK_ACTIVE_RECORD_ENABLE_CALLBACK_SETTERS') ? null : define('AK_ACTIVE_RECORD_ENABLE_CALLBACK_SETTERS', AK_ACTIVE_RECORD_ENABLE_AUTOMATIC_SETTERS_AND_GETTERS);
  33  defined('AK_ACTIVE_RECORD_ENABLE_CALLBACK_GETTERS') ? null : define('AK_ACTIVE_RECORD_ENABLE_CALLBACK_GETTERS', AK_ACTIVE_RECORD_ENABLE_AUTOMATIC_SETTERS_AND_GETTERS);
  34  
  35  // Forces loading database schema on every call
  36  if(AK_DEV_MODE) {
  37      AkDbSchemaCache::doRefresh(true);
  38  } else if (AK_ENVIRONMENT == 'testing') {
  39      AkDbSchemaCache::doRefresh(true);
  40      define('AK_ACTIVE_RECORD_CACHE_DATABASE_SCHEMA',true);
  41  }
  42  
  43  defined('AK_ACTIVE_RECORD_ENABLE_PERSISTENCE') ? null : define('AK_ACTIVE_RECORD_ENABLE_PERSISTENCE', AK_ENVIRONMENT != 'testing');
  44  defined('AK_ACTIVE_RECORD_CACHE_DATABASE_SCHEMA') ? null : define('AK_ACTIVE_RECORD_CACHE_DATABASE_SCHEMA', AK_ACTIVE_RECORD_ENABLE_PERSISTENCE && AK_ENVIRONMENT != 'development');
  45  defined('AK_ACTIVE_RECORD_CACHE_DATABASE_SCHEMA_LIFE') ? null : define('AK_ACTIVE_RECORD_CACHE_DATABASE_SCHEMA_LIFE', 300);
  46  defined('AK_ACTIVE_RECORD_VALIDATE_TABLE_NAMES') ? null : define('AK_ACTIVE_RECORD_VALIDATE_TABLE_NAMES', true);
  47  defined('AK_ACTIVE_RECORD_SKIP_SETTING_ACTIVE_RECORD_DEFAULTS') ? null : define('AK_ACTIVE_RECORD_SKIP_SETTING_ACTIVE_RECORD_DEFAULTS', false);
  48  defined('AK_NOT_EMPTY_REGULAR_EXPRESSION') ? null : define('AK_NOT_EMPTY_REGULAR_EXPRESSION','/.+/');
  49  defined('AK_EMAIL_REGULAR_EXPRESSION') ? null : define('AK_EMAIL_REGULAR_EXPRESSION',"/^([a-z0-9_\-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-z0-9\-]+\.)+))([a-z]{2,4}|[0-9]{1,3})(\]?)$/i");
  50  defined('AK_NUMBER_REGULAR_EXPRESSION') ? null : define('AK_NUMBER_REGULAR_EXPRESSION',"/^[0-9]+$/");
  51  defined('AK_PHONE_REGULAR_EXPRESSION') ? null : define('AK_PHONE_REGULAR_EXPRESSION',"/^([\+]?[(]?[\+]?[ ]?[0-9]{2,3}[)]?[ ]?)?[0-9 ()\-]{4,25}$/");
  52  defined('AK_DATE_REGULAR_EXPRESSION') ? null : define('AK_DATE_REGULAR_EXPRESSION',"/^(([0-9]{1,2}(\-|\/|\.| )[0-9]{1,2}(\-|\/|\.| )[0-9]{2,4})|([0-9]{2,4}(\-|\/|\.| )[0-9]{1,2}(\-|\/|\.| )[0-9]{1,2})){1}$/");
  53  defined('AK_IP4_REGULAR_EXPRESSION') ? null : define('AK_IP4_REGULAR_EXPRESSION',"/^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/");
  54  defined('AK_POST_CODE_REGULAR_EXPRESSION') ? null : define('AK_POST_CODE_REGULAR_EXPRESSION',"/^[0-9A-Za-z  -]{2,9}$/");
  55  /**#@-*/
  56  
  57  
  58  
  59  ak_compat('array_combine');
  60  
  61  /**
  62  * Active Record objects doesn't specify their attributes directly, but rather infer them from the table definition with
  63  * which they're linked. Adding, removing, and changing attributes and their type is done directly in the database. Any change
  64  * is instantly reflected in the Active Record objects. The mapping that binds a given Active Record class to a certain
  65  * database table will happen automatically in most common cases, but can be overwritten for the uncommon ones. 
  66  * 
  67  * See the mapping rules in table_name and the full example in README.txt for more insight.
  68  * 
  69  * == Creation ==
  70  * 
  71  * Active Records accepts constructor parameters either in an array or as a list of parameters in a specific format. The array method is especially useful when
  72  * you're receiving the data from somewhere else, like a HTTP request. It works like this:
  73  * 
  74  * <code>
  75  *   $user = new User(array('name' => 'David', 'occupation' => 'Code Artist'));
  76  *   echo $user->name; // Will print "David"
  77  * </code>
  78  *
  79  * You can also use a parameter list initialization.:
  80  * 
  81  *   $user = new User('name->', 'David', 'occupation->', 'Code Artist');
  82  * 
  83  * And of course you can just create a bare object and specify the attributes after the fact:
  84  * 
  85  * <code>
  86  *   $user = new User();
  87  *   $user->name = 'David';
  88  *   $user->occupation = 'Code Artist';
  89  * </code>
  90  *
  91  * == Conditions ==
  92  * 
  93  * Conditions can either be specified as a string or an array representing the WHERE-part of an SQL statement.
  94  * The array form is to be used when the condition input is tainted and requires sanitization. The string form can
  95  * be used for statements that doesn't involve tainted data. Examples:
  96  * 
  97  * <code>
  98  *   class User extends ActiveRecord
  99  *   {
 100  *     function authenticateUnsafely($user_name, $password)
 101  *     {
 102  *          return findFirst("user_name = '$user_name' AND password = '$password'");
 103  *     }
 104  *     
 105  *     function authenticateSafely($user_name, $password)
 106  *     {
 107  *          return findFirst("user_name = ? AND password = ?", $user_name, $password);
 108  *     }
 109  *    }
 110  * </code>
 111  *
 112  * The <tt>authenticateUnsafely</tt> method inserts the parameters directly into the query and is thus susceptible to SQL-injection
 113  * attacks if the <tt>$user_name</tt> and <tt>$password</tt> parameters come directly from a HTTP request. The <tt>authenticateSafely</tt> method, 
 114  * on the other hand, will sanitize the <tt>$user_name</tt> and <tt>$password</tt> before inserting them in the query, which will ensure that
 115  * an attacker can't escape the query and fake the login (or worse).
 116  *
 117  * When using multiple parameters in the conditions, it can easily become hard to read exactly what the fourth or fifth
 118  * question mark is supposed to represent. In those cases, you can resort to named bind variables instead. That's done by replacing 
 119  * the question marks with symbols and supplying a hash with values for the matching symbol keys:
 120  * 
 121  * <code>
 122  *   $Company->findFirst(
 123  *              "id = :id AND name = :name AND division = :division AND created_at > :accounting_date", 
 124  *               array(':id' => 3, ':name' => "37signals", ':division' => "First", ':accounting_date' => '2005-01-01')
 125  *             );
 126  * </code>
 127  *
 128  * == Accessing attributes before they have been type casted ==
 129  * 
 130  * Some times you want to be able to read the raw attribute data without having the column-determined type cast run its course first.
 131  * That can be done by using the <attribute>_before_type_cast accessors that all attributes have. For example, if your Account model
 132  * has a balance attribute, you can call $Account->balance_before_type_cast or $Account->id_before_type_cast. 
 133  * 
 134  * This is especially useful in validation situations where the user might supply a string for an integer field and you want to display
 135  * the original string back in an error message. Accessing the attribute normally would type cast the string to 0, which isn't what you
 136  * want.
 137  * 
 138  * == Saving arrays, hashes, and other non-mappable objects in text columns ==
 139  * 
 140  * Active Record can serialize any object in text columns. To do so, you must specify this with by setting the attribute serialize with 
 141  * a comma separated list of columns or an array. 
 142  * This makes it possible to store arrays, hashes, and other non-mappeable objects without doing any additional work. Example:
 143  *
 144  * <code> 
 145  *   class User extends ActiveRecord
 146  *   {
 147  *      var $serialize = 'preferences';
 148  *   }
 149  *
 150  *   $User = new User(array('preferences'=>array("background" => "black", "display" => 'large')));
 151  *   $User->find($user_id);
 152  *   $User->preferences // array("background" => "black", "display" => 'large')
 153  * </code>
 154  * 
 155  * == Single table inheritance ==
 156  * 
 157  * Active Record allows inheritance by storing the name of the class in a column that by default is called "type" (can be changed 
 158  * by overwriting <tt>AkActiveRecord->_inheritanceColumn</tt>). This means that an inheritance looking like this:
 159  * 
 160  * <code>
 161  *   class Company extends ActiveRecord{}
 162  *   class Firm extends Company{}
 163  *   class Client extends Company{}
 164  *   class PriorityClient extends Client{}
 165  * </code>
 166  *
 167  * When you do $Firm->create('name =>', "akelos"), this record will be saved in the companies table with type = "Firm". You can then
 168  * fetch this row again using $Company->find('first', "name = '37signals'") and it will return a Firm object.
 169  * 
 170  * If you don't have a type column defined in your table, single-table inheritance won't be triggered. In that case, it'll work just
 171  * like normal subclasses with no special magic for differentiating between them or reloading the right type with find.
 172  * 
 173  * Note, all the attributes for all the cases are kept in the same table. Read more:
 174  * http://www.martinfowler.com/eaaCatalog/singleTableInheritance.html
 175  * 
 176  * == Connection to multiple databases in different models ==
 177  * 
 178  * Connections are usually created through AkActiveRecord->establishConnection and retrieved by AkActiveRecord->connection.
 179  * All classes inheriting from AkActiveRecord will use this connection. But you can also set a class-specific connection. 
 180  * For example, if $Course is a AkActiveRecord, but resides in a different database you can just say $Course->establishConnection
 181  * and $Course and all its subclasses will use this connection instead.
 182  * 
 183  * Active Records will automatically record creation and/or update timestamps of database objects
 184  * if fields of the names created_at/created_on or updated_at/updated_on are present.
 185  * Date only: created_on, updated_on
 186  * Date and time: created_at, updated_at
 187  *
 188  * This behavior can be turned off by setting <tt>$this->_recordTimestamps = false</tt>.
 189  */
 190  class AkActiveRecord extends AkAssociatedActiveRecord
 191  {
 192      /**#@+
 193      * @access private
 194      */
 195      //var $disableAutomatedAssociationLoading = true;
 196      var $_tableName;
 197      var $_db;
 198      var $_newRecord;
 199      var $_freeze;
 200      var $_dataDictionary;
 201      var $_primaryKey;
 202      var $_inheritanceColumn;
 203  
 204      var $_associations;
 205  
 206      var $_internationalize;
 207  
 208      var $_errors = array();
 209  
 210      var $_attributes = array();
 211  
 212      var $_protectedAttributes = array();
 213      var $_accessibleAttributes = array();
 214  
 215      var $_recordTimestamps = true;
 216  
 217      // Column description
 218      var $_columnNames = array();
 219      // Array of column objects for the table associated with this class.
 220      var $_columns = array();
 221      // Columns that can be edited/viewed
 222      var $_contentColumns = array();
 223      // Methods that will be dinamically loaded for the model (EXPERIMENTAL) This pretends to generate something similar to Ruby on Rails finders.
 224      // If you set array('findOneByUsernameAndPassword', 'findByCompany', 'findAllByExipringDate')
 225      // You'll get $User->findOneByUsernameAndPassword('admin', 'pass');
 226      var $_dynamicMethods = false;
 227      var $_combinedAttributes = array();
 228  
 229      var $_BlobQueryStack = null;
 230  
 231      var $_automated_max_length_validator = false;
 232      var $_automated_validators_enabled = false;
 233      var $_automated_not_null_validator = false;
 234      var $_set_default_attribute_values_automatically = true;
 235  
 236      // This is needed for enabling support for static active record instantation under php
 237      var $_activeRecordHasBeenInstantiated = true;
 238  
 239      var $__ActsLikeAttributes = array();
 240  
 241      /**
 242      * Holds a hash with all the default error messages, such that they can be replaced by your own copy or localizations.
 243      */
 244      var $_defaultErrorMessages = array(
 245      'inclusion' =>  "is not included in the list",
 246      'exclusion' => "is reserved",
 247      'invalid' => "is invalid",
 248      'confirmation' => "doesn't match confirmation",
 249      'accepted' => "must be accepted",
 250      'empty' => "can't be empty",
 251      'blank' => "can't be blank",
 252      'too_long' => "is too long (max is %d characters)",
 253      'too_short' => "is too short (min is %d characters)",
 254      'wrong_length' => "is the wrong length (should be %d characters)",
 255      'taken' => "has already been taken",
 256      'not_a_number' => "is not a number"
 257      );
 258  
 259      var $__activeRecordObject = true;
 260  
 261      /**#@-*/
 262  
 263      function __construct()
 264      {
 265          $attributes = (array)func_get_args();
 266          return $this->init($attributes);
 267      }
 268  
 269      function init($attributes = array())
 270      {
 271          AK_LOG_EVENTS ? ($this->Logger =& Ak::getLogger()) : null;
 272          $this->_internationalize = is_null($this->_internationalize) && AK_ACTIVE_RECORD_INTERNATIONALIZE_MODELS_BY_DEFAULT ? count($this->getAvailableLocales()) > 1 : $this->_internationalize;
 273  
 274          @$this->_instantiateDefaultObserver();
 275  
 276          $this->setConnection();
 277  
 278          if(!empty($this->table_name)){
 279              $this->setTableName($this->table_name);
 280          }
 281          $this->act_as = !empty($this->acts_as) ? $this->acts_as : (empty($this->act_as) ? false : $this->act_as);
 282          if (!empty($this->act_as)) {
 283              $this->_loadActAsBehaviours();
 284          }
 285  
 286          if(!empty($this->combined_attributes)){
 287              foreach ($this->combined_attributes as $combined_attribute){
 288                  $this->addCombinedAttributeConfiguration($combined_attribute);
 289              }
 290          }
 291  
 292          if(isset($attributes[0]) && is_array($attributes[0]) && count($attributes) === 1){
 293              $attributes = $attributes[0];
 294              $this->_newRecord = true;
 295          }
 296  
 297          // new AkActiveRecord(23); //Returns object with primary key 23
 298          if(isset($attributes[0]) && count($attributes) === 1 && $attributes[0] > 0){
 299              $record = $this->find($attributes[0]);
 300              if(!$record){
 301                  return false;
 302              }else {
 303                  $this->setAttributes($record->getAttributes(), true);
 304              }
 305              // This option is only used internally for loading found objects
 306          }elseif(isset($attributes[0]) && isset($attributes[1]) && $attributes[0] == 'attributes' && is_array($attributes[1])){
 307              foreach(array_keys($attributes[1]) as $k){
 308                  $attributes[1][$k] = $this->castAttributeFromDatabase($k, $attributes[1][$k]);
 309              }
 310  
 311              $avoid_loading_associations = isset($attributes[1]['load_associations']) ? false : !empty($this->disableAutomatedAssociationLoading);
 312              $this->setAttributes($attributes[1], true);
 313          }else{
 314              $this->newRecord($attributes);
 315          }
 316  
 317          if($this->_dynamicMethods){
 318              $this->_buildFinders();
 319          }
 320          empty($avoid_loading_associations) ? $this->loadAssociations() : null;
 321      }
 322  
 323      function __destruct()
 324      {
 325  
 326      }
 327  
 328      /**
 329      * New objects can be instantiated as either empty (pass no construction parameter) or pre-set with attributes but not yet saved
 330      * (pass an array with key names matching the associated table column names). 
 331      * In both instances, valid attribute keys are determined by the column names of the associated table; hence you can't 
 332      * have attributes that aren't part of the table columns.
 333      */
 334      function newRecord($attributes)
 335      {
 336          $this->_newRecord = true;
 337  
 338          if(AK_ACTIVE_RECORD_SKIP_SETTING_ACTIVE_RECORD_DEFAULTS && empty($attributes)){
 339              return;
 340          }
 341  
 342          if(isset($attributes) && !is_array($attributes)){
 343              $attributes = func_get_args();
 344          }
 345          $this->setAttributes($this->attributesFromColumnDefinition(),true);
 346          $this->setAttributes($attributes);
 347      }
 348  
 349  
 350      /**
 351      * Returns a clone of the record that hasn't been assigned an id yet and is treated as a new record.
 352      */
 353      function cloneRecord()
 354      {
 355          $model_name = $this->getModelName();
 356          $attributes = $this->getAttributesBeforeTypeCast();
 357          if(isset($attributes[$this->getPrimaryKey()])){
 358              unset($attributes[$this->getPrimaryKey()]);
 359          }
 360          return new $model_name($attributes);
 361      }
 362  
 363  
 364      /**
 365      * Returns true if this object hasn't been saved yet that is, a record for the object doesn't exist yet.
 366      */
 367      function isNewRecord()
 368      {
 369          if(!isset($this->_newRecord) && !isset($this->{$this->getPrimaryKey()})){
 370              $this->_newRecord = true;
 371          }
 372          return $this->_newRecord;
 373      }
 374  
 375  
 376  
 377      /**
 378      * Reloads the attributes of this object from the database.
 379      */   
 380      function reload()
 381      {
 382          /**
 383          * @todo clear cache
 384          */        
 385          if($object = $this->find($this->getId())){
 386              $this->setAttributes($object->getAttributes(), true);
 387              return true;
 388          }else {
 389              return false;
 390          }
 391      }
 392  
 393  
 394  
 395      /**
 396                           Creating records
 397      ====================================================================
 398      */
 399      /**
 400      * Creates an object, instantly saves it as a record (if the validation permits it), and returns it. 
 401      * If the save fail under validations, the unsaved object is still returned.
 402      */
 403      function &create($attributes = null)
 404      {
 405          if(!isset($this->_activeRecordHasBeenInstantiated)){
 406              return Ak::handleStaticCall();
 407          }
 408  
 409          if(func_num_args() > 1){
 410              $attributes = func_get_args();
 411          }
 412          $model = $this->getModelName();
 413  
 414          $object =& new $model();
 415          $object->setAttributes($attributes);
 416          $object->save();
 417          return $object;
 418      }
 419  
 420      function createOrUpdate($validate = true)
 421      {
 422          if($validate && !$this->isValid()){
 423              $this->transactionFail();
 424              return false;
 425          }
 426          return $this->isNewRecord() ? $this->_create() : $this->_update();
 427      }
 428  
 429      function &findOrCreateBy()
 430      {
 431          $args = func_get_args();
 432          $Item =& Ak::call_user_func_array(array(&$this,'findFirstBy'), $args);
 433          if(!$Item){
 434              $attributes = array();
 435  
 436              list($sql, $columns) = $this->_getFindBySqlAndColumns(array_shift($args), $args);
 437  
 438              if(!empty($columns)){
 439                  foreach ($columns as $column){
 440                      $attributes[$column] = array_shift($args);
 441                  }
 442              }
 443              $Item =& $this->create($attributes);
 444              $Item->has_been_created = true;
 445          }else{
 446              $Item->has_been_created = false;
 447          }
 448          $Item->has_been_found = !$Item->has_been_created;
 449          return $Item;
 450      }
 451  
 452      /**
 453      * Creates a new record with values matching those of the instance attributes.
 454      * Must be called as a result of a call to createOrUpdate.
 455      *
 456      * @access private
 457      */
 458      function _create()
 459      {
 460          if (!$this->beforeCreate() || !$this->notifyObservers('beforeCreate')){
 461              return $this->transactionFail();
 462          }
 463  
 464          $this->_setRecordTimestamps();
 465  
 466          // deprecated section
 467          if($this->isLockingEnabled() && is_null($this->get('lock_version'))){
 468              Ak::deprecateWarning(array("Column %lock_version_column should have a default setting. Assumed '1'.",'%lock_version_column'=>'lock_version'));
 469              $this->setAttribute('lock_version',1);
 470          } // end
 471  
 472          $attributes = $this->getColumnsForAttributes($this->getAttributes());
 473          foreach ($attributes as $column=>$value){
 474              $attributes[$column] = $this->castAttributeForDatabase($column,$value);
 475          }
 476  
 477          $pk = $this->getPrimaryKey();
 478          $table = $this->getTableName();
 479  
 480          $id = $this->_db->incrementsPrimaryKeyAutomatically() ? null : $this->_db->getNextSequenceValueFor($table);
 481          $attributes[$pk] = $id;
 482  
 483          $attributes = array_diff($attributes, array(''));
 484          
 485  
 486          $sql = 'INSERT INTO '.$table.' '.
 487          '('.join(', ',array_keys($attributes)).') '.
 488          'VALUES ('.join(',',array_values($attributes)).')';
 489  
 490          $inserted_id = $this->_db->insert($sql, $id, $pk, $table, 'Create '.$this->getModelName());
 491          if ($this->transactionHasFailed()){
 492              return false;
 493          }
 494          $this->setId($inserted_id);
 495  
 496          $this->_newRecord = false;
 497          
 498          if (!$this->afterCreate() || !$this->notifyObservers('afterCreate')){
 499              return $this->transactionFail();
 500          }
 501          
 502          return true;
 503      }
 504  
 505      function _setRecordTimestamps()
 506      {
 507          if (!$this->_recordTimestamps){
 508              return;
 509          }
 510          if ($this->_newRecord){
 511              if ($this->hasColumn('created_at')){
 512                  $this->setAttribute('created_at', Ak::getDate());
 513              }
 514              if ($this->hasColumn('created_on')){
 515                  $this->setAttribute('created_on', Ak::getDate(null, 'Y-m-d'));
 516              }
 517          }else{
 518              if ($this->hasColumn('updated_at')){
 519                  $this->setAttribute('updated_at', Ak::getDate());
 520              }
 521              if ($this->hasColumn('updated_on')){
 522                  $this->setAttribute('updated_on', Ak::getDate(null, 'Y-m-d'));
 523              }
 524          }
 525  
 526          if($this->_newRecord && isset($this->expires_on)){
 527              if(isset($this->expires_at) && $this->hasColumn('expires_at')){
 528                  $this->setAttribute('expires_at',Ak::getDate(strtotime($this->expires_at) + (defined('AK_TIME_DIFFERENCE') ? AK_TIME_DIFFERENCE*60 : 0)));
 529              }elseif(isset($this->expires_on) && $this->hasColumn('expires_on')){
 530                  $this->setAttribute('expires_on',Ak::getDate(strtotime($this->expires_on) + (defined('AK_TIME_DIFFERENCE') ? AK_TIME_DIFFERENCE*60 : 0), 'Y-m-d'));
 531              }
 532          }
 533  
 534      }
 535  
 536      /*/Creating records*/
 537  
 538  
 539      /**
 540                           Saving records
 541      ====================================================================
 542      */
 543      /**
 544      * - No record exists: Creates a new record with values matching those of the object attributes.
 545      * - A record does exist: Updates the record with values matching those of the object attributes.
 546      */
 547      function save($validate = true)
 548      {
 549          if($this->isFrozen()){
 550              return false;
 551          }
 552          $result = false;
 553          $this->transactionStart();
 554          if($this->beforeSave() && $this->notifyObservers('beforeSave')){
 555              $result = $this->createOrUpdate($validate);
 556              if(!$this->transactionHasFailed()){
 557                  if(!$this->afterSave()){
 558                      $this->transactionFail();
 559                  }else{
 560                      if(!$this->notifyObservers('afterSave')){
 561                          $this->transactionFail();
 562                      }
 563                  }
 564              }
 565          }else{
 566              $this->transactionFail();
 567          }
 568  
 569          $result = $this->transactionHasFailed() ? false : $result;
 570          $this->transactionComplete();
 571  
 572          return $result;
 573      }
 574  
 575      /*/Saving records*/
 576  
 577      /**
 578                              Counting Records
 579      ====================================================================
 580      See also: Counting Attributes.
 581      */
 582  
 583      /**
 584        * Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part.
 585        * 
 586        *   $Product->countBySql("SELECT COUNT(*) FROM sales s, customers c WHERE s.customer_id = c.id");
 587        */
 588      function countBySql($sql)
 589      {
 590          if(!isset($this->_activeRecordHasBeenInstantiated)){
 591              return Ak::handleStaticCall();
 592          }
 593          if(!stristr($sql, 'COUNT') && stristr($sql, ' FROM ')){
 594              $sql = 'SELECT COUNT(*) '.substr($sql,strpos(str_replace(' from ',' FROM ', $sql),' FROM '));
 595          }
 596          if(!$this->isConnected()){
 597              $this->setConnection();
 598          }
 599  
 600          return (integer)$this->_db->selectValue($sql);
 601      }
 602      /*/Counting Records*/
 603  
 604      /**
 605                            Updating records
 606      ====================================================================
 607      See also: Callbacks.
 608      */
 609  
 610      /**
 611      * Finds the record from the passed id, instantly saves it with the passed attributes (if the validation permits it), 
 612      * and returns it. If the save fail under validations, the unsaved object is still returned.
 613      */
 614      function update($id, $attributes)
 615      {
 616          if(!isset($this->_activeRecordHasBeenInstantiated)){
 617              return Ak::handleStaticCall();
 618          }
 619          if(is_array($id)){
 620              $results = array();
 621              foreach ($id as $idx=>$single_id){
 622                  $results[] = $this->update($single_id, isset($attributes[$idx]) ? $attributes[$idx] : $attributes);
 623              }
 624              return $results;
 625          }else{
 626              $object =& $this->find($id);
 627              $object->updateAttributes($attributes);
 628              return $object;
 629          }
 630      }
 631  
 632      /**
 633      * Updates a single attribute and saves the record. This is especially useful for boolean flags on existing records. 
 634      */
 635      function updateAttribute($name, $value, $should_validate=true)
 636      {
 637          $this->setAttribute($name, $value);
 638          return $this->save($should_validate);
 639      }
 640  
 641  
 642      /**
 643      * Updates all the attributes in from the passed array and saves the record. If the object is 
 644      * invalid, the saving will fail and false will be returned.
 645      */
 646      function updateAttributes($attributes, $object = null)
 647      {
 648          isset($object) ? $object->setAttributes($attributes) : $this->setAttributes($attributes);
 649  
 650          return isset($object) ? $object->save() : $this->save();
 651      }
 652  
 653      /**
 654      * Updates all records with the SET-part of an SQL update statement in updates and returns an 
 655      * integer with the number of rows updates. A subset of the records can be selected by specifying conditions. Example:
 656      * <code>$Billing->updateAll("category = 'authorized', approved = 1", "author = 'David'");</code>
 657      * 
 658      * Important note: Conditions are not sanitized yet so beware of accepting 
 659      * variable conditions when using this function
 660      */
 661      function updateAll($updates, $conditions = null)
 662      {
 663          if(!isset($this->_activeRecordHasBeenInstantiated)){
 664              return Ak::handleStaticCall();
 665          }
 666          /**
 667          * @todo sanitize sql conditions
 668          */
 669          $sql = 'UPDATE '.$this->getTableName().' SET '.$updates;
 670          $this->addConditions($sql, $conditions);
 671          return $this->_db->update($sql, $this->getModelName().' Update All');
 672      }
 673  
 674  
 675      /**
 676      * Updates the associated record with values matching those of the instance attributes.
 677      * Must be called as a result of a call to createOrUpdate.
 678      *
 679      * @access private
 680      */
 681      function _update()
 682      {
 683          if(!$this->beforeUpdate() || !$this->notifyObservers('beforeUpdate')){
 684              return $this->transactionFail();
 685          }
 686  
 687          $this->_setRecordTimestamps();
 688  
 689          $lock_check_sql = '';
 690          if ($this->isLockingEnabled()){
 691              $previous_value = $this->lock_version;
 692              $this->setAttribute('lock_version', $previous_value + 1);
 693              $lock_check_sql = ' AND lock_version = '.$previous_value;
 694          }
 695  
 696          $quoted_attributes = $this->getAvailableAttributesQuoted();
 697          $sql = 'UPDATE '.$this->getTableName().' '.
 698          'SET '.join(', ', $quoted_attributes) .' '.
 699          'WHERE '.$this->getPrimaryKey().'='.$this->quotedId().$lock_check_sql;
 700  
 701          $affected_rows = $this->_db->update($sql,'Updating '.$this->getModelName());
 702          if($this->transactionHasFailed()){
 703              return false;
 704          }
 705  
 706          if ($this->isLockingEnabled() && $affected_rows != 1){
 707              $this->setAttribute('lock_version', $previous_value);
 708              trigger_error(Ak::t('Attempted to update a stale object'), E_USER_NOTICE);
 709              return $this->transactionFail();
 710          }
 711  
 712          if(!$this->afterUpdate() || !$this->notifyObservers('afterUpdate')){
 713              return $this->transactionFail();
 714          }
 715  
 716          return true;
 717      }
 718  
 719      /*/Updating records*/
 720  
 721  
 722  
 723      /**
 724                            Deleting records
 725      ====================================================================
 726      See also: Callbacks.
 727      */
 728  
 729      /**
 730      * Deletes the record with the given id without instantiating an object first. If an array of 
 731      * ids is provided, all of them are deleted.
 732      */
 733      function delete($id)
 734      {
 735          if(!isset($this->_activeRecordHasBeenInstantiated)){
 736              return Ak::handleStaticCall();
 737          }
 738          $id = func_num_args() > 1 ? func_get_args() : $id;
 739          return $this->deleteAll($this->getPrimaryKey().' IN ('.(is_array($id) ? join(', ',$id) : $id).')');
 740      }
 741  
 742  
 743      /**
 744      * Deletes all the records that matches the condition without instantiating the objects first 
 745      * (and hence not calling the destroy method). Example:
 746      * 
 747      * <code>$Post->destroyAll("person_id = 5 AND (category = 'Something' OR category = 'Else')");</code>
 748      * 
 749      * Important note: Conditions are not sanitized yet so beware of accepting 
 750      * variable conditions when using this function
 751      */
 752      function deleteAll($conditions = null)
 753      {
 754          if(!isset($this->_activeRecordHasBeenInstantiated)){
 755              return Ak::handleStaticCall();
 756          }
 757          /**
 758          * @todo sanitize sql conditions
 759          */
 760          $sql = 'DELETE FROM '.$this->getTableName();
 761          $this->addConditions($sql,$conditions);
 762          return $this->_db->delete($sql,$this->getModelName().' Delete All');
 763      }
 764  
 765  
 766      /**
 767      * Destroys the record with the given id by instantiating the object and calling destroy 
 768      * (all the callbacks are the triggered). If an array of ids is provided, all of them are destroyed.
 769      * Deletes the record in the database and freezes this instance to reflect that no changes should be 
 770      * made (since they can't be persisted).
 771      */
 772      function destroy($id = null)
 773      {
 774          if(!isset($this->_activeRecordHasBeenInstantiated)){
 775              return Ak::handleStaticCall();
 776          }
 777  
 778          $id = func_num_args() > 1 ? func_get_args() : $id;
 779  
 780          if(isset($id)){
 781              $this->transactionStart();
 782              $id_arr = is_array($id) ? $id : array($id);
 783              if($objects = $this->find($id_arr)){
 784                  $results = count($objects);
 785                  $no_problems = true;
 786                  for ($i=0; $results > $i; $i++){
 787                      if(!$objects[$i]->destroy()){
 788                          $no_problems = false;
 789                      }
 790                  }
 791                  $this->transactionComplete();
 792                  return $no_problems;
 793              }else {
 794                  $this->transactionComplete();
 795                  return false;
 796              }
 797          }else{
 798              if(!$this->isNewRecord()){
 799                  $this->transactionStart();
 800                  $return = $this->_destroy() && $this->freeze();
 801                  $this->transactionComplete();
 802                  return $return;
 803              }
 804          }
 805      }
 806  
 807      function _destroy()
 808      {
 809          if(!$this->beforeDestroy() || !$this->notifyObservers('beforeDestroy')){
 810              return $this->transactionFail();
 811          }
 812  
 813          $sql = 'DELETE FROM '.$this->getTableName().' WHERE '.$this->getPrimaryKey().' = '.$this->_db->quote_string($this->getId());
 814          if ($this->_db->delete($sql,$this->getModelName().' Destroy') !== 1){
 815              return $this->transactionFail();
 816          }
 817  
 818          if (!$this->afterDestroy() || !$this->notifyObservers('afterDestroy')){
 819              return $this->transactionFail();
 820          }
 821          return true;
 822      }
 823  
 824      /**
 825      * Destroys the objects for all the records that matches the condition by instantiating 
 826      * each object and calling the destroy method. 
 827      * 
 828      * Example:
 829      * 
 830      *   $Person->destroyAll("last_login < '2004-04-04'");
 831      */    
 832      function destroyAll($conditions)
 833      {
 834          if($objects = $this->find('all',array('conditions'=>$conditions))){
 835              $results = count($objects);
 836              $no_problems = true;
 837              for ($i=0; $results > $i; $i++){
 838                  if(!$objects[$i]->destroy()){
 839                      $no_problems = false;
 840                  }
 841              }
 842              return $no_problems;
 843          }else {
 844              return false;
 845          }
 846      }
 847  
 848      /*/Deleting records*/
 849  
 850  
 851  
 852  
 853      /**
 854                            Finding records
 855      ====================================================================
 856      */
 857  
 858      /**
 859      * Returns true if the given id represents the primary key of a record in the database, false otherwise. Example:
 860      * 
 861      * $Person->exists(5);
 862      */    
 863      function exists($id)
 864      {
 865          return $this->find('first',array('conditions' => array($this->getPrimaryKey().' = '.$id))) !== false;
 866      }
 867  
 868      /**
 869       * Find operates with three different retrieval approaches:
 870      * * Find by id: This can either be a specific id find(1), a list of ids find(1, 5, 6), 
 871      *   or an array of ids find(array(5, 6, 10)). If no record can be found for all of the listed ids, 
 872      *   then RecordNotFound will be raised.
 873      * * Find first: This will return the first record matched by the options used. These options 
 874      *   can either be specific conditions or merely an order. 
 875      *   If no record can matched, false is returned.
 876      * * Find all: This will return all the records matched by the options used. If no records are found, an empty array is returned.
 877      * 
 878      * All approaches accepts an $option array as their last parameter. The options are:
 879      * 
 880      * 'conditions' => An SQL fragment like "administrator = 1" or array("user_name = ?" => $username). See conditions in the intro.
 881      * 'order' => An SQL fragment like "created_at DESC, name".
 882      * 'limit' => An integer determining the limit on the number of rows that should be returned.
 883      * 'offset' => An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows.
 884      * 'joins' => An SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = $id". (Rarely needed).
 885      * 'include' => Names associations that should be loaded alongside using LEFT OUTER JOINs. The symbols 
 886      * named refer to already defined associations. See eager loading under Associations.
 887      * 
 888      * Examples for find by id:
 889      * <code>
 890      *   $Person->find(1);       // returns the object for ID = 1
 891      *   $Person->find(1, 2, 6); // returns an array for objects with IDs in (1, 2, 6), Returns false if any of those IDs is not available
 892      *   $Person->find(array(7, 17)); // returns an array for objects with IDs in (7, 17)
 893      *   $Person->find(array(1));     // returns an array for objects the object with ID = 1
 894      *   $Person->find(1, array('conditions' => "administrator = 1", 'order' => "created_on DESC"));
 895      * </code>
 896      *
 897      * Examples for find first:
 898      * <code> 
 899      *   $Person->find('first'); // returns the first object fetched by SELECT * FROM people
 900      *   $Person->find('first', array('conditions' => array("user_name = ':user_name'", ':user_name' => $user_name)));
 901      *   $Person->find('first', array('order' => "created_on DESC", 'offset' => 5));
 902      * </code>
 903      * 
 904      * Examples for find all:
 905      * <code> 
 906      *   $Person->find('all'); // returns an array of objects for all the rows fetched by SELECT * FROM people
 907      *   $Person->find(); // Same as $Person->find('all');
 908      *   $Person->find('all', array('conditions' => array("category IN (categories)", 'categories' => join(','$categories)), 'limit' => 50));
 909      *   $Person->find('all', array('offset' => 10, 'limit' => 10));
 910      *   $Person->find('all', array('include' => array('account', 'friends'));
 911      * </code>
 912      */
 913      function &find()
 914      {
 915          if(!isset($this->_activeRecordHasBeenInstantiated)){
 916              return Ak::handleStaticCall();
 917          }
 918  
 919          $args = func_get_args();
 920  
 921          $options = $this->_extractOptionsFromArgs($args);
 922          list($fetch,$options) = $this->_extractConditionsFromArgs($args,$options);
 923  
 924          $this->_sanitizeConditionsVariables($options);
 925  
 926          switch ($fetch) {
 927              case 'first':
 928                  // HACK: php4 pass by ref
 929                  $result =& $this->_findInitial($options);
 930                  return $result;
 931                  break;
 932  
 933              case 'all':
 934                  // HACK: php4 pass by ref
 935                  $result =& $this->_findEvery($options);
 936                  return $result;
 937                  break;
 938  
 939              default:
 940                  // HACK: php4 pass by ref
 941                  $result =& $this->_findFromIds($args, $options);
 942                  return $result;
 943                  break;
 944          }
 945          $result = false;
 946          return $result;
 947      }
 948  
 949      function &_findInitial($options)
 950      {
 951          // TODO: virtual_limit is a hack
 952          // actually we fetch_all and return only the first row
 953          $options = array_merge($options, array((!empty($options['include']) ?'virtual_limit':'limit')=>1));
 954          $result =& $this->_findEvery($options);
 955  
 956          if(!empty($result) && is_array($result)){
 957              $_result =& $result[0];
 958          }else{
 959              $_result = false;
 960              // if we return an empty array instead of false we need to change this->exists()!
 961              //$_result = array();
 962          }
 963          return  $_result;
 964  
 965      }
 966  
 967      function &_findEvery($options)
 968      {
 969          if((!empty($options['include']) && $this->hasAssociations())){
 970              $result =& $this->findWithAssociations($options);
 971          }else{
 972              $sql = $this->constructFinderSql($options);
 973              if(!empty($options['bind']) && is_array($options['bind']) && strstr($sql,'?')){
 974                  $sql = array_merge(array($sql),$options['bind']);
 975              }
 976              
 977              $result =& $this->findBySql($sql);
 978          }
 979  
 980          if(!empty($result) && is_array($result)){
 981              $_result =& $result;
 982          }else{
 983              $_result = false;
 984          }
 985          return  $_result;
 986  
 987      }
 988  
 989      function &_findFromIds($ids, $options)
 990      {
 991          $expects_array = is_array($ids[0]);
 992          $ids = array_unique($expects_array ? (isset($ids[1]) ? array_merge($ids[0],$ids) : $ids[0]) : $ids);
 993  
 994          $num_ids = count($ids);
 995  
 996          //at this point $options['conditions'] can't be an array
 997          $conditions = !empty($options['conditions']) ? ' AND '.$options['conditions'] : '';
 998  
 999          switch ($num_ids){
1000              case 0 :
1001                  trigger_error($this->t('Couldn\'t find %object_name without an ID%conditions',array('%object_name'=>$this->getModelName(),'%conditions'=>$conditions)), E_USER_ERROR);
1002                  break;
1003  
1004              case 1 :
1005                  $table_name = !empty($options['include']) && $this->hasAssociations() ? '__owner' : $this->getTableName();
1006                  $options['conditions'] = $table_name.'.'.$this->getPrimaryKey().' = '.$ids[0].$conditions;
1007                  $result =& $this->_findEvery($options);
1008                  if (!$expects_array && $result !== false){
1009                      return $result[0];
1010                  }
1011                  return  $result;
1012                  break;
1013  
1014              default:
1015                  $without_conditions = empty($options['conditions']) ? true : false;
1016                  $ids_condition = $this->getPrimaryKey().' IN ('.join(', ',$ids).')';
1017                  $options['conditions'] = $ids_condition.$conditions;
1018  
1019                  $result =& $this->_findEvery($options);
1020                  if(is_array($result) && (count($result) != $num_ids && $without_conditions)){
1021                      $result = false;
1022                  }
1023                  return $result;
1024                  break;
1025          }
1026  
1027      }
1028  
1029      function _extractOptionsFromArgs(&$args)
1030      {
1031          $last_arg = count($args)-1;
1032          return isset($args[$last_arg]) && is_array($args[$last_arg]) && $this->_isOptionsHash($args[$last_arg]) ? array_pop($args) : array();
1033      }
1034  
1035      function _isOptionsHash($options)
1036      {
1037          if (isset($options[0])){
1038              return false;
1039          }
1040          $valid_keys = array('conditions', 'include', 'joins', 'limit', 'offset', 'group', 'order', 'sort', 'bind', 'select','select_prefix', 'readonly');
1041          foreach (array_keys($options) as $key){
1042              if (!in_array($key,$valid_keys)){
1043                  return false;
1044              }
1045          }
1046          return true;
1047      }
1048  
1049      function _extractConditionsFromArgs($args, $options)
1050      {
1051          if(empty($args)){
1052              $fetch = 'all';
1053          } else {
1054              $fetch = $args[0];
1055          }
1056          $num_args = count($args);
1057  
1058          // deprecated: acts like findFirstBySQL
1059          if ($num_args === 1 && !is_numeric($args[0]) && is_string($args[0]) && $args[0] != 'all' && $args[0] != 'first'){
1060              //  $Users->find("last_name = 'Williams'");    => find('first',"last_name = 'Williams'");
1061              Ak::deprecateWarning(array("AR::find('%sql') is ambiguous and therefore deprecated, use AR::find('first',%sql) instead", '%sql'=>$args[0]));
1062              $options = array('conditions'=> $args[0]);
1063              return array('first',$options);
1064          } //end
1065  
1066          // set fetch_mode to 'all' if none is given
1067          if (!is_numeric($fetch) && !is_array($fetch) && $fetch != 'all' && $fetch != 'first') {
1068              array_unshift($args, 'all');
1069              $num_args = count($args);
1070          }
1071          if ($num_args > 1) {
1072              if (is_string($args[1])){
1073                  //  $Users->find(:fetch_mode,"first_name = ?",'Tim');
1074                  $fetch = array_shift($args);
1075                  $options = array_merge($options, array('conditions'=>$args));   //TODO: merge_conditions
1076              }elseif (is_array($args[1])) {
1077                  //  $Users->find(:fetch_mode,array('first_name = ?,'Tim'));
1078                  $fetch = array_shift($args);
1079                  $options = array_merge($options, array('conditions'=>$args[0]));   //TODO: merge_conditions
1080              }
1081          }
1082  
1083          return array($fetch,$options);
1084      }
1085  
1086      function _sanitizeConditionsVariables(&$options)
1087      {
1088          if(!empty($options['conditions']) && is_array($options['conditions'])){
1089              if (isset($options['conditions'][0]) && strstr($options['conditions'][0], '?') && count($options['conditions']) > 1){
1090                  //array('conditions' => array("name=?",$name))
1091                  $pattern = array_shift($options['conditions']);
1092                  $options['bind'] = array_values($options['conditions']);
1093                  $options['conditions'] = $pattern;
1094              }elseif (isset($options['conditions'][0])){
1095                  //array('conditions' => array("user_name = :user_name", ':user_name' => 'hilario')
1096                  $pattern = array_shift($options['conditions']);
1097                  $options['conditions'] = str_replace(array_keys($options['conditions']), array_values($this->getSanitizedConditionsArray($options['conditions'])),$pattern);
1098              }else{
1099                  //array('conditions' => array('user_name'=>'Hilario'))
1100                  $options['conditions'] = join(' AND ',(array)$this->getAttributesQuoted($options['conditions']));
1101              }
1102          }
1103          
1104      }
1105  
1106      function &findFirst()
1107      {
1108          if(!isset($this->_activeRecordHasBeenInstantiated)){
1109              return Ak::handleStaticCall();
1110          }
1111          $args = func_get_args();
1112          $result =& Ak::call_user_func_array(array(&$this,'find'), array_merge(array('first'),$args));
1113          return $result;
1114      }
1115  
1116      function &findAll()
1117      {
1118          if(!isset($this->_activeRecordHasBeenInstantiated)){
1119              return Ak::handleStaticCall();
1120          }
1121          $args = func_get_args();
1122          $result =& Ak::call_user_func_array(array(&$this,'find'), array_merge(array('all'),$args));
1123          return $result;
1124      }
1125  
1126  
1127      /**
1128      * Works like find_all, but requires a complete SQL string. Examples:
1129      * $Post->findBySql("SELECT p.*, c.author FROM posts p, comments c WHERE p.id = c.post_id");
1130      * $Post->findBySql(array("SELECT * FROM posts WHERE author = ? AND created_on > ?", $author_id, $start_date));
1131      */
1132      function &findBySql($sql, $limit = null, $offset = null, $bindings = null)
1133      {
1134          if ($limit || $offset){
1135              Ak::deprecateWarning("You're calling AR::findBySql with \$limit or \$offset parameters. This has been deprecated.");
1136              $this->_db->addLimitAndOffset($sql, array('limit'=>$limit,'offset'=>$offset));
1137          }
1138          if(!isset($this->_activeRecordHasBeenInstantiated)){
1139              return Ak::handleStaticCall();
1140          }
1141          $objects = array();
1142          $records = $this->_db->select ($sql,'selecting');
1143          foreach ($records as $record){
1144              $objects[] =& $this->instantiate($this->getOnlyAvailableAttributes($record), false);
1145          }
1146          return $objects;
1147      }
1148  
1149      /**
1150      * This function pretends to emulate RoR finders until AkActiveRecord::addMethod becomes stable on future PHP versions.
1151      * @todo use PHP5 __call method for handling the magic finder methods like findFirstByUnsenameAndPassword('bermi','pass')
1152      */
1153      function &findFirstBy()
1154      {
1155          if(!isset($this->_activeRecordHasBeenInstantiated)){
1156              return Ak::handleStaticCall();
1157          }
1158          $args = func_get_args();
1159          array_unshift($args,'first');
1160          $result =& Ak::call_user_func_array(array(&$this,'findBy'), $args);
1161          return $result;
1162      }
1163  
1164      function &findLastBy()
1165      {
1166          if(!isset($this->_activeRecordHasBeenInstantiated)){
1167              return Ak::handleStaticCall();
1168          }
1169          $args = func_get_args();
1170          $options = $this->_extractOptionsFromArgs($args);
1171          $options['order'] = $this->getPrimaryKey().' DESC';
1172          array_push($args, $options);
1173          $result =& Ak::call_user_func_array(array(&$this,'findFirstBy'), $args);
1174          return $result;
1175      }
1176  
1177      function &findAllBy()
1178      {
1179          if(!isset($this->_activeRecordHasBeenInstantiated)){
1180              return Ak::handleStaticCall();
1181          }
1182          $args = func_get_args();
1183          array_unshift($args,'all');
1184          $result =& Ak::call_user_func_array(array(&$this,'findBy'), $args);
1185          return $result;
1186      }
1187  
1188      /**
1189      * This method allows you to use finders in a more flexible way like:
1190      * 
1191      *   findBy('username AND password', $username, $password);
1192      *   findBy('age > ? AND name:contains', 18, 'Joe');
1193      *   findBy('is_active = true AND session_id', session_id());
1194      *   
1195      */
1196      function &findBy()
1197      {
1198          if(!isset($this->_activeRecordHasBeenInstantiated)){
1199              return Ak::handleStaticCall();
1200          }
1201          $args = func_get_args();
1202          $find_by_sql = array_shift($args);
1203          if($find_by_sql == 'all' || $find_by_sql == 'first'){
1204              $fetch = $find_by_sql;
1205              $find_by_sql = array_shift($args);
1206          }else{
1207              $fetch = 'all';
1208          }
1209  
1210          $options = $this->_extractOptionsFromArgs($args);
1211          
1212          $query_values = $args;
1213          $query_arguments_count = count($query_values);
1214  
1215          list($sql, $requested_args) = $this->_getFindBySqlAndColumns($find_by_sql, $query_values);
1216  
1217          if($query_arguments_count != count($requested_args)){
1218              trigger_error(Ak::t('Argument list did not match expected set. Requested arguments are:').join(', ',$requested_args),E_USER_ERROR);
1219              $false = false;
1220              return $false;
1221          }
1222  
1223          $true_bool_values = array(true,1,'true','True','TRUE','1','y','Y','yes','Yes','YES','s','Si','SI','V','v','T','t');
1224  
1225          foreach ($requested_args as $k=>$v){
1226              switch ($this->getColumnType($v)) {
1227                  case 'boolean':
1228                      $query_values[$k] = in_array($query_values[$k],$true_bool_values) ? true : false;
1229                      break;
1230  
1231                  case 'date':
1232                  case 'datetime':
1233                      $query_values[$k] = str_replace('/','-', $this->castAttributeForDatabase($k,$query_values[$k],false));
1234                      break;
1235  
1236                  default:
1237                      break;
1238              }
1239          }
1240  
1241          $conditions = array($sql);
1242          foreach ($query_values as $bind_value){
1243              $conditions[] = $bind_value;
1244          }
1245          /**
1246          * @todo merge_conditions
1247          */
1248          $options['conditions'] = $conditions;
1249  
1250          $result =& Ak::call_user_func_array(array(&$this,'find'), array($fetch,$options));
1251          return $result;
1252      }
1253  
1254  
1255      function _getFindBySqlAndColumns($find_by_sql, &$query_values)
1256      {
1257          $sql = str_replace(array('(',')','||','|','&&','&','  '),array(' ( ',' ) ',' OR ',' OR ',' AND ',' AND ',' '), $find_by_sql);
1258          $operators = array('AND','and','(',')','&','&&','NOT','<>','OR','|','||');
1259          $pieces = explode(' ',$sql);
1260          $pieces = array_diff($pieces,array(' ',''));
1261          $params = array_diff($pieces,$operators);
1262          $operators = array_diff($pieces,$params);
1263  
1264          $new_sql = '';
1265          $parameter_count = 0;
1266          $requested_args = array();
1267          foreach ($pieces as $piece){
1268              if(in_array($piece,$params) && $this->hasColumn($piece)){
1269                  $new_sql .= $piece.' = ? ';
1270                  $requested_args[$parameter_count] = $piece;
1271                  $parameter_count++;
1272              }elseif (!in_array($piece,$operators)){
1273  
1274                  if(strstr($piece,':')){
1275                      $_tmp_parts = explode(':',$piece);
1276                      if($this->hasColumn($_tmp_parts[0])){
1277                          $query_values[$parameter_count] = isset($query_values[$parameter_count]) ? $query_values[$parameter_count] : $this->get($_tmp_parts[0]);
1278                          switch (strtolower($_tmp_parts[1])) {
1279                              case 'like':
1280                              case '%like%':
1281                              case 'is':
1282                              case 'has':
1283                              case 'contains':
1284                                  $query_values[$parameter_count] = '%'.$query_values[$parameter_count].'%';
1285                                  $new_sql .= $_tmp_parts[0]." LIKE ? ";
1286                                  break;
1287                              case 'like_left':
1288                              case 'like%':
1289                              case 'begins':
1290                              case 'begins_with':
1291                              case 'starts':
1292                              case 'starts_with':
1293                                  $query_values[$parameter_count] = $query_values[$parameter_count].'%';
1294                                  $new_sql .= $_tmp_parts[0]." LIKE ? ";
1295                                  break;
1296                              case 'like_right':
1297                              case '%like':
1298                              case 'ends':
1299                              case 'ends_with':
1300                              case 'finishes':
1301                              case 'finishes_with':
1302                                  $query_values[$parameter_count] = '%'.$query_values[$parameter_count];
1303                                  $new_sql .= $_tmp_parts[0]." LIKE ? ";
1304                                  break;
1305                              default:
1306                                  $query_values[$parameter_count] = $query_values[$parameter_count];
1307                                  $new_sql .= $_tmp_parts[0].' '.$_tmp_parts[1].' ? ';
1308                                  break;
1309                          }
1310                          $requested_args[$parameter_count] = $_tmp_parts[0];
1311                          $parameter_count++;
1312                      }else {
1313                          $new_sql .= $_tmp_parts[0];
1314                      }
1315                  }else{
1316                      $new_sql .= $piece.' ';
1317                  }
1318              }else{
1319                  $new_sql .= $piece.' ';
1320              }
1321          }
1322  
1323          return array($new_sql, $requested_args);
1324      }
1325  
1326  
1327      /**
1328       *  Given a condition that uses bindings like "user = ?  AND created_at > ?" will return a
1329       * string replacing the "?" bindings with the column values for current Active Record
1330       * 
1331       * @return string
1332       */
1333      function _getVariableSqlCondition($variable_condition)
1334      {
1335          $query_values = array();
1336          list($sql, $requested_columns) = $this->_getFindBySqlAndColumns($variable_condition, $query_values);
1337          $replacements = array();
1338          $sql = preg_replace('/((('.join($requested_columns,'|').') = \?) = \?)/','$2', $sql);
1339          foreach ($requested_columns as $attribute){
1340              $replacements[$attribute] = $this->castAttributeForDatabase($attribute, $this->get($attribute));
1341          }
1342          return trim(preg_replace('/('.join('|',array_keys($replacements)).')\s+([^\?]+)\s+\?/e', "isset(\$replacements['\\1']) ? '\\1 \\2 '.\$replacements['\\1']:'\\1 \\2 null'", $sql));
1343      }
1344  
1345  
1346      function constructFinderSql($options, $select_from_prefix = 'default')
1347      {
1348          $sql = isset($options['select_prefix']) ? $options['select_prefix'] : ($select_from_prefix == 'default' ? 'SELECT '.(!empty($options['joins'])?$this->getTableName().'.':'') .'* FROM '.$this->getTableName() : $select_from_prefix);
1349          $sql .= !empty($options['joins']) ? ' '.$options['joins'] : '';
1350  
1351          $this->addConditions($sql, isset($options['conditions']) ? $options['conditions'] : array());
1352  
1353          // Create an alias for order
1354          if(empty($options['order']) && !empty($options['sort'])){
1355              $options['order'] = $options['sort'];
1356          }
1357  
1358          $sql .= !empty($options['group']) ? ' GROUP BY '.$options['group'] : ''; 
1359          $sql .= !empty($options['order']) ? ' ORDER BY '.$options['order'] : '';
1360  
1361          $this->_db->addLimitAndOffset($sql,$options);
1362  
1363          return $sql;
1364      }
1365  
1366  
1367      /**
1368      * Adds a sanitized version of $conditions to the $sql string. Note that the passed $sql string is changed.
1369      */
1370      function addConditions(&$sql, $conditions = null, $table_alias = null)
1371      {
1372          $concat = empty($sql) ? '' : ' WHERE ';
1373          if (empty($conditions) && $this->_getDatabaseType() == 'sqlite') $conditions = '1';  // sqlite HACK
1374          if(!empty($conditions)){
1375              $sql  .= $concat.$conditions;
1376              $concat = ' AND ';
1377          }
1378  
1379          if($this->getInheritanceColumn() !== false && $this->descendsFromActiveRecord($this)){
1380              $type_condition = $this->typeCondition($table_alias);
1381              $sql .= !empty($type_condition) ? $concat.$type_condition : '';
1382          }
1383          return $sql;
1384      }
1385  
1386      /**
1387      * Gets a sanitized version of the input array. Each element will be escaped
1388      */
1389      function getSanitizedConditionsArray($conditions_array)
1390      {
1391          $result = array();
1392          foreach ($conditions_array as $k=>$v){
1393              $k = str_replace(':','',$k); // Used for Oracle type bindings
1394              if($this->hasColumn($k)){
1395                  $v = $this->castAttributeForDatabase($k, $v);
1396                  $result[$k] = $v;
1397              }
1398          }
1399          return $result;
1400      }
1401  
1402  
1403      /**
1404      * This functions is used to get the conditions from an AkRequest object
1405      */
1406      function getConditions($conditions, $prefix = '', $model_name = null)
1407      {
1408          $model_name = isset($model_name) ? $model_name : $this->getModelName();
1409          $model_conditions = !empty($conditions[$model_name]) ? $conditions[$model_name] : $conditions;
1410          if(is_a($this->$model_name)){
1411              $model_instance =& $this->$model_name;
1412          }else{
1413              $model_instance =& $this;
1414          }
1415          $new_conditions = array();
1416          if(is_array($model_conditions)){
1417              foreach ($model_conditions as $col=>$value){
1418                  if($model_instance->hasColumn($col)){
1419                      $new_conditions[$prefix.$col] = $value;
1420                  }
1421              }
1422          }
1423          return $new_conditions;
1424      }
1425  
1426      /**
1427      *
1428      * @access private
1429      */
1430      function _quoteColumnName($column_name)
1431      {
1432          return $this->_db->nameQuote.$column_name.$this->_db->nameQuote;
1433      }
1434  
1435  
1436  
1437  
1438      /**
1439      * EXPERIMENTAL: Will allow to create finders when PHP includes aggregate_methods as a stable feature on PHP4, for PHP5 we might use __call
1440      *
1441      * @access private
1442      */
1443      function _buildFinders($finderFunctions = array('find','findFirst'))
1444      {
1445          if(!$this->_dynamicMethods){
1446              return;
1447          }
1448          $columns = !is_array($this->_dynamicMethods) ? array_keys($this->getColumns()) : $this->_dynamicMethods;
1449          $class_name = 'ak_'.md5(serialize($columns));
1450          if(!class_exists($class_name)){
1451              $permutations = Ak::permute($columns);
1452              $implementations = '';
1453              foreach ($finderFunctions as $finderFunction){
1454                  foreach ($permutations as $permutation){
1455                      $permutation = array_map(array('AkInflector','camelize'),$permutation);
1456                      foreach ($permutation as $k=>$v){
1457                          $method_name = $finderFunction.'By'.join($permutation,'And');
1458                          $implementation = 'function &'.$method_name.'(';
1459                          $first_param = '';
1460                          $params = '';
1461                          $i = 1;
1462                          foreach ($permutation as $column){
1463                              $column = AkInflector::underscore($column);
1464                              $params .= "$$column, ";
1465                              $first_param .= "$column ";
1466                              $i++;
1467                          }
1468                          $implementation .= trim($params,' ,')."){\n";
1469                          $implementation .= '$options = func_num_args() == '.$i.' ? func_get_arg('.($i-1).') : array();'."\n";
1470                          $implementation .= 'return $this->'.$finderFunction.'By(\''.$first_param.'\', '.trim($params,' ,').", \$options);\n }\n";
1471                          $implementations[$method_name] = $implementation;
1472                          array_shift($permutation);
1473                      }
1474                  }
1475              }
1476              eval('class '.$class_name.' { '.join("\n",$implementations).' } ');
1477          }
1478  
1479          aggregate_methods(&$this, $class_name);
1480      }
1481  
1482  
1483      /**
1484      * Finder methods must instantiate through this method to work with the single-table inheritance model and
1485      * eager loading associations.
1486      * that makes it possible to create objects of different types from the same table.
1487      */
1488      function &instantiate($record, $set_as_new = true)
1489      {
1490          $inheritance_column = $this->getInheritanceColumn();
1491          if(!empty($record[$inheritance_column])){
1492              $inheritance_column = $record[$inheritance_column];
1493              $inheritance_model_name = AkInflector::camelize($inheritance_column);
1494              @require_once(AkInflector::toModelFilename($inheritance_model_name));
1495              if(!class_exists($inheritance_model_name)){
1496                  trigger_error($this->t("The single-table inheritance mechanism failed to locate the subclass: '%class_name'. ".
1497                  "This error is raised because the column '%column' is reserved for storing the class in case of inheritance. ".
1498                  "Please rename this column if you didn't intend it to be used for storing the inheritance class ".
1499                  "or overwrite #{self.to_s}.inheritance_column to use another column for that information.",
1500                  array('%class_name'=>$inheritance_model_name, '%column'=>$this->getInheritanceColumn())),E_USER_ERROR);
1501              }
1502          }
1503  
1504          $model_name = isset($inheritance_model_name) ? $inheritance_model_name : $this->getModelName();
1505          $object =& new $model_name('attributes', $record);
1506  
1507          $object->_newRecord = $set_as_new;
1508          
1509          $object->afterInstantiate();
1510          $object->notifyObservers('afterInstantiate');
1511          
1512          (AK_CLI && AK_ENVIRONMENT == 'development') ? $object ->toString() : null;
1513  
1514          return $object;
1515      }
1516  
1517      /*/Finding records*/
1518  
1519  
1520  
1521      /**
1522                             Table inheritance
1523       ====================================================================
1524       */
1525      function descendsFromActiveRecord(&$object)
1526      {
1527          if(substr(strtolower(get_parent_class($object)),-12) == 'activerecord'){
1528              return true;
1529          }
1530          if(!method_exists($object, 'getInheritanceColumn')){
1531              return false;
1532          }
1533          $inheritance_column = $object->getInheritanceColumn();
1534          return !empty($inheritance_column);
1535      }
1536  
1537      /**
1538       * Gets the column name for use with single table inheritance. Can be overridden in subclasses.
1539      */
1540      function getInheritanceColumn()
1541      {
1542          return empty($this->_inheritanceColumn) ? ($this->hasColumn('type') ? 'type' : false ) : $this->_inheritanceColumn;
1543      }
1544  
1545      /**
1546       * Defines the column name for use with single table inheritance. Can be overridden in subclasses.
1547       */
1548      function setInheritanceColumn($column_name)
1549      {
1550          if(!$this->hasColumn($column_name)){
1551              trigger_error(Ak::t('Could not set "%column_name" as the inheritance column as this column is not available on the database.',array('%column_name'=>$column_name)), E_USER_NOTICE);
1552              return false;
1553          }elseif($this->getColumnType($column_name) != 'string'){
1554              trigger_error(Ak::t('Could not set %column_name as the inheritance column as this column type is "%column_type" instead of "string".',array('%column_name'=>$column_name,'%column_type'=>$this->getColumnType($column_name))), E_USER_NOTICE);
1555              return false;
1556          }else{
1557              $this->_inheritanceColumn = $column_name;
1558              return true;
1559          }
1560      }
1561  
1562  
1563      function getSubclasses()
1564      {
1565          $current_class = get_class($this);
1566          $subclasses = array();
1567          $classes = get_declared_classes();
1568  
1569          while ($class = array_shift($classes)) {
1570              $parent_class = get_parent_class($class);
1571              if($parent_class == $current_class || in_array($parent_class,$subclasses)){
1572                  $subclasses[] = $class;
1573              }elseif(!empty($parent_class)){
1574                  $classes[] = $parent_class;
1575              }
1576          }
1577          $subclasses = array_unique(array_map(array(&$this,'_getModelName'),$subclasses));
1578          return $subclasses;
1579      }
1580  
1581  
1582      function typeCondition($table_alias = null)
1583      {
1584          $inheritance_column = $this->getInheritanceColumn();
1585          $type_condition = array();
1586          $table_name = $this->getTableName();
1587          $available_types = array_merge(array($this->getModelName()),$this->getSubclasses());
1588          foreach ($available_types as $subclass){
1589              $type_condition[] = ' '.($table_alias != null ? $table_alias : $table_name).'.'.$inheritance_column.' = \''.AkInflector::humanize(AkInflector::underscore($subclass)).'\' ';
1590          }
1591          return empty($type_condition) ? '' : '('.join('OR',$type_condition).') ';
1592      }
1593  
1594      /*/Table inheritance*/
1595  
1596  
1597  
1598      /**
1599                           Setting Attributes
1600      ====================================================================
1601      See also: Getting Attributes, Model Attributes, Toggling Attributes, Counting Attributes.
1602      */
1603      function setAttribute($attribute, $value, $inspect_for_callback_child_method = AK_ACTIVE_RECORD_ENABLE_CALLBACK_SETTERS, $compose_after_set = true)
1604      {
1605          if($attribute[0] == '_'){
1606              return false;
1607          }
1608  
1609          if($this->isFrozen()){
1610              return false;
1611          }
1612          if($inspect_for_callback_child_method === true && method_exists($this,'set'.AkInflector::camelize($attribute))){
1613              static $watchdog;
1614              $watchdog[$attribute] = @$watchdog[$attribute]+1;
1615              if($watchdog[$attribute] == 5000){
1616                  if((!defined('AK_ACTIVE_RECORD_PROTECT_SET_RECURSION')) || defined('AK_ACTIVE_RECORD_PROTECT_SET_RECURSION') && AK_ACTIVE_RECORD_PROTECT_SET_RECURSION){
1617                      trigger_error(Ak::t('You are calling recursively AkActiveRecord::setAttribute by placing parent::setAttribute() or  parent::set() on your model "%method" method. In order to avoid this, set the 3rd paramenter of parent::setAttribute to FALSE. If this was the behaviour you expected, please define the constant AK_ACTIVE_RECORD_PROTECT_SET_RECURSION and set it to false',array('%method'=>'set'.AkInflector::camelize($attribute))),E_USER_ERROR);
1618                      return false;
1619                  }
1620              }
1621              $this->{$attribute.'_before_type_cast'} = $value;
1622              return $this->{'set'.AkInflector::camelize($attribute)}($value);
1623          }
1624          if($this->hasAttribute($attribute)){
1625              $this->{$attribute.'_before_type_cast'} = $value;
1626              $this->$attribute = $value;
1627              if($compose_after_set && !empty($this->_combinedAttributes) && !$this->requiredForCombination($attribute)){
1628                  $combined_attributes = $this->_getCombinedAttributesWhereThisAttributeIsUsed($attribute);
1629                  foreach ($combined_attributes as $combined_attribute){
1630                      $this->composeCombinedAttribute($combined_attribute);
1631                  }
1632              }
1633              if ($compose_after_set && $this->isCombinedAttribute($attribute)){
1634                  $this->decomposeCombinedAttribute($attribute);
1635              }
1636          }elseif(substr($attribute,-12) == 'confirmation' && $this->hasAttribute(substr($attribute,0,-13))){
1637              $this->$attribute = $value;
1638          }
1639  
1640          if($this->_internationalize){
1641              if(is_array($value)){
1642                  $this->setAttributeLocales($attribute, $value);
1643              }elseif(is_string($inspect_for_callback_child_method)){
1644                  $this->setAttributeByLocale($attribute, $value, $inspect_for_callback_child_method);
1645              }else{
1646                  $this->_groupInternationalizedAttribute($attribute, $value);
1647              }
1648          }
1649          return true;
1650      }
1651  
1652      function set($attribute, $value = null, $inspect_for_callback_child_method = true, $compose_after_set = true)
1653      {
1654          if(is_array($attribute)){
1655              return $this->setAttributes($attribute);
1656          }
1657          return $this->setAttribute($attribute, $value, $inspect_for_callback_child_method, $compose_after_set);
1658      }
1659  
1660      /**
1661      * Allows you to set all the attributes at once by passing in an array with 
1662      * keys matching the attribute names (which again matches the column names). 
1663      * Sensitive attributes can be protected from this form of mass-assignment by 
1664      * using the $this->setProtectedAttributes method. Or you can alternatively 
1665      * specify which attributes can be accessed in with the $this->setAccessibleAttributes method. 
1666      * Then all the attributes not included in that won?t be allowed to be mass-assigned.
1667      */
1668      function setAttributes($attributes, $override_attribute_protection = false)
1669      {
1670          $this->parseAkelosArgs($attributes);
1671          if(!$override_attribute_protection){
1672              $attributes = $this->removeAttributesProtectedFromMassAssignment($attributes);
1673          }
1674          if(!empty($attributes) && is_array($attributes)){
1675              foreach ($attributes as $k=>$v){
1676                  $this->setAttribute($k, $v);
1677              }
1678          }
1679      }
1680  
1681  
1682      function setId($value)
1683      {
1684          if($this->isFrozen()){
1685              return false;
1686          }
1687          $pk = $this->getPrimaryKey();
1688          $this->$pk = $value;
1689          return true;
1690      }
1691  
1692  
1693      /*/Setting Attributes*/
1694  
1695      /**
1696                           Getting Attributes
1697      ====================================================================
1698      See also: Setting Attributes, Model Attributes, Toggling Attributes, Counting Attributes.
1699      */
1700  
1701      function getAttribute($attribute, $inspect_for_callback_child_method = AK_ACTIVE_RECORD_ENABLE_CALLBACK_GETTERS)
1702      {
1703          if($attribute[0] == '_'){
1704              return false;
1705          }
1706  
1707          if($inspect_for_callback_child_method === true && method_exists($this,'get'.AkInflector::camelize($attribute))){
1708              static $watchdog;
1709              $watchdog[@$attribute] = @$watchdog[$attribute]+1;
1710              if($watchdog[$attribute] == 5000){
1711                  if((!defined('AK_ACTIVE_RECORD_PROTECT_GET_RECURSION')) || defined('AK_ACTIVE_RECORD_PROTECT_GET_RECURSION') && AK_ACTIVE_RECORD_PROTECT_GET_RECURSION){
1712                      trigger_error(Ak::t('You are calling recursivelly AkActiveRecord::getAttribute by placing parent::getAttribute() or  parent::get() on your model "%method" method. In order to avoid this, set the 2nd paramenter of parent::getAttribute to FALSE. If this was the behaviour you expected, please define the constant AK_ACTIVE_RECORD_PROTECT_GET_RECURSION and set it to false',array('%method'=>'get'.AkInflector::camelize($attribute))),E_USER_ERROR);
1713                      return false;
1714                  }
1715              }
1716              $value = $this->{'get'.AkInflector::camelize($attribute)}();
1717              return $this->getInheritanceColumn() === $attribute ? AkInflector::humanize(AkInflector::underscore($value)) : $value;
1718          }
1719          if(isset($this->$attribute) || (!isset($this->$attribute) && $this->isCombinedAttribute($attribute))){
1720              if($this->hasAttribute($attribute)){
1721                  if (!empty($this->_combinedAttributes) && $this->isCombinedAttribute($attribute)){
1722                      $this->composeCombinedAttribute($attribute);
1723                  }
1724                  return isset($this->$attribute) ? $this->$attribute : null;
1725              }elseif($this->_internationalize && $this->_isInternationalizeCandidate($attribute)){
1726                  if(!empty($this->$attribute) && is_string($this->$attribute)){
1727                      return $this->$attribute;
1728                  }
1729                  $current_locale = $this->getCurrentLocale();
1730                  if(!empty($this->$attribute[$current_locale]) && is_array($this->$attribute)){
1731                      return $this->$attribute[$current_locale];
1732                  }
1733                  return $this->getAttribute($current_locale.'_'.$attribute);
1734              }
1735          }
1736  
1737          if($this->_internationalize){
1738              return $this->getAttributeByLocale($attribute, is_bool($inspect_for_callback_child_method) ? $this->getCurrentLocale() : $inspect_for_callback_child_method);
1739          }
1740          return null;
1741      }
1742  
1743      function get($attribute = null, $inspect_for_callback_child_method = true)
1744      {
1745          return !isset($attribute) ? $this->getAttributes($inspect_for_callback_child_method) : $this->getAttribute($attribute, $inspect_for_callback_child_method);
1746      }
1747  
1748      /**
1749      * Returns an array of all the attributes with their names as keys and clones of their objects as values in case they are objects.
1750      */
1751      function getAttributes()
1752      {
1753          $attributes = array();
1754          $available_attributes = $this->getAvailableAttributes();
1755          foreach ($available_attributes as $available_attribute){
1756              $attribute = $this->getAttribute($available_attribute['name']);
1757              $attributes[$available_attribute['name']] = AK_PHP5 && is_object($attribute) ? clone($attribute) : $attribute;
1758          }
1759  
1760          if($this->_internationalize){
1761              $current_locale = $this->getCurrentLocale();
1762              foreach ($this->getInternationalizedColumns() as $column=>$languages){
1763                  if(empty($attributes[$column]) && isset($attributes[$current_locale.'_'.$column]) && in_array($current_locale,$languages)){
1764                      $attributes[$column] = $attributes[$current_locale.'_'.$column];
1765                  }
1766              }
1767          }
1768  
1769          return $attributes;
1770      }
1771  
1772  
1773      /**
1774      * Every Active Record class must use "id" as their primary ID. This getter overwrites the native id method, which isn't being used in this context.
1775      */
1776      function getId()
1777      {
1778          return $this->{$this->getPrimaryKey()};
1779      }
1780  
1781      /*/Getting Attributes*/
1782  
1783  
1784  
1785      /**
1786                           Toggling Attributes
1787      ====================================================================
1788      See also: Setting Attributes, Getting Attributes.
1789      */
1790      /**
1791      * Turns an attribute that's currently true into false and vice versa. Returns attribute value.
1792      */
1793      function toggleAttribute($attribute)
1794      {
1795          $value = $this->getAttribute($attribute);
1796          $new_value = $value ? false : true;
1797          $this->setAttribute($attribute, $new_value);
1798          return $new_value;
1799      }
1800  
1801  
1802      /**
1803      * Toggles the attribute and saves the record.
1804      */
1805      function toggleAttributeAndSave($attribute)
1806      {
1807          $value = $this->toggleAttribute($attribute);
1808          if($this->updateAttribute($attribute, $value)){
1809              return $value;
1810          }
1811          return null;
1812      }
1813  
1814      /*/Toggling Attributes*/
1815  
1816  
1817      /**
1818                           Counting Attributes
1819      ====================================================================
1820      See also: Counting Records, Setting Attributes, Getting Attributes.
1821      */
1822  
1823      /**
1824      * Increments the specified counter by one. So $DiscussionBoard->incrementCounter("post_count", 
1825      * $discussion_board_id); would increment the "post_count" counter on the board responding to 
1826      * $discussion_board_id. This is used for caching aggregate values, so that they doesn't need to 
1827      * be computed every time. Especially important for looping over a collection where each element 
1828      * require a number of aggregate values. Like the $DiscussionBoard that needs to list both the number of posts and comments.
1829      */
1830      function incrementCounter($counter_name, $id, $difference = 1)
1831      {
1832          return $this->updateAll("$counter_name = $counter_name + $difference", $this->getPrimaryKey().' = '.$this->castAttributeForDatabase($this->getPrimaryKey(), $id)) === 1;
1833      }
1834  
1835      /**
1836      * Works like AkActiveRecord::incrementCounter, but decrements instead.
1837      */
1838      function decrementCounter($counter_name, $id, $difference = 1)
1839      {
1840          return $this->updateAll("$counter_name = $counter_name - $difference", $this->getPrimaryKey().' = '.$this->castAttributeForDatabase($this->getPrimaryKey(), $id)) === 1;
1841      }
1842  
1843      /**
1844      * Initializes the attribute to zero if null and subtracts one. Only makes sense for number-based attributes. Returns attribute value.
1845      */
1846      function decrementAttribute($attribute)
1847      {
1848          if(!isset($this->$attribute)){
1849              $this->$attribute = 0;
1850          }
1851          return $this->$attribute -= 1;
1852      }
1853  
1854      /**
1855      * Decrements the attribute and saves the record.
1856      */
1857      function decrementAndSaveAttribute($attribute)
1858      {
1859          return $this->updateAttribute($attribute,$this->decrementAttribute($attribute));
1860      }
1861  
1862  
1863      /**
1864      * Initializes the attribute to zero if null and adds one. Only makes sense for number-based attributes. Returns attribute value.
1865      */
1866      function incrementAttribute($attribute)
1867      {
1868          if(!isset($this->$attribute)){
1869              $this->$attribute = 0;
1870          }
1871          return $this->$attribute += 1;
1872      }
1873  
1874      /**
1875      * Increments the attribute and saves the record.
1876      */
1877      function incrementAndSaveAttribute($attribute)
1878      {
1879          return $this->updateAttribute($attribute,$this->incrementAttribute($attribute));
1880      }
1881  
1882      /*/Counting Attributes*/
1883  
1884      /**
1885                           Protecting attributes
1886      ====================================================================
1887      */
1888  
1889      /**
1890      * If this macro is used, only those attributed named in it will be accessible 
1891      * for mass-assignment, such as new ModelName($attributes) and $this->attributes($attributes). 
1892      * This is the more conservative choice for mass-assignment protection. 
1893      * If you'd rather start from an all-open default and restrict attributes as needed, 
1894      * have a look at AkActiveRecord::setProtectedAttributes().
1895      */
1896      function setAccessibleAttributes()
1897      {
1898          $args = func_get_args();
1899          $this->_accessibleAttributes = array_unique(array_merge((array)$this->_accessibleAttributes, $args));
1900      }
1901  
1902      /**
1903       * Attributes named in this macro are protected from mass-assignment, such as 
1904       * new ModelName($attributes) and $this->attributes(attributes). Their assignment 
1905       * will simply be ignored. Instead, you can use the direct writer methods to do assignment. 
1906       * This is meant to protect sensitive attributes to be overwritten by URL/form hackers. 
1907       * 
1908       * Example:
1909       * <code>
1910       *   class Customer extends ActiveRecord
1911       *    {
1912       *      function Customer()
1913       *      {
1914       *          $this->setProtectedAttributes('credit_rating');
1915       *      }
1916       *    }
1917       *  
1918       *    $Customer = new Customer('name' => 'David', 'credit_rating' => 'Excellent');
1919       *    $Customer->credit_rating // => null
1920       *    $Customer->attributes(array('description' => 'Jolly fellow', 'credit_rating' => 'Superb'));
1921       *    $Customer->credit_rating // => null
1922       *  
1923       *    $Customer->credit_rating = 'Average'
1924       *    $Customer->credit_rating // => 'Average'
1925       *  </code>
1926       */    
1927      function setProtectedAttributes()
1928      {
1929          $args = func_get_args();
1930          $this->_protectedAttributes = array_unique(array_merge((array)$this->_protectedAttributes, $args));
1931      }
1932  
1933      function removeAttributesProtectedFromMassAssignment($attributes)
1934      {
1935          if(!empty($this->_accessibleAttributes) && is_array($this->_accessibleAttributes) &&  is_array($attributes)){
1936              foreach (array_keys($attributes) as $k){
1937                  if(!in_array($k,$this->_accessibleAttributes)){
1938                      unset($attributes[$k]);
1939                  }
1940              }
1941          }elseif (!empty($this->_protectedAttributes) && is_array($this->_protectedAttributes) &&  is_array($attributes)){
1942              foreach (array_keys($attributes) as $k){
1943                  if(in_array($k,$this->_protectedAttributes)){
1944                      unset($attributes[$k]);
1945                  }
1946              }
1947          }
1948          return $attributes;
1949      }
1950  
1951      /*/Protecting attributes*/
1952  
1953  
1954      /**
1955                            Model Attributes
1956       ====================================================================
1957       See also: Getting Attributes, Setting Attributes.
1958       */
1959      /**
1960      * Returns an array of all the attributes that have been specified for serialization as keys and the objects as values.
1961      */
1962      function getSerializedAttributes()
1963      {
1964          return isset($this->_serializedAttributes) ? $this->_serializedAttributes : array();
1965      }
1966  
1967      function getAvailableAttributes()
1968      {
1969          return array_merge($this->getColumns(), $this->getAvailableCombinedAttributes());
1970      }
1971  
1972      function