Subversion-Projekte lars-tiefland.cakephp

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

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