1<?php defined('SYSPATH') OR die('No direct script access.'); 2/** 3 * Validation rules. 4 * 5 * @package Kohana 6 * @category Security 7 * @author Kohana Team 8 * @copyright (c) 2008-2012 Kohana Team 9 * @license http://kohanaframework.org/license 10 */ 11class Kohana_Valid { 12 13 /** 14 * Checks if a field is not empty. 15 * 16 * @return boolean 17 */ 18 public static function not_empty($value) 19 { 20 if (is_object($value) AND $value instanceof ArrayObject) 21 { 22 // Get the array from the ArrayObject 23 $value = $value->getArrayCopy(); 24 } 25 26 // Value cannot be NULL, FALSE, '', or an empty array 27 return ! in_array($value, array(NULL, FALSE, '', array()), TRUE); 28 } 29 30 /** 31 * Checks a field against a regular expression. 32 * 33 * @param string $value value 34 * @param string $expression regular expression to match (including delimiters) 35 * @return boolean 36 */ 37 public static function regex($value, $expression) 38 { 39 return (bool) preg_match($expression, (string) $value); 40 } 41 42 /** 43 * Checks that a field is long enough. 44 * 45 * @param string $value value 46 * @param integer $length minimum length required 47 * @return boolean 48 */ 49 public static function min_length($value, $length) 50 { 51 return UTF8::strlen($value) >= $length; 52 } 53 54 /** 55 * Checks that a field is short enough. 56 * 57 * @param string $value value 58 * @param integer $length maximum length required 59 * @return boolean 60 */ 61 public static function max_length($value, $length) 62 { 63 return UTF8::strlen($value) <= $length; 64 } 65 66 /** 67 * Checks that a field is exactly the right length. 68 * 69 * @param string $value value 70 * @param integer|array $length exact length required, or array of valid lengths 71 * @return boolean 72 */ 73 public static function exact_length($value, $length) 74 { 75 if (is_array($length)) 76 { 77 foreach ($length as $strlen) 78 { 79 if (UTF8::strlen($value) === $strlen) 80 return TRUE; 81 } 82 return FALSE; 83 } 84 85 return UTF8::strlen($value) === $length; 86 } 87 88 /** 89 * Checks that a field is exactly the value required. 90 * 91 * @param string $value value 92 * @param string $required required value 93 * @return boolean 94 */ 95 public static function equals($value, $required) 96 { 97 return ($value === $required); 98 } 99 100 /** 101 * Check an email address for correct format. 102 * 103 * @link http://www.iamcal.com/publish/articles/php/parsing_email/ 104 * @link http://www.w3.org/Protocols/rfc822/ 105 * 106 * @param string $email email address 107 * @param boolean $strict strict RFC compatibility 108 * @return boolean 109 */ 110 public static function email($email, $strict = FALSE) 111 { 112 if (UTF8::strlen($email) > 254) 113 { 114 return FALSE; 115 } 116 117 if ($strict === TRUE) 118 { 119 $qtext = '[^\\x0d\\x22\\x5c\\x80-\\xff]'; 120 $dtext = '[^\\x0d\\x5b-\\x5d\\x80-\\xff]'; 121 $atom = '[^\\x00-\\x20\\x22\\x28\\x29\\x2c\\x2e\\x3a-\\x3c\\x3e\\x40\\x5b-\\x5d\\x7f-\\xff]+'; 122 $pair = '\\x5c[\\x00-\\x7f]'; 123 124 $domain_literal = "\\x5b($dtext|$pair)*\\x5d"; 125 $quoted_string = "\\x22($qtext|$pair)*\\x22"; 126 $sub_domain = "($atom|$domain_literal)"; 127 $word = "($atom|$quoted_string)"; 128 $domain = "$sub_domain(\\x2e$sub_domain)*"; 129 $local_part = "$word(\\x2e$word)*"; 130 131 $expression = "/^$local_part\\x40$domain$/D"; 132 } 133 else 134 { 135 $expression = '/^[-_a-z0-9\'+*$^&%=~!?{}]++(?:\.[-_a-z0-9\'+*$^&%=~!?{}]+)*+@(?:(?![-.])[-a-z0-9.]+(?<![-.])\.[a-z]{2,6}|\d{1,3}(?:\.\d{1,3}){3})$/iD'; 136 } 137 138 return (bool) preg_match($expression, (string) $email); 139 } 140 141 /** 142 * Validate the domain of an email address by checking if the domain has a 143 * valid MX record. 144 * 145 * @link http://php.net/checkdnsrr not added to Windows until PHP 5.3.0 146 * 147 * @param string $email email address 148 * @return boolean 149 */ 150 public static function email_domain($email) 151 { 152 if ( ! Valid::not_empty($email)) 153 return FALSE; // Empty fields cause issues with checkdnsrr() 154 155 // Check if the email domain has a valid MX record 156 return (bool) checkdnsrr(preg_replace('/^[^@]++@/', '', $email), 'MX'); 157 } 158 159 /** 160 * Validate a URL. 161 * 162 * @param string $url URL 163 * @return boolean 164 */ 165 public static function url($url) 166 { 167 // Based on http://www.apps.ietf.org/rfc/rfc1738.html#sec-5 168 if ( ! preg_match( 169 '~^ 170 171 # scheme 172 [-a-z0-9+.]++:// 173 174 # username:password (optional) 175 (?: 176 [-a-z0-9$_.+!*\'(),;?&=%]++ # username 177 (?::[-a-z0-9$_.+!*\'(),;?&=%]++)? # password (optional) 178 @ 179 )? 180 181 (?: 182 # ip address 183 \d{1,3}+(?:\.\d{1,3}+){3}+ 184 185 | # or 186 187 # hostname (captured) 188 ( 189 (?!-)[-a-z0-9]{1,63}+(?<!-) 190 (?:\.(?!-)[-a-z0-9]{1,63}+(?<!-)){0,126}+ 191 ) 192 ) 193 194 # port (optional) 195 (?::\d{1,5}+)? 196 197 # path (optional) 198 (?:/.*)? 199 200 $~iDx', $url, $matches)) 201 return FALSE; 202 203 // We matched an IP address 204 if ( ! isset($matches[1])) 205 return TRUE; 206 207 // Check maximum length of the whole hostname 208 // http://en.wikipedia.org/wiki/Domain_name#cite_note-0 209 if (strlen($matches[1]) > 253) 210 return FALSE; 211 212 // An extra check for the top level domain 213 // It must start with a letter 214 $tld = ltrim(substr($matches[1], (int) strrpos($matches[1], '.')), '.'); 215 return ctype_alpha($tld[0]); 216 } 217 218 /** 219 * Validate an IP. 220 * 221 * @param string $ip IP address 222 * @param boolean $allow_private allow private IP networks 223 * @return boolean 224 */ 225 public static function ip($ip, $allow_private = TRUE) 226 { 227 // Do not allow reserved addresses 228 $flags = FILTER_FLAG_NO_RES_RANGE; 229 230 if ($allow_private === FALSE) 231 { 232 // Do not allow private or reserved addresses 233 $flags = $flags | FILTER_FLAG_NO_PRIV_RANGE; 234 } 235 236 return (bool) filter_var($ip, FILTER_VALIDATE_IP, $flags); 237 } 238 239 /** 240 * Validates a credit card number, with a Luhn check if possible. 241 * 242 * @param integer $number credit card number 243 * @param string|array $type card type, or an array of card types 244 * @return boolean 245 * @uses Valid::luhn 246 */ 247 public static function credit_card($number, $type = NULL) 248 { 249 // Remove all non-digit characters from the number 250 if (($number = preg_replace('/\D+/', '', $number)) === '') 251 return FALSE; 252 253 if ($type == NULL) 254 { 255 // Use the default type 256 $type = 'default'; 257 } 258 elseif (is_array($type)) 259 { 260 foreach ($type as $t) 261 { 262 // Test each type for validity 263 if (Valid::credit_card($number, $t)) 264 return TRUE; 265 } 266 267 return FALSE; 268 } 269 270 $cards = Kohana::$config->load('credit_cards'); 271 272 // Check card type 273 $type = strtolower($type); 274 275 if ( ! isset($cards[$type])) 276 return FALSE; 277 278 // Check card number length 279 $length = strlen($number); 280 281 // Validate the card length by the card type 282 if ( ! in_array($length, preg_split('/\D+/', $cards[$type]['length']))) 283 return FALSE; 284 285 // Check card number prefix 286 if ( ! preg_match('/^'.$cards[$type]['prefix'].'/', $number)) 287 return FALSE; 288 289 // No Luhn check required 290 if ($cards[$type]['luhn'] == FALSE) 291 return TRUE; 292 293 return Valid::luhn($number); 294 } 295 296 /** 297 * Validate a number against the [Luhn](http://en.wikipedia.org/wiki/Luhn_algorithm) 298 * (mod10) formula. 299 * 300 * @param string $number number to check 301 * @return boolean 302 */ 303 public static function luhn($number) 304 { 305 // Force the value to be a string as this method uses string functions. 306 // Converting to an integer may pass PHP_INT_MAX and result in an error! 307 $number = (string) $number; 308 309 if ( ! ctype_digit($number)) 310 { 311 // Luhn can only be used on numbers! 312 return FALSE; 313 } 314 315 // Check number length 316 $length = strlen($number); 317 318 // Checksum of the card number 319 $checksum = 0; 320 321 for ($i = $length - 1; $i >= 0; $i -= 2) 322 { 323 // Add up every 2nd digit, starting from the right 324 $checksum += substr($number, $i, 1); 325 } 326 327 for ($i = $length - 2; $i >= 0; $i -= 2) 328 { 329 // Add up every 2nd digit doubled, starting from the right 330 $double = substr($number, $i, 1) * 2; 331 332 // Subtract 9 from the double where value is greater than 10 333 $checksum += ($double >= 10) ? ($double - 9) : $double; 334 } 335 336 // If the checksum is a multiple of 10, the number is valid 337 return ($checksum % 10 === 0); 338 } 339 340 /** 341 * Checks if a phone number is valid. 342 * 343 * @param string $number phone number to check 344 * @param array $lengths 345 * @return boolean 346 */ 347 public static function phone($number, $lengths = NULL) 348 { 349 if ( ! is_array($lengths)) 350 { 351 $lengths = array(7,10,11); 352 } 353 354 // Remove all non-digit characters from the number 355 $number = preg_replace('/\D+/', '', $number); 356 357 // Check if the number is within range 358 return in_array(strlen($number), $lengths); 359 } 360 361 /** 362 * Tests if a string is a valid date string. 363 * 364 * @param string $str date to check 365 * @return boolean 366 */ 367 public static function date($str) 368 { 369 return (strtotime($str) !== FALSE); 370 } 371 372 /** 373 * Checks whether a string consists of alphabetical characters only. 374 * 375 * @param string $str input string 376 * @param boolean $utf8 trigger UTF-8 compatibility 377 * @return boolean 378 */ 379 public static function alpha($str, $utf8 = FALSE) 380 { 381 $str = (string) $str; 382 383 if ($utf8 === TRUE) 384 { 385 return (bool) preg_match('/^\pL++$/uD', $str); 386 } 387 else 388 { 389 return ctype_alpha($str); 390 } 391 } 392 393 /** 394 * Checks whether a string consists of alphabetical characters and numbers only. 395 * 396 * @param string $str input string 397 * @param boolean $utf8 trigger UTF-8 compatibility 398 * @return boolean 399 */ 400 public static function alpha_numeric($str, $utf8 = FALSE) 401 { 402 if ($utf8 === TRUE) 403 { 404 return (bool) preg_match('/^[\pL\pN]++$/uD', $str); 405 } 406 else 407 { 408 return ctype_alnum($str); 409 } 410 } 411 412 /** 413 * Checks whether a string consists of alphabetical characters, numbers, underscores and dashes only. 414 * 415 * @param string $str input string 416 * @param boolean $utf8 trigger UTF-8 compatibility 417 * @return boolean 418 */ 419 public static function alpha_dash($str, $utf8 = FALSE) 420 { 421 if ($utf8 === TRUE) 422 { 423 $regex = '/^[-\pL\pN_]++$/uD'; 424 } 425 else 426 { 427 $regex = '/^[-a-z0-9_]++$/iD'; 428 } 429 430 return (bool) preg_match($regex, $str); 431 } 432 433 /** 434 * Checks whether a string consists of digits only (no dots or dashes). 435 * 436 * @param string $str input string 437 * @param boolean $utf8 trigger UTF-8 compatibility 438 * @return boolean 439 */ 440 public static function digit($str, $utf8 = FALSE) 441 { 442 if ($utf8 === TRUE) 443 { 444 return (bool) preg_match('/^\pN++$/uD', $str); 445 } 446 else 447 { 448 return (is_int($str) AND $str >= 0) OR ctype_digit($str); 449 } 450 } 451 452 /** 453 * Checks whether a string is a valid number (negative and decimal numbers allowed). 454 * 455 * Uses {@link http://www.php.net/manual/en/function.localeconv.php locale conversion} 456 * to allow decimal point to be locale specific. 457 * 458 * @param string $str input string 459 * @return boolean 460 */ 461 public static function numeric($str) 462 { 463 // Get the decimal point for the current locale 464 list($decimal) = array_values(localeconv()); 465 466 // A lookahead is used to make sure the string contains at least one digit (before or after the decimal point) 467 return (bool) preg_match('/^-?+(?=.*[0-9])[0-9]*+'.preg_quote($decimal).'?+[0-9]*+$/D', (string) $str); 468 } 469 470 /** 471 * Tests if a number is within a range. 472 * 473 * @param string $number number to check 474 * @param integer $min minimum value 475 * @param integer $max maximum value 476 * @param integer $step increment size 477 * @return boolean 478 */ 479 public static function range($number, $min, $max, $step = NULL) 480 { 481 if ($number < $min OR $number > $max) 482 { 483 // Number is outside of range 484 return FALSE; 485 } 486 487 if ( ! $step) 488 { 489 // Default to steps of 1 490 $step = 1; 491 } 492 493 // Check step requirements 494 return (($number - $min) % $step === 0); 495 } 496 497 /** 498 * Checks if a string is a proper decimal format. Optionally, a specific 499 * number of digits can be checked too. 500 * 501 * @param string $str number to check 502 * @param integer $places number of decimal places 503 * @param integer $digits number of digits 504 * @return boolean 505 */ 506 public static function decimal($str, $places = 2, $digits = NULL) 507 { 508 if ($digits > 0) 509 { 510 // Specific number of digits 511 $digits = '{'.( (int) $digits).'}'; 512 } 513 else 514 { 515 // Any number of digits 516 $digits = '+'; 517 } 518 519 // Get the decimal point for the current locale 520 list($decimal) = array_values(localeconv()); 521 522 return (bool) preg_match('/^[+-]?[0-9]'.$digits.preg_quote($decimal).'[0-9]{'.( (int) $places).'}$/D', $str); 523 } 524 525 /** 526 * Checks if a string is a proper hexadecimal HTML color value. The validation 527 * is quite flexible as it does not require an initial "#" and also allows for 528 * the short notation using only three instead of six hexadecimal characters. 529 * 530 * @param string $str input string 531 * @return boolean 532 */ 533 public static function color($str) 534 { 535 return (bool) preg_match('/^#?+[0-9a-f]{3}(?:[0-9a-f]{3})?$/iD', $str); 536 } 537 538 /** 539 * Checks if a field matches the value of another field. 540 * 541 * @param array $array array of values 542 * @param string $field field name 543 * @param string $match field name to match 544 * @return boolean 545 */ 546 public static function matches($array, $field, $match) 547 { 548 return ($array[$field] === $array[$match]); 549 } 550 551} 552