| 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)) |