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