Merge branch 'maint/7.0'
[ninja.git] / system / libraries / Validation.php
blob9b1e400a573082ec052474d84de702c63a4becf0
1 <?php defined('SYSPATH') OR die('No direct access allowed.');
2 /**
3 * Validation library.
5 * $Id: Validation.php 3917 2009-01-21 03:06:22Z zombor $
7 * @package Validation
8 * @author Kohana Team
9 * @copyright (c) 2007-2008 Kohana Team
10 * @license http://kohanaphp.com/license.html
12 class Validation extends ArrayObject {
14 // Filters
15 protected $pre_filters = array();
16 protected $post_filters = array();
18 // Rules and callbacks
19 protected $rules = array();
20 protected $callbacks = array();
22 // Rules that are allowed to run on empty fields
23 protected $empty_rules = array('required', 'matches');
25 // Errors
26 protected $errors = array();
27 protected $messages = array();
29 // Fields that are expected to be arrays
30 protected $array_fields = array();
32 // Checks if there is data to validate.
33 protected $submitted;
35 /**
36 * Creates a new Validation instance.
38 * @param array array to use for validation
39 * @return object
41 public static function factory(array $array)
43 return new Validation($array);
46 /**
47 * Sets the unique "any field" key and creates an ArrayObject from the
48 * passed array.
50 * @param array array to validate
51 * @return void
53 public function __construct(array $array)
55 // The array is submitted if the array is not empty
56 $this->submitted = ! empty($array);
58 parent::__construct($array, ArrayObject::ARRAY_AS_PROPS | ArrayObject::STD_PROP_LIST);
61 /**
62 * Magic clone method, clears errors and messages.
64 * @return void
66 public function __clone()
68 $this->errors = array();
69 $this->messages = array();
72 /**
73 * Create a copy of the current validation rules and change the array.
75 * @chainable
76 * @param array new array to validate
77 * @return Validation
79 public function copy(array $array)
81 $copy = clone $this;
83 $copy->exchangeArray($array);
85 return $copy;
88 /**
89 * Test if the data has been submitted.
91 * @return boolean
93 public function submitted($value = NULL)
95 if (is_bool($value))
97 $this->submitted = $value;
100 return $this->submitted;
104 * Returns an array of all the field names that have filters, rules, or callbacks.
106 * @return array
108 public function field_names()
110 // All the fields that are being validated
111 $fields = array_unique(array_merge
113 array_keys($this->pre_filters),
114 array_keys($this->rules),
115 array_keys($this->callbacks),
116 array_keys($this->post_filters)
119 // Remove wildcard fields
120 $fields = array_diff($fields, array('*'));
122 return $fields;
126 * Returns the array values of the current object.
128 * @return array
130 public function as_array()
132 return $this->getArrayCopy();
136 * Returns the ArrayObject values, removing all inputs without rules.
137 * To choose specific inputs, list the field name as arguments.
139 * @param boolean return only fields with filters, rules, and callbacks
140 * @return array
142 public function safe_array()
144 // Load choices
145 $choices = func_get_args();
146 $choices = empty($choices) ? NULL : array_combine($choices, $choices);
148 // Get field names
149 $fields = $this->field_names();
151 $safe = array();
152 foreach ($fields as $field)
154 if ($choices === NULL OR isset($choices[$field]))
156 if (isset($this[$field]))
158 $value = $this[$field];
160 if (is_object($value))
162 // Convert the value back into an array
163 $value = $value->getArrayCopy();
166 else
168 // Even if the field is not in this array, it must be set
169 $value = NULL;
172 // Add the field to the array
173 $safe[$field] = $value;
177 return $safe;
181 * Add additional rules that will forced, even for empty fields. All arguments
182 * passed will be appended to the list.
184 * @chainable
185 * @param string rule name
186 * @return object
188 public function allow_empty_rules($rules)
190 // Any number of args are supported
191 $rules = func_get_args();
193 // Merge the allowed rules
194 $this->empty_rules = array_merge($this->empty_rules, $rules);
196 return $this;
200 * Converts a filter, rule, or callback into a fully-qualified callback array.
202 * @return mixed
204 protected function callback($callback)
206 if (is_string($callback))
208 if (strpos($callback, '::') !== FALSE)
210 $callback = explode('::', $callback);
212 elseif (function_exists($callback))
214 // No need to check if the callback is a method
215 $callback = $callback;
217 elseif (method_exists($this, $callback))
219 // The callback exists in Validation
220 $callback = array($this, $callback);
222 elseif (method_exists('valid', $callback))
224 // The callback exists in valid::
225 $callback = array('valid', $callback);
229 if ( ! is_callable($callback, FALSE))
231 if (is_array($callback))
233 if (is_object($callback[0]))
235 // Object instance syntax
236 $name = get_class($callback[0]).'->'.$callback[1];
238 else
240 // Static class syntax
241 $name = $callback[0].'::'.$callback[1];
244 else
246 // Function syntax
247 $name = $callback;
250 throw new Kohana_Exception('validation.not_callable', $name);
253 return $callback;
257 * Add a pre-filter to one or more inputs. Pre-filters are applied before
258 * rules or callbacks are executed.
260 * @chainable
261 * @param callback filter
262 * @param string fields to apply filter to, use TRUE for all fields
263 * @return object
265 public function pre_filter($filter, $field = TRUE)
267 if ($field === TRUE OR $field === '*')
269 // Use wildcard
270 $fields = array('*');
272 else
274 // Add the filter to specific inputs
275 $fields = func_get_args();
276 $fields = array_slice($fields, 1);
279 // Convert to a proper callback
280 $filter = $this->callback($filter);
282 foreach ($fields as $field)
284 // Add the filter to specified field
285 $this->pre_filters[$field][] = $filter;
288 return $this;
292 * Add a post-filter to one or more inputs. Post-filters are applied after
293 * rules and callbacks have been executed.
295 * @chainable
296 * @param callback filter
297 * @param string fields to apply filter to, use TRUE for all fields
298 * @return object
300 public function post_filter($filter, $field = TRUE)
302 if ($field === TRUE)
304 // Use wildcard
305 $fields = array('*');
307 else
309 // Add the filter to specific inputs
310 $fields = func_get_args();
311 $fields = array_slice($fields, 1);
314 // Convert to a proper callback
315 $filter = $this->callback($filter);
317 foreach ($fields as $field)
319 // Add the filter to specified field
320 $this->post_filters[$field][] = $filter;
323 return $this;
327 * Add rules to a field. Validation rules may only return TRUE or FALSE and
328 * can not manipulate the value of a field.
330 * @chainable
331 * @param string field name
332 * @param callback rules (one or more arguments)
333 * @return object
335 public function add_rules($field, $rules)
337 // Get the rules
338 $rules = func_get_args();
339 $rules = array_slice($rules, 1);
341 if ($field === TRUE)
343 // Use wildcard
344 $field = '*';
347 foreach ($rules as $rule)
349 // Arguments for rule
350 $args = NULL;
352 if (is_string($rule))
354 if (preg_match('/^([^\[]++)\[(.+)\]$/', $rule, $matches))
356 // Split the rule into the function and args
357 $rule = $matches[1];
358 $args = preg_split('/(?<!\\\\),\s*/', $matches[2]);
360 // Replace escaped comma with comma
361 $args = str_replace('\,', ',', $args);
365 if ($rule === 'is_array')
367 // This field is expected to be an array
368 $this->array_fields[$field] = $field;
371 // Convert to a proper callback
372 $rule = $this->callback($rule);
374 // Add the rule, with args, to the field
375 $this->rules[$field][] = array($rule, $args);
378 return $this;
382 * Add callbacks to a field. Callbacks must accept the Validation object
383 * and the input name. Callback returns are not processed.
385 * @chainable
386 * @param string field name
387 * @param callbacks callbacks (unlimited number)
388 * @return object
390 public function add_callbacks($field, $callbacks)
392 // Get all callbacks as an array
393 $callbacks = func_get_args();
394 $callbacks = array_slice($callbacks, 1);
396 if ($field === TRUE)
398 // Use wildcard
399 $field = '*';
402 foreach ($callbacks as $callback)
404 // Convert to a proper callback
405 $callback = $this->callback($callback);
407 // Add the callback to specified field
408 $this->callbacks[$field][] = $callback;
411 return $this;
415 * Validate by processing pre-filters, rules, callbacks, and post-filters.
416 * All fields that have filters, rules, or callbacks will be initialized if
417 * they are undefined. Validation will only be run if there is data already
418 * in the array.
420 * @param object Validation object, used only for recursion
421 * @param object name of field for errors
422 * @return bool
424 public function validate($object = NULL, $field_name = NULL)
426 if ($object === NULL)
428 // Use the current object
429 $object = $this;
432 // Get all field names
433 $fields = $this->field_names();
435 // Copy the array from the object, to optimize multiple sets
436 $array = $this->getArrayCopy();
438 foreach ($fields as $field)
440 if ($field === '*')
442 // Ignore wildcard
443 continue;
446 if ( ! isset($array[$field]))
448 if (isset($this->array_fields[$field]))
450 // This field must be an array
451 $array[$field] = array();
453 else
455 $array[$field] = NULL;
460 // Swap the array back into the object
461 $this->exchangeArray($array);
463 // Get all defined field names
464 $fields = array_keys($array);
466 foreach ($this->pre_filters as $field => $callbacks)
468 foreach ($callbacks as $callback)
470 if ($field === '*')
472 foreach ($fields as $f)
474 $this[$f] = is_array($this[$f]) ? array_map($callback, $this[$f]) : call_user_func($callback, $this[$f]);
477 else
479 $this[$field] = is_array($this[$field]) ? array_map($callback, $this[$field]) : call_user_func($callback, $this[$field]);
484 if ($this->submitted === FALSE)
485 return FALSE;
487 foreach ($this->rules as $field => $callbacks)
489 foreach ($callbacks as $callback)
491 // Separate the callback and arguments
492 list ($callback, $args) = $callback;
494 // Function or method name of the rule
495 $rule = is_array($callback) ? $callback[1] : $callback;
497 if ($field === '*')
499 foreach ($fields as $f)
501 // Note that continue, instead of break, is used when
502 // applying rules using a wildcard, so that all fields
503 // will be validated.
505 if (isset($this->errors[$f]))
507 // Prevent other rules from being evaluated if an error has occurred
508 continue;
511 if (empty($this[$f]) AND ! in_array($rule, $this->empty_rules))
513 // This rule does not need to be processed on empty fields
514 continue;
517 if ($args === NULL)
519 if ( ! call_user_func($callback, $this[$f]))
521 $this->errors[$f] = $rule;
523 // Stop validating this field when an error is found
524 continue;
527 else
529 if ( ! call_user_func($callback, $this[$f], $args))
531 $this->errors[$f] = $rule;
533 // Stop validating this field when an error is found
534 continue;
539 else
541 if (isset($this->errors[$field]))
543 // Prevent other rules from being evaluated if an error has occurred
544 break;
547 if ( ! in_array($rule, $this->empty_rules) AND ! $this->required($this[$field]))
549 // This rule does not need to be processed on empty fields
550 continue;
553 if ($args === NULL)
555 if ( ! call_user_func($callback, $this[$field]))
557 $this->errors[$field] = $rule;
559 // Stop validating this field when an error is found
560 break;
563 else
565 if ( ! call_user_func($callback, $this[$field], $args))
567 $this->errors[$field] = $rule;
569 // Stop validating this field when an error is found
570 break;
577 foreach ($this->callbacks as $field => $callbacks)
579 foreach ($callbacks as $callback)
581 if ($field === '*')
583 foreach ($fields as $f)
585 // Note that continue, instead of break, is used when
586 // applying rules using a wildcard, so that all fields
587 // will be validated.
589 if (isset($this->errors[$f]))
591 // Stop validating this field when an error is found
592 continue;
595 call_user_func($callback, $this, $f);
598 else
600 if (isset($this->errors[$field]))
602 // Stop validating this field when an error is found
603 break;
606 call_user_func($callback, $this, $field);
611 foreach ($this->post_filters as $field => $callbacks)
613 foreach ($callbacks as $callback)
615 if ($field === '*')
617 foreach ($fields as $f)
619 $this[$f] = is_array($this[$f]) ? array_map($callback, $this[$f]) : call_user_func($callback, $this[$f]);
622 else
624 $this[$field] = is_array($this[$field]) ? array_map($callback, $this[$field]) : call_user_func($callback, $this[$field]);
629 // Return TRUE if there are no errors
630 return $this->errors === array();
634 * Add an error to an input.
636 * @chainable
637 * @param string input name
638 * @param string unique error name
639 * @return object
641 public function add_error($field, $name)
643 $this->errors[$field] = $name;
645 return $this;
649 * Sets or returns the message for an input.
651 * @chainable
652 * @param string input key
653 * @param string message to set
654 * @return string|object
656 public function message($input = NULL, $message = NULL)
658 if ($message === NULL)
660 if ($input === NULL)
662 $messages = array();
663 $keys = array_keys($this->messages);
665 foreach ($keys as $input)
667 $messages[] = $this->message($input);
670 return implode("\n", $messages);
673 // Return nothing if no message exists
674 if (empty($this->messages[$input]))
675 return '';
677 // Return the HTML message string
678 return $this->messages[$input];
680 else
682 $this->messages[$input] = $message;
685 return $this;
689 * Return the errors array.
691 * @param boolean load errors from a lang file
692 * @return array
694 public function errors($file = NULL)
696 if ($file === NULL)
698 return $this->errors;
700 else
703 $errors = array();
704 foreach ($this->errors as $input => $error)
706 // Key for this input error
707 $key = "$file.$input.$error";
709 if (($errors[$input] = Kohana::lang($key)) === $key)
711 // Get the default error message
712 $errors[$input] = Kohana::lang("$file.$input.default");
716 return $errors;
721 * Rule: required. Generates an error if the field has an empty value.
723 * @param mixed input value
724 * @return bool
726 public function required($str)
728 if (is_object($str) AND $str instanceof ArrayObject)
730 // Get the array from the ArrayObject
731 $str = $str->getArrayCopy();
734 if (is_array($str))
736 return ! empty($str);
738 else
740 return ! ($str === '' OR $str === NULL OR $str === FALSE);
745 * Rule: matches. Generates an error if the field does not match one or more
746 * other fields.
748 * @param mixed input value
749 * @param array input names to match against
750 * @return bool
752 public function matches($str, array $inputs)
754 foreach ($inputs as $key)
756 if ($str !== (isset($this[$key]) ? $this[$key] : NULL))
757 return FALSE;
760 return TRUE;
764 * Rule: length. Generates an error if the field is too long or too short.
766 * @param mixed input value
767 * @param array minimum, maximum, or exact length to match
768 * @return bool
770 public function length($str, array $length)
772 if ( ! is_string($str))
773 return FALSE;
775 $size = utf8::strlen($str);
776 $status = FALSE;
778 if (count($length) > 1)
780 list ($min, $max) = $length;
782 if ($size >= $min AND $size <= $max)
784 $status = TRUE;
787 else
789 $status = ($size === (int) $length[0]);
792 return $status;
796 * Rule: depends_on. Generates an error if the field does not depend on one
797 * or more other fields.
799 * @param mixed field name
800 * @param array field names to check dependency
801 * @return bool
803 public function depends_on($field, array $fields)
805 foreach ($fields as $depends_on)
807 if ( ! isset($this[$depends_on]) OR $this[$depends_on] == NULL)
808 return FALSE;
811 return TRUE;
815 * Rule: chars. Generates an error if the field contains characters outside of the list.
817 * @param string field value
818 * @param array allowed characters
819 * @return bool
821 public function chars($value, array $chars)
823 return ! preg_match('![^'.implode('', $chars).']!u', $value);
826 } // End Validation