1: <?php
2: /**
3: * CakeValidationSet.
4: *
5: * Provides the Model validation logic.
6: *
7: * PHP 5
8: *
9: * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
10: * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
11: *
12: * Licensed under The MIT License
13: * For full copyright and license information, please see the LICENSE.txt
14: * Redistributions of files must retain the above copyright notice.
15: *
16: * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
17: * @link http://cakephp.org CakePHP(tm) Project
18: * @package Cake.Model.Validator
19: * @since CakePHP(tm) v 2.2.0
20: * @license http://www.opensource.org/licenses/mit-license.php MIT License
21: */
22:
23: App::uses('CakeValidationRule', 'Model/Validator');
24:
25: /**
26: * CakeValidationSet object. Holds all validation rules for a field and exposes
27: * methods to dynamically add or remove validation rules
28: *
29: * @package Cake.Model.Validator
30: * @link http://book.cakephp.org/2.0/en/data-validation.html
31: */
32: class CakeValidationSet implements ArrayAccess, IteratorAggregate, Countable {
33:
34: /**
35: * Holds the CakeValidationRule objects
36: *
37: * @var array
38: */
39: protected $_rules = array();
40:
41: /**
42: * List of methods available for validation
43: *
44: * @var array
45: */
46: protected $_methods = array();
47:
48: /**
49: * I18n domain for validation messages.
50: *
51: * @var string
52: */
53: protected $_validationDomain = null;
54:
55: /**
56: * Whether the validation is stopped
57: *
58: * @var boolean
59: */
60: public $isStopped = false;
61:
62: /**
63: * Holds the fieldname
64: *
65: * @var string
66: */
67: public $field = null;
68:
69: /**
70: * Holds the original ruleSet
71: *
72: * @var array
73: */
74: public $ruleSet = array();
75:
76: /**
77: * Constructor
78: *
79: * @param string $fieldName The fieldname
80: * @param array $ruleset
81: */
82: public function __construct($fieldName, $ruleSet) {
83: $this->field = $fieldName;
84:
85: if (!is_array($ruleSet) || (is_array($ruleSet) && isset($ruleSet['rule']))) {
86: $ruleSet = array($ruleSet);
87: }
88:
89: foreach ($ruleSet as $index => $validateProp) {
90: $this->_rules[$index] = new CakeValidationRule($validateProp);
91: }
92: $this->ruleSet = $ruleSet;
93: }
94:
95: /**
96: * Sets the list of methods to use for validation
97: *
98: * @param array $methods Methods list
99: * @return void
100: */
101: public function setMethods(&$methods) {
102: $this->_methods =& $methods;
103: }
104:
105: /**
106: * Sets the I18n domain for validation messages.
107: *
108: * @param string $validationDomain The validation domain to be used.
109: * @return void
110: */
111: public function setValidationDomain($validationDomain) {
112: $this->_validationDomain = $validationDomain;
113: }
114:
115: /**
116: * Runs all validation rules in this set and returns a list of
117: * validation errors
118: *
119: * @param array $data Data array
120: * @param boolean $isUpdate Is record being updated or created
121: * @return array list of validation errors for this field
122: */
123: public function validate($data, $isUpdate = false) {
124: $this->reset();
125: $errors = array();
126: foreach ($this->getRules() as $name => $rule) {
127: $rule->isUpdate($isUpdate);
128: if ($rule->skip()) {
129: continue;
130: }
131:
132: $checkRequired = $rule->checkRequired($this->field, $data);
133: if (!$checkRequired && array_key_exists($this->field, $data)) {
134: if ($rule->checkEmpty($this->field, $data)) {
135: break;
136: }
137: $rule->process($this->field, $data, $this->_methods);
138: }
139:
140: if ($checkRequired || !$rule->isValid()) {
141: $errors[] = $this->_processValidationResponse($name, $rule);
142: if ($rule->isLast()) {
143: break;
144: }
145: }
146: }
147:
148: return $errors;
149: }
150:
151: /**
152: * Resets internal state for all validation rules in this set
153: *
154: * @return void
155: */
156: public function reset() {
157: foreach ($this->getRules() as $rule) {
158: $rule->reset();
159: }
160: }
161:
162: /**
163: * Gets a rule for a given name if exists
164: *
165: * @param string $name
166: * @return CakeValidationRule
167: */
168: public function getRule($name) {
169: if (!empty($this->_rules[$name])) {
170: return $this->_rules[$name];
171: }
172: }
173:
174: /**
175: * Returns all rules for this validation set
176: *
177: * @return array
178: */
179: public function getRules() {
180: return $this->_rules;
181: }
182:
183: /**
184: * Sets a CakeValidationRule $rule with a $name
185: *
186: * ## Example:
187: *
188: * {{{
189: * $set
190: * ->setRule('required', array('rule' => 'notEmpty', 'required' => true))
191: * ->setRule('inRange', array('rule' => array('between', 4, 10))
192: * }}}
193: *
194: * @param string $name The name under which the rule should be set
195: * @param CakeValidationRule|array $rule The validation rule to be set
196: * @return CakeValidationSet this instance
197: */
198: public function setRule($name, $rule) {
199: if (!($rule instanceof CakeValidationRule)) {
200: $rule = new CakeValidationRule($rule);
201: }
202: $this->_rules[$name] = $rule;
203: return $this;
204: }
205:
206: /**
207: * Removes a validation rule from the set
208: *
209: * ## Example:
210: *
211: * {{{
212: * $set
213: * ->removeRule('required')
214: * ->removeRule('inRange')
215: * }}}
216: *
217: * @param string $name The name under which the rule should be unset
218: * @return CakeValidationSet this instance
219: */
220: public function removeRule($name) {
221: unset($this->_rules[$name]);
222: return $this;
223: }
224:
225: /**
226: * Sets the rules for a given field
227: *
228: * ## Example:
229: *
230: * {{{
231: * $set->setRules(array(
232: * 'required' => array('rule' => 'notEmpty', 'required' => true),
233: * 'inRange' => array('rule' => array('between', 4, 10)
234: * ));
235: * }}}
236: *
237: * @param array $rules The rules to be set
238: * @param boolean $mergeVars [optional] If true, merges vars instead of replace. Defaults to true.
239: * @return ModelField
240: */
241: public function setRules($rules = array(), $mergeVars = true) {
242: if ($mergeVars === false) {
243: $this->_rules = array();
244: }
245: foreach ($rules as $name => $rule) {
246: $this->setRule($name, $rule);
247: }
248: return $this;
249: }
250:
251: /**
252: * Fetches the correct error message for a failed validation
253: *
254: * @param string $name the name of the rule as it was configured
255: * @param CakeValidationRule $rule the object containing validation information
256: * @return string
257: */
258: protected function _processValidationResponse($name, $rule) {
259: $message = $rule->getValidationResult();
260: if (is_string($message)) {
261: return $message;
262: }
263: $message = $rule->message;
264:
265: if ($message !== null) {
266: $args = null;
267: if (is_array($message)) {
268: $result = $message[0];
269: $args = array_slice($message, 1);
270: } else {
271: $result = $message;
272: }
273: if (is_array($rule->rule) && $args === null) {
274: $args = array_slice($rule->rule, 1);
275: }
276: $args = $this->_translateArgs($args);
277:
278: $message = __d($this->_validationDomain, $result, $args);
279: } elseif (is_string($name)) {
280: if (is_array($rule->rule)) {
281: $args = array_slice($rule->rule, 1);
282: $args = $this->_translateArgs($args);
283: $message = __d($this->_validationDomain, $name, $args);
284: } else {
285: $message = __d($this->_validationDomain, $name);
286: }
287: } else {
288: $message = __d('cake', 'This field cannot be left blank');
289: }
290:
291: return $message;
292: }
293:
294: /**
295: * Applies translations to validator arguments.
296: *
297: * @param array $args The args to translate
298: * @return array Translated args.
299: */
300: protected function _translateArgs($args) {
301: foreach ((array)$args as $k => $arg) {
302: if (is_string($arg)) {
303: $args[$k] = __d($this->_validationDomain, $arg);
304: }
305: }
306: return $args;
307: }
308:
309: /**
310: * Returns whether an index exists in the rule set
311: *
312: * @param string $index name of the rule
313: * @return boolean
314: */
315: public function offsetExists($index) {
316: return isset($this->_rules[$index]);
317: }
318:
319: /**
320: * Returns a rule object by its index
321: *
322: * @param string $index name of the rule
323: * @return CakeValidationRule
324: */
325: public function offsetGet($index) {
326: return $this->_rules[$index];
327: }
328:
329: /**
330: * Sets or replace a validation rule
331: *
332: * @param string $index name of the rule
333: * @param CakeValidationRule|array rule to add to $index
334: * @return void
335: */
336: public function offsetSet($index, $rule) {
337: $this->setRule($index, $rule);
338: }
339:
340: /**
341: * Unsets a validation rule
342: *
343: * @param string $index name of the rule
344: * @return void
345: */
346: public function offsetUnset($index) {
347: unset($this->_rules[$index]);
348: }
349:
350: /**
351: * Returns an iterator for each of the rules to be applied
352: *
353: * @return ArrayIterator
354: */
355: public function getIterator() {
356: return new ArrayIterator($this->_rules);
357: }
358:
359: /**
360: * Returns the number of rules in this set
361: *
362: * @return int
363: */
364: public function count() {
365: return count($this->_rules);
366: }
367:
368: }
369: