Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
13532 anikendra 1
<?php
2
/**
3
 * Object-relational mapper.
4
 *
5
 * DBO-backed object data model, for mapping database tables to CakePHP objects.
6
 *
7
 * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
8
 * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
9
 *
10
 * Licensed under The MIT License
11
 * For full copyright and license information, please see the LICENSE.txt
12
 * Redistributions of files must retain the above copyright notice.
13
 *
14
 * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
15
 * @link          http://cakephp.org CakePHP(tm) Project
16
 * @package       Cake.Model
17
 * @since         CakePHP(tm) v 0.10.0.0
18
 * @license       http://www.opensource.org/licenses/mit-license.php MIT License
19
 */
20
 
21
App::uses('ClassRegistry', 'Utility');
22
App::uses('Validation', 'Utility');
23
App::uses('String', 'Utility');
24
App::uses('Hash', 'Utility');
25
App::uses('BehaviorCollection', 'Model');
26
App::uses('ModelBehavior', 'Model');
27
App::uses('ModelValidator', 'Model');
28
App::uses('ConnectionManager', 'Model');
29
App::uses('Xml', 'Utility');
30
App::uses('CakeEvent', 'Event');
31
App::uses('CakeEventListener', 'Event');
32
App::uses('CakeEventManager', 'Event');
33
 
34
/**
35
 * Object-relational mapper.
36
 *
37
 * DBO-backed object data model.
38
 * Automatically selects a database table name based on a pluralized lowercase object class name
39
 * (i.e. class 'User' => table 'users'; class 'Man' => table 'men')
40
 * The table is required to have at least 'id auto_increment' primary key.
41
 *
42
 * @package       Cake.Model
43
 * @link          http://book.cakephp.org/2.0/en/models.html
44
 */
