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.2 API

  • Overview
  • Tree
  • Deprecated
  • Version:
    • 1.2
      • 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
  • BehaviorCollection
  • Cache
  • CacheEngine
  • CacheHelper
  • CakeErrorController
  • CakeLog
  • CakeSchema
  • CakeSession
  • CakeSocket
  • ClassRegistry
  • Component
  • Configure
  • ConnectionManager
  • ConsoleShell
  • ContainableBehavior
  • Controller
  • ControllerTask
  • CookieComponent
  • DataSource
  • DbAcl
  • DbAclSchema
  • DbConfigTask
  • DboAdodb
  • DboDb2
  • DboFirebird
  • DboMssql
  • DboMysql
  • DboMysqlBase
  • DboMysqli
  • DboOdbc
  • DboOracle
  • DboPostgres
  • DboSource
  • DboSqlite
  • DboSybase
  • Debugger
  • EmailComponent
  • ErrorHandler
  • ExtractTask
  • File
  • FileEngine
  • Flay
  • Folder
  • FormHelper
  • Helper
  • HtmlHelper
  • HttpSocket
  • I18n
  • I18nModel
  • i18nSchema
  • I18nShell
  • Inflector
  • IniAcl
  • JavascriptHelper
  • JsHelper
  • JsHelperObject
  • L10n
  • MagicDb
  • MagicFileResource
  • MediaView
  • MemcacheEngine
  • Model
  • ModelBehavior
  • ModelTask
  • Multibyte
  • NumberHelper
  • Object
  • Overloadable
  • Overloadable2
  • PagesController
  • PaginatorHelper
  • Permission
  • PluginTask
  • ProjectTask
  • RequestHandlerComponent
  • Router
  • RssHelper
  • Sanitize
  • Scaffold
  • ScaffoldView
  • SchemaShell
  • Security
  • SecurityComponent
  • SessionComponent
  • SessionHelper
  • SessionsSchema
  • Set
  • Shell
  • String
  • TestSuiteShell
  • TestTask
  • TextHelper
  • ThemeView
  • TimeHelper
  • TranslateBehavior
  • TreeBehavior
  • Validation
  • View
  • ViewTask
  • XcacheEngine
  • Xml
  • XmlElement
  • XmlHelper
  • XmlManager
  • XmlNode
  • XmlTextNode

Functions

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