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