model.php

Go to the documentation of this file.
00001 <?php
00002 /* SVN FILE: $Id: model.php 8274 2009-08-02 19:18:54Z jperras $ */
00003 /**
00004  * Object-relational mapper.
00005  *
00006  * DBO-backed object data model, for mapping database tables to Cake objects.
00007  *
00008  * PHP versions 5
00009  *
00010  * CakePHP(tm) :  Rapid Development Framework (http://www.cakephp.org)
00011  * Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
00012  *
00013  * Licensed under The MIT License
00014  * Redistributions of files must retain the above copyright notice.
00015  *
00016  * @filesource
00017  * @copyright     Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
00018  * @link          http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project
00019  * @package       cake
00020  * @subpackage    cake.cake.libs.model
00021  * @since         CakePHP(tm) v 0.10.0.0
00022  * @version       $Revision: 8274 $
00023  * @modifiedby    $LastChangedBy: jperras $
00024  * @lastmodified  $Date: 2009-08-02 15:18:54 -0400 (Sun, 02 Aug 2009) $
00025  * @license       http://www.opensource.org/licenses/mit-license.php The MIT License
00026  */
00027 /**
00028  * Included libs
00029  */
00030 App::import('Core', array('ClassRegistry', 'Overloadable', 'Validation', 'Behavior', 'ConnectionManager', 'Set', 'String'));
00031 /**
00032  * Object-relational mapper.
00033  *
00034  * DBO-backed object data model.
00035  * Automatically selects a database table name based on a pluralized lowercase object class name
00036  * (i.e. class 'User' => table 'users'; class 'Man' => table 'men')
00037  * The table is required to have at least 'id auto_increment' primary key.
00038  *
00039  * @package       cake
00040  * @subpackage    cake.cake.libs.model
00041  * @link          http://book.cakephp.org/view/66/Models
00042  */
00043 class Model extends Overloadable {
00044 /**
00045  * The name of the DataSource connection that this Model uses
00046  *
00047  * @var string
00048  * @access public
00049  * @link http://book.cakephp.org/view/435/useDbConfig
00050  */
00051     var $useDbConfig = 'default';
00052 /**
00053  * Custom database table name, or null/false if no table association is desired.
00054  *
00055  * @var string
00056  * @access public
00057  * @link http://book.cakephp.org/view/436/useTable
00058  */
00059     var $useTable = null;
00060 /**
00061  * Custom display field name. Display fields are used by Scaffold, in SELECT boxes' OPTION elements.
00062  *
00063  * @var string
00064  * @access public
00065  * @link http://book.cakephp.org/view/438/displayField
00066  */
00067     var $displayField = null;
00068 /**
00069  * Value of the primary key ID of the record that this model is currently pointing to.
00070  * Automatically set after database insertions.
00071  *
00072  * @var mixed
00073  * @access public
00074  */
00075     var $id = false;
00076 /**
00077  * Container for the data that this model gets from persistent storage (usually, a database).
00078  *
00079  * @var array
00080  * @access public
00081  * @link http://book.cakephp.org/view/441/data
00082  */
00083     var $data = array();
00084 /**
00085  * Table name for this Model.
00086  *
00087  * @var string
00088  * @access public
00089  */
00090     var $table = false;
00091 /**
00092  * The name of the primary key field for this model.
00093  *
00094  * @var string
00095  * @access public
00096  * @link http://book.cakephp.org/view/437/primaryKey
00097  */
00098     var $primaryKey = null;
00099 /**
00100  * Field-by-field table metadata.
00101  *
00102  * @var array
00103  * @access protected
00104  * @link http://book.cakephp.org/view/442/_schema
00105  */
00106     var $_schema = null;
00107 /**
00108  * List of validation rules. Append entries for validation as ('field_name' => '/^perl_compat_regexp$/')
00109  * that have to match with preg_match(). Use these rules with Model::validate()
00110  *
00111  * @var array
00112  * @access public
00113  * @link http://book.cakephp.org/view/443/validate
00114  * @link http://book.cakephp.org/view/125/Data-Validation
00115  */
00116     var $validate = array();
00117 /**
00118  * List of validation errors.
00119  *
00120  * @var array
00121  * @access public
00122  * @link http://book.cakephp.org/view/410/Validating-Data-from-the-Controller
00123  */
00124     var $validationErrors = array();
00125 /**
00126  * Database table prefix for tables in model.
00127  *
00128  * @var string
00129  * @access public
00130  * @link http://book.cakephp.org/view/475/tablePrefix
00131  */
00132     var $tablePrefix = null;
00133 /**
00134  * Name of the model.
00135  *
00136  * @var string
00137  * @access public
00138  * @link http://book.cakephp.org/view/444/name
00139  */
00140     var $name = null;
00141 /**
00142  * Alias name for model.
00143  *
00144  * @var string
00145  * @access public
00146  */
00147     var $alias = null;
00148 /**
00149  * List of table names included in the model description. Used for associations.
00150  *
00151  * @var array
00152  * @access public
00153  */
00154     var $tableToModel = array();
00155 /**
00156  * Whether or not to log transactions for this model.
00157  *
00158  * @var boolean
00159  * @access public
00160  */
00161     var $logTransactions = false;
00162 /**
00163  * Whether or not to enable transactions for this model (i.e. BEGIN/COMMIT/ROLLBACK statements)
00164  *
00165  * @var boolean
00166  * @access public
00167  */
00168     var $transactional = false;
00169 /**
00170  * Whether or not to cache queries for this model.  This enables in-memory
00171  * caching only, the results are not stored beyond the current request.
00172  *
00173  * @var boolean
00174  * @access public
00175  * @link http://book.cakephp.org/view/445/cacheQueries
00176  */
00177     var $cacheQueries = false;
00178 /**
00179  * Detailed list of belongsTo associations.
00180  *
00181  * @var array
00182  * @access public
00183  * @link http://book.cakephp.org/view/81/belongsTo
00184  */
00185     var $belongsTo = array();
00186 /**
00187  * Detailed list of hasOne associations.
00188  *
00189  * @var array
00190  * @access public
00191  * @link http://book.cakephp.org/view/80/hasOne
00192  */
00193     var $hasOne = array();
00194 /**
00195  * Detailed list of hasMany associations.
00196  *
00197  * @var array
00198  * @access public
00199  * @link http://book.cakephp.org/view/82/hasMany
00200  */
00201     var $hasMany = array();
00202 /**
00203  * Detailed list of hasAndBelongsToMany associations.
00204  *
00205  * @var array
00206  * @access public
00207  * @link http://book.cakephp.org/view/83/hasAndBelongsToMany-HABTM
00208  */
00209     var $hasAndBelongsToMany = array();
00210 /**
00211  * List of behaviors to load when the model object is initialized. Settings can be
00212  * passed to behaviors by using the behavior name as index. Eg:
00213  *
00214  * var $actsAs = array('Translate', 'MyBehavior' => array('setting1' => 'value1'))
00215  *
00216  * @var array
00217  * @access public
00218  * @link http://book.cakephp.org/view/90/Using-Behaviors
00219  */
00220     var $actsAs = null;
00221 /**
00222  * Holds the Behavior objects currently bound to this model.
00223  *
00224  * @var BehaviorCollection
00225  * @access public
00226  */
00227     var $Behaviors = null;
00228 /**
00229  * Whitelist of fields allowed to be saved.
00230  *
00231  * @var array
00232  * @access public
00233  */
00234     var $whitelist = array();
00235 /**
00236  * Whether or not to cache sources for this model.
00237  *
00238  * @var boolean
00239  * @access public
00240  */
00241     var $cacheSources = true;
00242 /**
00243  * Type of find query currently executing.
00244  *
00245  * @var string
00246  * @access public
00247  */
00248     var $findQueryType = null;
00249 /**
00250  * Number of associations to recurse through during find calls. Fetches only
00251  * the first level by default.
00252  *
00253  * @var integer
00254  * @access public
00255  * @link http://book.cakephp.org/view/439/recursive
00256  */
00257     var $recursive = 1;
00258 /**
00259  * The column name(s) and direction(s) to order find results by default.
00260  *
00261  * var $order = "Post.created DESC";
00262  * var $order = array("Post.view_count DESC", "Post.rating DESC");
00263  *
00264  * @var string
00265  * @access public
00266  * @link http://book.cakephp.org/view/440/order
00267  */
00268     var $order = null;
00269 /**
00270  * Whether or not the model record exists, set by Model::exists().
00271  *
00272  * @var bool
00273  * @access private
00274  */
00275     var $__exists = null;
00276 /**
00277  * Default list of association keys.
00278  *
00279  * @var array
00280  * @access private
00281  */
00282     var $__associationKeys = array(
00283         'belongsTo' => array('className', 'foreignKey', 'conditions', 'fields', 'order', 'counterCache'),
00284         'hasOne' => array('className', 'foreignKey','conditions', 'fields','order', 'dependent'),
00285         'hasMany' => array('className', 'foreignKey', 'conditions', 'fields', 'order', 'limit', 'offset', 'dependent', 'exclusive', 'finderQuery', 'counterQuery'),
00286         'hasAndBelongsToMany' => array('className', 'joinTable', 'with', 'foreignKey', 'associationForeignKey', 'conditions', 'fields', 'order', 'limit', 'offset', 'unique', 'finderQuery', 'deleteQuery', 'insertQuery')
00287     );
00288 /**
00289  * Holds provided/generated association key names and other data for all associations.
00290  *
00291  * @var array
00292  * @access private
00293  */
00294     var $__associations = array('belongsTo', 'hasOne', 'hasMany', 'hasAndBelongsToMany');
00295 /**
00296  * Holds model associations temporarily to allow for dynamic (un)binding.
00297  *
00298  * @var array
00299  * @access private
00300  */
00301     var $__backAssociation = array();
00302 /**
00303  * The ID of the model record that was last inserted.
00304  *
00305  * @var integer
00306  * @access private
00307  */
00308     var $__insertID = null;
00309 /**
00310  * The number of records returned by the last query.
00311  *
00312  * @var integer
00313  * @access private
00314  */
00315     var $__numRows = null;
00316 /**
00317  * The number of records affected by the last query.
00318  *
00319  * @var integer
00320  * @access private
00321  */
00322     var $__affectedRows = null;
00323 /**
00324  * List of valid finder method options, supplied as the first parameter to find().
00325  *
00326  * @var array
00327  * @access protected
00328  */
00329     var $_findMethods = array(
00330         'all' => true, 'first' => true, 'count' => true,
00331         'neighbors' => true, 'list' => true, 'threaded' => true
00332     );
00333 /**
00334  * Constructor. Binds the model's database table to the object.
00335  *
00336  * @param integer $id Set this ID for this model on startup
00337  * @param string $table Name of database table to use.
00338  * @param object $ds DataSource connection object.
00339  */
00340     function __construct($id = false, $table = null, $ds = null) {
00341         parent::__construct();
00342 
00343         if (is_array($id)) {
00344             extract(array_merge(
00345                 array(
00346                     'id' => $this->id, 'table' => $this->useTable, 'ds' => $this->useDbConfig,
00347                     'name' => $this->name, 'alias' => $this->alias
00348                 ),
00349                 $id
00350             ));
00351         }
00352 
00353         if ($this->name === null) {
00354             $this->name = (isset($name) ? $name : get_class($this));
00355         }
00356 
00357         if ($this->alias === null) {
00358             $this->alias = (isset($alias) ? $alias : $this->name);
00359         }
00360 
00361         if ($this->primaryKey === null) {
00362             $this->primaryKey = 'id';
00363         }
00364 
00365         ClassRegistry::addObject($this->alias, $this);
00366 
00367         $this->id = $id;
00368         unset($id);
00369 
00370         if ($table === false) {
00371             $this->useTable = false;
00372         } elseif ($table) {
00373             $this->useTable = $table;
00374         }
00375 
00376         if ($ds !== null) {
00377             $this->useDbConfig = $ds;
00378         }
00379 
00380         if (is_subclass_of($this, 'AppModel')) {
00381             $appVars = get_class_vars('AppModel');
00382             $merge = array('_findMethods');
00383 
00384             if ($this->actsAs !== null || $this->actsAs !== false) {
00385                 $merge[] = 'actsAs';
00386             }
00387             $parentClass = get_parent_class($this);
00388             if (strtolower($parentClass) !== 'appmodel') {
00389                 $parentVars = get_class_vars($parentClass);
00390                 foreach ($merge as $var) {
00391                     if (isset($parentVars[$var]) && !empty($parentVars[$var])) {
00392                         $appVars[$var] = Set::merge($appVars[$var], $parentVars[$var]);
00393                     }
00394                 }
00395             }
00396 
00397             foreach ($merge as $var) {
00398                 if (isset($appVars[$var]) && !empty($appVars[$var]) && is_array($this->{$var})) {
00399                     $this->{$var} = Set::merge($appVars[$var], $this->{$var});
00400                 }
00401             }
00402         }
00403         $this->Behaviors = new BehaviorCollection();
00404 
00405         if ($this->useTable !== false) {
00406             $this->setDataSource($ds);
00407 
00408             if ($this->useTable === null) {
00409                 $this->useTable = Inflector::tableize($this->name);
00410             }
00411             if (method_exists($this, 'setTablePrefix')) {
00412                 $this->setTablePrefix();
00413             }
00414             $this->setSource($this->useTable);
00415 
00416             if ($this->displayField == null) {
00417                 $this->displayField = $this->hasField(array('title', 'name', $this->primaryKey));
00418             }
00419         } elseif ($this->table === false) {
00420             $this->table = Inflector::tableize($this->name);
00421         }
00422         $this->__createLinks();
00423         $this->Behaviors->init($this->alias, $this->actsAs);
00424     }
00425 /**
00426  * Handles custom method calls, like findBy<field> for DB models,
00427  * and custom RPC calls for remote data sources.
00428  *
00429  * @param string $method Name of method to call.
00430  * @param array $params Parameters for the method.
00431  * @return mixed Whatever is returned by called method
00432  * @access protected
00433  */
00434     function call__($method, $params) {
00435         $result = $this->Behaviors->dispatchMethod($this, $method, $params);
00436 
00437         if ($result !== array('unhandled')) {
00438             return $result;
00439         }
00440         $db =& ConnectionManager::getDataSource($this->useDbConfig);
00441         $return = $db->query($method, $params, $this);
00442 
00443         if (!PHP5) {
00444             $this->resetAssociations();
00445         }
00446         return $return;
00447     }
00448 /**
00449  * Bind model associations on the fly.
00450  *
00451  * If $permanent is true, association will not be reset
00452  * to the originals defined in the model.
00453  *
00454  * @param mixed $model A model or association name (string) or set of binding options (indexed by model name type)
00455  * @param array $options If $model is a string, this is the list of association properties with which $model will
00456  *                       be bound
00457  * @param boolean $permanent Set to true to make the binding permanent
00458  * @return void
00459  * @access public
00460  * @todo
00461  */
00462     function bind($model, $options = array(), $permanent = true) {
00463         if (!is_array($model)) {
00464             $model = array($model => $options);
00465         }
00466 
00467         foreach ($model as $name => $options) {
00468             if (isset($options['type'])) {
00469                 $assoc = $options['type'];
00470             } elseif (isset($options[0])) {
00471                 $assoc = $options[0];
00472             } else {
00473                 $assoc = 'belongsTo';
00474             }
00475 
00476             if (!$permanent) {
00477                 $this->__backAssociation[$assoc] = $this->{$assoc};
00478             }
00479             foreach ($model as $key => $value) {
00480                 $assocName = $modelName = $key;
00481 
00482                 if (isset($this->{$assoc}[$assocName])) {
00483                     $this->{$assoc}[$assocName] = array_merge($this->{$assoc}[$assocName], $options);
00484                 } else {
00485                     if (isset($value['className'])) {
00486                         $modelName = $value['className'];
00487                     }
00488 
00489                     $this->__constructLinkedModel($assocName, $modelName);
00490                     $this->{$assoc}[$assocName] = $model[$assocName];
00491                     $this->__generateAssociation($assoc);
00492                 }
00493                 unset($this->{$assoc}[$assocName]['type'], $this->{$assoc}[$assocName][0]);
00494             }
00495         }
00496     }
00497 /**
00498  * Bind model associations on the fly.
00499  *
00500  * If $reset is false, association will not be reset
00501  * to the originals defined in the model
00502  *
00503  * Example: Add a new hasOne binding to the Profile model not
00504  * defined in the model source code:
00505  * <code>
00506  * $this->User->bindModel( array('hasOne' => array('Profile')) );
00507  * </code>
00508  *
00509  * @param array $params Set of bindings (indexed by binding type)
00510  * @param boolean $reset Set to false to make the binding permanent
00511  * @return boolean Success
00512  * @access public
00513  * @link http://book.cakephp.org/view/86/Creating-and-Destroying-Associations-on-the-Fly
00514  */
00515     function bindModel($params, $reset = true) {
00516         foreach ($params as $assoc => $model) {
00517             if ($reset === true) {
00518                 $this->__backAssociation[$assoc] = $this->{$assoc};
00519             }
00520 
00521             foreach ($model as $key => $value) {
00522                 $assocName = $key;
00523 
00524                 if (is_numeric($key)) {
00525                     $assocName = $value;
00526                     $value = array();
00527                 }
00528                 $modelName = $assocName;
00529                 $this->{$assoc}[$assocName] = $value;
00530             }
00531         }
00532         $this->__createLinks();
00533         return true;
00534     }
00535 /**
00536  * Turn off associations on the fly.
00537  *
00538  * If $reset is false, association will not be reset
00539  * to the originals defined in the model
00540  *
00541  * Example: Turn off the associated Model Support request,
00542  * to temporarily lighten the User model:
00543  * <code>
00544  * $this->User->unbindModel( array('hasMany' => array('Supportrequest')) );
00545  * </code>
00546  *
00547  * @param array $params Set of bindings to unbind (indexed by binding type)
00548  * @param boolean $reset  Set to false to make the unbinding permanent
00549  * @return boolean Success
00550  * @access public
00551  * @link http://book.cakephp.org/view/86/Creating-and-Destroying-Associations-on-the-Fly
00552  */
00553     function unbindModel($params, $reset = true) {
00554         foreach ($params as $assoc => $models) {
00555             if ($reset === true) {
00556                 $this->__backAssociation[$assoc] = $this->{$assoc};
00557             }
00558 
00559             foreach ($models as $model) {
00560                 $this->__backAssociation = array_merge($this->__backAssociation, $this->{$assoc});
00561                 unset ($this->__backAssociation[$model]);
00562                 unset ($this->{$assoc}[$model]);
00563             }
00564         }
00565         return true;
00566     }
00567 /**
00568  * Create a set of associations.
00569  *
00570  * @return void
00571  * @access private
00572  */
00573     function __createLinks() {
00574         foreach ($this->__associations as $type) {
00575             if (!is_array($this->{$type})) {
00576                 $this->{$type} = explode(',', $this->{$type});
00577 
00578                 foreach ($this->{$type} as $i => $className) {
00579                     $className = trim($className);
00580                     unset ($this->{$type}[$i]);
00581                     $this->{$type}[$className] = array();
00582                 }
00583             }
00584 
00585             if (!empty($this->{$type})) {
00586                 foreach ($this->{$type} as $assoc => $value) {
00587                     $plugin = null;
00588 
00589                     if (is_numeric($assoc)) {
00590                         unset ($this->{$type}[$assoc]);
00591                         $assoc = $value;
00592                         $value = array();
00593                         $this->{$type}[$assoc] = $value;
00594 
00595                         if (strpos($assoc, '.') !== false) {
00596                             $value = $this->{$type}[$assoc];
00597                             unset($this->{$type}[$assoc]);
00598                             list($plugin, $assoc) = explode('.', $assoc);
00599                             $this->{$type}[$assoc] = $value;
00600                             $plugin = $plugin . '.';
00601                         }
00602                     }
00603                     $className =  $assoc;
00604 
00605                     if (isset($value['className']) && !empty($value['className'])) {
00606                         $className = $value['className'];
00607                         if (strpos($className, '.') !== false) {
00608                             list($plugin, $className) = explode('.', $className);
00609                             $plugin = $plugin . '.';
00610                             $this->{$type}[$assoc]['className'] = $className;
00611                         }
00612                     }
00613                     $this->__constructLinkedModel($assoc, $plugin . $className);
00614                 }
00615                 $this->__generateAssociation($type);
00616             }
00617         }
00618     }
00619 /**
00620  * Private helper method to create associated models of a given class.
00621  *
00622  * @param string $assoc Association name
00623  * @param string $className Class name
00624  * @deprecated $this->$className use $this->$assoc instead. $assoc is the 'key' in the associations array;
00625  *  examples: var $hasMany = array('Assoc' => array('className' => 'ModelName'));
00626  *                  usage: $this->Assoc->modelMethods();
00627  *
00628  *              var $hasMany = array('ModelName');
00629  *                  usage: $this->ModelName->modelMethods();
00630  * @return void
00631  * @access private
00632  */
00633     function __constructLinkedModel($assoc, $className = null) {
00634         if (empty($className)) {
00635             $className = $assoc;
00636         }
00637 
00638         if (!isset($this->{$assoc}) || $this->{$assoc}->name !== $className) {
00639             $model = array('class' => $className, 'alias' => $assoc);
00640             if (PHP5) {
00641                 $this->{$assoc} = ClassRegistry::init($model);
00642             } else {
00643                 $this->{$assoc} =& ClassRegistry::init($model);
00644             }
00645             if ($assoc) {
00646                 $this->tableToModel[$this->{$assoc}->table] = $assoc;
00647             }
00648         }
00649     }
00650 /**
00651  * Build an array-based association from string.
00652  *
00653  * @param string $type 'belongsTo', 'hasOne', 'hasMany', 'hasAndBelongsToMany'
00654  * @return void
00655  * @access private
00656  */
00657     function __generateAssociation($type) {
00658         foreach ($this->{$type} as $assocKey => $assocData) {
00659             $class = $assocKey;
00660             $dynamicWith = false;
00661 
00662             foreach ($this->__associationKeys[$type] as $key) {
00663 
00664                 if (!isset($this->{$type}[$assocKey][$key]) || $this->{$type}[$assocKey][$key] === null) {
00665                     $data = '';
00666 
00667                     switch ($key) {
00668                         case 'fields':
00669                             $data = '';
00670                         break;
00671 
00672                         case 'foreignKey':
00673                             $data = (($type == 'belongsTo') ? Inflector::underscore($assocKey) : Inflector::singularize($this->table)) . '_id';
00674                         break;
00675 
00676                         case 'associationForeignKey':
00677                             $data = Inflector::singularize($this->{$class}->table) . '_id';
00678                         break;
00679 
00680                         case 'with':
00681                             $data = Inflector::camelize(Inflector::singularize($this->{$type}[$assocKey]['joinTable']));
00682                             $dynamicWith = true;
00683                         break;
00684 
00685                         case 'joinTable':
00686                             $tables = array($this->table, $this->{$class}->table);
00687                             sort ($tables);
00688                             $data = $tables[0] . '_' . $tables[1];
00689                         break;
00690 
00691                         case 'className':
00692                             $data = $class;
00693                         break;
00694 
00695                         case 'unique':
00696                             $data = true;
00697                         break;
00698                     }
00699                     $this->{$type}[$assocKey][$key] = $data;
00700                 }
00701             }
00702 
00703             if (!empty($this->{$type}[$assocKey]['with'])) {
00704                 $joinClass = $this->{$type}[$assocKey]['with'];
00705                 if (is_array($joinClass)) {
00706                     $joinClass = key($joinClass);
00707                 }
00708                 $plugin = null;
00709 
00710                 if (strpos($joinClass, '.') !== false) {
00711                     list($plugin, $joinClass) = explode('.', $joinClass);
00712                     $plugin = $plugin . '.';
00713                     $this->{$type}[$assocKey]['with'] = $joinClass;
00714                 }
00715 
00716                 if (!ClassRegistry::isKeySet($joinClass) && $dynamicWith === true) {
00717                     $this->{$joinClass} = new AppModel(array(
00718                         'name' => $joinClass,
00719                         'table' => $this->{$type}[$assocKey]['joinTable'],
00720                         'ds' => $this->useDbConfig
00721                     ));
00722                 } else {
00723                     $this->__constructLinkedModel($joinClass, $plugin . $joinClass);
00724                     $this->{$type}[$assocKey]['joinTable'] = $this->{$joinClass}->table;
00725                 }
00726 
00727                 if (count($this->{$joinClass}->schema()) <= 2 && $this->{$joinClass}->primaryKey !== false) {
00728                     $this->{$joinClass}->primaryKey = $this->{$type}[$assocKey]['foreignKey'];
00729                 }
00730             }
00731         }
00732     }
00733 /**
00734  * Sets a custom table for your controller class. Used by your controller to select a database table.
00735  *
00736  * @param string $tableName Name of the custom table
00737  * @return void
00738  * @access public
00739  */
00740     function setSource($tableName) {
00741         $this->setDataSource($this->useDbConfig);
00742         $db =& ConnectionManager::getDataSource($this->useDbConfig);
00743         $db->cacheSources = ($this->cacheSources && $db->cacheSources);
00744 
00745         if ($db->isInterfaceSupported('listSources')) {
00746             $sources = $db->listSources();
00747             if (is_array($sources) && !in_array(strtolower($this->tablePrefix . $tableName), array_map('strtolower', $sources))) {
00748                 return $this->cakeError('missingTable', array(array(
00749                     'className' => $this->alias,
00750                     'table' => $this->tablePrefix . $tableName
00751                 )));
00752             }
00753             $this->_schema = null;
00754         }
00755         $this->table = $this->useTable = $tableName;
00756         $this->tableToModel[$this->table] = $this->alias;
00757         $this->schema();
00758     }
00759 /**
00760  * This function does two things: 1) it scans the array $one for the primary key,
00761  * and if that's found, it sets the current id to the value of $one[id].
00762  * For all other keys than 'id' the keys and values of $one are copied to the 'data' property of this object.
00763  * 2) Returns an array with all of $one's keys and values.
00764  * (Alternative indata: two strings, which are mangled to
00765  * a one-item, two-dimensional array using $one for a key and $two as its value.)
00766  *
00767  * @param mixed $one Array or string of data
00768  * @param string $two Value string for the alternative indata method
00769  * @return array Data with all of $one's keys and values
00770  * @access public
00771  */
00772     function set($one, $two = null) {
00773         if (!$one) {
00774             return;
00775         }
00776         if (is_object($one)) {
00777             $one = Set::reverse($one);
00778         }
00779 
00780         if (is_array($one)) {
00781             $data = $one;
00782             if (empty($one[$this->alias])) {
00783                 if ($this->getAssociated(key($one)) === null) {
00784                     $data = array($this->alias => $one);
00785                 }
00786             }
00787         } else {
00788             $data = array($this->alias => array($one => $two));
00789         }
00790 
00791         foreach ($data as $modelName => $fieldSet) {
00792             if (is_array($fieldSet)) {
00793 
00794                 foreach ($fieldSet as $fieldName => $fieldValue) {
00795                     if (isset($this->validationErrors[$fieldName])) {
00796                         unset ($this->validationErrors[$fieldName]);
00797                     }
00798 
00799                     if ($modelName === $this->alias) {
00800                         if ($fieldName === $this->primaryKey) {
00801                             $this->id = $fieldValue;
00802                         }
00803                     }
00804                     if (is_array($fieldValue) || is_object($fieldValue)) {
00805                         $fieldValue = $this->deconstruct($fieldName, $fieldValue);
00806                     }
00807                     $this->data[$modelName][$fieldName] = $fieldValue;
00808                 }
00809             }
00810         }
00811         return $data;
00812     }
00813 /**
00814  * Deconstructs a complex data type (array or object) into a single field value.
00815  *
00816  * @param string $field The name of the field to be deconstructed
00817  * @param mixed $data An array or object to be deconstructed into a field
00818  * @return mixed The resulting data that should be assigned to a field
00819  * @access public
00820  */
00821     function deconstruct($field, $data) {
00822         if (!is_array($data)) {
00823             return $data;
00824         }
00825 
00826         $copy = $data;
00827         $type = $this->getColumnType($field);
00828 
00829         if (in_array($type, array('datetime', 'timestamp', 'date', 'time'))) {
00830             $useNewDate = (isset($data['year']) || isset($data['month']) ||
00831                 isset($data['day']) || isset($data['hour']) || isset($data['minute']));
00832 
00833             $dateFields = array('Y' => 'year', 'm' => 'month', 'd' => 'day', 'H' => 'hour', 'i' => 'min', 's' => 'sec');
00834             $timeFields = array('H' => 'hour', 'i' => 'min', 's' => 'sec');
00835 
00836             $db =& ConnectionManager::getDataSource($this->useDbConfig);
00837             $format = $db->columns[$type]['format'];
00838             $date = array();
00839 
00840             if (isset($data['hour']) && isset($data['meridian']) && $data['hour'] != 12 && 'pm' == $data['meridian']) {
00841                 $data['hour'] = $data['hour'] + 12;
00842             }
00843             if (isset($data['hour']) && isset($data['meridian']) && $data['hour'] == 12 && 'am' == $data['meridian']) {
00844                 $data['hour'] = '00';
00845             }
00846             if ($type == 'time') {
00847                 foreach ($timeFields as $key => $val) {
00848                     if (!isset($data[$val]) || $data[$val] === '0' || $data[$val] === '00') {
00849                         $data[$val] = '00';
00850                     } elseif ($data[$val] === '') {
00851                         $data[$val] = '';
00852                     } else {
00853                         $data[$val] = sprintf('%02d', $data[$val]);
00854                     }
00855                     if (!empty($data[$val])) {
00856                         $date[$key] = $data[$val];
00857                     } else {
00858                         return null;
00859                     }
00860                 }
00861             }
00862 
00863             if ($type == 'datetime' || $type == 'timestamp' || $type == 'date') {
00864                 foreach ($dateFields as $key => $val) {
00865                     if ($val == 'hour' || $val == 'min' || $val == 'sec') {
00866                         if (!isset($data[$val]) || $data[$val] === '0' || $data[$val] === '00') {
00867                             $data[$val] = '00';
00868                         } else {
00869                             $data[$val] = sprintf('%02d', $data[$val]);
00870                         }
00871                     }
00872                     if (!isset($data[$val]) || isset($data[$val]) && (empty($data[$val]) || $data[$val][0] === '-')) {
00873                         return null;
00874                     }
00875                     if (isset($data[$val]) && !empty($data[$val])) {
00876                         $date[$key] = $data[$val];
00877                     }
00878                 }
00879             }
00880             $date = str_replace(array_keys($date), array_values($date), $format);
00881             if ($useNewDate && !empty($date)) {
00882                 return $date;
00883             }
00884         }
00885         return $data;
00886     }
00887 /**
00888  * Returns an array of table metadata (column names and types) from the database.
00889  * $field => keys(type, null, default, key, length, extra)
00890  *
00891  * @param mixed $field Set to true to reload schema, or a string to return a specific field
00892  * @return array Array of table metadata
00893  * @access public
00894  */
00895     function schema($field = false) {
00896         if (!is_array($this->_schema) || $field === true) {
00897             $db =& ConnectionManager::getDataSource($this->useDbConfig);
00898             $db->cacheSources = ($this->cacheSources && $db->cacheSources);
00899             if ($db->isInterfaceSupported('describe') && $this->useTable !== false) {
00900                 $this->_schema = $db->describe($this, $field);
00901             } elseif ($this->useTable === false) {
00902                 $this->_schema = array();
00903             }
00904         }
00905         if (is_string($field)) {
00906             if (isset($this->_schema[$field])) {
00907                 return $this->_schema[$field];
00908             } else {
00909                 return null;
00910             }
00911         }
00912         return $this->_schema;
00913     }
00914 /**
00915  * Returns an associative array of field names and column types.
00916  *
00917  * @return array Field types indexed by field name
00918  * @access public
00919  */
00920     function getColumnTypes() {
00921         $columns = $this->schema();
00922         if (empty($columns)) {
00923             trigger_error(__('(Model::getColumnTypes) Unable to build model field data. If you are using a model without a database table, try implementing schema()', true), E_USER_WARNING);
00924         }
00925         $cols = array();
00926         foreach ($columns as $field => $values) {
00927             $cols[$field] = $values['type'];
00928         }
00929         return $cols;
00930     }
00931 /**
00932  * Returns the column type of a column in the model.
00933  *
00934  * @param string $column The name of the model column
00935  * @return string Column type
00936  * @access public
00937  */
00938     function getColumnType($column) {
00939         $db =& ConnectionManager::getDataSource($this->useDbConfig);
00940         $cols = $this->schema();
00941         $model = null;
00942 
00943         $column = str_replace(array($db->startQuote, $db->endQuote), '', $column);
00944 
00945         if (strpos($column, '.')) {
00946             list($model, $column) = explode('.', $column);
00947         }
00948         if ($model != $this->alias && isset($this->{$model})) {
00949             return $this->{$model}->getColumnType($column);
00950         }
00951         if (isset($cols[$column]) && isset($cols[$column]['type'])) {
00952             return $cols[$column]['type'];
00953         }
00954         return null;
00955     }
00956 /**
00957  * Returns true if the supplied field exists in the model's database table.
00958  *
00959  * @param mixed $name Name of field to look for, or an array of names
00960  * @return mixed If $name is a string, returns a boolean indicating whether the field exists.
00961  *               If $name is an array of field names, returns the first field that exists,
00962  *               or false if none exist.
00963  * @access public
00964  */
00965     function hasField($name) {
00966         if (is_array($name)) {
00967             foreach ($name as $n) {
00968                 if ($this->hasField($n)) {
00969                     return $n;
00970                 }
00971             }
00972             return false;
00973         }
00974 
00975         if (empty($this->_schema)) {
00976             $this->schema();
00977         }
00978 
00979         if ($this->_schema != null) {
00980             return isset($this->_schema[$name]);
00981         }
00982         return false;
00983     }
00984 /**
00985  * Initializes the model for writing a new record, loading the default values
00986  * for those fields that are not defined in $data. Especially helpful for
00987  * saving data in loops.
00988  *
00989  * @param mixed $data Optional data array to assign to the model after it is created.  If null or false,
00990  *                    schema data defaults are not merged.
00991  * @param boolean $filterKey If true, overwrites any primary key input with an empty value
00992  * @return array The current Model::data; after merging $data and/or defaults from database
00993  * @access public
00994  * @link http://book.cakephp.org/view/75/Saving-Your-Data
00995  */
00996     function create($data = array(), $filterKey = false) {
00997         $defaults = array();
00998         $this->id = false;
00999         $this->data = array();
01000         $this->__exists = null;
01001         $this->validationErrors = array();
01002 
01003         if ($data !== null && $data !== false) {
01004             foreach ($this->schema() as $field => $properties) {
01005                 if ($this->primaryKey !== $field && isset($properties['default'])) {
01006                     $defaults[$field] = $properties['default'];
01007                 }
01008             }
01009             $this->set(Set::filter($defaults));
01010             $this->set($data);
01011         }
01012         if ($filterKey) {
01013             $this->set($this->primaryKey, false);
01014         }
01015         return $this->data;
01016     }
01017 /**
01018  * Returns a list of fields from the database, and sets the current model
01019  * data (Model::$data) with the record found.
01020  *
01021  * @param mixed $fields String of single fieldname, or an array of fieldnames.
01022  * @param mixed $id The ID of the record to read
01023  * @return array Array of database fields, or false if not found
01024  * @access public
01025  */
01026     function read($fields = null, $id = null) {
01027         $this->validationErrors = array();
01028 
01029         if ($id != null) {
01030             $this->id = $id;
01031         }
01032 
01033         $id = $this->id;
01034 
01035         if (is_array($this->id)) {
01036             $id = $this->id[0];
01037         }
01038 
01039         if ($id !== null && $id !== false) {
01040             $this->data = $this->find('first', array(
01041                 'conditions' => array($this->alias . '.' . $this->primaryKey => $id),
01042                 'fields' => $fields
01043             ));
01044             return $this->data;
01045         } else {
01046             return false;
01047         }
01048     }
01049 /**
01050  * Returns the contents of a single field given the supplied conditions, in the
01051  * supplied order.
01052  *
01053  * @param string $name Name of field to get
01054  * @param array $conditions SQL conditions (defaults to NULL)
01055  * @param string $order SQL ORDER BY fragment
01056  * @return string field contents, or false if not found
01057  * @access public
01058  * @link http://book.cakephp.org/view/453/field
01059  */
01060     function field($name, $conditions = null, $order = null) {
01061         if ($conditions === null && $this->id !== false) {
01062             $conditions = array($this->alias . '.' . $this->primaryKey => $this->id);
01063         }
01064         if ($this->recursive >= 1) {
01065             $recursive = -1;
01066         } else {
01067             $recursive = $this->recursive;
01068         }
01069         if ($data = $this->find($conditions, $name, $order, $recursive)) {
01070             if (strpos($name, '.') === false) {
01071                 if (isset($data[$this->alias][$name])) {
01072                     return $data[$this->alias][$name];
01073                 }
01074             } else {
01075                 $name = explode('.', $name);
01076                 if (isset($data[$name[0]][$name[1]])) {
01077                     return $data[$name[0]][$name[1]];
01078                 }
01079             }
01080             if (isset($data[0]) && count($data[0]) > 0) {
01081                 $name = key($data[0]);
01082                 return $data[0][$name];
01083             }
01084         } else {
01085             return false;
01086         }
01087     }
01088 /**
01089  * Saves the value of a single field to the database, based on the current
01090  * model ID.
01091  *
01092  * @param string $name Name of the table field
01093  * @param mixed $value Value of the field
01094  * @param array $validate See $options param in Model::save(). Does not respect 'fieldList' key if passed
01095  * @return boolean See Model::save()
01096  * @access public
01097  * @see Model::save()
01098  * @link http://book.cakephp.org/view/75/Saving-Your-Data
01099  */
01100     function saveField($name, $value, $validate = false) {
01101         $id = $this->id;
01102         $this->create(false);
01103 
01104         if (is_array($validate)) {
01105             $options = array_merge(array('validate' => false, 'fieldList' => array($name)), $validate);
01106         } else {
01107             $options = array('validate' => $validate, 'fieldList' => array($name));
01108         }
01109         return $this->save(array($this->alias => array($this->primaryKey => $id, $name => $value)), $options);
01110     }
01111 /**
01112  * Saves model data (based on white-list, if supplied) to the database. By
01113  * default, validation occurs before save.
01114  *
01115  * @param array $data Data to save.
01116  * @param mixed $validate Either a boolean, or an array.
01117  *          If a boolean, indicates whether or not to validate before saving.
01118  *          If an array, allows control of validate, callbacks, and fieldList
01119  * @param array $fieldList List of fields to allow to be written
01120  * @return mixed On success Model::$data if its not empty or true, false on failure
01121  * @access public
01122  * @link http://book.cakephp.org/view/75/Saving-Your-Data
01123  */
01124     function save($data = null, $validate = true, $fieldList = array()) {
01125         $defaults = array('validate' => true, 'fieldList' => array(), 'callbacks' => true);
01126         $_whitelist = $this->whitelist;
01127         $fields = array();
01128 
01129         if (!is_array($validate)) {
01130             $options = array_merge($defaults, compact('validate', 'fieldList', 'callbacks'));
01131         } else {
01132             $options = array_merge($defaults, $validate);
01133         }
01134 
01135         if (!empty($options['fieldList'])) {
01136             $this->whitelist = $options['fieldList'];
01137         } elseif ($options['fieldList'] === null) {
01138             $this->whitelist = array();
01139         }
01140         $this->set($data);
01141 
01142         if (empty($this->data) && !$this->hasField(array('created', 'updated', 'modified'))) {
01143             return false;
01144         }
01145 
01146         foreach (array('created', 'updated', 'modified') as $field) {
01147             $keyPresentAndEmpty = (
01148                 isset($this->data[$this->alias]) &&
01149                 array_key_exists($field, $this->data[$this->alias]) &&
01150                 $this->data[$this->alias][$field] === null
01151             );
01152             if ($keyPresentAndEmpty) {
01153                 unset($this->data[$this->alias][$field]);
01154             }
01155         }
01156 
01157         $this->exists();
01158         $dateFields = array('modified', 'updated');
01159 
01160         if (!$this->__exists) {
01161             $dateFields[] = 'created';
01162         }
01163         if (isset($this->data[$this->alias])) {
01164             $fields = array_keys($this->data[$this->alias]);
01165         }
01166         if ($options['validate'] && !$this->validates($options)) {
01167             $this->whitelist = $_whitelist;
01168             return false;
01169         }
01170 
01171         $db =& ConnectionManager::getDataSource($this->useDbConfig);
01172 
01173         foreach ($dateFields as $updateCol) {
01174             if ($this->hasField($updateCol) && !in_array($updateCol, $fields)) {
01175                 $default = array('formatter' => 'date');
01176                 $colType = array_merge($default, $db->columns[$this->getColumnType($updateCol)]);
01177                 if (!array_key_exists('format', $colType)) {
01178                     $time = strtotime('now');
01179                 } else {
01180                     $time = $colType['formatter']($colType['format']);
01181                 }
01182                 if (!empty($this->whitelist)) {
01183                     $this->whitelist[] = $updateCol;
01184                 }
01185                 $this->set($updateCol, $time);
01186             }
01187         }
01188 
01189         if ($options['callbacks'] === true || $options['callbacks'] === 'before') {
01190             $result = $this->Behaviors->trigger($this, 'beforeSave', array($options), array(
01191                 'break' => true, 'breakOn' => false
01192             ));
01193             if (!$result || !$this->beforeSave($options)) {
01194                 $this->whitelist = $_whitelist;
01195                 return false;
01196             }
01197         }
01198         $fields = $values = array();
01199 
01200         if (isset($this->data[$this->alias][$this->primaryKey]) && empty($this->data[$this->alias][$this->primaryKey])) {
01201             unset($this->data[$this->alias][$this->primaryKey]);
01202         }
01203 
01204         foreach ($this->data as $n => $v) {
01205             if (isset($this->hasAndBelongsToMany[$n])) {
01206                 if (isset($v[$n])) {
01207                     $v = $v[$n];
01208                 }
01209                 $joined[$n] = $v;
01210             } else {
01211                 if ($n === $this->alias) {
01212                     foreach (array('created', 'updated', 'modified') as $field) {
01213                         if (array_key_exists($field, $v) && empty($v[$field])) {
01214                             unset($v[$field]);
01215                         }
01216                     }
01217 
01218                     foreach ($v as $x => $y) {
01219                         if ($this->hasField($x) && (empty($this->whitelist) || in_array($x, $this->whitelist))) {
01220                             list($fields[], $values[]) = array($x, $y);
01221                         }
01222                     }
01223                 }
01224             }
01225         }
01226         $count = count($fields);
01227 
01228         if (!$this->__exists && $count > 0) {
01229             $this->id = false;
01230         }
01231         $success = true;
01232         $created = false;
01233 
01234         if ($count > 0) {
01235             $cache = $this->_prepareUpdateFields(array_combine($fields, $values));
01236 
01237             if (!empty($this->id)) {
01238                 $success = (bool)$db->update($this, $fields, $values);
01239             } else {
01240                 foreach ($this->_schema as $field => $properties) {
01241                     if ($this->primaryKey === $field) {
01242                         $fInfo = $this->_schema[$field];
01243                         $isUUID = ($fInfo['length'] == 36 &&
01244                             ($fInfo['type'] === 'string' || $fInfo['type'] === 'binary')
01245                         );
01246                         if (empty($this->data[$this->alias][$this->primaryKey]) && $isUUID) {
01247                             list($fields[], $values[]) = array($this->primaryKey, String::uuid());
01248                         }
01249                         break;
01250                     }
01251                 }
01252 
01253                 if (!$db->create($this, $fields, $values)) {
01254                     $success = $created = false;
01255                 } else {
01256                     $created = true;
01257                 }
01258             }
01259 
01260             if ($success && !empty($this->belongsTo)) {
01261                 $this->updateCounterCache($cache, $created);
01262             }
01263         }
01264 
01265         if (!empty($joined) && $success === true) {
01266             $this->__saveMulti($joined, $this->id);
01267         }
01268 
01269         if ($success && $count > 0) {
01270             if (!empty($this->data)) {
01271                 $success = $this->data;
01272             }
01273             if ($options['callbacks'] === true || $options['callbacks'] === 'after') {
01274                 $this->Behaviors->trigger($this, 'afterSave', array($created, $options));
01275                 $this->afterSave($created);
01276             }
01277             if (!empty($this->data)) {
01278                 $success = Set::merge($success, $this->data);
01279             }
01280             $this->data = false;
01281             $this->__exists = null;
01282             $this->_clearCache();
01283             $this->validationErrors = array();
01284         }
01285         $this->whitelist = $_whitelist;
01286         return $success;
01287     }
01288 /**
01289  * Saves model hasAndBelongsToMany data to the database.
01290  *
01291  * @param array $joined Data to save
01292  * @param mixed $id ID of record in this model
01293  * @access private
01294  */
01295     function __saveMulti($joined, $id) {
01296         $db =& ConnectionManager::getDataSource($this->useDbConfig);
01297 
01298         foreach ($joined as $assoc => $data) {
01299 
01300             if (isset($this->hasAndBelongsToMany[$assoc])) {
01301                 list($join) = $this->joinModel($this->hasAndBelongsToMany[$assoc]['with']);
01302 
01303                 $conditions = array($join . '.' . $this->hasAndBelongsToMany[$assoc]['foreignKey'] => $id);
01304 
01305                 $links = $this->{$join}->find('all', array(
01306                     'conditions' => $conditions,
01307                     'recursive' => -1,
01308                     'fields' => $this->hasAndBelongsToMany[$assoc]['associationForeignKey']
01309                 ));
01310 
01311                 $isUUID = !empty($this->{$join}->primaryKey) && (
01312                         $this->{$join}->_schema[$this->{$join}->primaryKey]['length'] == 36 && (
01313                         $this->{$join}->_schema[$this->{$join}->primaryKey]['type'] === 'string' ||
01314                         $this->{$join}->_schema[$this->{$join}->primaryKey]['type'] === 'binary'
01315                     )
01316                 );
01317 
01318                 $newData = $newValues = array();
01319                 $primaryAdded = false;
01320 
01321                 $fields =  array(
01322                     $db->name($this->hasAndBelongsToMany[$assoc]['foreignKey']),
01323                     $db->name($this->hasAndBelongsToMany[$assoc]['associationForeignKey'])
01324                 );
01325 
01326                 $idField = $db->name($this->{$join}->primaryKey);
01327                 if ($isUUID && !in_array($idField, $fields)) {
01328                     $fields[] = $idField;
01329                     $primaryAdded = true;
01330                 }
01331 
01332                 foreach ((array)$data as $row) {
01333                     if ((is_string($row) && (strlen($row) == 36 || strlen($row) == 16)) || is_numeric($row)) {
01334                         $values = array(
01335                             $db->value($id, $this->getColumnType($this->primaryKey)),
01336                             $db->value($row)
01337                         );
01338                         if ($isUUID && $primaryAdded) {
01339                             $values[] = $db->value(String::uuid());
01340                         }
01341                         $values = join(',', $values);
01342                         $newValues[] = "({$values})";
01343                         unset($values);
01344                     } elseif (isset($row[$this->hasAndBelongsToMany[$assoc]['associationForeignKey']])) {
01345                         $newData[] = $row;
01346                     } elseif (isset($row[$join]) && isset($row[$join][$this->hasAndBelongsToMany[$assoc]['associationForeignKey']])) {
01347                         $newData[] = $row[$join];
01348                     }
01349                 }
01350 
01351                 if ($this->hasAndBelongsToMany[$assoc]['unique']) {
01352                     $associationForeignKey = "{$join}." . $this->hasAndBelongsToMany[$assoc]['associationForeignKey'];
01353                     $oldLinks = Set::extract($links, "{n}.{$associationForeignKey}");
01354                     if (!empty($oldLinks)) {
01355                         $conditions[$associationForeignKey] = $oldLinks;
01356                         $db->delete($this->{$join}, $conditions);
01357                     }
01358                 }
01359 
01360                 if (!empty($newData)) {
01361                     foreach ($newData as $data) {
01362                         $data[$this->hasAndBelongsToMany[$assoc]['foreignKey']] = $id;
01363                         $this->{$join}->create($data);
01364                         $this->{$join}->save();
01365                     }
01366                 }
01367 
01368                 if (!empty($newValues)) {
01369                     $fields =  join(',', $fields);
01370                     $db->insertMulti($this->{$join}, $fields, $newValues);
01371                 }
01372             }
01373         }
01374     }
01375 /**
01376  * Updates the counter cache of belongsTo associations after a save or delete operation
01377  *
01378  * @param array $keys Optional foreign key data, defaults to the information $this->data
01379  * @param boolean $created True if a new record was created, otherwise only associations with
01380  *                'counterScope' defined get updated
01381  * @return void
01382  * @access public
01383  */
01384     function updateCounterCache($keys = array(), $created = false) {
01385         $keys = empty($keys) ? $this->data[$this->alias] : $keys;
01386         $keys['old'] = isset($keys['old']) ? $keys['old'] : array();
01387 
01388         foreach ($this->belongsTo as $parent => $assoc) {
01389             $foreignKey = $assoc['foreignKey'];
01390             $fkQuoted = $this->escapeField($assoc['foreignKey']);
01391 
01392             if (!empty($assoc['counterCache'])) {
01393                 if ($assoc['counterCache'] === true) {
01394                     $assoc['counterCache'] = Inflector::underscore($this->alias) . '_count';
01395                 }
01396                 if (!$this->{$parent}->hasField($assoc['counterCache'])) {
01397                     continue;
01398                 }
01399 
01400                 if (!array_key_exists($foreignKey, $keys)) {
01401                     $keys[$foreignKey] = $this->field($foreignKey);
01402                 }
01403                 $recursive = (isset($assoc['counterScope']) ? 1 : -1);
01404                 $conditions = ($recursive == 1) ? (array)$assoc['counterScope'] : array();
01405 
01406                 if (isset($keys['old'][$foreignKey])) {
01407                     if ($keys['old'][$foreignKey] != $keys[$foreignKey]) {
01408                         $conditions[$fkQuoted] = $keys['old'][$foreignKey];
01409                         $count = intval($this->find('count', compact('conditions', 'recursive')));
01410 
01411                         $this->{$parent}->updateAll(
01412                             array($assoc['counterCache'] => $count),
01413                             array($this->{$parent}->escapeField() => $keys['old'][$foreignKey])
01414                         );
01415                     }
01416                 }
01417                 $conditions[$fkQuoted] = $keys[$foreignKey];
01418 
01419                 if ($recursive == 1) {
01420                     $conditions = array_merge($conditions, (array)$assoc['counterScope']);
01421                 }
01422                 $count = intval($this->find('count', compact('conditions', 'recursive')));
01423 
01424                 $this->{$parent}->updateAll(
01425                     array($assoc['counterCache'] => $count),
01426                     array($this->{$parent}->escapeField() => $keys[$foreignKey])
01427                 );
01428             }
01429         }
01430     }
01431 /**
01432  * Helper method for Model::updateCounterCache().  Checks the fields to be updated for
01433  *
01434  * @param array $data The fields of the record that will be updated
01435  * @return array Returns updated foreign key values, along with an 'old' key containing the old
01436  *               values, or empty if no foreign keys are updated.
01437  * @access protected
01438  */
01439     function _prepareUpdateFields($data) {
01440         $foreignKeys = array();
01441         foreach ($this->belongsTo as $assoc => $info) {
01442             if ($info['counterCache']) {
01443                 $foreignKeys[$assoc] = $info['foreignKey'];
01444             }
01445         }
01446         $included = array_intersect($foreignKeys, array_keys($data));
01447 
01448         if (empty($included) || empty($this->id)) {
01449             return array();
01450         }
01451         $old = $this->find('first', array(
01452             'conditions' => array($this->primaryKey => $this->id),
01453             'fields' => array_values($included),
01454             'recursive' => -1
01455         ));
01456         return array_merge($data, array('old' => $old[$this->alias]));
01457     }
01458 /**
01459  * Saves multiple individual records for a single model; Also works with a single record, as well as
01460  * all its associated records.
01461  *
01462  * @param array $data Record data to save.  This can be either a numerically-indexed array (for saving multiple
01463  *                      records of the same type), or an array indexed by association name.
01464  * @param array $options Options to use when saving record data, which are as follows:
01465  *                          - validate: Set to false to disable validation, true to validate each record before
01466  *                            saving, 'first' to validate *all* records before any are saved, or 'only' to only
01467  *                            validate the records, but not save them.
01468  *                          - atomic: If true (default), will attempt to save all records in a single transaction.
01469  *                            Should be set to false if database/table does not support transactions.
01470  *                              If false, we return an array similar to the $data array passed, but values are set to true/false
01471  *                              depending on whether each record saved successfully.
01472  *                          - fieldList: Equivalent to the $fieldList parameter in Model::save()
01473  * @return mixed True on success, or false on failure
01474  * @access public
01475  * @link http://book.cakephp.org/view/84/Saving-Related-Model-Data-hasOne-hasMany-belongsTo
01476  * @link http://book.cakephp.org/view/75/Saving-Your-Data
01477  */
01478     function saveAll($data = null, $options = array()) {
01479         if (empty($data)) {
01480             $data = $this->data;
01481         }
01482         $db =& ConnectionManager::getDataSource($this->useDbConfig);
01483 
01484         $options = array_merge(array('validate' => true, 'atomic' => true), $options);
01485         $this->validationErrors = $validationErrors = array();
01486         $validates = true;
01487         $return = array();
01488 
01489         if ($options['atomic'] && $options['validate'] !== 'only') {
01490             $db->begin($this);
01491         }
01492 
01493         if (Set::numeric(array_keys($data))) {
01494             while ($validates) {
01495                 foreach ($data as $key => $record) {
01496                     if (!$currentValidates = $this->__save($record, $options)) {
01497                         $validationErrors[$key] = $this->validationErrors;
01498                     }
01499 
01500                     if ($options['validate'] === 'only' || $options['validate'] === 'first') {
01501                         $validating = true;
01502                         if ($options['atomic']) {
01503                             $validates = $validates && $currentValidates;
01504                         } else {
01505                             $validates = $currentValidates;
01506                         }
01507                     } else {
01508                         $validating = false;
01509                         $validates = $currentValidates;
01510                     }
01511 
01512                     if (!$options['atomic']) {
01513                         $return[] = $validates;
01514                     } elseif (!$validates && !$validating) {
01515                         break;
01516                     }
01517                 }
01518                 $this->validationErrors = $validationErrors;
01519 
01520                 switch (true) {
01521                     case ($options['validate'] === 'only'):
01522                         return ($options['atomic'] ? $validates : $return);
01523                     break;
01524                     case ($options['validate'] === 'first'):
01525                         $options['validate'] = true;
01526                         continue;
01527                     break;
01528                     default:
01529                         if ($options['atomic']) {
01530                             if ($validates && ($db->commit($this) !== false)) {
01531                                 return true;
01532                             }
01533                             $db->rollback($this);
01534                             return false;
01535                         }
01536                         return $return;
01537                     break;
01538                 }
01539             }
01540             return $return;
01541         }
01542         $associations = $this->getAssociated();
01543 
01544         while ($validates) {
01545             foreach ($data as $association => $values) {
01546                 if (isset($associations[$association])) {
01547                     switch ($associations[$association]) {
01548                         case 'belongsTo':
01549                             if ($this->{$association}->__save($values, $options)) {
01550                                 $data[$this->alias][$this->belongsTo[$association]['foreignKey']] = $this->{$association}->id;
01551                             } else {
01552                                 $validationErrors[$association] = $this->{$association}->validationErrors;
01553                                 $validates = false;
01554                             }
01555                             if (!$options['atomic']) {
01556                                 $return[$association][] = $validates;
01557                             }
01558                         break;
01559                     }
01560                 }
01561             }
01562             if (!$this->__save($data, $options)) {
01563                 $validationErrors[$this->alias] = $this->validationErrors;
01564                 $validates = false;
01565             }
01566             if (!$options['atomic']) {
01567                 $return[$this->alias] = $validates;
01568             }
01569             $validating = ($options['validate'] === 'only' || $options['validate'] === 'first');
01570 
01571             foreach ($data as $association => $values) {
01572                 if (!$validates && !$validating) {
01573                     break;
01574                 }
01575                 if (isset($associations[$association])) {
01576                     $type = $associations[$association];
01577                     switch ($type) {
01578                         case 'hasOne':
01579                             $values[$this->{$type}[$association]['foreignKey']] = $this->id;
01580                             if (!$this->{$association}->__save($values, $options)) {
01581                                 $validationErrors[$association] = $this->{$association}->validationErrors;
01582                                 $validates = false;
01583                             }
01584                             if (!$options['atomic']) {
01585                                 $return[$association][] = $validates;
01586                             }
01587                         break;
01588                         case 'hasMany':
01589                             foreach ($values as $i => $value) {
01590                                 $values[$i][$this->{$type}[$association]['foreignKey']] =  $this->id;
01591                             }
01592                             $_options = array_merge($options, array('atomic' => false));
01593 
01594                             if ($_options['validate'] === 'first') {
01595                                 $_options['validate'] = 'only';
01596                             }
01597                             $_return = $this->{$association}->saveAll($values, $_options);
01598 
01599                             if ($_return === false || (is_array($_return) && in_array(false, $_return, true))) {
01600                                 $validationErrors[$association] = $this->{$association}->validationErrors;
01601                                 $validates = false;
01602                             }
01603                             if (is_array($_return)) {
01604                                 foreach ($_return as $val) {
01605                                     if (!isset($return[$association])) {
01606                                         $return[$association] = array();
01607                                     } elseif (!is_array($return[$association])) {
01608                                         $return[$association] = array($return[$association]);
01609                                     }
01610                                     $return[$association][] = $val;
01611                                 }
01612                             } else {
01613                                 $return[$association] = $_return;
01614                             }
01615                         break;
01616                     }
01617                 }
01618             }
01619             $this->validationErrors = $validationErrors;
01620 
01621             if (isset($validationErrors[$this->alias])) {
01622                 $this->validationErrors = $validationErrors[$this->alias];
01623             }
01624 
01625             switch (true) {
01626                 case ($options['validate'] === 'only'):
01627                     return ($options['atomic'] ? $validates : $return);
01628                 break;
01629                 case ($options['validate'] === 'first'):
01630                     $options['validate'] = true;
01631                     continue;
01632                 break;
01633                 default:
01634                     if ($options['atomic']) {
01635                         if ($validates) {
01636                             return ($db->commit($this) !== false);
01637                         } else {
01638                             $db->rollback($this);
01639                         }
01640                     }
01641                     return $return;
01642                 break;
01643             }
01644         }
01645     }
01646 /**
01647  * Private helper method used by saveAll.
01648  *
01649  * @return boolean Success
01650  * @access private
01651  * @see Model::saveAll()
01652  */
01653     function __save($data, $options) {
01654         if ($options['validate'] === 'first' || $options['validate'] === 'only') {
01655             if (!($this->create($data) && $this->validates($options))) {
01656                 return false;
01657             }
01658         } elseif (!($this->create(null) !== null && $this->save($data, $options))) {
01659             return false;
01660         }
01661         return true;
01662     }
01663 /**
01664  * Updates multiple model records based on a set of conditions.
01665  *
01666  * @param array $fields Set of fields and values, indexed by fields.
01667  *                      Fields are treated as SQL snippets, to insert literal values manually escape your data.
01668  * @param mixed $conditions Conditions to match, true for all records
01669  * @return boolean True on success, false on failure
01670  * @access public
01671  * @link http://book.cakephp.org/view/75/Saving-Your-Data
01672  */
01673     function updateAll($fields, $conditions = true) {
01674         $db =& ConnectionManager::getDataSource($this->useDbConfig);
01675         return $db->update($this, $fields, null, $conditions);
01676     }
01677 /**
01678  * Alias for del().
01679  *
01680  * @param mixed $id ID of record to delete
01681  * @param boolean $cascade Set to true to delete records that depend on this record
01682  * @return boolean True on success
01683  * @access public
01684  * @see Model::del()
01685  * @link http://book.cakephp.org/view/691/remove
01686  */
01687     function remove($id = null, $cascade = true) {
01688         return $this->del($id, $cascade);
01689     }
01690 /**
01691  * Removes record for given ID. If no ID is given, the current ID is used. Returns true on success.
01692  *
01693  * @param mixed $id ID of record to delete
01694  * @param boolean $cascade Set to true to delete records that depend on this record
01695  * @return boolean True on success
01696  * @access public
01697  * @link http://book.cakephp.org/view/690/del
01698  */
01699     function del($id = null, $cascade = true) {
01700         if (!empty($id)) {
01701             $this->id = $id;
01702         }
01703         $id = $this->id;
01704 
01705         if ($this->exists() && $this->beforeDelete($cascade)) {
01706             $db =& ConnectionManager::getDataSource($this->useDbConfig);
01707             if (!$this->Behaviors->trigger($this, 'beforeDelete', array($cascade), array('break' => true, 'breakOn' => false))) {
01708                 return false;
01709             }
01710             $this->_deleteDependent($id, $cascade);
01711             $this->_deleteLinks($id);
01712             $this->id = $id;
01713 
01714             if (!empty($this->belongsTo)) {
01715                 $keys = $this->find('first', array('fields' => $this->__collectForeignKeys()));
01716             }
01717 
01718             if ($db->delete($this)) {
01719                 if (!empty($this->belongsTo)) {
01720                     $this->updateCounterCache($keys[$this->alias]);
01721                 }
01722                 $this->Behaviors->trigger($this, 'afterDelete');
01723                 $this->afterDelete();
01724                 $this->_clearCache();
01725                 $this->id = false;
01726                 $this->__exists = null;
01727                 return true;
01728             }
01729         }
01730         return false;
01731     }
01732 /**
01733  * Alias for del().
01734  *
01735  * @param mixed $id ID of record to delete
01736  * @param boolean $cascade Set to true to delete records that depend on this record
01737  * @return boolean True on success
01738  * @access public
01739  * @see Model::del()
01740  */
01741     function delete($id = null, $cascade = true) {
01742         return $this->del($id, $cascade);
01743     }
01744 /**
01745  * Cascades model deletes through associated hasMany and hasOne child records.
01746  *
01747  * @param string $id ID of record that was deleted
01748  * @param boolean $cascade Set to true to delete records that depend on this record
01749  * @return void
01750  * @access protected
01751  */
01752     function _deleteDependent($id, $cascade) {
01753         if (!empty($this->__backAssociation)) {
01754             $savedAssociatons = $this->__backAssociation;
01755             $this->__backAssociation = array();
01756         }
01757         foreach (array_merge($this->hasMany, $this->hasOne) as $assoc => $data) {
01758             if ($data['dependent'] === true && $cascade === true) {
01759 
01760                 $model =& $this->{$assoc};
01761                 $conditions = array($model->escapeField($data['foreignKey']) => $id);
01762                 if ($data['conditions']) {
01763                     $conditions = array_merge($data['conditions'], $conditions);
01764                 }
01765                 $model->recursive = -1;
01766 
01767                 if (isset($data['exclusive']) && $data['exclusive']) {
01768                     $model->deleteAll($conditions);
01769                 } else {
01770                     $records = $model->find('all', array('conditions' => $conditions, 'fields' => $model->primaryKey));
01771 
01772                     if (!empty($records)) {
01773                         foreach ($records as $record) {
01774                             $model->delete($record[$model->alias][$model->primaryKey]);
01775                         }
01776                     }
01777                 }
01778             }
01779         }
01780         if (isset($savedAssociatons)) {
01781             $this->__backAssociation = $savedAssociatons;
01782         }
01783     }
01784 /**
01785  * Cascades model deletes through HABTM join keys.
01786  *
01787  * @param string $id ID of record that was deleted
01788  * @return void
01789  * @access protected
01790  */
01791     function _deleteLinks($id) {
01792         $db =& ConnectionManager::getDataSource($this->useDbConfig);
01793 
01794         foreach ($this->hasAndBelongsToMany as $assoc => $data) {
01795             $records = $this->{$data['with']}->find('all', array(
01796                 'conditions' => array_merge(array($this->{$data['with']}->escapeField($data['foreignKey']) => $id)),
01797                 'fields' => $this->{$data['with']}->primaryKey,
01798                 'recursive' => -1
01799             ));
01800             if (!empty($records)) {
01801                 foreach ($records as $record) {
01802                     $this->{$data['with']}->delete($record[$this->{$data['with']}->alias][$this->{$data['with']}->primaryKey]);
01803                 }
01804             }
01805         }
01806     }
01807 /**
01808  * Deletes multiple model records based on a set of conditions.
01809  *
01810  * @param mixed $conditions Conditions to match
01811  * @param boolean $cascade Set to true to delete records that depend on this record
01812  * @param boolean $callbacks Run callbacks (not being used)
01813  * @return boolean True on success, false on failure
01814  * @access public
01815  * @link http://book.cakephp.org/view/692/deleteAll
01816  */
01817     function deleteAll($conditions, $cascade = true, $callbacks = false) {
01818         if (empty($conditions)) {
01819             return false;
01820         }
01821         $db =& ConnectionManager::getDataSource($this->useDbConfig);
01822 
01823         if (!$cascade && !$callbacks) {
01824             return $db->delete($this, $conditions);
01825         } else {
01826             $ids = Set::extract(
01827                 $this->find('all', array_merge(array('fields' => "{$this->alias}.{$this->primaryKey}", 'recursive' => 0), compact('conditions'))),
01828                 "{n}.{$this->alias}.{$this->primaryKey}"
01829             );
01830 
01831             if (empty($ids)) {
01832                 return true;
01833             }
01834 
01835             if ($callbacks) {
01836                 $_id = $this->id;
01837                 $result = true;
01838                 foreach ($ids as $id) {
01839                     $result = ($result && $this->delete($id, $cascade));
01840                 }
01841                 $this->id = $_id;
01842                 return $result;
01843             } else {
01844                 foreach ($ids as $id) {
01845                     $this->_deleteLinks($id);
01846                     if ($cascade) {
01847                         $this->_deleteDependent($id, $cascade);
01848                     }
01849                 }
01850                 return $db->delete($this, array($this->alias . '.' . $this->primaryKey => $ids));
01851             }
01852         }
01853     }
01854 /**
01855  * Collects foreign keys from associations.
01856  *
01857  * @return array
01858  * @access private
01859  */
01860     function __collectForeignKeys($type = 'belongsTo') {
01861         $result = array();
01862 
01863         foreach ($this->{$type} as $assoc => $data) {
01864             if (isset($data['foreignKey']) && is_string($data['foreignKey'])) {
01865                 $result[$assoc] = $data['foreignKey'];
01866             }
01867         }
01868         return $result;
01869     }
01870 /**
01871  * Returns true if a record with the currently set ID exists.
01872  *
01873  * @param boolean $reset if true will force database query
01874  * @return boolean True if such a record exists
01875  * @access public
01876  */
01877     function exists($reset = false) {
01878         if (is_array($reset)) {
01879             extract($reset, EXTR_OVERWRITE);
01880         }
01881 
01882         if ($this->getID() === false || $this->useTable === false) {
01883             return false;
01884         }
01885         if (!empty($this->__exists) && $reset !== true) {
01886             return $this->__exists;
01887         }
01888         $conditions = array($this->alias . '.' . $this->primaryKey => $this->getID());
01889         $query = array('conditions' => $conditions, 'recursive' => -1, 'callbacks' => false);
01890 
01891         if (is_array($reset)) {
01892             $query = array_merge($query, $reset);
01893         }
01894         return $this->__exists = ($this->find('count', $query) > 0);
01895     }
01896 /**
01897  * Returns true if a record that meets given conditions exists.
01898  *
01899  * @param array $conditions SQL conditions array
01900  * @return boolean True if such a record exists
01901  * @access public
01902  */
01903     function hasAny($conditions = null) {
01904         return ($this->find('count', array('conditions' => $conditions, 'recursive' => -1)) != false);
01905     }
01906 /**
01907  * Returns a result set array.
01908  *
01909  * Also used to perform new-notation finds, where the first argument is type of find operation to perform
01910  * (all / first / count / neighbors / list / threaded ),
01911  * second parameter options for finding ( indexed array, including: 'conditions', 'limit',
01912  * 'recursive', 'page', 'fields', 'offset', 'order')
01913  *
01914  * Eg: find('all', array(
01915  *                  'conditions' => array('name' => 'Thomas Anderson'),
01916  *                  'fields' => array('name', 'email'),
01917  *                  'order' => 'field3 DESC',
01918  *                  'recursive' => 2,
01919  *                  'group' => 'type'));
01920  *
01921  * Specifying 'fields' for new-notation 'list':
01922  *  - If no fields are specified, then 'id' is used for key and 'model->displayField' is used for value.
01923  *  - If a single field is specified, 'id' is used for key and specified field is used for value.
01924  *  - If three fields are specified, they are used (in order) for key, value and group.
01925  *  - Otherwise, first and second fields are used for key and value.
01926  *
01927  * @param array $conditions SQL conditions array, or type of find operation (all / first / count / neighbors / list / threaded)
01928  * @param mixed $fields Either a single string of a field name, or an array of field names, or options for matching
01929  * @param string $order SQL ORDER BY conditions (e.g. "price DESC" or "name ASC")
01930  * @param integer $recursive The number of levels deep to fetch associated records
01931  * @return array Array of records
01932  * @access public
01933  * @link http://book.cakephp.org/view/449/find
01934  */
01935     function find($conditions = null, $fields = array(), $order = null, $recursive = null) {
01936         if (!is_string($conditions) || (is_string($conditions) && !array_key_exists($conditions, $this->_findMethods))) {
01937             $type = 'first';
01938             $query = array_merge(compact('conditions', 'fields', 'order', 'recursive'), array('limit' => 1));
01939         } else {
01940             list($type, $query) = array($conditions, $fields);
01941         }
01942 
01943         $db =& ConnectionManager::getDataSource($this->useDbConfig);
01944         $this->findQueryType = $type;
01945         $this->id = $this->getID();
01946 
01947         $query = array_merge(
01948             array(
01949                 'conditions' => null, 'fields' => null, 'joins' => array(), 'limit' => null,
01950                 'offset' => null, 'order' => null, 'page' => null, 'group' => null, 'callbacks' => true
01951             ),
01952             (array)$query
01953         );
01954 
01955         if ($type != 'all') {
01956             if ($this->_findMethods[$type] === true) {
01957                 $query = $this->{'_find' . ucfirst($type)}('before', $query);
01958             }
01959         }
01960 
01961         if (!is_numeric($query['page']) || intval($query['page']) < 1) {
01962             $query['page'] = 1;
01963         }
01964         if ($query['page'] > 1 && !empty($query['limit'])) {
01965             $query['offset'] = ($query['page'] - 1) * $query['limit'];
01966         }
01967         if ($query['order'] === null && $this->order !== null) {
01968             $query['order'] = $this->order;
01969         }
01970         $query['order'] = array($query['order']);
01971 
01972         if ($query['callbacks'] === true || $query['callbacks'] === 'before') {
01973             $return = $this->Behaviors->trigger($this, 'beforeFind', array($query), array(
01974                 'break' => true, 'breakOn' => false, 'modParams' => true
01975             ));
01976             $query = (is_array($return)) ? $return : $query;
01977 
01978             if ($return === false) {
01979                 return null;
01980             }
01981 
01982             $return = $this->beforeFind($query);
01983             $query = (is_array($return)) ? $return : $query;
01984 
01985             if ($return === false) {
01986                 return null;
01987             }
01988         }
01989 
01990         $results = $db->read($this, $query);
01991         $this->resetAssociations();
01992         $this->findQueryType = null;
01993 
01994         if ($query['callbacks'] === true || $query['callbacks'] === 'after') {
01995             $results = $this->__filterResults($results);
01996         }
01997 
01998         if ($type === 'all') {
01999             return $results;
02000         } else {
02001             if ($this->_findMethods[$type] === true) {
02002                 return $this->{'_find' . ucfirst($type)}('after', $query, $results);
02003             }
02004         }
02005     }
02006 /**
02007  * Handles the before/after filter logic for find('first') operations.  Only called by Model::find().
02008  *
02009  * @param string $state Either "before" or "after"
02010  * @param array $query
02011  * @param array $data
02012  * @return array
02013  * @access protected
02014  * @see Model::find()
02015  */
02016     function _findFirst($state, $query, $results = array()) {
02017         if ($state == 'before') {
02018             $query['limit'] = 1;
02019             if (empty($query['conditions']) && !empty($this->id)) {
02020                 $query['conditions'] = array($this->escapeField() => $this->id);
02021             }
02022             return $query;
02023         } elseif ($state == 'after') {
02024             if (empty($results[0])) {
02025                 return false;
02026             }
02027             return $results[0];
02028         }
02029     }
02030 /**
02031  * Handles the before/after filter logic for find('count') operations.  Only called by Model::find().
02032  *
02033  * @param string $state Either "before" or "after"
02034  * @param array $query
02035  * @param array $data
02036  * @return int The number of records found, or false
02037  * @access protected
02038  * @see Model::find()
02039  */
02040     function _findCount($state, $query, $results = array()) {
02041         if ($state == 'before') {
02042             $db =& ConnectionManager::getDataSource($this->useDbConfig);
02043             if (empty($query['fields'])) {
02044                 $query['fields'] = $db->calculate($this, 'count');
02045             } elseif (is_string($query['fields'])  && !preg_match('/count/i', $query['fields'])) {
02046                 $query['fields'] = $db->calculate($this, 'count', array(
02047                     $db->expression($query['fields']), 'count'
02048                 ));
02049             }
02050             $query['order'] = false;
02051             return $query;
02052         } elseif ($state == 'after') {
02053             if (isset($results[0][0]['count'])) {
02054                 return intval($results[0][0]['count']);
02055             } elseif (isset($results[0][$this->alias]['count'])) {
02056                 return intval($results[0][$this->alias]['count']);
02057             }
02058             return false;
02059         }
02060     }
02061 /**
02062  * Handles the before/after filter logic for find('list') operations.  Only called by Model::find().
02063  *
02064  * @param string $state Either "before" or "after"
02065  * @param array $query
02066  * @param array $data
02067  * @return array Key/value pairs of primary keys/display field values of all records found
02068  * @access protected
02069  * @see Model::find()
02070  */
02071     function _findList($state, $query, $results = array()) {
02072         if ($state == 'before') {
02073             if (empty($query['fields'])) {
02074                 $query['fields'] = array("{$this->alias}.{$this->primaryKey}", "{$this->alias}.{$this->displayField}");
02075                 $list = array("{n}.{$this->alias}.{$this->primaryKey}", "{n}.{$this->alias}.{$this->displayField}", null);
02076             } else {
02077                 if (!is_array($query['fields'])) {
02078                     $query['fields'] = String::tokenize($query['fields']);
02079                 }
02080 
02081                 if (count($query['fields']) == 1) {
02082                     if (strpos($query['fields'][0], '.') === false) {
02083                         $query['fields'][0] = $this->alias . '.' . $query['fields'][0];
02084                     }
02085 
02086                     $list = array("{n}.{$this->alias}.{$this->primaryKey}", '{n}.' . $query['fields'][0], null);
02087                     $query['fields'] = array("{$this->alias}.{$this->primaryKey}", $query['fields'][0]);
02088                 } elseif (count($query['fields']) == 3) {
02089                     for ($i = 0; $i < 3; $i++) {
02090                         if (strpos($query['fields'][$i], '.') === false) {
02091                             $query['fields'][$i] = $this->alias . '.' . $query['fields'][$i];
02092                         }
02093                     }
02094 
02095                     $list = array('{n}.' . $query['fields'][0], '{n}.' . $query['fields'][1], '{n}.' . $query['fields'][2]);
02096                 } else {
02097                     for ($i = 0; $i < 2; $i++) {
02098                         if (strpos($query['fields'][$i], '.') === false) {
02099                             $query['fields'][$i] = $this->alias . '.' . $query['fields'][$i];
02100                         }
02101                     }
02102 
02103                     $list = array('{n}.' . $query['fields'][0], '{n}.' . $query['fields'][1], null);
02104                 }
02105             }
02106             if (!isset($query['recursive']) || $query['recursive'] === null) {
02107                 $query['recursive'] = -1;
02108             }
02109             list($query['list']['keyPath'], $query['list']['valuePath'], $query['list']['groupPath']) = $list;
02110             return $query;
02111         } elseif ($state == 'after') {
02112             if (empty($results)) {
02113                 return array();
02114             }
02115             $lst = $query['list'];
02116             return Set::combine($results, $lst['keyPath'], $lst['valuePath'], $lst['groupPath']);
02117         }
02118     }
02119 /**
02120  * Detects the previous field's value, then uses logic to find the 'wrapping'
02121  * rows and return them.
02122  *
02123  * @param string $state Either "before" or "after"
02124  * @param mixed $query
02125  * @param array $results
02126  * @return array
02127  * @access protected
02128  */
02129     function _findNeighbors($state, $query, $results = array()) {
02130         if ($state == 'before') {
02131             $query = array_merge(array('recursive' => 0), $query);
02132             extract($query);
02133             $conditions = (array)$conditions;
02134             if (isset($field) && isset($value)) {
02135                 if (strpos($field, '.') === false) {
02136                     $field = $this->alias . '.' . $field;
02137                 }
02138             } else {
02139                 $field = $this->alias . '.' . $this->primaryKey;
02140                 $value = $this->id;
02141             }
02142             $query['conditions'] =  array_merge($conditions, array($field . ' <' => $value));
02143             $query['order'] = $field . ' DESC';
02144             $query['limit'] = 1;
02145             $query['field'] = $field;
02146             $query['value'] = $value;
02147             return $query;
02148         } elseif ($state == 'after') {
02149             extract($query);
02150             unset($query['conditions'][$field . ' <']);
02151             $return = array();
02152             if (isset($results[0])) {
02153                 $prevVal = Set::extract('/' . str_replace('.', '/', $field), $results[0]);
02154                 $query['conditions'][$field . ' >='] = $prevVal[0];
02155                 $query['conditions'][$field . ' !='] = $value;
02156                 $query['limit'] = 2;
02157             } else {
02158                 $return['prev'] = null;
02159                 $query['conditions'][$field . ' >'] = $value;
02160                 $query['limit'] = 1;
02161             }
02162             $query['order'] = $field . ' ASC';
02163             $return2 = $this->find('all', $query);
02164             if (!array_key_exists('prev', $return)) {
02165                 $return['prev'] = $return2[0];
02166             }
02167             if (count($return2) == 2) {
02168                 $return['next'] = $return2[1];
02169             } elseif (count($return2) == 1 && !$return['prev']) {
02170                 $return['next'] = $return2[0];
02171             } else {
02172                 $return['next'] = null;
02173             }
02174             return $return;
02175         }
02176     }
02177 /**
02178  * In the event of ambiguous results returned (multiple top level results, with different parent_ids)
02179  * top level results with different parent_ids to the first result will be dropped
02180  *
02181  * @param mixed $state
02182  * @param mixed $query
02183  * @param array $results
02184  * @return array Threaded results
02185  * @access protected
02186  */
02187     function _findThreaded($state, $query, $results = array()) {
02188         if ($state == 'before') {
02189             return $query;
02190         } elseif ($state == 'after') {
02191             $return = $idMap = array();
02192             $ids = Set::extract($results, '{n}.' . $this->alias . '.' . $this->primaryKey);
02193 
02194             foreach ($results as $result) {
02195                 $result['children'] = array();
02196                 $id = $result[$this->alias][$this->primaryKey];
02197                 $parentId = $result[$this->alias]['parent_id'];
02198                 if (isset($idMap[$id]['children'])) {
02199                     $idMap[$id] = array_merge($result, (array)$idMap[$id]);
02200                 } else {
02201                     $idMap[$id] = array_merge($result, array('children' => array()));
02202                 }
02203                 if (!$parentId || !in_array($parentId, $ids)) {
02204                     $return[] =& $idMap[$id];
02205                 } else {
02206                     $idMap[$parentId]['children'][] =& $idMap[$id];
02207                 }
02208             }
02209             if (count($return) > 1) {
02210                 $ids = array_unique(Set::extract('/' . $this->alias . '/parent_id', $return));
02211                 if (count($ids) > 1) {
02212                     $root = $return[0][$this->alias]['parent_id'];
02213                     foreach ($return as $key => $value) {
02214                         if ($value[$this->alias]['parent_id'] != $root) {
02215                             unset($return[$key]);
02216                         }
02217                     }
02218                 }
02219             }
02220             return $return;
02221         }
02222     }
02223 /**
02224  * Passes query results through model and behavior afterFilter() methods.
02225  *
02226  * @param array Results to filter
02227  * @param boolean $primary If this is the primary model results (results from model where the find operation was performed)
02228  * @return array Set of filtered results
02229  * @access private
02230  */
02231     function __filterResults($results, $primary = true) {
02232         $return = $this->Behaviors->trigger($this, 'afterFind', array($results, $primary), array('modParams' => true));
02233         if ($return !== true) {
02234             $results = $return;
02235         }
02236         return $this->afterFind($results, $primary);
02237     }
02238 /**
02239  * Called only when bindTo<ModelName>() is used.
02240  * This resets the association arrays for the model back
02241  * to those originally defined in the model.
02242  *
02243  * @return boolean Success
02244  * @access public
02245  */
02246     function resetAssociations() {
02247         if (!empty($this->__backAssociation)) {
02248             foreach ($this->__associations as $type) {
02249                 if (isset($this->__backAssociation[$type])) {
02250                     $this->{$type} = $this->__backAssociation[$type];
02251                 }
02252             }
02253             $this->__backAssociation = array();
02254         }
02255 
02256         foreach ($this->__associations as $type) {
02257             foreach ($this->{$type} as $key => $name) {
02258                 if (!empty($this->{$key}->__backAssociation)) {
02259                     $this->{$key}->resetAssociations();
02260                 }
02261             }
02262         }
02263         $this->__backAssociation = array();
02264         return true;
02265     }
02266 /**
02267  * Returns false if any fields passed match any (by default, all if $or = false) of their matching values.
02268  *
02269  * @param array $fields Field/value pairs to search (if no values specified, they are pulled from $this->data)
02270  * @param boolean $or If false, all fields specified must match in order for a false return value
02271  * @return boolean False if any records matching any fields are found
02272  * @access public
02273  */
02274     function isUnique($fields, $or = true) {
02275         if (!is_array($fields)) {
02276             $fields = func_get_args();
02277             if (is_bool($fields[count($fields) - 1])) {
02278                 $or = $fields[count($fields) - 1];
02279                 unset($fields[count($fields) - 1]);
02280             }
02281         }
02282 
02283         foreach ($fields as $field => $value) {
02284             if (is_numeric($field)) {
02285                 unset($fields[$field]);
02286 
02287                 $field = $value;
02288                 if (isset($this->data[$this->alias][$field])) {
02289                     $value = $this->data[$this->alias][$field];
02290                 } else {
02291                     $value = null;
02292                 }
02293             }
02294 
02295             if (strpos($field, '.') === false) {
02296                 unset($fields[$field]);
02297                 $fields[$this->alias . '.' . $field] = $value;
02298             }
02299         }
02300         if ($or) {
02301             $fields = array('or' => $fields);
02302         }
02303         if (!empty($this->id)) {
02304             $fields[$this->alias . '.' . $this->primaryKey . ' !='] =  $this->id;
02305         }
02306         return ($this->find('count', array('conditions' => $fields, 'recursive' => -1)) == 0);
02307     }
02308 /**
02309  * Returns a resultset for a given SQL statement. Custom SQL queries should be performed with this method.
02310  *
02311  * @param string $sql SQL statement
02312  * @return array Resultset
02313  * @access public
02314  * @link http://book.cakephp.org/view/456/query
02315  */
02316     function query() {
02317         $params = func_get_args();
02318         $db =& ConnectionManager::getDataSource($this->useDbConfig);
02319         return call_user_func_array(array(&$db, 'query'), $params);
02320     }
02321 /**
02322  * Returns true if all fields pass validation.
02323  *
02324  * @param string $options An optional array of custom options to be made available in the beforeValidate callback
02325  * @return boolean True if there are no errors
02326  * @access public
02327  * @link http://book.cakephp.org/view/410/Validating-Data-from-the-Controller
02328  */
02329     function validates($options = array()) {
02330         $errors = $this->invalidFields($options);
02331         if (is_array($errors)) {
02332             return count($errors) === 0;
02333         }
02334         return $errors;
02335     }
02336 /**
02337  * Returns an array of fields that have failed validation.
02338  *
02339  * @param string $options An optional array of custom options to be made available in the beforeValidate callback
02340  * @return array Array of invalid fields
02341  * @access public
02342  * @link http://book.cakephp.org/view/410/Validating-Data-from-the-Controller
02343  */
02344     function invalidFields($options = array()) {
02345         if (
02346             !$this->Behaviors->trigger(
02347                 $this,
02348                 'beforeValidate',
02349                 array($options),
02350                 array('break' => true, 'breakOn' => false)
02351             ) ||
02352             $this->beforeValidate($options) === false
02353         ) {
02354             return $this->validationErrors;
02355         }
02356 
02357         if (!isset($this->validate) || empty($this->validate)) {
02358             return $this->validationErrors;
02359         }
02360 
02361         $data = $this->data;
02362         $methods = array_map('strtolower', get_class_methods($this));
02363         $behaviorMethods = array_keys($this->Behaviors->methods());
02364 
02365         if (isset($data[$this->alias])) {
02366             $data = $data[$this->alias];
02367         } elseif (!is_array($data)) {
02368             $data = array();
02369         }
02370 
02371         $Validation =& Validation::getInstance();
02372         $this->exists();
02373 
02374         $_validate = $this->validate;
02375         $whitelist = $this->whitelist;
02376 
02377         if (array_key_exists('fieldList', $options)) {
02378             $whitelist = $options['fieldList'];
02379         }
02380 
02381         if (!empty($whitelist)) {
02382             $validate = array();
02383             foreach ((array)$whitelist as $f) {
02384                 if (!empty($this->validate[$f])) {
02385                     $validate[$f] = $this->validate[$f];
02386                 }
02387             }
02388             $this->validate = $validate;
02389         }
02390 
02391         foreach ($this->validate as $fieldName => $ruleSet) {
02392             if (!is_array($ruleSet) || (is_array($ruleSet) && isset($ruleSet['rule']))) {
02393                 $ruleSet = array($ruleSet);
02394             }
02395             $default = array(
02396                 'allowEmpty' => null,
02397                 'required' => null,
02398                 'rule' => 'blank',
02399                 'last' => false,
02400                 'on' => null
02401             );
02402 
02403             foreach ($ruleSet as $index => $validator) {
02404                 if (!is_array($validator)) {
02405                     $validator = array('rule' => $validator);
02406                 }
02407                 $validator = array_merge($default, $validator);
02408 
02409                 if (isset($validator['message'])) {
02410                     $message = $validator['message'];
02411                 } else {
02412                     $message = __('This field cannot be left blank', true);
02413                 }
02414 
02415                 if (
02416                     empty($validator['on']) || ($validator['on'] == 'create' &&
02417                     !$this->__exists) || ($validator['on'] == 'update' && $this->__exists
02418                 )) {
02419                     $required = (
02420                         (!isset($data[$fieldName]) && $validator['required'] === true) ||
02421                         (
02422                             isset($data[$fieldName]) && (empty($data[$fieldName]) &&
02423                             !is_numeric($data[$fieldName])) && $validator['allowEmpty'] === false
02424                         )
02425                     );
02426 
02427                     if ($required) {
02428                         $this->invalidate($fieldName, $message);
02429                         if ($validator['last']) {
02430                             break;
02431                         }
02432                     } elseif (array_key_exists($fieldName, $data)) {
02433                         if (empty($data[$fieldName]) && $data[$fieldName] != '0' && $validator['allowEmpty'] === true) {
02434                             break;
02435                         }
02436                         if (is_array($validator['rule'])) {
02437                             $rule = $validator['rule'][0];
02438                             unset($validator['rule'][0]);
02439                             $ruleParams = array_merge(array($data[$fieldName]), array_values($validator['rule']));
02440                         } else {
02441                             $rule = $validator['rule'];
02442                             $ruleParams = array($data[$fieldName]);
02443                         }
02444 
02445                         $valid = true;
02446 
02447                         if (in_array(strtolower($rule), $methods)) {
02448                             $ruleParams[] = $validator;
02449                             $ruleParams[0] = array($fieldName => $ruleParams[0]);
02450                             $valid = $this->dispatchMethod($rule, $ruleParams);
02451                         } elseif (in_array($rule, $behaviorMethods) || in_array(strtolower($rule), $behaviorMethods)) {
02452                             $ruleParams[] = $validator;
02453                             $ruleParams[0] = array($fieldName => $ruleParams[0]);
02454                             $valid = $this->Behaviors->dispatchMethod($this, $rule, $ruleParams);
02455                         } elseif (method_exists($Validation, $rule)) {
02456                             $valid = $Validation->dispatchMethod($rule, $ruleParams);
02457                         } elseif (!is_array($validator['rule'])) {
02458                             $valid = preg_match($rule, $data[$fieldName]);
02459                         }
02460 
02461                         if (!$valid || (is_string($valid) && strlen($valid) > 0)) {
02462                             if (is_string($valid) && strlen($valid) > 0) {
02463                                 $validator['message'] = $valid;
02464                             } elseif (!isset($validator['message'])) {
02465                                 if (is_string($index)) {
02466                                     $validator['message'] = $index;
02467                                 } elseif (is_numeric($index) && count($ruleSet) > 1) {
02468                                     $validator['message'] = $index + 1;
02469                                 } else {
02470                                     $validator['message'] = $message;
02471                                 }
02472                             }
02473                             $this->invalidate($fieldName, $validator['message']);
02474 
02475                             if ($validator['last']) {
02476                                 break;
02477                             }
02478                         }
02479                     }
02480                 }
02481             }
02482         }
02483         $this->validate = $_validate;
02484         return $this->validationErrors;
02485     }
02486 /**
02487  * Marks a field as invalid, optionally setting the name of validation
02488  * rule (in case of multiple validation for field) that was broken.
02489  *
02490  * @param string $field The name of the field to invalidate
02491  * @param mixed $value Name of validation rule that was not failed, or validation message to
02492  *                     be returned. If no validation key is provided, defaults to true.
02493  * @access public
02494  */
02495     function invalidate($field, $value = true) {
02496         if (!is_array($this->validationErrors)) {
02497             $this->validationErrors = array();
02498         }
02499         $this->validationErrors[$field] = $value;
02500     }
02501 /**
02502  * Returns true if given field name is a foreign key in this model.
02503  *
02504  * @param string $field Returns true if the input string ends in "_id"
02505  * @return boolean True if the field is a foreign key listed in the belongsTo array.
02506  * @access public
02507  */
02508     function isForeignKey($field) {
02509         $foreignKeys = array();
02510         if (!empty($this->belongsTo)) {
02511             foreach ($this->belongsTo as $assoc => $data) {
02512                 $foreignKeys[] = $data['foreignKey'];
02513             }
02514         }
02515         return in_array($field, $foreignKeys);
02516     }
02517 /**
02518  * Returns the display field for this model.
02519  *
02520  * @return string The name of the display field for this Model (i.e. 'name', 'title').
02521  * @access public
02522  * @deprecated
02523  */
02524     function getDisplayField() {
02525         return $this->displayField;
02526     }
02527 /**
02528  * Escapes the field name and prepends the model name. Escaping is done according to the current database driver's rules.
02529  *
02530  * @param string $field Field to escape (e.g: id)
02531  * @param string $alias Alias for the model (e.g: Post)
02532  * @return string The name of the escaped field for this Model (i.e. id becomes `Post`.`id`).
02533  * @access public
02534  */
02535     function escapeField($field = null, $alias = null) {
02536         if (empty($alias)) {
02537             $alias = $this->alias;
02538         }
02539         if (empty($field)) {
02540             $field = $this->primaryKey;
02541         }
02542         $db =& ConnectionManager::getDataSource($this->useDbConfig);
02543         if (strpos($field, $db->name($alias)) === 0) {
02544             return $field;
02545         }
02546         return $db->name($alias . '.' . $field);
02547     }
02548 /**
02549  * Returns the current record's ID
02550  *
02551  * @param integer $list Index on which the composed ID is located
02552  * @return mixed The ID of the current record, false if no ID
02553  * @access public
02554  */
02555     function getID($list = 0) {
02556         if (empty($this->id) || (is_array($this->id) && isset($this->id[0]) && empty($this->id[0]))) {
02557             return false;
02558         }
02559 
02560         if (!is_array($this->id)) {
02561             return $this->id;
02562         }
02563 
02564         if (empty($this->id)) {
02565             return false;
02566         }
02567 
02568         if (isset($this->id[$list]) && !empty($this->id[$list])) {
02569             return $this->id[$list];
02570         } elseif (isset($this->id[$list])) {
02571             return false;
02572         }
02573 
02574         foreach ($this->id as $id) {
02575             return $id;
02576         }
02577 
02578         return false;
02579     }
02580 /**
02581  * Returns the ID of the last record this model inserted.
02582  *
02583  * @return mixed Last inserted ID
02584  * @access public
02585  */
02586     function getLastInsertID() {
02587         return $this->getInsertID();
02588     }
02589 /**
02590  * Returns the ID of the last record this model inserted.
02591  *
02592  * @return mixed Last inserted ID
02593  * @access public
02594  */
02595     function getInsertID() {
02596         return $this->__insertID;
02597     }
02598 /**
02599  * Sets the ID of the last record this model inserted
02600  *
02601  * @param mixed Last inserted ID
02602  * @access public
02603  */
02604     function setInsertID($id) {
02605         $this->__insertID = $id;
02606     }
02607 /**
02608  * Returns the number of rows returned from the last query.
02609  *
02610  * @return int Number of rows
02611  * @access public
02612  */
02613     function getNumRows() {
02614         $db =& ConnectionManager::getDataSource($this->useDbConfig);
02615         return $db->lastNumRows();
02616     }
02617 /**
02618  * Returns the number of rows affected by the last query.
02619  *
02620  * @return int Number of rows
02621  * @access public
02622  */
02623     function getAffectedRows() {
02624         $db =& ConnectionManager::getDataSource($this->useDbConfig);
02625         return $db->lastAffected();
02626     }
02627 /**
02628  * Sets the DataSource to which this model is bound.
02629  *
02630  * @param string $dataSource The name of the DataSource, as defined in app/config/database.php
02631  * @return boolean True on success
02632  * @access public
02633  */
02634     function setDataSource($dataSource = null) {
02635         $oldConfig = $this->useDbConfig;
02636 
02637         if ($dataSource != null) {
02638             $this->useDbConfig = $dataSource;
02639         }
02640         $db =& ConnectionManager::getDataSource($this->useDbConfig);
02641         if (!empty($oldConfig) && isset($db->config['prefix'])) {
02642             $oldDb =& ConnectionManager::getDataSource($oldConfig);
02643 
02644             if (!isset($this->tablePrefix) || (!isset($oldDb->config['prefix']) || $this->tablePrefix == $oldDb->config['prefix'])) {
02645                 $this->tablePrefix = $db->config['prefix'];
02646             }
02647         } elseif (isset($db->config['prefix'])) {
02648             $this->tablePrefix = $db->config['prefix'];
02649         }
02650 
02651         if (empty($db) || $db == null || !is_object($db)) {
02652             return $this->cakeError('missingConnection', array(array('className' => $this->alias)));
02653         }
02654     }
02655 /**
02656  * Gets the DataSource to which this model is bound.
02657  * Not safe for use with some versions of PHP4, because this class is overloaded.
02658  *
02659  * @return object A DataSource object
02660  * @access public
02661  */
02662     function &getDataSource() {
02663         $db =& ConnectionManager::getDataSource($this->useDbConfig);
02664         return $db;
02665     }
02666 /**
02667  * Gets all the models with which this model is associated.
02668  *
02669  * @param string $type Only result associations of this type
02670  * @return array Associations
02671  * @access public
02672  */
02673     function getAssociated($type = null) {
02674         if ($type == null) {
02675             $associated = array();
02676             foreach ($this->__associations as $assoc) {
02677                 if (!empty($this->{$assoc})) {
02678                     $models = array_keys($this->{$assoc});
02679                     foreach ($models as $m) {
02680                         $associated[$m] = $assoc;
02681                     }
02682                 }
02683             }
02684             return $associated;
02685         } elseif (in_array($type, $this->__associations)) {
02686             if (empty($this->{$type})) {
02687                 return array();
02688             }
02689             return array_keys($this->{$type});
02690         } else {
02691             $assoc = array_merge($this->hasOne, $this->hasMany, $this->belongsTo, $this->hasAndBelongsToMany);
02692             if (array_key_exists($type, $assoc)) {
02693                 foreach ($this->__associations as $a) {
02694                     if (isset($this->{$a}[$type])) {
02695                         $assoc[$type]['association'] = $a;
02696                         break;
02697                     }
02698                 }
02699                 return $assoc[$type];
02700             }
02701             return null;
02702         }
02703     }
02704 /**
02705  * Gets the name and fields to be used by a join model.  This allows specifying join fields in the association definition.
02706  *
02707  * @param object $model The model to be joined
02708  * @param mixed $with The 'with' key of the model association
02709  * @param array $keys Any join keys which must be merged with the keys queried
02710  * @return array
02711  * @access public
02712  */
02713     function joinModel($assoc, $keys = array()) {
02714         if (is_string($assoc)) {
02715             return array($assoc, array_keys($this->{$assoc}->schema()));
02716         } elseif (is_array($assoc)) {
02717             $with = key($assoc);
02718             return array($with, array_unique(array_merge($assoc[$with], $keys)));
02719         } else {
02720             trigger_error(sprintf(__('Invalid join model settings in %s', true), $model->alias), E_USER_WARNING);
02721         }
02722     }
02723 /**
02724  * Called before each find operation. Return false if you want to halt the find
02725  * call, otherwise return the (modified) query data.
02726  *
02727  * @param array $queryData Data used to execute this query, i.e. conditions, order, etc.
02728  * @return mixed true if the operation should continue, false if it should abort; or, modified $queryData to continue with new $queryData
02729  * @access public
02730  * @link http://book.cakephp.org/view/680/beforeFind
02731  */
02732     function beforeFind($queryData) {
02733         return true;
02734     }
02735 /**
02736  * Called after each find operation. Can be used to modify any results returned by find().
02737  * Return value should be the (modified) results.
02738  *
02739  * @param mixed $results The results of the find operation
02740  * @param boolean $primary Whether this model is being queried directly (vs. being queried as an association)
02741  * @return mixed Result of the find operation
02742  * @access public
02743  * @link http://book.cakephp.org/view/681/afterFind
02744  */
02745     function afterFind($results, $primary = false) {
02746         return $results;
02747     }
02748 /**
02749  * Called before each save operation, after validation. Return a non-true result
02750  * to halt the save.
02751  *
02752  * @return boolean True if the operation should continue, false if it should abort
02753  * @access public
02754  * @link http://book.cakephp.org/view/683/beforeSave
02755  */
02756     function beforeSave($options = array()) {
02757         return true;
02758     }
02759 /**
02760  * Called after each successful save operation.
02761  *
02762  * @param boolean $created True if this save created a new record
02763  * @access public
02764  * @link http://book.cakephp.org/view/684/afterSave
02765  */
02766     function afterSave($created) {
02767     }
02768 /**
02769  * Called before every deletion operation.
02770  *
02771  * @param boolean $cascade If true records that depend on this record will also be deleted
02772  * @return boolean True if the operation should continue, false if it should abort
02773  * @access public
02774  * @link http://book.cakephp.org/view/685/beforeDelete
02775  */
02776     function beforeDelete($cascade = true) {
02777         return true;
02778     }
02779 /**
02780  * Called after every deletion operation.
02781  *
02782  * @access public
02783  * @link http://book.cakephp.org/view/686/afterDelete
02784  */
02785     function afterDelete() {
02786     }
02787 /**
02788  * Called during save operations, before validation. Please note that custom
02789  * validation rules can be defined in $validate.
02790  *
02791  * @return boolean True if validate operation should continue, false to abort
02792  * @param $options array Options passed from model::save(), see $options of model::save().
02793  * @access public
02794  * @link http://book.cakephp.org/view/682/beforeValidate
02795  */
02796     function beforeValidate($options = array()) {
02797         return true;
02798     }
02799 /**
02800  * Called when a DataSource-level error occurs.
02801  *
02802  * @access public
02803  * @link http://book.cakephp.org/view/687/onError
02804  */
02805     function onError() {
02806     }
02807 /**
02808  * Private method. Clears cache for this model.
02809  *
02810  * @param string $type If null this deletes cached views if Cache.check is true
02811  *                     Will be used to allow deleting query cache also
02812  * @return boolean true on delete
02813  * @access protected
02814  * @todo
02815  */
02816     function _clearCache($type = null) {
02817         if ($type === null) {
02818             if (Configure::read('Cache.check') === true) {
02819                 $assoc[] = strtolower(Inflector::pluralize($this->alias));
02820                 $assoc[] = strtolower(Inflector::underscore(Inflector::pluralize($this->alias)));
02821                 foreach ($this->__associations as $key => $association) {
02822                     foreach ($this->$association as $key => $className) {
02823                         $check = strtolower(Inflector::pluralize($className['className']));
02824                         if (!in_array($check, $assoc)) {
02825                             $assoc[] = strtolower(Inflector::pluralize($className['className']));
02826                             $assoc[] = strtolower(Inflector::underscore(Inflector::pluralize($className['className'])));
02827                         }
02828                     }
02829                 }
02830                 clearCache($assoc);
02831                 return true;
02832             }
02833         } else {
02834             //Will use for query cache deleting
02835         }
02836     }
02837 /**
02838  * Called when serializing a model.
02839  *
02840  * @return array Set of object variable names this model has
02841  * @access private
02842  */
02843     function __sleep() {
02844         $return = array_keys(get_object_vars($this));
02845         return $return;
02846     }
02847 /**
02848  * Called when de-serializing a model.
02849  *
02850  * @access private
02851  * @todo
02852  */
02853     function __wakeup() {
02854     }
02855 /**
02856  * @deprecated
02857  * @see Model::find('all')
02858  */
02859     function findAll($conditions = null, $fields = null, $order = null, $limit = null, $page = 1, $recursive = null) {
02860         //trigger_error(__('(Model::findAll) Deprecated, use Model::find("all")', true), E_USER_WARNING);
02861         return $this->find('all', compact('conditions', 'fields', 'order', 'limit', 'page', 'recursive'));
02862     }
02863 /**
02864  * @deprecated
02865  * @see Model::find('count')
02866  */
02867     function findCount($conditions = null, $recursive = 0) {
02868         //trigger_error(__('(Model::findCount) Deprecated, use Model::find("count")', true), E_USER_WARNING);
02869         return $this->find('count', compact('conditions', 'recursive'));
02870     }
02871 /**
02872  * @deprecated
02873  * @see Model::find('threaded')
02874  */
02875     function findAllThreaded($conditions = null, $fields = null, $order = null) {
02876         //trigger_error(__('(Model::findAllThreaded) Deprecated, use Model::find("threaded")', true), E_USER_WARNING);
02877         return $this->find('threaded', compact('conditions', 'fields', 'order'));
02878     }
02879 /**
02880  * @deprecated
02881  * @see Model::find('neighbors')
02882  */
02883     function findNeighbours($conditions = null, $field, $value) {
02884         //trigger_error(__('(Model::findNeighbours) Deprecated, use Model::find("neighbors")', true), E_USER_WARNING);
02885         $query = compact('conditions', 'field', 'value');
02886         $query['fields'] = $field;
02887         if (is_array($field)) {
02888             $query['field'] = $field[0];
02889         }
02890         return $this->find('neighbors', $query);
02891     }
02892 }
02893 if (!defined('CAKEPHP_UNIT_TEST_EXECUTION')) {
02894     Overloadable::overload('Model');
02895 }
02896 ?>

Generated on Sun Nov 22 00:30:52 2009 for CakePHP 1.2.x.x (v1.2.4.8284) by doxygen 1.4.7