CakePHP
  • Documentation
    • Book
    • API
    • Videos
    • Reporting Security Issues
    • Privacy Policy
    • Logos & Trademarks
  • Business Solutions
  • Swag
  • Road Trip
  • Team
  • Community
    • Community
    • Get Involved
    • Issues (GitHub)
    • Bakery
    • Featured Resources
    • Training
    • Meetups
    • My CakePHP
    • CakeFest
    • Newsletter
    • Linkedin
    • YouTube
    • Facebook
    • Twitter
    • Mastodon
    • Help & Support
    • Forum
    • Stack Overflow
    • Slack
    • Paid Support
CakePHP

C CakePHP 1.3 API

  • Overview
  • Tree
  • Deprecated
  • Version:
    • 1.3
      • 4.2
      • 4.1
      • 4.0
      • 3.9
      • 3.8
      • 3.7
      • 3.6
      • 3.5
      • 3.4
      • 3.3
      • 3.2
      • 3.1
      • 3.0
      • 2.10
      • 2.9
      • 2.8
      • 2.7
      • 2.6
      • 2.5
      • 2.4
      • 2.3
      • 2.2
      • 2.1
      • 2.0
      • 1.3
      • 1.2

Classes

  • AclBase
  • AclBehavior
  • AclComponent
  • AclNode
  • AclShell
  • Aco
  • AcoAction
  • AjaxHelper
  • ApcEngine
  • ApiShell
  • App
  • AppController
  • AppHelper
  • AppModel
  • Aro
  • AuthComponent
  • BakeShell
  • BakeTask
  • BehaviorCollection
  • Cache
  • CacheEngine
  • CacheHelper
  • CakeErrorController
  • CakeLog
  • CakeRoute
  • CakeSchema
  • CakeSession
  • CakeSocket
  • ClassRegistry
  • Component
  • Configure
  • ConnectionManager
  • ConsoleShell
  • ContainableBehavior
  • Controller
  • ControllerTask
  • CookieComponent
  • DataSource
  • DbAcl
  • DbConfigTask
  • DboMssql
  • DboMysql
  • DboMysqlBase
  • DboMysqli
  • DboOracle
  • DboPostgres
  • DboSource
  • DboSqlite
  • Debugger
  • EmailComponent
  • ErrorHandler
  • ExtractTask
  • File
  • FileEngine
  • FileLog
  • FixtureTask
  • Folder
  • FormHelper
  • Helper
  • HtmlHelper
  • HttpSocket
  • I18n
  • I18nModel
  • I18nShell
  • Inflector
  • IniAcl
  • JavascriptHelper
  • JqueryEngineHelper
  • JsBaseEngineHelper
  • JsHelper
  • L10n
  • MagicDb
  • MagicFileResource
  • MediaView
  • MemcacheEngine
  • Model
  • ModelBehavior
  • ModelTask
  • MootoolsEngineHelper
  • Multibyte
  • NumberHelper
  • Object
  • Overloadable
  • Overloadable2
  • PagesController
  • PaginatorHelper
  • Permission
  • PluginShortRoute
  • PluginTask
  • ProjectTask
  • PrototypeEngineHelper
  • RequestHandlerComponent
  • Router
  • RssHelper
  • Sanitize
  • Scaffold
  • ScaffoldView
  • SchemaShell
  • Security
  • SecurityComponent
  • SessionComponent
  • SessionHelper
  • Set
  • Shell
  • String
  • TemplateTask
  • TestSuiteShell
  • TestTask
  • TextHelper
  • ThemeView
  • TimeHelper
  • TranslateBehavior
  • TreeBehavior
  • Validation
  • View
  • ViewTask
  • XcacheEngine
  • Xml
  • XmlElement
  • XmlHelper
  • XmlManager
  • XmlNode
  • XmlTextNode

