[ 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 getAttributeCaption($attribute)
1973      {
1974          return $this->t(AkInflector::humanize($attribute));
1975      }
1976  
1977      /**
1978       * This function is useful in case you need to know if attributes have been assigned to an object.
1979       */
1980      function hasAttributesDefined()
1981      {
1982          $attributes = join('',$this->getAttributes());
1983          return empty($attributes);
1984      }
1985  
1986  
1987      /**
1988      * Returns the primary key field.
1989      */
1990      function getPrimaryKey()
1991      {
1992          if(!isset($this->_primaryKey)){
1993              $this->setPrimaryKey();
1994          }
1995          return $this->_primaryKey;
1996      }
1997  
1998      function getColumnNames()
1999      {
2000          if(empty($this->_columnNames)){
2001              $columns = $this->getColumns();
2002              foreach ($columns as $column_name=>$details){
2003                  $this->_columnNames[$column_name] = isset($details->columnName) ? $this->t($details->columnName) : $this->getAttributeCaption($column_name);
2004              }
2005          }
2006          return $this->_columnNames;
2007      }
2008  
2009  
2010      /**
2011      * Returns an array of columns objects where the primary id, all columns ending in "_id" or "_count", 
2012      * and columns used for single table inheritance has been removed.
2013      */
2014      function getContentColumns()
2015      {
2016          $inheritance_column = $this->getInheritanceColumn();
2017          $columns = $this->getColumns();
2018          foreach ($columns as $name=>$details){
2019              if((substr($name,-3) == '_id' || substr($name,-6) == '_count') ||
2020              !empty($details['primaryKey']) || ($inheritance_column !== false && $inheritance_column == $name)){
2021                  unset($columns[$name]);
2022              }
2023          }
2024          return $columns;
2025      }
2026  
2027      /**
2028      * Returns an array of names for the attributes available on this object sorted alphabetically.
2029      */
2030      function getAttributeNames()
2031      {
2032          if(!isset($this->_activeRecordHasBeenInstantiated)){
2033              return Ak::handleStaticCall();
2034          }
2035          $attributes = array_keys($this->getAvailableAttributes());
2036          $names = array_combine($attributes,array_map(array(&$this,'getAttributeCaption'), $attributes));
2037          natsort($names);
2038          return $names;
2039      }
2040  
2041  
2042      /**
2043      * Returns true if the specified attribute has been set by the user or by a database load and is neither null nor empty?
2044      */
2045      function isAttributePresent($attribute)
2046      {
2047          $value = $this->getAttribute($attribute);
2048          return !empty($value);
2049      }
2050  
2051      /**
2052      * Returns true if given attribute exists for this Model.
2053      *
2054      * @param string $attribute
2055      * @return boolean
2056      */
2057      function hasAttribute ($attribute)
2058      {
2059          empty($this->_columns) ? $this->getColumns() : $this->_columns; // HINT: only used by HasAndBelongsToMany joinObjects, if the table is not present yet!
2060          return isset($this->_columns[$attribute]) || (!empty($this->_combinedAttributes) && $this->isCombinedAttribute($attribute));
2061      }
2062  
2063      /*/Model Attributes*/
2064  
2065  
2066      /**
2067                            Combined attributes
2068      ====================================================================
2069      *
2070      * The Akelos Framework has a handy way to represent combined fields.
2071      * You can add a new attribute to your models using a printf patter to glue
2072      * multiple parameters in a single one.
2073      * 
2074      * For example, If we set...
2075      * $this->addCombinedAttributeConfiguration('name', "%s %s", 'first_name', 'last_name');
2076      * $this->addCombinedAttributeConfiguration('date', "%04d-%02d-%02d", 'year', 'month', 'day');
2077      * $this->setAttributes('first_name=>','John','last_name=>','Smith','year=>',2005,'month=>',9,'day=>',27);
2078      * 
2079      * $this->name // will have "John Smith" as value and
2080      * $this->date // will be 2005-09-27
2081      * 
2082      * On the other hand if you do
2083      *
2084      *   $this->setAttribute('date', '2008-11-30');
2085      *
2086      *   All the 'year', 'month' and 'day' getters will be fired (if they exist) the following attributes will be set
2087      *
2088      *    $this->year // will be 2008
2089      *    $this->month // will be 11 and
2090      *    $this->day // will be 27
2091      * 
2092      * Sometimes you might need a pattern for composing and another for decomposing attributes. In this case you can specify
2093      * an array as the pattern values, where first element will be the composing pattern and second element will be used
2094      * for decomposing.
2095      * 
2096      * You can also specify a callback method from this object function instead of a pattern. You can also assign a callback
2097      * for composing and another for decomposing by passing their names as an array like on the patterns.
2098      *
2099      *    <?php 
2100      *    class User extends ActiveRecord 
2101      *    { 
2102      *        function User()
2103      *        {
2104      *            // You can use a multiple patterns array where "%s, %s" will be used for combining fields and "%[^,], %s" will be used
2105      *            // for decomposing fields. (as you can see you can also use regular expressions on your patterns)
2106      *            $User->addCombinedAttributeConfiguration('name', array("%s, %s","%[^,], %s"), 'last_name', 'first_name');
2107      *            
2108      *            //Here we set email_link so compose_email_link() will be triggered for building up the field and parse_email_link will
2109      *            // be used for getting the fields out
2110      *            $User->addCombinedAttributeConfiguration('email_link', array("compose_email_link","parse_email_link"), 'email', 'name');
2111      *            
2112      *            // We need to tell the ActiveRecord to load it's magic (see the example below for a simpler solution)
2113      *            $attributes = (array)func_get_args();
2114      *            return $this->init($attributes);
2115      *   
2116      *        }
2117      *        function compose_email_link()
2118      *        {
2119      *            $args = func_get_arg(0); 
2120      *            return "<a href=\'mailto:{$args[\'email\']}\'>{$args[\'name\']}</a>"; 
2121      *        } 
2122      *        function parse_email_link($email_link) 
2123      *        { 
2124      *            $results = sscanf($email_link, "<a href=\'mailto:%[^\']\'>%[^<]</a>"); 
2125      *            return array(\'email\'=>$results[0],\'name\'=>$results[1]); 
2126      *        } 
2127      *        
2128      *    } 
2129      *   ?>
2130      *
2131      * You can also simplify your live by declaring the combined attributes as a class variable like:
2132      *    <?php 
2133      *    class User extends ActiveRecord 
2134      *    { 
2135      *       var $combined_attributes array(
2136      *       array('name', array("%s, %s","%[^,], %s"), 'last_name', 'first_name')
2137      *       array('email_link', array("compose_email_link","parse_email_link"), 'email', 'name')
2138      *       );
2139      *       
2140      *       // ....
2141      *    } 
2142      *   ?>
2143      *
2144      */
2145  
2146      /**
2147      * Returns true if given attribute is a combined attribute for this Model.
2148      *
2149      * @param string $attribute
2150      * @return boolean
2151      */
2152      function isCombinedAttribute ($attribute)
2153      {
2154          return !empty($this->_combinedAttributes) && isset($this->_combinedAttributes[$attribute]);
2155      }
2156  
2157      function addCombinedAttributeConfiguration($attribute)
2158      {
2159          $args = is_array($attribute) ? $attribute : func_get_args();
2160          $columns = array_slice($args,2);
2161          $invalid_columns = array();
2162          foreach ($columns as $colum){
2163              if(!$this->hasAttribute($colum)){
2164                  $invalid_columns[] = $colum;
2165              }
2166          }
2167          if(!empty($invalid_columns)){
2168              trigger_error(Ak::t('There was an error while setting the composed field "%field_name", the following mapping column/s "%columns" do not exist',
2169              array('%field_name'=>$args[0],'%columns'=>join(', ',$invalid_columns))), E_USER_ERROR);
2170          }else{
2171              $attribute = array_shift($args);
2172              $this->_combinedAttributes[$attribute] = $args;
2173              $this->composeCombinedAttribute($attribute);
2174          }
2175      }
2176  
2177      function composeCombinedAttributes()
2178      {
2179  
2180          if(!empty($this->_combinedAttributes)){
2181              $attributes = array_keys($this->_combinedAttributes);
2182              foreach ($attributes as $attribute){
2183                  $this->composeCombinedAttribute($attribute);
2184              }
2185          }
2186      }
2187  
2188      function composeCombinedAttribute($combined_attribute)
2189      {
2190          if($this->isCombinedAttribute($combined_attribute)){
2191              $config = $this->_combinedAttributes[$combined_attribute];
2192              $pattern = array_shift($config);
2193  
2194              $pattern = is_array($pattern) ? $pattern[0] : $pattern;
2195              $got = array();
2196  
2197              foreach ($config as $attribute){
2198                  if(isset($this->$attribute)){
2199                      $got[$attribute] = $this->getAttribute($attribute);
2200                  }
2201              }
2202              if(count($got) === count($config)){
2203                  $this->$combined_attribute = method_exists($this, $pattern) ? $this->{$pattern}($got) : vsprintf($pattern, $got);
2204              }
2205          }
2206      }
2207  
2208      /**
2209      * @access private
2210      */
2211      function _getCombinedAttributesWhereThisAttributeIsUsed($attribute)
2212      {
2213          $result = array();
2214          foreach ($this->_combinedAttributes as $combined_attribute=>$settings){
2215              if(in_array($attribute,$settings)){
2216                  $result[] = $combined_attribute;
2217              }
2218          }
2219          return $result;
2220      }
2221  
2222  
2223      function requiredForCombination($attribute)
2224      {
2225          foreach ($this->_combinedAttributes as $settings){
2226              if(in_array($attribute,$settings)){
2227                  return true;
2228              }
2229          }
2230          return false;
2231      }
2232  
2233      function hasCombinedAttributes()
2234      {
2235          return count($this->getCombinedSubattributes()) === 0 ? false :true;
2236      }
2237  
2238      function getCombinedSubattributes($attribute)
2239      {
2240          $result = array();
2241          if(is_array($this->_combinedAttributes[$attribute])){
2242              $attributes = $this->_combinedAttributes[$attribute];
2243              array_shift($attributes);
2244              foreach ($attributes as $attribute_to_check){
2245                  if(isset($this->_combinedAttributes[$attribute_to_check])){
2246                      $result[] = $attribute_to_check;
2247                  }
2248              }
2249          }
2250          return $result;
2251      }
2252  
2253      function decomposeCombinedAttributes()
2254      {
2255          if(!empty($this->_combinedAttributes)){
2256              $attributes = array_keys($this->_combinedAttributes);
2257              foreach ($attributes as $attribute){
2258                  $this->decomposeCombinedAttribute($attribute);
2259              }
2260          }
2261      }
2262  
2263      function decomposeCombinedAttribute($combined_attribute, $used_on_combined_fields = false)
2264      {
2265          if(isset($this->$combined_attribute) && $this->isCombinedAttribute($combined_attribute)){
2266              $config = $this->_combinedAttributes[$combined_attribute];
2267              $pattern = array_shift($config);
2268              $pattern = is_array($pattern) ? $pattern[1] : $pattern;
2269  
2270              if(method_exists($this, $pattern)){
2271                  $pieces = $this->{$pattern}($this->$combined_attribute);
2272                  if(is_array($pieces)){
2273                      foreach ($pieces as $k=>$v){
2274                          $is_combined = $this->isCombinedAttribute($k);
2275                          if($is_combined){
2276                              $this->decomposeCombinedAttribute($k);
2277                          }
2278                          $this->setAttribute($k, $v, true, !$is_combined);
2279                      }
2280                      if($is_combined && !$used_on_combined_fields){
2281                          $combined_attributes_contained_on_this_attribute = $this->getCombinedSubattributes($combined_attribute);
2282                          if(count($combined_attributes_contained_on_this_attribute)){
2283                              $this->decomposeCombinedAttribute($combined_attribute, true);
2284                          }
2285                      }
2286                  }
2287              }else{
2288                  $got = sscanf($this->$combined_attribute, $pattern);
2289                  for ($x=0; $x<count($got); $x++){
2290                      $attribute = $config[$x];
2291                      $is_combined = $this->isCombinedAttribute($attribute);
2292                      if($is_combined){
2293                          $this->decomposeCombinedAttribute($attribute);
2294                      }
2295                      $this->setAttribute($attribute, $got[$x], true, !$is_combined);
2296                  }
2297              }
2298          }
2299      }
2300  
2301      function getAvailableCombinedAttributes()
2302      {
2303          $combined_attributes = array();
2304          foreach ($this->_combinedAttributes as $attribute=>$details){
2305              $combined_attributes[$attribute] = array('name'=>$attribute, 'type'=>'string', 'path' => array_shift($details), 'uses'=>$details);
2306          }
2307          return !empty($this->_combinedAttributes) && is_array($this->_combinedAttributes) ? $combined_attributes : array();
2308      }
2309  
2310      /*/Combined attributes*/
2311  
2312  
2313  
2314  
2315      /**
2316                           Database connection
2317      ====================================================================
2318      */
2319      /**
2320      * Establishes the connection to the database. Accepts either a profile name specified in config/config.php or
2321      * an array as input where the 'type' key must be specified with the name of a database adapter (in lower-case) 
2322      * example for regular databases (MySQL, Postgresql, etc):
2323      * 
2324      *   $AkActiveRecord->establishConnection('development');
2325      *   $AkActiveRecord->establishConnection('super_user');
2326      * 
2327      *   $AkActiveRecord->establishConnection(
2328      *       array(
2329      *       'type'  => "mysql",
2330      *       'host'     => "localhost",
2331      *       'username' => "myuser",
2332      *       'password' => "mypass",
2333      *       'database' => "somedatabase"
2334      *       ));
2335      *
2336      *    Example for SQLite database:
2337      *
2338      *     $AkActiveRecord->establishConnection(
2339      *       array(
2340      *       'type' => "sqlite",
2341      *       'dbfile'  => "path/to/dbfile"
2342      *       )
2343      *     )
2344      */
2345      function &establishConnection($specification_or_profile = AK_DEFAULT_DATABASE_PROFILE)
2346      {
2347          $adapter =& AkDbAdapter::getInstance($specification_or_profile);
2348          return $this->setConnection(&$adapter);
2349      }
2350  
2351  
2352      /**
2353      * Returns true if a connection that's accessible to this class have already been opened.
2354      */ 
2355      function isConnected()
2356      {
2357          return isset($this->_db);
2358      }
2359  
2360      /**
2361      * Returns the connection currently associated with the class. This can also be used to 
2362      * "borrow" the connection to do database work unrelated to any of the specific Active Records.
2363      */
2364      function &getConnection()
2365      {
2366          return $this->_db;
2367      }
2368  
2369      /**
2370      * Sets the connection for the class.
2371      */
2372      function &setConnection($db_adapter = null)
2373      {
2374          if (is_null($db_adapter)){
2375              $db_adapter =& AkDbAdapter::getInstance();
2376          }
2377          return $this->_db =& $db_adapter;
2378      }
2379  
2380      /**
2381      * @access private
2382      */
2383      function _getDatabaseType()
2384      {
2385          return $this->_db->type();
2386      }
2387      /*/Database connection*/
2388  
2389  
2390      /**
2391                             Table Settings
2392      ====================================================================
2393      See also: Database Reflection.
2394      */
2395  
2396      /**
2397      * Defines the primary key field ? can be overridden in subclasses.
2398      */
2399      function setPrimaryKey($primary_key = 'id')
2400      {
2401          if(!$this->hasColumn($primary_key)){
2402              trigger_error($this->t('Opps! We could not find primary key column %primary_key on the table %table, for the model %model',array('%primary_key'=>$primary_key,'%table'=>$this->getTableName(), '%model'=>$this->getModelName())),E_USER_ERROR);
2403          }else {
2404              $this->_primaryKey = $primary_key;
2405          }
2406      }
2407  
2408  
2409      function getTableName($modify_for_associations = true)
2410      {
2411          if(!isset($this->_tableName)){
2412              // We check if we are on a inheritance Table Model
2413              $this->getClassForDatabaseTableMapping();
2414              if(!isset($this->_tableName)){
2415                  $this->setTableName();
2416              }
2417          }
2418  
2419          if($modify_for_associations && isset($this->_associationTablePrefixes[$this->_tableName])){
2420              return $this->_associationTablePrefixes[$this->_tableName];
2421          }
2422  
2423          return $this->_tableName;
2424      }
2425  
2426      function setTableName($table_name = null, $check_for_existence = AK_ACTIVE_RECORD_VALIDATE_TABLE_NAMES, $check_mode = false)
2427      {
2428          static $available_tables;
2429          if(empty($table_name)){
2430              $table_name = AkInflector::tableize($this->getModelName());
2431          }
2432          if($check_for_existence){
2433              if(!isset($available_tables) || $check_mode){
2434                  if(!isset($this->_db)){
2435                      $this->setConnection();
2436                  }
2437                  if (!AK_ACTIVE_RECORD_CACHE_DATABASE_SCHEMA || ($available_tables = AkDbSchemaCache::getAvailableTables()) === false) {
2438                      $available_tables = $this->_db->availableTables();
2439                      if (AK_ACTIVE_RECORD_CACHE_DATABASE_SCHEMA) {
2440                          AkDbSchemaCache::setAvailableTables($available_tables);
2441                      }
2442                  }
2443              }
2444              if(!in_array($table_name,(array)$available_tables)){
2445                  if(!$check_mode){
2446                      trigger_error(Ak::t('Unable to set "%table_name" table for the model "%model".'.
2447                      '  There is no "%table_name" available into current database layout.'.
2448                      ' Set AK_ACTIVE_RECORD_VALIDATE_TABLE_NAMES constant to false in order to'.
2449                      ' avoid table name validation',array('%table_name'=>$table_name,'%model'=>$this->getModelName())),E_USER_WARNING);
2450                  }
2451                  return false;
2452              }
2453          }
2454          $this->_tableName = $table_name;
2455          return true;
2456      }
2457  
2458  
2459      function getOnlyAvailableAttributes($attributes)
2460      {
2461          $table_name = $this->getTableName();
2462          $ret_attributes = array();
2463          if(!empty($attributes) && is_array($attributes)){
2464              $available_attributes = $this->getAvailableAttributes();
2465  
2466              $keys = array_keys($attributes);
2467              $size = sizeOf($keys);
2468              for ($i=0; $i < $size; $i++){
2469                  $k = str_replace($table_name.'.','',$keys[$i]);
2470                  if(isset($available_attributes[$k]['name'][$k])){
2471                      $ret_attributes[$k] =& $attributes[$keys[$i]];
2472                  }
2473              }
2474          }
2475          return $ret_attributes;
2476      }
2477  
2478      function getColumnsForAttributes($attributes)
2479      {
2480          $ret_attributes = array();
2481          $table_name = $this->getTableName();
2482          if(!empty($attributes) && is_array($attributes)){
2483              $columns = $this->getColumns();
2484              foreach ($attributes as $k=>$v){
2485                  $k = str_replace($table_name.'.','',$k);
2486                  if(isset($columns[$k]['name'][$k])){
2487                      $ret_attributes[$k] = $v;
2488                  }
2489              }
2490          }
2491          return $ret_attributes;
2492      }
2493  
2494      /**
2495      * Returns true if given attribute exists for this Model.
2496      *
2497      * @param string $name Name of table to look in
2498      * @return boolean
2499      */
2500      function hasColumn($column)
2501      {
2502          empty($this->_columns) ? $this->getColumns() : $this->_columns;
2503          return isset($this->_columns[$column]);
2504      }
2505  
2506  
2507      /*/Table Settings*/
2508  
2509      /**
2510                             Database Reflection
2511      ====================================================================
2512      See also: Table Settings, Type Casting.
2513      */
2514  
2515  
2516      /**
2517      * Initializes the attributes array with keys matching the columns from the linked table and
2518      * the values matching the corresponding default value of that column, so
2519      * that a new instance, or one populated from a passed-in array, still has all the attributes
2520      * that instances loaded from the database would.
2521      */
2522      function attributesFromColumnDefinition()
2523      {
2524          $attributes = array();
2525  
2526          foreach ((array)$this->getColumns() as $column_name=>$column_settings){
2527              if (!isset($column_settings['primaryKey']) && isset($column_settings['hasDefault'])) {
2528                  $attributes[$column_name] = $this->_extractValueFromDefault($column_settings['defaultValue']);
2529              } else {
2530                  $attributes[$column_name] = null;
2531              }
2532          }
2533          return $attributes;
2534      }
2535  
2536      /**
2537       * Gets information from the database engine about a single table
2538       *
2539       * @access private
2540       */
2541      function _databaseTableInternals($table)
2542      {
2543          if (!AK_ACTIVE_RECORD_CACHE_DATABASE_SCHEMA || ($cache = AkDbSchemaCache::getDbTableInternals($table))===false) {
2544              $cache = $this->_db->getColumnDetails($table);
2545              if (AK_ACTIVE_RECORD_CACHE_DATABASE_SCHEMA) {
2546                  AkDbSchemaCache::setDbTableInternals($table,$cache);
2547              }
2548          }
2549          return $cache;
2550      }
2551  
2552      function getColumnsWithRegexBoundaries()
2553      {
2554          $columns = array_keys($this->getColumns());
2555          foreach ($columns as $k=>$column){
2556              $columns[$k] = '/([^\.])\b('.$column.')\b/';
2557          }
2558          return $columns;
2559      }
2560  
2561  
2562      /**
2563      * If is the first time we use a model this function will run the installer for the model if it exists
2564      *
2565      * @access private
2566      */
2567      function _runCurrentModelInstallerIfExists(&$column_objects)
2568      {
2569          static $installed_models = array();
2570          if(!defined('AK_AVOID_AUTOMATIC_ACTIVE_RECORD_INSTALLERS') && !in_array($this->getModelName(), $installed_models)){
2571              $installed_models[] = $this->getModelName();
2572              require_once (AK_LIB_DIR.DS.'AkInstaller.php');
2573              $installer_name = $this->getModelName().'Installer';
2574              $installer_file = AK_APP_DIR.DS.'installers'.DS.AkInflector::underscore($installer_name).'.php';
2575              if(file_exists($installer_file)){
2576                  require_once($installer_file);
2577                  if(class_exists($installer_name)){
2578                      $Installer = new $installer_name();
2579                      if(method_exists($Installer,'install')){
2580                          $Installer->install();
2581                          $column_objects = $this->_databaseTableInternals($this->getTableName());
2582                          return !empty($column_objects);
2583                      }
2584                  }
2585              }
2586          }
2587          return false;
2588      }
2589  
2590  
2591      /**
2592      * Returns an array of column objects for the table associated with this class.
2593      */
2594      function getColumns($force_reload = false)
2595      {
2596          if(empty($this->_columns) || $force_reload){
2597              $this->_columns = $this->getColumnSettings($force_reload);
2598          }
2599  
2600          return (array)$this->_columns;
2601      }
2602  
2603      function getColumnSettings($force_reload = false)
2604      {
2605          if(empty($this->_columnsSettings) || $force_reload){
2606              $this->loadColumnsSettings($force_reload);
2607              $this->initiateColumnsToNull();
2608          }
2609          return isset($this->_columnsSettings) ? $this->_columnsSettings : array();
2610      }
2611  
2612      function loadColumnsSettings($force_reload = false)
2613      {
2614          if(is_null($this->_db)){
2615              $this->setConnection();
2616          }
2617          $this->_columnsSettings = $force_reload ? null : $this->_getPersistedTableColumnSettings();
2618  
2619          if(empty($this->_columnsSettings) || !AK_ACTIVE_RECORD_ENABLE_PERSISTENCE){
2620              if(empty($this->_dataDictionary)){
2621                  $this->_dataDictionary =& $this->_db->getDictionary();
2622              }
2623  
2624              $column_objects = $this->_databaseTableInternals($this->getTableName());
2625  
2626              if( !isset($this->_avoidTableNameValidation) &&
2627              !is_array($column_objects) &&
2628              !$this->_runCurrentModelInstallerIfExists($column_objects)){
2629                  trigger_error(Ak::t('Ooops! Could not fetch details for the table %table_name.', array('%table_name'=>$this->getTableName())), E_USER_ERROR);
2630                  return false;
2631              }elseif (empty($column_objects)){
2632                  $this->_runCurrentModelInstallerIfExists($column_objects);
2633              }
2634              if(is_array($column_objects)){
2635                  foreach (array_keys($column_objects) as $k){
2636                      $this->setColumnSettings($column_objects[$k]->name, $column_objects[$k]);
2637                  }
2638              }
2639              if(!empty($this->_columnsSettings) && AK_ACTIVE_RECORD_CACHE_DATABASE_SCHEMA){
2640                  $this->_persistTableColumnSettings();
2641              }
2642          }
2643          return isset($this->_columnsSettings) ? $this->_columnsSettings : array();
2644      }
2645  
2646  
2647  
2648      function setColumnSettings($column_name, $column_object)
2649      {
2650          $this->_columnsSettings[$column_name] = array();
2651          $this->_columnsSettings[$column_name]['name'] = $column_object->name;
2652  
2653          if($this->_internationalize && $this->_isInternationalizeCandidate($column_object->name)){
2654              $this->_addInternationalizedColumn($column_object->name);
2655          }
2656  
2657          $this->_columnsSettings[$column_name]['type'] = $this->getAkelosDataType($column_object);
2658          if(!empty($column_object->primary_key)){
2659              $this->_primaryKey = empty($this->_primaryKey) ? $column_object->name : $this->_primaryKey;
2660              $this->_columnsSettings[$column_name]['primaryKey'] = true;
2661          }
2662          if(!empty($column_object->auto_increment)){
2663              $this->_columnsSettings[$column_name]['autoIncrement'] = true;
2664          }
2665          if(!empty($column_object->has_default)){
2666              $this->_columnsSettings[$column_name]['hasDefault'] = true;
2667          }
2668          if(!empty($column_object->not_null)){
2669              $this->_columnsSettings[$column_name]['notNull'] = true;
2670          }
2671          if(!empty($column_object->max_length) && $column_object->max_length > 0){
2672              $this->_columnsSettings[$column_name]['maxLength'] = $column_object->max_length;
2673          }
2674          if(!empty($column_object->scale) && $column_object->scale > 0){
2675              $this->_columnsSettings[$column_name]['scale'] = $column_object->scale;
2676          }
2677          if(isset($column_object->default_value)){
2678              $this->_columnsSettings[$column_name]['defaultValue'] = $column_object->default_value;
2679          }
2680      }
2681  
2682  
2683      /**
2684      * Resets all the cached information about columns, which will cause they to be reloaded on the next request.
2685      */
2686      function resetColumnInformation()
2687      {
2688          $this->_clearPersitedColumnSettings();
2689          $this->_columnNames = $this->_columns = $this->_columnsSettings = $this->_contentColumns = array();
2690      }
2691  
2692      /**
2693      * @access private
2694      */
2695      function _getColumnsSettings()
2696      {
2697          return AkDbSchemaCache::getColumnsSettings();
2698      }
2699  
2700      /**
2701      * @access private
2702      */
2703      function _getModelColumnSettings()
2704      {
2705          return AkDbSchemaCache::getModelColumnSettings($this->getModelName());
2706          
2707      }
2708  
2709      /**
2710      * @access private
2711      */
2712      function _persistTableColumnSettings()
2713      {
2714          AkDbSchemaCache::setModelColumnSettings($this->getModelName(), $this->_columnsSettings);
2715      }
2716  
2717      /**
2718      * @access private
2719      */
2720      function _getPersistedTableColumnSettings()
2721      {
2722          return AkDbSchemaCache::getModelColumnSettings($this->getModelName());
2723          
2724      }
2725  
2726      /**
2727      * @access private
2728      */
2729      function _clearPersitedColumnSettings()
2730      {
2731          AkDbSchemaCache::clear($this->getModelName());
2732      }
2733  
2734  
2735  
2736      function initiateAttributeToNull($attribute)
2737      {
2738          if(!isset($this->$attribute)){
2739              $this->$attribute = null;
2740          }
2741      }
2742  
2743      function initiateColumnsToNull()
2744      {
2745          if(isset($this->_columnsSettings) && is_array($this->_columnsSettings)){
2746              array_map(array(&$this,'initiateAttributeToNull'),array_keys($this->_columnsSettings));
2747          }
2748      }
2749  
2750  
2751      /**
2752      * Akelos data types are mapped to phpAdodb data types
2753      *
2754      * Returns the Akelos data type for an Adodb Column Object
2755      *
2756      * 'C'=>'string', // Varchar, capped to 255 characters.
2757      * 'X' => 'text' // Larger varchar, capped to 4000 characters (to be compatible with Oracle). 
2758      * 'XL' => 'text' // For Oracle, returns CLOB, otherwise the largest varchar size.
2759      * 
2760      * 'C2' => 'string', // Multibyte varchar
2761      * 'X2' => 'string', // Multibyte varchar (largest size)
2762      * 
2763      * 'B' => 'binary', // BLOB (binary large object)
2764      * 
2765      * 'D' => array('date', 'datetime'), //  Date (some databases do not support this, and we return a datetime type)
2766      * 'T' =>  array('datetime', 'timestamp'), //Datetime or Timestamp
2767      * 'L' => 'boolean', // Integer field suitable for storing booleans (0 or 1)
2768      * 'I' => // Integer (mapped to I4)
2769      * 'I1' => 'integer', // 1-byte integer
2770      * 'I2' => 'integer', // 2-byte integer
2771      * 'I4' => 'integer', // 4-byte integer
2772      * 'I8' => 'integer', // 8-byte integer
2773      * 'F' => 'float', // Floating point number
2774      * 'N' => 'integer' //  Numeric or decimal number
2775      *
2776      * @return string One of this 'string','text','integer','float','datetime','timestamp',
2777      * 'time', 'name','date', 'binary', 'boolean'
2778      */
2779      function getAkelosDataType(&$adodb_column_object)
2780      {
2781          $config_var_name = AkInflector::variablize($adodb_column_object->name.'_data_type');
2782          if(!empty($this->{$config_var_name})){
2783              return $this->{$config_var_name};
2784          }
2785          if(stristr($adodb_column_object->type, 'BLOB')){
2786              return 'binary';
2787          }
2788          if(!empty($adodb_column_object->auto_increment)) {
2789              return 'serial';
2790          }
2791          $meta_type = $this->_dataDictionary->MetaType($adodb_column_object);
2792          $adodb_data_types = array(
2793          'C'=>'string', // Varchar, capped to 255 characters.
2794          'X' => 'text', // Larger varchar, capped to 4000 characters (to be compatible with Oracle).
2795          'XL' => 'text', // For Oracle, returns CLOB, otherwise the largest varchar size.
2796  
2797          'C2' => 'string', // Multibyte varchar
2798          'X2' => 'string', // Multibyte varchar (largest size)
2799  
2800          'B' => 'binary', // BLOB (binary large object)
2801  
2802          'D' => array('date'), //  Date
2803          'T' =>  array('datetime', 'timestamp'), //Datetime or Timestamp
2804          'L' => 'boolean', // Integer field suitable for storing booleans (0 or 1)
2805          'R' => 'serial', // Serial Integer
2806          'I' => 'integer', // Integer (mapped to I4)
2807          'I1' => 'integer', // 1-byte integer
2808          'I2' => 'integer', // 2-byte integer
2809          'I4' => 'integer', // 4-byte integer
2810          'I8' => 'integer', // 8-byte integer
2811          'F' => 'float', // Floating point number
2812          'N' => 'decimal' //  Numeric or decimal number
2813          );
2814  
2815          $result = !isset($adodb_data_types[$meta_type]) ?
2816          'string' :
2817          (is_array($adodb_data_types[$meta_type]) ? $adodb_data_types[$meta_type][0] : $adodb_data_types[$meta_type]);
2818  
2819          if($result == 'text'){
2820              if(stristr($adodb_column_object->type, 'CHAR') | (isset($adodb_column_object->max_length) && $adodb_column_object->max_length > 0 &&$adodb_column_object->max_length < 256 )){
2821                  return 'string';
2822              }
2823          }
2824  
2825          if($this->_getDatabaseType() == 'mysql'){
2826              if($result == 'integer' && stristr($adodb_column_object->type, 'TINYINT')){
2827                  return 'boolean';
2828              }
2829          }elseif($this->_getDatabaseType() == 'postgre'){
2830              if($adodb_column_object->type == 'timestamp' || $result == 'datetime'){
2831                  $adodb_column_object->max_length = 19;
2832              }
2833          }elseif($this->_getDatabaseType() == 'sqlite'){
2834              if($result == 'integer' && (int)$adodb_column_object->max_length === 1 && stristr($adodb_column_object->type, 'TINYINT')){
2835                  return 'boolean';
2836              }elseif($result == 'integer' && stristr($adodb_column_object->type, 'DOUBLE')){
2837                  return 'float';
2838              }
2839          }
2840  
2841          if($result == 'datetime' && substr($adodb_column_object->name,-3) == '_on'){
2842              $result = 'date';
2843          }
2844  
2845          return $result;
2846      }
2847  
2848  
2849      /**
2850       * This method retrieves current class name that will be used to map 
2851       * your database to this object.
2852       */
2853      function getClassForDatabaseTableMapping()
2854      {
2855          $class_name = get_class($this);
2856          if(is_subclass_of($this,'akactiverecord') || is_subclass_of($this,'AkActiveRecord')){
2857              $parent_class = get_parent_class($this);
2858              while (substr(strtolower($parent_class),-12) != 'activerecord'){
2859                  $class_name = $parent_class;
2860                  $parent_class = get_parent_class($parent_class);
2861              }
2862          }
2863  
2864          $class_name = $this->_getModelName($class_name);
2865          // This is an Active Record Inheritance so we set current table to parent table.
2866          if(!empty($class_name) && strtolower($class_name) != 'activerecord'){
2867              $this->_inheritanceClassName = $class_name;
2868              @$this->setTableName(AkInflector::tableize($class_name), false);
2869          }
2870  
2871          return $class_name;
2872      }
2873  
2874      function getDisplayField()
2875      {
2876          return  empty($this->displayField) && $this->hasAttribute('name') ? 'name' : (isset($this->displayField) && $this->hasAttribute($this->displayField) ? $this->displayField : $this->getPrimaryKey());
2877      }
2878  
2879      function setDisplayField($attribute_name)
2880      {
2881          if($this->hasAttribute($attribute_name)){
2882              $this->displayField = $attribute_name;
2883              return true;
2884          }else {
2885              return false;
2886          }
2887      }
2888  
2889  
2890  
2891  
2892      /*/Database Reflection*/
2893  
2894      /**
2895                                 Localization
2896      ====================================================================
2897      */
2898  
2899      function t($string, $array = null)
2900      {
2901          return Ak::t($string, $array, AkInflector::underscore($this->getModelName()));
2902      }
2903  
2904      function getInternationalizedColumns()
2905      {
2906          static $cache;
2907          $model = $this->getModelName();
2908          $available_locales = $this->getAvailableLocales();
2909          if(empty($cache[$model])){
2910              $cache[$model] = array();
2911              foreach ($this->getColumnSettings() as $column_name=>$details){
2912                  if(!empty($details['i18n'])){
2913                      $_tmp_pos = strpos($column_name,'_');
2914                      $column = substr($column_name,$_tmp_pos+1);
2915                      $lang = substr($column_name,0,$_tmp_pos);
2916                      if(in_array($lang, $available_locales)){
2917                          $cache[$model][$column] = empty($cache[$model][$column]) ? array($lang) :
2918                          array_merge($cache[$model][$column] ,array($lang));
2919                      }
2920                  }
2921              }
2922          }
2923  
2924          return $cache[$model];
2925      }
2926  
2927      function getAvailableLocales()
2928      {
2929          static $available_locales;
2930          if(empty($available_locales)){
2931              if(defined('AK_ACTIVE_RECORD_DEFAULT_LOCALES')){
2932                  $available_locales = Ak::stringToArray(AK_ACTIVE_RECORD_DEFAULT_LOCALES);
2933              }else{
2934                  $available_locales =  Ak::langs();
2935              }
2936          }
2937          return $available_locales;
2938      }
2939  
2940      function getCurrentLocale()
2941      {
2942          static $current_locale;
2943          if(empty($current_locale)){
2944              $current_locale = Ak::lang();
2945              $available_locales = $this->getAvailableLocales();
2946              if(!in_array($current_locale, $available_locales)){
2947                  $current_locale = array_shift($available_locales);
2948              }
2949          }
2950          return $current_locale;
2951      }
2952  
2953  
2954      function getAttributeByLocale($attribute, $locale)
2955      {
2956          $internationalizable_columns = $this->getInternationalizedColumns();
2957          if(!empty($internationalizable_columns[$attribute]) && is_array($internationalizable_columns[$attribute]) && in_array($locale, $internationalizable_columns[$attribute])){
2958              return $this->getAttribute($locale.'_'.$attribute);
2959          }
2960      }
2961  
2962      function getAttributeLocales($attribute)
2963      {
2964          $attribute_locales = array();
2965          foreach ($this->getAvailableLocales() as $locale){
2966              if($this->hasColumn($locale.'_'.$attribute)){
2967                  $attribute_locales[$locale] = $this->getAttributeByLocale($attribute, $locale);
2968              }
2969          }
2970          return $attribute_locales;
2971      }
2972  
2973      function setAttributeByLocale($attribute, $value, $locale)
2974      {
2975          $internationalizable_columns = $this->getInternationalizedColumns();
2976  
2977          if($this->_isInternationalizeCandidate($locale.'_'.$attribute) && !empty($internationalizable_columns[$attribute]) && is_array($internationalizable_columns[$attribute]) && in_array($locale, $internationalizable_columns[$attribute])){
2978              $this->setAttribute($locale.'_'.$attribute, $value);
2979          }
2980  
2981      }
2982  
2983      function setAttributeLocales($attribute, $values = array())
2984      {
2985          foreach ($values as $locale=>$value){
2986              $this->setAttributeByLocale($attribute, $value, $locale);
2987          }
2988      }
2989  
2990      /**
2991      * @access private
2992      */
2993      function _delocalizeAttribute($attribute)
2994      {
2995          return $this->_isInternationalizeCandidate($attribute) ? substr($attribute,3) : $attribute;
2996      }
2997  
2998      /**
2999      * @access private
3000      */
3001      function _isInternationalizeCandidate($column_name)
3002      {
3003          $pos = strpos($column_name,'_');
3004          return $pos === 2 && in_array(substr($column_name,0,$pos),$this->getAvailableLocales());
3005      }
3006  
3007      /**
3008      * @access private
3009      */
3010      function _addInternationalizedColumn($column_name)
3011      {
3012          $this->_columnsSettings[$column_name]['i18n'] = true;
3013      }
3014  
3015  
3016      /**
3017       * Adds an internationalized attribute to an array containing other locales for the same column name
3018       * 
3019       * Example:
3020       *  es_title and en_title will be available user title = array('es'=>'...', 'en' => '...')
3021       *
3022       * @access private
3023       */
3024      function _groupInternationalizedAttribute($attribute, $value)
3025      {
3026          if($this->_internationalize && $this->_isInternationalizeCandidate($attribute)){
3027              if(!empty($this->$attribute)){
3028                  $_tmp_pos = strpos($attribute,'_');
3029                  $column = substr($attribute,$_tmp_pos+1);
3030                  $lang = substr($attribute,0,$_tmp_pos);
3031                  $this->$column = empty($this->$column) ? array() : $this->$column;
3032                  if(empty($this->$column) || (!empty($this->$column) && is_array($this->$column))){
3033                      $this->$column = empty($this->$column) ? array($lang=>$value) : array_merge($this->$column,array($lang=>$value));
3034                  }
3035              }
3036          }
3037      }
3038  
3039      /*/Localization*/
3040  
3041  
3042  
3043  
3044      /**
3045                               Type Casting
3046      ====================================================================
3047      See also: Database Reflection.
3048      */
3049  
3050      function getAttributesBeforeTypeCast()
3051      {
3052          $attributes_array = array();
3053          $available_attributes = $this->getAvailableAttributes();
3054          foreach ($available_attributes as $attribute){
3055              $attribute_value = $this->getAttributeBeforeTypeCast($attribute['name']);
3056              if(!empty($attribute_value)){
3057                  $attributes_array[$attribute['name']] = $attribute_value;
3058              }
3059          }
3060          return $attributes_array;
3061      }
3062  
3063  
3064      function getAttributeBeforeTypeCast($attribute)
3065      {
3066          if(isset($this->{$attribute.'_before_type_cast'})){
3067              return $this->{$attribute.'_before_type_cast'};
3068          }
3069          return null;
3070      }
3071  
3072      function quotedId()
3073      {
3074          return $this->castAttributeForDatabase($this->getPrimaryKey(), $this->getId());
3075      }
3076  
3077      /**
3078      * Specifies that the attribute by the name of attr_name should be serialized before saving to the database and unserialized after loading from the database. If class_name is specified, the serialized object must be of that class on retrieval, as a new instance of the object will be loaded with serialized values.
3079      */
3080      function setSerializeAttribute($attr_name, $class_name = null)
3081      {
3082          if($this->hasColumn($attr_name)){
3083              $this->_serializedAttributes[$attr_name] = $class_name;
3084          }
3085      }
3086  
3087      function getAvailableAttributesQuoted()
3088      {
3089          return $this->getAttributesQuoted($this->getAttributes());
3090      }
3091  
3092  
3093      function getAttributesQuoted($attributes_array)
3094      {
3095          $set = array();
3096          $attributes_array = $this->getSanitizedConditionsArray($attributes_array);
3097          foreach (array_diff($attributes_array,array('')) as $k=>$v){
3098              $set[$k] = $k.'='.$v;
3099          }
3100  
3101          return $set;
3102      }
3103  
3104      function getColumnType($column_name)
3105      {
3106          empty($this->_columns) ? $this->getColumns() : null;
3107          return empty($this->_columns[$column_name]['type']) ? false : $this->_columns[$column_name]['type'];
3108      }
3109  
3110      function getColumnScale($column_name)
3111      {
3112          empty($this->_columns) ? $this->getColumns() : null;
3113          return empty($this->_columns[$column_name]['scale']) ? false : $this->_columns[$column_name]['scale'];
3114      }
3115  
3116      function castAttributeForDatabase($column_name, $value, $add_quotes = true)
3117      {
3118          $result = '';
3119          switch ($this->getColumnType($column_name)) {
3120              case 'datetime':
3121                  if(!empty($value)){
3122                      $date_time = $this->_db->quote_datetime(Ak::getTimestamp($value));
3123                      $result = $add_quotes ? $date_time : trim($date_time ,"'");
3124                  }else{
3125                      $result = 'null';
3126                  }
3127                  break;
3128  
3129              case 'date':
3130                  if(!empty($value)){
3131                      $date = $this->_db->quote_date(Ak::getTimestamp($value));
3132                      $result = $add_quotes ? $date : trim($date, "'");
3133                  }else{
3134                      $result = 'null';
3135                  }
3136                  break;
3137  
3138              case 'boolean':
3139                  $result = is_null($value) ? 'null' : (!empty($value) ? "'1'" : "'0'");
3140                  break;
3141  
3142              case 'binary':
3143                  if($this->_getDatabaseType() == 'postgre'){
3144                      $result =  is_null($value) ? 'null::bytea ' : " '".$this->_db->escape_blob($value)."'::bytea ";
3145                  }else{
3146                      $result = is_null($value) ? 'null' : ($add_quotes ? $this->_db->quote_string($value) : $value);
3147                  }
3148                  break;
3149  
3150              case 'decimal':
3151                  if(is_null($value)){
3152                      $result = 'null';
3153                  }else{
3154                      if($scale = $this->getColumnScale($column_name)){
3155                          $value = number_format($value, $scale, '.', '');
3156                      }
3157                      $result = $add_quotes ? $this->_db->quote_string($value) : $value;
3158                  }
3159                  break;
3160  
3161              case 'serial':
3162              case 'integer':
3163                  $result = (is_null($value) || $value==='') ? 'null' : (integer)$value;
3164                  break;
3165  
3166              case 'float':
3167                  $result = (empty($value) && $value !== 0) ? 'null' : (is_numeric($value) ? $value : $this->_db->quote_string($value));
3168                  $result = !empty($this->_columns[$column_name]['notNull']) && $result == 'null' && $this->_getDatabaseType() == 'sqlite' ? '0' : $result;
3169                  break;
3170  
3171              default:
3172                  $result = is_null($value) ? 'null' : ($add_quotes ? $this->_db->quote_string($value) : $value);
3173                  break;
3174          }
3175  
3176          //  !! nullable vs. not nullable !!
3177          return empty($this->_columns[$column_name]['notNull']) ? ($result === '' ? "''" : $result) : ($result === 'null' ? '' : $result);
3178      }
3179  
3180      function castAttributeFromDatabase($column_name, $value)
3181      {
3182          if($this->hasColumn($column_name)){
3183              $column_type = $this->getColumnType($column_name);
3184  
3185              if($column_type){
3186                  if('integer' == $column_type){
3187                      return is_null($value) ? null : (integer)$value;
3188                      //return is_null($value) ? null : $value;    // maybe for bigint we can do this
3189                  }elseif('boolean' == $column_type){
3190                      if (is_null($value)) {
3191                          return null;
3192                      }
3193                      if ($this->_getDatabaseType()=='postgre'){
3194                          return $value=='t' ? true : false;
3195                      }
3196                      return (integer)$value === 1 ? true : false;
3197                  }elseif(!empty($value) && 'date' == $column_type && strstr(trim($value),' ')){
3198                      return substr($value,0,10) == '0000-00-00' ? null : str_replace(substr($value,strpos($value,' ')), '', $value);
3199                  }elseif (!empty($value) && 'datetime' == $column_type && substr($value,0,10) == '0000-00-00'){
3200                      return null;
3201                  }elseif ('binary' == $column_type && $this->_getDatabaseType() == 'postgre'){
3202                      $value = $this->_db->unescape_blob($value);
3203                      $value = empty($value) || trim($value) == 'null' ? null : $value;
3204                  }
3205              }
3206          }
3207          return $value;
3208      }
3209  
3210  
3211      /**
3212      * Joins date arguments into a single attribute. Like the array generated by the date_helper, so
3213      * array('published_on(1i)' => 2002, 'published_on(2i)' => 'January', 'published_on(3i)' => 24)
3214      * Will be converted to array('published_on'=>'2002-01-24')
3215      *
3216      * @access private
3217      */
3218      function _castDateParametersFromDateHelper_(&$params)
3219      {
3220          if(empty($params)){
3221              return;
3222          }
3223          $date_attributes = array();
3224          foreach ($params as $k=>$v) {
3225              if(preg_match('/^([A-Za-z0-9_]+)\(([1-5]{1})i\)$/',$k,$match)){
3226                  $date_attributes[$match[1]][$match[2]] = $v;
3227                  $this->$k = $v;
3228                  unset($params[$k]);
3229              }
3230          }
3231          foreach ($date_attributes as $attribute=>$date){
3232              $params[$attribute] = trim(@$date[1].'-'.@$date[2].'-'.@$date[3].' '.@$date[4].':'.@$date[5].':'.@$date[6],' :-');
3233          }
3234      }
3235  
3236      /**
3237      * @access private
3238      */
3239      function _addBlobQueryStack($column_name, $blob_value)
3240      {
3241          $this->_BlobQueryStack[$column_name] = $blob_value;
3242      }
3243  
3244      /**
3245      * @access private
3246      */
3247      function _updateBlobFields($condition)
3248      {
3249          if(!empty($this->_BlobQueryStack) && is_array($this->_BlobQueryStack)){
3250              foreach ($this->_BlobQueryStack as $column=>$value){
3251                  $this->_db->UpdateBlob($this->getTableName(), $column, $value, $condition);
3252              }
3253              $this->_BlobQueryStack = null;
3254          }
3255      }
3256  
3257      /*/Type Casting*/
3258  
3259      /**
3260                               Optimistic Locking
3261      ====================================================================
3262      *
3263      * Active Records support optimistic locking if the field <tt>lock_version</tt> is present.  Each update to the
3264      * record increments the lock_version column and the locking facilities ensure that records instantiated twice
3265      * will let the last one saved return false on save() if the first was also updated. Example:
3266      *
3267      *   $p1 = new Person(1);
3268      *   $p2 = new Person(1);
3269      *
3270      *   $p1->first_name = "Michael";
3271      *   $p1->save();
3272      *
3273      *   $p2->first_name = "should fail";
3274      *   $p2->save(); // Returns false
3275      *
3276      * You're then responsible for dealing with the conflict by checking the return value of save(); and either rolling back, merging,
3277      * or otherwise apply the business logic needed to resolve the conflict.
3278      *
3279      * You must ensure that your database schema defaults the lock_version column to 0.
3280      *
3281      * This behavior can be turned off by setting <tt>AkActiveRecord::lock_optimistically = false</tt>.
3282      */
3283      function isLockingEnabled()
3284      {
3285          return (!isset($this->lock_optimistically) || $this->lock_optimistically !== false) && $this->hasColumn('lock_version');
3286      }
3287      /*/Optimistic Locking*/
3288  
3289  
3290      /**
3291                                      Callbacks
3292      ====================================================================
3293      See also: Observers.
3294      *
3295      * Callbacks are hooks into the life-cycle of an Active Record object that allows you to trigger logic
3296      * before or after an alteration of the object state. This can be used to make sure that associated and
3297      * dependent objects are deleted when destroy is called (by overwriting beforeDestroy) or to massage attributes
3298      * before they're validated (by overwriting beforeValidation). As an example of the callbacks initiated, consider
3299      * the AkActiveRecord->save() call:
3300      *
3301      * - (-) save()
3302      * - (-) needsValidation()
3303      * - (1) beforeValidation()
3304      * - (2) beforeValidationOnCreate() / beforeValidationOnUpdate()
3305      * - (-) validate()
3306      * - (-) validateOnCreate()
3307      * - (4) afterValidation()
3308      * - (5) afterValidationOnCreate() / afterValidationOnUpdate()
3309      * - (6) beforeSave()
3310      * - (7) beforeCreate() / beforeUpdate()
3311      * - (-) create()
3312      * - (8) afterCreate() / afterUpdate()
3313      * - (9) afterSave()
3314      * - (10) afterDestroy()
3315      * - (11) beforeDestroy()
3316      *
3317      *
3318      * That's a total of 15 callbacks, which gives you immense power to react and prepare for each state in the
3319      * Active Record lifecycle.
3320      *
3321      * Examples:
3322      *   class CreditCard extends ActiveRecord
3323      *   {
3324      *       // Strip everything but digits, so the user can specify "555 234 34" or
3325      *       // "5552-3434" or both will mean "55523434"
3326      *       function beforeValidationOnCreate
3327      *       {
3328      *           if(!empty($this->number)){
3329      *               $this->number = ereg_replace('[^0-9]*','',$this->number);
3330      *           }
3331      *       }
3332      *   }
3333      *
3334      *   class Subscription extends ActiveRecord
3335      *   {
3336      *       // Note: This is not implemented yet
3337      *       var $beforeCreate  = 'recordSignup';
3338      *
3339      *       function recordSignup()
3340      *       {
3341      *         $this->signed_up_on = date("Y-m-d");
3342      *       }
3343      *   }
3344      *
3345      *   class Firm extends ActiveRecord
3346      *   {
3347      *       //Destroys the associated clients and people when the firm is destroyed
3348      *       // Note: This is not implemented yet
3349      *       var $beforeDestroy = array('destroyAssociatedPeople', 'destroyAssociatedClients');
3350      *
3351      *       function destroyAssociatedPeople()
3352      *       {
3353      *           $Person = new Person();
3354      *           $Person->destroyAll("firm_id=>", $this->id);
3355      *       }
3356      *
3357      *       function destroyAssociatedClients()
3358      *       {
3359      *           $Client = new Client();
3360      *           $Client->destroyAll("client_of=>", $this->id);
3361      *       }
3362      *   }
3363      *
3364      *
3365      * == Canceling callbacks ==
3366      *
3367      * If a before* callback returns false, all the later callbacks and the associated action are cancelled. If an after* callback returns
3368      * false, all the later callbacks are cancelled. Callbacks are generally run in the order they are defined, with the exception of callbacks
3369      * defined as methods on the model, which are called last.
3370      *
3371      * Override this methods to hook Active Records
3372      *
3373      * @access public
3374      */
3375  
3376      function beforeCreate(){return true;}
3377      function beforeValidation(){return true;}
3378      function beforeValidationOnCreate(){return true;}
3379      function beforeValidationOnUpdate(){return true;}
3380      function beforeSave(){return true;}
3381      function beforeUpdate(){return true;}
3382      function afterUpdate(){return true;}
3383      function afterValidation(){return true;}
3384      function afterValidationOnCreate(){return true;}
3385      function afterValidationOnUpdate(){return true;}
3386      function afterInstantiate(){return true;}
3387      function afterCreate(){return true;}
3388      function afterDestroy(){return true;}
3389      function beforeDestroy(){return true;}
3390      function afterSave(){return true;}
3391  
3392      /*/Callbacks*/
3393  
3394  
3395      /**
3396                                      Transactions
3397      ====================================================================
3398      *
3399      * Transaction support for database operations
3400      *
3401      * Transactions are enabled automatically for Active record objects, But you can nest transactions within models.
3402      * This transactions are nested, and only the outermost will be executed
3403      *
3404      *   $User->transactionStart();
3405      *   $User->create('username'=>'Bermi');
3406      *   $Members->create('username'=>'Bermi');
3407      *
3408      *    if(!checkSomething()){
3409      *       $User->transactionFail();
3410      *    }
3411      *
3412      *   $User->transactionComplete();
3413      */
3414  
3415      function transactionStart()
3416      {
3417          return $this->_db->startTransaction();
3418      }
3419  
3420      function transactionComplete()
3421      {
3422          return $this->_db->stopTransaction();
3423      }
3424  
3425      function transactionFail()
3426      {
3427          $this->_db->failTransaction();
3428          return false;
3429      }
3430  
3431      function transactionHasFailed()
3432      {
3433          return $this->_db->hasTransactionFailed();
3434      }
3435  
3436      /*/Transactions*/
3437  
3438  
3439  
3440  
3441      /**
3442                                      Validators
3443      ====================================================================
3444      See also: Error Handling.
3445      *
3446      * Active Records implement validation by overwriting AkActiveRecord::validate (or the variations, validateOnCreate and
3447      * validateOnUpdate). Each of these methods can inspect the state of the object, which usually means ensuring
3448      * that a number of attributes have a certain value (such as not empty, within a given range, matching a certain regular expression).
3449      * 
3450      * Example:
3451      * 
3452      *   class Person extends ActiveRecord
3453      *   {
3454      *       function validate()
3455      *       {
3456      *           $this->addErrorOnEmpty(array('first_name', 'last_name'));
3457      *           if(!preg_match('/[0-9]{4,12}/', $this->phone_number)){
3458      *               $this->addError("phone_number", "has invalid format");
3459      *           }
3460      *       }
3461      * 
3462      *       function validateOnCreate() // is only run the first time a new object is saved
3463      *       {
3464      *           if(!isValidDiscount($this->membership_discount)){
3465      *               $this->addError("membership_discount", "has expired");
3466      *           }
3467      *       }
3468      * 
3469      *       function validateOnUpdate()
3470      *       {
3471      *           if($this->countChangedAttributes() == 0){
3472      *               $this->addErrorToBase("No changes have occurred");
3473      *           }
3474      *       }
3475      *   }
3476      * 
3477      *   $Person = new Person(array("first_name" => "David", "phone_number" => "what?"));
3478      *   $Person->save();                    // => false (and doesn't do the save);
3479      *   $Person->hasErrors();         // => false
3480      *   $Person->countErrors();          // => 2
3481      *   $Person->getErrorsOn("last_name");       // => "can't be empty"
3482      *   $Person->getErrorsOn("phone_number");    // => "has invalid format"
3483      *   $Person->yieldEachFullError();        // => "Last name can't be empty \n Phone number has invalid format"
3484      * 
3485      *   $Person->setAttributes(array("last_name" => "Heinemeier", "phone_number" => "555-555"));
3486      *   $Person->save(); // => true (and person is now saved in the database)
3487      * 
3488      * An "_errors" array is available for every Active Record.
3489      * 
3490      */
3491  
3492      /**
3493        * Encapsulates the pattern of wanting to validate a password or email address field with a confirmation. Example:
3494        * 
3495        *  Model:
3496        *     class Person extends ActiveRecord
3497        *     {
3498        *         function validate()
3499        *         {
3500        *             $this->validatesConfirmationOf('password');
3501        *             $this->validatesConfirmationOf('email_address', "should match confirmation");
3502        *         }
3503        *    }
3504        * 
3505        *  View:
3506        *    <?=$form_helper->password_field("person", "password"); ?>
3507        *    <?=$form_helper->password_field("person", "password_confirmation"); ?>
3508        * 
3509        * The person has to already have a password attribute (a column in the people table), but the password_confirmation is virtual.
3510        * It exists only as an in-memory variable for validating the password. This check is performed only if password_confirmation
3511        * is not null.
3512        * 
3513        */
3514      function validatesConfirmationOf($attribute_names, $message = 'confirmation')
3515      {
3516          $message = isset($this->_defaultErrorMessages[$message]) ? $this->t($this->_defaultErrorMessages[$message]) : $message;
3517          $attribute_names = Ak::toArray($attribute_names);
3518          foreach ($attribute_names as $attribute_name){
3519              $attribute_accessor = $attribute_name.'_confirmation';
3520              if(isset($this->$attribute_accessor) && @$this->$attribute_accessor != @$this->$attribute_name){
3521                  $this->addError($attribute_name, $message);
3522              }
3523          }
3524      }
3525  
3526      /**
3527        * Encapsulates the pattern of wanting to validate the acceptance of a terms of service check box (or similar agreement). Example:
3528        * 
3529        * class Person extends ActiveRecord
3530        * {
3531        *     function validateOnCreate()
3532        *     {
3533        *         $this->validatesAcceptanceOf('terms_of_service');
3534        *         $this->validatesAcceptanceOf('eula', "must be abided");
3535        *     }
3536        * }
3537        * 
3538        * The terms_of_service attribute is entirely virtual. No database column is needed. This check is performed only if
3539        * terms_of_service is not null.
3540        * 
3541        * 
3542        * @param accept 1 
3543        * Specifies value that is considered accepted.  The default value is a string "1", which makes it easy to relate to an HTML checkbox.
3544        */
3545      function validatesAcceptanceOf($attribute_names, $message = 'accepted', $accept = 1)
3546      {
3547          $message = isset($this->_defaultErrorMessages[$message]) ? $this->t($this->_defaultErrorMessages[$message]) : $message;
3548  
3549          $attribute_names = Ak::toArray($attribute_names);
3550          foreach ($attribute_names as $attribute_name){
3551              if(@$this->$attribute_name != $accept){
3552                  $this->addError($attribute_name, $message);
3553              }
3554          }
3555      }
3556  
3557      /**
3558      * Validates whether the associated object or objects are all valid themselves. Works with any kind of association.
3559      *
3560      *   class Book extends ActiveRecord
3561      *   {
3562      *       var $has_many = 'pages';
3563      *       var $belongs_to = 'library';
3564      * 
3565      *       function validate(){
3566      *           $this->validatesAssociated(array('pages', 'library'));
3567      *       }
3568      *   }
3569      * 
3570      *
3571      * Warning: If, after the above definition, you then wrote:
3572      *
3573      *   class Page extends ActiveRecord
3574      *   {
3575      *       var $belongs_to = 'book';
3576      *       function validate(){
3577      *           $this->validatesAssociated('book');
3578      *       }
3579      *   }
3580      *
3581      * ...this would specify a circular dependency and cause infinite recursion.
3582      *
3583      * NOTE: This validation will not fail if the association hasn't been assigned. If you want to ensure that the association
3584      * is both present and guaranteed to be valid, you also need to use validatesPresenceOf.
3585      */
3586      function validatesAssociated($attribute_names, $message = 'invalid')
3587      {
3588          $message = isset($this->_defaultErrorMessages[$message]) ? $this->t($this->_defaultErrorMessages[$message]) : $message;
3589          $attribute_names = Ak::toArray($attribute_names);
3590          foreach ($attribute_names as $attribute_name){
3591              if(!empty($this->$attribute_name)){
3592                  if(is_array($this->$attribute_name)){
3593                      foreach(array_keys($this->$attribute_name) as $k){
3594                          if(method_exists($this->{$attribute_name}[$k],'isValid') && !$this->{$attribute_name}[$k]->isValid()){
3595                              $this->addError($attribute_name, $message);
3596                          }
3597                      }
3598                  }elseif (method_exists($this->$attribute_name,'isValid') && !$this->$attribute_name->isValid()){
3599                      $this->addError($attribute_name, $message);
3600                  }
3601              }
3602          }
3603      }
3604  
3605      function isBlank($value = null)
3606      {
3607          return trim((string)$value) == '';
3608      }
3609  
3610      /**
3611        * Validates that the specified attributes are not blank (as defined by AkActiveRecord::isBlank()).
3612        */
3613      function validatesPresenceOf($attribute_names, $message = 'blank')
3614      {
3615          $message = isset($this->_defaultErrorMessages[$message]) ? $this->t($this->_defaultErrorMessages[$message]) : $message;
3616  
3617          $attribute_names = Ak::toArray($attribute_names);
3618          foreach ($attribute_names as $attribute_name){
3619              $this->addErrorOnBlank($attribute_name, $message);
3620          }
3621      }
3622  
3623      /**
3624        * Validates that the specified attribute matches the length restrictions supplied. Only one option can be used at a time:
3625        * 
3626        * class Person extends ActiveRecord
3627        * {
3628        *     function validate()
3629        *     {
3630        *         $this->validatesLengthOf('first_name', array('maximum'=>30));
3631        *         $this->validatesLengthOf('last_name', array('maximum'=>30,'message'=> "less than %d if you don't mind"));
3632        *         $this->validatesLengthOf('last_name', array('within'=>array(7, 32)));
3633        *         $this->validatesLengthOf('last_name', array('in'=>array(6, 20), 'too_long' => "pick a shorter name", 'too_short' => "pick a longer name"));
3634        *         $this->validatesLengthOf('fav_bra_size', array('minimum'=>1, 'too_short'=>"please enter at least %d character"));
3635        *         $this->validatesLengthOf('smurf_leader', array('is'=>4, 'message'=>"papa is spelled with %d characters... don't play me."));
3636        *     }
3637        * }
3638        *  
3639        * NOTE: Be aware that $this->validatesLengthOf('field', array('is'=>5)); Will match a string containing 5 characters (Ie. "Spain"), an integer 5, and an array with 5 elements. You must supply additional checking to check for appropriate types.
3640        *
3641        * Configuration options:
3642        * <tt>minimum</tt> - The minimum size of the attribute
3643        * <tt>maximum</tt> - The maximum size of the attribute
3644        * <tt>is</tt> - The exact size of the attribute
3645        * <tt>within</tt> - A range specifying the minimum and maximum size of the attribute
3646        * <tt>in</tt> - A synonym(or alias) for :within
3647        * <tt>allow_null</tt> - Attribute may be null; skip validation.
3648        * 
3649        * <tt>too_long</tt> - The error message if the attribute goes over the maximum (default "is" "is too long (max is %d characters)")
3650        * <tt>too_short</tt> - The error message if the attribute goes under the minimum (default "is" "is too short (min is %d characters)")
3651        * <tt>wrong_length</tt> - The error message if using the "is" method and the attribute is the wrong size (default "is" "is the wrong length (should be %d characters)")
3652        * <tt>message</tt> - The error message to use for a "minimum", "maximum", or "is" violation.  An alias of the appropriate too_long/too_short/wrong_length message
3653        */
3654      function validatesLengthOf($attribute_names, $options = array())
3655      {
3656          // Merge given options with defaults.
3657          $default_options = array(
3658          'too_long'     => $this->_defaultErrorMessages['too_long'],
3659          'too_short'     => $this->_defaultErrorMessages['too_short'],
3660          'wrong_length'     => $this->_defaultErrorMessages['wrong_length'],
3661          'allow_null' => false
3662          );
3663  
3664          $range_options = array();
3665          foreach ($options as $k=>$v){
3666              if(in_array($k,array('minimum','maximum','is','in','within'))){
3667                  $range_options[$k] = $v;
3668                  $option = $k;
3669                  $option_value = $v;
3670              }
3671          }
3672  
3673          // Ensure that one and only one range option is specified.
3674          switch (count($range_options)) {
3675              case 0:
3676                  trigger_error(Ak::t('Range unspecified.  Specify the "within", "maximum", "minimum, or "is" option.'), E_USER_ERROR);
3677                  return false;
3678                  break;
3679              case 1:
3680                  $options = array_merge($default_options, $options);
3681                  break;
3682              default:
3683                  trigger_error(Ak::t('Too many range options specified.  Choose only one.'), E_USER_ERROR);
3684                  return false;
3685                  break;
3686          }
3687  
3688  
3689          switch ($option) {
3690              case 'within':
3691              case 'in':
3692                  if(empty($option_value) || !is_array($option_value) || count($option_value) != 2 || !is_numeric($option_value[0]) || !is_numeric($option_value[1])){
3693                      trigger_error(Ak::t('%option must be a Range (array(min, max))',array('%option',$option)), E_USER_ERROR);
3694                      return false;
3695                  }
3696                  $attribute_names = Ak::toArray($attribute_names);
3697                  
3698                  foreach ($attribute_names as $attribute_name){
3699                      if((!empty($option['allow_null']) && !isset($this->$attribute_name)) || (Ak::size($this->$attribute_name)) < $option_value[0]){
3700                          $this->addError($attribute_name, sprintf($options['too_short'], $option_value[0]));
3701                      }elseif((!empty($option['allow_null']) && !isset($this->$attribute_name)) || (Ak::size($this->$attribute_name)) > $option_value[1]){
3702                          $this->addError($attribute_name, sprintf($options['too_long'], $option_value[1]));
3703                      }
3704                  }
3705                  break;
3706  
3707              case 'is':
3708              case 'minimum':
3709              case 'maximum':
3710  
3711                  if(empty($option_value) || !is_numeric($option_value) || $option_value <= 0){
3712                      trigger_error(Ak::t('%option must be a nonnegative Integer',array('%option',$option_value)), E_USER_ERROR);
3713                      return false;
3714                  }
3715  
3716                  // Declare different validations per option.
3717                  $validity_checks = array('is' => "==", 'minimum' => ">=", 'maximum' => "<=");
3718                  $message_options = array('is' => 'wrong_length', 'minimum' => 'too_short', 'maximum' => 'too_long');
3719  
3720                  $message = sprintf(!empty($options['message']) ? $options['message'] : $options[$message_options[$option]],$option_value);
3721  
3722                  $attribute_names = Ak::toArray($attribute_names);
3723                  foreach ($attribute_names as $attribute_name){
3724                      if((!$options['allow_null'] && !isset($this->$attribute_name)) ||
3725                      eval("return !(".Ak::size(@$this->$attribute_name)." {$validity_checks[$option]} $option_value);")){
3726                          $this->addError($attribute_name, $message);
3727                      }
3728                  }
3729                  break;
3730              default:
3731                  break;
3732          }
3733  
3734          return true;
3735      }
3736  
3737      function validatesSizeOf($attribute_names, $options = array())
3738      {
3739          return validatesLengthOf($attribute_names, $options);
3740      }
3741  
3742      /**
3743      * Validates whether the value of the specified attributes are unique across the system. Useful for making sure that only one user
3744      * can be named "davidhh".
3745      *
3746      *  class Person extends ActiveRecord
3747      *   {
3748      *       function validate()
3749      *       {
3750      *           $this->validatesUniquenessOf('passport_number');
3751      *           $this->validatesUniquenessOf('user_name', array('scope' => "account_id"));
3752      *       }
3753      *   }
3754      *
3755      * It can also validate whether the value of the specified attributes are unique based on multiple scope parameters.  For example,
3756      * making sure that a teacher can only be on the schedule once per semester for a particular class. 
3757      *
3758      *   class TeacherSchedule extends ActiveRecord
3759      *   {
3760      *       function validate()
3761      *       {
3762      *           $this->validatesUniquenessOf('passport_number');
3763      *           $this->validatesUniquenessOf('teacher_id', array('scope' => array("semester_id", "class_id"));
3764      *       }
3765      *   }
3766      * 
3767      * 
3768      * When the record is created, a check is performed to make sure that no record exist in the database with the given value for the specified
3769      * attribute (that maps to a column). When the record is updated, the same check is made but disregarding the record itself.
3770      *
3771      * Configuration options:
3772      * <tt>message</tt> - Specifies a custom error message (default is: "has already been taken")
3773      * <tt>scope</tt> - Ensures that the uniqueness is restricted to a condition of "scope = record.scope"
3774      * <tt>case_sensitive</tt> - Looks for an exact match.  Ignored by non-text columns (true by default).
3775      * <tt>if</tt> - Specifies a method to call or a string to evaluate to determine if the validation should
3776      * occur (e.g. 'if' => 'allowValidation', or 'if' => '$this->signup_step > 2').  The
3777      * method, or string should return or evaluate to a true or false value.
3778      */
3779      function validatesUniquenessOf($attribute_names, $options = array())
3780      {
3781          $default_options = array('case_sensitive'=>true, 'message'=>'taken');
3782          $options = array_merge($default_options, $options);
3783  
3784          if(!empty($options['if'])){
3785              if(method_exists($this,$options['if'])){
3786                  if($this->{$options['if']}() === false){
3787                      return true;
3788                  }
3789              }else {
3790                  eval('$__eval_result = ('.rtrim($options['if'],';').');');
3791                  if(empty($__eval_result)){
3792                      return true;
3793                  }
3794              }
3795          }
3796  
3797          $message = isset($this->_defaultErrorMessages[$options['message']]) ? $this->t($this->_defaultErrorMessages[$options['message']]) : $options['message'];
3798          unset($options['message']);
3799  
3800          foreach ((array)$attribute_names as $attribute_name){
3801              $value = isset($this->$attribute_name) ? $this->$attribute_name : null;
3802  
3803              if($value === null || ($options['case_sensitive'] || !$this->hasColumn($attribute_name))){
3804                  $condition_sql = $this->getTableName().'.'.$attribute_name.' '.$this->getAttributeCondition($value);
3805                  $condition_params = array($value);
3806              }else{
3807                  $condition_sql = 'LOWER('.$this->getTableName().'.'.$attribute_name.') '.$this->getAttributeCondition($value);
3808                  $condition_params = array(is_array($value) ? array_map('utf8_strtolower',$value) : utf8_strtolower($value));
3809              }
3810  
3811              if(!empty($options['scope'])){
3812                  foreach ((array)$options['scope'] as $scope_item){
3813                      $scope_value = $this->get($scope_item);
3814                      $condition_sql .= ' AND '.$this->getTableName().'.'.$scope_item.' '.$this->getAttributeCondition($scope_value);
3815                      $condition_params[] = $scope_value;
3816                  }
3817              }
3818  
3819              if(!$this->isNewRecord()){
3820                  $condition_sql .= ' AND '.$this->getTableName().'.'.$this->getPrimaryKey().' <> ?';
3821                  $condition_params[] = $this->getId();
3822              }
3823              array_unshift($condition_params,$condition_sql);
3824              if ($this->find('first', array('conditions' => $condition_params))){
3825                  $this->addError($attribute_name, $message);
3826              }
3827          }
3828      }
3829  
3830  
3831  
3832      /**
3833      * Validates whether the value of the specified attribute is of the correct form by matching it against the regular expression
3834      * provided.
3835      *
3836      * <code>
3837      *   class Person extends ActiveRecord
3838      *   {
3839      *       function validate()
3840      *       {
3841      *           $this->validatesFormatOf('email', "/^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/");
3842      *       }
3843      *   }
3844      * </code>
3845      *
3846      * A regular expression must be provided or else an exception will be raised.
3847      *
3848      * There are some regular expressions bundled with the Akelos Framework. 
3849      * You can override them by defining them as PHP constants (Ie. define('AK_EMAIL_REGULAR_EXPRESSION', '/^My custom email regex$/');). This must be done on your main configuration file.
3850      * This are predefined perl-like regular extensions.
3851      *
3852      * * AK_NOT_EMPTY_REGULAR_EXPRESSION ---> /.+/
3853      * * 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
3854      * * AK_NUMBER_REGULAR_EXPRESSION ---> /^[0-9]+$/
3855      * * AK_PHONE_REGULAR_EXPRESSION ---> /^([\+]?[(]?[\+]?[ ]?[0-9]{2,3}[)]?[ ]?)?[0-9 ()\-]{4,25}$/
3856      * * 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}$/
3857      * * 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])$/
3858      * * AK_POST_CODE_REGULAR_EXPRESSION ---> /^[0-9A-Za-z  -]{2,7}$/
3859      *
3860      * IMPORTANT: Predefined regular expressions may change in newer versions of the Framework, so is highly recommended to hardcode you own on regex on your validators.
3861      * 
3862      * Params:
3863      * <tt>$message</tt> - A custom error message (default is: "is invalid")
3864      * <tt>$regular_expression</tt> - The regular expression used to validate the format with (note: must be supplied!)
3865      */
3866      function validatesFormatOf($attribute_names, $regular_expression, $message = 'invalid', $regex_function = 'preg_match')
3867      {
3868          $message = isset($this->_defaultErrorMessages[$message]) ? $this->t($this->_defaultErrorMessages[$message]) : $message;
3869  
3870          $attribute_names = Ak::toArray($attribute_names);
3871          foreach ($attribute_names as $attribute_name){
3872              if(!isset($this->$attribute_name) || !$regex_function($regular_expression, $this->$attribute_name)){
3873                  $this->addError($attribute_name, $message);
3874              }
3875          }
3876      }
3877  
3878      /**
3879      * Validates whether the value of the specified attribute is available in a particular array of elements.
3880      *
3881      * class Person extends ActiveRecord
3882      * {
3883      *   function validate()
3884      *   {
3885      *       $this->validatesInclusionOf('gender', array('male', 'female'), "woah! what are you then!??!!");
3886      *       $this->validatesInclusionOf('age', range(0, 99));
3887      *   }
3888      *
3889      * Parameters:
3890      * <tt>$array_of_ possibilities</tt> - An array of available items
3891      * <tt>$message</tt> - Specifies a customer error message (default is: "is not included in the list")
3892      * <tt>$allow_null</tt> - If set to true, skips this validation if the attribute is null (default is: false)
3893      */
3894      function validatesInclusionOf($attribute_names, $array_of_possibilities, $message = 'inclusion', $allow_null = false)
3895      {
3896          $message = isset($this->_defaultErrorMessages[$message]) ? $this->t($this->_defaultErrorMessages[$message]) : $message;
3897  
3898          $attribute_names = Ak::toArray($attribute_names);
3899          foreach ($attribute_names as $attribute_name){
3900              if($allow_null ? (@$this->$attribute_name != '' ? (!in_array($this->$attribute_name,$array_of_possibilities)) : @$this->$attribute_name === 0 ) : (isset($this->$attribute_name) ? !in_array(@$this->$attribute_name,$array_of_possibilities) : true )){
3901                  $this->addError($attribute_name, $message);
3902              }
3903          }
3904      }
3905  
3906      /**
3907      * Validates that the value of the specified attribute is not in a particular array of elements.
3908      *
3909      *   class Person extends ActiveRecord
3910      *   {
3911      *       function validate()
3912      *       {
3913      *           $this->validatesExclusionOf('username', array('admin', 'superuser'), "You don't belong here");
3914      *           $this->validatesExclusionOf('age', range(30,60), "This site is only for under 30 and over 60");
3915      *       }
3916      *   }
3917      * 
3918      * Parameters:
3919      * <tt>$array_of_possibilities</tt> - An array of items that the value shouldn't be part of
3920      * <tt>$message</tt> - Specifies a customer error message (default is: "is reserved")
3921      * <tt>$allow_null</tt> - If set to true, skips this validation if the attribute is null (default is: false)
3922      */
3923      function validatesExclusionOf($attribute_names, $array_of_possibilities, $message = 'exclusion', $allow_null = false)
3924      {
3925          $message = isset($this->_defaultErrorMessages[$message]) ? $this->t($this->_defaultErrorMessages[$message]) : $message;
3926  
3927          $attribute_names = Ak::toArray($attribute_names);
3928          foreach ($attribute_names as $attribute_name){
3929  
3930              if($allow_null ? (!empty($this->$attribute_name) ? (in_array(@$this->$attribute_name,$array_of_possibilities)) : false ) : (isset($this->$attribute_name) ? in_array(@$this->$attribute_name,$array_of_possibilities) : true )){
3931                  $this->addError($attribute_name, $message);
3932              }
3933          }
3934      }
3935  
3936  
3937  
3938  
3939      /**
3940      * Validates whether the value of the specified attribute is numeric.
3941      * 
3942      *   class Person extends ActiveRecord
3943      *   {
3944      *       function validate()
3945      *       {
3946      *           $this->validatesNumericalityOf('value');
3947      *       }
3948      *   }
3949      *
3950      * Parameters:
3951      * <tt>$message</tt> - A custom error message (default is: "is not a number")
3952      * <tt>$only_integer</tt> Specifies whether the value has to be an integer, e.g. an integral value (default is false)
3953      * <tt>$allow_null</tt> Skip validation if attribute is null (default is false).
3954      */
3955      function validatesNumericalityOf($attribute_names, $message = 'not_a_number', $only_integer = false, $allow_null = false)
3956      {
3957          $message = isset($this->_defaultErrorMessages[$message]) ? $this->t($this->_defaultErrorMessages[$message]) : $message;
3958  
3959          $attribute_names = Ak::toArray($attribute_names);
3960          foreach ($attribute_names as $attribute_name){
3961              if (isset($this->$attribute_name)){
3962                  $value = $this->$attribute_name;
3963                  if ($only_integer){
3964                      $is_int = is_numeric($value) && (int)$value == $value;
3965                      $has_error = !$is_int;
3966                  }else{
3967                      $has_error = !is_numeric($value);
3968                  }
3969              }else{
3970                  $has_error = $allow_null ? false : true;
3971              }
3972  
3973              if ($has_error){
3974                  $this->addError($attribute_name, $message);
3975              }
3976          }
3977      }
3978  
3979  
3980  
3981      /**
3982      * Returns true if no errors were added otherwise false.
3983      */
3984      function isValid()
3985      {
3986          $this->clearErrors();
3987          if($this->beforeValidation() && $this->notifyObservers('beforeValidation')){
3988  
3989  
3990              if($this->_set_default_attribute_values_automatically){
3991                  $this->_setDefaultAttributeValuesAutomatically();
3992              }
3993  
3994              $this->validate();
3995  
3996              if($this->_automated_validators_enabled){
3997                  $this->_runAutomatedValidators();
3998              }
3999  
4000              $this->afterValidation();
4001              $this->notifyObservers('afterValidation');
4002  
4003              if ($this->isNewRecord()){
4004                  if($this->beforeValidationOnCreate()){
4005                      $this->notifyObservers('beforeValidationOnCreate');
4006                      $this->validateOnCreate();
4007                      $this->afterValidationOnCreate();
4008                      $this->notifyObservers('afterValidationOnCreate');
4009                  }
4010              }else{
4011                  if($this->beforeValidationOnUpdate()){
4012                      $this->notifyObservers('beforeValidationOnUpdate');
4013                      $this->validateOnUpdate();
4014                      $this->afterValidationOnUpdate();
4015                      $this->notifyObservers('afterValidationOnUpdate');
4016                  }
4017              }
4018          }
4019  
4020          return !$this->hasErrors();
4021      }
4022  
4023      /**
4024      * By default the Active Record will validate for the maximum length for database columns. You can
4025      * disable the automated validators by setting $this->_automated_validators_enabled to false.
4026      * Specific validators are (for now):
4027      * $this->_automated_max_length_validator = false; // false by default, but you can set it to true on your model
4028      * $this->_automated_not_null_validator = false; // disabled by default
4029      *
4030      * @access private
4031      */
4032      function _runAutomatedValidators()
4033      {
4034          foreach ($this->_columns as $column_name=>$column_settings){
4035              if($this->_automated_max_length_validator &&
4036              empty($column_settings['primaryKey']) &&
4037              !empty($this->$column_name) &&
4038              !empty($column_settings['maxLength']) && $column_settings['maxLength'] > 0 &&
4039              strlen($this->$column_name) > $column_settings['maxLength']){
4040                  $this->addError($column_name, sprintf($this->_defaultErrorMessages['too_long'], $column_settings['maxLength']));
4041              }elseif($this->_automated_not_null_validator && empty($column_settings['primaryKey']) && !empty($column_settings['notNull']) && (!isset($this->$column_name) || is_null($this->$column_name))){
4042                  $this->addError($column_name,'empty');
4043              }
4044          }
4045      }
4046  
4047      /**
4048      * $this->_set_default_attribute_values_automatically = true; // This enables automated attribute setting from database definition
4049      *
4050      * @access private
4051      */
4052      function _setDefaultAttributeValuesAutomatically()
4053      {
4054          foreach ($this->_columns as $column_name=>$column_settings){
4055              if(empty($column_settings['primaryKey']) && isset($column_settings['hasDefault']) && $column_settings['hasDefault'] && (!isset($this->$column_name) || is_null($this->$column_name))){
4056                  if(empty($column_settings['defaultValue'])){
4057                      if($column_settings['type'] == 'integer' && empty($column_settings['notNull'])){
4058                          $this->$column_name = 0;
4059                      }elseif(($column_settings['type'] == 'string' || $column_settings['type'] == 'text') && empty($column_settings['notNull'])){
4060                          $this->$column_name = '';
4061                      }
4062                  }else {
4063                      $this->$column_name = $column_settings['defaultValue'];
4064                  }
4065              }
4066          }
4067      }
4068  
4069      /**
4070      * Overwrite this method for validation checks on all saves and use addError($field, $message); for invalid attributes.
4071      */
4072      function validate()
4073      {
4074      }
4075  
4076      /**
4077      * Overwrite this method for validation checks used only on creation.
4078      */
4079      function validateOnCreate()
4080      {
4081      }
4082  
4083      /**
4084      * Overwrite this method for validation checks used only on updates.
4085      */
4086      function validateOnUpdate()
4087      {
4088      }
4089  
4090      /*/Validators*/
4091  
4092  
4093      /**
4094                                    Observers
4095      ====================================================================
4096      See also: Callbacks.
4097      */
4098  
4099      /**
4100      * $state store the state of this observable object
4101      *
4102      * @access private
4103      */
4104      var $_observable_state;
4105  
4106      /**
4107      * @access private
4108      */
4109      function _instantiateDefaultObserver()
4110      {
4111          $default_observer_name = ucfirst($this->getModelName().'Observer');
4112          if(class_exists($default_observer_name)){
4113              //$Observer =& new $default_observer_name($this);
4114              Ak::singleton($default_observer_name,  $this);
4115          }
4116      }
4117  
4118      /**
4119      * Calls the $method using the reference to each
4120      * registered observer.
4121      * @return true (this is used internally for triggering observers on default callbacks)
4122      */ 
4123      function notifyObservers ($method = null)
4124      {
4125          $observers =& $this->getObservers();
4126          $observer_count = count($observers);
4127  
4128          if(!empty($method)){
4129              $this->setObservableState($method);
4130          }
4131  
4132          $model_name = $this->getModelName();
4133          for ($i=0; $i<$observer_count; $i++) {
4134              if(in_array($model_name, $observers[$i]->_observing)){
4135                  if(method_exists($observers[$i], $method)){
4136                      $observers[$i]->$method($this);
4137                  }else{
4138                      $observers[$i]->update($this->getObservableState(), &$this);
4139                  }
4140              }else{
4141                  $observers[$i]->update($this->getObservableState(), &$this);
4142              }
4143          }
4144          $this->setObservableState('');
4145  
4146          return true;
4147      }
4148  
4149  
4150      function setObservableState($state_message)
4151      {
4152          $this->_observable_state = $state_message;
4153      }
4154  
4155      function getObservableState()
4156      {
4157          return $this->_observable_state;
4158      }
4159      
4160      /**
4161      * Register the reference to an object object
4162      * 
4163      * 
4164      * @param $observer AkObserver
4165      * @param $options array of options for the observer
4166      * @return void
4167      */ 
4168      function addObserver(&$observer)
4169      {
4170          $staticVarNs='AkActiveRecord::observers::' . $this->_modelName;
4171          $observer_class_name = get_class($observer);
4172          /**
4173           * get the statically stored observers for the namespace
4174           */
4175          $observers = &Ak::getStaticVar($staticVarNs);
4176          if (!is_array($observers)) {
4177              $observers = array('classes'=>array(),'objects'=>array());
4178          }
4179          /**
4180           * if not already registered, the observerclass will 
4181           * be registered now
4182           */
4183          if (!in_array($observer_class_name,$observers['classes'])) {
4184              $observers['classes'][] = $observer_class_name;
4185              $observers['objects'][] = &$observer;
4186              Ak::setStaticVar($staticVarNs, $observers);
4187              
4188          }
4189      }
4190      /**
4191      * Register the reference to an object object
4192      * @return void
4193      */ 
4194      function &getObservers()
4195      {
4196          $staticVarNs='AkActiveRecord::observers::' . $this->_modelName;
4197          $key = 'objects';
4198  
4199          $array = array();
4200          $observers_arr =& Ak::getStaticVar($staticVarNs);
4201          if (isset($observers_arr[$key])) {
4202              $observers = &$observers_arr[$key];
4203          } else {
4204              $observers = &$array;
4205          }
4206  
4207          return $observers;
4208      }
4209  
4210      /*/Observers*/
4211  
4212  
4213  
4214  
4215      /**
4216                                      Error Handling
4217      ====================================================================
4218      See also: Validators.
4219      */
4220  
4221  
4222      /**
4223      * Returns the Errors array that holds all information about attribute error messages.
4224      */
4225      function getErrors()
4226      {
4227          return $this->_errors;
4228      }
4229  
4230      /**
4231      * Adds an error to the base object instead of any particular attribute. This is used
4232      * to report errors that doesn't tie to any specific attribute, but rather to the object
4233      * as a whole. These error messages doesn't get prepended with any field name when iterating
4234      * with yieldEachFullError, so they should be complete sentences.
4235      */
4236      function addErrorToBase($message)
4237      {
4238          $this->addError($this->getModelName(), $message);
4239      }
4240  
4241      /**
4242      * Returns errors assigned to base object through addToBase according to the normal rules of getErrorsOn($attribute).
4243      */
4244      function getBaseErrors()
4245      {
4246          $errors = $this->getErrors();
4247          return (array)@$errors[$this->getModelName()];
4248      }
4249  
4250  
4251      /**
4252      * Adds an error message ($message) to the ($attribute), which will be returned on a call to <tt>getErrorsOn($attribute)</tt>
4253      * for the same attribute and ensure that this error object returns false when asked if <tt>hasErrors</tt>. More than one
4254      * error can be added to the same $attribute in which case an array will be returned on a call to <tt>getErrorsOn($attribute)</tt>.
4255      * If no $message is supplied, "invalid" is assumed.
4256      */
4257      function addError($attribute, $message = 'invalid')
4258      {
4259          $message = isset($this->_defaultErrorMessages[$message]) ? $this->t($this->_defaultErrorMessages[$message]) : $message;
4260          $this->_errors[$attribute][] = $message;
4261      }
4262  
4263      /**
4264      * Will add an error message to each of the attributes in $attributes that is empty.
4265      */
4266      function addErrorOnEmpty($attribute_names, $message = 'empty')
4267      {
4268          $message = isset($this->_defaultErrorMessages[$message]) ? $this->t($this->_defaultErrorMessages[$message]) : $message;
4269          $attribute_names = Ak::toArray($attribute_names);
4270          foreach ($attribute_names as $attribute){
4271              if(empty($this->$attribute)){
4272                  $this->addError($attribute, $message);
4273              }
4274          }
4275      }
4276  
4277      /**
4278      * Will add an error message to each of the attributes in $attributes that is blank (using $this->isBlank).
4279      */
4280      function addErrorOnBlank($attribute_names, $message = 'blank')
4281      {
4282          $message = isset($this->_defaultErrorMessages[$message]) ? $this->t($this->_defaultErrorMessages[$message]) : $message;
4283          $attribute_names = Ak::toArray($attribute_names);
4284          foreach ($attribute_names as $attribute){
4285              if($this->isBlank(@$this->$attribute)){
4286                  $this->addError($attribute, $message);
4287              }
4288          }
4289      }
4290  
4291      /**
4292      * Will add an error message to each of the attributes in $attributes that has a length outside of the passed boundary $range.
4293      * If the length is above the boundary, the too_long_message message will be used. If below, the too_short_message.
4294      */
4295      function addErrorOnBoundaryBreaking($attribute_names, $range_begin, $range_end, $too_long_message = 'too_long', $too_short_message = 'too_short')
4296      {
4297          $too_long_message = isset($this->_defaultErrorMessages[$too_long_message]) ? $this->_defaultErrorMessages[$too_long_message] : $too_long_message;
4298          $too_short_message = isset($this->_defaultErrorMessages[$too_short_message]) ? $this->_defaultErrorMessages[$too_short_message] : $too_short_message;
4299  
4300          $attribute_names = Ak::toArray($attribute_names);
4301          foreach ($attribute_names as $attribute){
4302              if(@$this->$attribute < $range_begin){
4303                  $this->addError($attribute, $too_short_message);
4304              }
4305              if(@$this->$attribute > $range_end){
4306                  $this->addError($attribute, $too_long_message);
4307              }
4308          }
4309  
4310      }
4311  
4312      function addErrorOnBoundryBreaking ($attributes, $range_begin, $range_end, $too_long_message = 'too_long', $too_short_message = 'too_short')
4313      {
4314          $this->addErrorOnBoundaryBreaking($attributes, $range_begin, $range_end, $too_long_message, $too_short_message);
4315      }
4316  
4317      /**
4318      * Returns true if the specified $attribute has errors associated with it.
4319      */
4320      function isInvalid($attribute)
4321      {
4322          return $this->getErrorsOn($attribute);
4323      }
4324  
4325      /**
4326      * Returns false, if no errors are associated with the specified $attribute.
4327      * Returns the error message, if one error is associated with the specified $attribute.
4328      * Returns an array of error messages, if more than one error is associated with the specified $attribute.
4329      */
4330      function getErrorsOn($attribute)
4331      {
4332          if (empty($this->_errors[$attribute])){
4333              return false;
4334          }elseif (count($this->_errors[$attribute]) == 1){
4335              $k = array_keys($this->_errors[$attribute]);
4336              return $this->_errors[$attribute][$k[0]];
4337          }else{
4338              return $this->_errors[$attribute];
4339          }
4340      }
4341  
4342  
4343      /**
4344      * Yields each attribute and associated message per error added.
4345      */
4346      function yieldEachError()
4347      {
4348          foreach ($this->_errors as $errors){
4349              foreach ($errors as $error){
4350                  $this->yieldError($error);
4351              }
4352          }
4353      }
4354  
4355      function yieldError($message)
4356      {
4357          $messages = is_array($message) ? $message : array($message);
4358          foreach ($messages as $message){
4359              echo "<div class='error'><p>$message</p></div>\n";
4360          }
4361  
4362      }
4363  
4364      /**
4365      * Yields each full error message added. So Person->addError("first_name", "can't be empty") will be returned
4366      * through iteration as "First name can't be empty".
4367      */
4368      function yieldEachFullError()
4369      {
4370          $full_messages = $this->getFullErrorMessages();
4371          foreach ($full_messages as $full_message){
4372              $this->yieldError($full_message);
4373          }
4374      }
4375  
4376  
4377      /**
4378      * Returns all the full error messages in an array.
4379      */
4380      function getFullErrorMessages()
4381      {
4382          $full_messages = array();
4383  
4384          foreach ($this->_errors as $attribute=>$errors){
4385              $full_messages[$attribute] = array();
4386              foreach ($errors as $error){
4387                  $full_messages[$attribute][] = $this->t('%attribute_name %error', array(
4388                  '%attribute_name'=>AkInflector::humanize($this->_delocalizeAttribute($attribute)),
4389                  '%error'=>$error
4390                  ));
4391              }
4392          }
4393          return $full_messages;
4394      }
4395  
4396      /**
4397      * Returns true if no errors have been added.
4398      */
4399      function hasErrors()
4400      {
4401          return !empty($this->_errors);
4402      }
4403  
4404      /**
4405      * Removes all the errors that have been added.
4406      */
4407      function clearErrors()
4408      {
4409          $this->_errors = array();
4410      }
4411  
4412      /**
4413      * Returns the total number of errors added. Two errors added to the same attribute will be counted as such
4414      * with this as well.
4415      */
4416      function countErrors()
4417      {
4418          $error_count = 0;
4419          foreach ($this->_errors as $errors){
4420              $error_count = count($errors)+$error_count;
4421          }
4422  
4423          return $error_count;
4424      }
4425  
4426  
4427      function errorsToString($print = false)
4428      {
4429          $result = "\n<div id='errors'>\n<ul class='error'>\n";
4430          foreach ($this->getFullErrorMessages() as $error){
4431              $result .= is_array($error) ? "<li class='error'>".join('</li><li class=\'error\'>',$error)."</li>\n" : "<li class='error'>$error</li>\n";
4432          }
4433          $result .= "</ul>\n</div>\n";
4434  
4435          if($print){
4436              echo $result;
4437          }
4438          return $result;
4439      }
4440  
4441      /*/Error Handling*/
4442  
4443  
4444  
4445      /**
4446                              Act as Behaviours
4447      ====================================================================
4448      See also: Acts as List, Acts as Tree, Acts as Nested Set.
4449      */
4450  
4451      /**
4452       * actAs provides a method for extending Active Record models.
4453       * 
4454       * Example:
4455       * $this->actsAs('list', array('scope' => 'todo_list'));
4456       */
4457      function actsAs($behaviour, $options = array())
4458      {
4459          $class_name = $this->_getActAsClassName($behaviour);
4460          $underscored_place_holder = AkInflector::underscore($behaviour);
4461          $camelized_place_holder = AkInflector::camelize($underscored_place_holder);
4462  
4463          if($this->$underscored_place_holder =& $this->_getActAsInstance($class_name, $options)){
4464              $this->$camelized_place_holder =& $this->$underscored_place_holder;
4465              if($this->$underscored_place_holder->init($options)){
4466                  $this->__ActsLikeAttributes[$underscored_place_holder] = $underscored_place_holder;
4467              }
4468          }
4469      }
4470  
4471      /**
4472      * @access private
4473      */
4474      function _getActAsClassName($behaviour)
4475      {
4476          $class_name = AkInflector::camelize($behaviour);
4477          return file_exists(AK_LIB_DIR.DS.'AkActiveRecord'.DS.'AkActsAsBehaviours'.DS.'AkActsAs'.$class_name.'.php') && !class_exists('ActsAs'.$class_name) ?
4478          'AkActsAs'.$class_name : 'ActsAs'.$class_name;
4479      }
4480  
4481      /**
4482      * @access private
4483      */
4484      function &_getActAsInstance($class_name, $options)
4485      {
4486          if(!class_exists($class_name)){
4487              if(substr($class_name,0,2) == 'Ak'){
4488                  include_once(AK_LIB_DIR.DS.'AkActiveRecord'.DS.'AkActsAsBehaviours'.DS.$class_name.'.php');
4489              }else{
4490                  include_once(AK_APP_PLUGINS_DIR.DS.AkInflector::underscore($class_name).DS.'lib'.DS.$class_name.'.php');
4491              }
4492          }
4493          if(!class_exists($class_name)){
4494              trigger_error(Ak::t('The class %class used for handling an "act_as %class" does not exist',array('%class'=>$class_name)), E_USER_ERROR);
4495              $false = false;
4496              return $false;
4497          }else{
4498              $ActAsInstance =& new $class_name($this, $options);
4499              return $ActAsInstance;
4500          }
4501      }
4502  
4503      /**
4504      * @access private
4505      */
4506      function _loadActAsBehaviours()
4507      {
4508          //$this->act_as = !empty($this->acts_as) ? $this->acts_as : (empty($this->act_as) ? false : $this->act_as);
4509          if(!empty($this->act_as)){
4510              if(is_string($this->act_as)){
4511                  $this->act_as = array_unique(array_diff(array_map('trim',explode(',',$this->act_as.',')), array('')));
4512                  foreach ($this->act_as as $type){
4513                      $this->actsAs($type);
4514                  }
4515              }elseif (is_array($this->act_as)){
4516                  foreach ($this->act_as as $type=>$options){
4517                      $this->actsAs($type, $options);
4518                  }
4519              }
4520          }
4521      }
4522  
4523      /**
4524      * Returns a comma separated list of possible acts like (active record, nested set, list)....
4525      */
4526      function actsLike()
4527      {
4528          $result = 'active record';
4529          foreach ($this->__ActsLikeAttributes as $type){
4530              if(!empty($this->$type) && is_object($this->$type) && method_exists($this->{$type}, 'getType')){
4531                  $result .= ','.$this->{$type}->getType();
4532              }
4533          }
4534          return $result;
4535      }
4536  
4537      /*/Act as Behaviours*/
4538  
4539      /**
4540                              Debugging
4541      ====================================================================
4542      */
4543  
4544      
4545      function dbug()
4546      {
4547          if(!$this->isConnected()){
4548              $this->setConnection();
4549          }
4550          $this->_db->connection->debug = $this->_db->connection->debug ? false : true;
4551          $this->db_debug =& $this->_db->connection->debug;
4552      }
4553  
4554      function toString($print = false)
4555      {
4556          $result = '';
4557          if(!AK_CLI || (AK_ENVIRONMENT == 'testing' && !AK_CLI)){
4558              $result = "<h2>Details for ".AkInflector::humanize(AkInflector::underscore($this->getModelName()))." with ".$this->getPrimaryKey()." ".$this->getId()."</h2>\n<dl>\n";
4559              foreach ($this->getColumnNames() as $column=>$caption){
4560                  $result .= "<dt>$caption</dt>\n<dd>".$this->getAttribute($column)."</dd>\n";
4561              }
4562              $result .= "</dl>\n<hr />";
4563              if($print){
4564                  echo $result;
4565              }
4566          }elseif(AK_DEV_MODE){
4567              $result =   "\n".
4568              str_replace("\n"," ",var_export($this->getAttributes(),true));
4569              $result .= "\n";
4570              echo $result;
4571              return '';
4572          }elseif (AK_CLI){
4573              $result = "\n-------\n Details for ".AkInflector::humanize(AkInflector::underscore($this->getModelName()))." with ".$this->getPrimaryKey()." ".$this->getId()." ==\n\n/==\n";
4574              foreach ($this->getColumnNames() as $column=>$caption){
4575                  $result .= "\t * $caption: ".$this->getAttribute($column)."\n";
4576              }
4577              $result .= "\n\n-------\n";
4578              if($print){
4579                  echo $result;
4580              }
4581          }
4582          return $result;
4583      }
4584  
4585      function dbugging($trace_this_on_debug_mode = null)
4586      {
4587          if(!empty($this->_db->debug) && !empty($trace_this_on_debug_mode)){
4588              $message = !is_scalar($trace_this_on_debug_mode) ? var_export($trace_this_on_debug_mode, true) : (string)$trace_this_on_debug_mode;
4589              Ak::trace($message);
4590          }
4591          return !empty($this->_db->debug);
4592      }
4593  
4594  
4595  
4596      function debug ($data = 'active_record_class', $_functions=0)
4597      {
4598          if(!AK_DEBUG && !AK_DEV_MODE){
4599              return;
4600          }
4601  
4602          $data = $data == 'active_record_class' ?  (AK_PHP5 ? clone($this) : $this) : $data;
4603  
4604          if($_functions!=0) {
4605              $sf=1;
4606          } else {
4607              $sf=0 ;
4608          }
4609  
4610          if (isset ($data)) {
4611              if (is_array($data) || is_object($data)) {
4612  
4613                  if (count ($data)) {
4614                      echo AK_CLI ? "/--\n" : "<ol>\n";
4615                      while (list ($key,$value) = each ($data)) {
4616                          if($key{0} == '_'){
4617                              continue;
4618                          }
4619                          $type=gettype($value);
4620                          if ($type=="array") {
4621                              AK_CLI ? printf ("\t* (%s) %s:\n",$type, $key) :
4622                              printf ("<li>(%s) <b>%s</b>:\n",$type, $key);
4623                              ob_start();
4624                              Ak::debug ($value,$sf);
4625                              $lines = explode("\n",ob_get_clean()."\n");
4626                              foreach ($lines as $line){
4627                                  echo "\t".$line."\n";
4628                              }
4629                          }elseif($type == "object"){
4630                              if(method_exists($value,'hasColumn') && $value->hasColumn($key)){
4631                                  $value->toString(true);
4632                                  AK_CLI ? printf ("\t* (%s) %s:\n",$type, $key) :
4633                                  printf ("<li>(%s) <b>%s</b>:\n",$type, $key);
4634                                  ob_start();
4635                                  Ak::debug ($value,$sf);
4636                                  $lines = explode("\n",ob_get_clean()."\n");
4637                                  foreach ($lines as $line){
4638                                      echo "\t".$line."\n";
4639                                  }
4640                              }
4641                          }elseif (eregi ("function", $type)) {
4642                              if ($sf) {
4643                                  AK_CLI ? printf ("\t* (%s) %s:\n",$type, $key, $value) :
4644                                  printf ("<li>(%s) <b>%s</b> </li>\n",$type, $key, $value);
4645                              }
4646                          } else {
4647                              if (!$value) {
4648                                  $value="(none)";
4649                              }
4650                              AK_CLI ? printf ("\t* (%s) %s = %s\n",$type, $key, $value) :
4651                              printf ("<li>(%s) <b>%s</b> = %s</li>\n",$type, $key, $value);
4652                          }
4653                      }
4654                      echo AK_CLI ? "\n--/\n" : "</ol>fin.\n";
4655                  } else {
4656                      echo "(empty)";
4657                  }
4658              }
4659          }
4660      }
4661  
4662      /*/Debugging*/
4663  
4664  
4665  
4666      /**
4667                          Utilities
4668      ====================================================================
4669      */
4670      /**
4671       * Selects and filters a search result to include only specified columns
4672       * 
4673       *    $people_for_select = $People->select($People->find(),'name','email');
4674       *    
4675       *    Now $people_for_select will hold an array with
4676       *    array (
4677       *        array ('name' => 'Jose','email' => 'jose@example.com'),
4678       *        array ('name' => 'Alicia','email' => 'alicia@example.com'),
4679       *        array ('name' => 'Hilario','email' => 'hilario@example.com'),
4680       *        array ('name' => 'Bermi','email' => 'bermi@example.com')
4681       *    );
4682       */
4683      function select(&$source_array)
4684      {
4685          $resulting_array = array();
4686          if(!empty($source_array) && is_array($source_array) && func_num_args() > 1) {
4687          (array)$args = array_filter(array_slice(func_get_args(),1),array($this,'hasColumn'));
4688          foreach ($source_array as $source_item){
4689              $item_fields = array();
4690              foreach ($args as $arg){
4691                  $item_fields[$arg] =& $source_item->get($arg);
4692              }
4693              $resulting_array[] =& $item_fields;
4694          }
4695          }
4696          return $resulting_array;
4697      }
4698  
4699  
4700      /**
4701       * Collect is a function for selecting items from double depth array
4702       * like the ones returned by the AkActiveRecord. This comes useful when you just need some
4703       * fields for generating tables, select lists with only desired fields.
4704       *
4705       *    $people_for_select = Ak::select($People->find(),'id','email');
4706       *    
4707       *    Returns something like:
4708       *    array (
4709       *        array ('10' => 'jose@example.com'),
4710       *        array ('15' => 'alicia@example.com'),
4711       *        array ('16' => 'hilario@example.com'),
4712       *        array ('18' => 'bermi@example.com')
4713       *    );
4714       */
4715      function collect(&$source_array, $key_index, $value_index)
4716      {
4717          $resulting_array = array();
4718          if(!empty($source_array) && is_array($source_array)) {
4719              foreach ($source_array as $source_item){
4720                  $resulting_array[$source_item->get($key_index)] = $source_item->get($value_index);
4721              }
4722          }
4723          return $resulting_array;
4724      }
4725  
4726      /**
4727       * Generate a json representation of the model record.
4728       * 
4729       * parameters:
4730       *
4731       * @param array $options 
4732       *  
4733       *              option parameters:
4734       *             array(
4735       *              'collection' => array($Person1,$Person), // array of ActiveRecords
4736       *              'include' => array('association1','association2'), // include the associations when exporting
4737       *              'exclude' => array('id','name'), // exclude the attribtues
4738       *              'only' => array('email','last_name') // only export these attributes
4739       *              )
4740       * @return string in Json Format
4741       */
4742      function toJson($options = array())
4743      {
4744          if (is_array($options) && isset($options[0]) && is_a($options[0], 'AkActiveRecord')) {
4745              $options = array('collection'=>$options);
4746          }
4747          if (isset($options['collection']) && is_array($options['collection']) && $options['collection'][0]->_modelName == $this->_modelName) {
4748              $json = '';
4749  
4750              $collection = $options['collection'];
4751              unset($options['collection']);
4752              $jsonVals = array();
4753              foreach ($collection as $element) {
4754                  $jsonVals[]= $element->toJson($options);
4755              }
4756              $json = '['.implode(',',$jsonVals).']';
4757              return $json;
4758          }
4759          /**
4760           * see if we need to include associations
4761           */
4762          $associatedIds = array();
4763          if (isset($options['include']) && !empty($options['include'])) {
4764              $options['include'] = is_array($options['include'])?$options['include']:preg_split('/,\s*/',$options['include']);
4765              foreach ($this->_associations as $key => $obj) {
4766                  if (in_array($key,$options['include'])) {
4767                      $associatedIds[$obj->getAssociationId() . '_id'] = array('name'=>$key,'type'=>$obj->getType());
4768                  }
4769              }
4770          }
4771          if (isset($options['only'])) {
4772              $options['only'] = is_array($options['only'])?$options['only']:preg_split('/,\s*/',$options['only']);
4773          }
4774          if (isset($options['except'])) {
4775              $options['except'] = is_array($options['except'])?$options['except']:preg_split('/,\s*/',$options['except']);
4776          }
4777          foreach ($this->_columns as $key => $def) {
4778              
4779              if (isset($options['except']) && in_array($key, $options['except'])) {
4780                  continue;
4781              } else if (isset($options['only']) && !in_array($key, $options['only'])) {
4782                  continue;
4783              } else {
4784                  $val = $this->$key;
4785                  $type = $this->getColumnType($key);
4786                  if (($type == 'serial' || $type=='integer') && $val!==null) $val = intval($val);
4787                  if ($type == 'float' && $val!==null) $val = floatval($val);
4788                  if ($type == 'boolean') $val = $val?1:0;
4789                  $data[$key] = $val;
4790              }
4791          }
4792          if (isset($options['include'])) {
4793              foreach($this->_associationIds as $key=>$val) {
4794                  if ((in_array($key,$options['include']) || in_array($val,$options['include']))) {
4795                      $this->$key->load();
4796                      $associationElement = $key;
4797                      $associationElement = $this->_convert_column_to_xml_element($associationElement);
4798                      if (is_array($this->$key)) {
4799                          $data[$associationElement] = array();
4800                          foreach ($this->$key as $el) {
4801                              if (is_a($el,'AkActiveRecord')) {
4802                                  $attributes = $el->getAttributes();
4803                                  foreach($attributes as $ak=>$av) {
4804                                      $type = $el->getColumnType($ak);
4805                                      if (($type == 'serial' || $type=='integer') && $av!==null) $av = intval($av);
4806                                      if ($type == 'float' && $av!==null) $av = floatval($av);
4807                                      if ($type == 'boolean') $av = $av?1:0;
4808                                      $attributes[$ak]=$av;
4809                                  }
4810                                  $data[$associationElement][] = $attributes;
4811                              }
4812                          }
4813                      } else {
4814                          $el = &$this->$key->load();
4815                          if (is_a($el,'AkActiveRecord')) {
4816                              $attributes = $el->getAttributes();
4817                              foreach($attributes as $ak=>$av) {
4818                                  $type = $el->getColumnType($ak);
4819                                  if (($type == 'serial' || $type=='integer') && $av!==null) $av = intval($av);
4820                                  if ($type == 'float' && $av!==null) $av = floatval($av);
4821                                  if ($type == 'boolean') $av = $av?1:0;
4822                                  $attributes[$ak]=$av;
4823                              }
4824                              $data[$associationElement] = $attributes;
4825                          }
4826                      }
4827                  }
4828              }
4829          }
4830          return Ak::toJson($data);
4831      }
4832      function _convert_column_to_xml_element($col)
4833      {
4834          return str_replace('_','-',$col);
4835      }
4836      function _convert_column_from_xml_element($col)
4837      {
4838          return str_replace('-','_',$col);
4839      }
4840      
4841      function _parseXmlAttributes($attributes)
4842      {
4843          $new = array();
4844          foreach($attributes as $key=>$value)
4845          {
4846              $new[$this->_convert_column_from_xml_element($key)] = $value;
4847          }
4848          return $new;
4849      }
4850      
4851      function &_generateModelFromArray($modelName,$attributes)
4852      {
4853          if (isset($attributes[0]) && is_array($attributes[0])) {
4854              $attributes = $attributes[0];
4855          }
4856          $record = new $modelName('attributes',$this->_parseXmlAttributes($attributes));
4857          $record->_newRecord = !empty($attributes['id']);
4858  
4859          $associatedIds = array();
4860          foreach ($record->getAssociatedIds() as $key) {
4861              if (isset($attributes[$key]) && is_array($attributes[$key])) {
4862                  $class = $record->$key->_AssociationHandler->getOption($key,'class_name');
4863                  $related = $this->_generateModelFromArray($class,$attributes[$key]);
4864                  $record->$key->build($related->getAttributes(),false);
4865                  $related = &$record->$key->load();
4866                  $record->$key = &$related;
4867              }
4868          }
4869          return $record;
4870      }
4871      
4872      function _fromArray($array)
4873      {
4874          $data  = $array;
4875          $modelName = $this->getModelName();
4876          $values = array();
4877          if (!isset($data[0])) {
4878              $data = array($data);
4879          }
4880          foreach ($data as $key => $value) {
4881              if (is_array($value)){
4882                  $values[] = &$this->_generateModelFromArray($modelName,$value);
4883              }
4884          }
4885          return count($values)==1?$values[0]:$values;
4886      }
4887      
4888      /**
4889       * Reads Xml in the following format:
4890       * 
4891       * 
4892       * <?xml version="1.0" encoding="UTF-8"?>
4893       * <person>
4894       *    <id>1</id>
4895       *    <first-name>Hansi</first-name>
4896       *    <last-name>Müller</last-name>
4897       *    <email>hans@mueller.com</email>
4898       *    <created-at type="datetime">2008-01-01 13:01:23</created-at>
4899       * </person>
4900       * 
4901       * and returns an ActiveRecord Object
4902       *
4903       * @param string $xml
4904       * @return AkActiveRecord
4905       */
4906      function fromXml($xml)
4907      {
4908          $array = Ak::xml_to_array($xml);
4909          $array = $this->_fromXmlCleanup($array);
4910           return $this->_fromArray($array);
4911      }
4912      
4913      function _fromXmlCleanup($array)
4914      {
4915          $result = array();
4916          $key = key($array);
4917          while(is_string($key) && is_array($array[$key]) && count($array[$key])==1) {
4918              $array = $array[$key][0];
4919              $key = key($array);
4920          }
4921          if (is_string($key) && is_array($array[$key])) {
4922              $array = $array[$key];
4923          }
4924          return $array;
4925      }
4926      /**
4927       * Reads Json string in the following format:
4928       * 
4929       * {"id":1,"first_name":"Hansi","last_name":"M\u00fcller",
4930       *  "email":"hans@mueller.com","created_at":"2008-01-01 13:01:23"}
4931       * 
4932       * and returns an ActiveRecord Object
4933       *
4934       * @param string $json
4935       * @return AkActiveRecord
4936       */
4937      function fromJson($json)
4938      {
4939          $json = Ak::fromJson($json);
4940          $array = Ak::convert('Object','Array',$json);
4941          return $this->_fromArray($array);
4942      }
4943      
4944      /**
4945       * Generate a xml representation of the model record.
4946       * 
4947       * Example result:
4948       * 
4949       * <?xml version="1.0" encoding="UTF-8"?>
4950       * <person>
4951       *    <id>1</id>
4952       *    <first-name>Hansi</first-name>
4953       *    <last-name>Müller</last-name>
4954       *    <email>hans@mueller.com</email>
4955       *    <created-at type="datetime">2008-01-01 13:01:23</created-at>
4956       * </person>
4957       * 
4958       * parameters:
4959       *
4960       * @param array $options 
4961       *  
4962       *              option parameters:
4963       *             array(
4964       *              'collection' => array($Person1,$Person), // array of ActiveRecords
4965       *              'include' => array('association1','association2'), // include the associations when exporting
4966       *              'exclude' => array('id','name'), // exclude the attribtues
4967       *              'only' => array('email','last_name') // only export these attributes
4968       *              )
4969       * @return string in Xml Format
4970       */
4971      function toXml($options = array())
4972      {
4973          if (is_array($options) && isset($options[0]) && is_a($options[0], 'AkActiveRecord')) {
4974              $options = array('collection'=>$options);
4975          }
4976          if (isset($options['collection']) && is_array($options['collection']) && $options['collection'][0]->_modelName == $this->_modelName) {
4977              $root = strtolower(AkInflector::pluralize($this->_modelName));
4978              $root = $this->_convert_column_to_xml_element($root);
4979              $xml = '';
4980              if (!(isset($options['skip_instruct']) && $options['skip_instruct'] == true)) {
4981                  $xml .= '<?xml version="1.0" encoding="UTF-8"?>';
4982              }
4983              $xml .= '<' . $root . '>';
4984              $collection = $options['collection'];
4985              unset($options['collection']);
4986              $options['skip_instruct'] = true;
4987              foreach ($collection as $element) {
4988                  $xml .= $element->toXml($options);
4989              }
4990              $xml .= '</' . $root .'>';
4991              return $xml;
4992          }
4993          /**
4994           * see if we need to include associations
4995           */
4996          $associatedIds = array();
4997          if (isset($options['include']) && !empty($options['include'])) {
4998              $options['include'] = is_array($options['include'])?$options['include']:preg_split('/,\s*/',$options['include']);
4999              foreach ($this->_associations as $key => $obj) {
5000                  if (in_array($key,$options['include'])) {
5001                      if ($obj->getType()!='hasAndBelongsToMany') {
5002                          $associatedIds[$obj->getAssociationId() . '_id'] = array('name'=>$key,'type'=>$obj->getType());
5003                      } else {
5004                          $associatedIds[$key] = array('name'=>$key,'type'=>$obj->getType());
5005                      }
5006                  }
5007              }
5008          }
5009          if (isset($options['only'])) {
5010              $options['only'] = is_array($options['only'])?$options['only']:preg_split('/,\s*/',$options['only']);
5011          }
5012          if (isset($options['except'])) {
5013              $options['except'] = is_array($options['except'])?$options['except']:preg_split('/,\s*/',$options['except']);
5014          }
5015          $xml = '';
5016          if (!(isset($options['skip_instruct']) && $options['skip_instruct'] == true)) {
5017              $xml .= '<?xml version="1.0" encoding="UTF-8"?>';
5018          }
5019          $root = $this->_convert_column_to_xml_element(strtolower($this->_modelName));
5020          
5021          $xml .= '<' . $root . '>';
5022          $xml .= "\n";
5023          foreach ($this->_columns as $key => $def) {
5024              
5025              if (isset($options['except']) && in_array($key, $options['except'])) {
5026                  continue;
5027              } else if (isset($options['only']) && !in_array($key, $options['only'])) {
5028                  continue;
5029              } else {
5030                  $columnType = $def['type'];
5031                  $elementName = $this->_convert_column_to_xml_element($key);
5032                  $xml .= '<' . $elementName;
5033                  $val = $this->$key;
5034                  if (!in_array($columnType,array('string','text','serial'))) {
5035                      $xml .= ' type="' . $columnType . '"';
5036                      if ($columnType=='boolean') $val = $val?1:0;
5037                  }
5038                  $xml .= '>' . Ak::utf8($val) . '</' . $elementName . '>';
5039                  $xml .= "\n";
5040              }
5041          }
5042          if (isset($options['include'])) {
5043              foreach($this->_associationIds as $key=>$val) {
5044                  if ((in_array($key,$options['include']) || in_array($val,$options['include']))) {
5045                      if (is_array($this->$key)) {
5046                          
5047                          $associationElement = $key;
5048                          $associationElement = AkInflector::pluralize($associationElement);
5049                          $associationElement = $this->_convert_column_to_xml_element($associationElement);
5050                          $xml .= '<'.$associationElement.'>';
5051                          foreach ($this->$key as $el) {
5052                              if (is_a($el,'AkActiveRecord')) {
5053                                  $xml .= $el->toXml(array('skip_instruct'=>true));
5054                              }
5055                          }
5056                          $xml .= '</' . $associationElement .'>';
5057                      } else {
5058                          $el = &$this->$key->load();
5059                          if (is_a($el,'AkActiveRecord')) {
5060                              $xml.=$el->toXml(array('skip_instruct'=>true));
5061                          }
5062                      }
5063                  }
5064              }
5065          }
5066          $xml .= '</' . $root . '>';
5067          return $xml;
5068      }
5069      /**
5070       * converts to yaml-strings 
5071       * 
5072       * examples: 
5073       * User::toYaml($users->find('all'));
5074       * $Bermi->toYaml();
5075       *
5076       * @param array of ActiveRecords[optional] $data
5077       */
5078      function toYaml($data = null)
5079      {
5080          return Ak::convert('active_record', 'yaml', empty($data) ? $this : $data);
5081      }
5082  
5083  
5084      /**
5085      * Parses an special formated array as a list of keys and values
5086      * 
5087      * This function generates an array with values and keys from an array with numeric keys.
5088      * 
5089      * This allows to parse an array to a function in the following manner.
5090      * create('first_name->', 'Bermi', 'last_name->', 'Ferrer');
5091      * //Previous code will be the same that
5092      * create(array('first_name'=>'Bermi', 'last_name'=> 'Ferrer'));
5093      *
5094      * Use this syntax only for quick testings, not for production environments. If the number of arguments varies, the result might be unpredictable.
5095      *
5096      * This function syntax is disabled by default. You need to define('AK_ENABLE_AKELOS_ARGS', true)
5097      * if you need this functionality.
5098      *
5099      * @deprecated
5100      */
5101      function parseAkelosArgs(&$args)
5102      {
5103          if(!AK_ENABLE_AKELOS_ARGS){
5104              $this->_castDateParametersFromDateHelper_($args);
5105              return ;
5106          }
5107          $k = array_keys($args);
5108          if(isset($k[1]) && substr($args[$k[0]],-1) == '>'){
5109              $size = sizeOf($k);
5110              $params = array();
5111              for($i = 0; $i < $size; $i++ ) {
5112                  $v = $args[$k[$i]];
5113                  if(!isset($key) && is_string($args[$k[$i]]) && substr($v,-1) == '>'){
5114                      $key = rtrim($v, '=-> ');
5115                  }elseif(isset($key)) {
5116                      $params[$key] = $v;
5117                      unset($key);
5118                  }else{
5119                      $params[$k[$i]] = $v;
5120                  }
5121              }
5122              if(!empty($params)){
5123                  $args = $params;
5124              }
5125          }
5126          $this->_castDateParametersFromDateHelper_($args);
5127      }
5128      /**
5129      * Gets an array from a string.
5130      *
5131      * Acts like Php explode() function but uses any of this as valid separators ' AND ',' and ',' + ',' ',',',';'
5132      */
5133      function getArrayFromAkString($string)
5134      {
5135          if(is_array($string)){
5136              return $string;
5137          }
5138          $string = str_replace(array(' AND ',' and ',' + ',' ',',',';'),array('|','|','|','','|','|'),trim($string));
5139          return strstr($string,'|') ? explode('|', $string) : array($string);
5140      }
5141      /*/Utilities*/
5142  
5143  
5144      function getAttributeCondition($argument)
5145      {
5146          if(is_array($argument)){
5147              return 'IN (?)';
5148          }elseif (is_null($argument)){
5149              return 'IS ?';
5150          }else{
5151              return '= ?';
5152          }
5153      }
5154  
5155  
5156      /**
5157                       Calculations
5158   ====================================================================
5159   */
5160  
5161      /**
5162      * @access private
5163      */
5164      var $_calculation_options = array('conditions', 'joins', 'order', 'select', 'group', 'having', 'distinct', 'limit', 'offset');
5165  
5166      /**
5167        * Count operates using three different approaches.
5168        *
5169        * * Count all: By not passing any parameters to count, it will return a count of all the rows for the model.
5170        * * Count by conditions or joins
5171        * * Count using options will find the row count matched by the options used.
5172        *
5173        * The last approach, count using options, accepts an option hash as the only parameter. The options are:
5174        *
5175        * * <tt>'conditions'</tt>: An SQL fragment like "administrator = 1" or array("user_name = ?", $username ). See conditions in the intro.
5176        * * <tt>'joins'</tt>: An SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id". (Rarely needed).
5177        * * <tt>'order'</tt>: An SQL fragment like "created_at DESC, name" (really only used with GROUP BY calculations).
5178        * * <tt>'group'</tt>: An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
5179        * * <tt>'select'</tt>: By default, this is * as in SELECT * FROM, but can be changed if you for example want to do a join.
5180        * * <tt>'distinct'</tt>: Set this to true to make this a distinct calculation, such as SELECT COUNT(DISTINCT posts.id) ...
5181        *
5182        * Examples for counting all:
5183        *   $Person->count();         // returns the total count of all people
5184        *
5185        * Examples for count by +conditions+ and +joins+ (this has been deprecated):
5186        *   $Person->count("age > 26");  // returns the number of people older than 26
5187        *   $Person->find("age > 26 AND job.salary > 60000", "LEFT JOIN jobs on jobs.person_id = ".$Person->id); // returns the total number of rows matching the conditions and joins fetched by SELECT COUNT(*).
5188        *
5189        * Examples for count with options:
5190        *   $Person->count('conditions' => "age > 26");
5191        *   $Person->count('conditions' => "age > 26 AND job.salary > 60000", 'joins' => "LEFT JOIN jobs on jobs.person_id = $Person->id"); // finds the number of rows matching the conditions and joins.
5192        *   $Person->count('id', 'conditions' => "age > 26"); // Performs a COUNT(id)
5193        *   $Person->count('all', 'conditions' => "age > 26"); // Performs a COUNT(*) ('all' is an alias for '*')
5194        *
5195        * Note: $Person->count('all') will not work because it will use 'all' as the condition.  Use $Person->count() instead.
5196        */
5197      function count()
5198      {
5199          $args = func_get_args();
5200          list($column_name, $options) = $this->_constructCountOptionsFromLegacyArgs($args);
5201          return $this->calculate('count', $column_name, $options);
5202      }
5203  
5204      /**
5205        * Calculates average value on a given column.  The value is returned as a float.  See #calculate for examples with options.
5206        *  
5207        *     $Person->average('age');
5208        */
5209      function average($column_name, $options = array())
5210      {
5211          return $this->calculate('avg', $column_name, $options);
5212      }
5213  
5214      /**
5215        * Calculates the minimum value on a given column.  The value is returned with the same data type of the column..  See #calculate for examples with options.
5216        *
5217        *   $Person->minimum('age');
5218        */
5219      function minimum($column_name, $options = array())
5220      {
5221          return $this->calculate('min', $column_name, $options);
5222      }
5223  
5224      /**
5225        * Calculates the maximum value on a given column.  The value is returned with the same data type of the column..  See #calculate for examples with options.
5226        *
5227        *   $Person->maximum('age');
5228        */
5229      function maximum($column_name, $options = array())
5230      {
5231          return $this->calculate('max', $column_name, $options);
5232      }
5233  
5234      /**
5235        * Calculates the sum value on a given column.  The value is returned with the same data type of the column..  See #calculate for examples with options.
5236        *
5237        *   $Person->sum('age');
5238        */
5239      function sum($column_name, $options = array())
5240      {
5241          return $this->calculate('sum', $column_name, $options);
5242      }
5243  
5244      /**
5245        * This calculates aggregate values in the given column:  Methods for count, sum, average, minimum, and maximum have been added as shortcuts.
5246        * Options such as 'conditions', 'order', 'group', 'having', and 'joins' can be passed to customize the query.
5247        *
5248        * There are two basic forms of output:
5249        *   * Single aggregate value: The single value is type cast to integer for COUNT, float for AVG, and the given column's type for everything else.
5250        *   * Grouped values: This returns an ordered hash of the values and groups them by the 'group' option.  It takes a column name.
5251        *
5252        *       $values = $Person->maximum('age', array('group' => 'last_name'));
5253        *       echo $values["Drake"]
5254        *       => 43
5255        *
5256        * Options:
5257        * * <tt>'conditions'</tt>: An SQL fragment like "administrator = 1" or array( "user_name = ?", username ). See conditions in the intro.
5258        * * <tt>'joins'</tt>: An SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id". (Rarely needed).
5259        *   The records will be returned read-only since they will have attributes that do not correspond to the table's columns.
5260        * * <tt>'order'</tt>: An SQL fragment like "created_at DESC, name" (really only used with GROUP BY calculations).
5261        * * <tt>'group'</tt>: An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
5262        * * <tt>'select'</tt>: By default, this is * as in SELECT * FROM, but can be changed if you for example want to do a join.
5263        * * <tt>'distinct'</tt>: Set this to true to make this a distinct calculation, such as SELECT COUNT(DISTINCT posts.id) ...
5264        *
5265        * Examples:
5266        *   $Person->calculate('count', 'all'); // The same as $Person->count();
5267        *   $Person->average('age'); // SELECT AVG(age) FROM people...
5268        *   $Person->minimum('age', array('conditions' => array('last_name != ?', 'Drake'))); // Selects the minimum age for everyone with a last name other than 'Drake'
5269        *   $Person->minimum('age', array('having' => 'min(age) > 17', 'group' => 'last'_name)); // Selects the minimum age for any family without any minors
5270        */
5271      function calculate($operation, $column_name, $options = array())
5272      {
5273          $this->_validateCalculationOptions($options);
5274          $column_name = empty($options['select']) ? $column_name : $options['select'];
5275          $column_name = $column_name == 'all' ? '*' : $column_name;
5276          $column      = $this->_getColumnFor($column_name);
5277          if (!empty($options['group'])){
5278              return $this->_executeGroupedCalculation($operation, $column_name, $column, $options);
5279          }else{
5280              return $this->_executeSimpleCalculation($operation, $column_name, $column, $options);
5281          }
5282  
5283          return 0;
5284      }
5285  
5286      /**
5287      * @access private
5288      */
5289      function _constructCountOptionsFromLegacyArgs($args)
5290      {
5291          $options = array();
5292          $column_name = 'all';
5293  
5294          /*
5295          We need to handle
5296          count()
5297          count(options=array())
5298          count($column_name='all', $options=array())
5299          count($conditions=null, $joins=null)
5300          */
5301          if(count($args) > 2){
5302              trigger_error(Ak::t("Unexpected parameters passed to count(\$options=array())", E_USER_ERROR));
5303          }elseif(count($args) > 0){
5304              if(!empty($args[0]) && is_array($args[0])){
5305                  $options = $args[0];
5306              }elseif(!empty($args[1]) && is_array($args[1])){
5307                  $column_name = array_shift($args);
5308                  $options = array_shift($args);
5309              }else{
5310                  $options = array('conditions' => $args[0]);
5311                  if(!empty($args[1])){
5312                      $options = array_merge($options, array('joins' => $args[1]));
5313                  }
5314              }
5315          }
5316          return array($column_name, $options);
5317      }
5318  
5319  
5320      /**
5321      * @access private
5322      */
5323      function _constructCalculationSql($operation, $column_name, $options)
5324      {
5325          $operation = strtolower($operation);
5326          $aggregate_alias = $this->_getColumnAliasFor($operation, $column_name);
5327          $use_workaround = $operation == 'count' && !empty($options['distinct']) && $this->_getDatabaseType() == 'sqlite';
5328  
5329          $sql = $use_workaround ?
5330          "SELECT COUNT(*) AS $aggregate_alias" : // A (slower) workaround if we're using a backend, like sqlite, that doesn't support COUNT DISTINCT.
5331          "SELECT $operation(".(empty($options['distinct'])?'':'DISTINCT ')."$column_name) AS $aggregate_alias";
5332  
5333  
5334          $sql .= empty($options['group']) ? '' : ", {$options['group_field']} AS {$options['group_alias']}";
5335          $sql .= $use_workaround ? " FROM (SELECT DISTINCT {$column_name}" : '';
5336          $sql .=  " FROM ".$this->getTableName()." ";
5337  
5338          $sql .=  empty($options['joins']) ? '' : " {$options['joins']} ";
5339  
5340          empty($options['conditions']) ? null : $this->addConditions($sql, $options['conditions']);
5341  
5342          if (!empty($options['group'])){
5343              $sql .=  " GROUP BY {$options['group_field']} ";
5344              $sql .= empty($options['having']) ? '' : " HAVING {$options['having']} ";
5345          }
5346  
5347          $sql .= empty($options['order']) ? '' : " ORDER BY {$options['order']} ";
5348          $this->_db->addLimitAndOffset($sql, $options);
5349          $sql .= $use_workaround ? ')' : '';
5350          return $sql;
5351      }
5352  
5353  
5354      /**
5355      * @access private
5356      */
5357      function _executeSimpleCalculation($operation, $column_name, $column, $options)
5358      {
5359          $value = $this->_db->selectValue($this->_constructCalculationSql($operation, $column_name, $options));
5360          return $this->_typeCastCalculatedValue($value, $column, $operation);
5361      }
5362  
5363      /**
5364      * @access private
5365      */
5366      function _executeGroupedCalculation($operation, $column_name, $column, $options)
5367      {
5368          $group_field = $options['group'];
5369          $group_alias = $this->_getColumnAliasFor($group_field);
5370          $group_column = $this->_getColumnFor($group_field);
5371          $options = array_merge(array('group_field' => $group_field, 'group_alias' => $group_alias),$options);
5372          $sql = $this->_constructCalculationSql($operation, $column_name, $options);
5373          $calculated_data = $this->_db->select($sql);
5374          $aggregate_alias = $this->_getColumnAliasFor($operation, $column_name);
5375  
5376          $all = array();
5377          foreach ($calculated_data as $row){
5378              $key = $this->_typeCastCalculatedValue($row[$group_alias], $group_column);
5379              $all[$key] = $this->_typeCastCalculatedValue($row[$aggregate_alias], $column, $operation);
5380          }
5381          return $all;
5382      }
5383  
5384      /**
5385      * @access private
5386      */
5387      function _validateCalculationOptions($options = array())
5388      {
5389          $invalid_options = array_diff(array_keys($options),$this->_calculation_options);
5390          if(!empty($invalid_options)){
5391              trigger_error(Ak::t('%options are not valid calculation options.', array('%options'=>join(', ',$invalid_options))), E_USER_ERROR);
5392          }
5393      }
5394  
5395      /**
5396      * Converts a given key to the value that the database adapter returns as
5397      * as a usable column name.
5398      *   users.id #=> users_id
5399      *   sum(id) #=> sum_id
5400      *   count(distinct users.id) #=> count_distinct_users_id
5401      *   count(*) #=> count_all
5402      *
5403      * @access private
5404      */
5405      function _getColumnAliasFor()
5406      {
5407          $args = func_get_args();
5408          $keys = strtolower(join(' ',(!empty($args) ? (is_array($args[0]) ? $args[0] : $args) : array())));
5409          return preg_replace(array('/\*/','/\W+/','/^ +/','/ +$/','/ +/'),array('all',' ','','','_'), $keys);
5410      }
5411  
5412      /**
5413      * @access private
5414      */
5415      function _getColumnFor($field)
5416      {
5417          $field_name = ltrim(substr($field,strpos($field,'.')),'.');
5418          if(in_array($field_name,$this->getColumnNames())){
5419              return $field_name;
5420          }
5421          return $field;
5422      }
5423  
5424      /**
5425      * @access private
5426      */
5427      function _typeCastCalculatedValue($value, $column, $operation = null)
5428      {
5429          $operation = strtolower($operation);
5430          if($operation == 'count'){
5431              return intval($value);
5432          }elseif ($operation == 'avg'){
5433              return floatval($value);
5434          }else{
5435              return empty($column) ? $value : AkActiveRecord::castAttributeFromDatabase($column, $value);
5436          }
5437      }
5438  
5439      /*/Calculations*/
5440  
5441      function hasBeenModified()
5442      {
5443          return Ak::objectHasBeenModified($this);
5444      }
5445  
5446      /**
5447      * Just freeze the attributes hash, such that associations are still accessible even on destroyed records.
5448      * 
5449      * @todo implement freeze correctly for its intended use
5450      */
5451      function freeze()
5452      {
5453          return $this->_freeze = true;
5454      }
5455  
5456      function isFrozen()
5457      {
5458          return !empty($this->_freeze);
5459      }
5460  
5461      /**
5462      * Alias for getModelName()
5463      */
5464      function getType()
5465      {
5466          return $this->getModelName();
5467      }
5468  
5469      function &objectCache()
5470      {
5471          static $cache;
5472          $false = false;
5473          $args =& func_get_args();
5474          if(count($args) == 2){
5475              if(!isset($cache[$args[0]])){
5476                  $cache[$args[0]] =& $args[1];
5477              }
5478          }elseif(!isset($cache[$args[0]])){
5479              return $false;
5480          }
5481          return $cache[$args[0]];
5482      }
5483  
5484  
5485      /**
5486                          Connection adapters
5487      ====================================================================
5488      Right now Akelos uses phpAdodb for bd abstraction. This are functionalities not
5489      provided in phpAdodb and that will move to a separated driver for each db 
5490      engine in a future
5491      */
5492      function _extractValueFromDefault($default)
5493      {
5494          if($this->_getDatabaseType() == 'postgre'){
5495              if(preg_match("/^'(.*)'::/", $default, $match)){
5496                  return $match[1];
5497              }
5498              // a postgre HACK; we dont know the column-type here
5499              if ($default=='true') {
5500                  return true;
5501              }
5502              if ($default=='false') {
5503                  return false;
5504              }
5505          }
5506          return $default;
5507      }
5508  
5509  
5510  }
5511  
5512  
5513  ?>


Generated: Mon Oct 27 12:43:49 2008 Cross-referenced by PHPXref 0.6