diff options
| author | Bharat Mediratta <bharat@menalto.com> | 2009-05-27 15:11:53 -0700 | 
|---|---|---|
| committer | Bharat Mediratta <bharat@menalto.com> | 2009-05-27 15:11:53 -0700 | 
| commit | 12fe58d997d2066dc362fd393a18b4e5da190513 (patch) | |
| tree | 3ad8e5afb77829e1541ec96d86785760d65c04ac /system/libraries/Validation.php | |
| parent | 00f47d4ddddcd1902db817018dd79ac01bcc8e82 (diff) | |
Rename 'kohana' to 'system' to conform to the Kohana filesystem layout.  I'm comfortable with us not clearly drawing the distinction about the fact that it's Kohana.
Diffstat (limited to 'system/libraries/Validation.php')
| -rw-r--r-- | system/libraries/Validation.php | 826 | 
1 files changed, 826 insertions, 0 deletions
diff --git a/system/libraries/Validation.php b/system/libraries/Validation.php new file mode 100644 index 00000000..5a48bfc5 --- /dev/null +++ b/system/libraries/Validation.php @@ -0,0 +1,826 @@ +<?php defined('SYSPATH') OR die('No direct access allowed.'); +/** + * Validation library. + * + * $Id: Validation.php 4120 2009-03-25 19:22:31Z jheathco $ + * + * @package    Validation + * @author     Kohana Team + * @copyright  (c) 2007-2008 Kohana Team + * @license    http://kohanaphp.com/license.html + */ +class Validation_Core extends ArrayObject { + +	// Filters +	protected $pre_filters = array(); +	protected $post_filters = array(); + +	// Rules and callbacks +	protected $rules = array(); +	protected $callbacks = array(); + +	// Rules that are allowed to run on empty fields +	protected $empty_rules = array('required', 'matches'); + +	// Errors +	protected $errors = array(); +	protected $messages = array(); + +	// Fields that are expected to be arrays +	protected $array_fields = array(); + +	// Checks if there is data to validate. +	protected $submitted; + +	/** +	 * Creates a new Validation instance. +	 * +	 * @param   array   array to use for validation +	 * @return  object +	 */ +	public static function factory(array $array) +	{ +		return new Validation($array); +	} + +	/** +	 * Sets the unique "any field" key and creates an ArrayObject from the +	 * passed array. +	 * +	 * @param   array   array to validate +	 * @return  void +	 */ +	public function __construct(array $array) +	{ +		// The array is submitted if the array is not empty +		$this->submitted = ! empty($array); + +		parent::__construct($array, ArrayObject::ARRAY_AS_PROPS | ArrayObject::STD_PROP_LIST); +	} + +	/** +	 * Magic clone method, clears errors and messages. +	 * +	 * @return  void +	 */ +	public function __clone() +	{ +		$this->errors = array(); +		$this->messages = array(); +	} + +	/** +	 * Create a copy of the current validation rules and change the array. +	 * +	 * @chainable +	 * @param   array  new array to validate +	 * @return  Validation +	 */ +	public function copy(array $array) +	{ +		$copy = clone $this; + +		$copy->exchangeArray($array); + +		return $copy; +	} + +	/** +	 * Test if the data has been submitted. +	 * +	 * @return  boolean +	 */ +	public function submitted($value = NULL) +	{ +		if (is_bool($value)) +		{ +			$this->submitted = $value; +		} + +		return $this->submitted; +	} + +	/** +	 * Returns an array of all the field names that have filters, rules, or callbacks. +	 * +	 * @return  array +	 */ +	public function field_names() +	{ +		// All the fields that are being validated +		$fields = array_keys(array_merge +		( +			$this->pre_filters, +			$this->rules, +			$this->callbacks, +			$this->post_filters +		)); + +		// Remove wildcard fields +		$fields = array_diff($fields, array('*')); + +		return $fields; +	} + +	/** +	 * Returns the array values of the current object. +	 * +	 * @return  array +	 */ +	public function as_array() +	{ +		return $this->getArrayCopy(); +	} + +	/** +	 * Returns the ArrayObject values, removing all inputs without rules. +	 * To choose specific inputs, list the field name as arguments. +	 * +	 * @param   boolean  return only fields with filters, rules, and callbacks +	 * @return  array +	 */ +	public function safe_array() +	{ +		// Load choices +		$choices = func_get_args(); +		$choices = empty($choices) ? NULL : array_combine($choices, $choices); + +		// Get field names +		$fields = $this->field_names(); + +		$safe = array(); +		foreach ($fields as $field) +		{ +			if ($choices === NULL OR isset($choices[$field])) +			{ +				if (isset($this[$field])) +				{ +					$value = $this[$field]; + +					if (is_object($value)) +					{ +						// Convert the value back into an array +						$value = $value->getArrayCopy(); +					} +				} +				else +				{ +					// Even if the field is not in this array, it must be set +					$value = NULL; +				} + +				// Add the field to the array +				$safe[$field] = $value; +			} +		} + +		return $safe; +	} + +	/** +	 * Add additional rules that will forced, even for empty fields. All arguments +	 * passed will be appended to the list. +	 * +	 * @chainable +	 * @param   string   rule name +	 * @return  object +	 */ +	public function allow_empty_rules($rules) +	{ +		// Any number of args are supported +		$rules = func_get_args(); + +		// Merge the allowed rules +		$this->empty_rules = array_merge($this->empty_rules, $rules); + +		return $this; +	} + +	/** +	 * Converts a filter, rule, or callback into a fully-qualified callback array. +	 * +	 * @return  mixed +	 */ +	protected function callback($callback) +	{ +		if (is_string($callback)) +		{ +			if (strpos($callback, '::') !== FALSE) +			{ +				$callback = explode('::', $callback); +			} +			elseif (function_exists($callback)) +			{ +				// No need to check if the callback is a method +				$callback = $callback; +			} +			elseif (method_exists($this, $callback)) +			{ +				// The callback exists in Validation +				$callback = array($this, $callback); +			} +			elseif (method_exists('valid', $callback)) +			{ +				// The callback exists in valid:: +				$callback = array('valid', $callback); +			} +		} + +		if ( ! is_callable($callback, FALSE)) +		{ +			if (is_array($callback)) +			{ +				if (is_object($callback[0])) +				{ +					// Object instance syntax +					$name = get_class($callback[0]).'->'.$callback[1]; +				} +				else +				{ +					// Static class syntax +					$name = $callback[0].'::'.$callback[1]; +				} +			} +			else +			{ +				// Function syntax +				$name = $callback; +			} + +			throw new Kohana_Exception('validation.not_callable', $name); +		} + +		return $callback; +	} + +	/** +	 * Add a pre-filter to one or more inputs. Pre-filters are applied before +	 * rules or callbacks are executed. +	 * +	 * @chainable +	 * @param   callback  filter +	 * @param   string    fields to apply filter to, use TRUE for all fields +	 * @return  object +	 */ +	public function pre_filter($filter, $field = TRUE) +	{ +		if ($field === TRUE OR $field === '*') +		{ +			// Use wildcard +			$fields = array('*'); +		} +		else +		{ +			// Add the filter to specific inputs +			$fields = func_get_args(); +			$fields = array_slice($fields, 1); +		} + +		// Convert to a proper callback +		$filter = $this->callback($filter); + +		foreach ($fields as $field) +		{ +			// Add the filter to specified field +			$this->pre_filters[$field][] = $filter; +		} + +		return $this; +	} + +	/** +	 * Add a post-filter to one or more inputs. Post-filters are applied after +	 * rules and callbacks have been executed. +	 * +	 * @chainable +	 * @param   callback  filter +	 * @param   string    fields to apply filter to, use TRUE for all fields +	 * @return  object +	 */ +	public function post_filter($filter, $field = TRUE) +	{ +		if ($field === TRUE) +		{ +			// Use wildcard +			$fields = array('*'); +		} +		else +		{ +			// Add the filter to specific inputs +			$fields = func_get_args(); +			$fields = array_slice($fields, 1); +		} + +		// Convert to a proper callback +		$filter = $this->callback($filter); + +		foreach ($fields as $field) +		{ +			// Add the filter to specified field +			$this->post_filters[$field][] = $filter; +		} + +		return $this; +	} + +	/** +	 * Add rules to a field. Validation rules may only return TRUE or FALSE and +	 * can not manipulate the value of a field. +	 * +	 * @chainable +	 * @param   string    field name +	 * @param   callback  rules (one or more arguments) +	 * @return  object +	 */ +	public function add_rules($field, $rules) +	{ +		// Get the rules +		$rules = func_get_args(); +		$rules = array_slice($rules, 1); + +		if ($field === TRUE) +		{ +			// Use wildcard +			$field = '*'; +		} + +		foreach ($rules as $rule) +		{ +			// Arguments for rule +			$args = NULL; + +			if (is_string($rule)) +			{ +				if (preg_match('/^([^\[]++)\[(.+)\]$/', $rule, $matches)) +				{ +					// Split the rule into the function and args +					$rule = $matches[1]; +					$args = preg_split('/(?<!\\\\),\s*/', $matches[2]); + +					// Replace escaped comma with comma +					$args = str_replace('\,', ',', $args); +				} +			} + +			if ($rule === 'is_array') +			{ +				// This field is expected to be an array +				$this->array_fields[$field] = $field; +			} + +			// Convert to a proper callback +			$rule = $this->callback($rule); + +			// Add the rule, with args, to the field +			$this->rules[$field][] = array($rule, $args); +		} + +		return $this; +	} + +	/** +	 * Add callbacks to a field. Callbacks must accept the Validation object +	 * and the input name. Callback returns are not processed. +	 * +	 * @chainable +	 * @param   string     field name +	 * @param   callbacks  callbacks (unlimited number) +	 * @return  object +	 */ +	public function add_callbacks($field, $callbacks) +	{ +		// Get all callbacks as an array +		$callbacks = func_get_args(); +		$callbacks = array_slice($callbacks, 1); + +		if ($field === TRUE) +		{ +			// Use wildcard +			$field = '*'; +		} + +		foreach ($callbacks as $callback) +		{ +			// Convert to a proper callback +			$callback = $this->callback($callback); + +			// Add the callback to specified field +			$this->callbacks[$field][] = $callback; +		} + +		return $this; +	} + +	/** +	 * Validate by processing pre-filters, rules, callbacks, and post-filters. +	 * All fields that have filters, rules, or callbacks will be initialized if +	 * they are undefined. Validation will only be run if there is data already +	 * in the array. +	 * +	 * @param   object  Validation object, used only for recursion +	 * @param   object  name of field for errors +	 * @return  bool +	 */ +	public function validate($object = NULL, $field_name = NULL) +	{ +		if ($object === NULL) +		{ +			// Use the current object +			$object = $this; +		} + +		// Get all field names +		$fields = $this->field_names(); + +		// Copy the array from the object, to optimize multiple sets +		$array = $this->getArrayCopy(); + +		foreach ($fields as $field) +		{ +			if ($field === '*') +			{ +				// Ignore wildcard +				continue; +			} + +			if ( ! isset($array[$field])) +			{ +				if (isset($this->array_fields[$field])) +				{ +					// This field must be an array +					$array[$field] = array(); +				} +				else +				{ +					$array[$field] = NULL; +				} +			} +		} + +		// Swap the array back into the object +		$this->exchangeArray($array); + +		// Get all defined field names +		$fields = array_keys($array); + +		foreach ($this->pre_filters as $field => $callbacks) +		{ +			foreach ($callbacks as $callback) +			{ +				if ($field === '*') +				{ +					foreach ($fields as $f) +					{ +						$this[$f] = is_array($this[$f]) ? array_map($callback, $this[$f]) : call_user_func($callback, $this[$f]); +					} +				} +				else +				{ +					$this[$field] = is_array($this[$field]) ? array_map($callback, $this[$field]) : call_user_func($callback, $this[$field]); +				} +			} +		} + +		if ($this->submitted === FALSE) +			return FALSE; + +		foreach ($this->rules as $field => $callbacks) +		{ +			foreach ($callbacks as $callback) +			{ +				// Separate the callback and arguments +				list ($callback, $args) = $callback; + +				// Function or method name of the rule +				$rule = is_array($callback) ? $callback[1] : $callback; + +				if ($field === '*') +				{ +					foreach ($fields as $f) +					{ +						// Note that continue, instead of break, is used when +						// applying rules using a wildcard, so that all fields +						// will be validated. + +						if (isset($this->errors[$f])) +						{ +							// Prevent other rules from being evaluated if an error has occurred +							continue; +						} + +						if (empty($this[$f]) AND ! in_array($rule, $this->empty_rules)) +						{ +							// This rule does not need to be processed on empty fields +							continue; +						} + +						if ($args === NULL) +						{ +							if ( ! call_user_func($callback, $this[$f])) +							{ +								$this->errors[$f] = $rule; + +								// Stop validating this field when an error is found +								continue; +							} +						} +						else +						{ +							if ( ! call_user_func($callback, $this[$f], $args)) +							{ +								$this->errors[$f] = $rule; + +								// Stop validating this field when an error is found +								continue; +							} +						} +					} +				} +				else +				{ +					if (isset($this->errors[$field])) +					{ +						// Prevent other rules from being evaluated if an error has occurred +						break; +					} + +					if ( ! in_array($rule, $this->empty_rules) AND ! $this->required($this[$field])) +					{ +						// This rule does not need to be processed on empty fields +						continue; +					} + +					if ($args === NULL) +					{ +						if ( ! call_user_func($callback, $this[$field])) +						{ +							$this->errors[$field] = $rule; + +							// Stop validating this field when an error is found +							break; +						} +					} +					else +					{ +						if ( ! call_user_func($callback, $this[$field], $args)) +						{ +							$this->errors[$field] = $rule; + +							// Stop validating this field when an error is found +							break; +						} +					} +				} +			} +		} + +		foreach ($this->callbacks as $field => $callbacks) +		{ +			foreach ($callbacks as $callback) +			{ +				if ($field === '*') +				{ +					foreach ($fields as $f) +					{ +						// Note that continue, instead of break, is used when +						// applying rules using a wildcard, so that all fields +						// will be validated. + +						if (isset($this->errors[$f])) +						{ +							// Stop validating this field when an error is found +							continue; +						} + +						call_user_func($callback, $this, $f); +					} +				} +				else +				{ +					if (isset($this->errors[$field])) +					{ +						// Stop validating this field when an error is found +						break; +					} + +					call_user_func($callback, $this, $field); +				} +			} +		} + +		foreach ($this->post_filters as $field => $callbacks) +		{ +			foreach ($callbacks as $callback) +			{ +				if ($field === '*') +				{ +					foreach ($fields as $f) +					{ +						$this[$f] = is_array($this[$f]) ? array_map($callback, $this[$f]) : call_user_func($callback, $this[$f]); +					} +				} +				else +				{ +					$this[$field] = is_array($this[$field]) ? array_map($callback, $this[$field]) : call_user_func($callback, $this[$field]); +				} +			} +		} + +		// Return TRUE if there are no errors +		return $this->errors === array(); +	} + +	/** +	 * Add an error to an input. +	 * +	 * @chainable +	 * @param   string  input name +	 * @param   string  unique error name +	 * @return  object +	 */ +	public function add_error($field, $name) +	{ +		$this->errors[$field] = $name; + +		return $this; +	} + +	/** +	 * Sets or returns the message for an input. +	 * +	 * @chainable +	 * @param   string   input key +	 * @param   string   message to set +	 * @return  string|object +	 */ +	public function message($input = NULL, $message = NULL) +	{ +		if ($message === NULL) +		{ +			if ($input === NULL) +			{ +				$messages = array(); +				$keys     = array_keys($this->messages); + +				foreach ($keys as $input) +				{ +					$messages[] = $this->message($input); +				} + +				return implode("\n", $messages); +			} + +			// Return nothing if no message exists +			if (empty($this->messages[$input])) +				return ''; + +			// Return the HTML message string +			return $this->messages[$input]; +		} +		else +		{ +			$this->messages[$input] = $message; +		} + +		return $this; +	} + +	/** +	 * Return the errors array. +	 * +	 * @param   boolean  load errors from a lang file +	 * @return  array +	 */ +	public function errors($file = NULL) +	{ +		if ($file === NULL) +		{ +			return $this->errors; +		} +		else +		{ + +			$errors = array(); +			foreach ($this->errors as $input => $error) +			{ +				// Key for this input error +				$key = "$file.$input.$error"; + +				if (($errors[$input] = Kohana::lang($key)) === $key) +				{ +					// Get the default error message +					$errors[$input] = Kohana::lang("$file.$input.default"); +				} +			} + +			return $errors; +		} +	} + +	/** +	 * Rule: required. Generates an error if the field has an empty value. +	 * +	 * @param   mixed   input value +	 * @return  bool +	 */ +	public function required($str) +	{ +		if (is_object($str) AND $str instanceof ArrayObject) +		{ +			// Get the array from the ArrayObject +			$str = $str->getArrayCopy(); +		} + +		if (is_array($str)) +		{ +			return ! empty($str); +		} +		else +		{ +			return ! ($str === '' OR $str === NULL OR $str === FALSE); +		} +	} + +	/** +	 * Rule: matches. Generates an error if the field does not match one or more +	 * other fields. +	 * +	 * @param   mixed   input value +	 * @param   array   input names to match against +	 * @return  bool +	 */ +	public function matches($str, array $inputs) +	{ +		foreach ($inputs as $key) +		{ +			if ($str !== (isset($this[$key]) ? $this[$key] : NULL)) +				return FALSE; +		} + +		return TRUE; +	} + +	/** +	 * Rule: length. Generates an error if the field is too long or too short. +	 * +	 * @param   mixed   input value +	 * @param   array   minimum, maximum, or exact length to match +	 * @return  bool +	 */ +	public function length($str, array $length) +	{ +		if ( ! is_string($str)) +			return FALSE; + +		$size = utf8::strlen($str); +		$status = FALSE; + +		if (count($length) > 1) +		{ +			list ($min, $max) = $length; + +			if ($size >= $min AND $size <= $max) +			{ +				$status = TRUE; +			} +		} +		else +		{ +			$status = ($size === (int) $length[0]); +		} + +		return $status; +	} + +	/** +	 * Rule: depends_on. Generates an error if the field does not depend on one +	 * or more other fields. +	 * +	 * @param   mixed   field name +	 * @param   array   field names to check dependency +	 * @return  bool +	 */ +	public function depends_on($field, array $fields) +	{ +		foreach ($fields as $depends_on) +		{ +			if ( ! isset($this[$depends_on]) OR $this[$depends_on] == NULL) +				return FALSE; +		} + +		return TRUE; +	} + +	/** +	 * Rule: chars. Generates an error if the field contains characters outside of the list. +	 * +	 * @param   string  field value +	 * @param   array   allowed characters +	 * @return  bool +	 */ +	public function chars($value, array $chars) +	{ +		return ! preg_match('![^'.implode('', $chars).']!u', $value); +	} + +} // End Validation  | 
