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