Valid.php 14 KB

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