Source for file AkActiveRecord.php

Documentation is available at AkActiveRecord.php

  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.  
  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. defined('AK_ACTIVE_RECORD_ENABLE_PERSISTENCE'? null : define('AK_ACTIVE_RECORD_ENABLE_PERSISTENCE'AK_ENVIRONMENT != 'testing');
  35. defined('AK_ACTIVE_RECORD_CACHE_DATABASE_SCHEMA'? null : define('AK_ACTIVE_RECORD_CACHE_DATABASE_SCHEMA'AK_ACTIVE_RECORD_ENABLE_PERSISTENCE && AK_ENVIRONMENT != 'development');
  36. defined('AK_ACTIVE_RECORD_CACHE_DATABASE_SCHEMA_LIFE'? null : define('AK_ACTIVE_RECORD_CACHE_DATABASE_SCHEMA_LIFE'300);
  37. defined('AK_ACTIVE_RECORD_VALIDATE_TABLE_NAMES'? null : define('AK_ACTIVE_RECORD_VALIDATE_TABLE_NAMES'true);
  38. defined('AK_ACTIVE_RECORD_SKIP_SETTING_ACTIVE_RECORD_DEFAULTS'? null : define('AK_ACTIVE_RECORD_SKIP_SETTING_ACTIVE_RECORD_DEFAULTS'false);
  39. defined('AK_NOT_EMPTY_REGULAR_EXPRESSION'? null : define('AK_NOT_EMPTY_REGULAR_EXPRESSION','/.+/');
  40. 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");
  41. defined('AK_NUMBER_REGULAR_EXPRESSION'? null : define('AK_NUMBER_REGULAR_EXPRESSION',"/^[0-9]+$/");
  42. defined('AK_PHONE_REGULAR_EXPRESSION'? null : define('AK_PHONE_REGULAR_EXPRESSION',"/^([\+]?[(]?[\+]?[ ]?[0-9]{2,3}[)]?[ ]?)?[0-9 ()\-]{4,25}$/");
  43. 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}$/");
  44. 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])$/");
  45. defined('AK_POST_CODE_REGULAR_EXPRESSION'? null : define('AK_POST_CODE_REGULAR_EXPRESSION',"/^[0-9A-Za-z  -]{2,9}$/");
  46. /**#@-*/
  47.  
  48. // Forces loading database schema on every call
  49. if(AK_DEV_MODE && isset($_SESSION['__activeRecordColumnsSettingsCache'])){
  50.     unset($_SESSION['__activeRecordColumnsSettingsCache']);
  51. }
  52.  
  53. ak_compat('array_combine');
  54.  
  55. /**
  56. * Active Record objects doesn't specify their attributes directly, but rather infer them from the table definition with
  57. * which they're linked. Adding, removing, and changing attributes and their type is done directly in the database. Any change
  58. * is instantly reflected in the Active Record objects. The mapping that binds a given Active Record class to a certain
  59. * database table will happen automatically in most common cases, but can be overwritten for the uncommon ones. 
  60. * See the mapping rules in table_name and the full example in README.txt for more insight.
  61. * == Creation ==
  62. * 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
  63. * you're receiving the data from somewhere else, like a HTTP request. It works like this:
  64. * <code>
  65. *   $user = new User(array('name' => 'David', 'occupation' => 'Code Artist'));
  66. *   echo $user->name; // Will print "David"
  67. * </code>
  68. *
  69. * You can also use a parameter list initialization.:
  70. *   $user = new User('name->', 'David', 'occupation->', 'Code Artist');
  71. * And of course you can just create a bare object and specify the attributes after the fact:
  72. * <code>
  73. *   $user = new User();
  74. *   $user->name = 'David';
  75. *   $user->occupation = 'Code Artist';
  76. * </code>
  77. *
  78. * == Conditions ==
  79. * Conditions can either be specified as a string or an array representing the WHERE-part of an SQL statement.
  80. * The array form is to be used when the condition input is tainted and requires sanitization. The string form can
  81. * be used for statements that doesn't involve tainted data. Examples:
  82. * <code>
  83. *   class User extends ActiveRecord
  84. *   {
  85. *     function authenticateUnsafely($user_name, $password)
  86. *     {
  87. *          return findFirst("user_name = '$user_name' AND password = '$password'");
  88. *     }
  89. *     
  90. *     function authenticateSafely($user_name, $password)
  91. *     {
  92. *          return findFirst("user_name = ? AND password = ?", $user_name, $password);
  93. *     }
  94. *    }
  95. * </code>
  96. *
  97. * The <tt>authenticateUnsafely</tt> method inserts the parameters directly into the query and is thus susceptible to SQL-injection
  98. * attacks if the <tt>$user_name</tt> and <tt>$password</tt> parameters come directly from a HTTP request. The <tt>authenticateSafely</tt> method, 
  99. * 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
  100. * an attacker can't escape the query and fake the login (or worse).
  101. *
  102. * When using multiple parameters in the conditions, it can easily become hard to read exactly what the fourth or fifth
  103. * question mark is supposed to represent. In those cases, you can resort to named bind variables instead. That's done by replacing 
  104. * the question marks with symbols and supplying a hash with values for the matching symbol keys:
  105. * <code>
  106. *   $Company->findFirst(
  107. *              "id = :id AND name = :name AND division = :division AND created_at > :accounting_date", 
  108. *               array(':id' => 3, ':name' => "37signals", ':division' => "First", ':accounting_date' => '2005-01-01')
  109. *             );
  110. * </code>
  111. *
  112. * == Accessing attributes before they have been type casted ==
  113. * Some times you want to be able to read the raw attribute data without having the column-determined type cast run its course first.
  114. * That can be done by using the <attribute>_before_type_cast accessors that all attributes have. For example, if your Account model
  115. * has a balance attribute, you can call $Account->balance_before_type_cast or $Account->id_before_type_cast. 
  116. * This is especially useful in validation situations where the user might supply a string for an integer field and you want to display
  117. * the original string back in an error message. Accessing the attribute normally would type cast the string to 0, which isn't what you
  118. * want.
  119. * == Saving arrays, hashes, and other non-mappable objects in text columns ==
  120. * Active Record can serialize any object in text columns. To do so, you must specify this with by setting the attribute serialize with 
  121. * a comma separated list of columns or an array. 
  122. * This makes it possible to store arrays, hashes, and other non-mappeable objects without doing any additional work. Example:
  123. *
  124. * <code> 
  125. *   class User extends ActiveRecord
  126. *   {
  127. *      var $serialize = 'preferences';
  128. *   }
  129. *
  130. *   $User = new User(array('preferences'=>array("background" => "black", "display" => 'large')));
  131. *   $User->find($user_id);
  132. *   $User->preferences // array("background" => "black", "display" => 'large')
  133. * </code>
  134. * == Single table inheritance ==
  135. * Active Record allows inheritance by storing the name of the class in a column that by default is called "type" (can be changed 
  136. * by overwriting <tt>AkActiveRecord->_inheritanceColumn</tt>). This means that an inheritance looking like this:
  137. * <code>
  138. *   class Company extends ActiveRecord{}
  139. *   class Firm extends Company{}
  140. *   class Client extends Company{}
  141. *   class PriorityClient extends Client{}
  142. * </code>
  143. *
  144. * When you do $Firm->create('name =>', "akelos"), this record will be saved in the companies table with type = "Firm". You can then
  145. * fetch this row again using $Company->find('first', "name = '37signals'") and it will return a Firm object.
  146. * 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
  147. * like normal subclasses with no special magic for differentiating between them or reloading the right type with find.
  148. * Note, all the attributes for all the cases are kept in the same table. Read more:
  149. * http://www.martinfowler.com/eaaCatalog/singleTableInheritance.html
  150. * == Connection to multiple databases in different models ==
  151. * Connections are usually created through AkActiveRecord->establishConnection and retrieved by AkActiveRecord->connection.
  152. * All classes inheriting from AkActiveRecord will use this connection. But you can also set a class-specific connection. 
  153. * For example, if $Course is a AkActiveRecord, but resides in a different database you can just say $Course->establishConnection
  154. * and $Course and all its subclasses will use this connection instead.
  155. * Active Records will automatically record creation and/or update timestamps of database objects
  156. * if fields of the names created_at/created_on or updated_at/updated_on are present.
  157. * Date only: created_on, updated_on
  158. * Date and time: created_at, updated_at
  159. *
  160. * This behavior can be turned off by setting <tt>$this->_recordTimestamps = false</tt>.
  161. */
  162. {
  163.     /**#@+
  164.     * @access private
  165.     */
  166.     //var $disableAutomatedAssociationLoading = true;
  167.     var $_tableName;
  168.     var $_db;
  169.     var $_newRecord;
  170.     var $_freeze;
  171.     var $_dataDictionary;
  172.     var $_primaryKey;
  173.     var $_inheritanceColumn;
  174.  
  175.     var $_associations;
  176.  
  177.     var $_internationalize;
  178.  
  179.     var $_errors = array();
  180.  
  181.     var $_attributes = array();
  182.  
  183.     var $_protectedAttributes = array();
  184.     var $_accessibleAttributes = array();
  185.  
  186.     var $_recordTimestamps = true;
  187.  
  188.     // Column description
  189.     var $_columnNames = array();
  190.     // Array of column objects for the table associated with this class.
  191.     var $_columns = array();
  192.     // Columns that can be edited/viewed
  193.     var $_contentColumns = array();
  194.     // Methods that will be dinamically loaded for the model (EXPERIMENTAL) This pretends to generate something similar to Ruby on Rails finders.
  195.     // If you set array('findOneByUsernameAndPassword', 'findByCompany', 'findAllByExipringDate')
  196.     // You'll get $User->findOneByUsernameAndPassword('admin', 'pass');
  197.     var $_dynamicMethods = false;
  198.     var $_combinedAttributes = array();
  199.  
  200.     var $_BlobQueryStack = null;
  201.  
  202.     var $_automated_max_length_validator = true;
  203.     var $_automated_validators_enabled = true;
  204.     var $_automated_not_null_validator = false;
  205.     var $_set_default_attribute_values_automatically = true;
  206.  
  207.     // This is needed for enabling support for static active record instantation under php
  208.     var $_activeRecordHasBeenInstantiated = true;
  209.  
  210.     var $__ActsLikeAttributes = array();
  211.  
  212.     /**
  213.     * Holds a hash with all the default error messages, such that they can be replaced by your own copy or localizations.
  214.     */
  215.     var $_defaultErrorMessages = array(
  216.     'inclusion' =>  "is not included in the list",
  217.     'exclusion' => "is reserved",
  218.     'invalid' => "is invalid",
  219.     'confirmation' => "doesn't match confirmation",
  220.     'accepted' => "must be accepted",
  221.     'empty' => "can't be empty",
  222.     'blank' => "can't be blank",
  223.     'too_long' => "is too long (max is %d characters)",
  224.     'too_short' => "is too short (min is %d characters)",
  225.     'wrong_length' => "is the wrong length (should be %d characters)",
  226.     'taken' => "has already been taken",
  227.     'not_a_number' => "is not a number"
  228.     );
  229.  
  230.     var $__activeRecordObject = true;
  231.  
  232.     /**#@-*/
  233.  
  234.     function __construct()
  235.     {
  236.         $attributes = (array)func_get_args();
  237.         return $this->init($attributes);
  238.     }
  239.  
  240.     function init($attributes = array())
  241.     {
  242.         AK_LOG_EVENTS ? ($this->Logger =Ak::getLogger()) : null;
  243.         $this->_internationalize is_null($this->_internationalize&& AK_ACTIVE_RECORD_INTERNATIONALIZE_MODELS_BY_DEFAULT ? count($this->getAvailableLocales()) > 1 : $this->_internationalize;
  244.  
  245.         @$this->_instantiateDefaultObserver();
  246.  
  247.         $this->setConnection();
  248.  
  249.         if(!empty($this->table_name)){
  250.             $this->setTableName($this->table_name);
  251.         }
  252.  
  253.         $this->_loadActAsBehaviours();
  254.  
  255.         if(!empty($this->combined_attributes)){
  256.             foreach ($this->combined_attributes as $combined_attribute){
  257.                 $this->addCombinedAttributeConfiguration($combined_attribute);
  258.             }
  259.         }
  260.  
  261.         if(isset($attributes[0]&& is_array($attributes[0]&& count($attributes=== 1){
  262.             $attributes $attributes[0];
  263.             $this->_newRecord = true;
  264.         }
  265.  
  266.         // new AkActiveRecord(23); //Returns object with primary key 23
  267.         if(isset($attributes[0]&& count($attributes=== 1 && $attributes[0> 0){
  268.             $record $this->find($attributes[0]);
  269.             if(!$record){
  270.                 return false;
  271.             }else {
  272.                 $this->setAttributes($record->getAttributes()true);
  273.             }
  274.             // This option is only used internally for loading found objects
  275.         }elseif(isset($attributes[0]&& isset($attributes[1]&& $attributes[0== 'attributes' && is_array($attributes[1])){
  276.             foreach(array_keys($attributes[1]as $k){
  277.                 $attributes[1][$k$this->castAttributeFromDatabase($k$attributes[1][$k]);
  278.             }
  279.  
  280.             $avoid_loading_associations = isset($attributes[1]['load_associations']? false : !empty($this->disableAutomatedAssociationLoading);
  281.             $this->setAttributes($attributes[1]true);
  282.         }else{
  283.             $this->newRecord($attributes);
  284.         }
  285.  
  286.         $this->_buildFinders();
  287.         empty($avoid_loading_associations$this->loadAssociations(: null;
  288.     }
  289.  
  290.     function __destruct()
  291.     {
  292.  
  293.     }
  294.  
  295.     /**
  296.     * New objects can be instantiated as either empty (pass no construction parameter) or pre-set with attributes but not yet saved
  297.     * (pass an array with key names matching the associated table column names). 
  298.     * In both instances, valid attribute keys are determined by the column names of the associated table; hence you can't 
  299.     * have attributes that aren't part of the table columns.
  300.     */
  301.     function newRecord($attributes)
  302.     {
  303.         $this->_newRecord = true;
  304.  
  305.         if(AK_ACTIVE_RECORD_SKIP_SETTING_ACTIVE_RECORD_DEFAULTS && empty($attributes)){
  306.             return;
  307.         }
  308.  
  309.         if(isset($attributes&& !is_array($attributes)){
  310.             $attributes func_get_args();
  311.         }
  312.         $this->setAttributes($this->attributesFromColumnDefinition(),true);
  313.         $this->setAttributes($attributes);
  314.     }
  315.  
  316.  
  317.     /**
  318.     * Returns a clone of the record that hasn't been assigned an id yet and is treated as a new record.
  319.     */
  320.     function cloneRecord()
  321.     {
  322.         $model_name $this->getModelName();
  323.         $attributes $this->getAttributesBeforeTypeCast();
  324.         if(isset($attributes[$this->getPrimaryKey()])){
  325.             unset($attributes[$this->getPrimaryKey()]);
  326.         }
  327.         return new $model_name($attributes);
  328.     }
  329.  
  330.  
  331.     /**
  332.     * Returns true if this object hasn't been saved yet that is, a record for the object doesn't exist yet.
  333.     */
  334.     function isNewRecord()
  335.     {
  336.         if(!isset($this->_newRecord&& !isset($this->{$this->getPrimaryKey()})){
  337.             $this->_newRecord = true;
  338.         }
  339.         return $this->_newRecord;
  340.     }
  341.  
  342.  
  343.  
  344.     /**
  345.     * Reloads the attributes of this object from the database.
  346.     */   
  347.     function reload()
  348.     {
  349.         /**
  350.         * @todo clear cache
  351.         */        
  352.         if($object $this->find($this->getId())){
  353.             $this->setAttributes($object->getAttributes()true);
  354.             return true;
  355.         }else {
  356.             return false;
  357.         }
  358.     }
  359.  
  360.  
  361.  
  362.     /**
  363.                          Creating records
  364.     ====================================================================
  365.     */
  366.     /**
  367.     * Creates an object, instantly saves it as a record (if the validation permits it), and returns it. 
  368.     * If the save fail under validations, the unsaved object is still returned.
  369.     */
  370.     function &create($attributes = null)
  371.     {
  372.         if(!isset($this->_activeRecordHasBeenInstantiated)){
  373.             return Ak::handleStaticCall();
  374.         }
  375.  
  376.         if(func_num_args(> 1){
  377.             $attributes func_get_args();
  378.         }
  379.         $model $this->getModelName();
  380.  
  381.         $object =new $model();
  382.         $object->setAttributes($attributes);
  383.         $object->save();
  384.         return $object;
  385.     }
  386.  
  387.     function createOrUpdate($validate = true)
  388.     {
  389.         if($validate && !$this->isValid()){
  390.             $this->transactionFail();
  391.             return false;
  392.         }
  393.         return $this->isNewRecord($this->_create($this->_update();
  394.     }
  395.  
  396.     function &findOrCreateBy()
  397.     {
  398.         $args func_get_args();
  399.         $Item =Ak::call_user_func_array(array(&$this,'findFirstBy')$args);
  400.         if(!$Item){
  401.             $attributes = array();
  402.  
  403.             list($sql$columns$this->_getFindBySqlAndColumns(array_shift($args)$args);
  404.  
  405.             if(!empty($columns)){
  406.                 foreach ($columns as $column){
  407.                     $attributes[$columnarray_shift($args);
  408.                 }
  409.             }
  410.             $Item =$this->create($attributes);
  411.             $Item->has_been_created = true;
  412.         }else{
  413.             $Item->has_been_created = false;
  414.         }
  415.         $Item->has_been_found = !$Item->has_been_created;
  416.         return $Item;
  417.     }
  418.  
  419.     /**
  420.     * Creates a new record with values matching those of the instance attributes.
  421.     * Must be called as a result of a call to createOrUpdate.
  422.     *
  423.     * @access private
  424.     */
  425.     function _create()
  426.     {
  427.         if (!$this->beforeCreate(|| !$this->notifyObservers('beforeCreate')){
  428.             return $this->transactionFail();
  429.         }
  430.  
  431.         $this->_setRecordTimestamps();
  432.  
  433.         // deprecated section
  434.         if($this->isLockingEnabled(&& is_null($this->get('lock_version'))){
  435.             Ak::deprecateWarning(array("Column %lock_version_column should have a default setting. Assumed '1'.",'%lock_version_column'=>'lock_version'));
  436.             $this->setAttribute('lock_version',1);
  437.         // end
  438.  
  439.         $attributes $this->getColumnsForAttributes($this->getAttributes());
  440.         foreach ($attributes as $column=>$value){
  441.             $attributes[$column$this->castAttributeForDatabase($column,$value);
  442.         }
  443.  
  444.         $pk $this->getPrimaryKey();
  445.         $table $this->getTableName();
  446.  
  447.         $id $this->_db->incrementsPrimaryKeyAutomatically(? null : $this->_db->getNextSequenceValueFor($table);
  448.         $attributes[$pk$id;
  449.  
  450.         $attributes array_diff($attributesarray(''));
  451.         
  452.  
  453.         $sql 'INSERT INTO '.$table.' '.
  454.         '('.join(', ',array_keys($attributes)).') '.
  455.         'VALUES ('.join(',',array_values($attributes)).')';
  456.  
  457.         $inserted_id $this->_db->insert($sql$id$pk$table'Create '.$this->getModelName());
  458.         if ($this->transactionHasFailed()){
  459.             return false;
  460.         }
  461.         $this->setId($inserted_id);
  462.  
  463.         $this->_newRecord = false;
  464.         
  465.         if (!$this->afterCreate(|| !$this->notifyObservers('afterCreate')){
  466.             return $this->transactionFail();
  467.         }
  468.         
  469.         return true;
  470.     }
  471.  
  472.     function _setRecordTimestamps()
  473.     {
  474.         if (!$this->_recordTimestamps){
  475.             return;
  476.         }
  477.         if ($this->_newRecord){
  478.             if ($this->hasColumn('created_at')){
  479.                 $this->setAttribute('created_at'Ak::getDate());
  480.             }
  481.             if ($this->hasColumn('created_on')){
  482.                 $this->setAttribute('created_on'Ak::getDate(null'Y-m-d'));
  483.             }
  484.         }else{
  485.             if ($this->hasColumn('updated_at')){
  486.                 $this->setAttribute('updated_at'Ak::getDate());
  487.             }
  488.             if ($this->hasColumn('updated_on')){
  489.                 $this->setAttribute('updated_on'Ak::getDate(null'Y-m-d'));
  490.             }
  491.         }
  492.  
  493.         if($this->_newRecord && isset($this->expires_on)){
  494.             if(isset($this->expires_at&& $this->hasColumn('expires_at')){
  495.                 $this->setAttribute('expires_at',Ak::getDate(strtotime($this->expires_at(defined('AK_TIME_DIFFERENCE'AK_TIME_DIFFERENCE*60 : 0)));
  496.             }elseif(isset($this->expires_on&& $this->hasColumn('expires_on')){
  497.                 $this->setAttribute('expires_on',Ak::getDate(strtotime($this->expires_on(defined('AK_TIME_DIFFERENCE'AK_TIME_DIFFERENCE*60 : 0)'Y-m-d'));
  498.             }
  499.         }
  500.  
  501.     }
  502.  
  503.     /*/Creating records*/
  504.  
  505.  
  506.     /**
  507.                          Saving records
  508.     ====================================================================
  509.     */
  510.     /**
  511.     * - No record exists: Creates a new record with values matching those of the object attributes.
  512.     * - A record does exist: Updates the record with values matching those of the object attributes.
  513.     */
  514.     function save($validate = true)
  515.     {
  516.         if($this->isFrozen()){
  517.             return false;
  518.         }
  519.         $result = false;
  520.         $this->transactionStart();
  521.         if($this->beforeSave(&& $this->notifyObservers('beforeSave')){
  522.             $result $this->createOrUpdate($validate);
  523.             if(!$this->transactionHasFailed()){
  524.                 if(!$this->afterSave()){
  525.                     $this->transactionFail();
  526.                 }else{
  527.                     if(!$this->notifyObservers('afterSave')){
  528.                         $this->transactionFail();
  529.                     }
  530.                 }
  531.             }
  532.         }else{
  533.             $this->transactionFail();
  534.         }
  535.  
  536.         $result $this->transactionHasFailed(? false : $result;
  537.         $this->transactionComplete();
  538.  
  539.         return $result;
  540.     }
  541.  
  542.     /*/Saving records*/
  543.  
  544.     /**
  545.                             Counting Records
  546.     ====================================================================
  547.     See also: Counting Attributes.
  548.     */
  549.  
  550.     /**
  551.       * Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part.
  552.       * 
  553.       *   $Product->countBySql("SELECT COUNT(*) FROM sales s, customers c WHERE s.customer_id = c.id");
  554.       */
  555.     function countBySql($sql)
  556.     {
  557.         if(!isset($this->_activeRecordHasBeenInstantiated)){
  558.             return Ak::handleStaticCall();
  559.         }
  560.         if(!stristr($sql'COUNT'&& stristr($sql' FROM ')){
  561.             $sql 'SELECT COUNT(*) '.substr($sql,strpos(str_replace(' from ',' FROM '$sql),' FROM '));
  562.         }
  563.         if(!$this->isConnected()){
  564.             $this->setConnection();
  565.         }
  566.  
  567.         return (integer)$this->_db->selectValue($sql);
  568.     }
  569.     /*/Counting Records*/
  570.  
  571.     /**
  572.                           Updating records
  573.     ====================================================================
  574.     See also: Callbacks.
  575.     */
  576.  
  577.     /**
  578.     * Finds the record from the passed id, instantly saves it with the passed attributes (if the validation permits it), 
  579.     * and returns it. If the save fail under validations, the unsaved object is still returned.
  580.     */
  581.     function update($id$attributes)
  582.     {
  583.         if(!isset($this->_activeRecordHasBeenInstantiated)){
  584.             return Ak::handleStaticCall();
  585.         }
  586.         if(is_array($id)){
  587.             $results = array();
  588.             foreach ($id as $idx=>$single_id){
  589.                 $results[$this->update($single_idisset($attributes[$idx]$attributes[$idx$attributes);
  590.             }
  591.             return $results;
  592.         }else{
  593.             $object =$this->find($id);
  594.             $object->updateAttributes($attributes);
  595.             return $object;
  596.         }
  597.     }
  598.  
  599.     /**
  600.     * Updates a single attribute and saves the record. This is especially useful for boolean flags on existing records. 
  601.     */
  602.     function updateAttribute($name$value$should_validate=true)
  603.     {
  604.         $this->setAttribute($name$value);
  605.         return $this->save($should_validate);
  606.     }
  607.  
  608.  
  609.     /**
  610.     * Updates all the attributes in from the passed array and saves the record. If the object is 
  611.     * invalid, the saving will fail and false will be returned.
  612.     */
  613.     function updateAttributes($attributes$object = null)
  614.     {
  615.         isset($object$object->setAttributes($attributes$this->setAttributes($attributes);
  616.  
  617.         return isset($object$object->save($this->save();
  618.     }
  619.  
  620.     /**
  621.     * Updates all records with the SET-part of an SQL update statement in updates and returns an 
  622.     * integer with the number of rows updates. A subset of the records can be selected by specifying conditions. Example:
  623.     * <code>$Billing->updateAll("category = 'authorized', approved = 1", "author = 'David'");</code>
  624.     * 
  625.     * Important note: Conditions are not sanitized yet so beware of accepting 
  626.     * variable conditions when using this function
  627.     */
  628.     function updateAll($updates$conditions = null)
  629.     {
  630.         if(!isset($this->_activeRecordHasBeenInstantiated)){
  631.             return Ak::handleStaticCall();
  632.         }
  633.         /**
  634.         * @todo sanitize sql conditions
  635.         */
  636.         $sql 'UPDATE '.$this->getTableName().' SET '.$updates;
  637.         $this->addConditions($sql$conditions);
  638.         return $this->_db->update($sql$this->getModelName().' Update All');
  639.     }
  640.  
  641.  
  642.     /**
  643.     * Updates the associated record with values matching those of the instance attributes.
  644.     * Must be called as a result of a call to createOrUpdate.
  645.     *
  646.     * @access private
  647.     */
  648.     function _update()
  649.     {
  650.         if(!$this->beforeUpdate(|| !$this->notifyObservers('beforeUpdate')){
  651.             return $this->transactionFail();
  652.         }
  653.  
  654.         $this->_setRecordTimestamps();
  655.  
  656.         $lock_check_sql '';
  657.         if ($this->isLockingEnabled()){
  658.             $previous_value $this->lock_version;
  659.             $this->setAttribute('lock_version'$previous_value + 1);
  660.             $lock_check_sql ' AND lock_version = '.$previous_value;
  661.         }
  662.  
  663.         $quoted_attributes $this->getAvailableAttributesQuoted();
  664.         $sql 'UPDATE '.$this->getTableName().' '.
  665.         'SET '.join(', '$quoted_attributes.' '.
  666.         'WHERE '.$this->getPrimaryKey().'='.$this->quotedId().$lock_check_sql;
  667.  
  668.         $affected_rows $this->_db->update($sql,'Updating '.$this->getModelName());
  669.         if($this->transactionHasFailed()){
  670.             return false;
  671.         }
  672.  
  673.         if ($this->isLockingEnabled(&& $affected_rows != 1){
  674.             $this->setAttribute('lock_version'$previous_value);
  675.             trigger_error(Ak::t('Attempted to update a stale object')E_USER_NOTICE);
  676.             return $this->transactionFail();
  677.         }
  678.  
  679.         if(!$this->afterUpdate(|| !$this->notifyObservers('afterUpdate')){
  680.             return $this->transactionFail();
  681.         }
  682.  
  683.         return true;
  684.     }
  685.  
  686.     /*/Updating records*/
  687.  
  688.  
  689.  
  690.     /**
  691.                           Deleting records
  692.     ====================================================================
  693.     See also: Callbacks.
  694.     */
  695.  
  696.     /**
  697.     * Deletes the record with the given id without instantiating an object first. If an array of 
  698.     * ids is provided, all of them are deleted.
  699.     */
  700.     function delete($id)
  701.     {
  702.         if(!isset($this->_activeRecordHasBeenInstantiated)){
  703.             return Ak::handleStaticCall();
  704.         }
  705.         $id func_num_args(> 1 ? func_get_args($id;
  706.         return $this->deleteAll($this->getPrimaryKey().' IN ('.(is_array($idjoin(', ',$id$id).')');
  707.     }
  708.  
  709.  
  710.     /**
  711.     * Deletes all the records that matches the condition without instantiating the objects first 
  712.     * (and hence not calling the destroy method). Example:
  713.     * 
  714.     * <code>$Post->destroyAll("person_id = 5 AND (category = 'Something' OR category = 'Else')");</code>
  715.     * 
  716.     * Important note: Conditions are not sanitized yet so beware of accepting 
  717.     * variable conditions when using this function
  718.     */
  719.     function deleteAll($conditions = null)
  720.     {
  721.         if(!isset($this->_activeRecordHasBeenInstantiated)){
  722.             return Ak::handleStaticCall();
  723.         }
  724.         /**
  725.         * @todo sanitize sql conditions
  726.         */
  727.         $sql 'DELETE FROM '.$this->getTableName();
  728.         $this->addConditions($sql,$conditions);
  729.         return $this->_db->delete($sql,$this->getModelName().' Delete All');
  730.     }
  731.  
  732.  
  733.     /**
  734.     * Destroys the record with the given id by instantiating the object and calling destroy 
  735.     * (all the callbacks are the triggered). If an array of ids is provided, all of them are destroyed.
  736.     * Deletes the record in the database and freezes this instance to reflect that no changes should be 
  737.     * made (since they can't be persisted).
  738.     */
  739.     function destroy($id = null)
  740.     {
  741.         if(!isset($this->_activeRecordHasBeenInstantiated)){
  742.             return Ak::handleStaticCall();
  743.         }
  744.  
  745.         $id func_num_args(> 1 ? func_get_args($id;
  746.  
  747.         if(isset($id)){
  748.             $this->transactionStart();
  749.             $id_arr is_array($id$id : array($id);
  750.             if($objects $this->find($id_arr)){
  751.                 $results count($objects);
  752.                 $no_problems = true;
  753.                 for ($i=0$results $i$i++){
  754.                     if(!$objects[$i]->destroy()){
  755.                         $no_problems = false;
  756.                     }
  757.                 }
  758.                 $this->transactionComplete();
  759.                 return $no_problems;
  760.             }else {
  761.                 $this->transactionComplete();
  762.                 return false;
  763.             }
  764.         }else{
  765.             if(!$this->isNewRecord()){
  766.                 $this->transactionStart();
  767.                 $return $this->_destroy(&& $this->freeze();
  768.                 $this->transactionComplete();
  769.                 return $return;
  770.             }
  771.         }
  772.     }
  773.  
  774.     function _destroy()
  775.     {
  776.         if(!$this->beforeDestroy(|| !$this->notifyObservers('beforeDestroy')){
  777.             return $this->transactionFail();
  778.         }
  779.  
  780.         $sql 'DELETE FROM '.$this->getTableName().' WHERE '.$this->getPrimaryKey().' = '.$this->_db->quote_string($this->getId());
  781.         if ($this->_db->delete($sql,$this->getModelName().' Destroy'!== 1){
  782.             return $this->transactionFail();
  783.         }
  784.  
  785.         if (!$this->afterDestroy(|| !$this->notifyObservers('afterDestroy')){
  786.             return $this->transactionFail();
  787.         }
  788.         return true;
  789.     }
  790.  
  791.     /**
  792.     * Destroys the objects for all the records that matches the condition by instantiating 
  793.     * each object and calling the destroy method. 
  794.     * 
  795.     * Example:
  796.     * 
  797.     *   $Person->destroyAll("last_login < '2004-04-04'");
  798.     */    
  799.     function destroyAll($conditions)
  800.     {
  801.         if($objects $this->find('all',array('conditions'=>$conditions))){
  802.             $results count($objects);
  803.             $no_problems = true;
  804.             for ($i=0$results $i$i++){
  805.                 if(!$objects[$i]->destroy()){
  806.                     $no_problems = false;
  807.                 }
  808.             }
  809.             return $no_problems;
  810.         }else {
  811.             return false;
  812.         }
  813.     }
  814.  
  815.     /*/Deleting records*/
  816.  
  817.  
  818.  
  819.  
  820.     /**
  821.                           Finding records
  822.     ====================================================================
  823.     */
  824.  
  825.     /**
  826.     * Returns true if the given id represents the primary key of a record in the database, false otherwise. Example:
  827.     * 
  828.     * $Person->exists(5);
  829.     */    
  830.     function exists($id)
  831.     {
  832.         return $this->find('first',array('conditions' => array($this->getPrimaryKey().' = '.$id))) !== false;
  833.     }
  834.  
  835.     /**
  836.      * Find operates with three different retrieval approaches:
  837.     * * Find by id: This can either be a specific id find(1), a list of ids find(1, 5, 6), 
  838.     *   or an array of ids find(array(5, 6, 10)). If no record can be found for all of the listed ids, 
  839.     *   then RecordNotFound will be raised.
  840.     * * Find first: This will return the first record matched by the options used. These options 
  841.     *   can either be specific conditions or merely an order. 
  842.     *   If no record can matched, false is returned.
  843.     * * Find all: This will return all the records matched by the options used. If no records are found, an empty array is returned.
  844.     * 
  845.     * All approaches accepts an $option array as their last parameter. The options are:
  846.     * 
  847.     * 'conditions' => An SQL fragment like "administrator = 1" or array("user_name = ?" => $username). See conditions in the intro.
  848.     * 'order' => An SQL fragment like "created_at DESC, name".
  849.     * 'limit' => An integer determining the limit on the number of rows that should be returned.
  850.     * 'offset' => An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows.
  851.     * 'joins' => An SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = $id". (Rarely needed).
  852.     * 'include' => Names associations that should be loaded alongside using LEFT OUTER JOINs. The symbols 
  853.     * named refer to already defined associations. See eager loading under Associations.
  854.     * 
  855.     * Examples for find by id:
  856.     * <code>
  857.     *   $Person->find(1);       // returns the object for ID = 1
  858.     *   $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
  859.     *   $Person->find(array(7, 17)); // returns an array for objects with IDs in (7, 17)
  860.     *   $Person->find(array(1));     // returns an array for objects the object with ID = 1
  861.     *   $Person->find(1, array('conditions' => "administrator = 1", 'order' => "created_on DESC"));
  862.     * </code>
  863.     *
  864.     * Examples for find first:
  865.     * <code> 
  866.     *   $Person->find('first'); // returns the first object fetched by SELECT * FROM people
  867.     *   $Person->find('first', array('conditions' => array("user_name = ':user_name'", ':user_name' => $user_name)));
  868.     *   $Person->find('first', array('order' => "created_on DESC", 'offset' => 5));
  869.     * </code>
  870.     * 
  871.     * Examples for find all:
  872.     * <code> 
  873.     *   $Person->find('all'); // returns an array of objects for all the rows fetched by SELECT * FROM people
  874.     *   $Person->find(); // Same as $Person->find('all');
  875.     *   $Person->find('all', array('conditions' => array("category IN (categories)", 'categories' => join(','$categories)), 'limit' => 50));
  876.     *   $Person->find('all', array('offset' => 10, 'limit' => 10));
  877.     *   $Person->find('all', array('include' => array('account', 'friends'));
  878.     * </code>
  879.     */
  880.     function &find()
  881.     {
  882.         if(!isset($this->_activeRecordHasBeenInstantiated)){
  883.             return Ak::handleStaticCall();
  884.         }
  885.  
  886.         $args func_get_args();
  887.  
  888.         $options $this->_extractOptionsFromArgs($args);
  889.         list($fetch,$options$this->_extractConditionsFromArgs($args,$options);
  890.  
  891.         $this->_sanitizeConditionsVariables($options);
  892.  
  893.         switch ($fetch{
  894.             case 'first':
  895.                 // HACK: php4 pass by ref
  896.                 $result =$this->_findInitial($options);
  897.                 return $result;
  898.                 break;
  899.  
  900.             case 'all':
  901.                 // HACK: php4 pass by ref
  902.                 $result =$this->_findEvery($options);
  903.                 return $result;
  904.                 break;
  905.  
  906.             default:
  907.                 // HACK: php4 pass by ref
  908.                 $result =$this->_findFromIds($args$options);
  909.                 return $result;
  910.                 break;
  911.         }
  912.         $result = false;
  913.         return $result;
  914.     }
  915.  
  916.     function &_findInitial($options)
  917.     {
  918.         // TODO: virtual_limit is a hack
  919.         // actually we fetch_all and return only the first row
  920.         $options array_merge($optionsarray((!empty($options['include']?'virtual_limit':'limit')=>1));
  921.         $result =$this->_findEvery($options);
  922.  
  923.         if(!empty($result&& is_array($result)){
  924.             $_result =$result[0];
  925.         }else{
  926.             $_result = false;
  927.             // if we return an empty array instead of false we need to change this->exists()!
  928.             //$_result = array();
  929.         }
  930.         return  $_result;
  931.  
  932.     }
  933.  
  934.     function &_findEvery($options)
  935.     {
  936.         $limit = isset($options['limit']$options['limit': null;
  937.         $offset = isset($options['offset']$options['offset': null;
  938.  
  939.         $sql $this->constructFinderSql($options);
  940.         if(!empty($options['bind']&& is_array($options['bind']&& strstr($sql,'?')){
  941.             $sql array_merge(array($sql),$options['bind']);
  942.         }
  943.  
  944.         if((!empty($options['include']&& $this->hasAssociations())){
  945.             $result =$this->findWithAssociations($options);
  946.         }else{
  947.             $result =$this->findBySql($sql);
  948.         }
  949.  
  950.         if(!empty($result&& is_array($result)){
  951.             $_result =$result;
  952.         }else{
  953.             $_result = false;
  954.         }
  955.         return  $_result;
  956.  
  957.     }
  958.  
  959.     function &_findFromIds($ids$options)
  960.     {
  961.         $expects_array is_array($ids[0]);
  962.         $ids array_unique($expects_array (isset($ids[1]array_merge($ids[0],$ids$ids[0]$ids);
  963.  
  964.         $num_ids count($ids);
  965.  
  966.         //at this point $options['conditions'] can't be an array
  967.         $conditions !empty($options['conditions']' AND '.$options['conditions''';
  968.  
  969.         switch ($num_ids){
  970.             case 0 :
  971.                 trigger_error($this->t('Couldn\'t find %object_name without an ID%conditions',array('%object_name'=>$this->getModelName(),'%conditions'=>$conditions))E_USER_ERROR);
  972.                 break;
  973.  
  974.             case 1 :
  975.                 $table_name !empty($options['include']&& $this->hasAssociations('__owner' $this->getTableName();
  976.                 $options['conditions'$table_name.'.'.$this->getPrimaryKey().' = '.$ids[0].$conditions;
  977.                 $result =$this->_findEvery($options);
  978.                 if (!$expects_array && $result !== false){
  979.                     return $result[0];
  980.                 }
  981.                 return  $result;
  982.                 break;
  983.  
  984.             default:
  985.                 $without_conditions = empty($options['conditions']? true : false;
  986.                 $ids_condition $this->getPrimaryKey().' IN ('.join(', ',$ids).')';
  987.                 $options['conditions'$ids_condition.$conditions;
  988.  
  989.                 $result =$this->_findEvery($options);
  990.                 if(is_array($result&& (count($result!= $num_ids && $without_conditions)){
  991.                     $result = false;
  992.                 }
  993.                 return $result;
  994.                 break;
  995.         }
  996.  
  997.     }
  998.  
  999.     function _extractOptionsFromArgs(&$args)
  1000.     {
  1001.         $last_arg count($args)-1;
  1002.         return isset($args[$last_arg]&& is_array($args[$last_arg]&& $this->_isOptionsHash($args[$last_arg]array_pop($args: array();
  1003.     }
  1004.  
  1005.     function _isOptionsHash($options)
  1006.     {
  1007.         if (isset($options[0])){
  1008.             return false;
  1009.         }
  1010.         $valid_keys = array('conditions''include''joins''limit''offset''order''sort''bind''select','select_prefix''readonly');
  1011.         foreach (array_keys($optionsas $key){
  1012.             if (!in_array($key,$valid_keys)){
  1013.                 return false;
  1014.             }
  1015.         }
  1016.         return true;
  1017.     }
  1018.  
  1019.     function _extractConditionsFromArgs($args$options)
  1020.     {
  1021.         if(empty($args)){
  1022.             $fetch 'all';
  1023.         else {
  1024.             $fetch $args[0];
  1025.         }
  1026.         $num_args count($args);
  1027.  
  1028.         // deprecated: acts like findFirstBySQL
  1029.         if ($num_args === 1 && !is_numeric($args[0]&& is_string($args[0]&& $args[0!= 'all' && $args[0!= 'first'){
  1030.             //  $Users->find("last_name = 'Williams'");    => find('first',"last_name = 'Williams'");
  1031.             Ak::deprecateWarning(array("AR::find('%sql') is ambiguous and therefore deprecated, use AR::find('first',%sql) instead"'%sql'=>$args[0]));
  1032.             $options = array('conditions'=> $args[0]);
  1033.             return array('first',$options);
  1034.         //end
  1035.  
  1036.         // set fetch_mode to 'all' if none is given
  1037.         if (!is_numeric($fetch&& !is_array($fetch&& $fetch != 'all' && $fetch != 'first'{
  1038.             array_unshift($args'all');
  1039.             $num_args count($args);
  1040.         }
  1041.         if ($num_args > 1{
  1042.             if (is_string($args[1])){
  1043.                 //  $Users->find(:fetch_mode,"first_name = ?",'Tim');
  1044.                 $fetch array_shift($args);
  1045.                 $options array_merge($optionsarray('conditions'=>$args));   //TODO: merge_conditions
  1046.             }elseif (is_array($args[1])) {
  1047.                 //  $Users->find(:fetch_mode,array('first_name = ?,'Tim'));
  1048.                 $fetch array_shift($args);
  1049.                 $options array_merge($optionsarray('conditions'=>$args[0]));   //TODO: merge_conditions
  1050.             }
  1051.         }
  1052.  
  1053.         return array($fetch,$options);
  1054.     }
  1055.  
  1056.     function _sanitizeConditionsVariables(&$options)
  1057.     {
  1058.         if(!empty($options['conditions']&& is_array($options['conditions'])){
  1059.             if (isset($options['conditions'][0]&& strstr($options['conditions'][0]'?'&& count($options['conditions']> 1){
  1060.                 //array('conditions' => array("name=?",$name))
  1061.                 $pattern array_shift($options['conditions']);
  1062.                 $options['bind'array_values($options['conditions']);
  1063.                 $options['conditions'$pattern;
  1064.             }elseif (isset($options['conditions'][0])){
  1065.                 //array('conditions' => array("user_name = :user_name", ':user_name' => 'hilario')
  1066.                 $pattern array_shift($options['conditions']);
  1067.                 $options['conditions'str_replace(array_keys($options['conditions'])array_values($this->getSanitizedConditionsArray($options['conditions'])),$pattern);
  1068.             }else{
  1069.                 //array('conditions' => array('user_name'=>'Hilario'))
  1070.                 $options['conditions'join(' AND ',(array)$this->getAttributesQuoted($options['conditions']));
  1071.             }
  1072.         }
  1073.         
  1074.     }
  1075.  
  1076.     function _validateFindOptions(&$options)
  1077.     {
  1078.         $valid_keys = array('conditions''include''joins''limit''offset''order''bind''select','select_prefix''readonly');
  1079.         foreach (array_keys($optionsas $key){
  1080.             if (!in_array($key,$valid_keys)) unset($options[$key]);
  1081.         }
  1082.     }
  1083.  
  1084.     function &findFirst()
  1085.     {
  1086.         if(!isset($this->_activeRecordHasBeenInstantiated)){
  1087.             return Ak::handleStaticCall();
  1088.         }
  1089.         $args func_get_args();
  1090.         $result =Ak::call_user_func_array(array(&$this,'find')array_merge(array('first'),$args));
  1091.         return $result;
  1092.     }
  1093.  
  1094.     function &findAll()
  1095.     {
  1096.         if(!isset($this->_activeRecordHasBeenInstantiated)){
  1097.             return Ak::handleStaticCall();
  1098.         }
  1099.         $args func_get_args();
  1100.         $result =Ak::call_user_func_array(array(&$this,'find')array_merge(array('all'),$args));
  1101.         return $result;
  1102.     }
  1103.  
  1104.  
  1105.     /**
  1106.     * Works like find_all, but requires a complete SQL string. Examples:
  1107.     * $Post->findBySql("SELECT p.*, c.author FROM posts p, comments c WHERE p.id = c.post_id");
  1108.     * $Post->findBySql(array("SELECT * FROM posts WHERE author = ? AND created_on > ?", $author_id, $start_date));
  1109.     */
  1110.     function &findBySql($sql$limit = null$offset = null$bindings = null)
  1111.     {
  1112.         if ($limit || $offset){
  1113.             Ak::deprecateWarning("You're calling AR::findBySql with \$limit or \$offset parameters. This has been deprecated.");
  1114.             $this->_db->addLimitAndOffset($sqlarray('limit'=>$limit,'offset'=>$offset));
  1115.         }
  1116.         if(!isset($this->_activeRecordHasBeenInstantiated)){
  1117.             return Ak::handleStaticCall();
  1118.         }
  1119.         $objects = array();
  1120.         $records $this->_db->select ($sql,'selecting');
  1121.         foreach ($records as $record){
  1122.             $objects[=$this->instantiate($this->getOnlyAvailableAttributes($record)false);
  1123.         }
  1124.         return $objects;
  1125.     }
  1126.  
  1127.     /**
  1128.     * This function pretends to emulate RoR finders until AkActiveRecord::addMethod becomes stable on future PHP versions.
  1129.     * @todo use PHP5 __call method for handling the magic finder methods like findFirstByUnsenameAndPassword('bermi','pass')
  1130.     */
  1131.     function &findFirstBy()
  1132.     {
  1133.         if(!isset($this->_activeRecordHasBeenInstantiated)){
  1134.             return Ak::handleStaticCall();
  1135.         }
  1136.         $args func_get_args();
  1137.         array_unshift($args,'first');
  1138.         $result =Ak::call_user_func_array(array(&$this,'findBy')$args);
  1139.         return $result;
  1140.     }
  1141.  
  1142.     function &findLastBy()
  1143.     {
  1144.         if(!isset($this->_activeRecordHasBeenInstantiated)){
  1145.             return Ak::handleStaticCall();
  1146.         }
  1147.         $args func_get_args();
  1148.         $options $this->_extractOptionsFromArgs($args);
  1149.         $options['order'$this->getPrimaryKey().' DESC';
  1150.         array_push($args$options);
  1151.         $result =Ak::call_user_func_array(array(&$this,'findFirstBy')$args);
  1152.         return $result;
  1153.     }
  1154.  
  1155.     function &findAllBy()
  1156.     {
  1157.         if(!isset($this->_activeRecordHasBeenInstantiated)){
  1158.             return Ak::handleStaticCall();
  1159.         }
  1160.         $args func_get_args();
  1161.         array_unshift($args,'all');
  1162.         $result =Ak::call_user_func_array(array(&$this,'findBy')$args);
  1163.         return $result;
  1164.     }
  1165.  
  1166.     /**
  1167.     * This method allows you to use finders in a more flexible way like:
  1168.     * 
  1169.     *   findBy('username AND password', $username, $password);
  1170.     *   findBy('age > ? AND name:contains', 18, 'Joe');
  1171.     *   findBy('is_active = true AND session_id', session_id());
  1172.     *   
  1173.     */
  1174.     function &findBy()
  1175.     {
  1176.         if(!isset($this->_activeRecordHasBeenInstantiated)){
  1177.             return Ak::handleStaticCall();
  1178.         }
  1179.         $args func_get_args();
  1180.         $find_by_sql array_shift($args);
  1181.         if($find_by_sql == 'all' || $find_by_sql == 'first'){
  1182.             $fetch $find_by_sql;
  1183.             $find_by_sql array_shift($args);
  1184.         }else{
  1185.             $fetch 'all';
  1186.         }
  1187.  
  1188.         $options $this->_extractOptionsFromArgs($args);
  1189.         
  1190.         $query_values $args;
  1191.         $query_arguments_count count($query_values);
  1192.  
  1193.         list($sql$requested_args$this->_getFindBySqlAndColumns($find_by_sql$query_values);
  1194.  
  1195.         if($query_arguments_count != count($requested_args)){
  1196.             trigger_error(Ak::t('Argument list did not match expected set. Requested arguments are:').join(', ',$requested_args),E_USER_ERROR);
  1197.             $false = false;
  1198.             return $false;
  1199.         }
  1200.  
  1201.         $true_bool_values = array(true,1,'true','True','TRUE','1','y','Y','yes','Yes','YES','s','Si','SI','V','v','T','t');
  1202.  
  1203.         foreach ($requested_args as $k=>$v){
  1204.             switch ($this->getColumnType($v)) {
  1205.                 case 'boolean':
  1206.                     $query_values[$kin_array($query_values[$k],$true_bool_values? true : false;
  1207.                     break;
  1208.  
  1209.                 case 'date':
  1210.                 case 'datetime':
  1211.                     $query_values[$kstr_replace('/','-'$this->castAttributeForDatabase($k,$query_values[$k],false));
  1212.                     break;
  1213.  
  1214.                 default:
  1215.                     break;
  1216.             }
  1217.         }
  1218.  
  1219.         $conditions = array($sql);
  1220.         foreach ($query_values as $bind_value){
  1221.             $conditions[$bind_value;
  1222.         }
  1223.         /**
  1224.         * @todo merge_conditions
  1225.         */
  1226.         $options['conditions'$conditions;
  1227.  
  1228.         $result =Ak::call_user_func_array(array(&$this,'find')array($fetch,$options));
  1229.         return $result;
  1230.     }
  1231.  
  1232.  
  1233.     function _getFindBySqlAndColumns($find_by_sql&$query_values)
  1234.     {
  1235.         $sql str_replace(array('(',')','||','|','&&','&','  '),array(' ( ',' ) ',' OR ',' OR ',' AND ',' AND ',' ')$find_by_sql);
  1236.         $operators = array('AND','and','(',')','&','&&','NOT','<>','OR','|','||');
  1237.         $pieces explode(' ',$sql);
  1238.         $pieces array_diff($pieces,array(' ',''));
  1239.         $params array_diff($pieces,$operators);
  1240.         $operators array_diff($pieces,$params);
  1241.  
  1242.         $new_sql '';
  1243.         $parameter_count = 0;
  1244.         $requested_args = array();
  1245.         foreach ($pieces as $piece){
  1246.             if(in_array($piece,$params&& $this->hasColumn($piece)){
  1247.                 $new_sql .= $piece.' = ? ';
  1248.                 $requested_args[$parameter_count$piece;
  1249.                 $parameter_count++;
  1250.             }elseif (!in_array($piece,$operators)){
  1251.  
  1252.                 if(strstr($piece,':')){
  1253.                     $_tmp_parts explode(':',$piece);
  1254.                     if($this->hasColumn($_tmp_parts[0])){
  1255.                         $query_values[$parameter_count= isset($query_values[$parameter_count]$query_values[$parameter_count$this->get($_tmp_parts[0]);
  1256.                         switch (strtolower($_tmp_parts[1])) {
  1257.                             case 'like':
  1258.                             case '%like%':
  1259.                             case 'is':
  1260.                             case 'has':
  1261.                             case 'contains':
  1262.                                 $query_values[$parameter_count'%'.$query_values[$parameter_count].'%';
  1263.                                 $new_sql .= $_tmp_parts[0]." LIKE ? ";
  1264.                                 break;
  1265.                             case 'like_left':
  1266.                             case 'like%':
  1267.                             case 'begins':
  1268.                             case 'begins_with':
  1269.                             case 'starts':
  1270.                             case 'starts_with':
  1271.                                 $query_values[$parameter_count$query_values[$parameter_count].'%';
  1272.                                 $new_sql .= $_tmp_parts[0]." LIKE ? ";
  1273.                                 break;
  1274.                             case 'like_right':
  1275.                             case '%like':
  1276.                             case 'ends':
  1277.                             case 'ends_with':
  1278.                             case 'finishes':
  1279.                             case 'finishes_with':
  1280.                                 $query_values[$parameter_count'%'.$query_values[$parameter_count];
  1281.                                 $new_sql .= $_tmp_parts[0]." LIKE ? ";
  1282.                                 break;
  1283.                             default:
  1284.                                 $query_values[$parameter_count$query_values[$parameter_count];
  1285.                                 $new_sql .= $_tmp_parts[0].' '.$_tmp_parts[1].' ? ';
  1286.                                 break;
  1287.                         }
  1288.                         $requested_args[$parameter_count$_tmp_parts[0];
  1289.                         $parameter_count++;
  1290.                     }else {
  1291.                         $new_sql .= $_tmp_parts[0];
  1292.                     }
  1293.                 }else{
  1294.                     $new_sql .= $piece.' ';
  1295.                 }
  1296.             }else{
  1297.                 $new_sql .= $piece.' ';
  1298.             }
  1299.         }
  1300.  
  1301.         return array($new_sql$requested_args);
  1302.     }
  1303.  
  1304.  
  1305.     /**
  1306.      *  Given a condition that uses bindings like "user = ?  AND created_at > ?" will return a
  1307.      * string replacing the "?" bindings with the column values for current Active Record
  1308.      * 
  1309.      * @return string
  1310.      */
  1311.     function _getVariableSqlCondition($variable_condition)
  1312.     {
  1313.         $query_values = array();
  1314.         list($sql$requested_columns$this->_getFindBySqlAndColumns($variable_condition$query_values);
  1315.         $replacements = array();
  1316.         $sql preg_replace('/((('.join($requested_columns,'|').') = \?) = \?)/','$2'$sql);
  1317.         foreach ($requested_columns as $attribute){
  1318.             $replacements[$attribute$this->castAttributeForDatabase($attribute$this->get($attribute));
  1319.         }
  1320.         return trim(preg_replace('/('.join('|',array_keys($replacements)).')\s+([^\?]+)\s+\?/e'"isset(\$replacements['\\1']) ? '\\1 \\2 '.\$replacements['\\1']:'\\1 \\2 null'"$sql));
  1321.     }
  1322.  
  1323.  
  1324.     function constructFinderSql($options$select_from_prefix 'default')
  1325.     {
  1326.         $sql = isset($options['select_prefix']$options['select_prefix'($select_from_prefix == 'default' 'SELECT * FROM '.$this->getTableName($select_from_prefix);
  1327.         $sql  .= !empty($options['joins']' '.$options['joins''';
  1328.  
  1329.         $this->addConditions($sqlisset($options['conditions']$options['conditions': array());
  1330.  
  1331.         // Create an alias for order
  1332.         if(empty($options['order']&& !empty($options['sort'])){
  1333.             $options['order'$options['sort'];
  1334.         }
  1335.  
  1336.         $sql  .= !empty($options['order']' ORDER BY  '.$options['order''';
  1337.  
  1338.         $this->_db->addLimitAndOffset($sql,$options);
  1339.  
  1340.         return $sql;
  1341.     }
  1342.  
  1343.  
  1344.     /**
  1345.     * Adds a sanitized version of $conditions to the $sql string. Note that the passed $sql string is changed.
  1346.     */
  1347.     function addConditions(&$sql$conditions = null$table_alias = null)
  1348.     {
  1349.         $concat = empty($sql'' ' WHERE ';
  1350.         if (empty($conditions&& $this->_getDatabaseType(== 'sqlite'$conditions '1';  // sqlite HACK
  1351.         if(!empty($conditions)){
  1352.             $sql  .= $concat.$conditions;
  1353.             $concat ' AND ';
  1354.         }
  1355.  
  1356.         if($this->getInheritanceColumn(!== false && $this->descendsFromActiveRecord($this)){
  1357.             $type_condition $this->typeCondition($table_alias);
  1358.             $sql .= !empty($type_condition$concat.$type_condition '';
  1359.         }
  1360.         return $sql;
  1361.     }
  1362.  
  1363.     /**
  1364.     * Gets a sanitized version of the input array. Each element will be escaped
  1365.     */
  1366.     function getSanitizedConditionsArray($conditions_array)
  1367.     {
  1368.         $result = array();
  1369.         foreach ($conditions_array as $k=>$v){
  1370.             $k str_replace(':','',$k)// Used for Oracle type bindings
  1371.             if($this->hasColumn($k)){
  1372.                 $v $this->castAttributeForDatabase($k$v);
  1373.                 $result[$k$v;
  1374.             }
  1375.         }
  1376.         return $result;
  1377.     }
  1378.  
  1379.  
  1380.     /**
  1381.     * This functions is used to get the conditions from an AkRequest object
  1382.     */
  1383.     function getConditions($conditions$prefix ''$model_name = null)
  1384.     {
  1385.         $model_name = isset($model_name$model_name $this->getModelName();
  1386.         $model_conditions !empty($conditions[$model_name]$conditions[$model_name$conditions;
  1387.         if(is_a($this->$model_name)){
  1388.             $model_instance =$this->$model_name;
  1389.         }else{
  1390.             $model_instance =$this;
  1391.         }
  1392.         $new_conditions = array();
  1393.         if(is_array($model_conditions)){
  1394.             foreach ($model_conditions as $col=>$value){
  1395.                 if($model_instance->hasColumn($col)){
  1396.                     $new_conditions[$prefix.$col$value;
  1397.                 }
  1398.             }
  1399.         }
  1400.         return $new_conditions;
  1401.     }
  1402.  
  1403.     /**
  1404.     *
  1405.     * @access private
  1406.     */
  1407.     function _quoteColumnName($column_name)
  1408.     {
  1409.         return $this->_db->nameQuote.$column_name.$this->_db->nameQuote;
  1410.     }
  1411.  
  1412.  
  1413.  
  1414.  
  1415.     /**
  1416.     * EXPERIMENTAL: Will allow to create finders when PHP includes aggregate_methods as a stable feature on PHP4, for PHP5 we might use __call
  1417.     *
  1418.     * @access private
  1419.     */
  1420.     function _buildFinders($finderFunctions = array('find','findFirst'))
  1421.     {
  1422.         if(!$this->_dynamicMethods){
  1423.             return;
  1424.         }
  1425.         $columns !is_array($this->_dynamicMethodsarray_keys($this->getColumns()) $this->_dynamicMethods;
  1426.         $class_name 'ak_'.md5(serialize($columns));
  1427.         if(!class_exists($class_name)){
  1428.             $permutations Ak::permute($columns);
  1429.             $implementations '';
  1430.             foreach ($finderFunctions as $finderFunction){
  1431.                 foreach ($permutations as $permutation){
  1432.                     $permutation array_map(array('AkInflector','camelize'),$permutation);
  1433.                     foreach ($permutation as $k=>$v){
  1434.                         $method_name $finderFunction.'By'.join($permutation,'And');
  1435.                         $implementation 'function &'.$method_name.'(';
  1436.                         $first_param '';
  1437.                         $params '';
  1438.                         $i = 1;
  1439.                         foreach ($permutation as $column){
  1440.                             $column AkInflector::underscore($column);
  1441.                             $params .= "$$column";
  1442.                             $first_param .= "$column ";
  1443.                             $i++;
  1444.                         }
  1445.                         $implementation .= trim($params,' ,')."){\n";
  1446.                         $implementation .= '$options = func_num_args() == '.$i.' ? func_get_arg('.($i-1).') : array();'."\n";
  1447.                         $implementation .= 'return $this->'.$finderFunction.'By(\''.$first_param.'\', '.trim($params,' ,').", \$options);\n }\n";
  1448.                         $implementations[$method_name$implementation;
  1449.                         array_shift($permutation);
  1450.                     }
  1451.                 }
  1452.             }
  1453.             eval('class '.$class_name.' { '.join("\n",$implementations).' } ');
  1454.         }
  1455.  
  1456.         aggregate_methods(&$this$class_name);
  1457.     }
  1458.  
  1459.  
  1460.     /**
  1461.     * Finder methods must instantiate through this method to work with the single-table inheritance model and
  1462.     * eager loading associations.
  1463.     * that makes it possible to create objects of different types from the same table.
  1464.     */
  1465.     function &instantiate($record$set_as_new = true)
  1466.     {
  1467.         $inheritance_column $this->getInheritanceColumn();
  1468.         if(!empty($record[$inheritance_column])){
  1469.             $inheritance_column $record[$inheritance_column];
  1470.             $inheritance_model_name AkInflector::camelize($inheritance_column);
  1471.             @require_once(AkInflector::toModelFilename($inheritance_model_name));
  1472.             if(!class_exists($inheritance_model_name)){
  1473.                 trigger_error($this->t("The single-table inheritance mechanism failed to locate the subclass: '%class_name'. ".
  1474.                 "This error is raised because the column '%column' is reserved for storing the class in case of inheritance. ".
  1475.                 "Please rename this column if you didn't intend it to be used for storing the inheritance class ".
  1476.                 "or overwrite #{self.to_s}.inheritance_column to use another column for that information.",
  1477.                 array('%class_name'=>$inheritance_model_name'%column'=>$this->getInheritanceColumn())),E_USER_ERROR);
  1478.             }
  1479.         }
  1480.  
  1481.         $model_name = isset($inheritance_model_name$inheritance_model_name $this->getModelName();
  1482.         $object =new $model_name('attributes'$record);
  1483.  
  1484.         $object->_newRecord = $set_as_new;
  1485.  
  1486.         (AK_CLI && AK_ENVIRONMENT == 'development'$object ->toString(: null;
  1487.  
  1488.         return $object;
  1489.     }
  1490.  
  1491.     /*/Finding records*/
  1492.  
  1493.  
  1494.  
  1495.     /**
  1496.                            Table inheritance
  1497.      ====================================================================
  1498.      */
  1499.     function descendsFromActiveRecord(&$object)
  1500.     {
  1501.         if(substr(strtolower(get_parent_class($object)),-12== 'activerecord'){
  1502.             return true;
  1503.         }
  1504.         if(!method_exists($object'getInheritanceColumn')){
  1505.             return false;
  1506.         }
  1507.         $inheritance_column $object->getInheritanceColumn();
  1508.         return !empty($inheritance_column);
  1509.     }
  1510.  
  1511.     /**
  1512.      * Gets the column name for use with single table inheritance. Can be overridden in subclasses.
  1513.     */
  1514.     function getInheritanceColumn()
  1515.     {
  1516.         return empty($this->_inheritanceColumn($this->hasColumn('type''type' : false $this->_inheritanceColumn;
  1517.     }
  1518.  
  1519.     /**
  1520.      * Defines the column name for use with single table inheritance. Can be overridden in subclasses.
  1521.      */
  1522.     function setInheritanceColumn($column_name)
  1523.     {
  1524.         if(!$this->hasColumn($column_name)){
  1525.             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);
  1526.             return false;
  1527.         }elseif($this->getColumnType($column_name!= 'string'){
  1528.             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);
  1529.             return false;
  1530.         }else{
  1531.             $this->_inheritanceColumn = $column_name;
  1532.             return true;
  1533.         }
  1534.     }
  1535.  
  1536.  
  1537.     function getSubclasses()
  1538.     {
  1539.         $current_class get_class($this);
  1540.         $subclasses = array();
  1541.         $classes get_declared_classes();
  1542.  
  1543.         while ($class array_shift($classes)) {
  1544.             $parent_class get_parent_class($class);
  1545.             if($parent_class == $current_class || in_array($parent_class,$subclasses)){
  1546.                 $subclasses[$class;
  1547.             }elseif(!empty($parent_class)){
  1548.                 $classes[$parent_class;
  1549.             }
  1550.         }
  1551.         $subclasses array_unique(array_map(array(&$this,'_getModelName'),$subclasses));
  1552.         return $subclasses;
  1553.     }
  1554.  
  1555.  
  1556.     function typeCondition($table_alias = null)
  1557.     {
  1558.         $inheritance_column $this->getInheritanceColumn();
  1559.         $type_condition = array();
  1560.         $table_name $this->getTableName();
  1561.         $available_types array_merge(array($this->getModelName()),$this->getSubclasses());
  1562.         foreach ($available_types as $subclass){
  1563.             $type_condition[' '.($table_alias != null ? $table_alias $table_name).'.'.$inheritance_column.' = \''.AkInflector::humanize(AkInflector::underscore($subclass)).'\' ';
  1564.         }
  1565.         return empty($type_condition'' '('.join('OR',$type_condition).') ';
  1566.     }
  1567.  
  1568.     /*/Table inheritance*/
  1569.  
  1570.  
  1571.  
  1572.     /**
  1573.                          Setting Attributes
  1574.     ====================================================================
  1575.     See also: Getting Attributes, Model Attributes, Toggling Attributes, Counting Attributes.
  1576.     */
  1577.     function setAttribute($attribute$value$inspect_for_callback_child_method = AK_ACTIVE_RECORD_ENABLE_CALLBACK_SETTERS$compose_after_set = true)
  1578.     {
  1579.         if($attribute[0== '_'){
  1580.             return false;
  1581.         }
  1582.  
  1583.         if($this->isFrozen()){
  1584.             return false;
  1585.         }
  1586.         if($inspect_for_callback_child_method === true && method_exists($this,'set'.AkInflector::camelize($attribute))){
  1587.             static $watchdog;
  1588.             $watchdog[$attribute@$watchdog[$attribute]+1;
  1589.             if($watchdog[$attribute== 5000){
  1590.                 if((!defined('AK_ACTIVE_RECORD_PROTECT_SET_RECURSION')) || defined('AK_ACTIVE_RECORD_PROTECT_SET_RECURSION'&& AK_ACTIVE_RECORD_PROTECT_SET_RECURSION){
  1591.                     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);
  1592.                     return false;
  1593.                 }
  1594.             }
  1595.             $this->{$attribute.'_before_type_cast'$value;
  1596.             return $this->{'set'.AkInflector::camelize($attribute)}($value);
  1597.         }
  1598.         if($this->hasAttribute($attribute)){
  1599.             $this->{$attribute.'_before_type_cast'$value;
  1600.             $this->$attribute $value;
  1601.             if($compose_after_set && !empty($this->_combinedAttributes&& !$this->requiredForCombination($attribute)){
  1602.                 $combined_attributes $this->_getCombinedAttributesWhereThisAttributeIsUsed($attribute);
  1603.                 foreach ($combined_attributes as $combined_attribute){
  1604.                     $this->composeCombinedAttribute($combined_attribute);
  1605.                 }
  1606.             }
  1607.             if ($compose_after_set && $this->isCombinedAttribute($attribute)){
  1608.                 $this->decomposeCombinedAttribute($attribute);
  1609.             }
  1610.         }elseif(substr($attribute,-12== 'confirmation' && $this->hasAttribute(substr($attribute,0,-13))){
  1611.             $this->$attribute $value;
  1612.         }
  1613.  
  1614.         if($this->_internationalize){
  1615.             if(is_array($value)){
  1616.                 $this->setAttributeLocales($attribute$value);
  1617.             }elseif(is_string($inspect_for_callback_child_method)){
  1618.                 $this->setAttributeByLocale($attribute$value$inspect_for_callback_child_method);
  1619.             }else{
  1620.                 $this->_groupInternationalizedAttribute($attribute$value);
  1621.             }
  1622.         }
  1623.         return true;
  1624.     }
  1625.  
  1626.     function set($attribute$value = null$inspect_for_callback_child_method = true$compose_after_set = true)
  1627.     {
  1628.         if(is_array($attribute)){
  1629.             return $this->setAttributes($attribute);
  1630.         }
  1631.         return $this->setAttribute($attribute$value$inspect_for_callback_child_method$compose_after_set);
  1632.     }
  1633.  
  1634.     /**
  1635.     * Allows you to set all the attributes at once by passing in an array with 
  1636.     * keys matching the attribute names (which again matches the column names). 
  1637.     * Sensitive attributes can be protected from this form of mass-assignment by 
  1638.     * using the $this->setProtectedAttributes method. Or you can alternatively 
  1639.     * specify which attributes can be accessed in with the $this->setAccessibleAttributes method. 
  1640.     * Then all the attributes not included in that won?t be allowed to be mass-assigned.
  1641.     */
  1642.     function setAttributes($attributes$override_attribute_protection = false)
  1643.     {
  1644.         $this->parseAkelosArgs($attributes);
  1645.         if(!$override_attribute_protection){
  1646.             $attributes $this->removeAttributesProtectedFromMassAssignment($attributes);
  1647.         }
  1648.         if(!empty($attributes&& is_array($attributes)){
  1649.             foreach ($attributes as $k=>$v){
  1650.                 $this->setAttribute($k$v);
  1651.             }
  1652.         }
  1653.     }
  1654.  
  1655.  
  1656.     function setId($value)
  1657.     {
  1658.         if($this->isFrozen()){
  1659.             return false;
  1660.         }
  1661.         $pk $this->getPrimaryKey();
  1662.         $this->$pk $value;
  1663.         return true;
  1664.     }
  1665.  
  1666.  
  1667.     /*/Setting Attributes*/
  1668.  
  1669.     /**
  1670.                          Getting Attributes
  1671.     ====================================================================
  1672.     See also: Setting Attributes, Model Attributes, Toggling Attributes, Counting Attributes.
  1673.     */
  1674.  
  1675.     function getAttribute($attribute$inspect_for_callback_child_method = AK_ACTIVE_RECORD_ENABLE_CALLBACK_GETTERS)
  1676.     {
  1677.         if($attribute[0== '_'){
  1678.             return false;
  1679.         }
  1680.  
  1681.         if($inspect_for_callback_child_method === true && method_exists($this,'get'.AkInflector::camelize($attribute))){
  1682.             static $watchdog;
  1683.             $watchdog[@$attribute@$watchdog[$attribute]+1;
  1684.             if($watchdog[$attribute== 5000){
  1685.                 if((!defined('AK_ACTIVE_RECORD_PROTECT_GET_RECURSION')) || defined('AK_ACTIVE_RECORD_PROTECT_GET_RECURSION'&& AK_ACTIVE_RECORD_PROTECT_GET_RECURSION){
  1686.                     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);
  1687.                     return false;
  1688.                 }
  1689.             }
  1690.             $value $this->{'get'.AkInflector::camelize($attribute)}();
  1691.             return $this->getInheritanceColumn(=== $attribute AkInflector::humanize(AkInflector::underscore($value)) $value;
  1692.         }
  1693.         if(isset($this->$attribute|| (!isset($this->$attribute&& $this->isCombinedAttribute($attribute))){
  1694.             if($this->hasAttribute($attribute)){
  1695.                 if (!empty($this->_combinedAttributes&& $this->isCombinedAttribute($attribute)){
  1696.                     $this->composeCombinedAttribute($attribute);
  1697.                 }
  1698.                 return isset($this->$attribute$this->$attribute : null;
  1699.             }elseif($this->_internationalize && $this->_isInternationalizeCandidate($attribute)){
  1700.                 if(!empty($this->$attribute&& is_string($this->$attribute)){
  1701.                     return $this->$attribute;
  1702.                 }
  1703.                 $current_locale $this->getCurrentLocale();
  1704.                 if(!empty($this->$attribute[$current_locale]&& is_array($this->$attribute)){
  1705.                     return $this->$attribute[$current_locale];
  1706.                 }
  1707.                 return $this->getAttribute($current_locale.'_'.$attribute);
  1708.             }
  1709.         }
  1710.  
  1711.         if($this->_internationalize){
  1712.             return $this->getAttributeByLocale($attributeis_bool($inspect_for_callback_child_method$this->getCurrentLocale($inspect_for_callback_child_method);
  1713.         }
  1714.         return null;
  1715.     }
  1716.  
  1717.     function get($attribute = null$inspect_for_callback_child_method = true)
  1718.     {
  1719.         return !isset($attribute$this->getAttributes($inspect_for_callback_child_method$this->getAttribute($attribute$inspect_for_callback_child_method);
  1720.     }
  1721.  
  1722.     /**
  1723.     * Returns an array of all the attributes with their names as keys and clones of their objects as values in case they are objects.
  1724.     */
  1725.     function getAttributes()
  1726.     {
  1727.         $attributes = array();
  1728.         $available_attributes $this->getAvailableAttributes();
  1729.         foreach ($available_attributes as $available_attribute){
  1730.             $attribute $this->getAttribute($available_attribute['name']);
  1731.             $attributes[$available_attribute['name']] AK_PHP5 && is_object($attribute? clone($attribute$attribute;
  1732.         }
  1733.  
  1734.         if($this->_internationalize){
  1735.             $current_locale $this->getCurrentLocale();
  1736.             foreach ($this->getInternationalizedColumns(as $column=>$languages){
  1737.                 if(empty($attributes[$column]&& isset($attributes[$current_locale.'_'.$column]&& in_array($current_locale,$languages)){
  1738.                     $attributes[$column$attributes[$current_locale.'_'.$column];
  1739.                 }
  1740.             }
  1741.         }
  1742.  
  1743.         return $attributes;
  1744.     }
  1745.  
  1746.  
  1747.     /**
  1748.     * 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.
  1749.     */
  1750.     function getId()
  1751.     {
  1752.         return $this->{$this->getPrimaryKey()};
  1753.     }
  1754.  
  1755.     /*/Getting Attributes*/
  1756.  
  1757.  
  1758.  
  1759.     /**
  1760.                          Toggling Attributes
  1761.     ====================================================================
  1762.     See also: Setting Attributes, Getting Attributes.
  1763.     */
  1764.     /**
  1765.     * Turns an attribute that's currently true into false and vice versa. Returns attribute value.
  1766.     */
  1767.     function toggleAttribute($attribute)
  1768.     {
  1769.         $value $this->getAttribute($attribute);
  1770.         $new_value $value ? false : true;
  1771.         $this->setAttribute($attribute$new_value);
  1772.         return $new_value;
  1773.     }
  1774.  
  1775.  
  1776.     /**
  1777.     * Toggles the attribute and saves the record.
  1778.     */
  1779.     function toggleAttributeAndSave($attribute)
  1780.     {
  1781.         $value $this->toggleAttribute($attribute);
  1782.         if($this->updateAttribute($attribute$value)){
  1783.             return $value;
  1784.         }
  1785.         return null;
  1786.     }
  1787.  
  1788.     /*/Toggling Attributes*/
  1789.  
  1790.  
  1791.     /**
  1792.                          Counting Attributes
  1793.     ====================================================================
  1794.     See also: Counting Records, Setting Attributes, Getting Attributes.
  1795.     */
  1796.  
  1797.     /**
  1798.     * Increments the specified counter by one. So $DiscussionBoard->incrementCounter("post_count", 
  1799.     * $discussion_board_id); would increment the "post_count" counter on the board responding to 
  1800.     * $discussion_board_id. This is used for caching aggregate values, so that they doesn't need to 
  1801.     * be computed every time. Especially important for looping over a collection where each element 
  1802.     * require a number of aggregate values. Like the $DiscussionBoard that needs to list both the number of posts and comments.
  1803.     */
  1804.     function incrementCounter($counter_name$id$difference = 1)
  1805.     {
  1806.         return $this->updateAll("$counter_name = $counter_name + $difference"$this->getPrimaryKey().' = '.$this->castAttributeForDatabase($this->getPrimaryKey()$id)) === 1;
  1807.     }
  1808.  
  1809.     /**
  1810.     * Works like AkActiveRecord::incrementCounter, but decrements instead.
  1811.     */
  1812.     function decrementCounter($counter_name$id$difference = 1)
  1813.     {
  1814.         return $this->updateAll("$counter_name = $counter_name - $difference"$this->getPrimaryKey().' = '.$this->castAttributeForDatabase($this->getPrimaryKey()$id)) === 1;
  1815.     }
  1816.  
  1817.     /**
  1818.     * Initializes the attribute to zero if null and subtracts one. Only makes sense for number-based attributes. Returns attribute value.
  1819.     */
  1820.     function decrementAttribute($attribute)
  1821.     {
  1822.         if(!isset($this->$attribute)){
  1823.             $this->$attribute = 0;
  1824.         }
  1825.         return $this->$attribute -= 1;
  1826.     }
  1827.  
  1828.     /**
  1829.     * Decrements the attribute and saves the record.
  1830.     */
  1831.     function decrementAndSaveAttribute($attribute)
  1832.     {
  1833.         return $this->updateAttribute($attribute,$this->decrementAttribute($attribute));
  1834.     }
  1835.  
  1836.  
  1837.     /**
  1838.     * Initializes the attribute to zero if null and adds one. Only makes sense for number-based attributes. Returns attribute value.
  1839.     */
  1840.     function incrementAttribute($attribute)
  1841.     {
  1842.         if(!isset($this->$attribute)){
  1843.             $this->$attribute = 0;
  1844.         }
  1845.         return $this->$attribute += 1;
  1846.     }
  1847.  
  1848.     /**
  1849.     * Increments the attribute and saves the record.
  1850.     */
  1851.     function incrementAndSaveAttribute($attribute)
  1852.     {
  1853.         return $this->updateAttribute($attribute,$this->incrementAttribute($attribute));
  1854.     }
  1855.  
  1856.     /*/Counting Attributes*/
  1857.  
  1858.     /**
  1859.                          Protecting attributes
  1860.     ====================================================================
  1861.     */
  1862.  
  1863.     /**
  1864.     * If this macro is used, only those attributed named in it will be accessible 
  1865.     * for mass-assignment, such as new ModelName($attributes) and $this->attributes($attributes). 
  1866.     * This is the more conservative choice for mass-assignment protection. 
  1867.     * If you'd rather start from an all-open default and restrict attributes as needed, 
  1868.     * have a look at AkActiveRecord::setProtectedAttributes().
  1869.     */
  1870.     function setAccessibleAttributes()
  1871.     {
  1872.         $args func_get_args();
  1873.         $this->_accessibleAttributes = array_unique(array_merge((array)$this->_accessibleAttributes$args));
  1874.     }
  1875.  
  1876.     /**
  1877.      * Attributes named in this macro are protected from mass-assignment, such as 
  1878.      * new ModelName($attributes) and $this->attributes(attributes). Their assignment 
  1879.      * will simply be ignored. Instead, you can use the direct writer methods to do assignment. 
  1880.      * This is meant to protect sensitive attributes to be overwritten by URL/form hackers. 
  1881.      * 
  1882.      * Example:
  1883.      * <code>
  1884.      *   class Customer extends ActiveRecord
  1885.      *    {
  1886.      *      function Customer()
  1887.      *      {
  1888.      *          $this->setProtectedAttributes('credit_rating');
  1889.      *      }
  1890.      *    }
  1891.      *  
  1892.      *    $Customer = new Customer('name' => 'David', 'credit_rating' => 'Excellent');
  1893.      *    $Customer->credit_rating // => null
  1894.      *    $Customer->attributes(array('description' => 'Jolly fellow', 'credit_rating' => 'Superb'));
  1895.      *    $Customer->credit_rating // => null
  1896.      *  
  1897.      *    $Customer->credit_rating = 'Average'
  1898.      *    $Customer->credit_rating // => 'Average'
  1899.      *  </code>
  1900.      */    
  1901.     function setProtectedAttributes()
  1902.     {
  1903.         $args func_get_args();
  1904.         $this->_protectedAttributes = array_unique(array_merge((array)$this->_protectedAttributes$args));
  1905.     }
  1906.  
  1907.     function removeAttributesProtectedFromMassAssignment($attributes)
  1908.     {
  1909.         if(!empty($this->_accessibleAttributes&& is_array($this->_accessibleAttributes&&  is_array($attributes)){
  1910.             foreach (array_keys($attributesas $k){
  1911.                 if(!in_array($k,$this->_accessibleAttributes)){
  1912.                     unset($attributes[$k]);
  1913.                 }
  1914.             }
  1915.         }elseif (!empty($this->_protectedAttributes&& is_array($this->_protectedAttributes&&  is_array($attributes)){
  1916.             foreach (array_keys($attributesas $k){
  1917.                 if(in_array($k,$this->_protectedAttributes)){
  1918.                     unset($attributes[$k]);
  1919.                 }
  1920.             }
  1921.         }
  1922.         return $attributes;
  1923.     }
  1924.  
  1925.     /*/Protecting attributes*/
  1926.  
  1927.  
  1928.     /**
  1929.                           Model Attributes
  1930.      ====================================================================
  1931.      See also: Getting Attributes, Setting Attributes.
  1932.      */
  1933.     /**
  1934.     * Returns an array of all the attributes that have been specified for serialization as keys and the objects as values.
  1935.     */
  1936.     function getSerializedAttributes()
  1937.     {
  1938.         return isset($this->_serializedAttributes$this->_serializedAttributes : array();
  1939.     }
  1940.  
  1941.     function getAvailableAttributes()
  1942.     {
  1943.         return array_merge($this->getColumns()$this->getAvailableCombinedAttributes());
  1944.     }
  1945.  
  1946.     function getAttributeCaption($attribute)
  1947.     {
  1948.         return $this->t(AkInflector::humanize($attribute));
  1949.     }
  1950.  
  1951.     /**
  1952.      * This function is useful in case you need to know if attributes have been assigned to an object.
  1953.      */
  1954.     function hasAttributesDefined()
  1955.     {
  1956.         $attributes join('',$this->getAttributes());
  1957.         return empty($attributes);
  1958.     }
  1959.  
  1960.  
  1961.     /**
  1962.     * Returns the primary key field.
  1963.     */
  1964.     function getPrimaryKey()
  1965.     {
  1966.         if(!isset($this->_primaryKey)){
  1967.             $this->setPrimaryKey();
  1968.         }
  1969.         return $this->_primaryKey;
  1970.     }
  1971.  
  1972.     function getColumnNames()
  1973.     {
  1974.         if(empty($this->_columnNames)){
  1975.             $columns $this->getColumns();
  1976.             foreach ($columns as $column_name=>$details){
  1977.                 $this->_columnNames[$column_name= isset($details->columnName$this->t($details->columnName$this->getAttributeCaption($column_name);
  1978.             }
  1979.         }
  1980.         return $this->_columnNames;
  1981.     }
  1982.  
  1983.  
  1984.     /**
  1985.     * Returns an array of columns objects where the primary id, all columns ending in "_id" or "_count", 
  1986.     * and columns used for single table inheritance has been removed.
  1987.     */
  1988.     function getContentColumns()
  1989.     {
  1990.         $inheritance_column $this->getInheritanceColumn();
  1991.         $columns $this->getColumns();
  1992.         foreach ($columns as $name=>$details){
  1993.             if((substr($name,-3== '_id' || substr($name,-6== '_count'||
  1994.             !empty($details['primaryKey']|| ($inheritance_column !== false && $inheritance_column == $name)){
  1995.                 unset($columns[$name]);
  1996.             }
  1997.         }
  1998.         return $columns;
  1999.     }
  2000.  
  2001.     /**
  2002.     * Returns an array of names for the attributes available on this object sorted alphabetically.
  2003.     */
  2004.     function getAttributeNames()
  2005.     {
  2006.         if(!isset($this->_activeRecordHasBeenInstantiated)){
  2007.             return Ak::handleStaticCall();
  2008.         }
  2009.         $attributes array_keys($this->getAvailableAttributes());
  2010.         $names = array_combine($attributes,array_map(array(&$this,'getAttributeCaption')$attributes));
  2011.         natsort($names);
  2012.         return $names;
  2013.     }
  2014.  
  2015.  
  2016.     /**
  2017.     * Returns true if the specified attribute has been set by the user or by a database load and is neither null nor empty?
  2018.     */
  2019.     function isAttributePresent($attribute)
  2020.     {
  2021.         $value $this->getAttribute($attribute);
  2022.         return !empty($value);
  2023.     }
  2024.  
  2025.     /**
  2026.     * Returns true if given attribute exists for this Model.
  2027.     *
  2028.     * @param string $attribute
  2029.     * @return boolean
  2030.     */
  2031.     function hasAttribute ($attribute)
  2032.     {
  2033.         empty($this->_columns$this->getColumns($this->_columns// HINT: only used by HasAndBelongsToMany joinObjects, if the table is not present yet!
  2034.         return isset($this->_columns[$attribute]|| (!empty($this->_combinedAttributes&& $this->isCombinedAttribute($attribute));
  2035.     }
  2036.  
  2037.     /*/Model Attributes*/
  2038.  
  2039.  
  2040.     /**
  2041.                           Combined attributes
  2042.     ====================================================================
  2043.     *
  2044.     * The Akelos Framework has a handy way to represent combined fields.
  2045.     * You can add a new attribute to your models using a printf patter to glue
  2046.     * multiple parameters in a single one.
  2047.     * 
  2048.     * For example, If we set...
  2049.     * $this->addCombinedAttributeConfiguration('name', "%s %s", 'first_name', 'last_name');
  2050.     * $this->addCombinedAttributeConfiguration('date', "%04d-%02d-%02d", 'year', 'month', 'day');
  2051.     * $this->setAttributes('first_name=>','John','last_name=>','Smith','year=>',2005,'month=>',9,'day=>',27);
  2052.     * 
  2053.     * $this->name // will have "John Smith" as value and
  2054.     * $this->date // will be 2005-09-27
  2055.     * 
  2056.     * On the other hand if you do
  2057.     *
  2058.     *   $this->setAttribute('date', '2008-11-30');
  2059.     *
  2060.     *   All the 'year', 'month' and 'day' getters will be fired (if they exist) the following attributes will be set
  2061.     *
  2062.     *    $this->year // will be 2008
  2063.     *    $this->month // will be 11 and
  2064.     *    $this->day // will be 27
  2065.     * 
  2066.     * Sometimes you might need a pattern for composing and another for decomposing attributes. In this case you can specify
  2067.     * an array as the pattern values, where first element will be the composing pattern and second element will be used
  2068.     * for decomposing.
  2069.     * 
  2070.     * You can also specify a callback method from this object function instead of a pattern. You can also assign a callback
  2071.     * for composing and another for decomposing by passing their names as an array like on the patterns.
  2072.     *
  2073.     *    <?php 
  2074.     *    class User extends ActiveRecord 
  2075.     *    { 
  2076.     *        function User()
  2077.     *        {
  2078.     *            // You can use a multiple patterns array where "%s, %s" will be used for combining fields and "%[^,], %s" will be used
  2079.     *            // for decomposing fields. (as you can see you can also use regular expressions on your patterns)
  2080.     *            $User->addCombinedAttributeConfiguration('name', array("%s, %s","%[^,], %s"), 'last_name', 'first_name');
  2081.     *            
  2082.     *            //Here we set email_link so compose_email_link() will be triggered for building up the field and parse_email_link will
  2083.     *            // be used for getting the fields out
  2084.     *            $User->addCombinedAttributeConfiguration('email_link', array("compose_email_link","parse_email_link"), 'email', 'name');
  2085.     *            
  2086.     *            // We need to tell the ActiveRecord to load it's magic (see the example below for a simpler solution)
  2087.     *            $attributes = (array)func_get_args();
  2088.     *            return $this->init($attributes);
  2089.     *   
  2090.     *        }
  2091.     *        function compose_email_link()
  2092.     *        {
  2093.     *            $args = func_get_arg(0); 
  2094.     *            return "<a href=\'mailto:{$args[\'email\']}\'>{$args[\'name\']}</a>"; 
  2095.     *        } 
  2096.     *        function parse_email_link($email_link) 
  2097.     *        { 
  2098.     *            $results = sscanf($email_link, "<a href=\'mailto:%[^\']\'>%[^<]</a>"); 
  2099.     *            return array(\'email\'=>$results[0],\'name\'=>$results[1]); 
  2100.     *        } 
  2101.     *        
  2102.     *    } 
  2103.     *   ?>
  2104.     *
  2105.     * You can also simplify your live by declaring the combined attributes as a class variable like:
  2106.     *    <?php 
  2107.     *    class User extends ActiveRecord 
  2108.     *    { 
  2109.     *       var $combined_attributes array(
  2110.     *       array('name', array("%s, %s","%[^,], %s"), 'last_name', 'first_name')
  2111.     *       array('email_link', array("compose_email_link","parse_email_link"), 'email', 'name')
  2112.     *       );
  2113.     *       
  2114.     *       // ....
  2115.     *    } 
  2116.     *   ?>
  2117.     *
  2118.     */
  2119.  
  2120.     /**
  2121.     * Returns true if given attribute is a combined attribute for this Model.
  2122.     *
  2123.     * @param string $attribute
  2124.     * @return boolean
  2125.     */
  2126.     function isCombinedAttribute ($attribute)
  2127.     {
  2128.         return !empty($this->_combinedAttributes&& isset($this->_combinedAttributes[$attribute]);
  2129.     }
  2130.  
  2131.     function addCombinedAttributeConfiguration($attribute)
  2132.     {
  2133.         $args is_array($attribute$attribute func_get_args();
  2134.         $columns array_slice($args,2);
  2135.         $invalid_columns = array();
  2136.         foreach ($columns as $colum){
  2137.             if(!$this->hasAttribute($colum)){
  2138.                 $invalid_columns[$colum;
  2139.             }
  2140.         }
  2141.         if(!empty($invalid_columns)){
  2142.             trigger_error(Ak::t('There was an error while setting the composed field "%field_name", the following mapping column/s "%columns" do not exist',
  2143.             array('%field_name'=>$args[0],'%columns'=>join(', ',$invalid_columns)))E_USER_ERROR);
  2144.         }else{
  2145.             $attribute array_shift($args);
  2146.             $this->_combinedAttributes[$attribute$args;
  2147.             $this->composeCombinedAttribute($attribute);
  2148.         }
  2149.     }
  2150.  
  2151.     function composeCombinedAttributes()
  2152.     {
  2153.  
  2154.         if(!empty($this->_combinedAttributes)){
  2155.             $attributes array_keys($this->_combinedAttributes);
  2156.             foreach ($attributes as $attribute){
  2157.                 $this->composeCombinedAttribute($attribute);
  2158.             }
  2159.         }
  2160.     }
  2161.  
  2162.     function composeCombinedAttribute($combined_attribute)
  2163.     {
  2164.         if($this->isCombinedAttribute($combined_attribute)){
  2165.             $config $this->_combinedAttributes[$combined_attribute];
  2166.             $pattern array_shift($config);
  2167.  
  2168.             $pattern is_array($pattern$pattern[0$pattern;
  2169.             $got = array();
  2170.  
  2171.             foreach ($config as $attribute){
  2172.                 if(isset($this->$attribute)){
  2173.                     $got[$attribute$this->getAttribute($attribute);
  2174.                 }
  2175.             }
  2176.             if(count($got=== count($config)){
  2177.                 $this->$combined_attribute method_exists($this$pattern$this->{$pattern}($gotvsprintf($pattern$got);
  2178.             }
  2179.         }
  2180.     }
  2181.  
  2182.     /**
  2183.     * @access private
  2184.     */
  2185.     function _getCombinedAttributesWhereThisAttributeIsUsed($attribute)
  2186.     {
  2187.         $result = array();
  2188.         foreach ($this->_combinedAttributes as $combined_attribute=>$settings){
  2189.             if(in_array($attribute,$settings)){
  2190.                 $result[$combined_attribute;
  2191.             }
  2192.         }
  2193.         return $result;
  2194.     }
  2195.  
  2196.  
  2197.     function requiredForCombination($attribute)
  2198.     {
  2199.         foreach ($this->_combinedAttributes as $settings){
  2200.             if(in_array($attribute,$settings)){
  2201.                 return true;
  2202.             }
  2203.         }
  2204.         return false;
  2205.     }
  2206.  
  2207.     function hasCombinedAttributes()
  2208.     {
  2209.         return count($this->getCombinedSubattributes()) === 0 ? false :true;
  2210.     }
  2211.  
  2212.     function getCombinedSubattributes($attribute)
  2213.     {
  2214.         $result = array();
  2215.         if(is_array($this->_combinedAttributes[$attribute])){
  2216.             $attributes $this->_combinedAttributes[$attribute];
  2217.             array_shift($attributes);
  2218.             foreach ($attributes as $attribute_to_check){
  2219.                 if(isset($this->_combinedAttributes[$attribute_to_check])){
  2220.                     $result[$attribute_to_check;
  2221.                 }
  2222.             }
  2223.         }
  2224.         return $result;
  2225.     }
  2226.  
  2227.     function decomposeCombinedAttributes()
  2228.     {
  2229.         if(!empty($this->_combinedAttributes)){
  2230.             $attributes array_keys($this->_combinedAttributes);
  2231.             foreach ($attributes as $attribute){
  2232.                 $this->decomposeCombinedAttribute($attribute);
  2233.             }
  2234.         }
  2235.     }
  2236.  
  2237.     function decomposeCombinedAttribute($combined_attribute$used_on_combined_fields = false)
  2238.     {
  2239.         if(isset($this->$combined_attribute&& $this->isCombinedAttribute($combined_attribute)){
  2240.             $config $this->_combinedAttributes[$combined_attribute];
  2241.             $pattern array_shift($config);
  2242.             $pattern is_array($pattern$pattern[1$pattern;
  2243.  
  2244.             if(method_exists($this$pattern)){
  2245.                 $pieces $this->{$pattern}($this->$combined_attribute);
  2246.                 if(is_array($pieces)){
  2247.                     foreach ($pieces as $k=>$v){
  2248.                         $is_combined $this->isCombinedAttribute($k);
  2249.                         if($is_combined){
  2250.                             $this->decomposeCombinedAttribute($k);
  2251.                         }
  2252.                         $this->setAttribute($k$vtrue!$is_combined);
  2253.                     }
  2254.                     if($is_combined && !$used_on_combined_fields){
  2255.                         $combined_attributes_contained_on_this_attribute $this->getCombinedSubattributes($combined_attribute);
  2256.                         if(count($combined_attributes_contained_on_this_attribute)){
  2257.                             $this->decomposeCombinedAttribute($combined_attributetrue);
  2258.                         }
  2259.                     }
  2260.                 }
  2261.             }else{
  2262.                 $got sscanf($this->$combined_attribute$pattern);
  2263.                 for ($x=0$x<count($got)$x++){
  2264.                     $attribute $config[$x];
  2265.                     $is_combined $this->isCombinedAttribute($attribute);
  2266.                     if($is_combined){
  2267.                         $this->decomposeCombinedAttribute($attribute);
  2268.                     }
  2269.                     $this->setAttribute($attribute$got[$x]true!$is_combined);
  2270.                 }
  2271.             }
  2272.         }
  2273.     }
  2274.  
  2275.     function getAvailableCombinedAttributes()
  2276.     {
  2277.         $combined_attributes = array();
  2278.         foreach ($this->_combinedAttributes as $attribute=>$details){
  2279.             $combined_attributes[$attribute= array('name'=>$attribute'type'=>'string''path' => array_shift($details)'uses'=>$details);
  2280.         }
  2281.         return !empty($this->_combinedAttributes&& is_array($this->_combinedAttributes$combined_attributes : array();
  2282.     }
  2283.  
  2284.     /*/Combined attributes*/
  2285.  
  2286.  
  2287.  
  2288.  
  2289.     /**
  2290.                          Database connection
  2291.     ====================================================================
  2292.     */
  2293.     /**
  2294.     * Establishes the connection to the database. Accepts either a profile name specified in config/config.php or
  2295.     * an array as input where the 'type' key must be specified with the name of a database adapter (in lower-case) 
  2296.     * example for regular databases (MySQL, Postgresql, etc):
  2297.     * 
  2298.     *   $AkActiveRecord->establishConnection('development');
  2299.     *   $AkActiveRecord->establishConnection('super_user');
  2300.     * 
  2301.     *   $AkActiveRecord->establishConnection(
  2302.     *       array(
  2303.     *       'type'  => "mysql",
  2304.     *       'host'     => "localhost",
  2305.     *       'username' => "myuser",
  2306.     *       'password' => "mypass",
  2307.     *       'database' => "somedatabase"
  2308.     *       ));
  2309.     *
  2310.     *    Example for SQLite database:
  2311.     *
  2312.     *     $AkActiveRecord->establishConnection(
  2313.     *       array(
  2314.     *       'type' => "sqlite",
  2315.     *       'dbfile'  => "path/to/dbfile"
  2316.     *       )
  2317.     *     )
  2318.     */
  2319.     function &establishConnection($specification_or_profile = AK_DEFAULT_DATABASE_PROFILE)
  2320.     {
  2321.         $adapter =AkDbAdapter::getInstance($specification_or_profile);
  2322.         return $this->setConnection(&$adapter);
  2323.     }
  2324.  
  2325.  
  2326.     /**
  2327.     * Returns true if a connection that's accessible to this class have already been opened.
  2328.     */ 
  2329.     function isConnected()
  2330.     {
  2331.         return isset($this->_db);
  2332.     }
  2333.  
  2334.     /**
  2335.     * Returns the connection currently associated with the class. This can also be used to 
  2336.     * "borrow" the connection to do database work unrelated to any of the specific Active Records.
  2337.     */
  2338.     function &getConnection()
  2339.     {
  2340.         return $this->_db;
  2341.     }
  2342.  
  2343.     /**
  2344.     * Sets the connection for the class.
  2345.     */
  2346.     function &setConnection($db_adapter = null)
  2347.     {
  2348.         if (is_null($db_adapter)){
  2349.             $db_adapter =AkDbAdapter::getInstance();
  2350.         }
  2351.         return $this->_db =$db_adapter;
  2352.     }
  2353.  
  2354.     /**
  2355.     * @access private
  2356.     */
  2357.     function _getDatabaseType()
  2358.     {
  2359.         return $this->_db->type();
  2360.     }
  2361.     /*/Database connection*/
  2362.  
  2363.  
  2364.     /**
  2365.                            Table Settings
  2366.     ====================================================================
  2367.     See also: Database Reflection.
  2368.     */
  2369.  
  2370.     /**
  2371.     * Defines the primary key field ? can be overridden in subclasses.
  2372.     */
  2373.     function setPrimaryKey($primary_key 'id')
  2374.     {
  2375.         if(!$this->hasColumn($primary_key)){
  2376.             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);
  2377.         }else {
  2378.             $this->_primaryKey = $primary_key;
  2379.         }
  2380.     }
  2381.  
  2382.  
  2383.     function getTableName($modify_for_associations = true)
  2384.     {
  2385.         if(!isset($this->_tableName)){
  2386.             // We check if we are on a inheritance Table Model
  2387.             $this->getClassForDatabaseTableMapping();
  2388.             if(!isset($this->_tableName)){
  2389.                 $this->setTableName();
  2390.             }
  2391.         }
  2392.  
  2393.         if($modify_for_associations && isset($this->_associationTablePrefixes[$this->_tableName])){
  2394.             return $this->_associationTablePrefixes[$this->_tableName];
  2395.         }
  2396.  
  2397.         return $this->_tableName;
  2398.     }
  2399.  
  2400.     function setTableName($table_name = null$check_for_existence = AK_ACTIVE_RECORD_VALIDATE_TABLE_NAMES$check_mode = false)
  2401.     {
  2402.         static $available_tables;
  2403.         if(empty($table_name)){
  2404.             $table_name AkInflector::tableize($this->getModelName());
  2405.         }
  2406.         if($check_for_existence){
  2407.             if(!isset($available_tables|| $check_mode){
  2408.                 if(!isset($this->_db)){
  2409.                     $this->setConnection();
  2410.                 }
  2411.                 if(empty($_SESSION['__activeRecordColumnsSettingsCache']['available_tables']||
  2412.                 !AK_ACTIVE_RECORD_ENABLE_PERSISTENCE){
  2413.                     $_SESSION['__activeRecordColumnsSettingsCache']['available_tables'$this->_db->availableTables();
  2414.                 }
  2415.                 $available_tables $_SESSION['__activeRecordColumnsSettingsCache']['available_tables'];
  2416.             }
  2417.             if(!in_array($table_name,(array)$available_tables)){
  2418.                 if(!$check_mode){
  2419.                     trigger_error(Ak::t('Unable to set "%table_name" table for the model "%model".'.
  2420.                     '  There is no "%table_name" available into current database layout.'.
  2421.                     ' Set AK_ACTIVE_RECORD_VALIDATE_TABLE_NAMES constant to false in order to'.
  2422.                     ' avoid table name validation',array('%table_name'=>$table_name,'%model'=>$this->getModelName())),E_USER_WARNING);
  2423.                 }
  2424.                 return false;
  2425.             }
  2426.         }
  2427.         $this->_tableName = $table_name;
  2428.         return true;
  2429.     }
  2430.  
  2431.  
  2432.     function getOnlyAvailableAttributes($attributes)
  2433.     {
  2434.         $table_name $this->getTableName();
  2435.         $ret_attributes = array();
  2436.         if(!empty($attributes&& is_array($attributes)){
  2437.             $available_attributes $this->getAvailableAttributes();
  2438.  
  2439.             $keys array_keys($attributes);
  2440.             $size = sizeOf($keys);
  2441.             for ($i=0$i $size$i++){
  2442.                 $k str_replace($table_name.'.','',$keys[$i]);
  2443.                 if(isset($available_attributes[$k]['name'][$k])){
  2444.                     $ret_attributes[$k=$attributes[$keys[$i]];
  2445.                 }
  2446.             }
  2447.         }
  2448.         return $ret_attributes;
  2449.     }
  2450.  
  2451.     function getColumnsForAttributes($attributes)
  2452.     {
  2453.         $ret_attributes = array();
  2454.         $table_name $this->getTableName();
  2455.         if(!empty($attributes&& is_array($attributes)){
  2456.             $columns $this->getColumns();
  2457.             foreach ($attributes as $k=>$v){
  2458.                 $k str_replace($table_name.'.','',$k);
  2459.                 if(isset($columns[$k]['name'][$k])){
  2460.                     $ret_attributes[$k$v;
  2461.                 }
  2462.             }
  2463.         }
  2464.         return $ret_attributes;
  2465.     }
  2466.  
  2467.     /**
  2468.     * Returns true if given attribute exists for this Model.
  2469.     *
  2470.     * @param string $name Name of table to look in
  2471.     * @return boolean
  2472.     */
  2473.     function hasColumn($column)
  2474.     {
  2475.         empty($this->_columns$this->getColumns($this->_columns;
  2476.         return isset($this->_columns[$column]);
  2477.     }
  2478.  
  2479.  
  2480.     /*/Table Settings*/
  2481.  
  2482.     /**
  2483.                            Database Reflection
  2484.     ====================================================================
  2485.     See also: Table Settings, Type Casting.
  2486.     */
  2487.  
  2488.  
  2489.     /**
  2490.     * Initializes the attributes array with keys matching the columns from the linked table and
  2491.     * the values matching the corresponding default value of that column, so
  2492.     * that a new instance, or one populated from a passed-in array, still has all the attributes
  2493.     * that instances loaded from the database would.
  2494.     */
  2495.     function attributesFromColumnDefinition()
  2496.     {
  2497.         $attributes = array();
  2498.  
  2499.         foreach ((array)$this->getColumns(as $column_name=>$column_settings){
  2500.             if (!isset($column_settings['primaryKey']&& isset($column_settings['hasDefault'])) {
  2501.                 $attributes[$column_name$this->_extractValueFromDefault($column_settings['defaultValue']);
  2502.             else {
  2503.                 $attributes[$column_name= null;
  2504.             }
  2505.         }
  2506.         return $attributes;
  2507.     }
  2508.  
  2509.     /**
  2510.      * Gets information from the database engine about a single table
  2511.      *
  2512.      * @access private
  2513.      */
  2514.     function _databaseTableInternals($table)
  2515.     {
  2516.         if(empty($_SESSION['__activeRecordColumnsSettingsCache']['database_table_'.$table.'_internals']|| !AK_ACTIVE_RECORD_ENABLE_PERSISTENCE){
  2517.             $_SESSION['__activeRecordColumnsSettingsCache']['database_table_'.$table.'_internals'$this->_db->getColumnDetails($table);
  2518.         }
  2519.         $cache[$table$_SESSION['__activeRecordColumnsSettingsCache']['database_table_'.$table.'_internals'];
  2520.  
  2521.         return $cache[$table];
  2522.     }
  2523.  
  2524.     function getColumnsWithRegexBoundaries()
  2525.     {
  2526.         $columns array_keys($this->getColumns());
  2527.         foreach ($columns as $k=>$column){
  2528.             $columns[$k'/([^\.])\b('.$column.')\b/';
  2529.         }
  2530.         return $columns;
  2531.     }
  2532.  
  2533.  
  2534.     /**
  2535.     * If is the first time we use a model this function will run the installer for the model if it exists
  2536.     *
  2537.     * @access private
  2538.     */
  2539.     function _runCurrentModelInstallerIfExists(&$column_objects)
  2540.     {
  2541.         static $installed_models = array();
  2542.         if(!defined('AK_AVOID_AUTOMATIC_ACTIVE_RECORD_INSTALLERS'&& !in_array($this->getModelName()$installed_models)){
  2543.             $installed_models[$this->getModelName();
  2544.             require_once(AK_LIB_DIR.DS.'AkInstaller.php');
  2545.             $installer_name $this->getModelName().'Installer';
  2546.             $installer_file AK_APP_DIR.DS.'installers'.DS.AkInflector::underscore($installer_name).'.php';
  2547.             if(file_exists($installer_file)){
  2548.                 require_once($installer_file);
  2549.                 if(class_exists($installer_name)){
  2550.                     $Installer = new $installer_name();
  2551.                     if(method_exists($Installer,'install')){
  2552.                         $Installer->install();
  2553.                         $column_objects $this->_databaseTableInternals($this->getTableName());
  2554.                         return !empty($column_objects);
  2555.                     }
  2556.                 }
  2557.             }
  2558.         }
  2559.         return false;
  2560.     }
  2561.  
  2562.  
  2563.     /**
  2564.     * Returns an array of column objects for the table associated with this class.
  2565.     */
  2566.     function getColumns($force_reload = false)
  2567.     {
  2568.         if(empty($this->_columns|| $force_reload){
  2569.             $this->_columns = $this->getColumnSettings($force_reload);
  2570.         }
  2571.  
  2572.         return (array)$this->_columns;
  2573.     }
  2574.  
  2575.     function getColumnSettings($force_reload = false)
  2576.     {
  2577.         if(empty($this->_columnsSettings|| $force_reload){
  2578.             $this->loadColumnsSettings($force_reload);
  2579.             $this->initiateColumnsToNull();
  2580.         }
  2581.         return isset($this->_columnsSettings$this->_columnsSettings : array();
  2582.     }
  2583.  
  2584.     function loadColumnsSettings($force_reload = false)
  2585.     {
  2586.         if(is_null($this->_db)){
  2587.             $this->setConnection();
  2588.         }
  2589.         $this->_columnsSettings = $force_reload ? null : $this->_getPersistedTableColumnSettings();
  2590.  
  2591.         if(empty($this->_columnsSettings|| !AK_ACTIVE_RECORD_ENABLE_PERSISTENCE){
  2592.             if(empty($this->_dataDictionary)){
  2593.                 $this->_dataDictionary =$this->_db->getDictionary();
  2594.             }
  2595.  
  2596.             $column_objects $this->_databaseTableInternals($this->getTableName());
  2597.  
  2598.             if!isset($this->_avoidTableNameValidation&&
  2599.             !is_array($column_objects&&
  2600.             !$this->_runCurrentModelInstallerIfExists($column_objects)){
  2601.                 trigger_error(Ak::t('Ooops! Could not fetch details for the table %table_name.'array('%table_name'=>$this->getTableName()))E_USER_ERROR);
  2602.                 return false;
  2603.             }elseif (empty($column_objects)){
  2604.                 $this->_runCurrentModelInstallerIfExists($column_objects);
  2605.             }
  2606.             if(is_array($column_objects)){
  2607.                 foreach (array_keys($column_objectsas $k){
  2608.                     $this->setColumnSettings($column_objects[$k]->name$column_objects[$k]);
  2609.                 }
  2610.             }
  2611.             if(!empty($this->_columnsSettings)){
  2612.                 $this->_persistTableColumnSettings();
  2613.             }
  2614.         }
  2615.         return isset($this->_columnsSettings$this->_columnsSettings : array();
  2616.     }
  2617.  
  2618.  
  2619.  
  2620.     function setColumnSettings($column_name$column_object)
  2621.     {
  2622.         $this->_columnsSettings[$column_name= array();
  2623.         $this->_columnsSettings[$column_name]['name'$column_object->name;
  2624.  
  2625.         if($this->_internationalize && $this->_isInternationalizeCandidate($column_object->name)){
  2626.             $this->_addInternationalizedColumn($column_object->name);
  2627.         }
  2628.  
  2629.         $this->_columnsSettings[$column_name]['type'$this->getAkelosDataType($column_object);
  2630.         if(!empty($column_object->primary_key)){
  2631.             $this->_primaryKey = empty($this->_primaryKey$column_object->name : $this->_primaryKey;
  2632.             $this->_columnsSettings[$column_name]['primaryKey'= true;
  2633.         }
  2634.         if(!empty($column_object->auto_increment)){
  2635.             $this->_columnsSettings[$column_name]['autoIncrement'= true;
  2636.         }
  2637.         if(!empty($column_object->has_default)){
  2638.             $this->_columnsSettings[$column_name]['hasDefault'= true;
  2639.         }
  2640.         if(!empty($column_object->not_null)){
  2641.             $this->_columnsSettings[$column_name]['notNull'= true;
  2642.         }
  2643.         if(!empty($column_object->max_length&& $column_object->max_length > 0){
  2644.             $this->_columnsSettings[$column_name]['maxLength'$column_object->max_length;
  2645.         }
  2646.         if(!empty($column_object->scale&& $column_object->scale > 0){
  2647.             $this->_columnsSettings[$column_name]['scale'$column_object->scale;
  2648.         }
  2649.         if(isset($column_object->default_value)){
  2650.             $this->_columnsSettings[$column_name]['defaultValue'$column_object->default_value;
  2651.         }
  2652.     }
  2653.  
  2654.  
  2655.     /**
  2656.     * Resets all the cached information about columns, which will cause they to be reloaded on the next request.
  2657.     */
  2658.     function resetColumnInformation()
  2659.     {
  2660.         if(isset($_SESSION['__activeRecordColumnsSettingsCache'][$this->getModelName()])){
  2661.             unset($_SESSION['__activeRecordColumnsSettingsCache'][$this->getModelName()]);
  2662.         }
  2663.         $this->_clearPersitedColumnSettings();
  2664.         $this->_columnNames = $this->_columns = $this->_columnsSettings = $this->_contentColumns = array();
  2665.     }
  2666.  
  2667.     /**
  2668.     * @access private
  2669.     */
  2670.     function _getColumnsSettings()
  2671.     {
  2672.         return $_SESSION['__activeRecordColumnsSettingsCache'];
  2673.     }
  2674.  
  2675.     /**
  2676.     * @access private
  2677.     */
  2678.     function _getModelColumnSettings()
  2679.     {
  2680.         return $_SESSION['__activeRecordColumnsSettingsCache'][$this->getModelName()];
  2681.     }
  2682.  
  2683.     /**
  2684.     * @access private
  2685.     */
  2686.     function _persistTableColumnSettings()
  2687.     {
  2688.         $_SESSION['__activeRecordColumnsSettingsCache'][$this->getModelName().'_column_settings'$this->_columnsSettings;
  2689.     }
  2690.  
  2691.     /**
  2692.     * @access private
  2693.     */
  2694.     function _getPersistedTableColumnSettings()
  2695.     {
  2696.         $model_name $this->getModelName();
  2697.         if(AK_ACTIVE_RECORD_CACHE_DATABASE_SCHEMA && !isset($_SESSION['__activeRecordColumnsSettingsCache']&& AK_CACHE_HANDLER > 0){
  2698.             $this->_loadPersistedColumnSetings();
  2699.         }
  2700.         return isset($_SESSION['__activeRecordColumnsSettingsCache'][$model_name.'_column_settings']?
  2701.         $_SESSION['__activeRecordColumnsSettingsCache'][$model_name.'_column_settings': false;
  2702.     }
  2703.  
  2704.     /**
  2705.     * @access private
  2706.     */
  2707.     function _clearPersitedColumnSettings()
  2708.     {
  2709.             $Cache =Ak::cache();
  2710.             $Cache->init(AK_ACTIVE_RECORD_CACHE_DATABASE_SCHEMA_LIFE);
  2711.             $Cache->clean('AkActiveRecord');
  2712.         }
  2713.     }
  2714.  
  2715.     /**
  2716.     * @access private
  2717.     */
  2718.     function _savePersitedColumnSettings()
  2719.     {
  2720.         if(isset($_SESSION['__activeRecordColumnsSettingsCache'])){
  2721.             $Cache =Ak::cache();
  2722.             $Cache->init(AK_ACTIVE_RECORD_CACHE_DATABASE_SCHEMA_LIFE);
  2723.             $Cache->save(serialize($_SESSION['__activeRecordColumnsSettingsCache'])'active_record_db_cache''AkActiveRecord');
  2724.         }
  2725.     }
  2726.  
  2727.     /**
  2728.     * @access private
  2729.     */
  2730.     function _loadPersistedColumnSetings()
  2731.     {
  2732.         if(!isset($_SESSION['__activeRecordColumnsSettingsCache'])){
  2733.             $Cache =Ak::cache();
  2734.             $Cache->init(AK_ACTIVE_RECORD_CACHE_DATABASE_SCHEMA_LIFE);
  2735.             if($serialized_column_settings $Cache->get('active_record_db_cache''AkActiveRecord'&& !empty($serialized_column_settings)){
  2736.                 $_SESSION['__activeRecordColumnsSettingsCache'@unserialize($serialized_column_settings);
  2737.  
  2738.             }elseif(AK_ACTIVE_RECORD_CACHE_DATABASE_SCHEMA){
  2739.                 register_shutdown_function(array($this,'_savePersitedColumnSettings'));
  2740.             }
  2741.         }else{
  2742.             $_SESSION['__activeRecordColumnsSettingsCache'= array();
  2743.         }
  2744.     }
  2745.  
  2746.  
  2747.     function initiateAttributeToNull($attribute)
  2748.     {
  2749.         if(!isset($this->$attribute)){
  2750.             $this->$attribute = null;
  2751.         }
  2752.     }
  2753.  
  2754.     function initiateColumnsToNull()
  2755.     {
  2756.         if(isset($this->_columnsSettings&& is_array($this->_columnsSettings)){
  2757.             array_map(array(&$this,'initiateAttributeToNull'),array_keys($this->_columnsSettings));
  2758.         }
  2759.     }
  2760.  
  2761.  
  2762.     /**
  2763.     * Akelos data types are mapped to phpAdodb data types
  2764.     *
  2765.     * Returns the Akelos data type for an Adodb Column Object
  2766.     *
  2767.     * 'C'=>'string', // Varchar, capped to 255 characters.
  2768.     * 'X' => 'text' // Larger varchar, capped to 4000 characters (to be compatible with Oracle). 
  2769.     * 'XL' => 'text' // For Oracle, returns CLOB, otherwise the largest varchar size.
  2770.     * 
  2771.     * 'C2' => 'string', // Multibyte varchar
  2772.     * 'X2' => 'string', // Multibyte varchar (largest size)
  2773.     * 
  2774.     * 'B' => 'binary', // BLOB (binary large object)
  2775.     * 
  2776.     * 'D' => array('date', 'datetime'), //  Date (some databases do not support this, and we return a datetime type)
  2777.     * 'T' =>  array('datetime', 'timestamp'), //Datetime or Timestamp
  2778.     * 'L' => 'boolean', // Integer field suitable for storing booleans (0 or 1)
  2779.     * 'I' => // Integer (mapped to I4)
  2780.     * 'I1' => 'integer', // 1-byte integer
  2781.     * 'I2' => 'integer', // 2-byte integer
  2782.     * 'I4' => 'integer', // 4-byte integer
  2783.     * 'I8' => 'integer', // 8-byte integer
  2784.     * 'F' => 'float', // Floating point number
  2785.     * 'N' => 'integer' //  Numeric or decimal number
  2786.     *
  2787.     * @return string One of this 'string','text','integer','float','datetime','timestamp',
  2788.     * 'time', 'name','date', 'binary', 'boolean'
  2789.     */
  2790.     function getAkelosDataType(&$adodb_column_object)
  2791.     {
  2792.         $config_var_name AkInflector::variablize($adodb_column_object->name.'_data_type');
  2793.         if(!empty($this->{$config_var_name})){
  2794.             return $this->{$config_var_name};
  2795.         }
  2796.         if(stristr($adodb_column_object->type'BLOB')){
  2797.             return 'binary';
  2798.         }
  2799.         if(!empty($adodb_column_object->auto_increment)) {
  2800.             return 'serial';
  2801.         }
  2802.         $meta_type $this->_dataDictionary->MetaType($adodb_column_object);
  2803.         $adodb_data_types = array(
  2804.         'C'=>'string'// Varchar, capped to 255 characters.
  2805.         'X' => 'text'// Larger varchar, capped to 4000 characters (to be compatible with Oracle).
  2806.         'XL' => 'text'// For Oracle, returns CLOB, otherwise the largest varchar size.
  2807.  
  2808.         'C2' => 'string'// Multibyte varchar
  2809.         'X2' => 'string'// Multibyte varchar (largest size)
  2810.  
  2811.         'B' => 'binary'// BLOB (binary large object)
  2812.  
  2813.         'D' => array('date')//  Date
  2814.         'T' =>  array('datetime''timestamp')//Datetime or Timestamp
  2815.         'L' => 'boolean'// Integer field suitable for storing booleans (0 or 1)
  2816.         'R' => 'serial'// Serial Integer
  2817.         'I' => 'integer'// Integer (mapped to I4)
  2818.         'I1' => 'integer'// 1-byte integer
  2819.         'I2' => 'integer'// 2-byte integer
  2820.         'I4' => 'integer'// 4-byte integer
  2821.         'I8' => 'integer'// 8-byte integer
  2822.         'F' => 'float'// Floating point number
  2823.         'N' => 'decimal' //  Numeric or decimal number
  2824.         );
  2825.  
  2826.         $result !isset($adodb_data_types[$meta_type]?
  2827.         'string' :
  2828.         (is_array($adodb_data_types[$meta_type]$adodb_data_types[$meta_type][0$adodb_data_types[$meta_type]);
  2829.  
  2830.         if($result == 'text'){
  2831.             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 )){
  2832.                 return 'string';
  2833.             }
  2834.         }
  2835.  
  2836.         if($this->_getDatabaseType(== 'mysql'){
  2837.             if($result == 'integer' && stristr($adodb_column_object->type'TINYINT')){
  2838.                 return 'boolean';
  2839.             }
  2840.         }elseif($this->_getDatabaseType(== 'postgre'){
  2841.             if($adodb_column_object->type == 'timestamp' || $result == 'datetime'){
  2842.                 $adodb_column_object->max_length = 19;
  2843.             }
  2844.         }elseif($this->_getDatabaseType(== 'sqlite'){
  2845.             if($result == 'integer' && (int)$adodb_column_object->max_length === 1 && stristr($adodb_column_object->type'TINYINT')){
  2846.                 return 'boolean';
  2847.             }elseif($result == 'integer' && stristr($adodb_column_object->type'DOUBLE')){
  2848.                 return 'float';
  2849.             }
  2850.         }
  2851.  
  2852.         if($result == 'datetime' && substr($adodb_column_object->name,-3== '_on'){
  2853.             $result 'date';
  2854.         }
  2855.  
  2856.         return $result;
  2857.     }
  2858.  
  2859.  
  2860.     /**
  2861.      * This method retrieves current class name that will be used to map 
  2862.      * your database to this object.
  2863.      */
  2864.     {
  2865.         $class_name get_class($this);
  2866.         if(is_subclass_of($this,'akactiverecord'|| is_subclass_of($this,'AkActiveRecord')){
  2867.             $parent_class get_parent_class($this);
  2868.             while (substr(strtolower($parent_class),-12!= 'activerecord'){
  2869.                 $class_name $parent_class;
  2870.                 $parent_class get_parent_class($parent_class);
  2871.             }
  2872.         }
  2873.  
  2874.         $class_name $this->_getModelName($class_name);
  2875.         // This is an Active Record Inheritance so we set current table to parent table.
  2876.         if(!empty($class_name&& strtolower($class_name!= 'activerecord'){
  2877.             $this->_inheritanceClassName = $class_name;
  2878.             @$this->setTableName(AkInflector::tableize($class_name)false);
  2879.         }
  2880.  
  2881.         return $class_name;
  2882.     }
  2883.  
  2884.     function getDisplayField()
  2885.     {
  2886.         return  empty($this->displayField&& $this->hasAttribute('name''name' (isset($this->displayField&& $this->hasAttribute($this->displayField$this->displayField : $this->getPrimaryKey());
  2887.     }
  2888.  
  2889.     function setDisplayField($attribute_name)
  2890.     {
  2891.         if($this->hasAttribute($attribute_name)){
  2892.             $this->displayField = $attribute_name;
  2893.             return true;
  2894.         }else {
  2895.             return false;
  2896.         }
  2897.     }
  2898.  
  2899.  
  2900.  
  2901.  
  2902.     /*/Database Reflection*/
  2903.  
  2904.     /**
  2905.                                Localization
  2906.     ====================================================================
  2907.     */
  2908.  
  2909.     function t($string$array = null)
  2910.     {
  2911.         return Ak::t($string$arrayAkInflector::underscore($this->getModelName()));
  2912.     }
  2913.  
  2914.     function getInternationalizedColumns()
  2915.     {
  2916.         static $cache;
  2917.         $model $this->getModelName();
  2918.         $available_locales $this->getAvailableLocales();
  2919.         if(empty($cache[$model])){
  2920.             $cache[$model= array();
  2921.             foreach ($this->getColumnSettings(as $column_name=>$details){
  2922.                 if(!empty($details['i18n'])){
  2923.                     $_tmp_pos strpos($column_name,'_');
  2924.                     $column substr($column_name,$_tmp_pos+1);
  2925.                     $lang substr($column_name,0,$_tmp_pos);
  2926.                     if(in_array($lang$available_locales)){
  2927.                         $cache[$model][$column= empty($cache[$model][$column]? array($lang:
  2928.                         array_merge($cache[$model][$column,array($lang));
  2929.                     }
  2930.                 }
  2931.             }
  2932.         }
  2933.  
  2934.         return $cache[$model];
  2935.     }
  2936.  
  2937.     function getAvailableLocales()
  2938.     {
  2939.         static $available_locales;
  2940.         if(empty($available_locales)){
  2941.             if(defined('AK_ACTIVE_RECORD_DEFAULT_LOCALES')){
  2942.                 $available_locales Ak::stringToArray(AK_ACTIVE_RECORD_DEFAULT_LOCALES);
  2943.             }else{
  2944.                 $available_locales =  Ak::langs();
  2945.             }
  2946.         }
  2947.         return $available_locales;
  2948.     }
  2949.  
  2950.     function getCurrentLocale()
  2951.     {
  2952.         static $current_locale;
  2953.         if(empty($current_locale)){
  2954.             $current_locale Ak::lang();
  2955.             $available_locales $this->getAvailableLocales();
  2956.             if(!in_array($current_locale$available_locales)){
  2957.                 $current_locale array_shift($available_locales);
  2958.             }
  2959.         }
  2960.         return $current_locale;
  2961.     }
  2962.  
  2963.  
  2964.     function getAttributeByLocale($attribute$locale)
  2965.     {
  2966.         $internationalizable_columns $this->getInternationalizedColumns();
  2967.         if(!empty($internationalizable_columns[$attribute]&& is_array($internationalizable_columns[$attribute]&& in_array($locale$internationalizable_columns[$attribute])){
  2968.             return $this->getAttribute($locale.'_'.$attribute);
  2969.         }
  2970.     }
  2971.  
  2972.     function getAttributeLocales($attribute)
  2973.     {
  2974.         $attribute_locales = array();
  2975.         foreach ($this->getAvailableLocales(as $locale){
  2976.             if($this->hasColumn($locale.'_'.$attribute)){
  2977.                 $attribute_locales[$locale$this->getAttributeByLocale($attribute$locale);
  2978.             }
  2979.         }
  2980.         return $attribute_locales;
  2981.     }
  2982.  
  2983.     function setAttributeByLocale($attribute$value$locale)
  2984.     {
  2985.         $internationalizable_columns $this->getInternationalizedColumns();
  2986.  
  2987.         if($this->_isInternationalizeCandidate($locale.'_'.$attribute&& !empty($internationalizable_columns[$attribute]&& is_array($internationalizable_columns[$attribute]&& in_array($locale$internationalizable_columns[$attribute])){
  2988.             $this->setAttribute($locale.'_'.$attribute$value);
  2989.         }
  2990.  
  2991.     }
  2992.  
  2993.     function setAttributeLocales($attribute$values = array())
  2994.     {
  2995.         foreach ($values as $locale=>$value){
  2996.             $this->setAttributeByLocale($attribute$value$locale);
  2997.         }
  2998.     }
  2999.  
  3000.     /**
  3001.     * @access private
  3002.     */
  3003.     function _delocalizeAttribute($attribute)
  3004.     {
  3005.         return $this->_isInternationalizeCandidate($attributesubstr($attribute,3$attribute;
  3006.     }
  3007.  
  3008.     /**
  3009.     * @access private
  3010.     */
  3011.     function _isInternationalizeCandidate($column_name)
  3012.     {
  3013.         $pos strpos($column_name,'_');
  3014.         return $pos === 2 && in_array(substr($column_name,0,$pos),$this->getAvailableLocales());
  3015.     }
  3016.  
  3017.     /**
  3018.     * @access private
  3019.     */
  3020.     function _addInternationalizedColumn($column_name)
  3021.     {
  3022.         $this->_columnsSettings[$column_name]['i18n'= true;
  3023.     }
  3024.  
  3025.  
  3026.     /**
  3027.      * Adds an internationalized attribute to an array containing other locales for the same column name
  3028.      * 
  3029.      * Example:
  3030.      *  es_title and en_title will be available user title = array('es'=>'...', 'en' => '...')
  3031.      *
  3032.      * @access private
  3033.      */
  3034.     function _groupInternationalizedAttribute($attribute$value)
  3035.     {
  3036.         if($this->_internationalize && $this->_isInternationalizeCandidate($attribute)){
  3037.             if(!empty($this->$attribute)){
  3038.                 $_tmp_pos strpos($attribute,'_');
  3039.                 $column substr($attribute,$_tmp_pos+1);
  3040.                 $lang substr($attribute,0,$_tmp_pos);
  3041.                 $this->$column = empty($this->$column? array($this->$column;
  3042.                 if(empty($this->$column|| (!empty($this->$column&& is_array($this->$column))){
  3043.                     $this->$column = empty($this->$column? array($lang=>$valuearray_merge($this->$column,array($lang=>$value));
  3044.                 }
  3045.             }
  3046.         }
  3047.     }
  3048.  
  3049.     /*/Localization*/
  3050.  
  3051.  
  3052.  
  3053.  
  3054.     /**
  3055.                              Type Casting
  3056.     ====================================================================
  3057.     See also: Database Reflection.
  3058.     */
  3059.  
  3060.     function getAttributesBeforeTypeCast()
  3061.     {
  3062.         $attributes_array = array();
  3063.         $available_attributes $this->getAvailableAttributes();
  3064.         foreach ($available_attributes as $attribute){
  3065.             $attribute_value $this->getAttributeBeforeTypeCast($attribute['name']);
  3066.             if(!empty($attribute_value)){
  3067.                 $attributes_array[$attribute['name']] $attribute_value;
  3068.             }
  3069.         }
  3070.         return $attributes_array;
  3071.     }
  3072.  
  3073.  
  3074.     function getAttributeBeforeTypeCast($attribute)
  3075.     {
  3076.         if(isset($this->{$attribute.'_before_type_cast'})){
  3077.             return $this->{$attribute.'_before_type_cast'};
  3078.         }
  3079.         return null;
  3080.     }
  3081.  
  3082.     function quotedId()
  3083.     {
  3084.         return $this->castAttributeForDatabase($this->getPrimaryKey()$this->getId());
  3085.     }
  3086.  
  3087.     /**
  3088.     * 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.
  3089.     */
  3090.     function setSerializeAttribute($attr_name$class_name = null)
  3091.     {
  3092.         if($this->hasColumn($attr_name)){
  3093.             $this->_serializedAttributes[$attr_name$class_name;
  3094.         }
  3095.     }
  3096.  
  3097.     function getAvailableAttributesQuoted()
  3098.     {
  3099.         return $this->getAttributesQuoted($this->getAttributes());
  3100.     }
  3101.  
  3102.  
  3103.     function getAttributesQuoted($attributes_array)
  3104.     {
  3105.         $set = array();
  3106.         $attributes_array $this->getSanitizedConditionsArray($attributes_array);
  3107.         foreach (array_diff($attributes_array,array('')) as $k=>$v){
  3108.             $set[$k$k.'='.$v;
  3109.         }
  3110.  
  3111.         return $set;
  3112.     }
  3113.  
  3114.     function getColumnType($column_name)
  3115.     {
  3116.         empty($this->_columns$this->getColumns(: null;
  3117.         return empty($this->_columns[$column_name]['type']? false : $this->_columns[$column_name]['type'];
  3118.     }
  3119.  
  3120.     function getColumnScale($column_name)
  3121.     {
  3122.         empty($this->_columns$this->getColumns(: null;
  3123.         return empty($this->_columns[$column_name]['scale']? false : $this->_columns[$column_name]['scale'];
  3124.     }
  3125.  
  3126.     function castAttributeForDatabase($column_name$value$add_quotes = true)
  3127.     {
  3128.         $result '';
  3129.         switch ($this->getColumnType($column_name)) {
  3130.             case 'datetime':
  3131.                 if(!empty($value)){
  3132.                     $date_time $this->_db->quote_datetime(Ak::getTimestamp($value));
  3133.                     $result $add_quotes $date_time trim($date_time ,"'");
  3134.                 }else{
  3135.                     $result 'null';
  3136.                 }
  3137.                 break;
  3138.  
  3139.             case 'date':
  3140.                 if(!empty($value)){
  3141.                     $date $this->_db->quote_date(Ak::getTimestamp($value));
  3142.                     $result $add_quotes $date trim($date"'");
  3143.                 }else{
  3144.                     $result 'null';
  3145.                 }
  3146.                 break;
  3147.  
  3148.             case 'boolean':
  3149.                 $result is_null($value'null' (!empty($value"'1'" "'0'");
  3150.                 break;
  3151.  
  3152.             case 'binary':
  3153.                 if($this->_getDatabaseType(== 'postgre'){
  3154.                     $result =  is_null($value'null::bytea ' " '".$this->_db->escape_blob($value)."'::bytea ";
  3155.                 }else{
  3156.                     $result is_null($value'null' ($add_quotes $this->_db->quote_string($value$value);
  3157.                 }
  3158.                 break;
  3159.  
  3160.             case 'decimal':
  3161.                 if(is_null($value)){
  3162.                     $result 'null';
  3163.                 }else{
  3164.                     if($scale $this->getColumnScale($column_name)){
  3165.                         $value number_format($value$scale);
  3166.                     }
  3167.                     $result $add_quotes $this->_db->quote_string($value$value;
  3168.                 }
  3169.                 break;
  3170.  
  3171.             case 'serial':
  3172.             case 'integer':
  3173.                 $result (is_null($value|| $value==='''null' : (integer)$value;
  3174.                 break;
  3175.  
  3176.             case 'float':
  3177.                 $result (empty($value&& $value !== 0'null' (is_numeric($value$value $this->_db->quote_string($value));
  3178.                 $result !empty($this->_columns[$column_name]['notNull']&& $result == 'null' && $this->_getDatabaseType(== 'sqlite' '0' $result;
  3179.                 break;
  3180.  
  3181.             default:
  3182.                 $result is_null($value'null' ($add_quotes $this->_db->quote_string($value$value);
  3183.                 break;
  3184.         }
  3185.  
  3186.         //  !! nullable vs. not nullable !!
  3187.         return empty($this->_columns[$column_name]['notNull']($result === '' "''" $result($result === 'null' '' $result);
  3188.     }
  3189.  
  3190.     function castAttributeFromDatabase($column_name$value)
  3191.     {
  3192.         if($this->hasColumn($column_name)){
  3193.             $column_type $this->getColumnType($column_name);
  3194.  
  3195.             if($column_type){
  3196.                 if('integer' == $column_type){
  3197.                     return is_null($value? null : (integer)$value;
  3198.                     //return is_null($value) ? null : $value;    // maybe for bigint we can do this
  3199.                 }elseif('boolean' == $column_type){
  3200.                     if (is_null($value)) {
  3201.                         return null;
  3202.                     }
  3203.                     if ($this->_getDatabaseType()=='postgre'){
  3204.                         return $value=='t' ? true : false;
  3205.                     }
  3206.                     return (integer)$value === 1 ? true : false;
  3207.                 }elseif(!empty($value&& 'date' == $column_type && strstr(trim($value),' ')){
  3208.                     return substr($value,0,10== '0000-00-00' ? null : str_replace(substr($value,strpos($value,' '))''$value);
  3209.                 }elseif (!empty($value&& 'datetime' == $column_type && substr($value,0,10== '0000-00-00'){
  3210.                     return null;
  3211.                 }elseif ('binary' == $column_type && $this->_getDatabaseType(== 'postgre'){
  3212.                     $value $this->_db->unescape_blob($value);
  3213.                     $value = empty($value|| trim($value== 'null' ? null : $value;
  3214.                 }
  3215.             }
  3216.         }
  3217.         return $value;
  3218.     }
  3219.  
  3220.  
  3221.     /**
  3222.     * Joins date arguments into a single attribute. Like the array generated by the date_helper, so
  3223.     * array('published_on(1i)' => 2002, 'published_on(2i)' => 'January', 'published_on(3i)' => 24)
  3224.     * Will be converted to array('published_on'=>'2002-01-24')
  3225.     *
  3226.     * @access private
  3227.     */
  3228.     function _castDateParametersFromDateHelper_(&$params)
  3229.     {
  3230.         if(empty($params)){
  3231.             return;
  3232.         }
  3233.         $date_attributes = array();
  3234.         foreach ($params as $k=>$v{
  3235.             if(preg_match('/^([A-Za-z0-9_]+)\(([1-5]{1})i\)$/',$k,$match)){
  3236.                 $date_attributes[$match[1]][$match[2]] $v;
  3237.                 $this->$k $v;
  3238.                 unset($params[$k]);
  3239.             }
  3240.         }
  3241.         foreach ($date_attributes as $attribute=>$date){
  3242.             $params[$attributetrim(@$date[1].'-'.@$date[2].'-'.@$date[3].' '.@$date[4].':'.@$date[5].':'.@$date[6],' :-');
  3243.         }
  3244.     }
  3245.  
  3246.     /**
  3247.     * @access private
  3248.     */
  3249.     function _addBlobQueryStack($column_name$blob_value)
  3250.     {
  3251.         $this->_BlobQueryStack[$column_name$blob_value;
  3252.     }
  3253.  
  3254.     /**
  3255.     * @access private
  3256.     */
  3257.     function _updateBlobFields($condition)
  3258.     {
  3259.         if(!empty($this->_BlobQueryStack&& is_array($this->_BlobQueryStack)){
  3260.             foreach ($this->_BlobQueryStack as $column=>$value){
  3261.                 $this->_db->UpdateBlob($this->getTableName()$column$value$condition);
  3262.             }
  3263.             $this->_BlobQueryStack = null;
  3264.         }
  3265.     }
  3266.  
  3267.     /*/Type Casting*/
  3268.  
  3269.     /**
  3270.                              Optimistic Locking
  3271.     ====================================================================
  3272.     *
  3273.     * Active Records support optimistic locking if the field <tt>lock_version</tt> is present.  Each update to the
  3274.     * record increments the lock_version column and the locking facilities ensure that records instantiated twice
  3275.     * will let the last one saved return false on save() if the first was also updated. Example:
  3276.     *
  3277.     *   $p1 = new Person(1);
  3278.     *   $p2 = new Person(1);
  3279.     *
  3280.     *   $p1->first_name = "Michael";
  3281.     *   $p1->save();
  3282.     *
  3283.     *   $p2->first_name = "should fail";
  3284.     *   $p2->save(); // Returns false
  3285.     *
  3286.     * You're then responsible for dealing with the conflict by checking the return value of save(); and either rolling back, merging,
  3287.     * or otherwise apply the business logic needed to resolve the conflict.
  3288.     *
  3289.     * You must ensure that your database schema defaults the lock_version column to 0.
  3290.     *
  3291.     * This behavior can be turned off by setting <tt>AkActiveRecord::lock_optimistically = false</tt>.
  3292.     */
  3293.     function isLockingEnabled()
  3294.     {
  3295.         return (!isset($this->lock_optimistically|| $this->lock_optimistically !== false&& $this->hasColumn('lock_version');
  3296.     }
  3297.     /*/Optimistic Locking*/
  3298.  
  3299.  
  3300.     /**
  3301.                                     Callbacks
  3302.     ====================================================================
  3303.     See also: Observers.
  3304.     *
  3305.     * Callbacks are hooks into the life-cycle of an Active Record object that allows you to trigger logic
  3306.     * before or after an alteration of the object state. This can be used to make sure that associated and
  3307.     * dependent objects are deleted when destroy is called (by overwriting beforeDestroy) or to massage attributes
  3308.     * before they're validated (by overwriting beforeValidation). As an example of the callbacks initiated, consider
  3309.     * the AkActiveRecord->save() call:
  3310.     *
  3311.     * - (-) save()
  3312.     * - (-) needsValidation()
  3313.     * - (1) beforeValidation()
  3314.     * - (2) beforeValidationOnCreate() / beforeValidationOnUpdate()
  3315.     * - (-) validate()
  3316.     * - (-) validateOnCreate()
  3317.     * - (4) afterValidation()
  3318.     * - (5) afterValidationOnCreate() / afterValidationOnUpdate()
  3319.     * - (6) beforeSave()
  3320.     * - (7) beforeCreate() / beforeUpdate()
  3321.     * - (-) create()
  3322.     * - (8) afterCreate() / afterUpdate()
  3323.     * - (9) afterSave()
  3324.     * - (10) afterDestroy()
  3325.     * - (11) beforeDestroy()
  3326.     *
  3327.     *
  3328.     * That's a total of 15 callbacks, which gives you immense power to react and prepare for each state in the
  3329.     * Active Record lifecycle.
  3330.     *
  3331.     * Examples:
  3332.     *   class CreditCard extends ActiveRecord
  3333.     *   {
  3334.     *       // Strip everything but digits, so the user can specify "555 234 34" or
  3335.     *       // "5552-3434" or both will mean "55523434"
  3336.     *       function beforeValidationOnCreate
  3337.     *       {
  3338.     *           if(!empty($this->number)){
  3339.     *               $this->number = ereg_replace('[^0-9]*','',$this->number);
  3340.     *           }
  3341.     *       }
  3342.     *   }
  3343.     *
  3344.     *   class Subscription extends ActiveRecord
  3345.     *   {
  3346.     *       // Note: This is not implemented yet
  3347.     *       var $beforeCreate  = 'recordSignup';
  3348.     *
  3349.     *       function recordSignup()
  3350.     *       {
  3351.     *         $this->signed_up_on = date("Y-m-d");
  3352.     *       }
  3353.     *   }
  3354.     *
  3355.     *   class Firm extends ActiveRecord
  3356.     *   {
  3357.     *       //Destroys the associated clients and people when the firm is destroyed
  3358.     *       // Note: This is not implemented yet
  3359.     *       var $beforeDestroy = array('destroyAssociatedPeople', 'destroyAssociatedClients');
  3360.     *
  3361.     *       function destroyAssociatedPeople()
  3362.     *       {
  3363.     *           $Person = new Person();
  3364.     *           $Person->destroyAll("firm_id=>", $this->id);
  3365.     *       }
  3366.     *
  3367.     *       function destroyAssociatedClients()
  3368.     *       {
  3369.     *           $Client = new Client();
  3370.     *           $Client->destroyAll("client_of=>", $this->id);
  3371.     *       }
  3372.     *   }
  3373.     *
  3374.     *
  3375.     * == Canceling callbacks ==
  3376.     *
  3377.     * If a before* callback returns false, all the later callbacks and the associated action are cancelled. If an after* callback returns
  3378.     * false, all the later callbacks are cancelled. Callbacks are generally run in the order they are defined, with the exception of callbacks
  3379.     * defined as methods on the model, which are called last.
  3380.     *
  3381.     * Override this methods to hook Active Records
  3382.     *
  3383.     * @access public
  3384.     */
  3385.  
  3386.     function beforeCreate(){return true;}
  3387.     function beforeValidation(){return true;}
  3388.     function beforeValidationOnCreate(){return true;}
  3389.     function beforeValidationOnUpdate(){return true;}
  3390.     function beforeSave(){return true;}
  3391.     function beforeUpdate(){return true;}
  3392.     function afterUpdate(){return true;}
  3393.     function afterValidation(){return true;}
  3394.     function afterValidationOnCreate(){return true;}
  3395.     function afterValidationOnUpdate(){return true;}
  3396.     function afterCreate(){return true;}
  3397.     function afterDestroy(){return true;}
  3398.     function beforeDestroy(){return true;}
  3399.     function afterSave(){return true;}
  3400.  
  3401.     /*/Callbacks*/
  3402.  
  3403.  
  3404.     /**
  3405.                                     Transactions
  3406.     ====================================================================
  3407.     *
  3408.     * Transaction support for database operations
  3409.     *
  3410.     * Transactions are enabled automatically for Active record objects, But you can nest transactions within models.
  3411.     * This transactions are nested, and only the outermost will be executed
  3412.     *
  3413.     *   $User->transactionStart();
  3414.     *   $User->create('username'=>'Bermi');
  3415.     *   $Members->create('username'=>'Bermi');
  3416.     *
  3417.     *    if(!checkSomething()){
  3418.     *       $User->transactionFail();
  3419.     *    }
  3420.     *
  3421.     *   $User->transactionComplete();
  3422.     */
  3423.  
  3424.     function transactionStart()
  3425.     {
  3426.         return $this->_db->startTransaction();
  3427.     }
  3428.  
  3429.     function transactionComplete()
  3430.     {
  3431.         return $this->_db->stopTransaction();
  3432.     }
  3433.  
  3434.     function transactionFail()
  3435.     {
  3436.         $this->_db->failTransaction();
  3437.         return false;
  3438.     }
  3439.  
  3440.     function transactionHasFailed()
  3441.     {
  3442.         return $this->_db->hasTransactionFailed();
  3443.     }
  3444.  
  3445.     /*/Transactions*/
  3446.  
  3447.  
  3448.  
  3449.  
  3450.     /**
  3451.                                     Validators
  3452.     ====================================================================
  3453.     See also: Error Handling.
  3454.     *
  3455.     * Active Records implement validation by overwriting AkActiveRecord::validate (or the variations, validateOnCreate and
  3456.     * validateOnUpdate). Each of these methods can inspect the state of the object, which usually means ensuring
  3457.     * that a number of attributes have a certain value (such as not empty, within a given range, matching a certain regular expression).
  3458.     * 
  3459.     * Example:
  3460.     * 
  3461.     *   class Person extends ActiveRecord
  3462.     *   {
  3463.     *       function validate()
  3464.     *       {
  3465.     *           $this->addErrorOnEmpty(array('first_name', 'last_name'));
  3466.     *           if(!preg_match('/[0-9]{4,12}/', $this->phone_number)){
  3467.     *               $this->addError("phone_number", "has invalid format");
  3468.     *           }
  3469.     *       }
  3470.     * 
  3471.     *       function validateOnCreate() // is only run the first time a new object is saved
  3472.     *       {
  3473.     *           if(!isValidDiscount($this->membership_discount)){
  3474.     *               $this->addError("membership_discount", "has expired");
  3475.     *           }
  3476.     *       }
  3477.     * 
  3478.     *       function validateOnUpdate()
  3479.     *       {
  3480.     *           if($this->countChangedAttributes() == 0){
  3481.     *               $this->addErrorToBase("No changes have occurred");
  3482.     *           }
  3483.     *       }
  3484.     *   }
  3485.     * 
  3486.     *   $Person = new Person(array("first_name" => "David", "phone_number" => "what?"));
  3487.     *   $Person->save();                    // => false (and doesn't do the save);
  3488.     *   $Person->hasErrors();         // => false
  3489.     *   $Person->countErrors();          // => 2
  3490.     *   $Person->getErrorsOn("last_name");       // => "can't be empty"
  3491.     *   $Person->getErrorsOn("phone_number");    // => "has invalid format"
  3492.     *   $Person->yieldEachFullError();        // => "Last name can't be empty \n Phone number has invalid format"
  3493.     * 
  3494.     *   $Person->setAttributes(array("last_name" => "Heinemeier", "phone_number" => "555-555"));
  3495.     *   $Person->save(); // => true (and person is now saved in the database)
  3496.     * 
  3497.     * An "_errors" array is available for every Active Record.
  3498.     * 
  3499.     */
  3500.  
  3501.     /**
  3502.       * Encapsulates the pattern of wanting to validate a password or email address field with a confirmation. Example:
  3503.       * 
  3504.       *  Model:
  3505.       *     class Person extends ActiveRecord
  3506.       *     {
  3507.       *         function validate()
  3508.       *         {
  3509.       *             $this->validatesConfirmationOf('password');
  3510.       *             $this->validatesConfirmationOf('email_address', "should match confirmation");
  3511.       *         }
  3512.       *    }
  3513.       * 
  3514.       *  View:
  3515.       *    <?=$form_helper->password_field("person", "password"); ?>
  3516.       *    <?=$form_helper->password_field("person", "password_confirmation"); ?>
  3517.       * 
  3518.       * The person has to already have a password attribute (a column in the people table), but the password_confirmation is virtual.
  3519.       * It exists only as an in-memory variable for validating the password. This check is performed only if password_confirmation
  3520.       * is not null.
  3521.       * 
  3522.       */
  3523.     function validatesConfirmationOf($attribute_names$message 'confirmation')
  3524.     {
  3525.         $message = isset($this->_defaultErrorMessages[$message]$this->t($this->_defaultErrorMessages[$message]$message;
  3526.         $attribute_names Ak::toArray($attribute_names);
  3527.         foreach ($attribute_names as $attribute_name){
  3528.             $attribute_accessor $attribute_name.'_confirmation';
  3529.             if(isset($this->$attribute_accessor&& @$this->$attribute_accessor != @$this->$attribute_name){
  3530.                 $this->addError($attribute_name$message);
  3531.             }
  3532.         }
  3533.     }
  3534.  
  3535.     /**
  3536.       * Encapsulates the pattern of wanting to validate the acceptance of a terms of service check box (or similar agreement). Example:
  3537.       * 
  3538.       * class Person extends ActiveRecord
  3539.       * {
  3540.       *     function validateOnCreate()
  3541.       *     {
  3542.       *         $this->validatesAcceptanceOf('terms_of_service');
  3543.       *         $this->validatesAcceptanceOf('eula', "must be abided");
  3544.       *     }
  3545.       * }
  3546.       * 
  3547.       * The terms_of_service attribute is entirely virtual. No database column is needed. This check is performed only if
  3548.       * terms_of_service is not null.
  3549.       * 
  3550.       * 
  3551.       * @param accept 1 
  3552.       * Specifies value that is considered accepted.  The default value is a string "1", which makes it easy to relate to an HTML checkbox.
  3553.       */
  3554.     function validatesAcceptanceOf($attribute_names$message 'accepted'$accept = 1)
  3555.     {
  3556.         $message = isset($this->_defaultErrorMessages[$message]$this->t($this->_defaultErrorMessages[$message]$message;
  3557.  
  3558.         $attribute_names Ak::toArray($attribute_names);
  3559.         foreach ($attribute_names as $attribute_name){
  3560.             if(@$this->$attribute_name != $accept){
  3561.                 $this->addError($attribute_name$message);
  3562.             }
  3563.         }
  3564.     }
  3565.  
  3566.     /**
  3567.     * Validates whether the associated object or objects are all valid themselves. Works with any kind of association.
  3568.     *
  3569.     *   class Book extends ActiveRecord
  3570.     *   {
  3571.     *       var $has_many = 'pages';
  3572.     *       var $belongs_to = 'library';
  3573.     * 
  3574.     *       function validate(){
  3575.     *           $this->validatesAssociated(array('pages', 'library'));
  3576.     *       }
  3577.     *   }
  3578.     * 
  3579.     *
  3580.     * Warning: If, after the above definition, you then wrote:
  3581.     *
  3582.     *   class Page extends ActiveRecord
  3583.     *   {
  3584.     *       var $belongs_to = 'book';
  3585.     *       function validate(){
  3586.     *           $this->validatesAssociated('book');
  3587.     *       }
  3588.     *   }
  3589.     *
  3590.     * ...this would specify a circular dependency and cause infinite recursion.
  3591.     *
  3592.     * NOTE: This validation will not fail if the association hasn't been assigned. If you want to ensure that the association
  3593.     * is both present and guaranteed to be valid, you also need to use validatesPresenceOf.
  3594.     */
  3595.     function validatesAssociated($attribute_names$message 'invalid')
  3596.     {
  3597.         $message = isset($this->_defaultErrorMessages[$message]$this->t($this->_defaultErrorMessages[$message]$message;
  3598.         $attribute_names Ak::toArray($attribute_names);
  3599.         foreach ($attribute_names as $attribute_name){
  3600.             if(!empty($this->$attribute_name)){
  3601.                 if(is_array($this->$attribute_name)){
  3602.                     foreach(array_keys($this->$attribute_nameas $k){
  3603.                         if(method_exists($this->{$attribute_name}[$k],'isValid'&& !$this->{$attribute_name}[$k]->isValid()){
  3604.                             $this->addError($attribute_name$message);
  3605.                         }
  3606.                     }
  3607.                 }elseif (method_exists($this->$attribute_name,'isValid'&& !$this->$attribute_name->isValid()){
  3608.                     $this->addError($attribute_name$message);
  3609.                 }
  3610.             }
  3611.         }
  3612.     }
  3613.  
  3614.     function isBlank($value = null)
  3615.     {
  3616.         return trim((string)$value== '';
  3617.     }
  3618.  
  3619.     /**
  3620.       * Validates that the specified attributes are not blank (as defined by AkActiveRecord::isBlank()).
  3621.       */
  3622.     function validatesPresenceOf($attribute_names$message 'blank')
  3623.     {
  3624.         $message = isset($this->_defaultErrorMessages[$message]$this->t($this->_defaultErrorMessages[$message]$message;
  3625.  
  3626.         $attribute_names Ak::toArray($attribute_names);
  3627.         foreach ($attribute_names as $attribute_name){
  3628.             $this->addErrorOnBlank($attribute_name$message);
  3629.         }
  3630.     }
  3631.  
  3632.     /**
  3633.       * Validates that the specified attribute matches the length restrictions supplied. Only one option can be used at a time:
  3634.       * 
  3635.       * class Person extends ActiveRecord
  3636.       * {
  3637.       *     function validate()
  3638.       *     {
  3639.       *         $this->validatesLengthOf('first_name', array('maximum'=>30));
  3640.       *         $this->validatesLengthOf('last_name', array('maximum'=>30,'message'=> "less than %d if you don't mind"));
  3641.       *         $this->validatesLengthOf('last_name', array('within'=>array(7, 32)));
  3642.       *         $this->validatesLengthOf('last_name', array('in'=>array(6, 20), 'too_long' => "pick a shorter name", 'too_short' => "pick a longer name"));
  3643.       *         $this->validatesLengthOf('fav_bra_size', array('minimum'=>1, 'too_short'=>"please enter at least %d character"));
  3644.       *         $this->validatesLengthOf('smurf_leader', array('is'=>4, 'message'=>"papa is spelled with %d characters... don't play me."));
  3645.       *     }
  3646.       * }
  3647.       *  
  3648.       * 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.
  3649.       *
  3650.       * Configuration options:
  3651.       * <tt>minimum</tt> - The minimum size of the attribute
  3652.       * <tt>maximum</tt> - The maximum size of the attribute
  3653.       * <tt>is</tt> - The exact size of the attribute
  3654.       * <tt>within</tt> - A range specifying the minimum and maximum size of the attribute
  3655.       * <tt>in</tt> - A synonym(or alias) for :within
  3656.       * <tt>allow_null</tt> - Attribute may be null; skip validation.
  3657.       * 
  3658.       * <tt>too_long</tt> - The error message if the attribute goes over the maximum (default "is" "is too long (max is %d characters)")
  3659.       * <tt>too_short</tt> - The error message if the attribute goes under the minimum (default "is" "is too short (min is %d characters)")
  3660.       * <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)")
  3661.       * <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
  3662.       */
  3663.     function validatesLengthOf($attribute_names$options = array())
  3664.     {
  3665.         // Merge given options with defaults.
  3666.         $default_options = array(
  3667.         'too_long'     => $this->_defaultErrorMessages['too_long'],
  3668.         'too_short'     => $this->_defaultErrorMessages['too_short'],
  3669.         'wrong_length'     => $this->_defaultErrorMessages['wrong_length'],
  3670.         'allow_null' => false
  3671.         );
  3672.  
  3673.         $range_options = array();
  3674.         foreach ($options as $k=>$v){
  3675.             if(in_array($k,array('minimum','maximum','is','in','within'))){
  3676.                 $range_options[$k$v;
  3677.                 $option $k;
  3678.                 $option_value $v;
  3679.             }
  3680.         }
  3681.  
  3682.         // Ensure that one and only one range option is specified.
  3683.         switch (count($range_options)) {
  3684.             case 0:
  3685.                 trigger_error(Ak::t('Range unspecified.  Specify the "within", "maximum", "minimum, or "is" option.')E_USER_ERROR);
  3686.                 return false;
  3687.                 break;
  3688.             case 1:
  3689.                 $options array_merge($default_options$options);
  3690.                 break;
  3691.             default:
  3692.                 trigger_error(Ak::t('Too many range options specified.  Choose only one.')E_USER_ERROR);
  3693.                 return false;
  3694.                 break;
  3695.         }
  3696.  
  3697.  
  3698.         switch ($option{
  3699.             case 'within':
  3700.             case 'in':
  3701.                 if(empty($option_value|| !is_array($option_value|| count($option_value!= 2 || !is_numeric($option_value[0]|| !is_numeric($option_value[1])){
  3702.                     trigger_error(Ak::t('%option must be a Range (array(min, max))',array('%option',$option))E_USER_ERROR);
  3703.                     return false;
  3704.                 }
  3705.                 $attribute_names Ak::toArray($attribute_names);
  3706.                 
  3707.                 foreach ($attribute_names as $attribute_name){
  3708.                     if((!empty($option['allow_null']&& !isset($this->$attribute_name)) || (Ak::size($this->$attribute_name)) $option_value[0]){
  3709.                         $this->addError($attribute_namesprintf($options['too_short']$option_value[0]));
  3710.                     }elseif((!empty($option['allow_null']&& !isset($this->$attribute_name)) || (Ak::size($this->$attribute_name)) $option_value[1]){
  3711.                         $this->addError($attribute_namesprintf($options['too_long']$option_value[1]));
  3712.                     }
  3713.                 }
  3714.                 break;
  3715.  
  3716.             case 'is':
  3717.             case 'minimum':
  3718.             case 'maximum':
  3719.  
  3720.                 if(empty($option_value|| !is_numeric($option_value|| $option_value <= 0){
  3721.                     trigger_error(Ak::t('%option must be a nonnegative Integer',array('%option',$option_value))E_USER_ERROR);
  3722.                     return false;
  3723.                 }
  3724.  
  3725.                 // Declare different validations per option.
  3726.                 $validity_checks = array('is' => "=="'minimum' => ">="'maximum' => "<=");
  3727.                 $message_options = array('is' => 'wrong_length''minimum' => 'too_short''maximum' => 'too_long');
  3728.  
  3729.                 $message sprintf(!empty($options['message']$options['message'$options[$message_options[$option]],$option_value);
  3730.  
  3731.                 $attribute_names Ak::toArray($attribute_names);
  3732.                 foreach ($attribute_names as $attribute_name){
  3733.                     if((!$options['allow_null'&& !isset($this->$attribute_name)) ||
  3734.                     eval("return !(".Ak::size(@$this->$attribute_name)." {$validity_checks[$option]} $option_value);")){
  3735.                         $this->addError($attribute_name$message);
  3736.                     }
  3737.                 }
  3738.                 break;
  3739.             default:
  3740.                 break;
  3741.         }
  3742.  
  3743.         return true;
  3744.     }
  3745.  
  3746.     function validatesSizeOf($attribute_names$options = array())
  3747.     {
  3748.         return validatesLengthOf($attribute_names$options);
  3749.     }
  3750.  
  3751.     /**
  3752.     * Validates whether the value of the specified attributes are unique across the system. Useful for making sure that only one user
  3753.     * can be named "davidhh".
  3754.     *
  3755.     *  class Person extends ActiveRecord
  3756.     *   {
  3757.     *       function validate()
  3758.     *       {
  3759.     *           $this->validatesUniquenessOf('passport_number');
  3760.     *           $this->validatesUniquenessOf('user_name', array('scope' => "account_id"));
  3761.     *       }
  3762.     *   }
  3763.     *
  3764.     * It can also validate whether the value of the specified attributes are unique based on multiple scope parameters.  For example,
  3765.     * making sure that a teacher can only be on the schedule once per semester for a particular class. 
  3766.     *
  3767.     *   class TeacherSchedule extends ActiveRecord
  3768.     *   {
  3769.     *       function validate()
  3770.     *       {
  3771.     *           $this->validatesUniquenessOf('passport_number');
  3772.     *           $this->validatesUniquenessOf('teacher_id', array('scope' => array("semester_id", "class_id"));
  3773.     *       }
  3774.     *   }
  3775.     * 
  3776.     * 
  3777.     * 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
  3778.     * attribute (that maps to a column). When the record is updated, the same check is made but disregarding the record itself.
  3779.     *
  3780.     * Configuration options:
  3781.     * <tt>message</tt> - Specifies a custom error message (default is: "has already been taken")
  3782.     * <tt>scope</tt> - Ensures that the uniqueness is restricted to a condition of "scope = record.scope"
  3783.     * <tt>case_sensitive</tt> - Looks for an exact match.  Ignored by non-text columns (true by default).
  3784.     * <tt>if</tt> - Specifies a method to call or a string to evaluate to determine if the validation should
  3785.     * occur (e.g. 'if' => 'allowValidation', or 'if' => '$this->signup_step > 2').  The
  3786.     * method, or string should return or evaluate to a true or false value.
  3787.     */
  3788.     function validatesUniquenessOf($attribute_names$options = array())
  3789.     {
  3790.         $default_options = array('case_sensitive'=>true'message'=>'taken');
  3791.         $options array_merge($default_options$options);
  3792.  
  3793.         if(!empty($options['if'])){
  3794.             if(method_exists($this,$options['if'])){
  3795.                 if($this->{$options['if']}(=== false){
  3796.                     return true;
  3797.                 }
  3798.             }else {
  3799.                 eval('$__eval_result = ('.rtrim($options['if'],';').');');
  3800.                 if(empty($__eval_result)){
  3801.                     return true;
  3802.                 }
  3803.             }
  3804.         }
  3805.  
  3806.         $message = isset($this->_defaultErrorMessages[$options['message']]$this->t($this->_defaultErrorMessages[$options['message']]$options['message'];
  3807.         unset($options['message']);
  3808.  
  3809.         foreach ((array)$attribute_names as $attribute_name){
  3810.             $value = isset($this->$attribute_name$this->$attribute_name : null;
  3811.  
  3812.             if($value === null || ($options['case_sensitive'|| !$this->hasColumn($attribute_name))){
  3813.                 $condition_sql $this->getTableName().'.'.$attribute_name.' '.$this->getAttributeCondition($value);
  3814.                 $condition_params = array($value);
  3815.             }else{
  3816.                 $condition_sql 'LOWER('.$this->getTableName().'.'.$attribute_name.') '.$this->getAttributeCondition($value);
  3817.                 $condition_params = array(is_array($valuearray_map('utf8_strtolower',$value: utf8_strtolower($value));
  3818.             }
  3819.  
  3820.             if(!empty($options['scope'])){
  3821.                 foreach ((array)$options['scope'as $scope_item){
  3822.                     $scope_value $this->get($scope_item);
  3823.                     $condition_sql .= ' AND '.$this->getTableName().'.'.$scope_item.' '.$this->getAttributeCondition($scope_value);
  3824.                     $condition_params[$scope_value;
  3825.                 }
  3826.             }
  3827.  
  3828.             if(!$this->isNewRecord()){
  3829.                 $condition_sql .= ' AND '.$this->getTableName().'.'.$this->getPrimaryKey().' <> ?';
  3830.                 $condition_params[$this->getId();
  3831.             }
  3832.             array_unshift($condition_params,$condition_sql);
  3833.             if ($this->find('first'array('conditions' => $condition_params))){
  3834.                 $this->addError($attribute_name$message);
  3835.             }
  3836.         }
  3837.     }
  3838.  
  3839.  
  3840.  
  3841.     /**
  3842.     * Validates whether the value of the specified attribute is of the correct form by matching it against the regular expression
  3843.     * provided.
  3844.     *
  3845.     * <code>
  3846.     *   class Person extends ActiveRecord
  3847.     *   {
  3848.     *       function validate()
  3849.     *       {
  3850.     *           $this->validatesFormatOf('email', "/^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/");
  3851.     *       }
  3852.     *   }
  3853.     * </code>
  3854.     *
  3855.     * A regular expression must be provided or else an exception will be raised.
  3856.     *
  3857.     * There are some regular expressions bundled with the Akelos Framework. 
  3858.     * 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.
  3859.     * This are predefined perl-like regular extensions.
  3860.     *
  3861.     * * AK_NOT_EMPTY_REGULAR_EXPRESSION ---> /.+/
  3862.     * * 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
  3863.     * * AK_NUMBER_REGULAR_EXPRESSION ---> /^[0-9]+$/
  3864.     * * AK_PHONE_REGULAR_EXPRESSION ---> /^([\+]?[(]?[\+]?[ ]?[0-9]{2,3}[)]?[ ]?)?[0-9 ()\-]{4,25}$/
  3865.     * * 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}$/
  3866.     * * 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])$/
  3867.     * * AK_POST_CODE_REGULAR_EXPRESSION ---> /^[0-9A-Za-z  -]{2,7}$/
  3868.     *
  3869.     * 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.
  3870.     * 
  3871.     * Params:
  3872.     * <tt>$message</tt> - A custom error message (default is: "is invalid")
  3873.     * <tt>$regular_expression</tt> - The regular expression used to validate the format with (note: must be supplied!)
  3874.     */
  3875.     function validatesFormatOf($attribute_names$regular_expression$message 'invalid'$regex_function 'preg_match')
  3876.     {
  3877.         $message = isset($this->_defaultErrorMessages[$message]$this->t($this->_defaultErrorMessages[$message]$message;
  3878.  
  3879.         $attribute_names Ak::toArray($attribute_names);
  3880.         foreach ($attribute_names as $attribute_name){
  3881.             if(!isset($this->$attribute_name|| !$regex_function($regular_expression$this->$attribute_name)){
  3882.                 $this->addError($attribute_name$message);
  3883.             }
  3884.         }
  3885.     }
  3886.  
  3887.     /**
  3888.     * Validates whether the value of the specified attribute is available in a particular array of elements.
  3889.     *
  3890.     * class Person extends ActiveRecord
  3891.     * {
  3892.     *   function validate()
  3893.     *   {
  3894.     *       $this->validatesInclusionOf('gender', array('male', 'female'), "woah! what are you then!??!!");
  3895.     *       $this->validatesInclusionOf('age', range(0, 99));
  3896.     *   }
  3897.     *
  3898.     * Parameters:
  3899.     * <tt>$array_of_ possibilities</tt> - An array of available items
  3900.     * <tt>$message</tt> - Specifies a customer error message (default is: "is not included in the list")
  3901.     * <tt>$allow_null</tt> - If set to true, skips this validation if the attribute is null (default is: false)
  3902.     */
  3903.     function validatesInclusionOf($attribute_names$array_of_possibilities$message 'inclusion'$allow_null = false)
  3904.     {
  3905.         $message = isset($this->_defaultErrorMessages[$message]$this->t($this->_defaultErrorMessages[$message]$message;
  3906.  
  3907.         $attribute_names Ak::toArray($attribute_names);
  3908.         foreach ($attribute_names as $attribute_name){
  3909.             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 )){
  3910.                 $this->addError($attribute_name$message);
  3911.             }
  3912.         }
  3913.     }
  3914.  
  3915.     /**
  3916.     * Validates that the value of the specified attribute is not in a particular array of elements.
  3917.     *
  3918.     *   class Person extends ActiveRecord
  3919.     *   {
  3920.     *       function validate()
  3921.     *       {
  3922.     *           $this->validatesExclusionOf('username', array('admin', 'superuser'), "You don't belong here");
  3923.     *           $this->validatesExclusionOf('age', range(30,60), "This site is only for under 30 and over 60");
  3924.     *       }
  3925.     *   }
  3926.     * 
  3927.     * Parameters:
  3928.     * <tt>$array_of_possibilities</tt> - An array of items that the value shouldn't be part of
  3929.     * <tt>$message</tt> - Specifies a customer error message (default is: "is reserved")
  3930.     * <tt>$allow_null</tt> - If set to true, skips this validation if the attribute is null (default is: false)
  3931.     */
  3932.     function validatesExclusionOf($attribute_names$array_of_possibilities$message 'exclusion'$allow_null = false)
  3933.     {
  3934.         $message = isset($this->_defaultErrorMessages[$message]$this->t($this->_defaultErrorMessages[$message]$message;
  3935.  
  3936.         $attribute_names Ak::toArray($attribute_names);
  3937.         foreach ($attribute_names as $attribute_name){
  3938.  
  3939.             if($allow_null (!empty($this->$attribute_name(in_array(@$this->$attribute_name,$array_of_possibilities)) : false (isset($this->$attribute_namein_array(@$this->$attribute_name,$array_of_possibilities: true )){
  3940.                 $this->addError($attribute_name$message);
  3941.             }
  3942.         }
  3943.     }
  3944.  
  3945.  
  3946.  
  3947.  
  3948.     /**
  3949.     * Validates whether the value of the specified attribute is numeric.
  3950.     * 
  3951.     *   class Person extends ActiveRecord
  3952.     *   {
  3953.     *       function validate()
  3954.     *       {
  3955.     *           $this->validatesNumericalityOf('value');
  3956.     *       }
  3957.     *   }
  3958.     *
  3959.     * Parameters:
  3960.     * <tt>$message</tt> - A custom error message (default is: "is not a number")
  3961.     * <tt>$only_integer</tt> Specifies whether the value has to be an integer, e.g. an integral value (default is false)
  3962.     * <tt>$allow_null</tt> Skip validation if attribute is null (default is false).
  3963.     */
  3964.     function validatesNumericalityOf($attribute_names$message 'not_a_number'$only_integer = false$allow_null = false)
  3965.     {
  3966.         $message = isset($this->_defaultErrorMessages[$message]$this->t($this->_defaultErrorMessages[$message]$message;
  3967.  
  3968.         $attribute_names Ak::toArray($attribute_names);
  3969.         foreach ($attribute_names as $attribute_name){
  3970.             if(
  3971.             $allow_null ?
  3972.             (
  3973.             isset($this->$attribute_name?
  3974.             (
  3975.             $only_integer ?
  3976.             !is_integer(@$this->$attribute_name:
  3977.             !is_numeric(@$this->$attribute_name)
  3978.             :
  3979.             false
  3980.             :
  3981.             (
  3982.             isset($this->$attribute_name?
  3983.             ($only_integer !is_integer(@$this->$attribute_name!is_numeric(@$this->$attribute_name)) :
  3984.             true
  3985.             )
  3986.             )
  3987.             {
  3988.                 $this->addError($attribute_name$message);
  3989.             }
  3990.         }
  3991.     }
  3992.  
  3993.  
  3994.  
  3995.     /**
  3996.     * Returns true if no errors were added otherwise false.
  3997.     */
  3998.     function isValid()
  3999.     {
  4000.         $this->clearErrors();
  4001.         if($this->beforeValidation(&& $this->notifyObservers('beforeValidation')){
  4002.  
  4003.  
  4004.             if($this->_set_default_attribute_values_automatically){
  4005.                 //$this->_setDefaultAttributeValuesAutomatically();
  4006.             }
  4007.  
  4008.             $this->validate();
  4009.  
  4010.             if($this->_automated_validators_enabled){
  4011.                 //$this->_runAutomatedValidators();
  4012.             }
  4013.  
  4014.             $this->afterValidation();
  4015.             $this->notifyObservers('afterValidation');
  4016.  
  4017.             if ($this->isNewRecord()){
  4018.                 if($this->beforeValidationOnCreate()){
  4019.                     $this->notifyObservers('beforeValidationOnCreate');
  4020.                     $this->validateOnCreate();
  4021.                     $this->afterValidationOnCreate();
  4022.                     $this->notifyObservers('afterValidationOnCreate');
  4023.                 }
  4024.             }else{
  4025.                 if($this->beforeValidationOnUpdate()){
  4026.                     $this->notifyObservers('beforeValidationOnUpdate');
  4027.                     $this->validateOnUpdate();
  4028.                     $this->afterValidationOnUpdate();
  4029.                     $this->notifyObservers('afterValidationOnUpdate');
  4030.                 }
  4031.             }
  4032.         }
  4033.  
  4034.         return !$this->hasErrors();
  4035.     }
  4036.  
  4037.     /**
  4038.     * By default the Active Record will validate for the maximum length for database columns. You can
  4039.     * disable the automated validators by setting $this->_automated_validators_enabled to false.
  4040.     * Specific validators are (for now):
  4041.     * $this->_automated_max_length_validator = true; // true by default, but you can set it to false on your model
  4042.     * $this->_automated_not_null_validator = false; // disabled by default
  4043.     *
  4044.     * @access private
  4045.     */
  4046.     function _runAutomatedValidators()
  4047.     {
  4048.         foreach ($this->_columns as $column_name=>$column_settings){
  4049.             if($this->_automated_max_length_validator &&
  4050.             empty($column_settings['primaryKey']&&
  4051.             !empty($this->$column_name&&
  4052.             !empty($column_settings['maxLength']&& $column_settings['maxLength'> 0 &&
  4053.             strlen($this->$column_name$column_settings['maxLength']){
  4054.                 $this->addError($column_namesprintf($this->_defaultErrorMessages['too_long']$column_settings['maxLength']));
  4055.             }elseif($this->_automated_not_null_validator && empty($column_settings['primaryKey']&& !empty($column_settings['notNull']&& (!isset($this->$column_name|| is_null($this->$column_name))){
  4056.                 $this->addError($column_name,'empty');
  4057.             }
  4058.         }
  4059.     }
  4060.  
  4061.     /**
  4062.     * $this->_set_default_attribute_values_automatically = true; // This enables automated attribute setting from database definition
  4063.     *
  4064.     * @access private
  4065.     */
  4066.     function _setDefaultAttributeValuesAutomatically()
  4067.     {
  4068.         foreach ($this->_columns as $column_name=>$column_settings){
  4069.             if(empty($column_settings['primaryKey']&& isset($column_settings['hasDefault']&& $column_settings['hasDefault'&& (!isset($this->$column_name|| is_null($this->$column_name))){
  4070.                 if(empty($column_settings['defaultValue'])){
  4071.                     if($column_settings['type'== 'integer' && empty($column_settings['notNull'])){
  4072.                         $this->$column_name = 0;
  4073.                     }elseif(($column_settings['type'== 'string' || $column_settings['type'== 'text'&& empty($column_settings['notNull'])){
  4074.                         $this->$column_name '';
  4075.                     }
  4076.                 }else {
  4077.                     $this->$column_name $column_settings['defaultValue'];
  4078.                 }
  4079.             }
  4080.         }
  4081.     }
  4082.  
  4083.     /**
  4084.     * Overwrite this method for validation checks on all saves and use addError($field, $message); for invalid attributes.
  4085.     */
  4086.     function validate()
  4087.     {
  4088.     }
  4089.  
  4090.     /**
  4091.     * Overwrite this method for validation checks used only on creation.
  4092.     */
  4093.     function validateOnCreate()
  4094.     {
  4095.     }
  4096.  
  4097.     /**
  4098.     * Overwrite this method for validation checks used only on updates.
  4099.     */
  4100.     function validateOnUpdate()
  4101.     {
  4102.     }
  4103.  
  4104.     /*/Validators*/
  4105.  
  4106.  
  4107.     /**
  4108.                                   Observers
  4109.     ====================================================================
  4110.     See also: Callbacks.
  4111.     */
  4112.  
  4113.     /**
  4114.     * $state store the state of this observable object
  4115.     *
  4116.     * @access private
  4117.     */
  4118.     var $_observable_state;
  4119.  
  4120.     /**
  4121.     * @access private
  4122.     */
  4123.     function _instantiateDefaultObserver()
  4124.     {
  4125.         $default_observer_name ucfirst($this->getModelName().'Observer');
  4126.         if(class_exists($default_observer_name)){
  4127.             //$Observer =& new $default_observer_name($this);
  4128.             Ak::singleton($default_observer_name,  $this);
  4129.         }
  4130.     }
  4131.  
  4132.     /**
  4133.     * Calls the $method using the reference to each
  4134.     * registered observer.
  4135.     * @return true (this is used internally for triggering observers on default callbacks)
  4136.     */ 
  4137.     function notifyObservers ($method = null)
  4138.     {
  4139.         $observers =$this->getObservers();
  4140.         $observer_count count($observers);
  4141.  
  4142.         if(!empty($method)){
  4143.             $this->setObservableState($method);
  4144.         }
  4145.  
  4146.         $model_name $this->getModelName();
  4147.         for ($i=0$i<$observer_count$i++{
  4148.             if(in_array($model_name$observers[$i]->_observing)){
  4149.                 if(method_exists($observers[$i]$method)){
  4150.                     $observers[$i]->$method($this);
  4151.                 }else{
  4152.                     $observers[$i]->update($this->getObservableState()&$this);
  4153.                 }
  4154.             }else{
  4155.                 $observers[$i]->update($this->getObservableState()&$this);
  4156.             }
  4157.         }
  4158.         $this->setObservableState('');
  4159.  
  4160.         return true;
  4161.     }
  4162.  
  4163.  
  4164.     function setObservableState($state_message)
  4165.     {
  4166.         $this->_observable_state = $state_message;
  4167.     }
  4168.  
  4169.     function getObservableState()
  4170.     {
  4171.         return $this->_observable_state;
  4172.     }
  4173.  
  4174.     /**
  4175.     * Register the reference to an object object
  4176.     * @return void
  4177.     */ 
  4178.     function &addObserver(&$observer)
  4179.     {
  4180.         static $observers$registered_observers;
  4181.         $observer_class_name get_class($observer);
  4182.         if(!isset($registered_observers[$observer_class_name]&& func_num_args(== 1){
  4183.             $observers[=$observer;
  4184.             $registered_observers[$observer_class_namecount($observers);
  4185.         }
  4186.         return $observers;
  4187.     }
  4188.  
  4189.     /**
  4190.     * Register the reference to an object object
  4191.     * @return void
  4192.     */ 
  4193.     function &getObservers()
  4194.     {
  4195.         $observers =$this->addObserver(&$thisfalse);
  4196.         return $observers;
  4197.     }
  4198.  
  4199.     /*/Observers*/
  4200.  
  4201.  
  4202.  
  4203.  
  4204.     /**
  4205.                                     Error Handling
  4206.     ====================================================================
  4207.     See also: Validators.
  4208.     */
  4209.  
  4210.  
  4211.     /**
  4212.     * Returns the Errors array that holds all information about attribute error messages.
  4213.     */
  4214.     function getErrors()
  4215.     {
  4216.         return $this->_errors;
  4217.     }
  4218.  
  4219.     /**
  4220.     * Adds an error to the base object instead of any particular attribute. This is used
  4221.     * to report errors that doesn't tie to any specific attribute, but rather to the object
  4222.     * as a whole. These error messages doesn't get prepended with any field name when iterating
  4223.     * with yieldEachFullError, so they should be complete sentences.
  4224.     */
  4225.     function addErrorToBase($message)
  4226.     {
  4227.         $this->addError($this->getModelName()$message);
  4228.     }
  4229.  
  4230.     /**
  4231.     * Returns errors assigned to base object through addToBase according to the normal rules of getErrorsOn($attribute).
  4232.     */
  4233.     function getBaseErrors()
  4234.     {
  4235.         $errors $this->getErrors();
  4236.         return (array)@$errors[$this->getModelName()];
  4237.     }
  4238.  
  4239.  
  4240.     /**
  4241.     * Adds an error message ($message) to the ($attribute), which will be returned on a call to <tt>getErrorsOn($attribute)</tt>
  4242.     * for the same attribute and ensure that this error object returns false when asked if <tt>hasErrors</tt>. More than one
  4243.     * error can be added to the same $attribute in which case an array will be returned on a call to <tt>getErrorsOn($attribute)</tt>.
  4244.     * If no $message is supplied, "invalid" is assumed.
  4245.     */
  4246.     function addError($attribute$message 'invalid')
  4247.     {
  4248.         $message = isset($this->_defaultErrorMessages[$message]$this->t($this->_defaultErrorMessages[$message]$message;
  4249.         $this->_errors[$attribute][$message;
  4250.     }
  4251.  
  4252.     /**
  4253.     * Will add an error message to each of the attributes in $attributes that is empty.
  4254.     */
  4255.     function addErrorOnEmpty($attribute_names$message 'empty')
  4256.     {
  4257.         $message = isset($this->_defaultErrorMessages[$message]$this->t($this->_defaultErrorMessages[$message]$message;
  4258.         $attribute_names Ak::toArray($attribute_names);
  4259.         foreach ($attribute_names as $attribute){
  4260.             if(empty($this->$attribute)){
  4261.                 $this->addError($attribute$message);
  4262.             }
  4263.         }
  4264.     }
  4265.  
  4266.     /**
  4267.     * Will add an error message to each of the attributes in $attributes that is blank (using $this->isBlank).
  4268.     */
  4269.     function addErrorOnBlank($attribute_names$message 'blank')
  4270.     {
  4271.         $message = isset($this->_defaultErrorMessages[$message]$this->t($this->_defaultErrorMessages[$message]$message;
  4272.         $attribute_names Ak::toArray($attribute_names);
  4273.         foreach ($attribute_names as $attribute){
  4274.             if($this->isBlank(@$this->$attribute)){
  4275.                 $this->addError($attribute$message);
  4276.             }
  4277.         }
  4278.     }
  4279.  
  4280.     /**
  4281.     * Will add an error message to each of the attributes in $attributes that has a length outside of the passed boundary $range.
  4282.     * If the length is above the boundary, the too_long_message message will be used. If below, the too_short_message.
  4283.     */
  4284.     function addErrorOnBoundaryBreaking($attribute_names$range_begin$range_end$too_long_message 'too_long'$too_short_message 'too_short')
  4285.     {
  4286.         $too_long_message = isset($this->_defaultErrorMessages[$too_long_message]$this->_defaultErrorMessages[$too_long_message$too_long_message;
  4287.         $too_short_message = isset($this->_defaultErrorMessages[$too_short_message]$this->_defaultErrorMessages[$too_short_message$too_short_message;
  4288.  
  4289.         $attribute_names Ak::toArray($attribute_names);
  4290.         foreach ($attribute_names as $attribute){
  4291.             if(@$this->$attribute $range_begin){
  4292.                 $this->addError($attribute$too_short_message);
  4293.             }
  4294.             if(@$this->$attribute $range_end){
  4295.                 $this->addError($attribute$too_long_message);
  4296.             }
  4297.         }
  4298.  
  4299.     }
  4300.  
  4301.     function addErrorOnBoundryBreaking ($attributes$range_begin$range_end$too_long_message 'too_long'$too_short_message 'too_short')
  4302.     {
  4303.         $this->addErrorOnBoundaryBreaking($attributes$range_begin$range_end$too_long_message$too_short_message);
  4304.     }
  4305.  
  4306.     /**
  4307.     * Returns true if the specified $attribute has errors associated with it.
  4308.     */
  4309.     function isInvalid($attribute)
  4310.     {
  4311.         return $this->getErrorsOn($attribute);
  4312.     }
  4313.  
  4314.     /**
  4315.     * Returns false, if no errors are associated with the specified $attribute.
  4316.     * Returns the error message, if one error is associated with the specified $attribute.
  4317.     * Returns an array of error messages, if more than one error is associated with the specified $attribute.
  4318.     */
  4319.     function getErrorsOn($attribute)
  4320.     {
  4321.         if (empty($this->_errors[$attribute])){
  4322.             return false;
  4323.         }elseif (count($this->_errors[$attribute]== 1){
  4324.             $k array_keys($this->_errors[$attribute]);
  4325.             return $this->_errors[$attribute][$k[0]];
  4326.         }else{
  4327.             return $this->_errors[$attribute];
  4328.         }
  4329.     }
  4330.  
  4331.  
  4332.     /**
  4333.     * Yields each attribute and associated message per error added.
  4334.     */
  4335.     function yieldEachError()
  4336.     {
  4337.         foreach ($this->_errors as $errors){
  4338.             foreach ($errors as $error){
  4339.                 $this->yieldError($error);
  4340.             }
  4341.         }
  4342.     }
  4343.  
  4344.     function yieldError($message)
  4345.     {
  4346.         $messages is_array($message$message : array($message);
  4347.         foreach ($messages as $message){
  4348.             echo "<div class='error'><p>$message</p></div>\n";
  4349.         }
  4350.  
  4351.     }
  4352.  
  4353.     /**
  4354.     * Yields each full error message added. So Person->addError("first_name", "can't be empty") will be returned
  4355.     * through iteration as "First name can't be empty".
  4356.     */
  4357.     function yieldEachFullError()
  4358.     {
  4359.         $full_messages $this->getFullErrorMessages();
  4360.         foreach ($full_messages as $full_message){
  4361.             $this->yieldError($full_message);
  4362.         }
  4363.     }
  4364.  
  4365.  
  4366.     /**
  4367.     * Returns all the full error messages in an array.
  4368.     */
  4369.     function getFullErrorMessages()
  4370.     {
  4371.         $full_messages = array();
  4372.  
  4373.         foreach ($this->_errors as $attribute=>$errors){
  4374.             $full_messages[$attribute= array();
  4375.             foreach ($errors as $error){
  4376.                 $full_messages[$attribute][$this->t('%attribute_name %error'array(
  4377.                 '%attribute_name'=>AkInflector::humanize($this->_delocalizeAttribute($attribute)),
  4378.                 '%error'=>$error
  4379.                 ));
  4380.             }
  4381.         }
  4382.         return $full_messages;
  4383.     }
  4384.  
  4385.     /**
  4386.     * Returns true if no errors have been added.
  4387.     */
  4388.     function hasErrors()
  4389.     {
  4390.         return !empty($this->_errors);
  4391.     }
  4392.  
  4393.     /**
  4394.     * Removes all the errors that have been added.
  4395.     */
  4396.     function clearErrors()
  4397.     {
  4398.         $this->_errors = array();
  4399.     }
  4400.  
  4401.     /**
  4402.     * Returns the total number of errors added. Two errors added to the same attribute will be counted as such
  4403.     * with this as well.
  4404.     */
  4405.     function countErrors()
  4406.     {
  4407.         $error_count = 0;
  4408.         foreach ($this->_errors as $errors){
  4409.             $error_count count($errors)+$error_count;
  4410.         }
  4411.  
  4412.         return $error_count;
  4413.     }
  4414.  
  4415.  
  4416.     function errorsToString($print = false)
  4417.     {
  4418.         $result "\n<div id='errors'>\n<ul class='error'>\n";
  4419.         foreach ($this->getFullErrorMessages(as $error){
  4420.             $result .= is_array($error"<li class='error'>".join('</li><li class=\'error\'>',$error)."</li>\n" "<li class='error'>$error</li>\n";
  4421.         }
  4422.         $result .= "</ul>\n</div>\n";
  4423.  
  4424.         if($print){
  4425.             echo $result;
  4426.         }
  4427.         return $result;
  4428.     }
  4429.  
  4430.     /*/Error Handling*/
  4431.  
  4432.  
  4433.  
  4434.     /**
  4435.                             Act as Behaviours
  4436.     ====================================================================
  4437.     See also: Acts as List, Acts as Tree, Acts as Nested Set.
  4438.     */
  4439.  
  4440.     /**
  4441.      * actAs provides a method for extending Active Record models.
  4442.      * 
  4443.      * Example:
  4444.      * $this->actsAs('list', array('scope' => 'todo_list'));
  4445.      */
  4446.     function actsAs($behaviour$options = array())
  4447.     {
  4448.         $class_name $this->_getActAsClassName($behaviour);
  4449.         $underscored_place_holder AkInflector::underscore($behaviour);
  4450.         $camelized_place_holder AkInflector::camelize($underscored_place_holder);
  4451.  
  4452.         if($this->$underscored_place_holder =$this->_getActAsInstance($class_name$options)){
  4453.             $this->$camelized_place_holder =$this->$underscored_place_holder;
  4454.             if($this->$underscored_place_holder->init($options)){
  4455.                 $this->__ActsLikeAttributes[$underscored_place_holder$underscored_place_holder;
  4456.             }
  4457.         }
  4458.     }
  4459.  
  4460.     /**
  4461.     * @access private
  4462.     */
  4463.     function _getActAsClassName($behaviour)
  4464.     {
  4465.         $class_name AkInflector::camelize($behaviour);
  4466.         return file_exists(AK_LIB_DIR.DS.'AkActiveRecord'.DS.'AkActsAsBehaviours'.DS.'AkActsAs'.$class_name.'.php'&& !class_exists('ActsAs'.$class_name?
  4467.         'AkActsAs'.$class_name 'ActsAs'.$class_name;
  4468.     }
  4469.  
  4470.     /**
  4471.     * @access private
  4472.     */
  4473.     function &_getActAsInstance($class_name$options)
  4474.     {
  4475.         if(!class_exists($class_name)){
  4476.             if(substr($class_name,0,2== 'Ak'){
  4477.                 include_once(AK_LIB_DIR.DS.'AkActiveRecord'.DS.'AkActsAsBehaviours'.DS.$class_name.'.php');
  4478.             }else{
  4479.                 include_once(AK_APP_PLUGINS_DIR.DS.AkInflector::underscore($class_name).DS.'lib'.DS.$class_name.'.php');
  4480.             }
  4481.         }
  4482.         if(!class_exists($class_name)){
  4483.             trigger_error(Ak::t('The class %class used for handling an "act_as %class" does not exist',array('%class'=>$class_name))E_USER_ERROR);
  4484.             $false = false;
  4485.             return $false;
  4486.         }else{
  4487.             $ActAsInstance =new $class_name($this$options);
  4488.             return $ActAsInstance;
  4489.         }
  4490.     }
  4491.  
  4492.     /**
  4493.     * @access private
  4494.     */
  4495.     function _loadActAsBehaviours()
  4496.     {
  4497.         $this->act_as = !empty($this->acts_as$this->acts_as : (empty($this->act_as? false : $this->act_as);
  4498.         if(!empty($this->act_as)){
  4499.             if(is_string($this->act_as)){
  4500.                 $this->act_as = array_unique(array_diff(array_map('trim',explode(',',$this->act_as.','))array('')));
  4501.                 foreach ($this->act_as as $type){
  4502.                     $this->actsAs($type);
  4503.                 }
  4504.             }elseif (is_array($this->act_as)){
  4505.                 foreach ($this->act_as as $type=>$options){
  4506.                     $this->actsAs($type$options);
  4507.                 }
  4508.             }
  4509.         }
  4510.     }
  4511.  
  4512.     /**
  4513.     * Returns a comma separated list of possible acts like (active record, nested set, list)....
  4514.     */
  4515.     function actsLike()
  4516.     {
  4517.         $result 'active record';
  4518.         foreach ($this->__ActsLikeAttributes as $type){
  4519.             if(!empty($this->$type&& is_object($this->$type&& method_exists($this->{$type}'getType')){
  4520.                 $result .= ','.$this->{$type}->getType();
  4521.             }
  4522.         }
  4523.         return $result;
  4524.     }
  4525.  
  4526.     /*/Act as Behaviours*/
  4527.  
  4528.     /**
  4529.                             Debugging
  4530.     ====================================================================
  4531.     */
  4532.  
  4533.     
  4534.     function dbug()
  4535.     {
  4536.         if(!$this->isConnected()){
  4537.             $this->setConnection();
  4538.         }
  4539.         $this->_db->connection->debug = $this->_db->connection->debug ? false : true;
  4540.         $this->db_debug =$this->_db->connection->debug;
  4541.     }
  4542.  
  4543.     function toString($print = false)
  4544.     {
  4545.         $result '';
  4546.         if(!AK_CLI || (AK_ENVIRONMENT == 'testing' && !AK_CLI)){
  4547.             $result "<h2>Details for ".AkInflector::humanize(AkInflector::underscore($this->getModelName()))." with ".$this->getPrimaryKey()." ".$this->getId()."</h2>\n<dl>\n";
  4548.             foreach ($this->getColumnNames(as $column=>$caption){
  4549.                 $result .= "<dt>$caption</dt>\n<dd>".$this->getAttribute($column)."</dd>\n";
  4550.             }
  4551.             $result .= "</dl>\n<hr />";
  4552.             if($print){
  4553.                 echo $result;
  4554.             }
  4555.         }elseif(AK_ENVIRONMENT == 'development'){
  4556.             $result =   "\n".
  4557.             str_replace("\n"," ",var_export($this->getAttributes(),true));
  4558.             $result .= "\n";
  4559.             echo $result;
  4560.             return '';
  4561.         }elseif (AK_CLI){
  4562.             $result "\n-------\n Details for ".AkInflector::humanize(AkInflector::underscore($this->getModelName()))." with ".$this->getPrimaryKey()." ".$this->getId()." ==\n\n/==\n";
  4563.             foreach ($this->getColumnNames(as $column=>$caption){
  4564.                 $result .= "\t * $caption".$this->getAttribute($column)."\n";
  4565.             }
  4566.             $result .= "\n\n-------\n";
  4567.             if($print){
  4568.                 echo $result;
  4569.             }
  4570.         }
  4571.         return $result;
  4572.     }
  4573.  
  4574.     function dbugging($trace_this_on_debug_mode = null)
  4575.     {
  4576.         if(!empty($this->_db->debug&& !empty($trace_this_on_debug_mode)){
  4577.             $message !is_scalar($trace_this_on_debug_modevar_export($trace_this_on_debug_modetrue: (string)$trace_this_on_debug_mode;
  4578.             Ak::trace($message);
  4579.         }
  4580.         return !empty($this->_db->debug);
  4581.     }
  4582.  
  4583.  
  4584.  
  4585.     function debug ($data 'active_record_class'$_functions=0)
  4586.     {
  4587.         if(!AK_DEBUG && !AK_DEV_MODE){
  4588.             return;
  4589.         }
  4590.  
  4591.         $data $data == 'active_record_class' ?  (AK_PHP5 ? clone($this$this$data;
  4592.  
  4593.         if($_functions!=0{
  4594.             $sf=1;
  4595.         else {
  4596.             $sf=0 ;
  4597.         }
  4598.  
  4599.         if (isset ($data)) {
  4600.             if (is_array($data|| is_object($data)) {
  4601.  
  4602.                 if (count ($data)) {
  4603.                     echo AK_CLI ? "/--\n" "<ol>\n";
  4604.                     while (list ($key,$valueeach ($data)) {
  4605.                         if($key{0== '_'){
  4606.                             continue;
  4607.                         }
  4608.                         $type=gettype($value);
  4609.                         if ($type=="array"{
  4610.                             AK_CLI ? printf ("\t* (%s) %s:\n",$type$key:
  4611.                             printf ("<li>(%s) <b>%s</b>:\n",$type$key);
  4612.                             ob_start();
  4613.                             Ak::debug ($value,$sf);
  4614.                             $lines explode("\n",ob_get_clean()."\n");
  4615.                             foreach ($lines as $line){
  4616.                                 echo "\t".$line."\n";
  4617.                             }
  4618.                         }elseif($type == "object"){
  4619.                             if(method_exists($value,'hasColumn'&& $value->hasColumn($key)){
  4620.                                 $value->toString(true);
  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.                             }
  4630.                         }elseif (eregi ("function"$type)) {
  4631.                             if ($sf{
  4632.                                 AK_CLI ? printf ("\t* (%s) %s:\n",$type$key$value:
  4633.                                 printf ("<li>(%s) <b>%s</b> </li>\n",$type$key$value);
  4634.                             }
  4635.                         else {
  4636.                             if (!$value{
  4637.                                 $value="(none)";
  4638.                             }
  4639.                             AK_CLI ? printf ("\t* (%s) %s = %s\n",$type$key$value:
  4640.                             printf ("<li>(%s) <b>%s</b> = %s</li>\n",$type$key$value);
  4641.                         }
  4642.                     }
  4643.                     echo AK_CLI ? "\n--/\n" "</ol>fin.\n";
  4644.                 else {
  4645.                     echo "(empty)";
  4646.                 }
  4647.             }
  4648.         }
  4649.     }
  4650.  
  4651.     /*/Debugging*/
  4652.  
  4653.  
  4654.  
  4655.     /**
  4656.                         Utilities
  4657.     ====================================================================
  4658.     */
  4659.     /**
  4660.      * Selects and filters a search result to include only specified columns
  4661.      * 
  4662.      *    $people_for_select = $People->select($People->find(),'name','email');
  4663.      *    
  4664.      *    Now $people_for_select will hold an array with
  4665.      *    array (
  4666.      *        array ('name' => 'Jose','email' => 'jose@example.com'),
  4667.      *        array ('name' => 'Alicia','email' => 'alicia@example.com'),
  4668.      *        array ('name' => 'Hilario','email' => 'hilario@example.com'),
  4669.      *        array ('name' => 'Bermi','email' => 'bermi@example.com')
  4670.      *    );
  4671.      */
  4672.     function select(&$source_array)
  4673.     {
  4674.         $resulting_array = array();
  4675.         if(!empty($source_array&& is_array($source_array&& func_num_args(> 1{
  4676.         (array)$args array_filter(array_slice(func_get_args(),1),array($this,'hasColumn'));
  4677.         foreach ($source_array as $source_item){
  4678.             $item_fields = array();
  4679.             foreach ($args as $arg){
  4680.                 $item_fields[$arg=$source_item->get($arg);
  4681.             }
  4682.             $resulting_array[=$item_fields;
  4683.         }
  4684.         }
  4685.         return $resulting_array;
  4686.     }
  4687.  
  4688.  
  4689.     /**
  4690.      * Collect is a function for selecting items from double depth array
  4691.      * like the ones returned by the AkActiveRecord. This comes useful when you just need some
  4692.      * fields for generating tables, select lists with only desired fields.
  4693.      *
  4694.      *    $people_for_select = Ak::select($People->find(),'id','email');
  4695.      *    
  4696.      *    Returns something like:
  4697.      *    array (
  4698.      *        array ('10' => 'jose@example.com'),
  4699.      *        array ('15' => 'alicia@example.com'),
  4700.      *        array ('16' => 'hilario@example.com'),
  4701.      *        array ('18' => 'bermi@example.com')
  4702.      *    );
  4703.      */
  4704.     function collect(&$source_array$key_index$value_index)
  4705.     {
  4706.         $resulting_array = array();
  4707.         if(!empty($source_array&& is_array($source_array)) {
  4708.             foreach ($source_array as $source_item){
  4709.                 $resulting_array[$source_item->get($key_index)$source_item->get($value_index);
  4710.             }
  4711.         }
  4712.         return $resulting_array;
  4713.     }
  4714.  
  4715.     function toJson()
  4716.     {
  4717.         return Ak::toJson($this->getAttributes());
  4718.     }
  4719.  
  4720.     /**
  4721.      * converts to yaml-strings 
  4722.      * 
  4723.      * examples: 
  4724.      * User::toYaml($users->find('all'));
  4725.      * $Bermi->toYaml();
  4726.      *
  4727.      * @param array of ActiveRecords[optional] $data
  4728.      */
  4729.     function toYaml($data = null)
  4730.     {
  4731.         return Ak::convert('active_record''yaml'empty($data$this $data);
  4732.     }
  4733.  
  4734.  
  4735.     /**
  4736.     * Parses an special formated array as a list of keys and values
  4737.     * 
  4738.     * This function generates an array with values and keys from an array with numeric keys.
  4739.     * 
  4740.     * This allows to parse an array to a function in the following manner.
  4741.     * create('first_name->', 'Bermi', 'last_name->', 'Ferrer');
  4742.     * //Previous code will be the same that
  4743.     * create(array('first_name'=>'Bermi', 'last_name'=> 'Ferrer'));
  4744.     *
  4745.     * Use this syntax only for quick testings, not for production environments. If the number of arguments varies, the result might be unpredictable.
  4746.     *
  4747.     * This function syntax is disabled by default. You need to define('AK_ENABLE_AKELOS_ARGS', true)
  4748.     * if you need this functionality.
  4749.     *
  4750.     * @deprecated
  4751.     */
  4752.     function parseAkelosArgs(&$args)
  4753.     {
  4754.         if(!AK_ENABLE_AKELOS_ARGS){
  4755.             $this->_castDateParametersFromDateHelper_($args);
  4756.             return ;
  4757.         }
  4758.         $k array_keys($args);
  4759.         if(isset($k[1]&& substr($args[$k[0]],-1== '>'){
  4760.             $size = sizeOf($k);
  4761.             $params = array();
  4762.             for($i = 0$i $size$i++ {
  4763.                 $v $args[$k[$i]];
  4764.                 if(!isset($key&& is_string($args[$k[$i]]&& substr($v,-1== '>'){
  4765.                     $key rtrim($v'=-> ');
  4766.                 }elseif(isset($key)) {
  4767.                     $params[$key$v;
  4768.                     unset($key);
  4769.                 }else{
  4770.                     $params[$k[$i]] $v;
  4771.                 }
  4772.             }
  4773.             if(!empty($params)){
  4774.                 $args $params;
  4775.             }
  4776.         }
  4777.         $this->_castDateParametersFromDateHelper_($args);
  4778.     }
  4779.     /**
  4780.     * Gets an array from a string.
  4781.     *
  4782.     * Acts like Php explode() function but uses any of this as valid separators ' AND ',' and ',' + ',' ',',',';'
  4783.     */
  4784.     function getArrayFromAkString($string)
  4785.     {
  4786.         if(is_array($string)){
  4787.             return $string;
  4788.         }
  4789.         $string str_replace(array(' AND ',' and ',' + ',' ',',',';'),array('|','|','|','','|','|'),trim($string));
  4790.         return strstr($string,'|'explode('|'$string: array($string);
  4791.     }
  4792.     /*/Utilities*/
  4793.  
  4794.  
  4795.     function getAttributeCondition($argument)
  4796.     {
  4797.         if(is_array($argument)){
  4798.             return 'IN (?)';
  4799.         }elseif (is_null($argument)){
  4800.             return 'IS ?';
  4801.         }else{
  4802.             return '= ?';
  4803.         }
  4804.     }
  4805.  
  4806.  
  4807.     /**
  4808.                      Calculations
  4809.  ====================================================================
  4810.  */
  4811.  
  4812.     /**
  4813.     * @access private
  4814.     */
  4815.     var $_calculation_options = array('conditions''joins''order''select''group''having''distinct''limit''offset');
  4816.  
  4817.     /**
  4818.       * Count operates using three different approaches.
  4819.       *
  4820.       * * Count all: By not passing any parameters to count, it will return a count of all the rows for the model.
  4821.       * * Count by conditions or joins
  4822.       * * Count using options will find the row count matched by the options used.
  4823.       *
  4824.       * The last approach, count using options, accepts an option hash as the only parameter. The options are:
  4825.       *
  4826.       * * <tt>'conditions'</tt>: An SQL fragment like "administrator = 1" or array("user_name = ?", $username ). See conditions in the intro.
  4827.       * * <tt>'joins'</tt>: An SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id". (Rarely needed).
  4828.       * * <tt>'order'</tt>: An SQL fragment like "created_at DESC, name" (really only used with GROUP BY calculations).
  4829.       * * <tt>'group'</tt>: An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
  4830.       * * <tt>'select'</tt>: By default, this is * as in SELECT * FROM, but can be changed if you for example want to do a join.
  4831.       * * <tt>'distinct'</tt>: Set this to true to make this a distinct calculation, such as SELECT COUNT(DISTINCT posts.id) ...
  4832.       *
  4833.       * Examples for counting all:
  4834.       *   $Person->count();         // returns the total count of all people
  4835.       *
  4836.       * Examples for count by +conditions+ and +joins+ (this has been deprecated):
  4837.       *   $Person->count("age > 26");  // returns the number of people older than 26
  4838.       *   $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(*).
  4839.       *
  4840.       * Examples for count with options:
  4841.       *   $Person->count('conditions' => "age > 26");
  4842.       *   $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.
  4843.       *   $Person->count('id', 'conditions' => "age > 26"); // Performs a COUNT(id)
  4844.       *   $Person->count('all', 'conditions' => "age > 26"); // Performs a COUNT(*) ('all' is an alias for '*')
  4845.       *
  4846.       * Note: $Person->count('all') will not work because it will use 'all' as the condition.  Use $Person->count() instead.
  4847.       */
  4848.     function count()
  4849.     {
  4850.         $args func_get_args();
  4851.         list($column_name$options$this->_constructCountOptionsFromLegacyArgs($args);
  4852.         return $this->calculate('count'$column_name$options);
  4853.     }
  4854.  
  4855.     /**
  4856.       * Calculates average value on a given column.  The value is returned as a float.  See #calculate for examples with options.
  4857.       *  
  4858.       *     $Person->average('age');
  4859.       */
  4860.     function average($column_name$options = array())
  4861.     {
  4862.         return $this->calculate('avg'$column_name$options);
  4863.     }
  4864.  
  4865.     /**
  4866.       * 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.
  4867.       *
  4868.       *   $Person->minimum('age');
  4869.       */
  4870.     function minimum($column_name$options = array())
  4871.     {
  4872.         return $this->calculate('min'$column_name$options);
  4873.     }
  4874.  
  4875.     /**
  4876.       * 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.
  4877.       *
  4878.       *   $Person->maximum('age');
  4879.       */
  4880.     function maximum($column_name$options = array())
  4881.     {
  4882.         return $this->calculate('max'$column_name$options);
  4883.     }
  4884.  
  4885.     /**
  4886.       * 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.
  4887.       *
  4888.       *   $Person->sum('age');
  4889.       */
  4890.     function sum($column_name$options = array())
  4891.     {
  4892.         return $this->calculate('sum'$column_name$options);
  4893.     }
  4894.  
  4895.     /**
  4896.       * This calculates aggregate values in the given column:  Methods for count, sum, average, minimum, and maximum have been added as shortcuts.
  4897.       * Options such as 'conditions', 'order', 'group', 'having', and 'joins' can be passed to customize the query.
  4898.       *
  4899.       * There are two basic forms of output:
  4900.       *   * 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.
  4901.       *   * Grouped values: This returns an ordered hash of the values and groups them by the 'group' option.  It takes a column name.
  4902.       *
  4903.       *       $values = $Person->maximum('age', array('group' => 'last_name'));
  4904.       *       echo $values["Drake"]
  4905.       *       => 43
  4906.       *
  4907.       * Options:
  4908.       * * <tt>'conditions'</tt>: An SQL fragment like "administrator = 1" or array( "user_name = ?", username ). See conditions in the intro.
  4909.       * * <tt>'joins'</tt>: An SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id". (Rarely needed).
  4910.       *   The records will be returned read-only since they will have attributes that do not correspond to the table's columns.
  4911.       * * <tt>'order'</tt>: An SQL fragment like "created_at DESC, name" (really only used with GROUP BY calculations).
  4912.       * * <tt>'group'</tt>: An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
  4913.       * * <tt>'select'</tt>: By default, this is * as in SELECT * FROM, but can be changed if you for example want to do a join.
  4914.       * * <tt>'distinct'</tt>: Set this to true to make this a distinct calculation, such as SELECT COUNT(DISTINCT posts.id) ...
  4915.       *
  4916.       * Examples:
  4917.       *   $Person->calculate('count', 'all'); // The same as $Person->count();
  4918.       *   $Person->average('age'); // SELECT AVG(age) FROM people...
  4919.       *   $Person->minimum('age', array('conditions' => array('last_name != ?', 'Drake'))); // Selects the minimum age for everyone with a last name other than 'Drake'
  4920.       *   $Person->minimum('age', array('having' => 'min(age) > 17', 'group' => 'last'_name)); // Selects the minimum age for any family without any minors
  4921.       */
  4922.     function calculate($operation$column_name$options = array())
  4923.     {
  4924.         $this->_validateCalculationOptions($options);
  4925.         $column_name = empty($options['select']$column_name $options['select'];
  4926.         $column_name $column_name == 'all' '*' $column_name;
  4927.         $column      $this->_getColumnFor($column_name);
  4928.         if (!empty($options['group'])){
  4929.             return $this->_executeGroupedCalculation($operation$column_name$column$options);
  4930.         }else{
  4931.             return $this->_executeSimpleCalculation($operation$column_name$column$options);
  4932.         }
  4933.  
  4934.         return 0;
  4935.     }
  4936.  
  4937.     /**
  4938.     * @access private
  4939.     */
  4940.     function _constructCountOptionsFromLegacyArgs($args)
  4941.     {
  4942.         $options = array();
  4943.         $column_name 'all';
  4944.  
  4945.         /*
  4946.         We need to handle
  4947.         count()
  4948.         count(options=array())
  4949.         count($column_name='all', $options=array())
  4950.         count($conditions=null, $joins=null)
  4951.         */
  4952.         if(count($args> 2){
  4953.             trigger_error(Ak::t("Unexpected parameters passed to count(\$options=array())"E_USER_ERROR));
  4954.         }elseif(count($args> 0){
  4955.             if(!empty($args[0]&& is_array($args[0])){
  4956.                 $options $args[0];
  4957.             }elseif(!empty($args[1]&& is_array($args[1])){
  4958.                 $column_name array_shift($args);
  4959.                 $options array_shift($args);
  4960.             }else{
  4961.                 $options = array('conditions' => $args[0]);
  4962.                 if(!empty($args[1])){
  4963.                     $options array_merge($optionsarray('joins' => $args[1]));
  4964.                 }
  4965.             }
  4966.         }
  4967.         return array($column_name$options);
  4968.     }
  4969.  
  4970.  
  4971.     /**
  4972.     * @access private
  4973.     */
  4974.     function _constructCalculationSql($operation$column_name$options)
  4975.     {
  4976.         $operation strtolower($operation);
  4977.         $aggregate_alias $this->_getColumnAliasFor($operation$column_name);
  4978.         $use_workaround $operation == 'count' && !empty($options['distinct']&& $this->_getDatabaseType(== 'sqlite';
  4979.  
  4980.         $sql $use_workaround ?
  4981.         "SELECT COUNT(*) AS $aggregate_alias// A (slower) workaround if we're using a backend, like sqlite, that doesn't support COUNT DISTINCT.
  4982.         "SELECT $operation(".(empty($options['distinct'])?'':'DISTINCT ')."$column_name) AS $aggregate_alias";
  4983.  
  4984.  
  4985.         $sql .= empty($options['group']'' ", {$options['group_field']} AS {$options['group_alias']}";
  4986.         $sql .= $use_workaround " FROM (SELECT DISTINCT {$column_name}'';
  4987.         $sql .=  " FROM ".$this->getTableName()." ";
  4988.  
  4989.         $sql .=  empty($options['joins']'' " {$options['joins']} ";
  4990.  
  4991.         empty($options['conditions']? null : $this->addConditions($sql$options['conditions']);
  4992.  
  4993.         if (!empty($options['group'])){
  4994.             $sql .=  " GROUP BY {$options['group_field']} ";
  4995.             $sql .= empty($options['having']'' " HAVING {$options['having']} ";
  4996.         }
  4997.  
  4998.         $sql .= empty($options['order']'' " ORDER BY {$options['order']} ";
  4999.         $this->_db->addLimitAndOffset($sql$options);
  5000.         $sql .= $use_workaround ')' '';
  5001.         return $sql;
  5002.     }
  5003.  
  5004.  
  5005.     /**
  5006.     * @access private
  5007.     */
  5008.     function _executeSimpleCalculation($operation$column_name$column$options)
  5009.     {
  5010.         $value $this->_db->selectValue($this->_constructCalculationSql($operation$column_name$options));
  5011.         return $this->_typeCastCalculatedValue($value$column$operation);
  5012.     }
  5013.  
  5014.     /**
  5015.     * @access private
  5016.     */
  5017.     function _executeGroupedCalculation($operation$column_name$column$options)
  5018.     {
  5019.         $group_field $options['group'];
  5020.         $group_alias $this->_getColumnAliasFor($group_field);
  5021.         $group_column $this->_getColumnFor($group_field);
  5022.         $options array_merge(array('group_field' => $group_field'group_alias' => $group_alias),$options);
  5023.         $sql $this->_constructCalculationSql($operation$column_name$options);
  5024.         $calculated_data $this->_db->select($sql);
  5025.         $aggregate_alias $this->_getColumnAliasFor($operation$column_name);
  5026.  
  5027.         $all = array();
  5028.         foreach ($calculated_data as $row){
  5029.             $key $this->_typeCastCalculatedValue($row[$group_alias]$group_column);
  5030.             $all[$key$this->_typeCastCalculatedValue($row[$aggregate_alias]$column$operation);
  5031.         }
  5032.         return $all;
  5033.     }
  5034.  
  5035.     /**
  5036.     * @access private
  5037.     */
  5038.     function _validateCalculationOptions($options = array())
  5039.     {
  5040.         $invalid_options array_diff(array_keys($options),$this->_calculation_options);
  5041.         if(!empty($invalid_options)){
  5042.             trigger_error(Ak::t('%options are not valid calculation options.'array('%options'=>join(', ',$invalid_options)))E_USER_ERROR);
  5043.         }
  5044.     }
  5045.  
  5046.     /**
  5047.     * Converts a given key to the value that the database adapter returns as
  5048.     * as a usable column name.
  5049.     *   users.id #=> users_id
  5050.     *   sum(id) #=> sum_id
  5051.     *   count(distinct users.id) #=> count_distinct_users_id
  5052.     *   count(*) #=> count_all
  5053.     *
  5054.     * @access private
  5055.     */
  5056.     function _getColumnAliasFor()
  5057.     {
  5058.         $args func_get_args();
  5059.         $keys strtolower(join(' ',(!empty($args(is_array($args[0]$args[0$args: array())));
  5060.         return preg_replace(array('/\*/','/\W+/','/^ +/','/ +$/','/ +/'),array('all',' ','','','_')$keys);
  5061.     }
  5062.  
  5063.     /**
  5064.     * @access private
  5065.     */
  5066.     function _getColumnFor($field)
  5067.     {
  5068.         $field_name ltrim(substr($field,strpos($field,'.')),'.');
  5069.         if(in_array($field_name,$this->getColumnNames())){
  5070.             return $field_name;
  5071.         }
  5072.         return $field;
  5073.     }
  5074.  
  5075.     /**
  5076.     * @access private
  5077.     */
  5078.     function _typeCastCalculatedValue($value$column$operation = null)
  5079.     {
  5080.         $operation strtolower($operation);
  5081.         if($operation == 'count'){
  5082.             return intval($value);
  5083.         }elseif ($operation == 'avg'){
  5084.             return floatval($value);
  5085.         }else{
  5086.             return empty($column$value AkActiveRecord::castAttributeFromDatabase($column$value);
  5087.         }
  5088.     }
  5089.  
  5090.     /*/Calculations*/
  5091.  
  5092.     function hasBeenModified()
  5093.     {
  5094.         return Ak::objectHasBeenModified($this);
  5095.     }
  5096.  
  5097.     /**
  5098.     * Just freeze the attributes hash, such that associations are still accessible even on destroyed records.
  5099.     * 
  5100.     * @todo implement freeze correctly for its intended use
  5101.     */
  5102.     function freeze()
  5103.     {
  5104.         return $this->_freeze = true;
  5105.     }
  5106.  
  5107.     function isFrozen()
  5108.     {
  5109.         return !empty($this->_freeze);
  5110.     }
  5111.  
  5112.     /**
  5113.     * Alias for getModelName()
  5114.     */
  5115.     function getType()
  5116.     {
  5117.         return $this->getModelName();
  5118.     }
  5119.  
  5120.     function &objectCache()
  5121.     {
  5122.         static $cache;
  5123.         $args =func_get_args();
  5124.         if(count($args== 2){
  5125.             if(!isset($cache[$args[0]])){
  5126.                 $cache[$args[0]] =$args[1];
  5127.             }
  5128.         }elseif(!isset($cache[$args[0]])){
  5129.             return false;
  5130.         }
  5131.         return $cache[$args[0]];
  5132.     }
  5133.  
  5134.  
  5135.     /**
  5136.                         Connection adapters
  5137.     ====================================================================
  5138.     Right now Akelos uses phpAdodb for bd abstraction. This are functionalities not
  5139.     provided in phpAdodb and that will move to a separated driver for each db 
  5140.     engine in a future
  5141.     */
  5142.     function _extractValueFromDefault($default)
  5143.     {
  5144.         if($this->_getDatabaseType(== 'postgre'){
  5145.             if(preg_match("/^'(.*)'::/"$default$match)){
  5146.                 return $match[1];
  5147.             }
  5148.             // a postgre HACK; we dont know the column-type here
  5149.             if ($default=='true'{
  5150.                 return true;
  5151.             }
  5152.             if ($default=='false'{
  5153.                 return false;
  5154.             }
  5155.         }
  5156.         return $default;
  5157.     }
  5158.  
  5159.  
  5160. }
  5161.  
  5162.  
  5163. ?>

Documentation generated on Tue, 17 Jun 2008 14:24:01 +0200 by phpDocumentor 1.3.2