validation(false) != null) { throw new \DomainException('Form instance already exists, cannot be recreated. Use instance() instead of forge() to retrieve the existing instance.'); } } return new static($fieldset); } public static function instance($name = null) { $fieldset = \Fieldset::instance($name); return $fieldset === false ? false : $fieldset->validation(); } /** * Fetch the currently active validation instance * * @return Validation */ public static function active() { return static::$active; } /** * Set or unset the currently active validation instance */ protected static function set_active($instance = null) { static::$active = $instance; } /** * Fetch the field currently being validated */ public static function active_field() { return static::$active_field; } /** * Set or unset the current field being validated */ protected static function set_active_field($instance = null) { static::$active_field = $instance; } /** * @var Fieldset the fieldset this instance validates */ protected $fieldset; /** * @var array available after validation started running: contains given input values */ protected $input = array(); /** * @var array contains values of fields that validated successfully */ protected $validated = array(); /** * @var array contains Validation_Error instances of encountered errors */ protected $errors = array(); /** * @var array contains a list of classnames and objects that may contain validation methods */ protected $callables = array(); /** * @var bool $global_input_fallback whether to fall back to Input::param */ protected $global_input_fallback = true; /** * @var array contains validation error messages, will overwrite those from lang files */ protected $error_messages = array(); protected function __construct($fieldset) { if ($fieldset instanceof Fieldset) { $fieldset->validation($this); $this->fieldset = $fieldset; } else { $this->fieldset = \Fieldset::forge($fieldset, array('validation_instance' => $this)); } $this->callables = array($this); $this->global_input_fallback = \Config::get('validation.global_input_fallback', true); } /** * Returns the related fieldset * * @return Fieldset */ public function fieldset() { return $this->fieldset; } /** * Simpler alias for Validation->add() * * @param string $name Field name * @param string $label Field label * @param string $rules Rules as a piped string * @return Fieldset_Field $this to allow chaining * @depricated Remove in v2.0, passing rules as string is to be removed use add() instead */ public function add_field($name, $label, $rules) { $field = $this->add($name, $label); is_array($rules) or $rules = explode('|', $rules); foreach ($rules as $rule) { if (($pos = strpos($rule, '[')) !== false) { preg_match('#\[(.*)\]#', $rule, $param); $rule = substr($rule, 0, $pos); // deal with rules that have comma's in the rule parameter if (in_array($rule, array('match_pattern'))) { call_fuel_func_array(array($field, 'add_rule'), array_merge(array($rule), array($param[1]))); } elseif (in_array($rule, array('valid_string'))) { call_fuel_func_array(array($field, 'add_rule'), array_merge(array($rule), array(explode(',', $param[1])))); } else { call_fuel_func_array(array($field, 'add_rule'), array_merge(array($rule), explode(',', $param[1]))); } } else { $field->add_rule($rule); } } return $field; } /** * This will overwrite lang file messages for this validation instance * * @param string * @param string * @return Validation this, to allow chaining */ public function set_message($rule, $message) { if ($message !== null) { $this->error_messages[$rule] = $message; } else { unset($this->error_messages[$rule]); } return $this; } /** * Fetches a specific error message for this validation instance * * @param string * @return string */ public function get_message($rule) { if ( ! array_key_exists($rule, $this->error_messages)) { return false; } return $this->error_messages[$rule]; } /** * Add Callable * * Adds an object for which you don't need to write a full callback, just * the method as a string will do. This also allows for overwriting functionality * from this object because the new class is prepended. * * @param string|Object $class Classname or object * @return Validation this, to allow chaining */ public function add_callable($class) { if ( ! (is_object($class) || class_exists($class))) { throw new \InvalidArgumentException('Input for add_callable is not a valid object or class.'); } // Prevent having the same class twice in the array, remove to re-add on top if... foreach ($this->callables as $key => $c) { // ...it already exists in callables if ($c === $class) { unset($this->callables[$key]); } // ...new object/class extends it or an instance of it elseif (is_string($c) and (is_subclass_of($class, $c) or (is_object($class) and is_a($class, $c)))) { unset($this->callables[$key]); } // but if there's a subclass in there to the new one, put the subclass on top and forget the new elseif (is_string($class) and (is_subclass_of($c, $class) or (is_object($c) and is_a($c, $class)))) { unset($this->callables[$key]); $class = $c; } } array_unshift($this->callables, $class); return $this; } /* * Remove Callable * * Removes an object from the callables array * * @param string|Object $class Classname or object * @return Validation this, to allow chaining */ public function remove_callable($class) { if (($key = array_search($class, $this->callables, true))) { unset($this->callables[$key]); } return $this; } /** * Fetch the objects for which you don't need to add a full callback but * just the method name * * @return array */ public function callables() { return $this->callables; } /** * Run validation * * Performs validation with current fieldset and on given input, will try POST * when input wasn't given. * * @param array $input input that overwrites POST values * @param bool $allow_partial will skip validation of values it can't find or are null * @return bool $temp_callables whether validation succeeded */ public function run($input = null, $allow_partial = false, $temp_callables = array()) { if (is_null($input) and \Input::method() != 'POST') { return false; } // Backup current state of callables so they can be restored after adding temp callables $callable_backup = $this->callables; // Add temporary callables, reversed so first ends on top foreach (array_reverse($temp_callables) as $temp_callable) { $this->add_callable($temp_callable); } static::set_active($this); $this->validated = array(); $this->errors = array(); $this->input = $input ?: array(); $fields = $this->field(null, true); foreach($fields as $field) { static::set_active_field($field); // convert form field array's to Fuel dotted notation $name = str_replace(array('[', ']'), array('.', ''), $field->name); $value = $this->input($name); if (($allow_partial === true and $value === null) or (is_array($allow_partial) and ! in_array($field->name, $allow_partial))) { continue; } try { foreach ($field->rules as $rule) { $callback = $rule[0]; $params = $rule[1]; $this->_run_rule($callback, $value, $params, $field); } if (strpos($name, '.') !== false) { \Arr::set($this->validated, $name, $value); } else { $this->validated[$name] = $value; } } catch (Validation_Error $v) { $this->errors[$field->name] = $v; if($field->fieldset()) { $field->fieldset()->Validation()->add_error($field->name, $v); } } } static::set_active(); static::set_active_field(); // Restore callables $this->callables = $callable_backup; return empty($this->errors); } /** * Takes the rule input and formats it into a name & callback * * @param string|array $callback short rule to be called on Validation callables array or full callback * @return array|bool rule array or false when it fails to find something callable */ protected function _find_rule($callback) { // Rules are validated and only accepted when given as an array consisting of // array(callback, params) or just callbacks in an array. if (is_string($callback)) { $callback_method = '_validation_'.$callback; foreach ($this->callables as $callback_class) { if (method_exists($callback_class, $callback_method)) { return array($callback => array($callback_class, $callback_method)); } } } // when no callable function was found, try regular callbacks if (is_callable($callback)) { if ($callback instanceof \Closure) { $callback_name = 'closure'; } elseif (is_array($callback)) { $callback_name = preg_replace('#^([a-z_]*\\\\)*#i', '', is_object($callback[0]) ? get_class($callback[0]) : $callback[0]).':'.$callback[1]; } else { $callback_name = preg_replace('#^([a-z_]*\\\\)*#i', '', str_replace('::', ':', $callback)); } return array($callback_name => $callback); } elseif (is_array($callback) and is_callable(reset($callback))) { return $callback; } else { $string = ! is_array($callback) ? $callback : (is_object(@$callback[0]) ? get_class(@$callback[0]).'->'.@$callback[1] : @$callback[0].'::'.@$callback[1]); \Errorhandler::notice('Invalid rule "'.$string.'" passed to Validation, not used.'); return false; } } /** * Run rule * * Performs a single rule on a field and its value * * @param callback $rule * @param mixed $value Value by reference, will be edited * @param array $params Extra parameters * @param array $field Validation field description * @throws \Validation_Error */ protected function _run_rule($rule, &$value, $params, $field) { if (($rule = $this->_find_rule($rule)) === false) { return; } $output = call_fuel_func_array(reset($rule), array_merge(array($value), $params)); if ($output === false and ($value !== false or key($rule) == 'required')) { throw new \Validation_Error($field, $value, $rule, $params); } elseif ($output !== true) { $value = $output; } } /** * Fetches the input value from either post or given input * * @param string $key * @param mixed $default * @return mixed|array the input value or full input values array */ public function input($key = null, $default = null) { if ($key === null) { return $this->input; } // key transformation from form array to dot notation if (strpos($key, '[') !== false) { $key = str_replace(array('[', ']'), array('.', ''), $key); } // if we don't have this key if ( ! array_key_exists($key, $this->input)) { // it might be in dot-notation if (strpos($key, '.') !== false) { // check the input first if (($result = \Arr::get($this->input, $key, null)) !== null) { $this->input[$key] = $result; } else { $this->input[$key] = $this->global_input_fallback ? \Arr::get(\Input::param(), $key, $default) : $default; } } else { // do a fallback to global input if needed, or use the provided default $this->input[$key] = $this->global_input_fallback ? \Input::param($key, $default) : $default; } } return $this->input[$key]; } /** * Validated * * Returns specific validated value or all validated field=>value pairs * * @param string $field fieldname * @param mixed $default value to return when not validated * @return mixed|array the validated value or full validated values array */ public function validated($field = null, $default = false) { if ($field === null) { return $this->validated; } return array_key_exists($field, $this->validated) ? $this->validated[$field] : $default; } /** * Error * * Return specific error or all errors thrown during validation * * @param string $field fieldname * @param mixed $default value to return when not validated * @return Validation_Error|array the validation error object or full array of error objects */ public function error($field = null, $default = false) { if ($field === null) { return $this->errors; } return array_key_exists($field, $this->errors) ? $this->errors[$field] : $default; } /** * Return error message * * Return specific error message or all error messages thrown during validation * * @param string $field fieldname * @param mixed $default value to return when not validated * @return string|array the error message or full array of error messages */ public function error_message($field = null, $default = false) { if ($field === null) { $messages = array(); foreach ($this->error() as $field => $e) { $messages[$field] = $e->get_message(); } return $messages; } return array_key_exists($field, $this->errors) ? $this->errors[$field]->get_message() : $default; } /** * Show errors * * Returns all errors in a list or with set markup from $options param * * @param array $options uses keys open_list, close_list, open_error, close_error & no_errors * @return string */ public function show_errors($options = array()) { $default = array( 'open_list' => \Config::get('validation.open_list', '