45
class Model extends Object implements CakeEventListener {
46
 
47
/**
48
 * The name of the DataSource connection that this Model uses
49
 *
50
 * The value must be an attribute name that you defined in `app/Config/database.php`
51
 * or created using `ConnectionManager::create()`.
52
 *
53
 * @var string
54
 * @link http://book.cakephp.org/2.0/en/models/model-attributes.html#usedbconfig
55
 */
56
	public $useDbConfig = 'default';
57
 
58
/**
59
 * Custom database table name, or null/false if no table association is desired.
60
 *
61
 * @var string
62
 * @link http://book.cakephp.org/2.0/en/models/model-attributes.html#usetable
63
 */
64
	public $useTable = null;
65
 
66
/**
67
 * Custom display field name. Display fields are used by Scaffold, in SELECT boxes' OPTION elements.
68
 *
69
 * This field is also used in `find('list')` when called with no extra parameters in the fields list
70
 *
71
 * @var string
72
 * @link http://book.cakephp.org/2.0/en/models/model-attributes.html#displayfield
73
 */
74
	public $displayField = null;
75
 
76
/**
77
 * Value of the primary key ID of the record that this model is currently pointing to.
78
 * Automatically set after database insertions.
79
 *
80
 * @var mixed
81
 */
82
	public $id = false;
83
 
84
/**
85
 * Container for the data that this model gets from persistent storage (usually, a database).
86
 *
87
 * @var array
88
 * @link http://book.cakephp.org/2.0/en/models/model-attributes.html#data
89
 */
90
	public $data = array();
91
 
92
/**
93
 * Holds physical schema/database name for this model. Automatically set during Model creation.
94
 *
95
 * @var string
96
 */
97
	public $schemaName = null;
98
 
99
/**
100
 * Table name for this Model.
101
 *
102
 * @var string
103
 */
104
	public $table = false;
105
 
106
/**
107
 * The name of the primary key field for this model.
108
 *
109
 * @var string
110
 * @link http://book.cakephp.org/2.0/en/models/model-attributes.html#primaryKey
111
 */
112
	public $primaryKey = null;
113
 
114
/**
115
 * Field-by-field table metadata.
116
 *
117
 * @var array
118
 */
119
	protected $_schema = null;
120
 
121
/**
122
 * List of validation rules. It must be an array with the field name as key and using
123
 * as value one of the following possibilities
124
 *
125
 * ### Validating using regular expressions
126
 *
127
 * {{{
128
 * public $validate = array(
129
 *     'name' => '/^[a-z].+$/i'
130
 * );
131
 * }}}
132
 *
133
 * ### Validating using methods (no parameters)
134
 *
135
 * {{{
136
 * public $validate = array(
137
 *     'name' => 'notEmpty'
138
 * );
139
 * }}}
140
 *
141
 * ### Validating using methods (with parameters)
142
 *
143
 * {{{
144
 * public $validate = array(
145
 *     'age' => array(
146
 *         'rule' => array('between', 5, 25)
147
 *     )
148
 * );
149
 * }}}
150
 *
151
 * ### Validating using custom method
152
 *
153
 * {{{
154
 * public $validate = array(
155
 *     'password' => array(
156
 *         'rule' => array('customValidation')
157
 *     )
158
 * );
159
 * public function customValidation($data) {
160
 *     // $data will contain array('password' => 'value')
161
 *     if (isset($this->data[$this->alias]['password2'])) {
162
 *         return $this->data[$this->alias]['password2'] === current($data);
163
 *     }
164
 *     return true;
165
 * }
166
 * }}}
167
 *
168
 * ### Validations with messages
169
 *
170
 * The messages will be used in Model::$validationErrors and can be used in the FormHelper
171
 *
172
 * {{{
173
 * public $validate = array(
174
 *     'age' => array(
175
 *         'rule' => array('between', 5, 25),
176
 *         'message' => array('The age must be between %d and %d.')
177
 *     )
178
 * );
179
 * }}}
180
 *
181
 * ### Multiple validations to the same field
182
 *
183
 * {{{
184
 * public $validate = array(
185
 *     'login' => array(
186
 *         array(
187
 *             'rule' => 'alphaNumeric',
188
 *             'message' => 'Only alphabets and numbers allowed',
189
 *             'last' => true
190
 *         ),
191
 *         array(
192
 *             'rule' => array('minLength', 8),
193
 *             'message' => array('Minimum length of %d characters')
194
 *         )
195
 *     )
196
 * );
197
 * }}}
198
 *
199
 * ### Valid keys in validations
200
 *
201
 * - `rule`: String with method name, regular expression (started by slash) or array with method and parameters
202
 * - `message`: String with the message or array if have multiple parameters. See http://php.net/sprintf
203
 * - `last`: Boolean value to indicate if continue validating the others rules if the current fail [Default: true]
204
 * - `required`: Boolean value to indicate if the field must be present on save
205
 * - `allowEmpty`: Boolean value to indicate if the field can be empty
206
 * - `on`: Possible values: `update`, `create`. Indicate to apply this rule only on update or create
207
 *
208
 * @var array
209
 * @link http://book.cakephp.org/2.0/en/models/model-attributes.html#validate
210
 * @link http://book.cakephp.org/2.0/en/models/data-validation.html
211
 */
212
	public $validate = array();
213
 
214
/**
215
 * List of validation errors.
216
 *
217
 * @var array
218
 */
219
	public $validationErrors = array();
220
 
221
/**
222
 * Name of the validation string domain to use when translating validation errors.
223
 *
224
 * @var string
225
 */
226
	public $validationDomain = null;
227
 
228
/**
229
 * Database table prefix for tables in model.
230
 *
231
 * @var string
232
 * @link http://book.cakephp.org/2.0/en/models/model-attributes.html#tableprefix
233
 */
234
	public $tablePrefix = null;
235
 
236
/**
237
 * Plugin model belongs to.
238
 *
239
 * @var string
240
 */
241
	public $plugin = null;
242
 
243
/**
244
 * Name of the model.
245
 *
246
 * @var string
247
 * @link http://book.cakephp.org/2.0/en/models/model-attributes.html#name
248
 */
249
	public $name = null;
250
 
251
/**
252
 * Alias name for model.
253
 *
254
 * @var string
255
 */
256
	public $alias = null;
257
 
258
/**
259
 * List of table names included in the model description. Used for associations.
260
 *
261
 * @var array
262
 */
263
	public $tableToModel = array();
264
 
265
/**
266
 * Whether or not to cache queries for this model. This enables in-memory
267
 * caching only, the results are not stored beyond the current request.
268
 *
269
 * @var boolean
270
 * @link http://book.cakephp.org/2.0/en/models/model-attributes.html#cachequeries
271
 */
272
	public $cacheQueries = false;
273
 
274
/**
275
 * Detailed list of belongsTo associations.
276
 *
277
 * ### Basic usage
278
 *
279
 * `public $belongsTo = array('Group', 'Department');`
280
 *
281
 * ### Detailed configuration
282
 *
283
 * {{{
284
 * public $belongsTo = array(
285
 *     'Group',
286
 *     'Department' => array(
287
 *         'className' => 'Department',
288
 *         'foreignKey' => 'department_id'
289
 *     )
290
 * );
291
 * }}}
292
 *
293
 * ### Possible keys in association
294
 *
295
 * - `className`: the class name of the model being associated to the current model.
296
 *   If you're defining a 'Profile belongsTo User' relationship, the className key should equal 'User.'
297
 * - `foreignKey`: the name of the foreign key found in the current model. This is
298
 *   especially handy if you need to define multiple belongsTo relationships. The default
299
 *   value for this key is the underscored, singular name of the other model, suffixed with '_id'.
300
 * - `conditions`: An SQL fragment used to filter related model records. It's good
301
 *   practice to use model names in SQL fragments: 'User.active = 1' is always
302
 *   better than just 'active = 1.'
303
 * - `type`: the type of the join to use in the SQL query, default is LEFT which
304
 *   may not fit your needs in all situations, INNER may be helpful when you want
305
 *   everything from your main and associated models or nothing at all!(effective
306
 *   when used with some conditions of course). (NB: type value is in lower case - i.e. left, inner)
307
 * - `fields`: A list of fields to be retrieved when the associated model data is
308
 *   fetched. Returns all fields by default.
309
 * - `order`: An SQL fragment that defines the sorting order for the returned associated rows.
310
 * - `counterCache`: If set to true the associated Model will automatically increase or
311
 *   decrease the "[singular_model_name]_count" field in the foreign table whenever you do
312
 *   a save() or delete(). If its a string then its the field name to use. The value in the
313
 *   counter field represents the number of related rows.
314
 * - `counterScope`: Optional conditions array to use for updating counter cache field.
315
 *
316
 * @var array
317
 * @link http://book.cakephp.org/2.0/en/models/associations-linking-models-together.html#belongsto
318
 */
319
	public $belongsTo = array();
320
 
321
/**
322
 * Detailed list of hasOne associations.
323
 *
324
 * ### Basic usage
325
 *
326
 * `public $hasOne = array('Profile', 'Address');`
327
 *
328
 * ### Detailed configuration
329
 *
330
 * {{{
331
 * public $hasOne = array(
332
 *     'Profile',
333
 *     'Address' => array(
334
 *         'className' => 'Address',
335
 *         'foreignKey' => 'user_id'
336
 *     )
337
 * );
338
 * }}}
339
 *
340
 * ### Possible keys in association
341
 *
342
 * - `className`: the class name of the model being associated to the current model.
343
 *   If you're defining a 'User hasOne Profile' relationship, the className key should equal 'Profile.'
344
 * - `foreignKey`: the name of the foreign key found in the other model. This is
345
 *   especially handy if you need to define multiple hasOne relationships.
346
 *   The default value for this key is the underscored, singular name of the
347
 *   current model, suffixed with '_id'. In the example above it would default to 'user_id'.
348
 * - `conditions`: An SQL fragment used to filter related model records. It's good
349
 *   practice to use model names in SQL fragments: "Profile.approved = 1" is
350
 *   always better than just "approved = 1."
351
 * - `fields`: A list of fields to be retrieved when the associated model data is
352
 *   fetched. Returns all fields by default.
353
 * - `order`: An SQL fragment that defines the sorting order for the returned associated rows.
354
 * - `dependent`: When the dependent key is set to true, and the model's delete()
355
 *   method is called with the cascade parameter set to true, associated model
356
 *   records are also deleted. In this case we set it true so that deleting a
357
 *   User will also delete her associated Profile.
358
 *
359
 * @var array
360
 * @link http://book.cakephp.org/2.0/en/models/associations-linking-models-together.html#hasone
361
 */
362
	public $hasOne = array();
363
 
364
/**
365
 * Detailed list of hasMany associations.
366
 *
367
 * ### Basic usage
368
 *
369
 * `public $hasMany = array('Comment', 'Task');`
370
 *
371
 * ### Detailed configuration
372
 *
373
 * {{{
374
 * public $hasMany = array(
375
 *     'Comment',
376
 *     'Task' => array(
377
 *         'className' => 'Task',
378
 *         'foreignKey' => 'user_id'
379
 *     )
380
 * );
381
 * }}}
382
 *
383
 * ### Possible keys in association
384
 *
385
 * - `className`: the class name of the model being associated to the current model.
386
 *   If you're defining a 'User hasMany Comment' relationship, the className key should equal 'Comment.'
387
 * - `foreignKey`: the name of the foreign key found in the other model. This is
388
 *   especially handy if you need to define multiple hasMany relationships. The default
389
 *   value for this key is the underscored, singular name of the actual model, suffixed with '_id'.
390
 * - `conditions`: An SQL fragment used to filter related model records. It's good
391
 *   practice to use model names in SQL fragments: "Comment.status = 1" is always
392
 *   better than just "status = 1."
393
 * - `fields`: A list of fields to be retrieved when the associated model data is
394
 *   fetched. Returns all fields by default.
395
 * - `order`: An SQL fragment that defines the sorting order for the returned associated rows.
396
 * - `limit`: The maximum number of associated rows you want returned.
397
 * - `offset`: The number of associated rows to skip over (given the current
398
 *   conditions and order) before fetching and associating.
399
 * - `dependent`: When dependent is set to true, recursive model deletion is
400
 *   possible. In this example, Comment records will be deleted when their
401
 *   associated User record has been deleted.
402
 * - `exclusive`: When exclusive is set to true, recursive model deletion does
403
 *   the delete with a deleteAll() call, instead of deleting each entity separately.
404
 *   This greatly improves performance, but may not be ideal for all circumstances.
405
 * - `finderQuery`: A complete SQL query CakePHP can use to fetch associated model
406
 *   records. This should be used in situations that require very custom results.
407
 *
408
 * @var array
409
 * @link http://book.cakephp.org/2.0/en/models/associations-linking-models-together.html#hasmany
410
 */
411
	public $hasMany = array();
412
 
413
/**
414
 * Detailed list of hasAndBelongsToMany associations.
415
 *
416
 * ### Basic usage
417
 *
418
 * `public $hasAndBelongsToMany = array('Role', 'Address');`
419
 *
420
 * ### Detailed configuration
421
 *
422
 * {{{
423
 * public $hasAndBelongsToMany = array(
424
 *     'Role',
425
 *     'Address' => array(
426
 *         'className' => 'Address',
427
 *         'foreignKey' => 'user_id',
428
 *         'associationForeignKey' => 'address_id',
429
 *         'joinTable' => 'addresses_users'
430
 *     )
431
 * );
432
 * }}}
433
 *
434
 * ### Possible keys in association
435
 *
436
 * - `className`: the class name of the model being associated to the current model.
437
 *   If you're defining a 'Recipe HABTM Tag' relationship, the className key should equal 'Tag.'
438
 * - `joinTable`: The name of the join table used in this association (if the
439
 *   current table doesn't adhere to the naming convention for HABTM join tables).
440
 * - `with`: Defines the name of the model for the join table. By default CakePHP
441
 *   will auto-create a model for you. Using the example above it would be called
442
 *   RecipesTag. By using this key you can override this default name. The join
443
 *   table model can be used just like any "regular" model to access the join table directly.
444
 * - `foreignKey`: the name of the foreign key found in the current model.
445
 *   This is especially handy if you need to define multiple HABTM relationships.
446
 *   The default value for this key is the underscored, singular name of the
447
 *   current model, suffixed with '_id'.
448
 * - `associationForeignKey`: the name of the foreign key found in the other model.
449
 *   This is especially handy if you need to define multiple HABTM relationships.
450
 *   The default value for this key is the underscored, singular name of the other
451
 *   model, suffixed with '_id'.
452
 * - `unique`: If true (default value) cake will first delete existing relationship
453
 *   records in the foreign keys table before inserting new ones, when updating a
454
 *   record. So existing associations need to be passed again when updating.
455
 *   To prevent deletion of existing relationship records, set this key to a string 'keepExisting'.
456
 * - `conditions`: An SQL fragment used to filter related model records. It's good
457
 *   practice to use model names in SQL fragments: "Comment.status = 1" is always
458
 *   better than just "status = 1."
459
 * - `fields`: A list of fields to be retrieved when the associated model data is
460
 *   fetched. Returns all fields by default.
461
 * - `order`: An SQL fragment that defines the sorting order for the returned associated rows.
462
 * - `limit`: The maximum number of associated rows you want returned.
463
 * - `offset`: The number of associated rows to skip over (given the current
464
 *   conditions and order) before fetching and associating.
465
 * - `finderQuery`, A complete SQL query CakePHP
466
 *   can use to fetch associated model records. This should
467
 *   be used in situations that require very custom results.
468
 *
469
 * @var array
470
 * @link http://book.cakephp.org/2.0/en/models/associations-linking-models-together.html#hasandbelongstomany-habtm
471
 */
472
	public $hasAndBelongsToMany = array();
473
 
474
/**
475
 * List of behaviors to load when the model object is initialized. Settings can be
476
 * passed to behaviors by using the behavior name as index. Eg:
477
 *
478
 * public $actsAs = array('Translate', 'MyBehavior' => array('setting1' => 'value1'))
479
 *
480
 * @var array
481
 * @link http://book.cakephp.org/2.0/en/models/behaviors.html#using-behaviors
482
 */
483
	public $actsAs = null;
484
 
485
/**
486
 * Holds the Behavior objects currently bound to this model.
487
 *
488
 * @var BehaviorCollection
489
 */
490
	public $Behaviors = null;
491
 
492
/**
493
 * Whitelist of fields allowed to be saved.
494
 *
495
 * @var array
496
 */
497
	public $whitelist = array();
498
 
499
/**
500
 * Whether or not to cache sources for this model.
501
 *
502
 * @var boolean
503
 */
504
	public $cacheSources = true;
505
 
506
/**
507
 * Type of find query currently executing.
508
 *
509
 * @var string
510
 */
511
	public $findQueryType = null;
512
 
513
/**
514
 * Number of associations to recurse through during find calls. Fetches only
515
 * the first level by default.
516
 *
517
 * @var integer
518
 * @link http://book.cakephp.org/2.0/en/models/model-attributes.html#recursive
519
 */
520
	public $recursive = 1;
521
 
522
/**
523
 * The column name(s) and direction(s) to order find results by default.
524
 *
525
 * public $order = "Post.created DESC";
526
 * public $order = array("Post.view_count DESC", "Post.rating DESC");
527
 *
528
 * @var string
529
 * @link http://book.cakephp.org/2.0/en/models/model-attributes.html#order
530
 */
531
	public $order = null;
532
 
533
/**
534
 * Array of virtual fields this model has. Virtual fields are aliased
535
 * SQL expressions. Fields added to this property will be read as other fields in a model
536
 * but will not be saveable.
537
 *
538
 * `public $virtualFields = array('two' => '1 + 1');`
539
 *
540
 * Is a simplistic example of how to set virtualFields
541
 *
542
 * @var array
543
 * @link http://book.cakephp.org/2.0/en/models/model-attributes.html#virtualfields
544
 */
545
	public $virtualFields = array();
546
 
547
/**
548
 * Default list of association keys.
549
 *
550
 * @var array
551
 */
552
	protected $_associationKeys = array(
553
		'belongsTo' => array('className', 'foreignKey', 'conditions', 'fields', 'order', 'counterCache'),
554
		'hasOne' => array('className', 'foreignKey', 'conditions', 'fields', 'order', 'dependent'),
555
		'hasMany' => array('className', 'foreignKey', 'conditions', 'fields', 'order', 'limit', 'offset', 'dependent', 'exclusive', 'finderQuery', 'counterQuery'),
556
		'hasAndBelongsToMany' => array('className', 'joinTable', 'with', 'foreignKey', 'associationForeignKey', 'conditions', 'fields', 'order', 'limit', 'offset', 'unique', 'finderQuery')
557
	);
558
 
559
/**
560
 * Holds provided/generated association key names and other data for all associations.
561
 *
562
 * @var array
563
 */
564
	protected $_associations = array('belongsTo', 'hasOne', 'hasMany', 'hasAndBelongsToMany');
565
 
566
// @codingStandardsIgnoreStart
567
 
568
/**
569
 * Holds model associations temporarily to allow for dynamic (un)binding.
570
 *
571
 * @var array
572
 */
573
	public $__backAssociation = array();
574
 
575
/**
576
 * Back inner association
577
 *
578
 * @var array
579
 */
580
	public $__backInnerAssociation = array();
581
 
582
/**
583
 * Back original association
584
 *
585
 * @var array
586
 */
587
	public $__backOriginalAssociation = array();
588
 
589
/**
590
 * Back containable association
591
 *
592
 * @var array
593
 */
594
	public $__backContainableAssociation = array();
595
 
596
// @codingStandardsIgnoreEnd
597
 
598
/**
599
 * The ID of the model record that was last inserted.
600
 *
601
 * @var integer
602
 */
603
	protected $_insertID = null;
604
 
605
/**
606
 * Has the datasource been configured.
607
 *
608
 * @var boolean
609
 * @see Model::getDataSource
610
 */
611
	protected $_sourceConfigured = false;
612
 
613
/**
614
 * List of valid finder method options, supplied as the first parameter to find().
615
 *
616
 * @var array
617
 */
618
	public $findMethods = array(
619
		'all' => true, 'first' => true, 'count' => true,
620
		'neighbors' => true, 'list' => true, 'threaded' => true
621
	);
622
 
623
/**
624
 * Instance of the CakeEventManager this model is using
625
 * to dispatch inner events.
626
 *
627
 * @var CakeEventManager
628
 */
629
	protected $_eventManager = null;
630
 
631
/**
632
 * Instance of the ModelValidator
633
 *
634
 * @var ModelValidator
635
 */
636
	protected $_validator = null;
637
 
638
/**
639
 * Constructor. Binds the model's database table to the object.
640
 *
641
 * If `$id` is an array it can be used to pass several options into the model.
642
 *
643
 * - `id`: The id to start the model on.
644
 * - `table`: The table to use for this model.
645
 * - `ds`: The connection name this model is connected to.
646
 * - `name`: The name of the model eg. Post.
647
 * - `alias`: The alias of the model, this is used for registering the instance in the `ClassRegistry`.
648
 *   eg. `ParentThread`
649
 *
650
 * ### Overriding Model's __construct method.
651
 *
652
 * When overriding Model::__construct() be careful to include and pass in all 3 of the
653
 * arguments to `parent::__construct($id, $table, $ds);`
654
 *
655
 * ### Dynamically creating models
656
 *
657
 * You can dynamically create model instances using the $id array syntax.
658
 *
659
 * {{{
660
 * $Post = new Model(array('table' => 'posts', 'name' => 'Post', 'ds' => 'connection2'));
661
 * }}}
662
 *
663
 * Would create a model attached to the posts table on connection2. Dynamic model creation is useful
664
 * when you want a model object that contains no associations or attached behaviors.
665
 *
666
 * @param boolean|integer|string|array $id Set this ID for this model on startup,
667
 * can also be an array of options, see above.
668
 * @param string $table Name of database table to use.
669
 * @param string $ds DataSource connection name.
670
 */
671
	public function __construct($id = false, $table = null, $ds = null) {
672
		parent::__construct();
673
 
674
		if (is_array($id)) {
675
			extract(array_merge(
676
				array(
677
					'id' => $this->id, 'table' => $this->useTable, 'ds' => $this->useDbConfig,
678
					'name' => $this->name, 'alias' => $this->alias, 'plugin' => $this->plugin
679
				),
680
				$id
681
			));
682
		}
683
 
684
		if ($this->plugin === null) {
685
			$this->plugin = (isset($plugin) ? $plugin : $this->plugin);
686
		}
687
 
688
		if ($this->name === null) {
689
			$this->name = (isset($name) ? $name : get_class($this));
690
		}
691
 
692
		if ($this->alias === null) {
693
			$this->alias = (isset($alias) ? $alias : $this->name);
694
		}
695
 
696
		if ($this->primaryKey === null) {
697
			$this->primaryKey = 'id';
698
		}
699
 
700
		ClassRegistry::addObject($this->alias, $this);
701
 
702
		$this->id = $id;
703
		unset($id);
704
 
705
		if ($table === false) {
706
			$this->useTable = false;
707
		} elseif ($table) {
708
			$this->useTable = $table;
709
		}
710
 
711
		if ($ds !== null) {
712
			$this->useDbConfig = $ds;
713
		}
714
 
715
		if (is_subclass_of($this, 'AppModel')) {
716
			$merge = array('actsAs', 'findMethods');
717
			$parentClass = get_parent_class($this);
718
			if ($parentClass !== 'AppModel') {
719
				$this->_mergeVars($merge, $parentClass);
720
			}
721
			$this->_mergeVars($merge, 'AppModel');
722
		}
723
		$this->_mergeVars(array('findMethods'), 'Model');
724
 
725
		$this->Behaviors = new BehaviorCollection();
726
 
727
		if ($this->useTable !== false) {
728
 
729
			if ($this->useTable === null) {
730
				$this->useTable = Inflector::tableize($this->name);
731
			}
732
 
733
			if (!$this->displayField) {
734
				unset($this->displayField);
735
			}
736
			$this->table = $this->useTable;
737
			$this->tableToModel[$this->table] = $this->alias;
738
		} elseif ($this->table === false) {
739
			$this->table = Inflector::tableize($this->name);
740
		}
741
 
742
		if ($this->tablePrefix === null) {
743
			unset($this->tablePrefix);
744
		}
745
 
746
		$this->_createLinks();
747
		$this->Behaviors->init($this->alias, $this->actsAs);
748
	}
749
 
750
/**
751
 * Returns a list of all events that will fire in the model during it's lifecycle.
752
 * You can override this function to add you own listener callbacks
753
 *
754
 * @return array
755
 */
756
	public function implementedEvents() {
757
		return array(
758
			'Model.beforeFind' => array('callable' => 'beforeFind', 'passParams' => true),
759
			'Model.afterFind' => array('callable' => 'afterFind', 'passParams' => true),
760
			'Model.beforeValidate' => array('callable' => 'beforeValidate', 'passParams' => true),
761
			'Model.afterValidate' => array('callable' => 'afterValidate'),
762
			'Model.beforeSave' => array('callable' => 'beforeSave', 'passParams' => true),
763
			'Model.afterSave' => array('callable' => 'afterSave', 'passParams' => true),
764
			'Model.beforeDelete' => array('callable' => 'beforeDelete', 'passParams' => true),
765
			'Model.afterDelete' => array('callable' => 'afterDelete'),
766
		);
767
	}
768
 
769
/**
770
 * Returns the CakeEventManager manager instance that is handling any callbacks.
771
 * You can use this instance to register any new listeners or callbacks to the
772
 * model events, or create your own events and trigger them at will.
773
 *
774
 * @return CakeEventManager
775
 */
776
	public function getEventManager() {
777
		if (empty($this->_eventManager)) {
778
			$this->_eventManager = new CakeEventManager();
779
			$this->_eventManager->attach($this->Behaviors);
780
			$this->_eventManager->attach($this);
781
		}
782
 
783
		return $this->_eventManager;
784
	}
785
 
786
/**
787
 * Handles custom method calls, like findBy<field> for DB models,
788
 * and custom RPC calls for remote data sources.
789
 *
790
 * @param string $method Name of method to call.
791
 * @param array $params Parameters for the method.
792
 * @return mixed Whatever is returned by called method
793
 */
794
	public function __call($method, $params) {
795
		$result = $this->Behaviors->dispatchMethod($this, $method, $params);
796
		if ($result !== array('unhandled')) {
797
			return $result;
798
		}
799
 
800
		return $this->getDataSource()->query($method, $params, $this);
801
	}
802
 
803
/**
804
 * Handles the lazy loading of model associations by looking in the association arrays for the requested variable
805
 *
806
 * @param string $name variable tested for existence in class
807
 * @return boolean true if the variable exists (if is a not loaded model association it will be created), false otherwise
808
 */
809
	public function __isset($name) {
810
		$className = false;
811
 
812
		foreach ($this->_associations as $type) {
813
			if (isset($name, $this->{$type}[$name])) {
814
				$className = empty($this->{$type}[$name]['className']) ? $name : $this->{$type}[$name]['className'];
815
				break;
816
			} elseif (isset($name, $this->__backAssociation[$type][$name])) {
817
				$className = empty($this->__backAssociation[$type][$name]['className']) ?
818
					$name : $this->__backAssociation[$type][$name]['className'];
819
				break;
820
			} elseif ($type === 'hasAndBelongsToMany') {
821
				foreach ($this->{$type} as $k => $relation) {
822
					if (empty($relation['with'])) {
823
						continue;
824
					}
825
 
826
					if (is_array($relation['with'])) {
827
						if (key($relation['with']) === $name) {
828
							$className = $name;
829
						}
830
					} else {
831
						list($plugin, $class) = pluginSplit($relation['with']);
832
						if ($class === $name) {
833
							$className = $relation['with'];
834
						}
835
					}
836
 
837
					if ($className) {
838
						$assocKey = $k;
839
						$dynamic = !empty($relation['dynamicWith']);
840
						break(2);
841
					}
842
				}
843
			}
844
		}
845
 
846
		if (!$className) {
847
			return false;
848
		}
849
 
850
		list($plugin, $className) = pluginSplit($className);
851
 
852
		if (!ClassRegistry::isKeySet($className) && !empty($dynamic)) {
853
			$this->{$className} = new AppModel(array(
854
				'name' => $className,
855
				'table' => $this->hasAndBelongsToMany[$assocKey]['joinTable'],
856
				'ds' => $this->useDbConfig
857
			));
858
		} else {
859
			$this->_constructLinkedModel($name, $className, $plugin);
860
		}
861
 
862
		if (!empty($assocKey)) {
863
			$this->hasAndBelongsToMany[$assocKey]['joinTable'] = $this->{$name}->table;
864
			if (count($this->{$name}->schema()) <= 2 && $this->{$name}->primaryKey !== false) {
865
				$this->{$name}->primaryKey = $this->hasAndBelongsToMany[$assocKey]['foreignKey'];
866
			}
867
		}
868
 
869
		return true;
870
	}
871
 
872
/**
873
 * Returns the value of the requested variable if it can be set by __isset()
874
 *
875
 * @param string $name variable requested for it's value or reference
876
 * @return mixed value of requested variable if it is set
877
 */
878
	public function __get($name) {
879
		if ($name === 'displayField') {
880
			return $this->displayField = $this->hasField(array('title', 'name', $this->primaryKey));
881
		}
882
 
883
		if ($name === 'tablePrefix') {
884
			$this->setDataSource();
885
			if (property_exists($this, 'tablePrefix') && !empty($this->tablePrefix)) {
886
				return $this->tablePrefix;
887
			}
888
 
889
			return $this->tablePrefix = null;
890
		}
891
 
892
		if (isset($this->{$name})) {
893
			return $this->{$name};
894
		}
895
	}
896
 
897
/**
898
 * Bind model associations on the fly.
899
 *
900
 * If `$reset` is false, association will not be reset
901
 * to the originals defined in the model
902
 *
903
 * Example: Add a new hasOne binding to the Profile model not
904
 * defined in the model source code:
905
 *
906
 * `$this->User->bindModel(array('hasOne' => array('Profile')));`
907
 *
908
 * Bindings that are not made permanent will be reset by the next Model::find() call on this
909
 * model.
910
 *
911
 * @param array $params Set of bindings (indexed by binding type)
912
 * @param boolean $reset Set to false to make the binding permanent
913
 * @return boolean Success
914
 * @link http://book.cakephp.org/2.0/en/models/associations-linking-models-together.html#creating-and-destroying-associations-on-the-fly
915
 */
916
	public function bindModel($params, $reset = true) {
917
		foreach ($params as $assoc => $model) {
918
			if ($reset === true && !isset($this->__backAssociation[$assoc])) {
919
				$this->__backAssociation[$assoc] = $this->{$assoc};
920
			}
921
 
922
			foreach ($model as $key => $value) {
923
				$assocName = $key;
924
 
925
				if (is_numeric($key)) {
926
					$assocName = $value;
927
					$value = array();
928
				}
929
 
930
				$this->{$assoc}[$assocName] = $value;
931
 
932
				if (property_exists($this, $assocName)) {
933
					unset($this->{$assocName});
934
				}
935
 
936
				if ($reset === false && isset($this->__backAssociation[$assoc])) {
937
					$this->__backAssociation[$assoc][$assocName] = $value;
938
				}
939
			}
940
		}
941
 
942
		$this->_createLinks();
943
		return true;
944
	}
945
 
946
/**
947
 * Turn off associations on the fly.
948
 *
949
 * If $reset is false, association will not be reset
950
 * to the originals defined in the model
951
 *
952
 * Example: Turn off the associated Model Support request,
953
 * to temporarily lighten the User model:
954
 *
955
 * `$this->User->unbindModel(array('hasMany' => array('Supportrequest')));`
956
 *
957
 * unbound models that are not made permanent will reset with the next call to Model::find()
958
 *
959
 * @param array $params Set of bindings to unbind (indexed by binding type)
960
 * @param boolean $reset Set to false to make the unbinding permanent
961
 * @return boolean Success
962
 * @link http://book.cakephp.org/2.0/en/models/associations-linking-models-together.html#creating-and-destroying-associations-on-the-fly
963
 */
964
	public function unbindModel($params, $reset = true) {
965
		foreach ($params as $assoc => $models) {
966
			if ($reset === true && !isset($this->__backAssociation[$assoc])) {
967
				$this->__backAssociation[$assoc] = $this->{$assoc};
968
			}
969
 
970
			foreach ($models as $model) {
971
				if ($reset === false && isset($this->__backAssociation[$assoc][$model])) {
972
					unset($this->__backAssociation[$assoc][$model]);
973
				}
974
 
975
				unset($this->{$assoc}[$model]);
976
			}
977
		}
978
 
979
		return true;
980
	}
981
 
982
/**
983
 * Create a set of associations.
984
 *
985
 * @return void
986
 */
987
	protected function _createLinks() {
988
		foreach ($this->_associations as $type) {
989
			$association =& $this->{$type};
990
 
991
			if (!is_array($association)) {
992
				$association = explode(',', $association);
993
 
994
				foreach ($association as $i => $className) {
995
					$className = trim($className);
996
					unset ($association[$i]);
997
					$association[$className] = array();
998
				}
999
			}
1000
 
1001
			if (!empty($association)) {
1002
				foreach ($association as $assoc => $value) {
1003
					$plugin = null;
1004
 
1005
					if (is_numeric($assoc)) {
1006
						unset($association[$assoc]);
1007
						$assoc = $value;
1008
						$value = array();
1009
 
1010
						if (strpos($assoc, '.') !== false) {
1011
							list($plugin, $assoc) = pluginSplit($assoc, true);
1012
							$association[$assoc] = array('className' => $plugin . $assoc);
1013
						} else {
1014
							$association[$assoc] = $value;
1015
						}
1016
					}
1017
 
1018
					$this->_generateAssociation($type, $assoc);
1019
				}
1020
			}
1021
		}
1022
	}
1023
 
1024
/**
1025
 * Protected helper method to create associated models of a given class.
1026
 *
1027
 * @param string $assoc Association name
1028
 * @param string $className Class name
1029
 * @param string $plugin name of the plugin where $className is located
1030
 * 	examples: public $hasMany = array('Assoc' => array('className' => 'ModelName'));
1031
 * 					usage: $this->Assoc->modelMethods();
1032
 *
1033
 * 				public $hasMany = array('ModelName');
1034
 * 					usage: $this->ModelName->modelMethods();
1035
 * @return void
1036
 */
1037
	protected function _constructLinkedModel($assoc, $className = null, $plugin = null) {
1038
		if (empty($className)) {
1039
			$className = $assoc;
1040
		}
1041
 
1042
		if (!isset($this->{$assoc}) || $this->{$assoc}->name !== $className) {
1043
			if ($plugin) {
1044
				$plugin .= '.';
1045
			}
1046
 
1047
			$model = array('class' => $plugin . $className, 'alias' => $assoc);
1048
			$this->{$assoc} = ClassRegistry::init($model);
1049
 
1050
			if ($plugin) {
1051
				ClassRegistry::addObject($plugin . $className, $this->{$assoc});
1052
			}
1053
 
1054
			if ($assoc) {
1055
				$this->tableToModel[$this->{$assoc}->table] = $assoc;
1056
			}
1057
		}
1058
	}
1059
 
1060
/**
1061
 * Build an array-based association from string.
1062
 *
1063
 * @param string $type 'belongsTo', 'hasOne', 'hasMany', 'hasAndBelongsToMany'
1064
 * @param string $assocKey
1065
 * @return void
1066
 */
1067
	protected function _generateAssociation($type, $assocKey) {
1068
		$class = $assocKey;
1069
		$dynamicWith = false;
1070
		$assoc =& $this->{$type}[$assocKey];
1071
 
1072
		foreach ($this->_associationKeys[$type] as $key) {
1073
			if (!isset($assoc[$key]) || $assoc[$key] === null) {
1074
				$data = '';
1075
 
1076
				switch ($key) {
1077
					case 'fields':
1078
						$data = '';
1079
						break;
1080
 
1081
					case 'foreignKey':
1082
						$data = (($type === 'belongsTo') ? Inflector::underscore($assocKey) : Inflector::singularize($this->table)) . '_id';
1083
						break;
1084
 
1085
					case 'associationForeignKey':
1086
						$data = Inflector::singularize($this->{$class}->table) . '_id';
1087
						break;
1088
 
1089
					case 'with':
1090
						$data = Inflector::camelize(Inflector::singularize($assoc['joinTable']));
1091
						$dynamicWith = true;
1092
						break;
1093
 
1094
					case 'joinTable':
1095
						$tables = array($this->table, $this->{$class}->table);
1096
						sort($tables);
1097
						$data = $tables[0] . '_' . $tables[1];
1098
						break;
1099
 
1100
					case 'className':
1101
						$data = $class;
1102
						break;
1103
 
1104
					case 'unique':
1105
						$data = true;
1106
						break;
1107
				}
1108
 
1109
				$assoc[$key] = $data;
1110
			}
1111
 
1112
			if ($dynamicWith) {
1113
				$assoc['dynamicWith'] = true;
1114
			}
1115
		}
1116
	}
1117
 
1118
/**
1119
 * Sets a custom table for your model class. Used by your controller to select a database table.
1120
 *
1121
 * @param string $tableName Name of the custom table
1122
 * @throws MissingTableException when database table $tableName is not found on data source
1123
 * @return void
1124
 */
1125
	public function setSource($tableName) {
1126
		$this->setDataSource($this->useDbConfig);
1127
		$db = ConnectionManager::getDataSource($this->useDbConfig);
1128
 
1129
		if (method_exists($db, 'listSources')) {
1130
			$restore = $db->cacheSources;
1131
			$db->cacheSources = ($restore && $this->cacheSources);
1132
			$sources = $db->listSources();
1133
			$db->cacheSources = $restore;
1134
 
1135
			if (is_array($sources) && !in_array(strtolower($this->tablePrefix . $tableName), array_map('strtolower', $sources))) {
1136
				throw new MissingTableException(array(
1137
					'table' => $this->tablePrefix . $tableName,
1138
					'class' => $this->alias,
1139
					'ds' => $this->useDbConfig,
1140
				));
1141
			}
1142
 
1143
			if ($sources) {
1144
				$this->_schema = null;
1145
			}
1146
		}
1147
 
1148
		$this->table = $this->useTable = $tableName;
1149
		$this->tableToModel[$this->table] = $this->alias;
1150
	}
1151
 
1152
/**
1153
 * This function does two things:
1154
 *
1155
 * 1. it scans the array $one for the primary key,
1156
 * and if that's found, it sets the current id to the value of $one[id].
1157
 * For all other keys than 'id' the keys and values of $one are copied to the 'data' property of this object.
1158
 * 2. Returns an array with all of $one's keys and values.
1159
 * (Alternative indata: two strings, which are mangled to
1160
 * a one-item, two-dimensional array using $one for a key and $two as its value.)
1161
 *
1162
 * @param string|array|SimpleXmlElement|DomNode $one Array or string of data
1163
 * @param string $two Value string for the alternative indata method
1164
 * @return array Data with all of $one's keys and values
1165
 * @link http://book.cakephp.org/2.0/en/models/saving-your-data.html
1166
 */
1167
	public function set($one, $two = null) {
1168
		if (!$one) {
1169
			return;
1170
		}
1171
 
1172
		if (is_object($one)) {
1173
			if ($one instanceof SimpleXMLElement || $one instanceof DOMNode) {
1174
				$one = $this->_normalizeXmlData(Xml::toArray($one));
1175
			} else {
1176
				$one = Set::reverse($one);
1177
			}
1178
		}
1179
 
1180
		if (is_array($one)) {
1181
			$data = $one;
1182
			if (empty($one[$this->alias])) {
1183
				$data = $this->_setAliasData($one);
1184
			}
1185
		} else {
1186
			$data = array($this->alias => array($one => $two));
1187
		}
1188
 
1189
		foreach ($data as $modelName => $fieldSet) {
1190
			if (!is_array($fieldSet)) {
1191
				continue;
1192
			}
1193
 
1194
			foreach ($fieldSet as $fieldName => $fieldValue) {
1195
				unset($this->validationErrors[$fieldName]);
1196
 
1197
				if ($modelName === $this->alias && $fieldName === $this->primaryKey) {
1198
					$this->id = $fieldValue;
1199
				}
1200
 
1201
				if (is_array($fieldValue) || is_object($fieldValue)) {
1202
					$fieldValue = $this->deconstruct($fieldName, $fieldValue);
1203
				}
1204
 
1205
				$this->data[$modelName][$fieldName] = $fieldValue;
1206
			}
1207
		}
1208
 
1209
		return $data;
1210
	}
1211
 
1212
/**
1213
 * Move values to alias
1214
 *
1215
 * @param array $data
1216
 * @return array
1217
 */
1218
	protected function _setAliasData($data) {
1219
		$models = array_keys($this->getAssociated());
1220
		$schema = array_keys((array)$this->schema());
1221
 
1222
		foreach ($data as $field => $value) {
1223
			if (in_array($field, $schema) || !in_array($field, $models)) {
1224
				$data[$this->alias][$field] = $value;
1225
				unset($data[$field]);
1226
			}
1227
		}
1228
 
1229
		return $data;
1230
	}
1231
 
1232
/**
1233
 * Normalize `Xml::toArray()` to use in `Model::save()`
1234
 *
1235
 * @param array $xml XML as array
1236
 * @return array
1237
 */
1238
	protected function _normalizeXmlData(array $xml) {
1239
		$return = array();
1240
		foreach ($xml as $key => $value) {
1241
			if (is_array($value)) {
1242
				$return[Inflector::camelize($key)] = $this->_normalizeXmlData($value);
1243
			} elseif ($key[0] === '@') {
1244
				$return[substr($key, 1)] = $value;
1245
			} else {
1246
				$return[$key] = $value;
1247
			}
1248
		}
1249
 
1250
		return $return;
1251
	}
1252
 
1253
/**
1254
 * Deconstructs a complex data type (array or object) into a single field value.
1255
 *
1256
 * @param string $field The name of the field to be deconstructed
1257
 * @param array|object $data An array or object to be deconstructed into a field
1258
 * @return mixed The resulting data that should be assigned to a field
1259
 */
1260
	public function deconstruct($field, $data) {
1261
		if (!is_array($data)) {
1262
			return $data;
1263
		}
1264
 
1265
		$type = $this->getColumnType($field);
1266
 
1267
		if (!in_array($type, array('datetime', 'timestamp', 'date', 'time'))) {
1268
			return $data;
1269
		}
1270
 
1271
		$useNewDate = (isset($data['year']) || isset($data['month']) ||
1272
			isset($data['day']) || isset($data['hour']) || isset($data['minute']));
1273
 
1274
		$dateFields = array('Y' => 'year', 'm' => 'month', 'd' => 'day', 'H' => 'hour', 'i' => 'min', 's' => 'sec');
1275
		$timeFields = array('H' => 'hour', 'i' => 'min', 's' => 'sec');
1276
		$date = array();
1277
 
1278
		if (isset($data['meridian']) && empty($data['meridian'])) {
1279
			return null;
1280
		}
1281
 
1282
		if (
1283
			isset($data['hour']) &&
1284
			isset($data['meridian']) &&
1285
			!empty($data['hour']) &&
1286
			$data['hour'] != 12 &&
1287
			$data['meridian'] === 'pm'
1288
		) {
1289
			$data['hour'] = $data['hour'] + 12;
1290
		}
1291
 
1292
		if (isset($data['hour']) && isset($data['meridian']) && $data['hour'] == 12 && $data['meridian'] === 'am') {
1293
			$data['hour'] = '00';
1294
		}
1295
 
1296
		if ($type === 'time') {
1297
			foreach ($timeFields as $key => $val) {
1298
				if (!isset($data[$val]) || $data[$val] === '0' || $data[$val] === '00') {
1299
					$data[$val] = '00';
1300
				} elseif ($data[$val] !== '') {
1301
					$data[$val] = sprintf('%02d', $data[$val]);
1302
				}
1303
 
1304
				if (!empty($data[$val])) {
1305
					$date[$key] = $data[$val];
1306
				} else {
1307
					return null;
1308
				}
1309
			}
1310
		}
1311
 
1312
		if ($type === 'datetime' || $type === 'timestamp' || $type === 'date') {
1313
			foreach ($dateFields as $key => $val) {
1314
				if ($val === 'hour' || $val === 'min' || $val === 'sec') {
1315
					if (!isset($data[$val]) || $data[$val] === '0' || $data[$val] === '00') {
1316
						$data[$val] = '00';
1317
					} else {
1318
						$data[$val] = sprintf('%02d', $data[$val]);
1319
					}
1320
				}
1321
 
1322
				if (!isset($data[$val]) || isset($data[$val]) && (empty($data[$val]) || $data[$val][0] === '-')) {
1323
					return null;
1324
				}
1325
 
1326
				if (isset($data[$val]) && !empty($data[$val])) {
1327
					$date[$key] = $data[$val];
1328
				}
1329
			}
1330
		}
1331
 
1332
		if ($useNewDate && !empty($date)) {
1333
			$format = $this->getDataSource()->columns[$type]['format'];
1334
			foreach (array('m', 'd', 'H', 'i', 's') as $index) {
1335
				if (isset($date[$index])) {
1336
					$date[$index] = sprintf('%02d', $date[$index]);
1337
				}
1338
			}
1339
 
1340
			return str_replace(array_keys($date), array_values($date), $format);
1341
		}
1342
 
1343
		return $data;
1344
	}
1345
 
1346
/**
1347
 * Returns an array of table metadata (column names and types) from the database.
1348
 * $field => keys(type, null, default, key, length, extra)
1349
 *
1350
 * @param boolean|string $field Set to true to reload schema, or a string to return a specific field
1351
 * @return array Array of table metadata
1352
 */
1353
	public function schema($field = false) {
1354
		if ($this->useTable !== false && (!is_array($this->_schema) || $field === true)) {
1355
			$db = $this->getDataSource();
1356
			$db->cacheSources = ($this->cacheSources && $db->cacheSources);
1357
			if (method_exists($db, 'describe')) {
1358
				$this->_schema = $db->describe($this);
1359
			}
1360
		}
1361
 
1362
		if (!is_string($field)) {
1363
			return $this->_schema;
1364
		}
1365
 
1366
		if (isset($this->_schema[$field])) {
1367
			return $this->_schema[$field];
1368
		}
1369
 
1370
		return null;
1371
	}
1372
 
1373
/**
1374
 * Returns an associative array of field names and column types.
1375
 *
1376
 * @return array Field types indexed by field name
1377
 */
1378
	public function getColumnTypes() {
1379
		$columns = $this->schema();
1380
		if (empty($columns)) {
1381
			trigger_error(__d('cake_dev', '(Model::getColumnTypes) Unable to build model field data. If you are using a model without a database table, try implementing schema()'), E_USER_WARNING);
1382
		}
1383
 
1384
		$cols = array();
1385
		foreach ($columns as $field => $values) {
1386
			$cols[$field] = $values['type'];
1387
		}
1388
 
1389
		return $cols;
1390
	}
1391
 
1392
/**
1393
 * Returns the column type of a column in the model.
1394
 *
1395
 * @param string $column The name of the model column
1396
 * @return string Column type
1397
 */
1398
	public function getColumnType($column) {
1399
		$db = $this->getDataSource();
1400
		$cols = $this->schema();
1401
		$model = null;
1402
 
1403
		$startQuote = isset($db->startQuote) ? $db->startQuote : null;
1404
		$endQuote = isset($db->endQuote) ? $db->endQuote : null;
1405
		$column = str_replace(array($startQuote, $endQuote), '', $column);
1406
 
1407
		if (strpos($column, '.')) {
1408
			list($model, $column) = explode('.', $column);
1409
		}
1410
 
1411
		if ($model != $this->alias && isset($this->{$model})) {
1412
			return $this->{$model}->getColumnType($column);
1413
		}
1414
 
1415
		if (isset($cols[$column]) && isset($cols[$column]['type'])) {
1416
			return $cols[$column]['type'];
1417
		}
1418
 
1419
		return null;
1420
	}
1421
 
1422
/**
1423
 * Returns true if the supplied field exists in the model's database table.
1424
 *
1425
 * @param string|array $name Name of field to look for, or an array of names
1426
 * @param boolean $checkVirtual checks if the field is declared as virtual
1427
 * @return mixed If $name is a string, returns a boolean indicating whether the field exists.
1428
 *               If $name is an array of field names, returns the first field that exists,
1429
 *               or false if none exist.
1430
 */
1431
	public function hasField($name, $checkVirtual = false) {
1432
		if (is_array($name)) {
1433
			foreach ($name as $n) {
1434
				if ($this->hasField($n, $checkVirtual)) {
1435
					return $n;
1436
				}
1437
			}
1438
 
1439
			return false;
1440
		}
1441
 
1442
		if ($checkVirtual && !empty($this->virtualFields) && $this->isVirtualField($name)) {
1443
			return true;
1444
		}
1445
 
1446
		if (empty($this->_schema)) {
1447
			$this->schema();
1448
		}
1449
 
1450
		if ($this->_schema) {
1451
			return isset($this->_schema[$name]);
1452
		}
1453
 
1454
		return false;
1455
	}
1456
 
1457
/**
1458
 * Check that a method is callable on a model. This will check both the model's own methods, its
1459
 * inherited methods and methods that could be callable through behaviors.
1460
 *
1461
 * @param string $method The method to be called.
1462
 * @return boolean True on method being callable.
1463
 */
1464
	public function hasMethod($method) {
1465
		if (method_exists($this, $method)) {
1466
			return true;
1467
		}
1468
 
1469
		return $this->Behaviors->hasMethod($method);
1470
	}
1471
 
1472
/**
1473
 * Returns true if the supplied field is a model Virtual Field
1474
 *
1475
 * @param string $field Name of field to look for
1476
 * @return boolean indicating whether the field exists as a model virtual field.
1477
 */
1478
	public function isVirtualField($field) {
1479
		if (empty($this->virtualFields) || !is_string($field)) {
1480
			return false;
1481
		}
1482
 
1483
		if (isset($this->virtualFields[$field])) {
1484
			return true;
1485
		}
1486
 
1487
		if (strpos($field, '.') !== false) {
1488
			list($model, $field) = explode('.', $field);
1489
			if ($model == $this->alias && isset($this->virtualFields[$field])) {
1490
				return true;
1491
			}
1492
		}
1493
 
1494
		return false;
1495
	}
1496
 
1497
/**
1498
 * Returns the expression for a model virtual field
1499
 *
1500
 * @param string $field Name of field to look for
1501
 * @return mixed If $field is string expression bound to virtual field $field
1502
 *    If $field is null, returns an array of all model virtual fields
1503
 *    or false if none $field exist.
1504
 */
1505
	public function getVirtualField($field = null) {
1506
		if (!$field) {
1507
			return empty($this->virtualFields) ? false : $this->virtualFields;
1508
		}
1509
 
1510
		if ($this->isVirtualField($field)) {
1511
			if (strpos($field, '.') !== false) {
1512
				list(, $field) = pluginSplit($field);
1513
			}
1514
 
1515
			return $this->virtualFields[$field];
1516
		}
1517
 
1518
		return false;
1519
	}
1520
 
1521
/**
1522
 * Initializes the model for writing a new record, loading the default values
1523
 * for those fields that are not defined in $data, and clearing previous validation errors.
1524
 * Especially helpful for saving data in loops.
1525
 *
1526
 * @param boolean|array $data Optional data array to assign to the model after it is created. If null or false,
1527
 *   schema data defaults are not merged.
1528
 * @param boolean $filterKey If true, overwrites any primary key input with an empty value
1529
 * @return array The current Model::data; after merging $data and/or defaults from database
1530
 * @link http://book.cakephp.org/2.0/en/models/saving-your-data.html#model-create-array-data-array
1531
 */
1532
	public function create($data = array(), $filterKey = false) {
1533
		$defaults = array();
1534
		$this->id = false;
1535
		$this->data = array();
1536
		$this->validationErrors = array();
1537
 
1538
		if ($data !== null && $data !== false) {
1539
			$schema = (array)$this->schema();
1540
			foreach ($schema as $field => $properties) {
1541
				if ($this->primaryKey !== $field && isset($properties['default']) && $properties['default'] !== '') {
1542
					$defaults[$field] = $properties['default'];
1543
				}
1544
			}
1545
 
1546
			$this->set($defaults);
1547
			$this->set($data);
1548
		}
1549
 
1550
		if ($filterKey) {
1551
			$this->set($this->primaryKey, false);
1552
		}
1553
 
1554
		return $this->data;
1555
	}
1556
 
1557
/**
1558
 * This function is a convenient wrapper class to create(false) and, as the name suggests, clears the id, data, and validation errors.
1559
 *
1560
 * @return boolean Always true upon success
1561
 * @see Model::create()
1562
 */
1563
	public function clear() {
1564
		$this->create(false);
1565
		return true;
1566
	}
1567
 
1568
/**
1569
 * Returns a list of fields from the database, and sets the current model
1570
 * data (Model::$data) with the record found.
1571
 *
1572
 * @param string|array $fields String of single field name, or an array of field names.
1573
 * @param integer|string $id The ID of the record to read
1574
 * @return array Array of database fields, or false if not found
1575
 * @link http://book.cakephp.org/2.0/en/models/retrieving-your-data.html#model-read
1576
 */
1577
	public function read($fields = null, $id = null) {
1578
		$this->validationErrors = array();
1579
 
1580
		if ($id) {
1581
			$this->id = $id;
1582
		}
1583
 
1584
		$id = $this->id;
1585
 
1586
		if (is_array($this->id)) {
1587
			$id = $this->id[0];
1588
		}
1589
 
1590
		if ($id !== null && $id !== false) {
1591
			$this->data = $this->find('first', array(
1592
				'conditions' => array($this->alias . '.' . $this->primaryKey => $id),
1593
				'fields' => $fields
1594
			));
1595
 
1596
			return $this->data;
1597
		}
1598
 
1599
		return false;
1600
	}
1601
 
1602
/**
1603
 * Returns the contents of a single field given the supplied conditions, in the
1604
 * supplied order.
1605
 *
1606
 * @param string $name Name of field to get
1607
 * @param array $conditions SQL conditions (defaults to NULL)
1608
 * @param string $order SQL ORDER BY fragment
1609
 * @return string field contents, or false if not found
1610
 * @link http://book.cakephp.org/2.0/en/models/retrieving-your-data.html#model-field
1611
 */
1612
	public function field($name, $conditions = null, $order = null) {
1613
		if ($conditions === null && $this->id !== false) {
1614
			$conditions = array($this->alias . '.' . $this->primaryKey => $this->id);
1615
		}
1616
 
1617
		$recursive = $this->recursive;
1618
		if ($this->recursive >= 1) {
1619
			$recursive = -1;
1620
		}
1621
 
1622
		$fields = $name;
1623
		$data = $this->find('first', compact('conditions', 'fields', 'order', 'recursive'));
1624
		if (!$data) {
1625
			return false;
1626
		}
1627
 
1628
		if (strpos($name, '.') === false) {
1629
			if (isset($data[$this->alias][$name])) {
1630
				return $data[$this->alias][$name];
1631
			}
1632
		} else {
1633
			$name = explode('.', $name);
1634
			if (isset($data[$name[0]][$name[1]])) {
1635
				return $data[$name[0]][$name[1]];
1636
			}
1637
		}
1638
 
1639
		if (isset($data[0]) && count($data[0]) > 0) {
1640
			return array_shift($data[0]);
1641
		}
1642
	}
1643
 
1644
/**
1645
 * Saves the value of a single field to the database, based on the current
1646
 * model ID.
1647
 *
1648
 * @param string $name Name of the table field
1649
 * @param mixed $value Value of the field
1650
 * @param boolean|array $validate Either a boolean, or an array.
1651
 *   If a boolean, indicates whether or not to validate before saving.
1652
 *   If an array, allows control of 'validate', 'callbacks' and 'counterCache' options.
1653
 *   See Model::save() for details of each options.
1654
 * @return boolean See Model::save()
1655
 * @see Model::save()
1656
 * @link http://book.cakephp.org/2.0/en/models/saving-your-data.html#model-savefield-string-fieldname-string-fieldvalue-validate-false
1657
 */
1658
	public function saveField($name, $value, $validate = false) {
1659
		$id = $this->id;
1660
		$this->create(false);
1661
 
1662
		$options = array('validate' => $validate, 'fieldList' => array($name));
1663
		if (is_array($validate)) {
1664
			$options = array_merge(array('validate' => false, 'fieldList' => array($name)), $validate);
1665
		}
1666
 
1667
		return $this->save(array($this->alias => array($this->primaryKey => $id, $name => $value)), $options);
1668
	}
1669
 
1670
/**
1671
 * Saves model data (based on white-list, if supplied) to the database. By
1672
 * default, validation occurs before save.
1673
 *
1674
 * @param array $data Data to save.
1675
 * @param boolean|array $validate Either a boolean, or an array.
1676
 *   If a boolean, indicates whether or not to validate before saving.
1677
 *   If an array, can have following keys:
1678
 *
1679
 *   - validate: Set to true/false to enable or disable validation.
1680
 *   - fieldList: An array of fields you want to allow for saving.
1681
 *   - callbacks: Set to false to disable callbacks. Using 'before' or 'after'
1682
 *      will enable only those callbacks.
1683
 *   - `counterCache`: Boolean to control updating of counter caches (if any)
1684
 *
1685
 * @param array $fieldList List of fields to allow to be saved
1686
 * @return mixed On success Model::$data if its not empty or true, false on failure
1687
 * @link http://book.cakephp.org/2.0/en/models/saving-your-data.html
1688
 */
1689
	public function save($data = null, $validate = true, $fieldList = array()) {
1690
		$defaults = array(
1691
			'validate' => true, 'fieldList' => array(),
1692
			'callbacks' => true, 'counterCache' => true
1693
		);
1694
		$_whitelist = $this->whitelist;
1695
		$fields = array();
1696
 
1697
		if (!is_array($validate)) {
1698
			$options = array_merge($defaults, compact('validate', 'fieldList'));
1699
		} else {
1700
			$options = array_merge($defaults, $validate);
1701
		}
1702
 
1703
		if (!empty($options['fieldList'])) {
1704
			if (!empty($options['fieldList'][$this->alias]) && is_array($options['fieldList'][$this->alias])) {
1705
				$this->whitelist = $options['fieldList'][$this->alias];
1706
			} elseif (Hash::dimensions($options['fieldList']) < 2) {
1707
				$this->whitelist = $options['fieldList'];
1708
			}
1709
		} elseif ($options['fieldList'] === null) {
1710
			$this->whitelist = array();
1711
		}
1712
 
1713
		$this->set($data);
1714
 
1715
		if (empty($this->data) && !$this->hasField(array('created', 'updated', 'modified'))) {
1716
			$this->whitelist = $_whitelist;
1717
			return false;
1718
		}
1719
 
1720
		foreach (array('created', 'updated', 'modified') as $field) {
1721
			$keyPresentAndEmpty = (
1722
				isset($this->data[$this->alias]) &&
1723
				array_key_exists($field, $this->data[$this->alias]) &&
1724
				$this->data[$this->alias][$field] === null
1725
			);
1726
 
1727
			if ($keyPresentAndEmpty) {
1728
				unset($this->data[$this->alias][$field]);
1729
			}
1730
		}
1731
 
1732
		$exists = $this->exists();
1733
		$dateFields = array('modified', 'updated');
1734
 
1735
		if (!$exists) {
1736
			$dateFields[] = 'created';
1737
		}
1738
 
1739
		if (isset($this->data[$this->alias])) {
1740
			$fields = array_keys($this->data[$this->alias]);
1741
		}
1742
 
1743
		if ($options['validate'] && !$this->validates($options)) {
1744
			$this->whitelist = $_whitelist;
1745
			return false;
1746
		}
1747
 
1748
		$db = $this->getDataSource();
1749
		$now = time();
1750
 
1751
		foreach ($dateFields as $updateCol) {
1752
			if (in_array($updateCol, $fields) || !$this->hasField($updateCol)) {
1753
				continue;
1754
			}
1755
 
1756
			$default = array('formatter' => 'date');
1757
			$colType = array_merge($default, $db->columns[$this->getColumnType($updateCol)]);
1758
 
1759
			$time = $now;
1760
			if (array_key_exists('format', $colType)) {
1761
				$time = call_user_func($colType['formatter'], $colType['format']);
1762
			}
1763
 
1764
			if (!empty($this->whitelist)) {
1765
				$this->whitelist[] = $updateCol;
1766
			}
1767
			$this->set($updateCol, $time);
1768
		}
1769
 
1770
		if ($options['callbacks'] === true || $options['callbacks'] === 'before') {
1771
			$event = new CakeEvent('Model.beforeSave', $this, array($options));
1772
			list($event->break, $event->breakOn) = array(true, array(false, null));
1773
			$this->getEventManager()->dispatch($event);
1774
			if (!$event->result) {
1775
				$this->whitelist = $_whitelist;
1776
				return false;
1777
			}
1778
		}
1779
 
1780
		$db = $this->getDataSource();
1781
 
1782
		if (empty($this->data[$this->alias][$this->primaryKey])) {
1783
			unset($this->data[$this->alias][$this->primaryKey]);
1784
		}
1785
		$fields = $values = array();
1786
 
1787
		foreach ($this->data as $n => $v) {
1788
			if (isset($this->hasAndBelongsToMany[$n])) {
1789
				if (isset($v[$n])) {
1790
					$v = $v[$n];
1791
				}
1792
				$joined[$n] = $v;
1793
			} elseif ($n === $this->alias) {
1794
				foreach (array('created', 'updated', 'modified') as $field) {
1795
					if (array_key_exists($field, $v) && empty($v[$field])) {
1796
						unset($v[$field]);
1797
					}
1798
				}
1799
 
1800
				foreach ($v as $x => $y) {
1801
					if ($this->hasField($x) && (empty($this->whitelist) || in_array($x, $this->whitelist))) {
1802
						list($fields[], $values[]) = array($x, $y);
1803
					}
1804
				}
1805
			}
1806
		}
1807
 
1808
		$count = count($fields);
1809
 
1810
		if (!$exists && $count > 0) {
1811
			$this->id = false;
1812
		}
1813
 
1814
		$success = true;
1815
		$created = false;
1816
 
1817
		if ($count > 0) {
1818
			$cache = $this->_prepareUpdateFields(array_combine($fields, $values));
1819
 
1820
			if (!empty($this->id)) {
1821
				$success = (bool)$db->update($this, $fields, $values);
1822
			} else {
1823
				if (empty($this->data[$this->alias][$this->primaryKey]) && $this->_isUUIDField($this->primaryKey)) {
1824
					if (array_key_exists($this->primaryKey, $this->data[$this->alias])) {
1825
						$j = array_search($this->primaryKey, $fields);
1826
						$values[$j] = String::uuid();
1827
					} else {
1828
						list($fields[], $values[]) = array($this->primaryKey, String::uuid());
1829
					}
1830
				}
1831
 
1832
				if (!$db->create($this, $fields, $values)) {
1833
					$success = false;
1834
				} else {
1835
					$created = true;
1836
				}
1837
			}
1838
 
1839
			if ($success && $options['counterCache'] && !empty($this->belongsTo)) {
1840
				$this->updateCounterCache($cache, $created);
1841
			}
1842
		}
1843
 
1844
		if (!empty($joined) && $success === true) {
1845
			$this->_saveMulti($joined, $this->id, $db);
1846
		}
1847
 
1848
		if ($success && $count === 0) {
1849
			$success = false;
1850
		}
1851
 
1852
		if ($success && $count > 0) {
1853
			if (!empty($this->data)) {
1854
				if ($created) {
1855
					$this->data[$this->alias][$this->primaryKey] = $this->id;
1856
				}
1857
			}
1858
 
1859
			if ($options['callbacks'] === true || $options['callbacks'] === 'after') {
1860
				$event = new CakeEvent('Model.afterSave', $this, array($created, $options));
1861
				$this->getEventManager()->dispatch($event);
1862
			}
1863
 
1864
			if (!empty($this->data)) {
1865
				$success = $this->data;
1866
			}
1867
 
1868
			$this->data = false;
1869
			$this->_clearCache();
1870
			$this->validationErrors = array();
1871
		}
1872
 
1873
		$this->whitelist = $_whitelist;
1874
		return $success;
1875
	}
1876
 
1877
/**
1878
 * Check if the passed in field is a UUID field
1879
 *
1880
 * @param string $field the field to check
1881
 * @return boolean
1882
 */
1883
	protected function _isUUIDField($field) {
1884
		$field = $this->schema($field);
1885
		return $field['length'] == 36 && in_array($field['type'], array('string', 'binary'));
1886
	}
1887
 
1888
/**
1889
 * Saves model hasAndBelongsToMany data to the database.
1890
 *
1891
 * @param array $joined Data to save
1892
 * @param integer|string $id ID of record in this model
1893
 * @param DataSource $db
1894
 * @return void
1895
 */
1896
	protected function _saveMulti($joined, $id, $db) {
1897
		foreach ($joined as $assoc => $data) {
1898
			if (!isset($this->hasAndBelongsToMany[$assoc])) {
1899
				continue;
1900
			}
1901
 
1902
			$habtm = $this->hasAndBelongsToMany[$assoc];
1903
 
1904
			list($join) = $this->joinModel($habtm['with']);
1905
 
1906
			$Model = $this->{$join};
1907
 
1908
			if (!empty($habtm['with'])) {
1909
				$withModel = is_array($habtm['with']) ? key($habtm['with']) : $habtm['with'];
1910
				list(, $withModel) = pluginSplit($withModel);
1911
				$dbMulti = $this->{$withModel}->getDataSource();
1912
			} else {
1913
				$dbMulti = $db;
1914
			}
1915
 
1916
			$isUUID = !empty($Model->primaryKey) && $Model->_isUUIDField($Model->primaryKey);
1917
 
1918
			$newData = $newValues = $newJoins = array();
1919
			$primaryAdded = false;
1920
 
1921
			$fields = array(
1922
				$dbMulti->name($habtm['foreignKey']),
1923
				$dbMulti->name($habtm['associationForeignKey'])
1924
			);
1925
 
1926
			$idField = $db->name($Model->primaryKey);
1927
			if ($isUUID && !in_array($idField, $fields)) {
1928
				$fields[] = $idField;
1929
				$primaryAdded = true;
1930
			}
1931
 
1932
			foreach ((array)$data as $row) {
1933
				if ((is_string($row) && (strlen($row) == 36 || strlen($row) == 16)) || is_numeric($row)) {
1934
					$newJoins[] = $row;
1935
					$values = array($id, $row);
1936
 
1937
					if ($isUUID && $primaryAdded) {
1938
						$values[] = String::uuid();
1939
					}
1940
 
1941
					$newValues[$row] = $values;
1942
					unset($values);
1943
				} elseif (isset($row[$habtm['associationForeignKey']])) {
1944
					if (!empty($row[$Model->primaryKey])) {
1945
						$newJoins[] = $row[$habtm['associationForeignKey']];
1946
					}
1947
 
1948
					$newData[] = $row;
1949
				} elseif (isset($row[$join]) && isset($row[$join][$habtm['associationForeignKey']])) {
1950
					if (!empty($row[$join][$Model->primaryKey])) {
1951
						$newJoins[] = $row[$join][$habtm['associationForeignKey']];
1952
					}
1953
 
1954
					$newData[] = $row[$join];
1955
				}
1956
			}
1957
 
1958
			$keepExisting = $habtm['unique'] === 'keepExisting';
1959
			if ($habtm['unique']) {
1960
				$conditions = array(
1961
					$join . '.' . $habtm['foreignKey'] => $id
1962
				);
1963
 
1964
				if (!empty($habtm['conditions'])) {
1965
					$conditions = array_merge($conditions, (array)$habtm['conditions']);
1966
				}
1967
 
1968
				$associationForeignKey = $Model->alias . '.' . $habtm['associationForeignKey'];
1969
				$links = $Model->find('all', array(
1970
					'conditions' => $conditions,
1971
					'recursive' => empty($habtm['conditions']) ? -1 : 0,
1972
					'fields' => $associationForeignKey,
1973
				));
1974
 
1975
				$oldLinks = Hash::extract($links, "{n}.{$associationForeignKey}");
1976
				if (!empty($oldLinks)) {
1977
					if ($keepExisting && !empty($newJoins)) {
1978
						$conditions[$associationForeignKey] = array_diff($oldLinks, $newJoins);
1979
					} else {
1980
						$conditions[$associationForeignKey] = $oldLinks;
1981
					}
1982
 
1983
					$dbMulti->delete($Model, $conditions);
1984
				}
1985
			}
1986
 
1987
			if (!empty($newData)) {
1988
				foreach ($newData as $data) {
1989
					$data[$habtm['foreignKey']] = $id;
1990
					if (empty($data[$Model->primaryKey])) {
1991
						$Model->create();
1992
					}
1993
 
1994
					$Model->save($data);
1995
				}
1996
			}
1997
 
1998
			if (!empty($newValues)) {
1999
				if ($keepExisting && !empty($links)) {
2000
					foreach ($links as $link) {
2001
						$oldJoin = $link[$join][$habtm['associationForeignKey']];
2002
						if (!in_array($oldJoin, $newJoins)) {
2003
							$conditions[$associationForeignKey] = $oldJoin;
2004
							$db->delete($Model, $conditions);
2005
						} else {
2006
							unset($newValues[$oldJoin]);
2007
						}
2008
					}
2009
 
2010
					$newValues = array_values($newValues);
2011
				}
2012
 
2013
				if (!empty($newValues)) {
2014
					$dbMulti->insertMulti($Model, $fields, $newValues);
2015
				}
2016
			}
2017
		}
2018
	}
2019
 
2020
/**
2021
 * Updates the counter cache of belongsTo associations after a save or delete operation
2022
 *
2023
 * @param array $keys Optional foreign key data, defaults to the information $this->data
2024
 * @param boolean $created True if a new record was created, otherwise only associations with
2025
 *   'counterScope' defined get updated
2026
 * @return void
2027
 */
2028
	public function updateCounterCache($keys = array(), $created = false) {
2029
		$keys = empty($keys) ? $this->data[$this->alias] : $keys;
2030
		$keys['old'] = isset($keys['old']) ? $keys['old'] : array();
2031
 
2032
		foreach ($this->belongsTo as $parent => $assoc) {
2033
			if (empty($assoc['counterCache'])) {
2034
				continue;
2035
			}
2036
 
2037
			$Model = $this->{$parent};
2038
 
2039
			if (!is_array($assoc['counterCache'])) {
2040
				if (isset($assoc['counterScope'])) {
2041
					$assoc['counterCache'] = array($assoc['counterCache'] => $assoc['counterScope']);
2042
				} else {
2043
					$assoc['counterCache'] = array($assoc['counterCache'] => array());
2044
				}
2045
			}
2046
 
2047
			$foreignKey = $assoc['foreignKey'];
2048
			$fkQuoted = $this->escapeField($assoc['foreignKey']);
2049
 
2050
			foreach ($assoc['counterCache'] as $field => $conditions) {
2051
				if (!is_string($field)) {
2052
					$field = Inflector::underscore($this->alias) . '_count';
2053
				}
2054
 
2055
				if (!$Model->hasField($field)) {
2056
					continue;
2057
				}
2058
 
2059
				if ($conditions === true) {
2060
					$conditions = array();
2061
				} else {
2062
					$conditions = (array)$conditions;
2063
				}
2064
 
2065
				if (!array_key_exists($foreignKey, $keys)) {
2066
					$keys[$foreignKey] = $this->field($foreignKey);
2067
				}
2068
 
2069
				$recursive = (empty($conditions) ? -1 : 0);
2070
 
2071
				if (isset($keys['old'][$foreignKey]) && $keys['old'][$foreignKey] != $keys[$foreignKey]) {
2072
					$conditions[$fkQuoted] = $keys['old'][$foreignKey];
2073
					$count = intval($this->find('count', compact('conditions', 'recursive')));
2074
 
2075
					$Model->updateAll(
2076
						array($field => $count),
2077
						array($Model->escapeField() => $keys['old'][$foreignKey])
2078
					);
2079
				}
2080
 
2081
				$conditions[$fkQuoted] = $keys[$foreignKey];
2082
 
2083
				if ($recursive === 0) {
2084
					$conditions = array_merge($conditions, (array)$conditions);
2085
				}
2086
 
2087
				$count = intval($this->find('count', compact('conditions', 'recursive')));
2088
 
2089
				$Model->updateAll(
2090
					array($field => $count),
2091
					array($Model->escapeField() => $keys[$foreignKey])
2092
				);
2093
			}
2094
		}
2095
	}
2096
 
2097
/**
2098
 * Helper method for `Model::updateCounterCache()`. Checks the fields to be updated for
2099
 *
2100
 * @param array $data The fields of the record that will be updated
2101
 * @return array Returns updated foreign key values, along with an 'old' key containing the old
2102
 *     values, or empty if no foreign keys are updated.
2103
 */
2104
	protected function _prepareUpdateFields($data) {
2105
		$foreignKeys = array();
2106
		foreach ($this->belongsTo as $assoc => $info) {
2107
			if ($info['counterCache']) {
2108
				$foreignKeys[$assoc] = $info['foreignKey'];
2109
			}
2110
		}
2111
 
2112
		$included = array_intersect($foreignKeys, array_keys($data));
2113
 
2114
		if (empty($included) || empty($this->id)) {
2115
			return array();
2116
		}
2117
 
2118
		$old = $this->find('first', array(
2119
			'conditions' => array($this->alias . '.' . $this->primaryKey => $this->id),
2120
			'fields' => array_values($included),
2121
			'recursive' => -1
2122
		));
2123
 
2124
		return array_merge($data, array('old' => $old[$this->alias]));
2125
	}
2126
 
2127
/**
2128
 * Backwards compatible passthrough method for:
2129
 * saveMany(), validateMany(), saveAssociated() and validateAssociated()
2130
 *
2131
 * Saves multiple individual records for a single model; Also works with a single record, as well as
2132
 * all its associated records.
2133
 *
2134
 * #### Options
2135
 *
2136
 * - `validate`: Set to false to disable validation, true to validate each record before saving,
2137
 *   'first' to validate *all* records before any are saved (default),
2138
 *   or 'only' to only validate the records, but not save them.
2139
 * - `atomic`: If true (default), will attempt to save all records in a single transaction.
2140
 *   Should be set to false if database/table does not support transactions.
2141
 * - `fieldList`: Equivalent to the $fieldList parameter in Model::save().
2142
 *   It should be an associate array with model name as key and array of fields as value. Eg.
2143
 *   {{{
2144
 *   array(
2145
 *       'SomeModel' => array('field'),
2146
 *       'AssociatedModel' => array('field', 'otherfield')
2147
 *   )
2148
 *   }}}
2149
 * - `deep`: See saveMany/saveAssociated
2150
 * - `callbacks`: See Model::save()
2151
 * - `counterCache`: See Model::save()
2152
 *
2153
 * @param array $data Record data to save. This can be either a numerically-indexed array (for saving multiple
2154
 *     records of the same type), or an array indexed by association name.
2155
 * @param array $options Options to use when saving record data, See $options above.
2156
 * @return mixed If atomic: True on success, or false on failure.
2157
 *    Otherwise: array similar to the $data array passed, but values are set to true/false
2158
 *    depending on whether each record saved successfully.
2159
 * @link http://book.cakephp.org/2.0/en/models/saving-your-data.html#model-saveassociated-array-data-null-array-options-array
2160
 * @link http://book.cakephp.org/2.0/en/models/saving-your-data.html#model-saveall-array-data-null-array-options-array
2161
 */
2162
	public function saveAll($data = array(), $options = array()) {
2163
		$options = array_merge(array('validate' => 'first'), $options);
2164
		if (Hash::numeric(array_keys($data))) {
2165
			if ($options['validate'] === 'only') {
2166
				return $this->validateMany($data, $options);
2167
			}
2168
 
2169
			return $this->saveMany($data, $options);
2170
		}
2171
 
2172
		if ($options['validate'] === 'only') {
2173
			return $this->validateAssociated($data, $options);
2174
		}
2175
 
2176
		return $this->saveAssociated($data, $options);
2177
	}
2178
 
2179
/**
2180
 * Saves multiple individual records for a single model
2181
 *
2182
 * #### Options
2183
 *
2184
 * - `validate`: Set to false to disable validation, true to validate each record before saving,
2185
 *   'first' to validate *all* records before any are saved (default),
2186
 * - `atomic`: If true (default), will attempt to save all records in a single transaction.
2187
 *   Should be set to false if database/table does not support transactions.
2188
 * - `fieldList`: Equivalent to the $fieldList parameter in Model::save()
2189
 * - `deep`: If set to true, all associated data will be saved as well.
2190
 * - `callbacks`: See Model::save()
2191
 * - `counterCache`: See Model::save()
2192
 *
2193
 * @param array $data Record data to save. This should be a numerically-indexed array
2194
 * @param array $options Options to use when saving record data, See $options above.
2195
 * @return mixed If atomic: True on success, or false on failure.
2196
 *    Otherwise: array similar to the $data array passed, but values are set to true/false
2197
 *    depending on whether each record saved successfully.
2198
 * @link http://book.cakephp.org/2.0/en/models/saving-your-data.html#model-savemany-array-data-null-array-options-array
2199
 */
2200
	public function saveMany($data = null, $options = array()) {
2201
		if (empty($data)) {
2202
			$data = $this->data;
2203
		}
2204
 
2205
		$options = array_merge(array('validate' => 'first', 'atomic' => true, 'deep' => false), $options);
2206
		$this->validationErrors = $validationErrors = array();
2207
 
2208
		if (empty($data) && $options['validate'] !== false) {
2209
			$result = $this->save($data, $options);
2210
			if (!$options['atomic']) {
2211
				return array(!empty($result));
2212
			}
2213
 
2214
			return !empty($result);
2215
		}
2216
 
2217
		if ($options['validate'] === 'first') {
2218
			$validates = $this->validateMany($data, $options);
2219
			if ((!$validates && $options['atomic']) || (!$options['atomic'] && in_array(false, $validates, true))) {
2220
				return $validates;
2221
			}
2222
			$options['validate'] = false;
2223
		}
2224
 
2225
		if ($options['atomic']) {
2226
			$db = $this->getDataSource();
2227
			$transactionBegun = $db->begin();
2228
		}
2229
 
2230
		$return = array();
2231
		foreach ($data as $key => $record) {
2232
			$validates = $this->create(null) !== null;
2233
			$saved = false;
2234
			if ($validates) {
2235
				if ($options['deep']) {
2236
					$saved = $this->saveAssociated($record, array_merge($options, array('atomic' => false)));
2237
				} else {
2238
					$saved = $this->save($record, $options);
2239
				}
2240
			}
2241
 
2242
			$validates = ($validates && ($saved === true || (is_array($saved) && !in_array(false, $saved, true))));
2243
			if (!$validates) {
2244
				$validationErrors[$key] = $this->validationErrors;
2245
			}
2246
 
2247
			if (!$options['atomic']) {
2248
				$return[$key] = $validates;
2249
			} elseif (!$validates) {
2250
				break;
2251
			}
2252
		}
2253
 
2254
		$this->validationErrors = $validationErrors;
2255
 
2256
		if (!$options['atomic']) {
2257
			return $return;
2258
		}
2259
 
2260
		if ($validates) {
2261
			if ($transactionBegun) {
2262
				return $db->commit() !== false;
2263
			}
2264
			return true;
2265
		}
2266
 
2267
		$db->rollback();
2268
		return false;
2269
	}
2270
 
2271
/**
2272
 * Validates multiple individual records for a single model
2273
 *
2274
 * #### Options
2275
 *
2276
 * - `atomic`: If true (default), returns boolean. If false returns array.
2277
 * - `fieldList`: Equivalent to the $fieldList parameter in Model::save()
2278
 * - `deep`: If set to true, all associated data will be validated as well.
2279
 *
2280
 * Warning: This method could potentially change the passed argument `$data`,
2281
 * If you do not want this to happen, make a copy of `$data` before passing it
2282
 * to this method
2283
 *
2284
 * @param array $data Record data to validate. This should be a numerically-indexed array
2285
 * @param array $options Options to use when validating record data (see above), See also $options of validates().
2286
 * @return boolean|array If atomic: True on success, or false on failure.
2287
 *    Otherwise: array similar to the $data array passed, but values are set to true/false
2288
 *    depending on whether each record validated successfully.
2289
 */
2290
	public function validateMany(&$data, $options = array()) {
2291
		return $this->validator()->validateMany($data, $options);
2292
	}
2293
 
2294
/**
2295
 * Saves a single record, as well as all its directly associated records.
2296
 *
2297
 * #### Options
2298
 *
2299
 * - `validate`: Set to `false` to disable validation, `true` to validate each record before saving,
2300
 *   'first' to validate *all* records before any are saved(default),
2301
 * - `atomic`: If true (default), will attempt to save all records in a single transaction.
2302
 *   Should be set to false if database/table does not support transactions.
2303
 * - `fieldList`: Equivalent to the $fieldList parameter in Model::save().
2304
 *   It should be an associate array with model name as key and array of fields as value. Eg.
2305
 *   {{{
2306
 *   array(
2307
 *       'SomeModel' => array('field'),
2308
 *       'AssociatedModel' => array('field', 'otherfield')
2309
 *   )
2310
 *   }}}
2311
 * - `deep`: If set to true, not only directly associated data is saved, but deeper nested associated data as well.
2312
 * - `callbacks`: See Model::save()
2313
 * - `counterCache`: See Model::save()
2314
 *
2315
 * @param array $data Record data to save. This should be an array indexed by association name.
2316
 * @param array $options Options to use when saving record data, See $options above.
2317
 * @return mixed If atomic: True on success, or false on failure.
2318
 *    Otherwise: array similar to the $data array passed, but values are set to true/false
2319
 *    depending on whether each record saved successfully.
2320
 * @link http://book.cakephp.org/2.0/en/models/saving-your-data.html#model-saveassociated-array-data-null-array-options-array
2321
 */
2322
	public function saveAssociated($data = null, $options = array()) {
2323
		if (empty($data)) {
2324
			$data = $this->data;
2325
		}
2326
 
2327
		$options = array_merge(array('validate' => 'first', 'atomic' => true, 'deep' => false), $options);
2328
		$this->validationErrors = $validationErrors = array();
2329
 
2330
		if (empty($data) && $options['validate'] !== false) {
2331
			$result = $this->save($data, $options);
2332
			if (!$options['atomic']) {
2333
				return array(!empty($result));
2334
			}
2335
 
2336
			return !empty($result);
2337
		}
2338
 
2339
		if ($options['validate'] === 'first') {
2340
			$validates = $this->validateAssociated($data, $options);
2341
			if ((!$validates && $options['atomic']) || (!$options['atomic'] && in_array(false, Hash::flatten($validates), true))) {
2342
				return $validates;
2343
			}
2344
 
2345
			$options['validate'] = false;
2346
		}
2347
 
2348
		if ($options['atomic']) {
2349
			$db = $this->getDataSource();
2350
			$transactionBegun = $db->begin();
2351
		}
2352
 
2353
		$associations = $this->getAssociated();
2354
		$return = array();
2355
		$validates = true;
2356
		foreach ($data as $association => $values) {
2357
			$isEmpty = empty($values) || (isset($values[$association]) && empty($values[$association]));
2358
			if ($isEmpty || !isset($associations[$association]) || $associations[$association] !== 'belongsTo') {
2359
				continue;
2360
			}
2361
 
2362
			$Model = $this->{$association};
2363
 
2364
			$validates = $Model->create(null) !== null;
2365
			$saved = false;
2366
			if ($validates) {
2367
				if ($options['deep']) {
2368
					$saved = $Model->saveAssociated($values, array_merge($options, array('atomic' => false)));
2369
				} else {
2370
					$saved = $Model->save($values, array_merge($options, array('atomic' => false)));
2371
				}
2372
				$validates = ($saved === true || (is_array($saved) && !in_array(false, $saved, true)));
2373
			}
2374
 
2375
			if ($validates) {
2376
				$key = $this->belongsTo[$association]['foreignKey'];
2377
				if (isset($data[$this->alias])) {
2378
					$data[$this->alias][$key] = $Model->id;
2379
				} else {
2380
					$data = array_merge(array($key => $Model->id), $data, array($key => $Model->id));
2381
				}
2382
				$options = $this->_addToWhiteList($key, $options);
2383
			} else {
2384
				$validationErrors[$association] = $Model->validationErrors;
2385
			}
2386
 
2387
			$return[$association] = $validates;
2388
		}
2389
 
2390
		if ($validates && !($this->create(null) !== null && $this->save($data, $options))) {
2391
			$validationErrors[$this->alias] = $this->validationErrors;
2392
			$validates = false;
2393
		}
2394
		$return[$this->alias] = $validates;
2395
 
2396
		foreach ($data as $association => $values) {
2397
			if (!$validates) {
2398
				break;
2399
			}
2400
 
2401
			$isEmpty = empty($values) || (isset($values[$association]) && empty($values[$association]));
2402
			if ($isEmpty || !isset($associations[$association])) {
2403
				continue;
2404
			}
2405
 
2406
			$Model = $this->{$association};
2407
 
2408
			$type = $associations[$association];
2409
			$key = $this->{$type}[$association]['foreignKey'];
2410
			switch ($type) {
2411
				case 'hasOne':
2412
					if (isset($values[$association])) {
2413
						$values[$association][$key] = $this->id;
2414
					} else {
2415
						$values = array_merge(array($key => $this->id), $values, array($key => $this->id));
2416
					}
2417
 
2418
					$validates = $Model->create(null) !== null;
2419
					$saved = false;
2420
 
2421
					if ($validates) {
2422
						$options = $Model->_addToWhiteList($key, $options);
2423
						if ($options['deep']) {
2424
							$saved = $Model->saveAssociated($values, array_merge($options, array('atomic' => false)));
2425
						} else {
2426
							$saved = $Model->save($values, $options);
2427
						}
2428
					}
2429
 
2430
					$validates = ($validates && ($saved === true || (is_array($saved) && !in_array(false, $saved, true))));
2431
					if (!$validates) {
2432
						$validationErrors[$association] = $Model->validationErrors;
2433
					}
2434
 
2435
					$return[$association] = $validates;
2436
					break;
2437
				case 'hasMany':
2438
					foreach ($values as $i => $value) {
2439
						if (isset($values[$i][$association])) {
2440
							$values[$i][$association][$key] = $this->id;
2441
						} else {
2442
							$values[$i] = array_merge(array($key => $this->id), $value, array($key => $this->id));
2443
						}
2444
					}
2445
 
2446
					$options = $Model->_addToWhiteList($key, $options);
2447
					$_return = $Model->saveMany($values, array_merge($options, array('atomic' => false)));
2448
					if (in_array(false, $_return, true)) {
2449
						$validationErrors[$association] = $Model->validationErrors;
2450
						$validates = false;
2451
					}
2452
 
2453
					$return[$association] = $_return;
2454
					break;
2455
			}
2456
		}
2457
		$this->validationErrors = $validationErrors;
2458
 
2459
		if (isset($validationErrors[$this->alias])) {
2460
			$this->validationErrors = $validationErrors[$this->alias];
2461
			unset($validationErrors[$this->alias]);
2462
			$this->validationErrors = array_merge($this->validationErrors, $validationErrors);
2463
		}
2464
 
2465
		if (!$options['atomic']) {
2466
			return $return;
2467
		}
2468
		if ($validates) {
2469
			if ($transactionBegun) {
2470
				return $db->commit() !== false;
2471
			}
2472
 
2473
			return true;
2474
		}
2475
 
2476
		$db->rollback();
2477
		return false;
2478
	}
2479
 
2480
/**
2481
 * Helper method for saveAll() and friends, to add foreign key to fieldlist
2482
 *
2483
 * @param string $key fieldname to be added to list
2484
 * @param array $options
2485
 * @return array $options
2486
 */
2487
	protected function _addToWhiteList($key, $options) {
2488
		if (empty($options['fieldList']) && $this->whitelist && !in_array($key, $this->whitelist)) {
2489
			$options['fieldList'][$this->alias] = $this->whitelist;
2490
			$options['fieldList'][$this->alias][] = $key;
2491
			return $options;
2492
		}
2493
 
2494
		if (!empty($options['fieldList'][$this->alias]) && is_array($options['fieldList'][$this->alias])) {
2495
			$options['fieldList'][$this->alias][] = $key;
2496
			return $options;
2497
		}
2498
 
2499
		if (!empty($options['fieldList']) && is_array($options['fieldList']) && Hash::dimensions($options['fieldList']) < 2) {
2500
			$options['fieldList'][] = $key;
2501
		}
2502
 
2503
		return $options;
2504
	}
2505
 
2506
/**
2507
 * Validates a single record, as well as all its directly associated records.
2508
 *
2509
 * #### Options
2510
 *
2511
 * - `atomic`: If true (default), returns boolean. If false returns array.
2512
 * - `fieldList`: Equivalent to the $fieldList parameter in Model::save()
2513
 * - `deep`: If set to true, not only directly associated data , but deeper nested associated data is validated as well.
2514
 *
2515
 * Warning: This method could potentially change the passed argument `$data`,
2516
 * If you do not want this to happen, make a copy of `$data` before passing it
2517
 * to this method
2518
 *
2519
 * @param array $data Record data to validate. This should be an array indexed by association name.
2520
 * @param array $options Options to use when validating record data (see above), See also $options of validates().
2521
 * @return array|boolean If atomic: True on success, or false on failure.
2522
 *    Otherwise: array similar to the $data array passed, but values are set to true/false
2523
 *    depending on whether each record validated successfully.
2524
 */
2525
	public function validateAssociated(&$data, $options = array()) {
2526
		return $this->validator()->validateAssociated($data, $options);
2527
	}
2528
 
2529
/**
2530
 * Updates multiple model records based on a set of conditions.
2531
 *
2532
 * @param array $fields Set of fields and values, indexed by fields.
2533
 *    Fields are treated as SQL snippets, to insert literal values manually escape your data.
2534
 * @param mixed $conditions Conditions to match, true for all records
2535
 * @return boolean True on success, false on failure
2536
 * @link http://book.cakephp.org/2.0/en/models/saving-your-data.html#model-updateall-array-fields-array-conditions
2537
 */
2538
	public function updateAll($fields, $conditions = true) {
2539
		return $this->getDataSource()->update($this, $fields, null, $conditions);
2540
	}
2541
 
2542
/**
2543
 * Removes record for given ID. If no ID is given, the current ID is used. Returns true on success.
2544
 *
2545
 * @param integer|string $id ID of record to delete
2546
 * @param boolean $cascade Set to true to delete records that depend on this record
2547
 * @return boolean True on success
2548
 * @link http://book.cakephp.org/2.0/en/models/deleting-data.html
2549
 */
2550
	public function delete($id = null, $cascade = true) {
2551
		if (!empty($id)) {
2552
			$this->id = $id;
2553
		}
2554
 
2555
		$id = $this->id;
2556
 
2557
		$event = new CakeEvent('Model.beforeDelete', $this, array($cascade));
2558
		list($event->break, $event->breakOn) = array(true, array(false, null));
2559
		$this->getEventManager()->dispatch($event);
2560
		if ($event->isStopped()) {
2561
			return false;
2562
		}
2563
 
2564
		if (!$this->exists()) {
2565
			return false;
2566
		}
2567
 
2568
		$this->_deleteDependent($id, $cascade);
2569
		$this->_deleteLinks($id);
2570
		$this->id = $id;
2571
 
2572
		if (!empty($this->belongsTo)) {
2573
			foreach ($this->belongsTo as $assoc) {
2574
				if (empty($assoc['counterCache'])) {
2575
					continue;
2576
				}
2577
 
2578
				$keys = $this->find('first', array(
2579
					'fields' => $this->_collectForeignKeys(),
2580
					'conditions' => array($this->alias . '.' . $this->primaryKey => $id),
2581
					'recursive' => -1,
2582
					'callbacks' => false
2583
				));
2584
				break;
2585
			}
2586
		}
2587
 
2588
		if (!$this->getDataSource()->delete($this, array($this->alias . '.' . $this->primaryKey => $id))) {
2589
			return false;
2590
		}
2591
 
2592
		if (!empty($keys[$this->alias])) {
2593
			$this->updateCounterCache($keys[$this->alias]);
2594
		}
2595
 
2596
		$this->getEventManager()->dispatch(new CakeEvent('Model.afterDelete', $this));
2597
		$this->_clearCache();
2598
		$this->id = false;
2599
 
2600
		return true;
2601
	}
2602
 
2603
/**
2604
 * Cascades model deletes through associated hasMany and hasOne child records.
2605
 *
2606
 * @param string $id ID of record that was deleted
2607
 * @param boolean $cascade Set to true to delete records that depend on this record
2608
 * @return void
2609
 */
2610
	protected function _deleteDependent($id, $cascade) {
2611
		if ($cascade !== true) {
2612
			return;
2613
		}
2614
 
2615
		if (!empty($this->__backAssociation)) {
2616
			$savedAssociations = $this->__backAssociation;
2617
			$this->__backAssociation = array();
2618
		}
2619
 
2620
		foreach (array_merge($this->hasMany, $this->hasOne) as $assoc => $data) {
2621
			if ($data['dependent'] !== true) {
2622
				continue;
2623
			}
2624
 
2625
			$Model = $this->{$assoc};
2626
 
2627
			if ($data['foreignKey'] === false && $data['conditions'] && in_array($this->name, $Model->getAssociated('belongsTo'))) {
2628
				$Model->recursive = 0;
2629
				$conditions = array($this->escapeField(null, $this->name) => $id);
2630
			} else {
2631
				$Model->recursive = -1;
2632
				$conditions = array($Model->escapeField($data['foreignKey']) => $id);
2633
				if ($data['conditions']) {
2634
					$conditions = array_merge((array)$data['conditions'], $conditions);
2635
				}
2636
			}
2637
 
2638
			if (isset($data['exclusive']) && $data['exclusive']) {
2639
				$Model->deleteAll($conditions);
2640
			} else {
2641
				$records = $Model->find('all', array(
2642
					'conditions' => $conditions, 'fields' => $Model->primaryKey
2643
				));
2644
 
2645
				if (!empty($records)) {
2646
					foreach ($records as $record) {
2647
						$Model->delete($record[$Model->alias][$Model->primaryKey]);
2648
					}
2649
				}
2650
			}
2651
		}
2652
 
2653
		if (isset($savedAssociations)) {
2654
			$this->__backAssociation = $savedAssociations;
2655
		}
2656
	}
2657
 
2658
/**
2659
 * Cascades model deletes through HABTM join keys.
2660
 *
2661
 * @param string $id ID of record that was deleted
2662
 * @return void
2663
 */
2664
	protected function _deleteLinks($id) {
2665
		foreach ($this->hasAndBelongsToMany as $data) {
2666
			list(, $joinModel) = pluginSplit($data['with']);
2667
			$Model = $this->{$joinModel};
2668
			$records = $Model->find('all', array(
2669
				'conditions' => array($Model->escapeField($data['foreignKey']) => $id),
2670
				'fields' => $Model->primaryKey,
2671
				'recursive' => -1,
2672
				'callbacks' => false
2673
			));
2674
 
2675
			if (!empty($records)) {
2676
				foreach ($records as $record) {
2677
					$Model->delete($record[$Model->alias][$Model->primaryKey]);
2678
				}
2679
			}
2680
		}
2681
	}
2682
 
2683
/**
2684
 * Deletes multiple model records based on a set of conditions.
2685
 *
2686
 * @param mixed $conditions Conditions to match
2687
 * @param boolean $cascade Set to true to delete records that depend on this record
2688
 * @param boolean $callbacks Run callbacks
2689
 * @return boolean True on success, false on failure
2690
 * @link http://book.cakephp.org/2.0/en/models/deleting-data.html#deleteall
2691
 */
2692
	public function deleteAll($conditions, $cascade = true, $callbacks = false) {
2693
		if (empty($conditions)) {
2694
			return false;
2695
		}
2696
 
2697
		$db = $this->getDataSource();
2698
 
2699
		if (!$cascade && !$callbacks) {
2700
			return $db->delete($this, $conditions);
2701
		}
2702
 
2703
		$ids = $this->find('all', array_merge(array(
2704
			'fields' => "DISTINCT {$this->alias}.{$this->primaryKey}",
2705
			'order' => false,
2706
			'recursive' => 0), compact('conditions'))
2707
		);
2708
 
2709
		if ($ids === false || $ids === null) {
2710
			return false;
2711
		}
2712
 
2713
		$ids = Hash::extract($ids, "{n}.{$this->alias}.{$this->primaryKey}");
2714
		if (empty($ids)) {
2715
			return true;
2716
		}
2717
 
2718
		if ($callbacks) {
2719
			$_id = $this->id;
2720
			$result = true;
2721
			foreach ($ids as $id) {
2722
				$result = $result && $this->delete($id, $cascade);
2723
			}
2724
 
2725
			$this->id = $_id;
2726
			return $result;
2727
		}
2728
 
2729
		foreach ($ids as $id) {
2730
			$this->_deleteLinks($id);
2731
			if ($cascade) {
2732
				$this->_deleteDependent($id, $cascade);
2733
			}
2734
		}
2735
 
2736
		return $db->delete($this, array($this->alias . '.' . $this->primaryKey => $ids));
2737
	}
2738
 
2739
/**
2740
 * Collects foreign keys from associations.
2741
 *
2742
 * @param string $type
2743
 * @return array
2744
 */
2745
	protected function _collectForeignKeys($type = 'belongsTo') {
2746
		$result = array();
2747
 
2748
		foreach ($this->{$type} as $assoc => $data) {
2749
			if (isset($data['foreignKey']) && is_string($data['foreignKey'])) {
2750
				$result[$assoc] = $data['foreignKey'];
2751
			}
2752
		}
2753
 
2754
		return $result;
2755
	}
2756
 
2757
/**
2758
 * Returns true if a record with particular ID exists.
2759
 *
2760
 * If $id is not passed it calls `Model::getID()` to obtain the current record ID,
2761
 * and then performs a `Model::find('count')` on the currently configured datasource
2762
 * to ascertain the existence of the record in persistent storage.
2763
 *
2764
 * @param integer|string $id ID of record to check for existence
2765
 * @return boolean True if such a record exists
2766
 */
2767
	public function exists($id = null) {
2768
		if ($id === null) {
2769
			$id = $this->getID();
2770
		}
2771
 
2772
		if ($id === false) {
2773
			return false;
2774
		}
2775
 
2776
		return (bool)$this->find('count', array(
2777
			'conditions' => array(
2778
				$this->alias . '.' . $this->primaryKey => $id
2779
			),
2780
			'recursive' => -1,
2781
			'callbacks' => false
2782
		));
2783
	}
2784
 
2785
/**
2786
 * Returns true if a record that meets given conditions exists.
2787
 *
2788
 * @param array $conditions SQL conditions array
2789
 * @return boolean True if such a record exists
2790
 */
2791
	public function hasAny($conditions = null) {
2792
		return (bool)$this->find('count', array('conditions' => $conditions, 'recursive' => -1));
2793
	}
2794
 
2795
/**
2796
 * Queries the datasource and returns a result set array.
2797
 *
2798
 * Used to perform find operations, where the first argument is type of find operation to perform
2799
 * (all / first / count / neighbors / list / threaded),
2800
 * second parameter options for finding (indexed array, including: 'conditions', 'limit',
2801
 * 'recursive', 'page', 'fields', 'offset', 'order', 'callbacks')
2802
 *
2803
 * Eg:
2804
 * {{{
2805
 * $model->find('all', array(
2806
 *   'conditions' => array('name' => 'Thomas Anderson'),
2807
 *   'fields' => array('name', 'email'),
2808
 *   'order' => 'field3 DESC',
2809
 *   'recursive' => 2,
2810
 *   'group' => 'type',
2811
 *   'callbacks' => false,
2812
 * ));
2813
 * }}}
2814
 *
2815
 * In addition to the standard query keys above, you can provide Datasource, and behavior specific
2816
 * keys. For example, when using a SQL based datasource you can use the joins key to specify additional
2817
 * joins that should be part of the query.
2818
 *
2819
 * {{{
2820
 * $model->find('all', array(
2821
 *   'conditions' => array('name' => 'Thomas Anderson'),
2822
 *   'joins' => array(
2823
 *     array(
2824
 *       'alias' => 'Thought',
2825
 *       'table' => 'thoughts',
2826
 *       'type' => 'LEFT',
2827
 *       'conditions' => '`Thought`.`person_id` = `Person`.`id`'
2828
 *     )
2829
 *   )
2830
 * ));
2831
 * }}}
2832
 *
2833
 * ### Disabling callbacks
2834
 *
2835
 * The `callbacks` key allows you to disable or specify the callbacks that should be run. To
2836
 * disable beforeFind & afterFind callbacks set `'callbacks' => false` in your options. You can
2837
 * also set the callbacks option to 'before' or 'after' to enable only the specified callback.
2838
 *
2839
 * ### Adding new find types
2840
 *
2841
 * Behaviors and find types can also define custom finder keys which are passed into find().
2842
 * See the documentation for custom find types
2843
 * (http://book.cakephp.org/2.0/en/models/retrieving-your-data.html#creating-custom-find-types)
2844
 * for how to implement custom find types.
2845
 *
2846
 * Specifying 'fields' for notation 'list':
2847
 *
2848
 * - If no fields are specified, then 'id' is used for key and 'model->displayField' is used for value.
2849
 * - If a single field is specified, 'id' is used for key and specified field is used for value.
2850
 * - If three fields are specified, they are used (in order) for key, value and group.
2851
 * - Otherwise, first and second fields are used for key and value.
2852
 *
2853
 * Note: find(list) + database views have issues with MySQL 5.0. Try upgrading to MySQL 5.1 if you
2854
 * have issues with database views.
2855
 *
2856
 * Note: find(count) has its own return values.
2857
 *
2858
 * @param string $type Type of find operation (all / first / count / neighbors / list / threaded)
2859
 * @param array $query Option fields (conditions / fields / joins / limit / offset / order / page / group / callbacks)
2860
 * @return array Array of records, or Null on failure.
2861
 * @link http://book.cakephp.org/2.0/en/models/retrieving-your-data.html
2862
 */
2863
	public function find($type = 'first', $query = array()) {
2864
		$this->findQueryType = $type;
2865
		$this->id = $this->getID();
2866
 
2867
		$query = $this->buildQuery($type, $query);
2868
		if ($query === null) {
2869
			return null;
2870
		}
2871
 
2872
		return $this->_readDataSource($type, $query);
2873
	}
2874
 
2875
/**
2876
 * Read from the datasource
2877
 *
2878
 * Model::_readDataSource() is used by all find() calls to read from the data source and can be overloaded to allow
2879
 * caching of datasource calls.
2880
 *
2881
 * {{{
2882
 * protected function _readDataSource($type, $query) {
2883
 * 		$cacheName = md5(json_encode($query));
2884
 * 		$cache = Cache::read($cacheName, 'cache-config-name');
2885
 * 		if ($cache !== false) {
2886
 * 			return $cache;
2887
 * 		}
2888
 *
2889
 * 		$results = parent::_readDataSource($type, $query);
2890
 * 		Cache::write($cacheName, $results, 'cache-config-name');
2891
 * 		return $results;
2892
 * }
2893
 * }}}
2894
 *
2895
 * @param string $type Type of find operation (all / first / count / neighbors / list / threaded)
2896
 * @param array $query Option fields (conditions / fields / joins / limit / offset / order / page / group / callbacks)
2897
 * @return array
2898
 */
2899
	protected function _readDataSource($type, $query) {
2900
		$results = $this->getDataSource()->read($this, $query);
2901
		$this->resetAssociations();
2902
 
2903
		if ($query['callbacks'] === true || $query['callbacks'] === 'after') {
2904
			$results = $this->_filterResults($results);
2905
		}
2906
 
2907
		$this->findQueryType = null;
2908
 
2909
		if ($this->findMethods[$type] === true) {
2910
			return $this->{'_find' . ucfirst($type)}('after', $query, $results);
2911
		}
2912
	}
2913
 
2914
/**
2915
 * Builds the query array that is used by the data source to generate the query to fetch the data.
2916
 *
2917
 * @param string $type Type of find operation (all / first / count / neighbors / list / threaded)
2918
 * @param array $query Option fields (conditions / fields / joins / limit / offset / order / page / group / callbacks)
2919
 * @return array Query array or null if it could not be build for some reasons
2920
 * @see Model::find()
2921
 */
2922
	public function buildQuery($type = 'first', $query = array()) {
2923
		$query = array_merge(
2924
			array(
2925
				'conditions' => null, 'fields' => null, 'joins' => array(), 'limit' => null,
2926
				'offset' => null, 'order' => null, 'page' => 1, 'group' => null, 'callbacks' => true,
2927
			),
2928
			(array)$query
2929
		);
2930
 
2931
		if ($this->findMethods[$type] === true) {
2932
			$query = $this->{'_find' . ucfirst($type)}('before', $query);
2933
		}
2934
 
2935
		if (!is_numeric($query['page']) || intval($query['page']) < 1) {
2936
			$query['page'] = 1;
2937
		}
2938
 
2939
		if ($query['page'] > 1 && !empty($query['limit'])) {
2940
			$query['offset'] = ($query['page'] - 1) * $query['limit'];
2941
		}
2942
 
2943
		if ($query['order'] === null && $this->order !== null) {
2944
			$query['order'] = $this->order;
2945
		}
2946
 
2947
		$query['order'] = array($query['order']);
2948
 
2949
		if ($query['callbacks'] === true || $query['callbacks'] === 'before') {
2950
			$event = new CakeEvent('Model.beforeFind', $this, array($query));
2951
			list($event->break, $event->breakOn, $event->modParams) = array(true, array(false, null), 0);
2952
			$this->getEventManager()->dispatch($event);
2953
 
2954
			if ($event->isStopped()) {
2955
				return null;
2956
			}
2957
 
2958
			$query = $event->result === true ? $event->data[0] : $event->result;
2959
		}
2960
 
2961
		return $query;
2962
	}
2963
 
2964
/**
2965
 * Handles the before/after filter logic for find('all') operations. Only called by Model::find().
2966
 *
2967
 * @param string $state Either "before" or "after"
2968
 * @param array $query
2969
 * @param array $results
2970
 * @return array
2971
 * @see Model::find()
2972
 */
2973
	protected function _findAll($state, $query, $results = array()) {
2974
		if ($state === 'before') {
2975
			return $query;
2976
		}
2977
 
2978
		return $results;
2979
	}
2980
 
2981
/**
2982
 * Handles the before/after filter logic for find('first') operations. Only called by Model::find().
2983
 *
2984
 * @param string $state Either "before" or "after"
2985
 * @param array $query
2986
 * @param array $results
2987
 * @return array
2988
 * @see Model::find()
2989
 */
2990
	protected function _findFirst($state, $query, $results = array()) {
2991
		if ($state === 'before') {
2992
			$query['limit'] = 1;
2993
			return $query;
2994
		}
2995
 
2996
		if (empty($results[0])) {
2997
			return array();
2998
		}
2999
 
3000
		return $results[0];
3001
	}
3002
 
3003
/**
3004
 * Handles the before/after filter logic for find('count') operations. Only called by Model::find().
3005
 *
3006
 * @param string $state Either "before" or "after"
3007
 * @param array $query
3008
 * @param array $results
3009
 * @return integer The number of records found, or false
3010
 * @see Model::find()
3011
 */
3012
	protected function _findCount($state, $query, $results = array()) {
3013
		if ($state === 'before') {
3014
			if (!empty($query['type']) && isset($this->findMethods[$query['type']]) && $query['type'] !== 'count') {
3015
				$query['operation'] = 'count';
3016
				$query = $this->{'_find' . ucfirst($query['type'])}('before', $query);
3017
			}
3018
 
3019
			$db = $this->getDataSource();
3020
			$query['order'] = false;
3021
			if (!method_exists($db, 'calculate')) {
3022
				return $query;
3023
			}
3024
 
3025
			if (!empty($query['fields']) && is_array($query['fields'])) {
3026
				if (!preg_match('/^count/i', current($query['fields']))) {
3027
					unset($query['fields']);
3028
				}
3029
			}
3030
 
3031
			if (empty($query['fields'])) {
3032
				$query['fields'] = $db->calculate($this, 'count');
3033
			} elseif (method_exists($db, 'expression') && is_string($query['fields']) && !preg_match('/count/i', $query['fields'])) {
3034
				$query['fields'] = $db->calculate($this, 'count', array(
3035
					$db->expression($query['fields']), 'count'
3036
				));
3037
			}
3038
 
3039
			return $query;
3040
		}
3041
 
3042
		foreach (array(0, $this->alias) as $key) {
3043
			if (isset($results[0][$key]['count'])) {
3044
				if ($query['group']) {
3045
					return count($results);
3046
				}
3047
 
3048
				return intval($results[0][$key]['count']);
3049
			}
3050
		}
3051
 
3052
		return false;
3053
	}
3054
 
3055
/**
3056
 * Handles the before/after filter logic for find('list') operations. Only called by Model::find().
3057
 *
3058
 * @param string $state Either "before" or "after"
3059
 * @param array $query
3060
 * @param array $results
3061
 * @return array Key/value pairs of primary keys/display field values of all records found
3062
 * @see Model::find()
3063
 */
3064
	protected function _findList($state, $query, $results = array()) {
3065
		if ($state === 'before') {
3066
			if (empty($query['fields'])) {
3067
				$query['fields'] = array("{$this->alias}.{$this->primaryKey}", "{$this->alias}.{$this->displayField}");
3068
				$list = array("{n}.{$this->alias}.{$this->primaryKey}", "{n}.{$this->alias}.{$this->displayField}", null);
3069
			} else {
3070
				if (!is_array($query['fields'])) {
3071
					$query['fields'] = String::tokenize($query['fields']);
3072
				}
3073
 
3074
				if (count($query['fields']) === 1) {
3075
					if (strpos($query['fields'][0], '.') === false) {
3076
						$query['fields'][0] = $this->alias . '.' . $query['fields'][0];
3077
					}
3078
 
3079
					$list = array("{n}.{$this->alias}.{$this->primaryKey}", '{n}.' . $query['fields'][0], null);
3080
					$query['fields'] = array("{$this->alias}.{$this->primaryKey}", $query['fields'][0]);
3081
				} elseif (count($query['fields']) === 3) {
3082
					for ($i = 0; $i < 3; $i++) {
3083
						if (strpos($query['fields'][$i], '.') === false) {
3084
							$query['fields'][$i] = $this->alias . '.' . $query['fields'][$i];
3085
						}
3086
					}
3087
 
3088
					$list = array('{n}.' . $query['fields'][0], '{n}.' . $query['fields'][1], '{n}.' . $query['fields'][2]);
3089
				} else {
3090
					for ($i = 0; $i < 2; $i++) {
3091
						if (strpos($query['fields'][$i], '.') === false) {
3092
							$query['fields'][$i] = $this->alias . '.' . $query['fields'][$i];
3093
						}
3094
					}
3095
 
3096
					$list = array('{n}.' . $query['fields'][0], '{n}.' . $query['fields'][1], null);
3097
				}
3098
			}
3099
 
3100
			if (!isset($query['recursive']) || $query['recursive'] === null) {
3101
				$query['recursive'] = -1;
3102
			}
3103
			list($query['list']['keyPath'], $query['list']['valuePath'], $query['list']['groupPath']) = $list;
3104
 
3105
			return $query;
3106
		}
3107
 
3108
		if (empty($results)) {
3109
			return array();
3110
		}
3111
 
3112
		return Hash::combine($results, $query['list']['keyPath'], $query['list']['valuePath'], $query['list']['groupPath']);
3113
	}
3114
 
3115
/**
3116
 * Detects the previous field's value, then uses logic to find the 'wrapping'
3117
 * rows and return them.
3118
 *
3119
 * @param string $state Either "before" or "after"
3120
 * @param array $query
3121
 * @param array $results
3122
 * @return array
3123
 */
3124
	protected function _findNeighbors($state, $query, $results = array()) {
3125
		extract($query);
3126
 
3127
		if ($state === 'before') {
3128
			$conditions = (array)$conditions;
3129
			if (isset($field) && isset($value)) {
3130
				if (strpos($field, '.') === false) {
3131
					$field = $this->alias . '.' . $field;
3132
				}
3133
			} else {
3134
				$field = $this->alias . '.' . $this->primaryKey;
3135
				$value = $this->id;
3136
			}
3137
 
3138
			$query['conditions'] = array_merge($conditions, array($field . ' <' => $value));
3139
			$query['order'] = $field . ' DESC';
3140
			$query['limit'] = 1;
3141
			$query['field'] = $field;
3142
			$query['value'] = $value;
3143
 
3144
			return $query;
3145
		}
3146
 
3147
		unset($query['conditions'][$field . ' <']);
3148
		$return = array();
3149
		if (isset($results[0])) {
3150
			$prevVal = Hash::get($results[0], $field);
3151
			$query['conditions'][$field . ' >='] = $prevVal;
3152
			$query['conditions'][$field . ' !='] = $value;
3153
			$query['limit'] = 2;
3154
		} else {
3155
			$return['prev'] = null;
3156
			$query['conditions'][$field . ' >'] = $value;
3157
			$query['limit'] = 1;
3158
		}
3159
 
3160
		$query['order'] = $field . ' ASC';
3161
		$neighbors = $this->find('all', $query);
3162
		if (!array_key_exists('prev', $return)) {
3163
			$return['prev'] = isset($neighbors[0]) ? $neighbors[0] : null;
3164
		}
3165
 
3166
		if (count($neighbors) === 2) {
3167
			$return['next'] = $neighbors[1];
3168
		} elseif (count($neighbors) === 1 && !$return['prev']) {
3169
			$return['next'] = $neighbors[0];
3170
		} else {
3171
			$return['next'] = null;
3172
		}
3173
 
3174
		return $return;
3175
	}
3176
 
3177
/**
3178
 * In the event of ambiguous results returned (multiple top level results, with different parent_ids)
3179
 * top level results with different parent_ids to the first result will be dropped
3180
 *
3181
 * @param string $state
3182
 * @param mixed $query
3183
 * @param array $results
3184
 * @return array Threaded results
3185
 */
3186
	protected function _findThreaded($state, $query, $results = array()) {
3187
		if ($state === 'before') {
3188
			return $query;
3189
		}
3190
 
3191
		$parent = 'parent_id';
3192
		if (isset($query['parent'])) {
3193
			$parent = $query['parent'];
3194
		}
3195
 
3196
		return Hash::nest($results, array(
3197
			'idPath' => '{n}.' . $this->alias . '.' . $this->primaryKey,
3198
			'parentPath' => '{n}.' . $this->alias . '.' . $parent
3199
		));
3200
	}
3201
 
3202
/**
3203
 * Passes query results through model and behavior afterFind() methods.
3204
 *
3205
 * @param array $results Results to filter
3206
 * @param boolean $primary If this is the primary model results (results from model where the find operation was performed)
3207
 * @return array Set of filtered results
3208
 */
3209
	protected function _filterResults($results, $primary = true) {
3210
		$event = new CakeEvent('Model.afterFind', $this, array($results, $primary));
3211
		$event->modParams = 0;
3212
		$this->getEventManager()->dispatch($event);
3213
		return $event->result;
3214
	}
3215
 
3216
/**
3217
 * This resets the association arrays for the model back
3218
 * to those originally defined in the model. Normally called at the end
3219
 * of each call to Model::find()
3220
 *
3221
 * @return boolean Success
3222
 */
3223
	public function resetAssociations() {
3224
		if (!empty($this->__backAssociation)) {
3225
			foreach ($this->_associations as $type) {
3226
				if (isset($this->__backAssociation[$type])) {
3227
					$this->{$type} = $this->__backAssociation[$type];
3228
				}
3229
			}
3230
 
3231
			$this->__backAssociation = array();
3232
		}
3233
 
3234
		foreach ($this->_associations as $type) {
3235
			foreach ($this->{$type} as $key => $name) {
3236
				if (property_exists($this, $key) && !empty($this->{$key}->__backAssociation)) {
3237
					$this->{$key}->resetAssociations();
3238
				}
3239
			}
3240
		}
3241
 
3242
		$this->__backAssociation = array();
3243
		return true;
3244
	}
3245
 
3246
/**
3247
 * Returns false if any fields passed match any (by default, all if $or = false) of their matching values.
3248
 *
3249
 * @param array $fields Field/value pairs to search (if no values specified, they are pulled from $this->data)
3250
 * @param boolean $or If false, all fields specified must match in order for a false return value
3251
 * @return boolean False if any records matching any fields are found
3252
 */
3253
	public function isUnique($fields, $or = true) {
3254
		if (!is_array($fields)) {
3255
			$fields = func_get_args();
3256
			if (is_bool($fields[count($fields) - 1])) {
3257
				$or = $fields[count($fields) - 1];
3258
				unset($fields[count($fields) - 1]);
3259
			}
3260
		}
3261
 
3262
		foreach ($fields as $field => $value) {
3263
			if (is_numeric($field)) {
3264
				unset($fields[$field]);
3265
 
3266
				$field = $value;
3267
				$value = null;
3268
				if (isset($this->data[$this->alias][$field])) {
3269
					$value = $this->data[$this->alias][$field];
3270
				}
3271
			}
3272
 
3273
			if (strpos($field, '.') === false) {
3274
				unset($fields[$field]);
3275
				$fields[$this->alias . '.' . $field] = $value;
3276
			}
3277
		}
3278
 
3279
		if ($or) {
3280
			$fields = array('or' => $fields);
3281
		}
3282
 
3283
		if (!empty($this->id)) {
3284
			$fields[$this->alias . '.' . $this->primaryKey . ' !='] = $this->id;
3285
		}
3286
 
3287
		return !$this->find('count', array('conditions' => $fields, 'recursive' => -1));
3288
	}
3289
 
3290
/**
3291
 * Returns a resultset for a given SQL statement. Custom SQL queries should be performed with this method.
3292
 *
3293
 * @param string $sql SQL statement
3294
 * @param boolean|array $params Either a boolean to control query caching or an array of parameters
3295
 *    for use with prepared statement placeholders.
3296
 * @param boolean $cache If $params is provided, a boolean flag for enabling/disabled
3297
 *    query caching.
3298
 * @return mixed Resultset array or boolean indicating success / failure depending on the query executed
3299
 * @link http://book.cakephp.org/2.0/en/models/retrieving-your-data.html#model-query
3300
 */
3301
	public function query($sql) {
3302
		$params = func_get_args();
3303
		$db = $this->getDataSource();
3304
		return call_user_func_array(array(&$db, 'query'), $params);
3305
	}
3306
 
3307
/**
3308
 * Returns true if all fields pass validation. Will validate hasAndBelongsToMany associations
3309
 * that use the 'with' key as well. Since _saveMulti is incapable of exiting a save operation.
3310
 *
3311
 * Will validate the currently set data. Use Model::set() or Model::create() to set the active data.
3312
 *
3313
 * @param array $options An optional array of custom options to be made available in the beforeValidate callback
3314
 * @return boolean True if there are no errors
3315
 */
3316
	public function validates($options = array()) {
3317
		return $this->validator()->validates($options);
3318
	}
3319
 
3320
/**
3321
 * Returns an array of fields that have failed the validation of the current model.
3322
 *
3323
 * Additionally it populates the validationErrors property of the model with the same array.
3324
 *
3325
 * @param array|string $options An optional array of custom options to be made available in the beforeValidate callback
3326
 * @return array Array of invalid fields and their error messages
3327
 * @see Model::validates()
3328
 */
3329
	public function invalidFields($options = array()) {
3330
		return $this->validator()->errors($options);
3331
	}
3332
 
3333
/**
3334
 * Marks a field as invalid, optionally setting the name of validation
3335
 * rule (in case of multiple validation for field) that was broken.
3336
 *
3337
 * @param string $field The name of the field to invalidate
3338
 * @param mixed $value Name of validation rule that was not failed, or validation message to
3339
 *    be returned. If no validation key is provided, defaults to true.
3340
 * @return void
3341
 */
3342
	public function invalidate($field, $value = true) {
3343
		$this->validator()->invalidate($field, $value);
3344
	}
3345
 
3346
/**
3347
 * Returns true if given field name is a foreign key in this model.
3348
 *
3349
 * @param string $field Returns true if the input string ends in "_id"
3350
 * @return boolean True if the field is a foreign key listed in the belongsTo array.
3351
 */
3352
	public function isForeignKey($field) {
3353
		$foreignKeys = array();
3354
		if (!empty($this->belongsTo)) {
3355
			foreach ($this->belongsTo as $data) {
3356
				$foreignKeys[] = $data['foreignKey'];
3357
			}
3358
		}
3359
 
3360
		return in_array($field, $foreignKeys);
3361
	}
3362
 
3363
/**
3364
 * Escapes the field name and prepends the model name. Escaping is done according to the
3365
 * current database driver's rules.
3366
 *
3367
 * @param string $field Field to escape (e.g: id)
3368
 * @param string $alias Alias for the model (e.g: Post)
3369
 * @return string The name of the escaped field for this Model (i.e. id becomes `Post`.`id`).
3370
 */
3371
	public function escapeField($field = null, $alias = null) {
3372
		if (empty($alias)) {
3373
			$alias = $this->alias;
3374
		}
3375
 
3376
		if (empty($field)) {
3377
			$field = $this->primaryKey;
3378
		}
3379
 
3380
		$db = $this->getDataSource();
3381
		if (strpos($field, $db->name($alias) . '.') === 0) {
3382
			return $field;
3383
		}
3384
 
3385
		return $db->name($alias . '.' . $field);
3386
	}
3387
 
3388
/**
3389
 * Returns the current record's ID
3390
 *
3391
 * @param integer $list Index on which the composed ID is located
3392
 * @return mixed The ID of the current record, false if no ID
3393
 */
3394
	public function getID($list = 0) {
3395
		if (empty($this->id) || (is_array($this->id) && isset($this->id[0]) && empty($this->id[0]))) {
3396
			return false;
3397
		}
3398
 
3399
		if (!is_array($this->id)) {
3400
			return $this->id;
3401
		}
3402
 
3403
		if (isset($this->id[$list]) && !empty($this->id[$list])) {
3404
			return $this->id[$list];
3405
		}
3406
 
3407
		if (isset($this->id[$list])) {
3408
			return false;
3409
		}
3410
 
3411
		return current($this->id);
3412
	}
3413
 
3414
/**
3415
 * Returns the ID of the last record this model inserted.
3416
 *
3417
 * @return mixed Last inserted ID
3418
 */
3419
	public function getLastInsertID() {
3420
		return $this->getInsertID();
3421
	}
3422
 
3423
/**
3424
 * Returns the ID of the last record this model inserted.
3425
 *
3426
 * @return mixed Last inserted ID
3427
 */
3428
	public function getInsertID() {
3429
		return $this->_insertID;
3430
	}
3431
 
3432
/**
3433
 * Sets the ID of the last record this model inserted
3434
 *
3435
 * @param integer|string $id Last inserted ID
3436
 * @return void
3437
 */
3438
	public function setInsertID($id) {
3439
		$this->_insertID = $id;
3440
	}
3441
 
3442
/**
3443
 * Returns the number of rows returned from the last query.
3444
 *
3445
 * @return integer Number of rows
3446
 */
3447
	public function getNumRows() {
3448
		return $this->getDataSource()->lastNumRows();
3449
	}
3450
 
3451
/**
3452
 * Returns the number of rows affected by the last query.
3453
 *
3454
 * @return integer Number of rows
3455
 */
3456
	public function getAffectedRows() {
3457
		return $this->getDataSource()->lastAffected();
3458
	}
3459
 
3460
/**
3461
 * Sets the DataSource to which this model is bound.
3462
 *
3463
 * @param string $dataSource The name of the DataSource, as defined in app/Config/database.php
3464
 * @return void
3465
 * @throws MissingConnectionException
3466
 */
3467
	public function setDataSource($dataSource = null) {
3468
		$oldConfig = $this->useDbConfig;
3469
 
3470
		if ($dataSource) {
3471
			$this->useDbConfig = $dataSource;
3472
		}
3473
 
3474
		$db = ConnectionManager::getDataSource($this->useDbConfig);
3475
		if (!empty($oldConfig) && isset($db->config['prefix'])) {
3476
			$oldDb = ConnectionManager::getDataSource($oldConfig);
3477
 
3478
			if (!isset($this->tablePrefix) || (!isset($oldDb->config['prefix']) || $this->tablePrefix == $oldDb->config['prefix'])) {
3479
				$this->tablePrefix = $db->config['prefix'];
3480
			}
3481
		} elseif (isset($db->config['prefix'])) {
3482
			$this->tablePrefix = $db->config['prefix'];
3483
		}
3484
 
3485
		$this->schemaName = $db->getSchemaName();
3486
	}
3487
 
3488
/**
3489
 * Gets the DataSource to which this model is bound.
3490
 *
3491
 * @return DataSource A DataSource object
3492
 */
3493
	public function getDataSource() {
3494
		if (!$this->_sourceConfigured && $this->useTable !== false) {
3495
			$this->_sourceConfigured = true;
3496
			$this->setSource($this->useTable);
3497
		}
3498
 
3499
		return ConnectionManager::getDataSource($this->useDbConfig);
3500
	}
3501
 
3502
/**
3503
 * Get associations
3504
 *
3505
 * @return array
3506
 */
3507
	public function associations() {
3508
		return $this->_associations;
3509
	}
3510
 
3511
/**
3512
 * Gets all the models with which this model is associated.
3513
 *
3514
 * @param string $type Only result associations of this type
3515
 * @return array Associations
3516
 */
3517
	public function getAssociated($type = null) {
3518
		if (!$type) {
3519
			$associated = array();
3520
			foreach ($this->_associations as $assoc) {
3521
				if (!empty($this->{$assoc})) {
3522
					$models = array_keys($this->{$assoc});
3523
					foreach ($models as $m) {
3524
						$associated[$m] = $assoc;
3525
					}
3526
				}
3527
			}
3528
 
3529
			return $associated;
3530
		}
3531
 
3532
		if (in_array($type, $this->_associations)) {
3533
			if (empty($this->{$type})) {
3534
				return array();
3535
			}
3536
 
3537
			return array_keys($this->{$type});
3538
		}
3539
 
3540
		$assoc = array_merge(
3541
			$this->hasOne,
3542
			$this->hasMany,
3543
			$this->belongsTo,
3544
			$this->hasAndBelongsToMany
3545
		);
3546
 
3547
		if (array_key_exists($type, $assoc)) {
3548
			foreach ($this->_associations as $a) {
3549
				if (isset($this->{$a}[$type])) {
3550
					$assoc[$type]['association'] = $a;
3551
					break;
3552
				}
3553
			}
3554
 
3555
			return $assoc[$type];
3556
		}
3557
 
3558
		return null;
3559
	}
3560
 
3561
/**
3562
 * Gets the name and fields to be used by a join model. This allows specifying join fields
3563
 * in the association definition.
3564
 *
3565
 * @param string|array $assoc The model to be joined
3566
 * @param array $keys Any join keys which must be merged with the keys queried
3567
 * @return array
3568
 */
3569
	public function joinModel($assoc, $keys = array()) {
3570
		if (is_string($assoc)) {
3571
			list(, $assoc) = pluginSplit($assoc);
3572
			return array($assoc, array_keys($this->{$assoc}->schema()));
3573
		}
3574
 
3575
		if (is_array($assoc)) {
3576
			$with = key($assoc);
3577
			return array($with, array_unique(array_merge($assoc[$with], $keys)));
3578
		}
3579
 
3580
		trigger_error(
3581
			__d('cake_dev', 'Invalid join model settings in %s. The association parameter has the wrong type, expecting a string or array, but was passed type: %s', $this->alias, gettype($assoc)),
3582
			E_USER_WARNING
3583
		);
3584
	}
3585
 
3586
/**
3587
 * Called before each find operation. Return false if you want to halt the find
3588
 * call, otherwise return the (modified) query data.
3589
 *
3590
 * @param array $query Data used to execute this query, i.e. conditions, order, etc.
3591
 * @return mixed true if the operation should continue, false if it should abort; or, modified
3592
 *  $query to continue with new $query
3593
 * @link http://book.cakephp.org/2.0/en/models/callback-methods.html#beforefind
3594
 */
3595
	public function beforeFind($query) {
3596
		return true;
3597
	}
3598
 
3599
/**
3600
 * Called after each find operation. Can be used to modify any results returned by find().
3601
 * Return value should be the (modified) results.
3602
 *
3603
 * @param mixed $results The results of the find operation
3604
 * @param boolean $primary Whether this model is being queried directly (vs. being queried as an association)
3605
 * @return mixed Result of the find operation
3606
 * @link http://book.cakephp.org/2.0/en/models/callback-methods.html#afterfind
3607
 */
3608
	public function afterFind($results, $primary = false) {
3609
		return $results;
3610
	}
3611
 
3612
/**
3613
 * Called before each save operation, after validation. Return a non-true result
3614
 * to halt the save.
3615
 *
3616
 * @param array $options Options passed from Model::save().
3617
 * @return boolean True if the operation should continue, false if it should abort
3618
 * @link http://book.cakephp.org/2.0/en/models/callback-methods.html#beforesave
3619
 * @see Model::save()
3620
 */
3621
	public function beforeSave($options = array()) {
3622
		return true;
3623
	}
3624
 
3625
/**
3626
 * Called after each successful save operation.
3627
 *
3628
 * @param boolean $created True if this save created a new record
3629
 * @param array $options Options passed from Model::save().
3630
 * @return void
3631
 * @link http://book.cakephp.org/2.0/en/models/callback-methods.html#aftersave
3632
 * @see Model::save()
3633
 */
3634
	public function afterSave($created, $options = array()) {
3635
	}
3636
 
3637
/**
3638
 * Called before every deletion operation.
3639
 *
3640
 * @param boolean $cascade If true records that depend on this record will also be deleted
3641
 * @return boolean True if the operation should continue, false if it should abort
3642
 * @link http://book.cakephp.org/2.0/en/models/callback-methods.html#beforedelete
3643
 */
3644
	public function beforeDelete($cascade = true) {
3645
		return true;
3646
	}
3647
 
3648
/**
3649
 * Called after every deletion operation.
3650
 *
3651
 * @return void
3652
 * @link http://book.cakephp.org/2.0/en/models/callback-methods.html#afterdelete
3653
 */
3654
	public function afterDelete() {
3655
	}
3656
 
3657
/**
3658
 * Called during validation operations, before validation. Please note that custom
3659
 * validation rules can be defined in $validate.
3660
 *
3661
 * @param array $options Options passed from Model::save().
3662
 * @return boolean True if validate operation should continue, false to abort
3663
 * @link http://book.cakephp.org/2.0/en/models/callback-methods.html#beforevalidate
3664
 * @see Model::save()
3665
 */
3666
	public function beforeValidate($options = array()) {
3667
		return true;
3668
	}
3669
 
3670
/**
3671
 * Called after data has been checked for errors
3672
 *
3673
 * @return void
3674
 */
3675
	public function afterValidate() {
3676
	}
3677
 
3678
/**
3679
 * Called when a DataSource-level error occurs.
3680
 *
3681
 * @return void
3682
 * @link http://book.cakephp.org/2.0/en/models/callback-methods.html#onerror
3683
 */
3684
	public function onError() {
3685
	}
3686
 
3687
/**
3688
 * Clears cache for this model.
3689
 *
3690
 * @param string $type If null this deletes cached views if Cache.check is true
3691
 *     Will be used to allow deleting query cache also
3692
 * @return mixed True on delete, null otherwise
3693
 */
3694
	protected function _clearCache($type = null) {
3695
		if ($type !== null || Configure::read('Cache.check') !== true) {
3696
			return;
3697
		}
3698
		$pluralized = Inflector::pluralize($this->alias);
3699
		$assoc = array(
3700
			strtolower($pluralized),
3701
			Inflector::underscore($pluralized)
3702
		);
3703
		foreach ($this->_associations as $association) {
3704
			foreach ($this->{$association} as $className) {
3705
				$pluralizedAssociation = Inflector::pluralize($className['className']);
3706
				if (!in_array(strtolower($pluralizedAssociation), $assoc)) {
3707
					$assoc = array_merge($assoc, array(
3708
						strtolower($pluralizedAssociation),
3709
						Inflector::underscore($pluralizedAssociation)
3710
					));
3711
				}
3712
			}
3713
		}
3714
		clearCache(array_unique($assoc));
3715
		return true;
3716
	}
3717
 
3718
/**
3719
 * Returns an instance of a model validator for this class
3720
 *
3721
 * @param ModelValidator Model validator instance.
3722
 *  If null a new ModelValidator instance will be made using current model object
3723
 * @return ModelValidator
3724
 */
3725
	public function validator(ModelValidator $instance = null) {
3726
		if ($instance) {
3727
			$this->_validator = $instance;
3728
		} elseif (!$this->_validator) {
3729
			$this->_validator = new ModelValidator($this);
3730
		}
3731
 
3732
		return $this->_validator;
3733
	}
3734
 
3735
}