| 34 | | |
|---|
| 35 | | // Data to validate |
|---|
| 36 | | protected $data = array(); |
|---|
| 37 | | |
|---|
| 38 | | // Result from validation rules |
|---|
| 39 | | protected $result; |
|---|
| 40 | | |
|---|
| 41 | | /** |
|---|
| 42 | | * @param array array to validate |
|---|
| 43 | | */ |
|---|
| 44 | | public function __construct( & $data = array()) |
|---|
| 45 | | { |
|---|
| 46 | | $this->set_data($data); |
|---|
| 47 | | |
|---|
| 48 | | // Load the default error messages |
|---|
| 49 | | $this->messages = Kohana::lang('validation'); |
|---|
| 50 | | |
|---|
| 51 | | // Add one more instance to the count |
|---|
| 52 | | self::$instances++; |
|---|
| 53 | | |
|---|
| 54 | | Log::add('debug', 'Validation Library Initialized, instance '.self::$instances); |
|---|
| 55 | | } |
|---|
| 56 | | |
|---|
| 57 | | /** |
|---|
| 58 | | * Magically gets a validation variable. This can be an error string or a |
|---|
| 59 | | * data field, or an array of all field data. |
|---|
| 60 | | * |
|---|
| 61 | | * @param string Variable name |
|---|
| 62 | | * @return string|array The variable contents or NULL if the variable does not exist |
|---|
| 63 | | */ |
|---|
| 64 | | public function __get($key) |
|---|
| 65 | | { |
|---|
| 66 | | if ( ! isset($this->$key)) |
|---|
| 67 | | { |
|---|
| 68 | | if ($key === 'error_string') |
|---|
| 69 | | { |
|---|
| 70 | | // Complete error message string |
|---|
| 71 | | $messages = FALSE; |
|---|
| 72 | | foreach(array_keys($this->errors) as $field) |
|---|
| 73 | | { |
|---|
| 74 | | $messages .= $this->__get($field.'_error'); |
|---|
| 75 | | } |
|---|
| 76 | | return $messages; |
|---|
| 77 | | } |
|---|
| 78 | | elseif (substr($key, -6) === '_error') |
|---|
| 79 | | { |
|---|
| 80 | | // Get the field name |
|---|
| 81 | | $field = substr($key, 0, -6); |
|---|
| 82 | | |
|---|
| 83 | | // Return the error messages for this field |
|---|
| 84 | | $messages = FALSE; |
|---|
| 85 | | if (isset($this->errors[$field]) AND ! empty($this->errors[$field])) |
|---|
| 86 | | { |
|---|
| 87 | | foreach($this->errors[$field] as $error) |
|---|
| | 30 | protected $messages = array(); |
|---|
| | 31 | |
|---|
| | 32 | /** |
|---|
| | 33 | * Creates a new Validation instance. |
|---|
| | 34 | * |
|---|
| | 35 | * @param array array to use for validation |
|---|
| | 36 | * @return object |
|---|
| | 37 | */ |
|---|
| | 38 | public static function factory($array = NULL) |
|---|
| | 39 | { |
|---|
| | 40 | return new Validation( ! is_array($array) ? $_POST : $array); |
|---|
| | 41 | } |
|---|
| | 42 | |
|---|
| | 43 | /** |
|---|
| | 44 | * Sets the unique "any field" key and creates an ArrayObject from the |
|---|
| | 45 | * passed array. |
|---|
| | 46 | * |
|---|
| | 47 | * @param array array to validate |
|---|
| | 48 | * @return void |
|---|
| | 49 | */ |
|---|
| | 50 | public function __construct(array $array) |
|---|
| | 51 | { |
|---|
| | 52 | // Set a dynamic, unique "any field" key |
|---|
| | 53 | $this->any_field = uniqid(NULL, TRUE); |
|---|
| | 54 | |
|---|
| | 55 | parent::__construct($array, ArrayObject::ARRAY_AS_PROPS | ArrayObject::STD_PROP_LIST); |
|---|
| | 56 | } |
|---|
| | 57 | |
|---|
| | 58 | /** |
|---|
| | 59 | * Returns the ArrayObject array values. |
|---|
| | 60 | * |
|---|
| | 61 | * @return array |
|---|
| | 62 | */ |
|---|
| | 63 | public function as_array() |
|---|
| | 64 | { |
|---|
| | 65 | return $this->getArrayCopy(); |
|---|
| | 66 | } |
|---|
| | 67 | |
|---|
| | 68 | /** |
|---|
| | 69 | * Set the format of message strings. |
|---|
| | 70 | * |
|---|
| | 71 | * @chainable |
|---|
| | 72 | * @param string new message format |
|---|
| | 73 | * @return object |
|---|
| | 74 | */ |
|---|
| | 75 | public function message_format($str) |
|---|
| | 76 | { |
|---|
| | 77 | if (strpos($str, '{message}') === FALSE) |
|---|
| | 78 | throw new Kohana_Exception('validation.error_format'); |
|---|
| | 79 | |
|---|
| | 80 | // Set the new message format |
|---|
| | 81 | $this->message_format = $str; |
|---|
| | 82 | |
|---|
| | 83 | return $this; |
|---|
| | 84 | } |
|---|
| | 85 | |
|---|
| | 86 | /** |
|---|
| | 87 | * Sets or returns the message for an input. |
|---|
| | 88 | * |
|---|
| | 89 | * @chainable |
|---|
| | 90 | * @param string input key |
|---|
| | 91 | * @param string message to set |
|---|
| | 92 | * @return string|object |
|---|
| | 93 | */ |
|---|
| | 94 | public function message($input, $message = NULL) |
|---|
| | 95 | { |
|---|
| | 96 | if ($message === NULL) |
|---|
| | 97 | { |
|---|
| | 98 | // Return nothing if no message exists |
|---|
| | 99 | if (empty($this->messages[$input])) |
|---|
| | 100 | return ''; |
|---|
| | 101 | |
|---|
| | 102 | // Return the HTML message string |
|---|
| | 103 | return str_replace('{message}', $this->messages[$input], $this->message_format); |
|---|
| | 104 | } |
|---|
| | 105 | else |
|---|
| | 106 | { |
|---|
| | 107 | $this->messages[$input] = $message; |
|---|
| | 108 | } |
|---|
| | 109 | |
|---|
| | 110 | return $this; |
|---|
| | 111 | } |
|---|
| | 112 | |
|---|
| | 113 | /** |
|---|
| | 114 | * Add a pre-filter to one or more inputs. |
|---|
| | 115 | * |
|---|
| | 116 | * @chainable |
|---|
| | 117 | * @param callback filter |
|---|
| | 118 | * @param string fields to apply filter to, use TRUE for all fields |
|---|
| | 119 | * @return object |
|---|
| | 120 | */ |
|---|
| | 121 | public function pre_filter($filter, $field = TRUE) |
|---|
| | 122 | { |
|---|
| | 123 | if ( ! is_callable($filter)) |
|---|
| | 124 | throw new Kohana_Exception('validation.filter_not_callable'); |
|---|
| | 125 | |
|---|
| | 126 | if ($field === TRUE) |
|---|
| | 127 | { |
|---|
| | 128 | // Handle "any field" filters |
|---|
| | 129 | $fields = $this->any_field; |
|---|
| | 130 | } |
|---|
| | 131 | else |
|---|
| | 132 | { |
|---|
| | 133 | // Add the filter to specific inputs |
|---|
| | 134 | $fields = func_get_args(); |
|---|
| | 135 | $fields = array_slice($fields, 1); |
|---|
| | 136 | } |
|---|
| | 137 | |
|---|
| | 138 | foreach ($fields as $field) |
|---|
| | 139 | { |
|---|
| | 140 | // Add the filter to specified field |
|---|
| | 141 | $this->pre_filters[$field][] = $filter; |
|---|
| | 142 | } |
|---|
| | 143 | |
|---|
| | 144 | return $this; |
|---|
| | 145 | } |
|---|
| | 146 | |
|---|
| | 147 | /** |
|---|
| | 148 | * Add a post-filter to one or more inputs. |
|---|
| | 149 | * |
|---|
| | 150 | * @chainable |
|---|
| | 151 | * @param callback filter |
|---|
| | 152 | * @param string fields to apply filter to, use TRUE for all fields |
|---|
| | 153 | * @return object |
|---|
| | 154 | */ |
|---|
| | 155 | public function post_filter($filter, $field = TRUE) |
|---|
| | 156 | { |
|---|
| | 157 | if ( ! is_callable($filter, TRUE)) |
|---|
| | 158 | throw new Kohana_Exception('validation.filter_not_callable'); |
|---|
| | 159 | |
|---|
| | 160 | if ($field === TRUE) |
|---|
| | 161 | { |
|---|
| | 162 | // Handle "any field" filters |
|---|
| | 163 | $fields = $this->any_field; |
|---|
| | 164 | } |
|---|
| | 165 | else |
|---|
| | 166 | { |
|---|
| | 167 | // Add the filter to specific inputs |
|---|
| | 168 | $fields = func_get_args(); |
|---|
| | 169 | $fields = array_slice($fields, 1); |
|---|
| | 170 | } |
|---|
| | 171 | |
|---|
| | 172 | foreach ($fields as $field) |
|---|
| | 173 | { |
|---|
| | 174 | // Add the filter to specified field |
|---|
| | 175 | $this->post_filters[$field][] = $filter; |
|---|
| | 176 | } |
|---|
| | 177 | |
|---|
| | 178 | return $this; |
|---|
| | 179 | } |
|---|
| | 180 | |
|---|
| | 181 | /** |
|---|
| | 182 | * Add rules to a field. Rules are callbacks or validation methods. Rules can |
|---|
| | 183 | * only return TRUE or FALSE. |
|---|
| | 184 | * |
|---|
| | 185 | * @chainable |
|---|
| | 186 | * @param string field name |
|---|
| | 187 | * @param callback rules (unlimited number) |
|---|
| | 188 | * @return object |
|---|
| | 189 | */ |
|---|
| | 190 | public function add_rules($field, $rules) |
|---|
| | 191 | { |
|---|
| | 192 | // Handle "any field" filters |
|---|
| | 193 | ($field === TRUE) and $field = $this->any_field; |
|---|
| | 194 | |
|---|
| | 195 | // Get the rules |
|---|
| | 196 | $rules = func_get_args(); |
|---|
| | 197 | $rules = array_slice($rules, 1); |
|---|
| | 198 | |
|---|
| | 199 | foreach ($rules as $rule) |
|---|
| | 200 | { |
|---|
| | 201 | // Rule arguments |
|---|
| | 202 | $args = NULL; |
|---|
| | 203 | |
|---|
| | 204 | if (is_string($rule)) |
|---|
| | 205 | { |
|---|
| | 206 | if (preg_match('/^([^\[]++)\[(.+)\]$/', $rule, $matches)) |
|---|
| | 207 | { |
|---|
| | 208 | // Split the rule into the function and args |
|---|
| | 209 | $rule = $matches[1]; |
|---|
| | 210 | $args = preg_split('/(?<!\\\\),\s*/', $matches[2]); |
|---|
| | 211 | } |
|---|
| | 212 | |
|---|
| | 213 | if (method_exists($this, $rule)) |
|---|
| | 214 | { |
|---|
| | 215 | // Make the rule a valid callback |
|---|
| | 216 | $rule = array($this, $rule); |
|---|
| | 217 | } |
|---|
| | 218 | } |
|---|
| | 219 | |
|---|
| | 220 | if ( ! is_callable($rule, TRUE)) |
|---|
| | 221 | throw new Kohana_Exception('validation.rule_not_callable'); |
|---|
| | 222 | |
|---|
| | 223 | // Add the rule to specified field |
|---|
| | 224 | $this->rules[$field][] = array($rule, $args); |
|---|
| | 225 | } |
|---|
| | 226 | |
|---|
| | 227 | return $this; |
|---|
| | 228 | } |
|---|
| | 229 | |
|---|
| | 230 | /** |
|---|
| | 231 | * Add callbacks to a field. Callbacks must accept the Validation object |
|---|
| | 232 | * and the input name. Callback returns are not processed. |
|---|
| | 233 | * |
|---|
| | 234 | * @chainable |
|---|
| | 235 | * @param string field name |
|---|
| | 236 | * @param callbacks callbacks (unlimited number) |
|---|
| | 237 | * @return object |
|---|
| | 238 | */ |
|---|
| | 239 | public function add_callbacks($field, $callbacks) |
|---|
| | 240 | { |
|---|
| | 241 | // Handle "any field" filters |
|---|
| | 242 | ($field === TRUE) and $field = $this->any_field; |
|---|
| | 243 | |
|---|
| | 244 | if (func_get_args() > 2) |
|---|
| | 245 | { |
|---|
| | 246 | // Multiple callback |
|---|
| | 247 | $callbacks = func_get_args(); |
|---|
| | 248 | $callbacks = array_slice($callbacks, 1); |
|---|
| | 249 | } |
|---|
| | 250 | else |
|---|
| | 251 | { |
|---|
| | 252 | // Only one callback |
|---|
| | 253 | $callbacks = array($callbacks); |
|---|
| | 254 | } |
|---|
| | 255 | |
|---|
| | 256 | foreach ($callbacks as $callback) |
|---|
| | 257 | { |
|---|
| | 258 | if ( ! is_callable($callback, TRUE)) |
|---|
| | 259 | throw new Kohana_Exception('validation.callback_not_callable'); |
|---|
| | 260 | |
|---|
| | 261 | // Add the filter to specified field |
|---|
| | 262 | $this->callbacks[$field][] = $callback; |
|---|
| | 263 | } |
|---|
| | 264 | |
|---|
| | 265 | return $this; |
|---|
| | 266 | } |
|---|
| | 267 | |
|---|
| | 268 | /** |
|---|
| | 269 | * Validate by processing pre-filters, rules, callbacks, and post-filters. |
|---|
| | 270 | * All fields that have filters, rules, or callbacks will be initialized if |
|---|
| | 271 | * they are undefined. Validation will only be run if there is data already |
|---|
| | 272 | * in the array. |
|---|
| | 273 | * |
|---|
| | 274 | * @return bool |
|---|
| | 275 | */ |
|---|
| | 276 | public function validate() |
|---|
| | 277 | { |
|---|
| | 278 | // All the fields that are being validated |
|---|
| | 279 | $all_fields = array_unique(array_merge |
|---|
| | 280 | ( |
|---|
| | 281 | array_keys($this->pre_filters), |
|---|
| | 282 | array_keys($this->rules), |
|---|
| | 283 | array_keys($this->callbacks), |
|---|
| | 284 | array_keys($this->post_filters) |
|---|
| | 285 | )); |
|---|
| | 286 | |
|---|
| | 287 | // Only run validation when POST data exists |
|---|
| | 288 | $run_validation = (count($this) > 0); |
|---|
| | 289 | |
|---|
| | 290 | foreach ($all_fields as $i => $field) |
|---|
| | 291 | { |
|---|
| | 292 | if ($field === $this->any_field) |
|---|
| | 293 | { |
|---|
| | 294 | // Remove "any field" from the list of fields |
|---|
| | 295 | unset($all_fields[$i]); |
|---|
| | 296 | continue; |
|---|
| | 297 | } |
|---|
| | 298 | |
|---|
| | 299 | // Make sure all fields are defined |
|---|
| | 300 | isset($this[$field]) or $this[$field] = NULL; |
|---|
| | 301 | } |
|---|
| | 302 | |
|---|
| | 303 | if ($run_validation === FALSE) |
|---|
| | 304 | return FALSE; |
|---|
| | 305 | |
|---|
| | 306 | // Reset all fields to ALL defined fields |
|---|
| | 307 | $all_fields = array_keys($this->getArrayCopy()); |
|---|
| | 308 | |
|---|
| | 309 | foreach ($this->pre_filters as $field => $calls) |
|---|
| | 310 | { |
|---|
| | 311 | foreach ($calls as $func) |
|---|
| | 312 | { |
|---|
| | 313 | if ($field === $this->any_field) |
|---|
| | 314 | { |
|---|
| | 315 | foreach ($all_fields as $f) |
|---|
| 106 | | $data[$key] = $this->data[$key]; |
|---|
| 107 | | } |
|---|
| 108 | | } |
|---|
| 109 | | return $data; |
|---|
| 110 | | } |
|---|
| 111 | | } |
|---|
| 112 | | } |
|---|
| 113 | | |
|---|
| 114 | | /** |
|---|
| 115 | | * This function takes an array of key names, rules, and field names as |
|---|
| 116 | | * input and sets internal field information. |
|---|
| 117 | | * |
|---|
| 118 | | * @param string|array Key names |
|---|
| 119 | | * @param string Rules |
|---|
| 120 | | * @param string Field name |
|---|
| 121 | | * @return void |
|---|
| 122 | | */ |
|---|
| 123 | | public function set_rules($data, $rules = '', $field = FALSE) |
|---|
| 124 | | { |
|---|
| 125 | | // Normalize rules to an array |
|---|
| 126 | | if ( ! is_array($data)) |
|---|
| 127 | | { |
|---|
| 128 | | if ($rules == '') return FALSE; |
|---|
| 129 | | |
|---|
| 130 | | // Make data into an array |
|---|
| 131 | | $data = array($data => array($field, $rules)); |
|---|
| 132 | | } |
|---|
| 133 | | |
|---|
| 134 | | // Set the field information |
|---|
| 135 | | foreach ($data as $name => $rules) |
|---|
| 136 | | { |
|---|
| 137 | | if (is_array($rules)) |
|---|
| 138 | | { |
|---|
| 139 | | if (count($rules) > 1) |
|---|
| 140 | | { |
|---|
| 141 | | $field = current($rules); |
|---|
| 142 | | $rules = next($rules); |
|---|
| 143 | | } |
|---|
| 144 | | else |
|---|
| 145 | | { |
|---|
| 146 | | $rules = current($rules); |
|---|
| 147 | | } |
|---|
| 148 | | } |
|---|
| 149 | | |
|---|
| 150 | | // Empty field names default to the name of the element |
|---|
| 151 | | $this->fields[$name] = empty($field) ? $name : $field; |
|---|
| 152 | | $this->rules[$name] = $rules; |
|---|
| 153 | | |
|---|
| 154 | | // Prevent fields from getting the wrong name |
|---|
| 155 | | unset($field); |
|---|
| 156 | | } |
|---|
| 157 | | } |
|---|
| 158 | | |
|---|
| 159 | | /** |
|---|
| 160 | | * Lets users set their own error messages on the fly. |
|---|
| 161 | | * Note - The key name has to match the function name that it corresponds to. |
|---|
| 162 | | * |
|---|
| 163 | | * @param string Function name |
|---|
| 164 | | * @param string Error message |
|---|
| 165 | | * @return void |
|---|
| 166 | | */ |
|---|
| 167 | | public function set_message($func, $message = '') |
|---|
| 168 | | { |
|---|
| 169 | | if ( ! is_array($func)) |
|---|
| 170 | | { |
|---|
| 171 | | $func = array($func => $message); |
|---|
| 172 | | } |
|---|
| 173 | | |
|---|
| 174 | | foreach($func as $name => $message) |
|---|
| 175 | | { |
|---|
| 176 | | $this->messages[$name] = $message; |
|---|
| 177 | | } |
|---|
| 178 | | } |
|---|
| 179 | | |
|---|
| 180 | | /** |
|---|
| 181 | | * @param array Data to validate |
|---|
| 182 | | * @return void |
|---|
| 183 | | */ |
|---|
| 184 | | public function set_data( & $data) |
|---|
| 185 | | { |
|---|
| 186 | | if ( ! empty($data) AND is_array($data)) |
|---|
| 187 | | { |
|---|
| 188 | | $this->data =& $data; |
|---|
| 189 | | } |
|---|
| 190 | | else |
|---|
| 191 | | { |
|---|
| 192 | | $this->data =& $_POST; |
|---|
| 193 | | } |
|---|
| 194 | | } |
|---|
| 195 | | |
|---|
| 196 | | /** |
|---|
| 197 | | * Allows the user to change the error message format. Error formats must |
|---|
| 198 | | * contain the string "{message}" or Kohana_Exception will be triggered. |
|---|
| 199 | | * |
|---|
| 200 | | * @param string Error message format |
|---|
| 201 | | * @return void |
|---|
| 202 | | */ |
|---|
| 203 | | public function error_format($string = '') |
|---|
| 204 | | { |
|---|
| 205 | | if (strpos((string) $string, '{message}') === FALSE) |
|---|
| 206 | | throw new Kohana_Exception('validation.error_format'); |
|---|
| 207 | | |
|---|
| 208 | | $this->error_format = $string; |
|---|
| 209 | | } |
|---|
| 210 | | |
|---|
| 211 | | /** |
|---|
| 212 | | * @param string Function name |
|---|
| 213 | | * @param string field name |
|---|
| 214 | | * @return void |
|---|
| 215 | | */ |
|---|
| 216 | | public function add_error($func, $field) |
|---|
| 217 | | { |
|---|
| 218 | | // Set the friendly field name |
|---|
| 219 | | $friendly = isset($this->fields[$field]) ? $this->fields[$field] : $field; |
|---|
| 220 | | |
|---|
| 221 | | // Fetch the message |
|---|
| 222 | | $message = isset($this->messages[$func]) ? $this->messages[$func] : $this->messages['unknown_error']; |
|---|
| 223 | | |
|---|
| 224 | | // Replacements in strings |
|---|
| 225 | | $replace = array_slice(func_get_args(), 1); |
|---|
| 226 | | |
|---|
| 227 | | if ( ! empty($replace) AND $replace[0] === $field) |
|---|
| 228 | | { |
|---|
| 229 | | // Add the friendly name instead of the field name |
|---|
| 230 | | $replace[0] = $friendly; |
|---|
| 231 | | } |
|---|
| 232 | | |
|---|
| 233 | | // Add the field name into the message, if there is a place for it |
|---|
| 234 | | $message = (strpos($message, '%s') !== FALSE) ? vsprintf($message, $replace) : $message; |
|---|
| 235 | | |
|---|
| 236 | | $this->errors[$field][] = $message; |
|---|
| 237 | | } |
|---|
| 238 | | |
|---|
| 239 | | /** |
|---|
| 240 | | * This function does all the work. |
|---|
| 241 | | * |
|---|
| 242 | | * @return boolean The validation result |
|---|
| 243 | | */ |
|---|
| 244 | | public function run() |
|---|
| 245 | | { |
|---|
| 246 | | // Do we even have any data to process? Mm? |
|---|
| 247 | | if (count($this->data) == 0 OR count($this->rules) == 0) |
|---|
| 248 | | { |
|---|
| 249 | | return FALSE; |
|---|
| 250 | | } |
|---|
| 251 | | |
|---|
| 252 | | // Cycle through the rules and test for errors |
|---|
| 253 | | foreach ($this->rules as $field => $rules) |
|---|
| 254 | | { |
|---|
| 255 | | // Set the current field, for other functions to use |
|---|
| 256 | | $this->current_field = $field; |
|---|
| 257 | | |
|---|
| 258 | | // Insert uploads into the data |
|---|
| 259 | | if (strpos($rules, 'upload') !== FALSE AND isset($_FILES[$field])) |
|---|
| 260 | | { |
|---|
| 261 | | if (is_array($_FILES[$field]['error'])) |
|---|
| 262 | | { |
|---|
| 263 | | foreach($_FILES[$field]['error'] as $error) |
|---|
| 264 | | { |
|---|
| 265 | | if ($error !== UPLOAD_ERR_NO_FILE) |
|---|
| | 340 | // Prevent other rules from running when this field already has errors |
|---|
| | 341 | if ( ! empty($this->errors[$f])) break; |
|---|
| | 342 | |
|---|
| | 343 | // Don't process rules on empty fields |
|---|
| | 344 | if (($func[1] !== 'required' AND $func[1] !== 'matches') AND empty($this[$f])) |
|---|
| | 345 | continue; |
|---|
| | 346 | |
|---|
| | 347 | // Run each rule |
|---|
| | 348 | if ( ! call_user_func($func, $this[$f], $args)) |
|---|
| 272 | | elseif ($_FILES[$field]['error'] !== UPLOAD_ERR_NO_FILE) |
|---|
| 273 | | { |
|---|
| 274 | | $this->data[$field] = $_FILES[$field]; |
|---|
| 275 | | } |
|---|
| 276 | | } |
|---|
| 277 | | |
|---|
| 278 | | // Process empty fields |
|---|
| 279 | | if ( ! isset($this->data[$field]) OR $this->data[$field] == NULL) |
|---|
| 280 | | { |
|---|
| 281 | | // This field is required |
|---|
| 282 | | if (strpos($rules, 'required') !== FALSE) |
|---|
| 283 | | { |
|---|
| 284 | | $this->add_error('required', $field); |
|---|
| 285 | | } |
|---|
| 286 | | continue; |
|---|
| 287 | | } |
|---|
| 288 | | |
|---|
| 289 | | // Loop through the rules and process each one |
|---|
| 290 | | foreach(explode('|', $rules) as $rule) |
|---|
| 291 | | { |
|---|
| 292 | | // To properly handle recursion |
|---|
| 293 | | $this->run_rule($rule, $field); |
|---|
| 294 | | |
|---|
| 295 | | // Stop validating when there is an error |
|---|
| 296 | | if ($this->result === FALSE) |
|---|
| 297 | | break; |
|---|
| 298 | | } |
|---|
| 299 | | } |
|---|
| 300 | | |
|---|
| 301 | | // Run validation finished Event and return |
|---|
| 302 | | if (count($this->errors) == 0) |
|---|
| 303 | | { |
|---|
| 304 | | Event::run('validation.success', $this->data); |
|---|
| 305 | | return TRUE; |
|---|
| | 354 | else |
|---|
| | 355 | { |
|---|
| | 356 | // Prevent other rules from running when this field already has errors |
|---|
| | 357 | if ( ! empty($this->errors[$field])) break; |
|---|
| | 358 | |
|---|
| | 359 | // Don't process rules on empty fields |
|---|
| | 360 | if (($func[1] !== 'required' AND $func[1] !== 'matches') AND empty($this[$field])) |
|---|
| | 361 | continue; |
|---|
| | 362 | |
|---|
| | 363 | // Run each rule |
|---|
| | 364 | if ( ! call_user_func($func, $this[$field], $args)) |
|---|
| | 365 | { |
|---|
| | 366 | $this->errors[$field] = is_array($func) ? $func[1] : $func; |
|---|
| | 367 | // Stop after an error is found |
|---|
| | 368 | break; |
|---|
| | 369 | } |
|---|
| | 370 | } |
|---|
| | 371 | } |
|---|
| | 372 | } |
|---|
| | 373 | |
|---|
| | 374 | foreach ($this->callbacks as $field => $calls) |
|---|
| | 375 | { |
|---|
| | 376 | foreach ($calls as $func) |
|---|
| | 377 | { |
|---|
| | 378 | if ($field === $this->any_field) |
|---|
| | 379 | { |
|---|
| | 380 | foreach ($all_fields as $f) |
|---|
| | 381 | { |
|---|
| | 382 | // Execute the callback |
|---|
| | 383 | call_user_func($func, $this, $f); |
|---|
| | 384 | |
|---|
| | 385 | // Stop after an error is found |
|---|
| | 386 | if ( ! empty($errors[$f])) break 2; |
|---|
| | 387 | } |
|---|
| | 388 | } |
|---|
| | 389 | else |
|---|
| | 390 | { |
|---|
| | 391 | // Execute the callback |
|---|
| | 392 | call_user_func($func, $this, $field); |
|---|
| | 393 | |
|---|
| | 394 | // Stop after an error is found |
|---|
| | 395 | if ( ! empty($errors[$f])) break; |
|---|
| | 396 | } |
|---|
| | 397 | } |
|---|
| | 398 | } |
|---|
| | 399 | |
|---|
| | 400 | foreach ($this->post_filters as $field => $calls) |
|---|
| | 401 | { |
|---|
| | 402 | foreach ($calls as $func) |
|---|
| | 403 | { |
|---|
| | 404 | if ($field === $this->any_field) |
|---|
| | 405 | { |
|---|
| | 406 | foreach ($all_fields as $f) |
|---|
| | 407 | { |
|---|
| | 408 | // Process each filter |
|---|
| | 409 | $this[$f] = is_array($this[$f]) ? array_map($func, $this[$f]) : call_user_func($func, $this[$f]); |
|---|
| | 410 | } |
|---|
| | 411 | } |
|---|
| | 412 | else |
|---|
| | 413 | { |
|---|
| | 414 | // Process each filter |
|---|
| | 415 | $this[$field] = is_array($this[$field]) ? array_map($func, $this[$field]) : call_user_func($func, $this[$field]); |
|---|
| | 416 | } |
|---|
| | 417 | } |
|---|
| | 418 | } |
|---|
| | 419 | |
|---|
| | 420 | // Return TRUE if there are no errors |
|---|
| | 421 | return (count($this->errors) === 0); |
|---|
| | 422 | } |
|---|
| | 423 | |
|---|
| | 424 | /** |
|---|
| | 425 | * Add an error to an input. |
|---|
| | 426 | * |
|---|
| | 427 | * @chainable |
|---|
| | 428 | * @param string input name |
|---|
| | 429 | * @param string unique error name |
|---|
| | 430 | * @return object |
|---|
| | 431 | */ |
|---|
| | 432 | public function add_error($field, $name) |
|---|
| | 433 | { |
|---|
| | 434 | if (isset($this[$field])) |
|---|
| | 435 | { |
|---|
| | 436 | $this->errors[$field] = $name; |
|---|
| | 437 | } |
|---|
| | 438 | |
|---|
| | 439 | return $this; |
|---|
| | 440 | } |
|---|
| | 441 | |
|---|
| | 442 | /** |
|---|
| | 443 | * Return the errors array. |
|---|
| | 444 | * |
|---|
| | 445 | * @return array |
|---|
| | 446 | */ |
|---|
| | 447 | public function errors() |
|---|
| | 448 | { |
|---|
| | 449 | return $this->errors; |
|---|
| | 450 | } |
|---|
| | 451 | |
|---|
| | 452 | /** |
|---|
| | 453 | * Rule: required. Generates an error if the field has an empty value. |
|---|
| | 454 | * |
|---|
| | 455 | * @param mixed input value |
|---|
| | 456 | * @return bool |
|---|
| | 457 | */ |
|---|
| | 458 | public function required($str) |
|---|
| | 459 | { |
|---|
| | 460 | return ! ($str === '' OR $str === NULL OR $str === FALSE OR (is_array($str) AND empty($str))); |
|---|
| | 461 | } |
|---|
| | 462 | |
|---|
| | 463 | /** |
|---|
| | 464 | * Rule: matches. Generates an error if the field does not match one or more |
|---|
| | 465 | * other fields. |
|---|
| | 466 | * |
|---|
| | 467 | * @param mixed input value |
|---|
| | 468 | * @param array input names to match against |
|---|
| | 469 | * @return bool |
|---|
| | 470 | */ |
|---|
| | 471 | public function matches($str, array $inputs) |
|---|
| | 472 | { |
|---|
| | 473 | foreach ($inputs as $key) |
|---|
| | 474 | { |
|---|
| | 475 | if ($str !== (isset($this[$key]) ? $this[$key] : NULL)) |
|---|
| | 476 | return FALSE; |
|---|
| | 477 | } |
|---|
| | 478 | |
|---|
| | 479 | return TRUE; |
|---|
| | 480 | } |
|---|
| | 481 | |
|---|
| | 482 | /** |
|---|
| | 483 | * Rule: length. Generates an error if the field is too long or too short. |
|---|
| | 484 | * |
|---|
| | 485 | * @param mixed input value |
|---|
| | 486 | * @param array minimum, maximum, or exact length to match |
|---|
| | 487 | * @return bool |
|---|
| | 488 | */ |
|---|
| | 489 | public function length($str, array $length) |
|---|
| | 490 | { |
|---|
| | 491 | if ( ! is_string($str)) |
|---|
| | 492 | return FALSE; |
|---|
| | 493 | |
|---|
| | 494 | $size = strlen($str); |
|---|
| | 495 | $status = FALSE; |
|---|
| | 496 | |
|---|
| | 497 | if (count($length) > 1) |
|---|
| | 498 | { |
|---|
| | 499 | list ($min, $max) = $length; |
|---|
| | 500 | |
|---|
| | 501 | if ($size >= $min AND $size <= $max) |
|---|
| | 502 | { |
|---|
| | 503 | $status = TRUE; |
|---|
| | 504 | } |
|---|
| 309 | | Event::run('validation.failure', $this->data); |
|---|
| 310 | | return FALSE; |
|---|
| 311 | | } |
|---|
| 312 | | } |
|---|
| 313 | | |
|---|
| 314 | | /** |
|---|
| 315 | | * Handles recursively calling rules on arrays of data. |
|---|
| 316 | | * |
|---|
| 317 | | * @param string Validation rule to be run on the data |
|---|
| 318 | | * @param string Name of field |
|---|
| 319 | | * @return void |
|---|
| 320 | | */ |
|---|
| 321 | | protected function run_rule($rule, $field) |
|---|
| 322 | | { |
|---|
| 323 | | // Use key_string to extract the field data |
|---|
| 324 | | $data = Kohana::key_string($field, $this->data); |
|---|
| 325 | | |
|---|
| 326 | | // Make sure that data input is not upload data |
|---|
| 327 | | if (is_array($data) AND ! (isset($data['tmp_name']) AND isset($data['error']))) |
|---|
| 328 | | { |
|---|
| 329 | | foreach($data as $key => $value) |
|---|
| 330 | | { |
|---|
| 331 | | // Recursion is fun! |
|---|
| 332 | | $this->run_rule($rule, $field.'.'.$key); |
|---|
| 333 | | |
|---|
| 334 | | if ($this->result === FALSE) |
|---|
| 335 | | break; |
|---|
| 336 | | } |
|---|
| 337 | | } |
|---|
| 338 | | else |
|---|
| 339 | | { |
|---|
| 340 | | if (strpos($rule, '=') === 0) |
|---|
| 341 | | { |
|---|
| 342 | | $rule = substr($rule, 1); |
|---|
| 343 | | $this->data[$field] = $rule($data); |
|---|
| 344 | | return; |
|---|
| 345 | | } |
|---|
| 346 | | |
|---|
| 347 | | // Handle callback rules |
|---|
| 348 | | $callback = FALSE; |
|---|
| 349 | | if (preg_match('/callback_(.+)/', $rule, $match)) |
|---|
| 350 | | { |
|---|
| 351 | | $callback = $match[1]; |
|---|
| 352 | | } |
|---|
| 353 | | |
|---|
| 354 | | // Handle params |
|---|
| 355 | | $params = FALSE; |
|---|
| 356 | | if (preg_match('/([^\[]*+)\[(.+)\]/', $rule, $match)) |
|---|
| 357 | | { |
|---|
| 358 | | $rule = $match[1]; |
|---|
| 359 | | $params = preg_split('/(?<!\\\\),/', $match[2]); |
|---|
| 360 | | $params = str_replace('\,', ',', $params); |
|---|
| 361 | | } |
|---|
| 362 | | |
|---|
| 363 | | // Process this field with the rule |
|---|
| 364 | | if ($callback !== FALSE) |
|---|
| 365 | | { |
|---|
| 366 | | if ( ! method_exists(Kohana::instance(), $callback)) |
|---|
| 367 | | throw new Kohana_Exception('validation.invalid_rule', $callback); |
|---|
| 368 | | |
|---|
| 369 | | $this->result = Kohana::instance()->$callback($data, $params); |
|---|
| 370 | | } |
|---|
| 371 | | elseif ($rule === 'matches' OR $rule === 'depends_on') |
|---|
| 372 | | { |
|---|
| 373 | | $this->result = $this->$rule($field, $params); |
|---|
| 374 | | } |
|---|
| 375 | | elseif (method_exists($this, $rule)) |
|---|
| 376 | | { |
|---|
| 377 | | $this->result = $this->$rule($data, $params); |
|---|
| 378 | | } |
|---|
| 379 | | elseif (is_callable($rule)) |
|---|
| 380 | | { |
|---|
| 381 | | if (strpos($rule, '::') !== FALSE) |
|---|
| 382 | | { |
|---|
| 383 | | $this->result = call_user_func(explode('::', $rule), $data); |
|---|
| 384 | | } |
|---|
| 385 | | else |
|---|
| 386 | | &nb |
|---|