~tsep-dev/tsep/0.9-beta

« back to all changes in this revision

Viewing changes to branches/symfony/cake/libs/model/model.php

  • Committer: geoffreyfishing
  • Date: 2011-01-11 23:46:12 UTC
  • Revision ID: svn-v4:ae0de26e-ed09-4cbe-9a20-e40b4c60ac6c::125
Created a symfony branch for future migration to symfony

Show diffs side-by-side

added added

removed removed

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