5 * Copyright (c) 1997-2006 Pierre-Alain Joye,Tomas V.V.Cox, Amir Saied
7 * This source file is subject to the New BSD license, That is bundled
8 * with this package in the file LICENSE, and is available through
9 * the world-wide-web at
10 * http://www.opensource.org/licenses/bsd-license.php
11 * If you did not receive a copy of the new BSDlicense and are unable
12 * to obtain it through the world-wide-web, please send a note to
13 * pajoye@php.net so we can mail you a copy immediately.
15 * Author: Tomas V.V.Cox <cox@idecnet.com>
16 * Pierre-Alain Joye <pajoye@php.net>
17 * Amir Mohammad Saied <amir@php.net>
20 * Package to validate various datas. It includes :
21 * - numbers (min/max, decimal or not)
22 * - email (syntax, domain check)
23 * - string (predifined type alpha upper and/or lowercase, numeric,...)
24 * - date (min, max, rfc822 compliant)
26 * - possibility valid multiple data with a single method call (::multiple)
30 * @author Tomas V.V.Cox <cox@idecnet.com>
31 * @author Pierre-Alain Joye <pajoye@php.net>
32 * @author Amir Mohammad Saied <amir@php.net>
33 * @copyright 1997-2006 Pierre-Alain Joye,Tomas V.V.Cox,Amir Mohammad Saied
34 * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
36 * @link http://pear.php.net/package/Validate
41 * Methods for common data validations
43 define('VALIDATE_NUM', '0-9');
44 define('VALIDATE_SPACE', '\s');
45 define('VALIDATE_ALPHA_LOWER', 'a-z');
46 define('VALIDATE_ALPHA_UPPER', 'A-Z');
47 define('VALIDATE_ALPHA', VALIDATE_ALPHA_LOWER . VALIDATE_ALPHA_UPPER);
48 define('VALIDATE_EALPHA_LOWER', VALIDATE_ALPHA_LOWER . 'áéíóúýàèìòùäëïöüÿâêîôûãñõ¨åæç½ðøþß');
49 define('VALIDATE_EALPHA_UPPER', VALIDATE_ALPHA_UPPER . 'ÁÉÍÓÚÝÀÈÌÒÙÄËÏÖܾÂÊÎÔÛÃÑÕ¦ÅÆǼÐØÞ');
50 define('VALIDATE_EALPHA', VALIDATE_EALPHA_LOWER . VALIDATE_EALPHA_UPPER);
51 define('VALIDATE_PUNCTUATION', VALIDATE_SPACE . '\.,;\:&"\'\?\!\(\)');
52 define('VALIDATE_NAME', VALIDATE_EALPHA . VALIDATE_SPACE . "'" . '\-');
53 define('VALIDATE_STREET', VALIDATE_NUM . VALIDATE_NAME . "/\\ºª\.");
55 define('VALIDATE_ITLD_EMAILS', 1);
56 define('VALIDATE_GTLD_EMAILS', 2);
57 define('VALIDATE_CCTLD_EMAILS', 4);
58 define('VALIDATE_ALL_EMAILS', 8);
64 * Package to validate various datas. It includes :
65 * - numbers (min/max, decimal or not)
66 * - email (syntax, domain check)
67 * - string (predifined type alpha upper and/or lowercase, numeric,...)
70 * - possibility valid multiple data with a single method call (::multiple)
74 * @author Tomas V.V.Cox <cox@idecnet.com>
75 * @author Pierre-Alain Joye <pajoye@php.net>
76 * @author Amir Mohammad Saied <amir@php.net>
77 * @author Diogo Cordeiro <diogo@fc.up.pt>
78 * @copyright 1997-2006 Pierre-Alain Joye,Tomas V.V.Cox,Amir Mohammad Saied
79 * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
80 * @version Release: @package_version@
81 * @link http://pear.php.net/package/Validate
85 // {{{ International, Generic and Country code TLDs
87 * International Top-Level Domain
89 * This is an array of the known international
90 * top-level domain names.
93 * @var array $_iTld (International top-level domains)
101 * Generic top-level domain
103 * This is an array of the official
104 * generic top-level domains.
107 * @var array $_gTld (Generic top-level domains)
135 * Country code top-level domains
137 * This is an array of the official country
138 * codes top-level domains
141 * @var array $_ccTld (Country Code Top-Level Domain)
145 'ad', 'ae', 'af', 'ag',
146 'ai', 'al', 'am', 'an',
147 'ao', 'aq', 'ar', 'as',
148 'at', 'au', 'aw', 'ax',
149 'az', 'ba', 'bb', 'bd',
150 'be', 'bf', 'bg', 'bh',
151 'bi', 'bj', 'bm', 'bn',
152 'bo', 'br', 'bs', 'bt',
153 'bu', 'bv', 'bw', 'by',
154 'bz', 'ca', 'cc', 'cd',
155 'cf', 'cg', 'ch', 'ci',
156 'ck', 'cl', 'cm', 'cn',
157 'co', 'cr', 'cs', 'cu',
158 'cv', 'cx', 'cy', 'cz',
159 'de', 'dj', 'dk', 'dm',
160 'do', 'dz', 'ec', 'ee',
161 'eg', 'eh', 'er', 'es',
162 'et', 'eu', 'fi', 'fj',
163 'fk', 'fm', 'fo', 'fr',
164 'ga', 'gb', 'gd', 'ge',
165 'gf', 'gg', 'gh', 'gi',
166 'gl', 'gm', 'gn', 'gp',
167 'gq', 'gr', 'gs', 'gt',
168 'gu', 'gw', 'gy', 'hk',
169 'hm', 'hn', 'hr', 'ht',
170 'hu', 'id', 'ie', 'il',
171 'im', 'in', 'io', 'iq',
172 'ir', 'is', 'it', 'je',
173 'jm', 'jo', 'jp', 'ke',
174 'kg', 'kh', 'ki', 'km',
175 'kn', 'kp', 'kr', 'kw',
176 'ky', 'kz', 'la', 'lb',
177 'lc', 'li', 'lk', 'lr',
178 'ls', 'lt', 'lu', 'lv',
179 'ly', 'ma', 'mc', 'md',
180 'me', 'mg', 'mh', 'mk',
181 'ml', 'mm', 'mn', 'mo',
182 'mp', 'mq', 'mr', 'ms',
183 'mt', 'mu', 'mv', 'mw',
184 'mx', 'my', 'mz', 'na',
185 'nc', 'ne', 'nf', 'ng',
186 'ni', 'nl', 'no', 'np',
187 'nr', 'nu', 'nz', 'om',
188 'pa', 'pe', 'pf', 'pg',
189 'ph', 'pk', 'pl', 'pm',
190 'pn', 'pr', 'ps', 'pt',
191 'pw', 'py', 'qa', 're',
192 'ro', 'rs', 'ru', 'rw',
193 'sa', 'sb', 'sc', 'sd',
194 'se', 'sg', 'sh', 'si',
195 'sj', 'sk', 'sl', 'sm',
196 'sn', 'so', 'sr', 'st',
197 'su', 'sv', 'sy', 'sz',
198 'tc', 'td', 'tf', 'tg',
199 'th', 'tj', 'tk', 'tl',
200 'tm', 'tn', 'to', 'tp',
201 'tr', 'tt', 'tv', 'tw',
202 'tz', 'ua', 'ug', 'uk',
203 'us', 'uy', 'uz', 'va',
204 'vc', 've', 'vg', 'vi',
205 'vn', 'vu', 'wf', 'ws',
206 'ye', 'yt', 'yu', 'za',
212 * Validate a tag URI (RFC4151)
214 * @param string $uri tag URI to validate
216 * @return bool true if valid tag URI, false if not
221 private function __uriRFC4151(string $uri): bool
225 '/^tag:(?<name>.*),(?<date>\d{4}-?\d{0,2}-?\d{0,2}):(?<specific>.*)(.*:)*$/',
229 $date = $matches['date'];
230 $date6 = strtotime($date);
231 if ((strlen($date) == 4) && $date <= date('Y')) {
233 } elseif ((strlen($date) == 7) && ($date6 < strtotime("now"))) {
235 } elseif ((strlen($date) == 10) && ($date6 < strtotime("now"))) {
238 if (self::email($matches['name'])) {
241 $namevalid = self::email('info@' . $matches['name']);
243 return $datevalid && $namevalid;
252 * @param string $number Number to validate
253 * @param array $options array where:
254 * 'decimal' is the decimal char or false when decimal
256 * i.e. ',.' to allow both ',' and '.'
257 * 'dec_prec' Number of allowed decimals
258 * 'min' minimum value
259 * 'max' maximum value
261 * @return bool true if valid number, false if not
265 public function number($number, array $options = []): bool
267 $decimal = $dec_prec = $min = $max = null;
268 if (is_array($options)) {
272 $dec_prec = $dec_prec ? "{1,$dec_prec}" : '+';
273 $dec_regex = $decimal ? "[$decimal][0-9]$dec_prec" : '';
275 if (!preg_match("|^[-+]?\s*[0-9]+($dec_regex)?\$|", $number)) {
279 if ($decimal != '.') {
280 $number = strtr($number, $decimal, '.');
283 $number = (float)str_replace(' ', '', $number);
284 if ($min !== null && $min > $number) {
288 if ($max !== null && $max < $number) {
295 * Converting a string to UTF-7 (RFC 2152)
297 * @param string $string string to be converted
299 * @return string converted string
303 public function __stringToUtf7(string $string): string
307 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K',
308 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
309 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
310 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
311 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2',
312 '3', '4', '5', '6', '7', '8', '9', '+', ','
318 if (!empty($string)) {
320 while ($i <= strlen($string)) {
321 $char = substr($string, $i, 1);
323 if ((ord($char) >= 0x7F) || (ord($char) <= 0x1F)) {
328 } elseif ($char == '&') {
333 } elseif (($i == strlen($string) ||
334 !((ord($char) >= 0x7F)) || (ord($char) <= 0x1F))) {
336 if (ord($char) > 64) {
339 $return .= $utf7[ord($char)];
347 $return .= $utf7[ord($char) >> 2];
348 $residue = (ord($char) & 0x03) << 4;
352 $return .= $utf7[$residue | (ord($char) >> 4)];
353 $residue = (ord($char) & 0x0F) << 2;
357 $return .= $utf7[$residue | (ord($char) >> 6)];
358 $return .= $utf7[ord($char) & 0x3F];
371 * Validate an email according to full RFC822 (inclusive human readable part)
373 * @param string $email email to validate,
374 * will return the address for optional dns validation
375 * @param array $options email() options
377 * @return bool true if valid email, false if not
381 private function __emailRFC822(string &$email, array &$options): bool
383 static $address = null;
384 static $uncomment = null;
386 // atom = 1*<any CHAR except specials, SPACE and CTLs>
387 $atom = '[^][()<>@,;:\\".\s\000-\037\177-\377]+\s*';
388 // qtext = <any CHAR excepting <">, ; => may be folded
389 // "\" & CR, and including linear-white-space>
390 $qtext = '[^"\\\\\r]';
391 // quoted-pair = "\" CHAR ; may quote any char
392 $quoted_pair = '\\\\.';
393 // quoted-string = <"> *(qtext/quoted-pair) <">; Regular qtext or
395 $quoted_string = '"(?:' . $qtext . '|' . $quoted_pair . ')*"\s*';
396 // word = atom / quoted-string
397 $word = '(?:' . $atom . '|' . $quoted_string . ')';
398 // local-part = word *("." word) ; uninterpreted
400 $local_part = $word . '(?:\.\s*' . $word . ')*';
401 // dtext = <any CHAR excluding "[", ; => may be folded
402 // "]", "\" & CR, & including linear-white-space>
403 $dtext = '[^][\\\\\r]';
404 // domain-literal = "[" *(dtext / quoted-pair) "]"
405 $domain_literal = '\[(?:' . $dtext . '|' . $quoted_pair . ')*\]\s*';
406 // sub-domain = domain-ref / domain-literal
407 // domain-ref = atom ; symbolic reference
408 $sub_domain = '(?:' . $atom . '|' . $domain_literal . ')';
409 // domain = sub-domain *("." sub-domain)
410 $domain = $sub_domain . '(?:\.\s*' . $sub_domain . ')*';
411 // addr-spec = local-part "@" domain ; global address
412 $addr_spec = $local_part . '@\s*' . $domain;
413 // route = 1#("@" domain) ":" ; path-relative
414 $route = '@' . $domain . '(?:,@\s*' . $domain . ')*:\s*';
415 // route-addr = "<" [route] addr-spec ">"
416 $route_addr = '<\s*(?:' . $route . ')?' . $addr_spec . '>\s*';
417 // phrase = 1*word ; Sequence of words
418 $phrase = $word . '+';
419 // mailbox = addr-spec ; simple address
420 // / phrase route-addr ; name & addr-spec
421 $mailbox = '(?:' . $addr_spec . '|' . $phrase . $route_addr . ')';
422 // group = phrase ":" [#mailbox] ";"
423 $group = $phrase . ':\s*(?:' . $mailbox . '(?:,\s*' . $mailbox . ')*)?;\s*';
424 // address = mailbox ; one addressee
425 // / group ; named list
426 $address = '/^\s*(?:' . $mailbox . '|' . $group . ')$/';
429 '/((?:(?:\\\\"|[^("])*(?:' . $quoted_string .
430 ')?)*)((?<!\\\\)\((?:(?2)|.)*?(?<!\\\\)\))/';
433 $email = preg_replace($uncomment, '$1 ', $email);
434 return preg_match($address, $email);
438 * Full TLD Validation function
440 * This function is used to make a much more proficient validation
441 * against all types of official domain names.
443 * @param string $email The email address to check.
444 * @param array $options The options for validation
448 * @return bool True if validating succeeds
450 public function _fullTLDValidation(string $email, array $options): bool
453 if (!empty($options["VALIDATE_ITLD_EMAILS"])) {
454 array_push($validate, 'itld');
456 if (!empty($options["VALIDATE_GTLD_EMAILS"])) {
457 array_push($validate, 'gtld');
459 if (!empty($options["VALIDATE_CCTLD_EMAILS"])) {
460 array_push($validate, 'cctld');
463 if (count($validate) === 0) {
464 array_push($validate, 'itld', 'gtld', 'cctld');
467 $self = new Validate;
471 foreach ($validate as $valid) {
472 $tmpVar = '_' . (string)$valid;
474 $toValidate[$valid] = $self->{$tmpVar};
477 $e = $self->executeFullEmailValidation($email, $toValidate);
483 * Execute the validation
485 * This function will execute the full email vs tld
486 * validation using an array of tlds passed to it.
488 * @param string $email The email to validate.
489 * @param array $arrayOfTLDs The array of the TLDs to validate
493 * @return bool true or false (Depending on if it validates or if it does not)
495 public function executeFullEmailValidation(string $email, array $arrayOfTLDs): bool
497 $emailEnding = explode('.', $email);
498 $emailEnding = $emailEnding[count($emailEnding) - 1];
499 foreach ($arrayOfTLDs as $validator => $keys) {
500 if (in_array($emailEnding, $keys)) {
510 * @param string $email email to validate
511 * @param mixed boolean (BC) $check_domain Check or not if the domain exists
512 * array $options associative array of options
513 * 'check_domain' boolean Check or not if the domain exists
514 * 'use_rfc822' boolean Apply the full RFC822 grammar
518 * 'check_domain' => 'true',
519 * 'fullTLDValidation' => 'true',
520 * 'use_rfc822' => 'true',
521 * 'VALIDATE_GTLD_EMAILS' => 'true',
522 * 'VALIDATE_CCTLD_EMAILS' => 'true',
523 * 'VALIDATE_ITLD_EMAILS' => 'true',
526 * @return bool true if valid email, false if not
531 public function email(string $email, array $options = null): bool
533 $check_domain = false;
535 if (is_bool($options)) {
536 $check_domain = $options;
537 } elseif (is_array($options)) {
542 * Check for IDN usage so we can encode the domain as Punycode
547 if (Validate::_includePathFileExists('Net/IDNA2.php')) {
548 include_once('Net/IDNA2.php');
552 if ($hasIDNA === true) {
553 if (strpos($email, '@') !== false) {
554 $tmpEmail = explode('@', $email);
555 $domain = array_pop($tmpEmail);
557 // Check if the domain contains characters > 127 which means
558 // it's an idn domain name.
559 $chars = count_chars($domain, 1);
560 if (!empty($chars) && max(array_keys($chars)) > 127) {
561 $idna =& Net_IDNA2::singleton();
562 $domain = $idna->encode($domain);
565 array_push($tmpEmail, $domain);
566 $email = implode('@', $tmpEmail);
571 * @todo Fix bug here.. even if it passes this, it won't be passing
572 * The regular expression below
574 if (isset($fullTLDValidation)) {
575 //$valid = Validate::_fullTLDValidation($email, $fullTLDValidation);
576 $valid = Validate::_fullTLDValidation($email, $options);
583 // the base regexp for address
584 $regex = '&^(?: # recipient:
585 ("\s*(?:[^"\f\n\r\t\v\b\s]+\s*)+")| #1 quoted name
586 ([-\w!\#\$%\&\'*+~/^`|{}]+(?:\.[-\w!\#\$%\&\'*+~/^`|{}]+)*)) #2 OR dot-atom
587 @(((\[)? #3 domain, 4 as IPv4, 5 optionally bracketed
588 (?:(?:(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:[0-1]?[0-9]?[0-9]))\.){3}
589 (?:(?:25[0-5])|(?:2[0-4][0-9])|(?:[0-1]?[0-9]?[0-9]))))(?(5)\])|
590 ((?:[a-z0-9](?:[-a-z0-9]*[a-z0-9])?\.)*[a-z0-9](?:[-a-z0-9]*[a-z0-9])?) #6 domain as hostname
591 \.((?:([^- ])[-a-z]*[-a-z]))) #7 TLD
594 //checks if exists the domain (MX or A)
595 if ($use_rfc822 ? Validate::__emailRFC822($email, $options) :
596 preg_match($regex, $email)) {
597 if ($check_domain && function_exists('checkdnsrr')) {
598 $domain = preg_replace('/[^-a-z.0-9]/i', '', array_pop(explode('@', $email)));
599 if (checkdnsrr($domain, 'MX') || checkdnsrr($domain, 'A')) {
610 * Validate a string using the given format 'format'
612 * @param string $string String to validate
613 * @param array|string $options Options array where:
614 * 'format' is the format of the string
615 * Ex:VALIDATE_NUM . VALIDATE_ALPHA (see constants)
616 * 'min_length' minimum length
617 * 'max_length' maximum length
619 * @return bool true if valid string, false if not
623 public function string(string $string, $options): bool
629 if (is_array($options)) {
633 if ($format && !preg_match("|^[$format]*\$|s", $string)) {
637 if ($min_length && strlen($string) < $min_length) {
641 if ($max_length && strlen($string) > $max_length) {
649 * Validate an URI (RFC2396)
650 * This function will validate 'foobarstring' by default, to get it to validate
651 * only http, https, ftp and such you have to pass it in the allowed_schemes
654 * $options = ['allowed_schemes' => ['http', 'https', 'ftp']]
655 * var_dump(Validate::uri('http://www.example.org', $options));
658 * NOTE 1: The rfc2396 normally allows middle '-' in the top domain
659 * e.g. http://example.co-m should be valid
660 * However, as '-' is not used in any known TLD, it is invalid
661 * NOTE 2: As double shlashes // are allowed in the path part, only full URIs
662 * including an authority can be valid, no relative URIs
663 * the // are mandatory (optionally preceeded by the 'sheme:' )
664 * NOTE 3: the full complience to rfc2396 is not achieved by default
665 * the characters ';/?:@$,' will not be accepted in the query part
666 * if not urlencoded, refer to the option "strict'"
668 * @param string $url URI to validate
669 * @param array|null $options Options used by the validation method.
671 * 'domain_check' => boolean
672 * Whether to check the DNS entry or not
673 * 'allowed_schemes' => array, list of protocols
674 * List of allowed schemes ('http',
676 * 'strict' => string the refused chars
677 * in query and fragment parts
679 * empty: accept all rfc2396 foreseen chars
681 * @return bool true if valid uri, false if not
686 public function uri(string $url, $options = null): bool
689 $domain_check = false;
690 $allowed_schemes = null;
691 if (is_array($options)) {
694 if (is_array($allowed_schemes) &&
695 in_array("tag", $allowed_schemes)
697 if (strpos($url, "tag:") === 0) {
698 return self::__uriRFC4151($url);
703 '&^(?:([a-z][-+.a-z0-9]*):)? # 1. scheme
704 (?:// # authority start
705 (?:((?:%[0-9a-f]{2}|[-a-z0-9_.!~*\'();:\&=+$,])*)@)? # 2. authority-userinfo
706 (?:((?:[a-z0-9](?:[-a-z0-9]*[a-z0-9])?\.)*[a-z](?:[a-z0-9]+)?\.?) # 3. authority-hostname OR
707 |([0-9]{1,3}(?:\.[0-9]{1,3}){3})) # 4. authority-ipv4
708 (?::([0-9]*))?) # 5. authority-port
709 ((?:/(?:%[0-9a-f]{2}|[-a-z0-9_.!~*\'():@\&=+$,;])*)*/?)? # 6. path
710 (?:\?([^#]*))? # 7. query
711 (?:\#((?:%[0-9a-f]{2}|[-a-z0-9_.!~*\'();/?:@\&=+$,])*))? # 8. fragment
716 $scheme = isset($matches[1]) ? $matches[1] : '';
717 $authority = isset($matches[3]) ? $matches[3] : '';
718 if (is_array($allowed_schemes) &&
719 !in_array($scheme, $allowed_schemes)
723 if (!empty($matches[4])) {
724 $parts = explode('.', $matches[4]);
725 foreach ($parts as $part) {
730 } elseif ($domain_check && function_exists('checkdnsrr')) {
731 if (!checkdnsrr($authority, 'A')) {
736 $strict = '#[' . preg_quote($strict, '#') . ']#';
737 if ((!empty($matches[7]) && preg_match($strict, $matches[7]))
738 || (!empty($matches[8]) && preg_match($strict, $matches[8]))) {
748 * Validate date and times. Note that this method need the Date_Calc class
750 * @param string $date Date to validate
751 * @param array $options array options where :
752 * 'format' The format of the date (%d-%m-%Y)
753 * or rfc822_compliant
754 * 'min' The date has to be greater
755 * than this [$day, $month, $year]
756 * or PEAR::Date object
757 * 'max' The date has to be smaller than
758 * this [$day, $month, $year]
759 * or PEAR::Date object
761 * @return bool true if valid date/time, false if not
765 public function date(string $date, array $options): bool
773 if (strtolower($format) == 'rfc822_compliant') {
774 $preg = '&^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),) \s+
776 (?:(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)?) \s+
777 (?:(\d{2}(\d{2})?)?) \s+
778 (?:(\d{2}?)):(?:(\d{2}?))(:(?:(\d{2}?)))? \s+
779 (?:[+-]\d{4}|UT|GMT|EST|EDT|CST|CDT|MST|MDT|PST|PDT|[A-IK-Za-ik-z])$&xi';
781 if (!preg_match($preg, $date, $matches)) {
785 $year = (int)$matches[4];
786 $months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
787 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
788 $month = array_keys($months, $matches[3]);
789 $month = (int)$month[0] + 1;
790 $day = (int)$matches[2];
791 $weekday = $matches[1];
792 $hour = (int)$matches[6];
793 $minute = (int)$matches[7];
794 isset($matches[9]) ? $second = (int)$matches[9] : $second = 0;
796 if ((strlen($year) != 4) ||
797 ($day > 31 || $day < 1) ||
804 $date_len = strlen($format);
805 for ($i = 0; $i < $date_len; $i++) {
808 $next = $format{$i + 1};
813 $day = (int)Validate::_substr($date, 1, 2);
815 $day = (int)Validate::_substr($date, 0, 2);
817 if ($day < 1 || $day > 31) {
824 $month = (int)Validate::_substr($date, 0, 2);
826 $month = (int)Validate::_substr($date, 1, 2);
828 if ($month < 1 || $month > 12) {
835 $year = Validate::_substr($date, 4);
836 $year = (int)$year ? $year : '';
838 $year = (int)(substr(date('Y'), 0, 2) .
839 Validate::_substr($date, 2));
841 if (strlen($year) != 4 || $year < 0 || $year > 9999) {
848 $hour = Validate::_substr($date, 1, 2);
850 $hour = Validate::_substr($date, 2);
852 if (!preg_match('/^\d+$/', $hour) || $hour < 0 || $hour > 12) {
859 $hour = Validate::_substr($date, 1, 2);
861 $hour = Validate::_substr($date, 2);
863 if (!preg_match('/^\d+$/', $hour) || $hour < 0 || $hour > 24) {
869 $t = Validate::_substr($date, 2);
870 if (!preg_match('/^\d+$/', $t) || $t < 0 || $t > 59) {
875 trigger_error("Not supported char `$next' after % in offset " . ($i + 2), E_USER_WARNING);
880 if (Validate::_substr($date, 1) != $c) {
886 // there is remaing data, we don't want it
887 if (strlen($date) && (strtolower($format) != 'rfc822_compliant')) {
891 if (isset($day) && isset($month) && isset($year) && isset($weekday)) {
892 if (!checkdate($month, $day, $year)) {
896 if (strtolower($format) == 'rfc822_compliant') {
897 if ($weekday != date("D", mktime(0, 0, 0, $month, $day, $year))) {
903 include_once 'Date/Calc.php';
904 if (is_a($min, 'Date') &&
905 (Date_Calc::compareDates(
915 } elseif (is_array($min) &&
916 (Date_Calc::compareDates(
930 include_once 'Date/Calc.php';
931 if (is_a($max, 'Date') &&
932 (Date_Calc::compareDates(
942 } elseif (is_array($max) &&
943 (Date_Calc::compareDates(
963 * @param string &$date Date
964 * @param string $num Length
965 * @param string|false $opt Unknown
970 private function _substr(string &$date, string $num, $opt = false): string
972 if ($opt && strlen($date) >= $opt && preg_match('/^[0-9]{' . $opt . '}/', $date, $m)) {
975 $ret = substr($date, 0, $num);
977 $date = substr($date, strlen($ret));
981 public function _modf($val, $div)
983 if (function_exists('bcmod')) {
984 return bcmod($val, $div);
985 } elseif (function_exists('fmod')) {
986 return fmod($val, $div);
990 return intval($val - $i * $div + .1);
994 * Calculates sum of product of number digits with weights
996 * @param string $number number string
997 * @param array $weights reference to array of weights
1001 * @return int returns product of number digits with weights
1003 public function _multWeights(string $number, array &$weights): int
1005 if (!is_array($weights)) {
1010 $count = min(count($weights), strlen($number));
1011 if ($count == 0) { // empty string or weights array
1014 for ($i = 0; $i < $count; ++$i) {
1015 $sum += intval(substr($number, $i, 1)) * $weights[$i];
1022 * Calculates control digit for a given number
1024 * @param string $number number string
1025 * @param array $weights reference to array of weights
1026 * @param int $modulo (optionsl) number
1027 * @param int $subtract (optional) number
1028 * @param bool $allow_high (optional) true if function can return number higher than 10
1032 * @return int -1 calculated control number is returned
1034 public function _getControlNumber(string $number, array &$weights, int $modulo = 10, int $subtract = 0, bool $allow_high = false): int
1037 $sum = Validate::_multWeights($number, $weights);
1041 $mod = Validate::_modf($sum, $modulo); // calculate control digit
1043 if ($subtract > $mod && $mod > 0) {
1044 $mod = $subtract - $mod;
1046 if ($allow_high === false) {
1047 $mod %= 10; // change 10 to zero
1053 * Validates a number
1055 * @param string $number number to validate
1056 * @param array $weights reference to array of weights
1057 * @param int $modulo (optional) number
1058 * @param int $subtract (optional) number
1062 * @return bool true if valid, false if not
1064 public function _checkControlNumber(string $number, array &$weights, int $modulo = 10, int $subtract = 0): bool
1066 if (strlen($number) < count($weights)) {
1069 $target_digit = substr($number, count($weights), 1);
1070 $control_digit = Validate::_getControlNumber($number, $weights, $modulo, $subtract, $modulo > 10);
1072 if ($control_digit == -1) {
1075 if ($target_digit === 'X' && $control_digit == 10) {
1078 if ($control_digit != $target_digit) {
1085 * Bulk data validation for data introduced in the form of an
1086 * assoc array in the form $var_name => $value.
1087 * Can be used on any of Validate subpackages
1089 * @param array $data Ex: ['name' => 'toto', 'email' => 'toto@thing.info'];
1090 * @param array $val_type Contains the validation type and all parameters used in.
1091 * 'val_type' is not optional
1092 * others validations properties must have the same name as the function
1094 * Ex: ['toto' => ['type'=>'string','format'='toto@thing.info','min_length'=>5]];
1095 * @param bool $remove if set, the elements not listed in data will be removed
1097 * @return array value name => true|false the value name comes from the data key
1101 public function multiple(array &$data, array &$val_type, bool $remove = false): array
1103 $keys = array_keys($data);
1106 foreach ($keys as $var_name) {
1107 if (!isset($val_type[$var_name])) {
1109 unset($data[$var_name]);
1113 $opt = $val_type[$var_name];
1114 $methods = get_class_methods('Validate');
1115 $val2check = $data[$var_name];
1116 // core validation method
1117 if (in_array(strtolower($opt['type']), $methods)) {
1118 //$opt[$opt['type']] = $data[$var_name];
1119 $method = $opt['type'];
1120 unset($opt['type']);
1122 if (sizeof($opt) == 1 && is_array(reset($opt))) {
1123 $opt = array_pop($opt);
1125 $valid[$var_name] = call_user_func(['Validate', $method], $val2check, $opt);
1128 * external validation method in the form:
1129 * "<class name><underscore><method name>"
1130 * Ex: us_ssn will include class Validate/US.php and call method ssn()
1132 } elseif (strpos($opt['type'], '_') !== false) {
1133 $validateType = explode('_', $opt['type']);
1134 $method = array_pop($validateType);
1135 $class = implode('_', $validateType);
1136 $classPath = str_replace('_', DIRECTORY_SEPARATOR, $class);
1137 $class = 'Validate_' . $class;
1138 if (Validate::_includePathFileExists("Validate/$classPath.php")) {
1139 include_once "Validate/$classPath.php";
1141 trigger_error("$class isn't installed or you may have some permission issues", E_USER_ERROR);
1144 $ce = substr(phpversion(), 0, 1) > 4 ?
1145 class_exists($class, false) : class_exists($class);
1147 !in_array($method, get_class_methods($class))
1150 "Invalid validation type $class::$method",
1155 unset($opt['type']);
1156 if (sizeof($opt) == 1) {
1157 $opt = array_pop($opt);
1159 $valid[$var_name] = call_user_func(
1160 array($class, $method),
1166 "Invalid validation type {$opt['type']}",
1175 * Determine whether specified file exists along the include path.
1177 * @param string $filename file to search for
1181 * @return bool true if file exists
1183 private function _includePathFileExists(string $filename): bool
1185 $paths = explode(":", ini_get("include_path"));
1188 foreach ($paths as $val) {
1189 $result = file_exists($val . "/" . $filename);