Functions

  • mb_encode_mimeheader
  • mb_stripos
  • mb_stristr
  • mb_strlen
  • mb_strpos
  • mb_strrchr
  • mb_strrichr
  • mb_strripos
  • mb_strrpos
  • mb_strstr
  • mb_strtolower
  • mb_strtoupper
  • mb_substr
  • mb_substr_count
   1: <?php
   2: /**
   3:  * Object-relational mapper.
   4:  *
   5:  * DBO-backed object data model, for mapping database tables to Cake objects.
   6:  *
   7:  * PHP versions 5
   8:  *
   9:  * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  10:  * Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
  11:  *
  12:  * Licensed under The MIT License
  13:  * Redistributions of files must retain the above copyright notice.
  14:  *
  15:  * @copyright     Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
  16:  * @link          http://cakephp.org CakePHP(tm) Project
  17:  * @package       cake
  18:  * @subpackage    cake.cake.libs.model
  19:  * @since         CakePHP(tm) v 0.10.0.0
  20:  * @license       MIT License (http://www.opensource.org/licenses/mit-license.php)
  21:  */
  22: 
  23: /**
  24:  * Included libs
  25:  */
  26: App::import('Core', array('ClassRegistry', 'Validation', 'Set', 'String'));
  27: App::import('Model', 'ModelBehavior', false);
  28: App::import('Model', 'ConnectionManager', false);
  29: 
  30: if (!class_exists('Overloadable')) {
  31:     require LIBS . 'overloadable.php';
  32: }
  33: 
  34: /**
  35:  * Object-relational mapper.
  36:  *
  37:  * DBO-backed object data model.
  38:  * Automatically selects a database table name based on a pluralized lowercase object class name
  39:  * (i.e. class 'User' => table 'users'; class 'Man' => table 'men')
  40:  * The table is required to have at least 'id auto_increment' primary key.
  41:  *
  42:  * @package       cake
  43:  * @subpackage    cake.cake.libs.model
  44:  * @link          http://book.cakephp.org/1.3/en/The-Manual/Developing-with-CakePHP/Models.html#Models
  45:  */
  46: class Model extends Overloadable {
  47: 
  48: /**
  49:  * The name of the DataSource connection that this Model uses
  50:  *
  51:  * @var string
  52:  * @access public
  53:  * @link http://book.cakephp.org/1.3/en/The-Manual/Developing-with-CakePHP/Models.html#Model-Attributes#useDbConfig-1058
  54:  */
  55:     var $useDbConfig = 'default';
  56: 
  57: /**
  58:  * Custom database table name, or null/false if no table association is desired.
  59:  *
  60:  * @var string
  61:  * @access public
  62:  * @link http://book.cakephp.org/1.3/en/The-Manual/Developing-with-CakePHP/Models.html#Model-Attributes#useTable-1059
  63:  */
  64:     var $useTable = null;
  65: 
  66: /**
  67:  * Custom display field name. Display fields are used by Scaffold, in SELECT boxes' OPTION elements.
  68:  *
  69:  * @var string
  70:  * @access public
  71:  * @link http://book.cakephp.org/1.3/en/The-Manual/Developing-with-CakePHP/Models.html#Model-Attributes#displayField-1062
  72:  */
  73:     var $displayField = null;
  74: 
  75: /**
  76:  * Value of the primary key ID of the record that this model is currently pointing to.
  77:  * Automatically set after database insertions.
  78:  *
  79:  * @var mixed
  80:  * @access public
  81:  */
  82:     var $id = false;
  83: 
  84: /**
  85:  * Container for the data that this model gets from persistent storage (usually, a database).
  86:  *
  87:  * @var array
  88:  * @access public
  89:  * @link http://book.cakephp.org/1.3/en/The-Manual/Developing-with-CakePHP/Models.html#Model-Attributes#data-1065
  90:  */
  91:     var $data = array();
  92: 
  93: /**
  94:  * Table name for this Model.
  95:  *
  96:  * @var string
  97:  * @access public
  98:  */
  99:     var $table = false;
 100: 
 101: /**
 102:  * The name of the primary key field for this model.
 103:  *
 104:  * @var string
 105:  * @access public
 106:  * @link http://book.cakephp.org/1.3/en/The-Manual/Developing-with-CakePHP/Models.html#Model-Attributes#primaryKey-1061
 107:  */
 108:     var $primaryKey = null;
 109: 
 110: /**
 111:  * Field-by-field table metadata.
 112:  *
 113:  * @var array
 114:  * @access protected
 115:  * @link http://book.cakephp.org/1.3/en/The-Manual/Developing-with-CakePHP/Models.html#Model-Attributes#_schema-1066
 116:  */
 117:     var $_schema = null;
 118: 
 119: /**
 120:  * List of validation rules. Append entries for validation as ('field_name' => '/^perl_compat_regexp$/')
 121:  * that have to match with preg_match(). Use these rules with Model::validate()
 122:  *
 123:  * @var array
 124:  * @access public
 125:  * @link http://book.cakephp.org/1.3/en/The-Manual/Developing-with-CakePHP/Models.html#Model-Attributes#validate-1067
 126:  * @link http://book.cakephp.org/1.3/en/The-Manual/Developing-with-CakePHP/Models.html#Data-Validation
 127:  */
 128:     var $validate = array();
 129: 
 130: /**
 131:  * List of validation errors.
 132:  *
 133:  * @var array
 134:  * @access public
 135:  * @link http://book.cakephp.org/1.3/en/The-Manual/Developing-with-CakePHP/Models.html#Validating-Data-from-the-Controller
 136:  */
 137:     var $validationErrors = array();
 138: 
 139: /**
 140:  * Database table prefix for tables in model.
 141:  *
 142:  * @var string
 143:  * @access public
 144:  * @link http://book.cakephp.org/1.3/en/The-Manual/Developing-with-CakePHP/Models.html#Model-Attributes#tablePrefix-1060
 145:  */
 146:     var $tablePrefix = null;
 147: 
 148: /**
 149:  * Name of the model.
 150:  *
 151:  * @var string
 152:  * @access public
 153:  * @link http://book.cakephp.org/1.3/en/The-Manual/Developing-with-CakePHP/Models.html#Model-Attributes#name-1068
 154:  */
 155:     var $name = null;
 156: 
 157: /**
 158:  * Alias name for model.
 159:  *
 160:  * @var string
 161:  * @access public
 162:  */
 163:     var $alias = null;
 164: 
 165: /**
 166:  * List of table names included in the model description. Used for associations.
 167:  *
 168:  * @var array
 169:  * @access public
 170:  */
 171:     var $tableToModel = array();
 172: 
 173: /**
 174:  * Whether or not to log transactions for this model.
 175:  *
 176:  * @var boolean
 177:  * @access public
 178:  */
 179:     var $logTransactions = false;
 180: 
 181: /**
 182:  * Whether or not to cache queries for this model.  This enables in-memory
 183:  * caching only, the results are not stored beyond the current request.
 184:  *
 185:  * @var boolean
 186:  * @access public
 187:  * @link http://book.cakephp.org/1.3/en/The-Manual/Developing-with-CakePHP/Models.html#Model-Attributes#cacheQueries-1069
 188:  */
 189:     var $cacheQueries = false;
 190: 
 191: /**
 192:  * Detailed list of belongsTo associations.
 193:  *
 194:  * @var array
 195:  * @access public
 196:  * @link http://book.cakephp.org/1.3/en/The-Manual/Developing-with-CakePHP/Models.html#belongsTo
 197:  */
 198:     var $belongsTo = array();
 199: 
 200: /**
 201:  * Detailed list of hasOne associations.
 202:  *
 203:  * @var array
 204:  * @access public
 205:  * @link http://book.cakephp.org/1.3/en/The-Manual/Developing-with-CakePHP/Models.html#hasOne
 206:  */
 207:     var $hasOne = array();
 208: 
 209: /**
 210:  * Detailed list of hasMany associations.
 211:  *
 212:  * @var array
 213:  * @access public
 214:  * @link http://book.cakephp.org/1.3/en/The-Manual/Developing-with-CakePHP/Models.html#hasMany
 215:  */
 216:     var $hasMany = array();
 217: 
 218: /**
 219:  * Detailed list of hasAndBelongsToMany associations.
 220:  *
 221:  * @var array
 222:  * @access public
 223:  * @link http://book.cakephp.org/1.3/en/The-Manual/Developing-with-CakePHP/Models.html#hasAndBelongsToMany-HABTM
 224:  */
 225:     var $hasAndBelongsToMany = array();
 226: 
 227: /**
 228:  * List of behaviors to load when the model object is initialized. Settings can be
 229:  * passed to behaviors by using the behavior name as index. Eg:
 230:  *
 231:  * var $actsAs = array('Translate', 'MyBehavior' => array('setting1' => 'value1'))
 232:  *
 233:  * @var array
 234:  * @access public
 235:  * @link http://book.cakephp.org/1.3/en/The-Manual/Developing-with-CakePHP/Models.html#Using-Behaviors
 236:  */
 237:     var $actsAs = null;
 238: 
 239: /**
 240:  * Holds the Behavior objects currently bound to this model.
 241:  *
 242:  * @var BehaviorCollection
 243:  * @access public
 244:  */
 245:     var $Behaviors = null;
 246: 
 247: /**
 248:  * Whitelist of fields allowed to be saved.
 249:  *
 250:  * @var array
 251:  * @access public
 252:  */
 253:     var $whitelist = array();
 254: 
 255: /**
 256:  * Whether or not to cache sources for this model.
 257:  *
 258:  * @var boolean
 259:  * @access public
 260:  */
 261:     var $cacheSources = true;
 262: 
 263: /**
 264:  * Type of find query currently executing.
 265:  *
 266:  * @var string
 267:  * @access public
 268:  */
 269:     var $findQueryType = null;
 270: 
 271: /**
 272:  * Number of associations to recurse through during find calls. Fetches only
 273:  * the first level by default.
 274:  *
 275:  * @var integer
 276:  * @access public
 277:  * @link http://book.cakephp.org/1.3/en/The-Manual/Developing-with-CakePHP/Models.html#Model-Attributes#recursive-1063
 278:  */
 279:     var $recursive = 1;
 280: 
 281: /**
 282:  * The column name(s) and direction(s) to order find results by default.
 283:  *
 284:  * var $order = "Post.created DESC";
 285:  * var $order = array("Post.view_count DESC", "Post.rating DESC");
 286:  *
 287:  * @var string
 288:  * @access public
 289:  * @link http://book.cakephp.org/1.3/en/The-Manual/Developing-with-CakePHP/Models.html#Model-Attributes#order-1064
 290:  */
 291:     var $order = null;
 292: 
 293: /**
 294:  * Array of virtual fields this model has.  Virtual fields are aliased
 295:  * SQL expressions. Fields added to this property will be read as other fields in a model
 296:  * but will not be saveable.
 297:  *
 298:  * `var $virtualFields = array('two' => '1 + 1');`
 299:  *
 300:  * Is a simplistic example of how to set virtualFields
 301:  *
 302:  * @var array
 303:  * @access public
 304:  */
 305:     var $virtualFields = array();
 306: 
 307: /**
 308:  * Default list of association keys.
 309:  *
 310:  * @var array
 311:  * @access private
 312:  */
 313:     var $__associationKeys = array(
 314:         'belongsTo' => array('className', 'foreignKey', 'conditions', 'fields', 'order', 'counterCache'),
 315:         'hasOne' => array('className', 'foreignKey','conditions', 'fields','order', 'dependent'),
 316:         'hasMany' => array('className', 'foreignKey', 'conditions', 'fields', 'order', 'limit', 'offset', 'dependent', 'exclusive', 'finderQuery', 'counterQuery'),
 317:         'hasAndBelongsToMany' => array('className', 'joinTable', 'with', 'foreignKey', 'associationForeignKey', 'conditions', 'fields', 'order', 'limit', 'offset', 'unique', 'finderQuery', 'deleteQuery', 'insertQuery')
 318:     );
 319: 
 320: /**
 321:  * Holds provided/generated association key names and other data for all associations.
 322:  *
 323:  * @var array
 324:  * @access private
 325:  */
 326:     var $__associations = array('belongsTo', 'hasOne', 'hasMany', 'hasAndBelongsToMany');
 327: 
 328: /**
 329:  * Holds model associations temporarily to allow for dynamic (un)binding.
 330:  *
 331:  * @var array
 332:  * @access private
 333:  */
 334:     var $__backAssociation = array();
 335: 
 336: /**
 337:  * The ID of the model record that was last inserted.
 338:  *
 339:  * @var integer
 340:  * @access private
 341:  */
 342:     var $__insertID = null;
 343: 
 344: /**
 345:  * The number of records returned by the last query.
 346:  *
 347:  * @var integer
 348:  * @access private
 349:  */
 350:     var $__numRows = null;
 351: 
 352: /**
 353:  * The number of records affected by the last query.
 354:  *
 355:  * @var integer
 356:  * @access private
 357:  */
 358:     var $__affectedRows = null;
 359: 
 360: /**
 361:  * Safe update mode
 362:  *
 363:  * If true, this prevents Model::save() from generating a query with WHERE 1 = 1 on race condition.
 364:  *
 365:  * @var bool
 366:  */
 367:     var $__safeUpdateMode = false;
 368: 
 369: /**
 370:  * List of valid finder method options, supplied as the first parameter to find().
 371:  *
 372:  * @var array
 373:  * @access protected
 374:  */
 375:     var $_findMethods = array(
 376:         'all' => true, 'first' => true, 'count' => true,
 377:         'neighbors' => true, 'list' => true, 'threaded' => true
 378:     );
 379: 
 380: /**
 381:  * Constructor. Binds the model's database table to the object.
 382:  *
 383:  * If `$id` is an array it can be used to pass several options into the model.
 384:  *
 385:  * - id - The id to start the model on.
 386:  * - table - The table to use for this model.
 387:  * - ds - The connection name this model is connected to.
 388:  * - name - The name of the model eg. Post.
 389:  * - alias - The alias of the model, this is used for registering the instance in the `ClassRegistry`.
 390:  *   eg. `ParentThread`
 391:  *
 392:  * ### Overriding Model's __construct method.
 393:  *
 394:  * When overriding Model::__construct() be careful to include and pass in all 3 of the
 395:  * arguments to `parent::__construct($id, $table, $ds);`
 396:  *
 397:  * ### Dynamically creating models
 398:  *
 399:  * You can dynamically create model instances using the $id array syntax.
 400:  *
 401:  * {{{
 402:  * $Post = new Model(array('table' => 'posts', 'name' => 'Post', 'ds' => 'connection2'));
 403:  * }}}
 404:  *
 405:  * Would create a model attached to the posts table on connection2.  Dynamic model creation is useful
 406:  * when you want a model object that contains no associations or attached behaviors.
 407:  *
 408:  * @param mixed $id Set this ID for this model on startup, can also be an array of options, see above.
 409:  * @param string $table Name of database table to use.
 410:  * @param string $ds DataSource connection name.
 411:  */
 412:     function __construct($id = false, $table = null, $ds = null) {
 413:         parent::__construct();
 414: 
 415:         if (is_array($id)) {
 416:             extract(array_merge(
 417:                 array(
 418:                     'id' => $this->id, 'table' => $this->useTable, 'ds' => $this->useDbConfig,
 419:                     'name' => $this->name, 'alias' => $this->alias
 420:                 ),
 421:                 $id
 422:             ));
 423:         }
 424: 
 425:         if ($this->name === null) {
 426:             $this->name = (isset($name) ? $name : get_class($this));
 427:         }
 428: 
 429:         if ($this->alias === null) {
 430:             $this->alias = (isset($alias) ? $alias : $this->name);
 431:         }
 432: 
 433:         if ($this->primaryKey === null) {
 434:             $this->primaryKey = 'id';
 435:         }
 436: 
 437:         ClassRegistry::addObject($this->alias, $this);
 438: 
 439:         $this->id = $id;
 440:         unset($id);
 441: 
 442:         if ($table === false) {
 443:             $this->useTable = false;
 444:         } elseif ($table) {
 445:             $this->useTable = $table;
 446:         }
 447: 
 448:         if ($ds !== null) {
 449:             $this->useDbConfig = $ds;
 450:         }
 451: 
 452:         if (is_subclass_of($this, 'AppModel')) {
 453:             $appVars = get_class_vars('AppModel');
 454:             $merge = array('_findMethods');
 455: 
 456:             if ($this->actsAs !== null || $this->actsAs !== false) {
 457:                 $merge[] = 'actsAs';
 458:             }
 459:             $parentClass = get_parent_class($this);
 460:             if (strtolower($parentClass) !== 'appmodel') {
 461:                 $parentVars = get_class_vars($parentClass);
 462:                 foreach ($merge as $var) {
 463:                     if (isset($parentVars[$var]) && !empty($parentVars[$var])) {
 464:                         $appVars[$var] = Set::merge($appVars[$var], $parentVars[$var]);
 465:                     }
 466:                 }
 467:             }
 468: 
 469:             foreach ($merge as $var) {
 470:                 if (isset($appVars[$var]) && !empty($appVars[$var]) && is_array($this->{$var})) {
 471:                     $this->{$var} = Set::merge($appVars[$var], $this->{$var});
 472:                 }
 473:             }
 474:         }
 475:         $this->Behaviors = new BehaviorCollection();
 476: 
 477:         if ($this->useTable !== false) {
 478:             $this->setDataSource($ds);
 479: 
 480:             if ($this->useTable === null) {
 481:                 $this->useTable = Inflector::tableize($this->name);
 482:             }
 483:             $this->setSource($this->useTable);
 484: 
 485:             if ($this->displayField == null) {
 486:                 $this->displayField = $this->hasField(array('title', 'name', $this->primaryKey));
 487:             }
 488:         } elseif ($this->table === false) {
 489:             $this->table = Inflector::tableize($this->name);
 490:         }
 491:         $this->__createLinks();
 492:         $this->Behaviors->init($this->alias, $this->actsAs);
 493:     }
 494: 
 495: /**
 496:  * Handles custom method calls, like findBy<field> for DB models,
 497:  * and custom RPC calls for remote data sources.
 498:  *
 499:  * @param string $method Name of method to call.
 500:  * @param array $params Parameters for the method.
 501:  * @return mixed Whatever is returned by called method
 502:  * @access protected
 503:  */
 504:     function call__($method, $params) {
 505:         $result = $this->Behaviors->dispatchMethod($this, $method, $params);
 506: 
 507:         if ($result !== array('unhandled')) {
 508:             return $result;
 509:         }
 510:         $db =& ConnectionManager::getDataSource($this->useDbConfig);
 511:         $return = $db->query($method, $params, $this);
 512: 
 513:         if (!PHP5) {
 514:             $this->resetAssociations();
 515:         }
 516:         return $return;
 517:     }
 518: 
 519: /**
 520:  * Bind model associations on the fly.
 521:  *
 522:  * If `$reset` is false, association will not be reset
 523:  * to the originals defined in the model
 524:  *
 525:  * Example: Add a new hasOne binding to the Profile model not
 526:  * defined in the model source code:
 527:  *
 528:  * `$this->User->bindModel( array('hasOne' => array('Profile')) );`
 529:  *
 530:  * Bindings that are not made permanent will be reset by the next Model::find() call on this
 531:  * model.
 532:  *
 533:  * @param array $params Set of bindings (indexed by binding type)
 534:  * @param boolean $reset Set to false to make the binding permanent
 535:  * @return boolean Success
 536:  * @access public
 537:  * @link http://book.cakephp.org/1.3/en/The-Manual/Developing-with-CakePHP/Models.html#Creating-and-Destroying-Associations-on-the-Fly
 538:  */
 539:     function bindModel($params, $reset = true) {
 540:         foreach ($params as $assoc => $model) {
 541:             if ($reset === true && !isset($this->__backAssociation[$assoc])) {
 542:                 $this->__backAssociation[$assoc] = $this->{$assoc};
 543:             }
 544:             foreach ($model as $key => $value) {
 545:                 $assocName = $key;
 546: 
 547:                 if (is_numeric($key)) {
 548:                     $assocName = $value;
 549:                     $value = array();
 550:                 }
 551:                 $modelName = $assocName;
 552:                 $this->{$assoc}[$assocName] = $value;
 553: 
 554:                 if ($reset === false && isset($this->__backAssociation[$assoc])) {
 555:                     $this->__backAssociation[$assoc][$assocName] = $value;
 556:                 }
 557:             }
 558:         }
 559:         $this->__createLinks();
 560:         return true;
 561:     }
 562: 
 563: /**
 564:  * Turn off associations on the fly.
 565:  *
 566:  * If $reset is false, association will not be reset
 567:  * to the originals defined in the model
 568:  *
 569:  * Example: Turn off the associated Model Support request,
 570:  * to temporarily lighten the User model:
 571:  *
 572:  * `$this->User->unbindModel( array('hasMany' => array('Supportrequest')) );`
 573:  *
 574:  * unbound models that are not made permanent will reset with the next call to Model::find()
 575:  *
 576:  * @param array $params Set of bindings to unbind (indexed by binding type)
 577:  * @param boolean $reset  Set to false to make the unbinding permanent
 578:  * @return boolean Success
 579:  * @access public
 580:  * @link http://book.cakephp.org/1.3/en/The-Manual/Developing-with-CakePHP/Models.html#Creating-and-Destroying-Associations-on-the-Fly
 581:  */
 582:     function unbindModel($params, $reset = true) {
 583:         foreach ($params as $assoc => $models) {
 584:             if ($reset === true && !isset($this->__backAssociation[$assoc])) {
 585:                 $this->__backAssociation[$assoc] = $this->{$assoc};
 586:             }
 587:             foreach ($models as $model) {
 588:                 if ($reset === false && isset($this->__backAssociation[$assoc][$model])) {
 589:                     unset($this->__backAssociation[$assoc][$model]);
 590:                 }
 591:                 unset($this->{$assoc}[$model]);
 592:             }
 593:         }
 594:         return true;
 595:     }
 596: 
 597: /**
 598:  * Create a set of associations.
 599:  *
 600:  * @return void
 601:  * @access private
 602:  */
 603:     function __createLinks() {
 604:         foreach ($this->__associations as $type) {
 605:             if (!is_array($this->{$type})) {
 606:                 $this->{$type} = explode(',', $this->{$type});
 607: 
 608:                 foreach ($this->{$type} as $i => $className) {
 609:                     $className = trim($className);
 610:                     unset ($this->{$type}[$i]);
 611:                     $this->{$type}[$className] = array();
 612:                 }
 613:             }
 614: 
 615:             if (!empty($this->{$type})) {
 616:                 foreach ($this->{$type} as $assoc => $value) {
 617:                     $plugin = null;
 618: 
 619:                     if (is_numeric($assoc)) {
 620:                         unset ($this->{$type}[$assoc]);
 621:                         $assoc = $value;
 622:                         $value = array();
 623:                         $this->{$type}[$assoc] = $value;
 624: 
 625:                         if (strpos($assoc, '.') !== false) {
 626:                             $value = $this->{$type}[$assoc];
 627:                             unset($this->{$type}[$assoc]);
 628:                             list($plugin, $assoc) = pluginSplit($assoc, true);
 629:                             $this->{$type}[$assoc] = $value;
 630:                         }
 631:                     }
 632:                     $className =  $assoc;
 633: 
 634:                     if (!empty($value['className'])) {
 635:                         list($plugin, $className) = pluginSplit($value['className'], true);
 636:                         $this->{$type}[$assoc]['className'] = $className;
 637:                     }
 638:                     $this->__constructLinkedModel($assoc, $plugin . $className);
 639:                 }
 640:                 $this->__generateAssociation($type);
 641:             }
 642:         }
 643:     }
 644: 
 645: /**
 646:  * Private helper method to create associated models of a given class.
 647:  *
 648:  * @param string $assoc Association name
 649:  * @param string $className Class name
 650:  * @deprecated $this->$className use $this->$assoc instead. $assoc is the 'key' in the associations array;
 651:  *  examples: var $hasMany = array('Assoc' => array('className' => 'ModelName'));
 652:  *                  usage: $this->Assoc->modelMethods();
 653:  *
 654:  *              var $hasMany = array('ModelName');
 655:  *                  usage: $this->ModelName->modelMethods();
 656:  * @return void
 657:  * @access private
 658:  */
 659:     function __constructLinkedModel($assoc, $className = null) {
 660:         if (empty($className)) {
 661:             $className = $assoc;
 662:         }
 663: 
 664:         if (!isset($this->{$assoc}) || $this->{$assoc}->name !== $className) {
 665:             $model = array('class' => $className, 'alias' => $assoc);
 666:             if (PHP5) {
 667:                 $this->{$assoc} = ClassRegistry::init($model);
 668:             } else {
 669:                 $this->{$assoc} =& ClassRegistry::init($model);
 670:             }
 671:             if (strpos($className, '.') !== false) {
 672:                 ClassRegistry::addObject($className, $this->{$assoc});
 673:             }
 674:             if ($assoc) {
 675:                 $this->tableToModel[$this->{$assoc}->table] = $assoc;
 676:             }
 677:         }
 678:     }
 679: 
 680: /**
 681:  * Build an array-based association from string.
 682:  *
 683:  * @param string $type 'belongsTo', 'hasOne', 'hasMany', 'hasAndBelongsToMany'
 684:  * @return void
 685:  * @access private
 686:  */
 687:     function __generateAssociation($type) {
 688:         foreach ($this->{$type} as $assocKey => $assocData) {
 689:             $class = $assocKey;
 690:             $dynamicWith = false;
 691: 
 692:             foreach ($this->__associationKeys[$type] as $key) {
 693: 
 694:                 if (!isset($this->{$type}[$assocKey][$key]) || $this->{$type}[$assocKey][$key] === null) {
 695:                     $data = '';
 696: 
 697:                     switch ($key) {
 698:                         case 'fields':
 699:                             $data = '';
 700:                         break;
 701: 
 702:                         case 'foreignKey':
 703:                             $data = (($type == 'belongsTo') ? Inflector::underscore($assocKey) : Inflector::singularize($this->table)) . '_id';
 704:                         break;
 705: 
 706:                         case 'associationForeignKey':
 707:                             $data = Inflector::singularize($this->{$class}->table) . '_id';
 708:                         break;
 709: 
 710:                         case 'with':
 711:                             $data = Inflector::camelize(Inflector::singularize($this->{$type}[$assocKey]['joinTable']));
 712:                             $dynamicWith = true;
 713:                         break;
 714: 
 715:                         case 'joinTable':
 716:                             $tables = array($this->table, $this->{$class}->table);
 717:                             sort ($tables);
 718:                             $data = $tables[0] . '_' . $tables[1];
 719:                         break;
 720: 
 721:                         case 'className':
 722:                             $data = $class;
 723:                         break;
 724: 
 725:                         case 'unique':
 726:                             $data = true;
 727:                         break;
 728:                     }
 729:                     $this->{$type}[$assocKey][$key] = $data;
 730:                 }
 731:             }
 732: 
 733:             if (!empty($this->{$type}[$assocKey]['with'])) {
 734:                 $joinClass = $this->{$type}[$assocKey]['with'];
 735:                 if (is_array($joinClass)) {
 736:                     $joinClass = key($joinClass);
 737:                 }
 738: 
 739:                 $plugin = null;
 740:                 if (strpos($joinClass, '.') !== false) {
 741:                     list($plugin, $joinClass) = explode('.', $joinClass);
 742:                     $plugin .= '.';
 743:                     $this->{$type}[$assocKey]['with'] = $joinClass;
 744:                 }
 745: 
 746:                 if (!ClassRegistry::isKeySet($joinClass) && $dynamicWith === true) {
 747:                     $this->{$joinClass} = new AppModel(array(
 748:                         'name' => $joinClass,
 749:                         'table' => $this->{$type}[$assocKey]['joinTable'],
 750:                         'ds' => $this->useDbConfig
 751:                     ));
 752:                 } else {
 753:                     $this->__constructLinkedModel($joinClass, $plugin . $joinClass);
 754:                     $this->{$type}[$assocKey]['joinTable'] = $this->{$joinClass}->table;
 755:                 }
 756: 
 757:                 if (count($this->{$joinClass}->schema()) <= 2 && $this->{$joinClass}->primaryKey !== false) {
 758:                     $this->{$joinClass}->primaryKey = $this->{$type}[$assocKey]['foreignKey'];
 759:                 }
 760:             }
 761:         }
 762:     }
 763: 
 764: /**
 765:  * Sets a custom table for your controller class. Used by your controller to select a database table.
 766:  *
 767:  * @param string $tableName Name of the custom table
 768:  * @return void
 769:  * @access public
 770:  */
 771:     function setSource($tableName) {
 772:         $this->setDataSource($this->useDbConfig);
 773:         $db =& ConnectionManager::getDataSource($this->useDbConfig);
 774:         $db->cacheSources = ($this->cacheSources && $db->cacheSources);
 775: 
 776:         if ($db->isInterfaceSupported('listSources')) {
 777:             $sources = $db->listSources();
 778:             if (is_array($sources) && !in_array(strtolower($this->tablePrefix . $tableName), array_map('strtolower', $sources))) {
 779:                 return $this->cakeError('missingTable', array(array(
 780:                     'className' => $this->alias,
 781:                     'table' => $this->tablePrefix . $tableName,
 782:                     'code' => 500
 783:                 )));
 784:             }
 785:             $this->_schema = null;
 786:         }
 787:         $this->table = $this->useTable = $tableName;
 788:         $this->tableToModel[$this->table] = $this->alias;
 789:         $this->schema();
 790:     }
 791: 
 792: /**
 793:  * This function does two things:
 794:  *
 795:  * 1. it scans the array $one for the primary key,
 796:  * and if that's found, it sets the current id to the value of $one[id].
 797:  * For all other keys than 'id' the keys and values of $one are copied to the 'data' property of this object.
 798:  * 2. Returns an array with all of $one's keys and values.
 799:  * (Alternative indata: two strings, which are mangled to
 800:  * a one-item, two-dimensional array using $one for a key and $two as its value.)
 801:  *
 802:  * @param mixed $one Array or string of data
 803:  * @param string $two Value string for the alternative indata method
 804:  * @return array Data with all of $one's keys and values
 805:  * @access public
 806:  * @link http://book.cakephp.org/1.3/en/The-Manual/Developing-with-CakePHP/Models.html#Saving-Your-Data
 807:  */
 808:     function set($one, $two = null) {
 809:         if (!$one) {
 810:             return;
 811:         }
 812:         if (is_object($one)) {
 813:             $one = Set::reverse($one);
 814:         }
 815: 
 816:         if (is_array($one)) {
 817:             $data = $one;
 818:             if (empty($one[$this->alias])) {
 819:                 if ($this->getAssociated(key($one)) === null) {
 820:                     $data = array($this->alias => $one);
 821:                 }
 822:             }
 823:         } else {
 824:             $data = array($this->alias => array($one => $two));
 825:         }
 826: 
 827:         foreach ($data as $modelName => $fieldSet) {
 828:             if (is_array($fieldSet)) {
 829: 
 830:                 foreach ($fieldSet as $fieldName => $fieldValue) {
 831:                     if (isset($this->validationErrors[$fieldName])) {
 832:                         unset ($this->validationErrors[$fieldName]);
 833:                     }
 834: 
 835:                     if ($modelName === $this->alias) {
 836:                         if ($fieldName === $this->primaryKey) {
 837:                             $this->id = $fieldValue;
 838:                         }
 839:                     }
 840:                     if (is_array($fieldValue) || is_object($fieldValue)) {
 841:                         $fieldValue = $this->deconstruct($fieldName, $fieldValue);
 842:                     }
 843:                     $this->data[$modelName][$fieldName] = $fieldValue;
 844:                 }
 845:             }
 846:         }
 847:         return $data;
 848:     }
 849: 
 850: /**
 851:  * Deconstructs a complex data type (array or object) into a single field value.
 852:  *
 853:  * @param string $field The name of the field to be deconstructed
 854:  * @param mixed $data An array or object to be deconstructed into a field
 855:  * @return mixed The resulting data that should be assigned to a field
 856:  * @access public
 857:  */
 858:     function deconstruct($field, $data) {
 859:         if (!is_array($data)) {
 860:             return $data;
 861:         }
 862: 
 863:         $copy = $data;
 864:         $type = $this->getColumnType($field);
 865: 
 866:         if (in_array($type, array('datetime', 'timestamp', 'date', 'time'))) {
 867:             $useNewDate = (isset($data['year']) || isset($data['month']) ||
 868:                 isset($data['day']) || isset($data['hour']) || isset($data['minute']));
 869: 
 870:             $dateFields = array('Y' => 'year', 'm' => 'month', 'd' => 'day', 'H' => 'hour', 'i' => 'min', 's' => 'sec');
 871:             $timeFields = array('H' => 'hour', 'i' => 'min', 's' => 'sec');
 872: 
 873:             $db =& ConnectionManager::getDataSource($this->useDbConfig);
 874:             $format = $db->columns[$type]['format'];
 875:             $date = array();
 876: 
 877:             if (isset($data['hour']) && isset($data['meridian']) && $data['hour'] != 12 && 'pm' == $data['meridian']) {
 878:                 $data['hour'] = $data['hour'] + 12;
 879:             }
 880:             if (isset($data['hour']) && isset($data['meridian']) && $data['hour'] == 12 && 'am' == $data['meridian']) {
 881:                 $data['hour'] = '00';
 882:             }
 883:             if ($type == 'time') {
 884:                 foreach ($timeFields as $key => $val) {
 885:                     if (!isset($data[$val]) || $data[$val] === '0' || $data[$val] === '00') {
 886:                         $data[$val] = '00';
 887:                     } elseif ($data[$val] === '') {
 888:                         $data[$val] = '';
 889:                     } else {
 890:                         $data[$val] = sprintf('%02d', $data[$val]);
 891:                     }
 892:                     if (!empty($data[$val])) {
 893:                         $date[$key] = $data[$val];
 894:                     } else {
 895:                         return null;
 896:                     }
 897:                 }
 898:             }
 899: 
 900:             if ($type == 'datetime' || $type == 'timestamp' || $type == 'date') {
 901:                 foreach ($dateFields as $key => $val) {
 902:                     if ($val == 'hour' || $val == 'min' || $val == 'sec') {
 903:                         if (!isset($data[$val]) || $data[$val] === '0' || $data[$val] === '00') {
 904:                             $data[$val] = '00';
 905:                         } else {
 906:                             $data[$val] = sprintf('%02d', $data[$val]);
 907:                         }
 908:                     }
 909:                     if (!isset($data[$val]) || isset($data[$val]) && (empty($data[$val]) || $data[$val][0] === '-')) {
 910:                         return null;
 911:                     }
 912:                     if (isset($data[$val]) && !empty($data[$val])) {
 913:                         $date[$key] = $data[$val];
 914:                     }
 915:                 }
 916:             }
 917:             $date = str_replace(array_keys($date), array_values($date), $format);
 918:             if ($useNewDate && !empty($date)) {
 919:                 return $date;
 920:             }
 921:         }
 922:         return $data;
 923:     }
 924: 
 925: /**
 926:  * Returns an array of table metadata (column names and types) from the database.
 927:  * $field => keys(type, null, default, key, length, extra)
 928:  *
 929:  * @param mixed $field Set to true to reload schema, or a string to return a specific field
 930:  * @return array Array of table metadata
 931:  * @access public
 932:  */
 933:     function schema($field = false) {
 934:         if (!is_array($this->_schema) || $field === true) {
 935:             $db =& ConnectionManager::getDataSource($this->useDbConfig);
 936:             $db->cacheSources = ($this->cacheSources && $db->cacheSources);
 937:             if ($db->isInterfaceSupported('describe') && $this->useTable !== false) {
 938:                 $this->_schema = $db->describe($this, $field);
 939:             } elseif ($this->useTable === false) {
 940:                 $this->_schema = array();
 941:             }
 942:         }
 943:         if (is_string($field)) {
 944:             if (isset($this->_schema[$field])) {
 945:                 return $this->_schema[$field];
 946:             } else {
 947:                 return null;
 948:             }
 949:         }
 950:         return $this->_schema;
 951:     }
 952: 
 953: /**
 954:  * Returns an associative array of field names and column types.
 955:  *
 956:  * @return array Field types indexed by field name
 957:  * @access public
 958:  */
 959:     function getColumnTypes() {
 960:         $columns = $this->schema();
 961:         if (empty($columns)) {
 962:             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);
 963:         }
 964:         $cols = array();
 965:         foreach ($columns as $field => $values) {
 966:             $cols[$field] = $values['type'];
 967:         }
 968:         return $cols;
 969:     }
 970: 
 971: /**
 972:  * Returns the column type of a column in the model.
 973:  *
 974:  * @param string $column The name of the model column
 975:  * @return string Column type
 976:  * @access public
 977:  */
 978:     function getColumnType($column) {
 979:         $db =& ConnectionManager::getDataSource($this->useDbConfig);
 980:         $cols = $this->schema();
 981:         $model = null;
 982: 
 983:         $column = str_replace(array($db->startQuote, $db->endQuote), '', $column);
 984: 
 985:         if (strpos($column, '.')) {
 986:             list($model, $column) = explode('.', $column);
 987:         }
 988:         if ($model != $this->alias && isset($this->{$model})) {
 989:             return $this->{$model}->getColumnType($column);
 990:         }
 991:         if (isset($cols[$column]) && isset($cols[$column]['type'])) {
 992:             return $cols[$column]['type'];
 993:         }
 994:         return null;
 995:     }
 996: 
 997: /**
 998:  * Returns true if the supplied field exists in the model's database table.
 999:  *
1000:  * @param mixed $name Name of field to look for, or an array of names
1001:  * @param boolean $checkVirtual checks if the field is declared as virtual
1002:  * @return mixed If $name is a string, returns a boolean indicating whether the field exists.
1003:  *               If $name is an array of field names, returns the first field that exists,
1004:  *               or false if none exist.
1005:  * @access public
1006:  */
1007:     function hasField($name, $checkVirtual = false) {
1008:         if (is_array($name)) {
1009:             foreach ($name as $n) {
1010:                 if ($this->hasField($n, $checkVirtual)) {
1011:                     return $n;
1012:                 }
1013:             }
1014:             return false;
1015:         }
1016: 
1017:         if ($checkVirtual && !empty($this->virtualFields)) {
1018:             if ($this->isVirtualField($name)) {
1019:                 return true;
1020:             }
1021:         }
1022: 
1023:         if (empty($this->_schema)) {
1024:             $this->schema();
1025:         }
1026: 
1027:         if ($this->_schema != null) {
1028:             return isset($this->_schema[$name]);
1029:         }
1030:         return false;
1031:     }
1032: 
1033: /**
1034:  * Returns true if the supplied field is a model Virtual Field
1035:  *
1036:  * @param mixed $name Name of field to look for
1037:  * @return boolean indicating whether the field exists as a model virtual field.
1038:  * @access public
1039:  */
1040:     function isVirtualField($field) {
1041:         if (empty($this->virtualFields) || !is_string($field)) {
1042:             return false;
1043:         }
1044:         if (isset($this->virtualFields[$field])) {
1045:             return true;
1046:         }
1047:         if (strpos($field, '.') !== false) {
1048:             list($model, $field) = explode('.', $field);
1049:             if ($model == $this->alias && isset($this->virtualFields[$field])) {
1050:                 return true;
1051:             }
1052:         }
1053:         return false;
1054:     }
1055: 
1056: /**
1057:  * Returns the expression for a model virtual field
1058:  *
1059:  * @param mixed $name Name of field to look for
1060:  * @return mixed If $field is string expression bound to virtual field $field
1061:  *    If $field is null, returns an array of all model virtual fields
1062:  *    or false if none $field exist.
1063:  * @access public
1064:  */
1065:     function getVirtualField($field = null) {
1066:         if ($field == null) {
1067:             return empty($this->virtualFields) ? false : $this->virtualFields;
1068:         }
1069:         if ($this->isVirtualField($field)) {
1070:             if (strpos($field, '.') !== false) {
1071:                 list($model, $field) = explode('.', $field);
1072:             }
1073:             return $this->virtualFields[$field];
1074:         }
1075:         return false;
1076:     }
1077: 
1078: /**
1079:  * Initializes the model for writing a new record, loading the default values
1080:  * for those fields that are not defined in $data, and clearing previous validation errors.
1081:  * Especially helpful for saving data in loops.
1082:  *
1083:  * @param mixed $data Optional data array to assign to the model after it is created.  If null or false,
1084:  *   schema data defaults are not merged.
1085:  * @param boolean $filterKey If true, overwrites any primary key input with an empty value
1086:  * @return array The current Model::data; after merging $data and/or defaults from database
1087:  * @access public
1088:  * @link http://book.cakephp.org/1.3/en/The-Manual/Developing-with-CakePHP/Models.html#Saving-Your-Data
1089:  */
1090:     function create($data = array(), $filterKey = false) {
1091:         $defaults = array();
1092:         $this->id = false;
1093:         $this->data = array();
1094:         $this->validationErrors = array();
1095: 
1096:         if ($data !== null && $data !== false) {
1097:             foreach ($this->schema() as $field => $properties) {
1098:                 if ($this->primaryKey !== $field && isset($properties['default']) && $properties['default'] !== '') {
1099:                     $defaults[$field] = $properties['default'];
1100:                 }
1101:             }
1102:             $this->set($defaults);
1103:             $this->set($data);
1104:         }
1105:         if ($filterKey) {
1106:             $this->set($this->primaryKey, false);
1107:         }
1108:         return $this->data;
1109:     }
1110: 
1111: /**
1112:  * Returns a list of fields from the database, and sets the current model
1113:  * data (Model::$data) with the record found.
1114:  *
1115:  * @param mixed $fields String of single fieldname, or an array of fieldnames.
1116:  * @param mixed $id The ID of the record to read
1117:  * @return array Array of database fields, or false if not found
1118:  * @access public
1119:  * @link http://book.cakephp.org/1.3/en/The-Manual/Developing-with-CakePHP/Models.html#Retrieving-Your-Data#read-1029
1120:  */
1121:     function read($fields = null, $id = null) {
1122:         $this->validationErrors = array();
1123: 
1124:         if ($id != null) {
1125:             $this->id = $id;
1126:         }
1127: 
1128:         $id = $this->id;
1129: 
1130:         if (is_array($this->id)) {
1131:             $id = $this->id[0];
1132:         }
1133: 
1134:         if ($id !== null && $id !== false) {
1135:             $this->data = $this->find('first', array(
1136:                 'conditions' => array($this->alias . '.' . $this->primaryKey => $id),
1137:                 'fields' => $fields
1138:             ));
1139:             return $this->data;
1140:         } else {
1141:             return false;
1142:         }
1143:     }
1144: 
1145: /**
1146:  * Returns the contents of a single field given the supplied conditions, in the
1147:  * supplied order.
1148:  *
1149:  * @param string $name Name of field to get
1150:  * @param array $conditions SQL conditions (defaults to NULL)
1151:  * @param string $order SQL ORDER BY fragment
1152:  * @return string field contents, or false if not found
1153:  * @access public
1154:  * @link http://book.cakephp.org/1.3/en/The-Manual/Developing-with-CakePHP/Models.html#Retrieving-Your-Data#field-1028
1155:  */
1156:     function field($name, $conditions = null, $order = null) {
1157:         if ($conditions === null && $this->id !== false) {
1158:             $conditions = array($this->alias . '.' . $this->primaryKey => $this->id);
1159:         }
1160:         if ($this->recursive >= 1) {
1161:             $recursive = -1;
1162:         } else {
1163:             $recursive = $this->recursive;
1164:         }
1165:         $fields = $name;
1166:         if ($data = $this->find('first', compact('conditions', 'fields', 'order', 'recursive'))) {
1167:             if (strpos($name, '.') === false) {
1168:                 if (isset($data[$this->alias][$name])) {
1169:                     return $data[$this->alias][$name];
1170:                 }
1171:             } else {
1172:                 $name = explode('.', $name);
1173:                 if (isset($data[$name[0]][$name[1]])) {
1174:                     return $data[$name[0]][$name[1]];
1175:                 }
1176:             }
1177:             if (isset($data[0]) && count($data[0]) > 0) {
1178:                 return array_shift($data[0]);
1179:             }
1180:         } else {
1181:             return false;
1182:         }
1183:     }
1184: 
1185: /**
1186:  * Saves the value of a single field to the database, based on the current
1187:  * model ID.
1188:  *
1189:  * @param string $name Name of the table field
1190:  * @param mixed $value Value of the field
1191:  * @param array $validate See $options param in Model::save(). Does not respect 'fieldList' key if passed
1192:  * @return boolean See Model::save()
1193:  * @access public
1194:  * @see Model::save()
1195:  * @link http://book.cakephp.org/1.3/en/The-Manual/Developing-with-CakePHP/Models.html#Saving-Your-Data
1196:  */
1197:     function saveField($name, $value, $validate = false) {
1198:         $id = $this->id;
1199:         $this->create(false);
1200: 
1201:         if (is_array($validate)) {
1202:             $options = array_merge(array('validate' => false, 'fieldList' => array($name)), $validate);
1203:         } else {
1204:             $options = array('validate' => $validate, 'fieldList' => array($name));
1205:         }
1206:         return $this->save(array($this->alias => array($this->primaryKey => $id, $name => $value)), $options);
1207:     }
1208: 
1209: /**
1210:  * Saves model data (based on white-list, if supplied) to the database. By
1211:  * default, validation occurs before save.
1212:  *
1213:  * @param array $data Data to save.
1214:  * @param mixed $validate Either a boolean, or an array.
1215:  *   If a boolean, indicates whether or not to validate before saving.
1216:  *   If an array, allows control of validate, callbacks, and fieldList
1217:  * @param array $fieldList List of fields to allow to be written
1218:  * @return mixed On success Model::$data if its not empty or true, false on failure
1219:  * @access public
1220:  * @link http://book.cakephp.org/1.3/en/The-Manual/Developing-with-CakePHP/Models.html#Saving-Your-Data
1221:  */
1222:     function save($data = null, $validate = true, $fieldList = array()) {
1223:         $defaults = array('validate' => true, 'fieldList' => array(), 'callbacks' => true);
1224:         $_whitelist = $this->whitelist;
1225:         $fields = array();
1226: 
1227:         if (!is_array($validate)) {
1228:             $options = array_merge($defaults, compact('validate', 'fieldList', 'callbacks'));
1229:         } else {
1230:             $options = array_merge($defaults, $validate);
1231:         }
1232: 
1233:         if (!empty($options['fieldList'])) {
1234:             $this->whitelist = $options['fieldList'];
1235:         } elseif ($options['fieldList'] === null) {
1236:             $this->whitelist = array();
1237:         }
1238:         $this->set($data);
1239: 
1240:         if (empty($this->data) && !$this->hasField(array('created', 'updated', 'modified'))) {
1241:             return false;
1242:         }
1243: 
1244:         foreach (array('created', 'updated', 'modified') as $field) {
1245:             $keyPresentAndEmpty = (
1246:                 isset($this->data[$this->alias]) &&
1247:                 array_key_exists($field, $this->data[$this->alias]) &&
1248:                 $this->data[$this->alias][$field] === null
1249:             );
1250:             if ($keyPresentAndEmpty) {
1251:                 unset($this->data[$this->alias][$field]);
1252:             }
1253:         }
1254: 
1255:         $exists = $this->exists();
1256:         $dateFields = array('modified', 'updated');
1257: 
1258:         if (!$exists) {
1259:             $dateFields[] = 'created';
1260:         }
1261:         if (isset($this->data[$this->alias])) {
1262:             $fields = array_keys($this->data[$this->alias]);
1263:         }
1264:         if ($options['validate'] && !$this->validates($options)) {
1265:             $this->whitelist = $_whitelist;
1266:             return false;
1267:         }
1268: 
1269:         $db =& ConnectionManager::getDataSource($this->useDbConfig);
1270: 
1271:         foreach ($dateFields as $updateCol) {
1272:             if ($this->hasField($updateCol) && !in_array($updateCol, $fields)) {
1273:                 $default = array('formatter' => 'date');
1274:                 $colType = array_merge($default, $db->columns[$this->getColumnType($updateCol)]);
1275:                 if (!array_key_exists('format', $colType)) {
1276:                     $time = strtotime('now');
1277:                 } else {
1278:                     $time = $colType['formatter']($colType['format']);
1279:                 }
1280:                 if (!empty($this->whitelist)) {
1281:                     $this->whitelist[] = $updateCol;
1282:                 }
1283:                 $this->set($updateCol, $time);
1284:             }
1285:         }
1286: 
1287:         if ($options['callbacks'] === true || $options['callbacks'] === 'before') {
1288:             $result = $this->Behaviors->trigger($this, 'beforeSave', array($options), array(
1289:                 'break' => true, 'breakOn' => false
1290:             ));
1291:             if (!$result || !$this->beforeSave($options)) {
1292:                 $this->whitelist = $_whitelist;
1293:                 return false;
1294:             }
1295:         }
1296: 
1297:         if (empty($this->data[$this->alias][$this->primaryKey])) {
1298:             unset($this->data[$this->alias][$this->primaryKey]);
1299:         }
1300:         $fields = $values = array();
1301: 
1302:         foreach ($this->data as $n => $v) {
1303:             if (isset($this->hasAndBelongsToMany[$n])) {
1304:                 if (isset($v[$n])) {
1305:                     $v = $v[$n];
1306:                 }
1307:                 $joined[$n] = $v;
1308:             } else {
1309:                 if ($n === $this->alias) {
1310:                     foreach (array('created', 'updated', 'modified') as $field) {
1311:                         if (array_key_exists($field, $v) && empty($v[$field])) {
1312:                             unset($v[$field]);
1313:                         }
1314:                     }
1315: 
1316:                     foreach ($v as $x => $y) {
1317:                         if ($this->hasField($x) && (empty($this->whitelist) || in_array($x, $this->whitelist))) {
1318:                             list($fields[], $values[]) = array($x, $y);
1319:                         }
1320:                     }
1321:                 }
1322:             }
1323:         }
1324:         $count = count($fields);
1325: 
1326:         if (!$exists && $count > 0) {
1327:             $this->id = false;
1328:         }
1329:         $success = true;
1330:         $created = false;
1331: 
1332:         if ($count > 0) {
1333:             $cache = $this->_prepareUpdateFields(array_combine($fields, $values));
1334: 
1335:             if (!empty($this->id)) {
1336:                 $this->__safeUpdateMode = true;
1337:                 $success = (bool)$db->update($this, $fields, $values);
1338:                 $this->__safeUpdateMode = false;
1339:             } else {
1340:                 $fInfo = $this->_schema[$this->primaryKey];
1341:                 $isUUID = ($fInfo['length'] == 36 &&
1342:                     ($fInfo['type'] === 'string' || $fInfo['type'] === 'binary')
1343:                 );
1344:                 if (empty($this->data[$this->alias][$this->primaryKey]) && $isUUID) {
1345:                     if (array_key_exists($this->primaryKey, $this->data[$this->alias])) {
1346:                         $j = array_search($this->primaryKey, $fields);
1347:                         $values[$j] = String::uuid();
1348:                     } else {
1349:                         list($fields[], $values[]) = array($this->primaryKey, String::uuid());
1350:                     }
1351:                 }
1352: 
1353:                 if (!$db->create($this, $fields, $values)) {
1354:                     $success = $created = false;
1355:                 } else {
1356:                     $created = true;
1357:                 }
1358:             }
1359: 
1360:             if ($success && !empty($this->belongsTo)) {
1361:                 $this->updateCounterCache($cache, $created);
1362:             }
1363:         }
1364: 
1365:         if (!empty($joined) && $success === true) {
1366:             $this->__saveMulti($joined, $this->id, $db);
1367:         }
1368: 
1369:         if ($success && $count > 0) {
1370:             if (!empty($this->data)) {
1371:                 $success = $this->data;
1372:             }
1373:             if ($options['callbacks'] === true || $options['callbacks'] === 'after') {
1374:                 $this->Behaviors->trigger($this, 'afterSave', array($created, $options));
1375:                 $this->afterSave($created);
1376:             }
1377:             if (!empty($this->data)) {
1378:                 $success = Set::merge($success, $this->data);
1379:             }
1380:             $this->data = false;
1381:             $this->_clearCache();
1382:             $this->validationErrors = array();
1383:         }
1384:         $this->whitelist = $_whitelist;
1385:         return $success;
1386:     }
1387: 
1388: /**
1389:  * Saves model hasAndBelongsToMany data to the database.
1390:  *
1391:  * @param array $joined Data to save
1392:  * @param mixed $id ID of record in this model
1393:  * @access private
1394:  */
1395:     function __saveMulti($joined, $id, &$db) {
1396:         foreach ($joined as $assoc => $data) {
1397: 
1398:             if (isset($this->hasAndBelongsToMany[$assoc])) {
1399:                 list($join) = $this->joinModel($this->hasAndBelongsToMany[$assoc]['with']);
1400: 
1401:                 $isUUID = !empty($this->{$join}->primaryKey) && (
1402:                         $this->{$join}->_schema[$this->{$join}->primaryKey]['length'] == 36 && (
1403:                         $this->{$join}->_schema[$this->{$join}->primaryKey]['type'] === 'string' ||
1404:                         $this->{$join}->_schema[$this->{$join}->primaryKey]['type'] === 'binary'
1405:                     )
1406:                 );
1407: 
1408:                 $newData = $newValues = array();
1409:                 $primaryAdded = false;
1410: 
1411:                 $fields =  array(
1412:                     $db->name($this->hasAndBelongsToMany[$assoc]['foreignKey']),
1413:                     $db->name($this->hasAndBelongsToMany[$assoc]['associationForeignKey'])
1414:                 );
1415: 
1416:                 $idField = $db->name($this->{$join}->primaryKey);
1417:                 if ($isUUID && !in_array($idField, $fields)) {
1418:                     $fields[] = $idField;
1419:                     $primaryAdded = true;
1420:                 }
1421: 
1422:                 foreach ((array)$data as $row) {
1423:                     if ((is_string($row) && (strlen($row) == 36 || strlen($row) == 16)) || is_numeric($row)) {
1424:                         $values = array(
1425:                             $db->value($id, $this->getColumnType($this->primaryKey)),
1426:                             $db->value($row)
1427:                         );
1428:                         if ($isUUID && $primaryAdded) {
1429:                             $values[] = $db->value(String::uuid());
1430:                         }
1431:                         $values = implode(',', $values);
1432:                         $newValues[] = "({$values})";
1433:                         unset($values);
1434:                     } elseif (isset($row[$this->hasAndBelongsToMany[$assoc]['associationForeignKey']])) {
1435:                         $newData[] = $row;
1436:                     } elseif (isset($row[$join]) && isset($row[$join][$this->hasAndBelongsToMany[$assoc]['associationForeignKey']])) {
1437:                         $newData[] = $row[$join];
1438:                     }
1439:                 }
1440: 
1441:                 if ($this->hasAndBelongsToMany[$assoc]['unique']) {
1442:                     $conditions = array(
1443:                         $join . '.' . $this->hasAndBelongsToMany[$assoc]['foreignKey'] => $id
1444:                     );
1445:                     if (!empty($this->hasAndBelongsToMany[$assoc]['conditions'])) {
1446:                         $conditions = array_merge($conditions, (array)$this->hasAndBelongsToMany[$assoc]['conditions']);
1447:                     }
1448:                     $links = $this->{$join}->find('all', array(
1449:                         'conditions' => $conditions,
1450:                         'recursive' => empty($this->hasAndBelongsToMany[$assoc]['conditions']) ? -1 : 0,
1451:                         'fields' => $this->hasAndBelongsToMany[$assoc]['associationForeignKey']
1452:                     ));
1453: 
1454:                     $associationForeignKey = "{$join}." . $this->hasAndBelongsToMany[$assoc]['associationForeignKey'];
1455:                     $oldLinks = Set::extract($links, "{n}.{$associationForeignKey}");
1456:                     if (!empty($oldLinks)) {
1457:                         $conditions[$associationForeignKey] = $oldLinks;
1458:                         $db->delete($this->{$join}, $conditions);
1459:                     }
1460:                 }
1461: 
1462:                 if (!empty($newData)) {
1463:                     foreach ($newData as $data) {
1464:                         $data[$this->hasAndBelongsToMany[$assoc]['foreignKey']] = $id;
1465:                         $this->{$join}->create($data);
1466:                         $this->{$join}->save();
1467:                     }
1468:                 }
1469: 
1470:                 if (!empty($newValues)) {
1471:                     $fields = implode(',', $fields);
1472:                     $db->insertMulti($this->{$join}, $fields, $newValues);
1473:                 }
1474:             }
1475:         }
1476:     }
1477: 
1478: /**
1479:  * Updates the counter cache of belongsTo associations after a save or delete operation
1480:  *
1481:  * @param array $keys Optional foreign key data, defaults to the information $this->data
1482:  * @param boolean $created True if a new record was created, otherwise only associations with
1483:  *   'counterScope' defined get updated
1484:  * @return void
1485:  * @access public
1486:  */
1487:     function updateCounterCache($keys = array(), $created = false) {
1488:         $keys = empty($keys) ? $this->data[$this->alias] : $keys;
1489:         $keys['old'] = isset($keys['old']) ? $keys['old'] : array();
1490: 
1491:         foreach ($this->belongsTo as $parent => $assoc) {
1492:             $foreignKey = $assoc['foreignKey'];
1493:             $fkQuoted = $this->escapeField($assoc['foreignKey']);
1494: 
1495:             if (!empty($assoc['counterCache'])) {
1496:                 if ($assoc['counterCache'] === true) {
1497:                     $assoc['counterCache'] = Inflector::underscore($this->alias) . '_count';
1498:                 }
1499:                 if (!$this->{$parent}->hasField($assoc['counterCache'])) {
1500:                     continue;
1501:                 }
1502: 
1503:                 if (!array_key_exists($foreignKey, $keys)) {
1504:                     $keys[$foreignKey] = $this->field($foreignKey);
1505:                 }
1506:                 $recursive = (isset($assoc['counterScope']) ? 1 : -1);
1507:                 $conditions = ($recursive == 1) ? (array)$assoc['counterScope'] : array();
1508: 
1509:                 if (isset($keys['old'][$foreignKey])) {
1510:                     if ($keys['old'][$foreignKey] != $keys[$foreignKey]) {
1511:                         $conditions[$fkQuoted] = $keys['old'][$foreignKey];
1512:                         $count = intval($this->find('count', compact('conditions', 'recursive')));
1513: 
1514:                         $this->{$parent}->updateAll(
1515:                             array($assoc['counterCache'] => $count),
1516:                             array($this->{$parent}->escapeField() => $keys['old'][$foreignKey])
1517:                         );
1518:                     }
1519:                 }
1520:                 $conditions[$fkQuoted] = $keys[$foreignKey];
1521: 
1522:                 if ($recursive == 1) {
1523:                     $conditions = array_merge($conditions, (array)$assoc['counterScope']);
1524:                 }
1525:                 $count = intval($this->find('count', compact('conditions', 'recursive')));
1526: 
1527:                 $this->{$parent}->updateAll(
1528:                     array($assoc['counterCache'] => $count),
1529:                     array($this->{$parent}->escapeField() => $keys[$foreignKey])
1530:                 );
1531:             }
1532:         }
1533:     }
1534: 
1535: /**
1536:  * Helper method for Model::updateCounterCache().  Checks the fields to be updated for
1537:  *
1538:  * @param array $data The fields of the record that will be updated
1539:  * @return array Returns updated foreign key values, along with an 'old' key containing the old
1540:  *     values, or empty if no foreign keys are updated.
1541:  * @access protected
1542:  */
1543:     function _prepareUpdateFields($data) {
1544:         $foreignKeys = array();
1545:         foreach ($this->belongsTo as $assoc => $info) {
1546:             if ($info['counterCache']) {
1547:                 $foreignKeys[$assoc] = $info['foreignKey'];
1548:             }
1549:         }
1550:         $included = array_intersect($foreignKeys, array_keys($data));
1551: 
1552:         if (empty($included) || empty($this->id)) {
1553:             return array();
1554:         }
1555:         $old = $this->find('first', array(
1556:             'conditions' => array($this->primaryKey => $this->id),
1557:             'fields' => array_values($included),
1558:             'recursive' => -1
1559:         ));
1560:         return array_merge($data, array('old' => $old[$this->alias]));
1561:     }
1562: 
1563: /**
1564:  * Saves multiple individual records for a single model; Also works with a single record, as well as
1565:  * all its associated records.
1566:  *
1567:  * #### Options
1568:  *
1569:  * - validate: Set to false to disable validation, true to validate each record before saving,
1570:  *   'first' to validate *all* records before any are saved (default),
1571:  *   or 'only' to only validate the records, but not save them.
1572:  * - atomic: If true (default), will attempt to save all records in a single transaction.
1573:  *   Should be set to false if database/table does not support transactions.
1574:  * - fieldList: Equivalent to the $fieldList parameter in Model::save()
1575:  *
1576:  * @param array $data Record data to save.  This can be either a numerically-indexed array (for saving multiple
1577:  *     records of the same type), or an array indexed by association name.
1578:  * @param array $options Options to use when saving record data, See $options above.
1579:  * @return mixed If atomic: True on success, or false on failure.
1580:  *    Otherwise: array similar to the $data array passed, but values are set to true/false
1581:  *    depending on whether each record saved successfully.
1582:  * @access public
1583:  * @link http://book.cakephp.org/1.3/en/The-Manual/Developing-with-CakePHP/Models.html#Saving-Related-Model-Data-hasOne-hasMany-belongsTo
1584:  * @link http://book.cakephp.org/1.3/en/The-Manual/Developing-with-CakePHP/Models.html#Saving-Your-Data
1585:  */
1586:     function saveAll($data = null, $options = array()) {
1587:         if (empty($data)) {
1588:             $data = $this->data;
1589:         }
1590:         $db =& ConnectionManager::getDataSource($this->useDbConfig);
1591: 
1592:         $options = array_merge(array('validate' => 'first', 'atomic' => true), $options);
1593:         $this->validationErrors = $validationErrors = array();
1594:         $validates = true;
1595:         $return = array();
1596: 
1597:         if (empty($data) && $options['validate'] !== false) {
1598:             $result = $this->save($data, $options);
1599:             return !empty($result);
1600:         }
1601: 
1602:         if ($options['atomic'] && $options['validate'] !== 'only') {
1603:             $transactionBegun = $db->begin($this);
1604:         }
1605: 
1606:         if (Set::numeric(array_keys($data))) {
1607:             while ($validates) {
1608:                 $return = array();
1609:                 foreach ($data as $key => $record) {
1610:                     if (!$currentValidates = $this->__save($record, $options)) {
1611:                         $validationErrors[$key] = $this->validationErrors;
1612:                     }
1613: 
1614:                     if ($options['validate'] === 'only' || $options['validate'] === 'first') {
1615:                         $validating = true;
1616:                         if ($options['atomic']) {
1617:                             $validates = $validates && $currentValidates;
1618:                         } else {
1619:                             $validates = $currentValidates;
1620:                         }
1621:                     } else {
1622:                         $validating = false;
1623:                         $validates = $currentValidates;
1624:                     }
1625: 
1626:                     if (!$options['atomic']) {
1627:                         $return[] = $validates;
1628:                     } elseif (!$validates && !$validating) {
1629:                         break;
1630:                     }
1631:                 }
1632:                 $this->validationErrors = $validationErrors;
1633: 
1634:                 switch (true) {
1635:                     case ($options['validate'] === 'only'):
1636:                         return ($options['atomic'] ? $validates : $return);
1637:                     break;
1638:                     case ($options['validate'] === 'first'):
1639:                         $options['validate'] = true;
1640:                     break;
1641:                     default:
1642:                         if ($options['atomic']) {
1643:                             if ($validates) {
1644:                                 if ($transactionBegun) {
1645:                                     return $db->commit($this) !== false;
1646:                                 } else {
1647:                                     return true;
1648:                                 }
1649:                             }
1650:                             $db->rollback($this);
1651:                             return false;
1652:                         }
1653:                         return $return;
1654:                     break;
1655:                 }
1656:             }
1657:             if ($options['atomic'] && !$validates) {
1658:                 $db->rollback($this);
1659:                 return false;
1660:             }
1661:             return $return;
1662:         }
1663:         $associations = $this->getAssociated();
1664: 
1665:         while ($validates) {
1666:             foreach ($data as $association => $values) {
1667:                 if (isset($associations[$association])) {
1668:                     switch ($associations[$association]) {
1669:                         case 'belongsTo':
1670:                             if ($this->{$association}->__save($values, $options)) {
1671:                                 $data[$this->alias][$this->belongsTo[$association]['foreignKey']] = $this->{$association}->id;
1672:                             } else {
1673:                                 $validationErrors[$association] = $this->{$association}->validationErrors;
1674:                                 $validates = false;
1675:                             }
1676:                             if (!$options['atomic']) {
1677:                                 $return[$association][] = $validates;
1678:                             }
1679:                         break;
1680:                     }
1681:                 }
1682:             }
1683: 
1684:             if (!$this->__save($data, $options)) {
1685:                 $validationErrors[$this->alias] = $this->validationErrors;
1686:                 $validates = false;
1687:             }
1688:             if (!$options['atomic']) {
1689:                 $return[$this->alias] = $validates;
1690:             }
1691:             $validating = ($options['validate'] === 'only' || $options['validate'] === 'first');
1692: 
1693:             foreach ($data as $association => $values) {
1694:                 if (!$validates && !$validating) {
1695:                     break;
1696:                 }
1697:                 if (isset($associations[$association])) {
1698:                     $type = $associations[$association];
1699:                     switch ($type) {
1700:                         case 'hasOne':
1701:                             if (!$validating) {
1702:                                 $values[$this->{$type}[$association]['foreignKey']] = $this->id;
1703:                             }
1704: 
1705:                             if (!$this->{$association}->__save($values, $options)) {
1706:                                 $validationErrors[$association] = $this->{$association}->validationErrors;
1707:                                 $validates = false;
1708:                             }
1709:                             if (!$options['atomic']) {
1710:                                 $return[$association][] = $validates;
1711:                             }
1712:                         break;
1713:                         case 'hasMany':
1714:                             if (!$validating) {
1715:                                 foreach ($values as $i => $value) {
1716:                                     $values[$i][$this->{$type}[$association]['foreignKey']] =  $this->id;
1717:                                 }
1718:                             }
1719: 
1720:                             $_options = array_merge($options, array('atomic' => false));
1721: 
1722:                             if ($_options['validate'] === 'first') {
1723:                                 $_options['validate'] = 'only';
1724:                             }
1725:                             $_return = $this->{$association}->saveAll($values, $_options);
1726: 
1727:                             if ($_return === false || (is_array($_return) && in_array(false, $_return, true))) {
1728:                                 $validationErrors[$association] = $this->{$association}->validationErrors;
1729:                                 $validates = false;
1730:                             }
1731:                             if (is_array($_return)) {
1732:                                 foreach ($_return as $val) {
1733:                                     if (!isset($return[$association])) {
1734:                                         $return[$association] = array();
1735:                                     } elseif (!is_array($return[$association])) {
1736:                                         $return[$association] = array($return[$association]);
1737:                                     }
1738:                                     $return[$association][] = $val;
1739:                                 }
1740:                             } else {
1741:                                 $return[$association] = $_return;
1742:                             }
1743:                         break;
1744:                     }
1745:                 }
1746:             }
1747:             $this->validationErrors = $validationErrors;
1748: 
1749:             if (isset($validationErrors[$this->alias])) {
1750:                 $this->validationErrors = $validationErrors[$this->alias];
1751:             }
1752: 
1753:             switch (true) {
1754:                 case ($options['validate'] === 'only'):
1755:                     return ($options['atomic'] ? $validates : $return);
1756:                 break;
1757:                 case ($options['validate'] === 'first'):
1758:                     $options['validate'] = true;
1759:                     $return = array();
1760:                 break;
1761:                 default:
1762:                     if ($options['atomic']) {
1763:                         if ($validates) {
1764:                             if ($transactionBegun) {
1765:                                 return $db->commit($this) !== false;
1766:                             } else {
1767:                                 return true;
1768:                             }
1769:                         } else {
1770:                             $db->rollback($this);
1771:                         }
1772:                     }
1773:                     return $return;
1774:                 break;
1775:             }
1776:             if ($options['atomic'] && !$validates) {
1777:                 $db->rollback($this);
1778:                 return false;
1779:             }
1780:         }
1781:     }
1782: 
1783: /**
1784:  * Private helper method used by saveAll.
1785:  *
1786:  * @return boolean Success
1787:  * @access private
1788:  * @see Model::saveAll()
1789:  */
1790:     function __save($data, $options) {
1791:         if ($options['validate'] === 'first' || $options['validate'] === 'only') {
1792:             if (!($this->create($data) && $this->validates($options))) {
1793:                 return false;
1794:             }
1795:         } elseif (!($this->create(null) !== null && $this->save($data, $options))) {
1796:             return false;
1797:         }
1798:         return true;
1799:     }
1800: 
1801: /**
1802:  * Updates multiple model records based on a set of conditions.
1803:  *
1804:  * @param array $fields Set of fields and values, indexed by fields.
1805:  *    Fields are treated as SQL snippets, to insert literal values manually escape your data.
1806:  * @param mixed $conditions Conditions to match, true for all records
1807:  * @return boolean True on success, false on failure
1808:  * @access public
1809:  * @link http://book.cakephp.org/1.3/en/The-Manual/Developing-with-CakePHP/Models.html#Saving-Your-Data
1810:  */
1811:     function updateAll($fields, $conditions = true) {
1812:         $db =& ConnectionManager::getDataSource($this->useDbConfig);
1813:         return $db->update($this, $fields, null, $conditions);
1814:     }
1815: 
1816: /**
1817:  * Removes record for given ID. If no ID is given, the current ID is used. Returns true on success.
1818:  *
1819:  * @param mixed $id ID of record to delete
1820:  * @param boolean $cascade Set to true to delete records that depend on this record
1821:  * @return boolean True on success
1822:  * @access public
1823:  * @link http://book.cakephp.org/1.3/en/The-Manual/Developing-with-CakePHP/Models.html#delete
1824:  */
1825:     function delete($id = null, $cascade = true) {
1826:         if (!empty($id)) {
1827:             $this->id = $id;
1828:         }
1829:         $id = $this->id;
1830: 
1831:         if ($this->beforeDelete($cascade)) {
1832:             $filters = $this->Behaviors->trigger($this, 'beforeDelete', array($cascade), array(
1833:                 'break' => true, 'breakOn' => false
1834:             ));
1835:             if (!$filters || !$this->exists()) {
1836:                 return false;
1837:             }
1838:             $db =& ConnectionManager::getDataSource($this->useDbConfig);
1839: 
1840:             $this->_deleteDependent($id, $cascade);
1841:             $this->_deleteLinks($id);
1842:             $this->id = $id;
1843: 
1844:             if (!empty($this->belongsTo)) {
1845:                 $keys = $this->find('first', array(
1846:                     'fields' => $this->__collectForeignKeys(),
1847:                     'conditions' => array($this->alias . '.' . $this->primaryKey => $id)
1848:                 ));
1849:             }
1850: 
1851:             if ($db->delete($this, array($this->alias . '.' . $this->primaryKey => $id))) {
1852:                 if (!empty($this->belongsTo)) {
1853:                     $this->updateCounterCache($keys[$this->alias]);
1854:                 }
1855:                 $this->Behaviors->trigger($this, 'afterDelete');
1856:                 $this->afterDelete();
1857:                 $this->_clearCache();
1858:                 $this->id = false;
1859:                 return true;
1860:             }
1861:         }
1862:         return false;
1863:     }
1864: 
1865: /**
1866:  * Cascades model deletes through associated hasMany and hasOne child records.
1867:  *
1868:  * @param string $id ID of record that was deleted
1869:  * @param boolean $cascade Set to true to delete records that depend on this record
1870:  * @return void
1871:  * @access protected
1872:  */
1873:     function _deleteDependent($id, $cascade) {
1874:         if (!empty($this->__backAssociation)) {
1875:             $savedAssociatons = $this->__backAssociation;
1876:             $this->__backAssociation = array();
1877:         }
1878:         if ($cascade === true) {
1879:             foreach (array_merge($this->hasMany, $this->hasOne) as $assoc => $data) {
1880:                 if ($data['dependent'] === true) {
1881: 
1882:                     $model =& $this->{$assoc};
1883:                     if ($data['foreignKey'] === false && $data['conditions'] && in_array($this->name, $model->getAssociated('belongsTo'))) {
1884:                         $model->recursive = 0;
1885:                         $conditions = array($this->escapeField(null, $this->name) => $id);
1886:                     } else {
1887:                         $model->recursive = -1;
1888:                         $conditions = array($model->escapeField($data['foreignKey']) => $id);
1889:                         if ($data['conditions']) {
1890:                             $conditions = array_merge((array)$data['conditions'], $conditions);
1891:                         }
1892:                     }
1893: 
1894:                     if (isset($data['exclusive']) && $data['exclusive']) {
1895:                         $model->deleteAll($conditions);
1896:                     } else {
1897:                         $records = $model->find('all', array(
1898:                             'conditions' => $conditions, 'fields' => $model->primaryKey
1899:                         ));
1900: 
1901:                         if (!empty($records)) {
1902:                             foreach ($records as $record) {
1903:                                 $model->delete($record[$model->alias][$model->primaryKey]);
1904:                             }
1905:                         }
1906:                     }
1907:                 }
1908:             }
1909:         }
1910:         if (isset($savedAssociatons)) {
1911:             $this->__backAssociation = $savedAssociatons;
1912:         }
1913:     }
1914: 
1915: /**
1916:  * Cascades model deletes through HABTM join keys.
1917:  *
1918:  * @param string $id ID of record that was deleted
1919:  * @return void
1920:  * @access protected
1921:  */
1922:     function _deleteLinks($id) {
1923:         foreach ($this->hasAndBelongsToMany as $assoc => $data) {
1924:             $joinModel = $data['with'];
1925:             $records = $this->{$joinModel}->find('all', array(
1926:                 'conditions' => array_merge(array($this->{$joinModel}->escapeField($data['foreignKey']) => $id)),
1927:                 'fields' => $this->{$joinModel}->primaryKey,
1928:                 'recursive' => -1
1929:             ));
1930:             if (!empty($records)) {
1931:                 foreach ($records as $record) {
1932:                     $this->{$joinModel}->delete($record[$this->{$joinModel}->alias][$this->{$joinModel}->primaryKey]);
1933:                 }
1934:             }
1935:         }
1936:     }
1937: 
1938: /**
1939:  * Deletes multiple model records based on a set of conditions.
1940:  *
1941:  * @param mixed $conditions Conditions to match
1942:  * @param boolean $cascade Set to true to delete records that depend on this record
1943:  * @param boolean $callbacks Run callbacks
1944:  * @return boolean True on success, false on failure
1945:  * @access public
1946:  * @link http://book.cakephp.org/1.3/en/The-Manual/Developing-with-CakePHP/Models.html#deleteAll
1947:  */
1948:     function deleteAll($conditions, $cascade = true, $callbacks = false) {
1949:         if (empty($conditions)) {
1950:             return false;
1951:         }
1952:         $db =& ConnectionManager::getDataSource($this->useDbConfig);
1953: 
1954:         if (!$cascade && !$callbacks) {
1955:             return $db->delete($this, $conditions);
1956:         } else {
1957:             $ids = $this->find('all', array_merge(array(
1958:                 'fields' => "{$this->alias}.{$this->primaryKey}",
1959:                 'recursive' => 0), compact('conditions'))
1960:             );
1961:             if ($ids === false) {
1962:                 return false;
1963:             }
1964: 
1965:             $ids = Set::extract($ids, "{n}.{$this->alias}.{$this->primaryKey}");
1966:             if (empty($ids)) {
1967:                 return true;
1968:             }
1969: 
1970:             if ($callbacks) {
1971:                 $_id = $this->id;
1972:                 $result = true;
1973:                 foreach ($ids as $id) {
1974:                     $result = ($result && $this->delete($id, $cascade));
1975:                 }
1976:                 $this->id = $_id;
1977:                 return $result;
1978:             } else {
1979:                 foreach ($ids as $id) {
1980:                     $this->_deleteLinks($id);
1981:                     if ($cascade) {
1982:                         $this->_deleteDependent($id, $cascade);
1983:                     }
1984:                 }
1985:                 return $db->delete($this, array($this->alias . '.' . $this->primaryKey => $ids));
1986:             }
1987:         }
1988:     }
1989: 
1990: /**
1991:  * Collects foreign keys from associations.
1992:  *
1993:  * @return array
1994:  * @access private
1995:  */
1996:     function __collectForeignKeys($type = 'belongsTo') {
1997:         $result = array();
1998: 
1999:         foreach ($this->{$type} as $assoc => $data) {
2000:             if (isset($data['foreignKey']) && is_string($data['foreignKey'])) {
2001:                 $result[$assoc] = $data['foreignKey'];
2002:             }
2003:         }
2004:         return $result;
2005:     }
2006: 
2007: /**
2008:  * Returns true if a record with the currently set ID exists.
2009:  *
2010:  * Internally calls Model::getID() to obtain the current record ID to verify,
2011:  * and then performs a Model::find('count') on the currently configured datasource
2012:  * to ascertain the existence of the record in persistent storage.
2013:  *
2014:  * @return boolean True if such a record exists
2015:  * @access public
2016:  */
2017:     function exists() {
2018:         if ($this->getID() === false) {
2019:             return false;
2020:         }
2021:         $conditions = array($this->alias . '.' . $this->primaryKey => $this->getID());
2022:         $query = array('conditions' => $conditions, 'recursive' => -1, 'callbacks' => false);
2023:         return ($this->find('count', $query) > 0);
2024:     }
2025: 
2026: /**
2027:  * Returns true if a record that meets given conditions exists.
2028:  *
2029:  * @param array $conditions SQL conditions array
2030:  * @return boolean True if such a record exists
2031:  * @access public
2032:  */
2033:     function hasAny($conditions = null) {
2034:         return ($this->find('count', array('conditions' => $conditions, 'recursive' => -1)) != false);
2035:     }
2036: 
2037: /**
2038:  * Queries the datasource and returns a result set array.
2039:  *
2040:  * Also used to perform new-notation finds, where the first argument is type of find operation to perform
2041:  * (all / first / count / neighbors / list / threaded ),
2042:  * second parameter options for finding ( indexed array, including: 'conditions', 'limit',
2043:  * 'recursive', 'page', 'fields', 'offset', 'order')
2044:  *
2045:  * Eg:
2046:  * {{{
2047:  *  find('all', array(
2048:  *      'conditions' => array('name' => 'Thomas Anderson'),
2049:  *      'fields' => array('name', 'email'),
2050:  *      'order' => 'field3 DESC',
2051:  *      'recursive' => 2,
2052:  *      'group' => 'type'
2053:  * ));
2054:  * }}}
2055:  *
2056:  * In addition to the standard query keys above, you can provide Datasource, and behavior specific
2057:  * keys.  For example, when using a SQL based datasource you can use the joins key to specify additional
2058:  * joins that should be part of the query.
2059:  *
2060:  * {{{
2061:  * find('all', array(
2062:  *      'conditions' => array('name' => 'Thomas Anderson'),
2063:  *      'joins' => array(
2064:  *          array(
2065:  *              'alias' => 'Thought',
2066:  *              'table' => 'thoughts',
2067:  *              'type' => 'LEFT',
2068:  *              'conditions' => '`Thought`.`person_id` = `Person`.`id`'
2069:  *          )
2070:  *      )
2071:  * ));
2072:  * }}}
2073:  *
2074:  * Behaviors and find types can also define custom finder keys which are passed into find().
2075:  *
2076:  * Specifying 'fields' for new-notation 'list':
2077:  *
2078:  *  - If no fields are specified, then 'id' is used for key and 'model->displayField' is used for value.
2079:  *  - If a single field is specified, 'id' is used for key and specified field is used for value.
2080:  *  - If three fields are specified, they are used (in order) for key, value and group.
2081:  *  - Otherwise, first and second fields are used for key and value.
2082:  *
2083:  *  Note: find(list) + database views have issues with MySQL 5.0. Try upgrading to MySQL 5.1 if you
2084:  *  have issues with database views.
2085:  *
2086:  * @param array $conditions SQL conditions array, or type of find operation (all / first / count /
2087:  *    neighbors / list / threaded)
2088:  * @param mixed $fields Either a single string of a field name, or an array of field names, or
2089:  *    options for matching
2090:  * @param string $order SQL ORDER BY conditions (e.g. "price DESC" or "name ASC")
2091:  * @param integer $recursive The number of levels deep to fetch associated records
2092:  * @return array Array of records
2093:  * @access public
2094:  * @link http://book.cakephp.org/1.3/en/The-Manual/Developing-with-CakePHP/Models.html#find
2095:  */
2096:     function find($conditions = null, $fields = array(), $order = null, $recursive = null) {
2097:         if (!is_string($conditions) || (is_string($conditions) && !array_key_exists($conditions, $this->_findMethods))) {
2098:             $type = 'first';
2099:             $query = array_merge(compact('conditions', 'fields', 'order', 'recursive'), array('limit' => 1));
2100:         } else {
2101:             list($type, $query) = array($conditions, $fields);
2102:         }
2103: 
2104:         $this->findQueryType = $type;
2105:         $this->id = $this->getID();
2106: 
2107:         $query = array_merge(
2108:             array(
2109:                 'conditions' => null, 'fields' => null, 'joins' => array(), 'limit' => null,
2110:                 'offset' => null, 'order' => null, 'page' => null, 'group' => null, 'callbacks' => true
2111:             ),
2112:             (array)$query
2113:         );
2114: 
2115:         if ($type != 'all') {
2116:             if ($this->_findMethods[$type] === true) {
2117:                 $query = $this->{'_find' . ucfirst($type)}('before', $query);
2118:             }
2119:         }
2120: 
2121:         if (!is_numeric($query['page']) || intval($query['page']) < 1) {
2122:             $query['page'] = 1;
2123:         }
2124:         if ($query['page'] > 1 && !empty($query['limit'])) {
2125:             $query['offset'] = ($query['page'] - 1) * $query['limit'];
2126:         }
2127:         if ($query['order'] === null && $this->order !== null) {
2128:             $query['order'] = $this->order;
2129:         }
2130:         $query['order'] = array($query['order']);
2131: 
2132:         if ($query['callbacks'] === true || $query['callbacks'] === 'before') {
2133:             $return = $this->Behaviors->trigger($this, 'beforeFind', array($query), array(
2134:                 'break' => true, 'breakOn' => false, 'modParams' => true
2135:             ));
2136:             $query = (is_array($return)) ? $return : $query;
2137: 
2138:             if ($return === false) {
2139:                 return null;
2140:             }
2141: 
2142:             $return = $this->beforeFind($query);
2143:             $query = (is_array($return)) ? $return : $query;
2144: 
2145:             if ($return === false) {
2146:                 return null;
2147:             }
2148:         }
2149: 
2150:         if (!$db =& ConnectionManager::getDataSource($this->useDbConfig)) {
2151:             return false;
2152:         }
2153:         $results = $db->read($this, $query);
2154:         $this->resetAssociations();
2155: 
2156:         if ($query['callbacks'] === true || $query['callbacks'] === 'after') {
2157:             $results = $this->__filterResults($results);
2158:         }
2159: 
2160:         $this->findQueryType = null;
2161: 
2162:         if ($type === 'all') {
2163:             return $results;
2164:         } else {
2165:             if ($this->_findMethods[$type] === true) {
2166:                 return $this->{'_find' . ucfirst($type)}('after', $query, $results);
2167:             }
2168:         }
2169:     }
2170: 
2171: /**
2172:  * Handles the before/after filter logic for find('first') operations.  Only called by Model::find().
2173:  *
2174:  * @param string $state Either "before" or "after"
2175:  * @param array $query
2176:  * @param array $data
2177:  * @return array
2178:  * @access protected
2179:  * @see Model::find()
2180:  */
2181:     function _findFirst($state, $query, $results = array()) {
2182:         if ($state == 'before') {
2183:             $query['limit'] = 1;
2184:             return $query;
2185:         } elseif ($state == 'after') {
2186:             if (empty($results[0])) {
2187:                 return false;
2188:             }
2189:             return $results[0];
2190:         }
2191:     }
2192: 
2193: /**
2194:  * Handles the before/after filter logic for find('count') operations.  Only called by Model::find().
2195:  *
2196:  * @param string $state Either "before" or "after"
2197:  * @param array $query
2198:  * @param array $data
2199:  * @return int The number of records found, or false
2200:  * @access protected
2201:  * @see Model::find()
2202:  */
2203:     function _findCount($state, $query, $results = array()) {
2204:         if ($state == 'before') {
2205:             $db =& ConnectionManager::getDataSource($this->useDbConfig);
2206:             if (empty($query['fields'])) {
2207:                 $query['fields'] = $db->calculate($this, 'count');
2208:             } elseif (is_string($query['fields'])  && !preg_match('/count/i', $query['fields'])) {
2209:                 $query['fields'] = $db->calculate($this, 'count', array(
2210:                     $db->expression($query['fields']), 'count'
2211:                 ));
2212:             }
2213:             $query['order'] = false;
2214:             return $query;
2215:         } elseif ($state == 'after') {
2216:             if (isset($results[0][0]['count'])) {
2217:                 return intval($results[0][0]['count']);
2218:             } elseif (isset($results[0][$this->alias]['count'])) {
2219:                 return intval($results[0][$this->alias]['count']);
2220:             }
2221:             return false;
2222:         }
2223:     }
2224: 
2225: /**
2226:  * Handles the before/after filter logic for find('list') operations.  Only called by Model::find().
2227:  *
2228:  * @param string $state Either "before" or "after"
2229:  * @param array $query
2230:  * @param array $data
2231:  * @return array Key/value pairs of primary keys/display field values of all records found
2232:  * @access protected
2233:  * @see Model::find()
2234:  */
2235:     function _findList($state, $query, $results = array()) {
2236:         if ($state == 'before') {
2237:             if (empty($query['fields'])) {
2238:                 $query['fields'] = array("{$this->alias}.{$this->primaryKey}", "{$this->alias}.{$this->displayField}");
2239:                 $list = array("{n}.{$this->alias}.{$this->primaryKey}", "{n}.{$this->alias}.{$this->displayField}", null);
2240:             } else {
2241:                 if (!is_array($query['fields'])) {
2242:                     $query['fields'] = String::tokenize($query['fields']);
2243:                 }
2244: 
2245:                 if (count($query['fields']) == 1) {
2246:                     if (strpos($query['fields'][0], '.') === false) {
2247:                         $query['fields'][0] = $this->alias . '.' . $query['fields'][0];
2248:                     }
2249: 
2250:                     $list = array("{n}.{$this->alias}.{$this->primaryKey}", '{n}.' . $query['fields'][0], null);
2251:                     $query['fields'] = array("{$this->alias}.{$this->primaryKey}", $query['fields'][0]);
2252:                 } elseif (count($query['fields']) == 3) {
2253:                     for ($i = 0; $i < 3; $i++) {
2254:                         if (strpos($query['fields'][$i], '.') === false) {
2255:                             $query['fields'][$i] = $this->alias . '.' . $query['fields'][$i];
2256:                         }
2257:                     }
2258: 
2259:                     $list = array('{n}.' . $query['fields'][0], '{n}.' . $query['fields'][1], '{n}.' . $query['fields'][2]);
2260:                 } else {
2261:                     for ($i = 0; $i < 2; $i++) {
2262:                         if (strpos($query['fields'][$i], '.') === false) {
2263:                             $query['fields'][$i] = $this->alias . '.' . $query['fields'][$i];
2264:                         }
2265:                     }
2266: 
2267:                     $list = array('{n}.' . $query['fields'][0], '{n}.' . $query['fields'][1], null);
2268:                 }
2269:             }
2270:             if (!isset($query['recursive']) || $query['recursive'] === null) {
2271:                 $query['recursive'] = -1;
2272:             }
2273:             list($query['list']['keyPath'], $query['list']['valuePath'], $query['list']['groupPath']) = $list;
2274:             return $query;
2275:         } elseif ($state == 'after') {
2276:             if (empty($results)) {
2277:                 return array();
2278:             }
2279:             $lst = $query['list'];
2280:             return Set::combine($results, $lst['keyPath'], $lst['valuePath'], $lst['groupPath']);
2281:         }
2282:     }
2283: 
2284: /**
2285:  * Detects the previous field's value, then uses logic to find the 'wrapping'
2286:  * rows and return them.
2287:  *
2288:  * @param string $state Either "before" or "after"
2289:  * @param mixed $query
2290:  * @param array $results
2291:  * @return array
2292:  * @access protected
2293:  */
2294:     function _findNeighbors($state, $query, $results = array()) {
2295:         if ($state == 'before') {
2296:             $query = array_merge(array('recursive' => 0), $query);
2297:             extract($query);
2298:             $conditions = (array)$conditions;
2299:             if (isset($field) && isset($value)) {
2300:                 if (strpos($field, '.') === false) {
2301:                     $field = $this->alias . '.' . $field;
2302:                 }
2303:             } else {
2304:                 $field = $this->alias . '.' . $this->primaryKey;
2305:                 $value = $this->id;
2306:             }
2307:             $query['conditions'] =  array_merge($conditions, array($field . ' <' => $value));
2308:             $query['order'] = $field . ' DESC';
2309:             $query['limit'] = 1;
2310:             $query['field'] = $field;
2311:             $query['value'] = $value;
2312:             return $query;
2313:         } elseif ($state == 'after') {
2314:             extract($query);
2315:             unset($query['conditions'][$field . ' <']);
2316:             $return = array();
2317:             if (isset($results[0])) {
2318:                 $prevVal = Set::extract('/' . str_replace('.', '/', $field), $results[0]);
2319:                 $query['conditions'][$field . ' >='] = $prevVal[0];
2320:                 $query['conditions'][$field . ' !='] = $value;
2321:                 $query['limit'] = 2;
2322:             } else {
2323:                 $return['prev'] = null;
2324:                 $query['conditions'][$field . ' >'] = $value;
2325:                 $query['limit'] = 1;
2326:             }
2327:             $query['order'] = $field . ' ASC';
2328:             $return2 = $this->find('all', $query);
2329:             if (!array_key_exists('prev', $return)) {
2330:                 $return['prev'] = $return2[0];
2331:             }
2332:             if (count($return2) == 2) {
2333:                 $return['next'] = $return2[1];
2334:             } elseif (count($return2) == 1 && !$return['prev']) {
2335:                 $return['next'] = $return2[0];
2336:             } else {
2337:                 $return['next'] = null;
2338:             }
2339:             return $return;
2340:         }
2341:     }
2342: 
2343: /**
2344:  * In the event of ambiguous results returned (multiple top level results, with different parent_ids)
2345:  * top level results with different parent_ids to the first result will be dropped
2346:  *
2347:  * @param mixed $state
2348:  * @param mixed $query
2349:  * @param array $results
2350:  * @return array Threaded results
2351:  * @access protected
2352:  */
2353:     function _findThreaded($state, $query, $results = array()) {
2354:         if ($state == 'before') {
2355:             return $query;
2356:         } elseif ($state == 'after') {
2357:             $return = $idMap = array();
2358:             $ids = Set::extract($results, '{n}.' . $this->alias . '.' . $this->primaryKey);
2359: 
2360:             foreach ($results as $result) {
2361:                 $result['children'] = array();
2362:                 $id = $result[$this->alias][$this->primaryKey];
2363:                 $parentId = $result[$this->alias]['parent_id'];
2364:                 if (isset($idMap[$id]['children'])) {
2365:                     $idMap[$id] = array_merge($result, (array)$idMap[$id]);
2366:                 } else {
2367:                     $idMap[$id] = array_merge($result, array('children' => array()));
2368:                 }
2369:                 if (!$parentId || !in_array($parentId, $ids)) {
2370:                     $return[] =& $idMap[$id];
2371:                 } else {
2372:                     $idMap[$parentId]['children'][] =& $idMap[$id];
2373:                 }
2374:             }
2375:             if (count($return) > 1) {
2376:                 $ids = array_unique(Set::extract('/' . $this->alias . '/parent_id', $return));
2377:                 if (count($ids) > 1) {
2378:                     $root = $return[0][$this->alias]['parent_id'];
2379:                     foreach ($return as $key => $value) {
2380:                         if ($value[$this->alias]['parent_id'] != $root) {
2381:                             unset($return[$key]);
2382:                         }
2383:                     }
2384:                 }
2385:             }
2386:             return $return;
2387:         }
2388:     }
2389: 
2390: /**
2391:  * Passes query results through model and behavior afterFilter() methods.
2392:  *
2393:  * @param array Results to filter
2394:  * @param boolean $primary If this is the primary model results (results from model where the find operation was performed)
2395:  * @return array Set of filtered results
2396:  * @access private
2397:  */
2398:     function __filterResults($results, $primary = true) {
2399:         $return = $this->Behaviors->trigger($this, 'afterFind', array($results, $primary), array('modParams' => true));
2400:         if ($return !== true) {
2401:             $results = $return;
2402:         }
2403:         return $this->afterFind($results, $primary);
2404:     }
2405: 
2406: /**
2407:  * This resets the association arrays for the model back
2408:  * to those originally defined in the model. Normally called at the end
2409:  * of each call to Model::find()
2410:  *
2411:  * @return boolean Success
2412:  * @access public
2413:  */
2414:     function resetAssociations() {
2415:         if (!empty($this->__backAssociation)) {
2416:             foreach ($this->__associations as $type) {
2417:                 if (isset($this->__backAssociation[$type])) {
2418:                     $this->{$type} = $this->__backAssociation[$type];
2419:                 }
2420:             }
2421:             $this->__backAssociation = array();
2422:         }
2423: 
2424:         foreach ($this->__associations as $type) {
2425:             foreach ($this->{$type} as $key => $name) {
2426:                 if (!empty($this->{$key}->__backAssociation)) {
2427:                     $this->{$key}->resetAssociations();
2428:                 }
2429:             }
2430:         }
2431:         $this->__backAssociation = array();
2432:         return true;
2433:     }
2434: 
2435: /**
2436:  * Returns false if any fields passed match any (by default, all if $or = false) of their matching values.
2437:  *
2438:  * @param array $fields Field/value pairs to search (if no values specified, they are pulled from $this->data)
2439:  * @param boolean $or If false, all fields specified must match in order for a false return value
2440:  * @return boolean False if any records matching any fields are found
2441:  * @access public
2442:  */
2443:     function isUnique($fields, $or = true) {
2444:         if (!is_array($fields)) {
2445:             $fields = func_get_args();
2446:             if (is_bool($fields[count($fields) - 1])) {
2447:                 $or = $fields[count($fields) - 1];
2448:                 unset($fields[count($fields) - 1]);
2449:             }
2450:         }
2451: 
2452:         foreach ($fields as $field => $value) {
2453:             if (is_numeric($field)) {
2454:                 unset($fields[$field]);
2455: 
2456:                 $field = $value;
2457:                 if (isset($this->data[$this->alias][$field])) {
2458:                     $value = $this->data[$this->alias][$field];
2459:                 } else {
2460:                     $value = null;
2461:                 }
2462:             }
2463: 
2464:             if (strpos($field, '.') === false) {
2465:                 unset($fields[$field]);
2466:                 $fields[$this->alias . '.' . $field] = $value;
2467:             }
2468:         }
2469:         if ($or) {
2470:             $fields = array('or' => $fields);
2471:         }
2472:         if (!empty($this->id)) {
2473:             $fields[$this->alias . '.' . $this->primaryKey . ' !='] =  $this->id;
2474:         }
2475:         return ($this->find('count', array('conditions' => $fields, 'recursive' => -1)) == 0);
2476:     }
2477: 
2478: /**
2479:  * Returns a resultset for a given SQL statement. Custom SQL queries should be performed with this method.
2480:  *
2481:  * @param string $sql SQL statement
2482:  * @return array Resultset
2483:  * @access public
2484:  * @link http://book.cakephp.org/1.3/en/The-Manual/Developing-with-CakePHP/Models.html#query
2485:  */
2486:     function query() {
2487:         $params = func_get_args();
2488:         $db =& ConnectionManager::getDataSource($this->useDbConfig);
2489:         return call_user_func_array(array(&$db, 'query'), $params);
2490:     }
2491: 
2492: /**
2493:  * Returns true if all fields pass validation. Will validate hasAndBelongsToMany associations
2494:  * that use the 'with' key as well. Since __saveMulti is incapable of exiting a save operation.
2495:  *
2496:  * Will validate the currently set data.  Use Model::set() or Model::create() to set the active data.
2497:  *
2498:  * @param string $options An optional array of custom options to be made available in the beforeValidate callback
2499:  * @return boolean True if there are no errors
2500:  * @access public
2501:  * @link http://book.cakephp.org/1.3/en/The-Manual/Developing-with-CakePHP/Models.html#Validating-Data-from-the-Controller
2502:  */
2503:     function validates($options = array()) {
2504:         $errors = $this->invalidFields($options);
2505:         if (empty($errors) && $errors !== false) {
2506:             $errors = $this->__validateWithModels($options);
2507:         }
2508:         if (is_array($errors)) {
2509:             return count($errors) === 0;
2510:         }
2511:         return $errors;
2512:     }
2513: 
2514: /**
2515:  * Returns an array of fields that have failed validation. On the current model.
2516:  *
2517:  * @param string $options An optional array of custom options to be made available in the beforeValidate callback
2518:  * @return array Array of invalid fields
2519:  * @see Model::validates()
2520:  * @access public
2521:  * @link http://book.cakephp.org/1.3/en/The-Manual/Developing-with-CakePHP/Models.html#Validating-Data-from-the-Controller
2522:  */
2523:     function invalidFields($options = array()) {
2524:         if (
2525:             !$this->Behaviors->trigger(
2526:                 $this,
2527:                 'beforeValidate',
2528:                 array($options),
2529:                 array('break' => true, 'breakOn' => false)
2530:             ) ||
2531:             $this->beforeValidate($options) === false
2532:         ) {
2533:             return false;
2534:         }
2535: 
2536:         if (!isset($this->validate) || empty($this->validate)) {
2537:             return $this->validationErrors;
2538:         }
2539: 
2540:         $data = $this->data;
2541:         $methods = array_map('strtolower', get_class_methods($this));
2542:         $behaviorMethods = array_keys($this->Behaviors->methods());
2543: 
2544:         if (isset($data[$this->alias])) {
2545:             $data = $data[$this->alias];
2546:         } elseif (!is_array($data)) {
2547:             $data = array();
2548:         }
2549: 
2550:         $Validation =& Validation::getInstance();
2551:         $exists = null;
2552: 
2553:         $_validate = $this->validate;
2554:         $whitelist = $this->whitelist;
2555: 
2556:         if (!empty($options['fieldList'])) {
2557:             $whitelist = $options['fieldList'];
2558:         }
2559: 
2560:         if (!empty($whitelist)) {
2561:             $validate = array();
2562:             foreach ((array)$whitelist as $f) {
2563:                 if (!empty($this->validate[$f])) {
2564:                     $validate[$f] = $this->validate[$f];
2565:                 }
2566:             }
2567:             $this->validate = $validate;
2568:         }
2569: 
2570:         foreach ($this->validate as $fieldName => $ruleSet) {
2571:             if (!is_array($ruleSet) || (is_array($ruleSet) && isset($ruleSet['rule']))) {
2572:                 $ruleSet = array($ruleSet);
2573:             }
2574:             $default = array(
2575:                 'allowEmpty' => null,
2576:                 'required' => null,
2577:                 'rule' => 'blank',
2578:                 'last' => false,
2579:                 'on' => null
2580:             );
2581: 
2582:             foreach ($ruleSet as $index => $validator) {
2583:                 if (!is_array($validator)) {
2584:                     $validator = array('rule' => $validator);
2585:                 }
2586:                 $validator = array_merge($default, $validator);
2587: 
2588:                 if (isset($validator['message'])) {
2589:                     $message = $validator['message'];
2590:                 } else {
2591:                     $message = __('This field cannot be left blank', true);
2592:                 }
2593: 
2594:                 if (!empty($validator['on'])) {
2595:                     if ($exists === null) {
2596:                         $exists = $this->exists();
2597:                     }
2598:                     if (($validator['on'] == 'create' && $exists) || ($validator['on'] == 'update' && !$exists)) {
2599:                         continue;
2600:                     }
2601:                 }
2602: 
2603:                 $required = (
2604:                     (!isset($data[$fieldName]) && $validator['required'] === true) ||
2605:                     (
2606:                         isset($data[$fieldName]) && (empty($data[$fieldName]) &&
2607:                         !is_numeric($data[$fieldName])) && $validator['allowEmpty'] === false
2608:                     )
2609:                 );
2610: 
2611:                 if ($required) {
2612:                     $this->invalidate($fieldName, $message);
2613:                     if ($validator['last']) {
2614:                         break;
2615:                     }
2616:                 } elseif (array_key_exists($fieldName, $data)) {
2617:                     if (empty($data[$fieldName]) && $data[$fieldName] != '0' && $validator['allowEmpty'] === true) {
2618:                         break;
2619:                     }
2620:                     if (is_array($validator['rule'])) {
2621:                         $rule = $validator['rule'][0];
2622:                         unset($validator['rule'][0]);
2623:                         $ruleParams = array_merge(array($data[$fieldName]), array_values($validator['rule']));
2624:                     } else {
2625:                         $rule = $validator['rule'];
2626:                         $ruleParams = array($data[$fieldName]);
2627:                     }
2628: 
2629:                     $valid = true;
2630: 
2631:                     if (in_array(strtolower($rule), $methods)) {
2632:                         $ruleParams[] = $validator;
2633:                         $ruleParams[0] = array($fieldName => $ruleParams[0]);
2634:                         $valid = $this->dispatchMethod($rule, $ruleParams);
2635:                     } elseif (in_array($rule, $behaviorMethods) || in_array(strtolower($rule), $behaviorMethods)) {
2636:                         $ruleParams[] = $validator;
2637:                         $ruleParams[0] = array($fieldName => $ruleParams[0]);
2638:                         $valid = $this->Behaviors->dispatchMethod($this, $rule, $ruleParams);
2639:                     } elseif (method_exists($Validation, $rule)) {
2640:                         $valid = $Validation->dispatchMethod($rule, $ruleParams);
2641:                     } elseif (!is_array($validator['rule'])) {
2642:                         $valid = preg_match($rule, $data[$fieldName]);
2643:                     } elseif (Configure::read('debug') > 0) {
2644:                         trigger_error(sprintf(__('Could not find validation handler %s for %s', true), $rule, $fieldName), E_USER_WARNING);
2645:                     }
2646: 
2647:                     if (!$valid || (is_string($valid) && strlen($valid) > 0)) {
2648:                         if (is_string($valid) && strlen($valid) > 0) {
2649:                             $validator['message'] = $valid;
2650:                         } elseif (!isset($validator['message'])) {
2651:                             if (is_string($index)) {
2652:                                 $validator['message'] = $index;
2653:                             } elseif (is_numeric($index) && count($ruleSet) > 1) {
2654:                                 $validator['message'] = $index + 1;
2655:                             } else {
2656:                                 $validator['message'] = $message;
2657:                             }
2658:                         }
2659:                         $this->invalidate($fieldName, $validator['message']);
2660: 
2661:                         if ($validator['last']) {
2662:                             break;
2663:                         }
2664:                     }
2665:                 }
2666:             }
2667:         }
2668:         $this->validate = $_validate;
2669:         return $this->validationErrors;
2670:     }
2671: 
2672: /**
2673:  * Runs validation for hasAndBelongsToMany associations that have 'with' keys
2674:  * set. And data in the set() data set.
2675:  *
2676:  * @param array $options Array of options to use on Valdation of with models
2677:  * @return boolean Failure of validation on with models.
2678:  * @access private
2679:  * @see Model::validates()
2680:  */
2681:     function __validateWithModels($options) {
2682:         $valid = true;
2683:         foreach ($this->hasAndBelongsToMany as $assoc => $association) {
2684:             if (empty($association['with']) || !isset($this->data[$assoc])) {
2685:                 continue;
2686:             }
2687:             list($join) = $this->joinModel($this->hasAndBelongsToMany[$assoc]['with']);
2688:             $data = $this->data[$assoc];
2689: 
2690:             $newData = array();
2691:             foreach ((array)$data as $row) {
2692:                 if (isset($row[$this->hasAndBelongsToMany[$assoc]['associationForeignKey']])) {
2693:                     $newData[] = $row;
2694:                 } elseif (isset($row[$join]) && isset($row[$join][$this->hasAndBelongsToMany[$assoc]['associationForeignKey']])) {
2695:                     $newData[] = $row[$join];
2696:                 }
2697:             }
2698:             if (empty($newData)) {
2699:                 continue;
2700:             }
2701:             foreach ($newData as $data) {
2702:                 $data[$this->hasAndBelongsToMany[$assoc]['foreignKey']] = $this->id;
2703:                 $this->{$join}->create($data);
2704:                 $valid = ($valid && $this->{$join}->validates($options));
2705:             }
2706:         }
2707:         return $valid;
2708:     }
2709: /**
2710:  * Marks a field as invalid, optionally setting the name of validation
2711:  * rule (in case of multiple validation for field) that was broken.
2712:  *
2713:  * @param string $field The name of the field to invalidate
2714:  * @param mixed $value Name of validation rule that was not failed, or validation message to
2715:  *    be returned. If no validation key is provided, defaults to true.
2716:  * @access public
2717:  */
2718:     function invalidate($field, $value = true) {
2719:         if (!is_array($this->validationErrors)) {
2720:             $this->validationErrors = array();
2721:         }
2722:         $this->validationErrors[$field] = $value;
2723:     }
2724: 
2725: /**
2726:  * Returns true if given field name is a foreign key in this model.
2727:  *
2728:  * @param string $field Returns true if the input string ends in "_id"
2729:  * @return boolean True if the field is a foreign key listed in the belongsTo array.
2730:  * @access public
2731:  */
2732:     function isForeignKey($field) {
2733:         $foreignKeys = array();
2734:         if (!empty($this->belongsTo)) {
2735:             foreach ($this->belongsTo as $assoc => $data) {
2736:                 $foreignKeys[] = $data['foreignKey'];
2737:             }
2738:         }
2739:         return in_array($field, $foreignKeys);
2740:     }
2741: 
2742: /**
2743:  * Escapes the field name and prepends the model name. Escaping is done according to the
2744:  * current database driver's rules.
2745:  *
2746:  * @param string $field Field to escape (e.g: id)
2747:  * @param string $alias Alias for the model (e.g: Post)
2748:  * @return string The name of the escaped field for this Model (i.e. id becomes `Post`.`id`).
2749:  * @access public
2750:  */
2751:     function escapeField($field = null, $alias = null) {
2752:         if (empty($alias)) {
2753:             $alias = $this->alias;
2754:         }
2755:         if (empty($field)) {
2756:             $field = $this->primaryKey;
2757:         }
2758:         $db =& ConnectionManager::getDataSource($this->useDbConfig);
2759:         if (strpos($field, $db->name($alias) . '.') === 0) {
2760:             return $field;
2761:         }
2762:         return $db->name($alias . '.' . $field);
2763:     }
2764: 
2765: /**
2766:  * Returns the current record's ID
2767:  *
2768:  * @param integer $list Index on which the composed ID is located
2769:  * @return mixed The ID of the current record, false if no ID
2770:  * @access public
2771:  */
2772:     function getID($list = 0) {
2773:         if (empty($this->id) || (is_array($this->id) && isset($this->id[0]) && empty($this->id[0]))) {
2774:             return false;
2775:         }
2776: 
2777:         if (!is_array($this->id)) {
2778:             return $this->id;
2779:         }
2780: 
2781:         if (empty($this->id)) {
2782:             return false;
2783:         }
2784: 
2785:         if (isset($this->id[$list]) && !empty($this->id[$list])) {
2786:             return $this->id[$list];
2787:         } elseif (isset($this->id[$list])) {
2788:             return false;
2789:         }
2790: 
2791:         foreach ($this->id as $id) {
2792:             return $id;
2793:         }
2794: 
2795:         return false;
2796:     }
2797: 
2798: /**
2799:  * Returns the ID of the last record this model inserted.
2800:  *
2801:  * @return mixed Last inserted ID
2802:  * @access public
2803:  */
2804:     function getLastInsertID() {
2805:         return $this->getInsertID();
2806:     }
2807: 
2808: /**
2809:  * Returns the ID of the last record this model inserted.
2810:  *
2811:  * @return mixed Last inserted ID
2812:  * @access public
2813:  */
2814:     function getInsertID() {
2815:         return $this->__insertID;
2816:     }
2817: 
2818: /**
2819:  * Sets the ID of the last record this model inserted
2820:  *
2821:  * @param mixed Last inserted ID
2822:  * @access public
2823:  */
2824:     function setInsertID($id) {
2825:         $this->__insertID = $id;
2826:     }
2827: 
2828: /**
2829:  * Returns the number of rows returned from the last query.
2830:  *
2831:  * @return int Number of rows
2832:  * @access public
2833:  */
2834:     function getNumRows() {
2835:         $db =& ConnectionManager::getDataSource($this->useDbConfig);
2836:         return $db->lastNumRows();
2837:     }
2838: 
2839: /**
2840:  * Returns the number of rows affected by the last query.
2841:  *
2842:  * @return int Number of rows
2843:  * @access public
2844:  */
2845:     function getAffectedRows() {
2846:         $db =& ConnectionManager::getDataSource($this->useDbConfig);
2847:         return $db->lastAffected();
2848:     }
2849: 
2850: /**
2851:  * Sets the DataSource to which this model is bound.
2852:  *
2853:  * @param string $dataSource The name of the DataSource, as defined in app/config/database.php
2854:  * @return boolean True on success
2855:  * @access public
2856:  */
2857:     function setDataSource($dataSource = null) {
2858:         $oldConfig = $this->useDbConfig;
2859: 
2860:         if ($dataSource != null) {
2861:             $this->useDbConfig = $dataSource;
2862:         }
2863:         $db =& ConnectionManager::getDataSource($this->useDbConfig);
2864:         if (!empty($oldConfig) && isset($db->config['prefix'])) {
2865:             $oldDb =& ConnectionManager::getDataSource($oldConfig);
2866: 
2867:             if (!isset($this->tablePrefix) || (!isset($oldDb->config['prefix']) || $this->tablePrefix == $oldDb->config['prefix'])) {
2868:                 $this->tablePrefix = $db->config['prefix'];
2869:             }
2870:         } elseif (isset($db->config['prefix'])) {
2871:             $this->tablePrefix = $db->config['prefix'];
2872:         }
2873: 
2874:         if (empty($db) || !is_object($db)) {
2875:             return $this->cakeError('missingConnection', array(array('code' => 500, 'className' => $this->alias)));
2876:         }
2877:     }
2878: 
2879: /**
2880:  * Gets the DataSource to which this model is bound.
2881:  * Not safe for use with some versions of PHP4, because this class is overloaded.
2882:  *
2883:  * @return object A DataSource object
2884:  * @access public
2885:  */
2886:     function &getDataSource() {
2887:         $db =& ConnectionManager::getDataSource($this->useDbConfig);
2888:         return $db;
2889:     }
2890: 
2891: /**
2892:  * Gets all the models with which this model is associated.
2893:  *
2894:  * @param string $type Only result associations of this type
2895:  * @return array Associations
2896:  * @access public
2897:  */
2898:     function getAssociated($type = null) {
2899:         if ($type == null) {
2900:             $associated = array();
2901:             foreach ($this->__associations as $assoc) {
2902:                 if (!empty($this->{$assoc})) {
2903:                     $models = array_keys($this->{$assoc});
2904:                     foreach ($models as $m) {
2905:                         $associated[$m] = $assoc;
2906:                     }
2907:                 }
2908:             }
2909:             return $associated;
2910:         } elseif (in_array($type, $this->__associations)) {
2911:             if (empty($this->{$type})) {
2912:                 return array();
2913:             }
2914:             return array_keys($this->{$type});
2915:         } else {
2916:             $assoc = array_merge(
2917:                 $this->hasOne,
2918:                 $this->hasMany,
2919:                 $this->belongsTo,
2920:                 $this->hasAndBelongsToMany
2921:             );
2922:             if (array_key_exists($type, $assoc)) {
2923:                 foreach ($this->__associations as $a) {
2924:                     if (isset($this->{$a}[$type])) {
2925:                         $assoc[$type]['association'] = $a;
2926:                         break;
2927:                     }
2928:                 }
2929:                 return $assoc[$type];
2930:             }
2931:             return null;
2932:         }
2933:     }
2934: 
2935: /**
2936:  * Gets the name and fields to be used by a join model.  This allows specifying join fields
2937:  * in the association definition.
2938:  *
2939:  * @param object $model The model to be joined
2940:  * @param mixed $with The 'with' key of the model association
2941:  * @param array $keys Any join keys which must be merged with the keys queried
2942:  * @return array
2943:  * @access public
2944:  */
2945:     function joinModel($assoc, $keys = array()) {
2946:         if (is_string($assoc)) {
2947:             return array($assoc, array_keys($this->{$assoc}->schema()));
2948:         } elseif (is_array($assoc)) {
2949:             $with = key($assoc);
2950:             return array($with, array_unique(array_merge($assoc[$with], $keys)));
2951:         }
2952:         trigger_error(
2953:             sprintf(__('Invalid join model settings in %s', true), $model->alias),
2954:             E_USER_WARNING
2955:         );
2956:     }
2957: 
2958: /**
2959:  * Called before each find operation. Return false if you want to halt the find
2960:  * call, otherwise return the (modified) query data.
2961:  *
2962:  * @param array $queryData Data used to execute this query, i.e. conditions, order, etc.
2963:  * @return mixed true if the operation should continue, false if it should abort; or, modified
2964:  *               $queryData to continue with new $queryData
2965:  * @access public
2966:  * @link http://book.cakephp.org/1.3/en/The-Manual/Developing-with-CakePHP/Models.html#Callback-Methods#beforeFind-1049
2967:  */
2968:     function beforeFind($queryData) {
2969:         return true;
2970:     }
2971: 
2972: /**
2973:  * Called after each find operation. Can be used to modify any results returned by find().
2974:  * Return value should be the (modified) results.
2975:  *
2976:  * @param mixed $results The results of the find operation
2977:  * @param boolean $primary Whether this model is being queried directly (vs. being queried as an association)
2978:  * @return mixed Result of the find operation
2979:  * @access public
2980:  * @link http://book.cakephp.org/1.3/en/The-Manual/Developing-with-CakePHP/Models.html#Callback-Methods#afterFind-1050
2981:  */
2982:     function afterFind($results, $primary = false) {
2983:         return $results;
2984:     }
2985: 
2986: /**
2987:  * Called before each save operation, after validation. Return a non-true result
2988:  * to halt the save.
2989:  *
2990:  * @return boolean True if the operation should continue, false if it should abort
2991:  * @access public
2992:  * @link http://book.cakephp.org/1.3/en/The-Manual/Developing-with-CakePHP/Models.html#Callback-Methods#beforeSave-1052
2993:  */
2994:     function beforeSave($options = array()) {
2995:         return true;
2996:     }
2997: 
2998: /**
2999:  * Called after each successful save operation.
3000:  *
3001:  * @param boolean $created True if this save created a new record
3002:  * @access public
3003:  * @link http://book.cakephp.org/1.3/en/The-Manual/Developing-with-CakePHP/Models.html#Callback-Methods#afterSave-1053
3004:  */
3005:     function afterSave($created) {
3006:     }
3007: 
3008: /**
3009:  * Called before every deletion operation.
3010:  *
3011:  * @param boolean $cascade If true records that depend on this record will also be deleted
3012:  * @return boolean True if the operation should continue, false if it should abort
3013:  * @access public
3014:  * @link http://book.cakephp.org/1.3/en/The-Manual/Developing-with-CakePHP/Models.html#Callback-Methods#beforeDelete-1054
3015:  */
3016:     function beforeDelete($cascade = true) {
3017:         return true;
3018:     }
3019: 
3020: /**
3021:  * Called after every deletion operation.
3022:  *
3023:  * @access public
3024:  * @link http://book.cakephp.org/1.3/en/The-Manual/Developing-with-CakePHP/Models.html#Callback-Methods#afterDelete-1055
3025:  */
3026:     function afterDelete() {
3027:     }
3028: 
3029: /**
3030:  * Called during validation operations, before validation. Please note that custom
3031:  * validation rules can be defined in $validate.
3032:  *
3033:  * @return boolean True if validate operation should continue, false to abort
3034:  * @param $options array Options passed from model::save(), see $options of model::save().
3035:  * @access public
3036:  * @link http://book.cakephp.org/1.3/en/The-Manual/Developing-with-CakePHP/Models.html#Callback-Methods#beforeValidate-1051
3037:  */
3038:     function beforeValidate($options = array()) {
3039:         return true;
3040:     }
3041: 
3042: /**
3043:  * Called when a DataSource-level error occurs.
3044:  *
3045:  * @access public
3046:  * @link http://book.cakephp.org/1.3/en/The-Manual/Developing-with-CakePHP/Models.html#Callback-Methods#onError-1056
3047:  */
3048:     function onError() {
3049:     }
3050: 
3051: /**
3052:  * Private method. Clears cache for this model.
3053:  *
3054:  * @param string $type If null this deletes cached views if Cache.check is true
3055:  *     Will be used to allow deleting query cache also
3056:  * @return boolean true on delete
3057:  * @access protected
3058:  * @todo
3059:  */
3060:     function _clearCache($type = null) {
3061:         if ($type === null) {
3062:             if (Configure::read('Cache.check') === true) {
3063:                 $assoc[] = strtolower(Inflector::pluralize($this->alias));
3064:                 $assoc[] = strtolower(Inflector::underscore(Inflector::pluralize($this->alias)));
3065:                 foreach ($this->__associations as $key => $association) {
3066:                     foreach ($this->$association as $key => $className) {
3067:                         $check = strtolower(Inflector::pluralize($className['className']));
3068:                         if (!in_array($check, $assoc)) {
3069:                             $assoc[] = strtolower(Inflector::pluralize($className['className']));
3070:                             $assoc[] = strtolower(Inflector::underscore(Inflector::pluralize($className['className'])));
3071:                         }
3072:                     }
3073:                 }
3074:                 clearCache($assoc);
3075:                 return true;
3076:             }
3077:         } else {
3078:             //Will use for query cache deleting
3079:         }
3080:     }
3081: 
3082: /**
3083:  * Called when serializing a model.
3084:  *
3085:  * @return array Set of object variable names this model has
3086:  * @access private
3087:  */
3088:     function __sleep() {
3089:         $return = array_keys(get_object_vars($this));
3090:         return $return;
3091:     }
3092: 
3093: /**
3094:  * Called when de-serializing a model.
3095:  *
3096:  * @access private
3097:  * @todo
3098:  */
3099:     function __wakeup() {
3100:     }
3101: }
3102: if (!defined('CAKEPHP_UNIT_TEST_EXECUTION')) {
3103:     Overloadable::overload('Model');
3104: }
3105: 
OpenHub
Rackspace
Rackspace
  • Business Solutions
  • Showcase
  • Documentation
  • Book
  • API
  • Videos
  • Reporting Security Issues
  • Privacy Policy
  • Logos & Trademarks
  • Community
  • Get Involved
  • Issues (GitHub)
  • Bakery
  • Featured Resources
  • Training
  • Meetups
  • My CakePHP
  • CakeFest
  • Newsletter
  • Linkedin
  • YouTube
  • Facebook
  • Twitter
  • Mastodon
  • Help & Support
  • Forum
  • Stack Overflow
  • Slack
  • Paid Support

Generated using CakePHP API Docs