]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - extlib/Validate.php
Merge remote-tracking branch 'upstream/master' into social-master
[quix0rs-gnu-social.git] / extlib / Validate.php
1 <?php
2 /**
3  * Validation class
4  *
5  * Copyright (c) 1997-2006 Pierre-Alain Joye,Tomas V.V.Cox, Amir Saied
6  *
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.
14  *
15  * Author: Tomas V.V.Cox  <cox@idecnet.com>
16  *         Pierre-Alain Joye <pajoye@php.net>
17  *         Amir Mohammad Saied <amir@php.net>
18  *
19  *
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)
25  *   - uri (RFC2396)
26  *   - possibility valid multiple data with a single method call (::multiple)
27  *
28  * @category   Validate
29  * @package    Validate
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
35  * @version    CVS: $Id$
36  * @link       http://pear.php.net/package/Validate
37  */
38
39 // {{{ Constants
40 /**
41  * Methods for common data validations
42  */
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 . "/\\ºª\.");
54
55 define('VALIDATE_ITLD_EMAILS',  1);
56 define('VALIDATE_GTLD_EMAILS',  2);
57 define('VALIDATE_CCTLD_EMAILS', 4);
58 define('VALIDATE_ALL_EMAILS',   8);
59 // }}}
60
61 /**
62  * Validation class
63  *
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,...)
68  *   - date (min, max)
69  *   - uri (RFC2396)
70  *   - possibility valid multiple data with a single method call (::multiple)
71  *
72  * @category  Validate
73  * @package   Validate
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  * @copyright 1997-2006 Pierre-Alain Joye,Tomas V.V.Cox,Amir Mohammad Saied
78  * @license   http://www.opensource.org/licenses/bsd-license.php  New BSD License
79  * @version   Release: @package_version@
80  * @link      http://pear.php.net/package/Validate
81  */
82 class Validate
83 {
84     // {{{ International, Generic and Country code TLDs
85     /**
86      * International Top-Level Domain
87      *
88      * This is an array of the known international
89      * top-level domain names.
90      *
91      * @access protected
92      * @var    array     $_iTld (International top-level domains)
93      */
94     var $_itld = array(
95         'arpa',
96         'root',
97     );
98
99     /**
100      * Generic top-level domain
101      *
102      * This is an array of the official
103      * generic top-level domains.
104      *
105      * @access protected
106      * @var    array     $_gTld (Generic top-level domains)
107      */
108     var $_gtld = array(
109         'aero',
110         'biz',
111         'cat',
112         'com',
113         'coop',
114         'edu',
115         'gov',
116         'info',
117         'int',
118         'jobs',
119         'mil',
120         'mobi',
121         'museum',
122         'name',
123         'net',
124         'org',
125         'pro',
126         'travel',
127         'asia',
128         'post',
129         'tel',
130         'geo',
131     );
132
133     /**
134      * Country code top-level domains
135      *
136      * This is an array of the official country
137      * codes top-level domains
138      *
139      * @access protected
140      * @var    array     $_ccTld (Country Code Top-Level Domain)
141      */
142     var $_cctld = array(
143         'ac',
144         'ad','ae','af','ag',
145         'ai','al','am','an',
146         'ao','aq','ar','as',
147         'at','au','aw','ax',
148         'az','ba','bb','bd',
149         'be','bf','bg','bh',
150         'bi','bj','bm','bn',
151         'bo','br','bs','bt',
152         'bu','bv','bw','by',
153         'bz','ca','cc','cd',
154         'cf','cg','ch','ci',
155         'ck','cl','cm','cn',
156         'co','cr','cs','cu',
157         'cv','cx','cy','cz',
158         'de','dj','dk','dm',
159         'do','dz','ec','ee',
160         'eg','eh','er','es',
161         'et','eu','fi','fj',
162         'fk','fm','fo','fr',
163         'ga','gb','gd','ge',
164         'gf','gg','gh','gi',
165         'gl','gm','gn','gp',
166         'gq','gr','gs','gt',
167         'gu','gw','gy','hk',
168         'hm','hn','hr','ht',
169         'hu','id','ie','il',
170         'im','in','io','iq',
171         'ir','is','it','je',
172         'jm','jo','jp','ke',
173         'kg','kh','ki','km',
174         'kn','kp','kr','kw',
175         'ky','kz','la','lb',
176         'lc','li','lk','lr',
177         'ls','lt','lu','lv',
178         'ly','ma','mc','md',
179         'me','mg','mh','mk',
180         'ml','mm','mn','mo',
181         'mp','mq','mr','ms',
182         'mt','mu','mv','mw',
183         'mx','my','mz','na',
184         'nc','ne','nf','ng',
185         'ni','nl','no','np',
186         'nr','nu','nz','om',
187         'pa','pe','pf','pg',
188         'ph','pk','pl','pm',
189         'pn','pr','ps','pt',
190         'pw','py','qa','re',
191         'ro','rs','ru','rw',
192         'sa','sb','sc','sd',
193         'se','sg','sh','si',
194         'sj','sk','sl','sm',
195         'sn','so','sr','st',
196         'su','sv','sy','sz',
197         'tc','td','tf','tg',
198         'th','tj','tk','tl',
199         'tm','tn','to','tp',
200         'tr','tt','tv','tw',
201         'tz','ua','ug','uk',
202         'us','uy','uz','va',
203         'vc','ve','vg','vi',
204         'vn','vu','wf','ws',
205         'ye','yt','yu','za',
206         'zm','zw',
207     );
208     // }}}
209
210     /**
211      * Validate a tag URI (RFC4151)
212      *
213      * @param string $uri tag URI to validate
214      *
215      * @return boolean true if valid tag URI, false if not
216      *
217      * @access private
218      */
219     function __uriRFC4151($uri)
220     {
221         $datevalid = false;
222         if (preg_match(
223             '/^tag:(?<name>.*),(?<date>\d{4}-?\d{0,2}-?\d{0,2}):(?<specific>.*)(.*:)*$/', $uri, $matches)) {
224             $date  = $matches['date'];
225             $date6 = strtotime($date);
226             if ((strlen($date) == 4) && $date <= date('Y')) {
227                 $datevalid = true;
228             } elseif ((strlen($date) == 7) && ($date6 < strtotime("now"))) {
229                 $datevalid = true;
230             } elseif ((strlen($date) == 10) && ($date6 < strtotime("now"))) {
231                 $datevalid = true;
232             }
233             if (self::email($matches['name'])) {
234                 $namevalid = true;
235             } else {
236                 $namevalid = self::email('info@' . $matches['name']);
237             }
238             return $datevalid && $namevalid;
239         } else {
240             return false;
241         }
242     }
243
244     /**
245      * Validate a number
246      *
247      * @param string $number  Number to validate
248      * @param array  $options array where:
249      *                          'decimal'  is the decimal char or false when decimal
250      *                                     not allowed.
251      *                                     i.e. ',.' to allow both ',' and '.'
252      *                          'dec_prec' Number of allowed decimals
253      *                          'min'      minimum value
254      *                          'max'      maximum value
255      *
256      * @return boolean true if valid number, false if not
257      *
258      * @access public
259      */
260     function number($number, $options = array())
261     {
262         $decimal = $dec_prec = $min = $max = null;
263         if (is_array($options)) {
264             extract($options);
265         }
266
267         $dec_prec  = $dec_prec ? "{1,$dec_prec}" : '+';
268         $dec_regex = $decimal  ? "[$decimal][0-9]$dec_prec" : '';
269
270         if (!preg_match("|^[-+]?\s*[0-9]+($dec_regex)?\$|", $number)) {
271             return false;
272         }
273
274         if ($decimal != '.') {
275             $number = strtr($number, $decimal, '.');
276         }
277
278         $number = (float)str_replace(' ', '', $number);
279         if ($min !== null && $min > $number) {
280             return false;
281         }
282
283         if ($max !== null && $max < $number) {
284             return false;
285         }
286         return true;
287     }
288
289     /**
290      * Converting a string to UTF-7 (RFC 2152)
291      *
292      * @param string $string string to be converted
293      *
294      * @return  string  converted string
295      *
296      * @access  private
297      */
298     function __stringToUtf7($string)
299     {
300         $return = '';
301         $utf7   = array(
302                         'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K',
303                         'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
304                         'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
305                         'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
306                         's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2',
307                         '3', '4', '5', '6', '7', '8', '9', '+', ','
308                     );
309
310
311         $state = 0;
312
313         if (!empty($string)) {
314             $i = 0;
315             while ($i <= strlen($string)) {
316                 $char = substr($string, $i, 1);
317                 if ($state == 0) {
318                     if ((ord($char) >= 0x7F) || (ord($char) <= 0x1F)) {
319                         if ($char) {
320                             $return .= '&';
321                         }
322                         $state = 1;
323                     } elseif ($char == '&') {
324                         $return .= '&-';
325                     } else {
326                         $return .= $char;
327                     }
328                 } elseif (($i == strlen($string) ||
329                             !((ord($char) >= 0x7F)) || (ord($char) <= 0x1F))) {
330                     if ($state != 1) {
331                         if (ord($char) > 64) {
332                             $return .= '';
333                         } else {
334                             $return .= $utf7[ord($char)];
335                         }
336                     }
337                     $return .= '-';
338                     $state   = 0;
339                 } else {
340                     switch($state) {
341                     case 1:
342                         $return .= $utf7[ord($char) >> 2];
343                         $residue = (ord($char) & 0x03) << 4;
344                         $state   = 2;
345                         break;
346                     case 2:
347                         $return .= $utf7[$residue | (ord($char) >> 4)];
348                         $residue = (ord($char) & 0x0F) << 2;
349                         $state   = 3;
350                         break;
351                     case 3:
352                         $return .= $utf7[$residue | (ord($char) >> 6)];
353                         $return .= $utf7[ord($char) & 0x3F];
354                         $state   = 1;
355                         break;
356                     }
357                 }
358                 $i++;
359             }
360             return $return;
361         }
362         return '';
363     }
364
365     /**
366      * Validate an email according to full RFC822 (inclusive human readable part)
367      *
368      * @param string $email   email to validate,
369      *                        will return the address for optional dns validation
370      * @param array  $options email() options
371      *
372      * @return boolean true if valid email, false if not
373      *
374      * @access private
375      */
376     function __emailRFC822(&$email, &$options)
377     {
378         static $address   = null;
379         static $uncomment = null;
380         if (!$address) {
381             // atom        =  1*<any CHAR except specials, SPACE and CTLs>
382             $atom = '[^][()<>@,;:\\".\s\000-\037\177-\377]+\s*';
383             // qtext       =  <any CHAR excepting <">,     ; => may be folded
384             //         "\" & CR, and including linear-white-space>
385             $qtext = '[^"\\\\\r]';
386             // quoted-pair =  "\" CHAR                     ; may quote any char
387             $quoted_pair = '\\\\.';
388             // quoted-string = <"> *(qtext/quoted-pair) <">; Regular qtext or
389             //                                             ;   quoted chars.
390             $quoted_string = '"(?:' . $qtext . '|' . $quoted_pair . ')*"\s*';
391             // word        =  atom / quoted-string
392             $word = '(?:' . $atom . '|' . $quoted_string . ')';
393             // local-part  =  word *("." word)             ; uninterpreted
394             //                                             ; case-preserved
395             $local_part = $word . '(?:\.\s*' . $word . ')*';
396             // dtext       =  <any CHAR excluding "[",     ; => may be folded
397             //         "]", "\" & CR, & including linear-white-space>
398             $dtext = '[^][\\\\\r]';
399             // domain-literal =  "[" *(dtext / quoted-pair) "]"
400             $domain_literal = '\[(?:' . $dtext . '|' . $quoted_pair . ')*\]\s*';
401             // sub-domain  =  domain-ref / domain-literal
402             // domain-ref  =  atom                         ; symbolic reference
403             $sub_domain = '(?:' . $atom . '|' . $domain_literal . ')';
404             // domain      =  sub-domain *("." sub-domain)
405             $domain = $sub_domain . '(?:\.\s*' . $sub_domain . ')*';
406             // addr-spec   =  local-part "@" domain        ; global address
407             $addr_spec = $local_part . '@\s*' . $domain;
408             // route       =  1#("@" domain) ":"           ; path-relative
409             $route = '@' . $domain . '(?:,@\s*' . $domain . ')*:\s*';
410             // route-addr  =  "<" [route] addr-spec ">"
411             $route_addr = '<\s*(?:' . $route . ')?' . $addr_spec . '>\s*';
412             // phrase      =  1*word                       ; Sequence of words
413             $phrase = $word  . '+';
414             // mailbox     =  addr-spec                    ; simple address
415             //             /  phrase route-addr            ; name & addr-spec
416             $mailbox = '(?:' . $addr_spec . '|' . $phrase . $route_addr . ')';
417             // group       =  phrase ":" [#mailbox] ";"
418             $group = $phrase . ':\s*(?:' . $mailbox . '(?:,\s*' . $mailbox . ')*)?;\s*';
419             //     address     =  mailbox                      ; one addressee
420             //                 /  group                        ; named list
421             $address = '/^\s*(?:' . $mailbox . '|' . $group . ')$/';
422
423             $uncomment =
424             '/((?:(?:\\\\"|[^("])*(?:' . $quoted_string .
425                                              ')?)*)((?<!\\\\)\((?:(?2)|.)*?(?<!\\\\)\))/';
426         }
427         // strip comments
428         $email = preg_replace($uncomment, '$1 ', $email);
429         return preg_match($address, $email);
430     }
431
432     /**
433      * Full TLD Validation function
434      *
435      * This function is used to make a much more proficient validation
436      * against all types of official domain names.
437      *
438      * @param string $email   The email address to check.
439      * @param array  $options The options for validation
440      *
441      * @access protected
442      *
443      * @return bool True if validating succeeds
444      */
445     function _fullTLDValidation($email, $options)
446     {
447         $validate = array();
448         if(!empty($options["VALIDATE_ITLD_EMAILS"])) array_push($validate, 'itld');
449         if(!empty($options["VALIDATE_GTLD_EMAILS"])) array_push($validate, 'gtld');
450         if(!empty($options["VALIDATE_CCTLD_EMAILS"])) array_push($validate, 'cctld');
451
452         if (count($validate) === 0) {
453             array_push($validate, 'itld', 'gtld', 'cctld');
454         }
455
456         $self = new Validate;
457
458         $toValidate = array();
459
460         foreach ($validate as $valid) {
461             $tmpVar = '_' . (string)$valid;
462
463             $toValidate[$valid] = $self->{$tmpVar};
464         }
465
466         $e = $self->executeFullEmailValidation($email, $toValidate);
467
468         return $e;
469     }
470
471     /**
472      * Execute the validation
473      *
474      * This function will execute the full email vs tld
475      * validation using an array of tlds passed to it.
476      *
477      * @param string $email       The email to validate.
478      * @param array  $arrayOfTLDs The array of the TLDs to validate
479      *
480      * @access public
481      *
482      * @return true or false (Depending on if it validates or if it does not)
483      */
484     function executeFullEmailValidation($email, $arrayOfTLDs)
485     {
486         $emailEnding = explode('.', $email);
487         $emailEnding = $emailEnding[count($emailEnding)-1];
488         foreach ($arrayOfTLDs as $validator => $keys) {
489             if (in_array($emailEnding, $keys)) {
490                 return true;
491             }
492         }
493         return false;
494     }
495
496     /**
497      * Validate an email
498      *
499      * @param string $email  email to validate
500      * @param mixed  boolean (BC) $check_domain Check or not if the domain exists
501      *              array $options associative array of options
502      *              'check_domain' boolean Check or not if the domain exists
503      *              'use_rfc822' boolean Apply the full RFC822 grammar
504      *
505      * Ex.
506      *  $options = array(
507      *      'check_domain' => 'true',
508      *      'fullTLDValidation' => 'true',
509      *      'use_rfc822' => 'true',
510      *      'VALIDATE_GTLD_EMAILS' => 'true',
511      *      'VALIDATE_CCTLD_EMAILS' => 'true',
512      *      'VALIDATE_ITLD_EMAILS' => 'true',
513      *      );
514      *
515      * @return boolean true if valid email, false if not
516      *
517      * @access public
518      */
519     function email($email, $options = null)
520     {
521         $check_domain = false;
522         $use_rfc822   = false;
523         if (is_bool($options)) {
524             $check_domain = $options;
525         } elseif (is_array($options)) {
526             extract($options);
527         }
528
529         /**
530          * Check for IDN usage so we can encode the domain as Punycode
531          * before continuing.
532          */
533         $hasIDNA = false;
534
535         if (Validate::_includePathFileExists('Net/IDNA.php')) {
536             include_once('Net/IDNA.php');
537             $hasIDNA = true;
538         }
539
540         if ($hasIDNA === true) {
541             if (strpos($email, '@') !== false) {
542                 $tmpEmail = explode('@', $email);
543                 $domain = array_pop($tmpEmail);
544
545                 // Check if the domain contains characters > 127 which means
546                 // it's an idn domain name.
547                 $chars = count_chars($domain, 1);
548                 if (!empty($chars) && max(array_keys($chars)) > 127) {
549                     $idna   =& Net_IDNA::singleton();
550                     $domain = $idna->encode($domain);
551                 }
552
553                 array_push($tmpEmail, $domain);
554                 $email = implode('@', $tmpEmail);
555             }
556         }
557
558         /**
559          * @todo Fix bug here.. even if it passes this, it won't be passing
560          *       The regular expression below
561          */
562         if (isset($fullTLDValidation)) {
563             //$valid = Validate::_fullTLDValidation($email, $fullTLDValidation);
564             $valid = Validate::_fullTLDValidation($email, $options);
565
566             if (!$valid) {
567                 return false;
568             }
569         }
570
571         // the base regexp for address
572         $regex = '&^(?:                                               # recipient:
573          ("\s*(?:[^"\f\n\r\t\v\b\s]+\s*)+")|                          #1 quoted name
574          ([-\w!\#\$%\&\'*+~/^`|{}]+(?:\.[-\w!\#\$%\&\'*+~/^`|{}]+)*)) #2 OR dot-atom
575          @(((\[)?                     #3 domain, 4 as IPv4, 5 optionally bracketed
576          (?:(?:(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:[0-1]?[0-9]?[0-9]))\.){3}
577                (?:(?:25[0-5])|(?:2[0-4][0-9])|(?:[0-1]?[0-9]?[0-9]))))(?(5)\])|
578          ((?:[a-z0-9](?:[-a-z0-9]*[a-z0-9])?\.)*[a-z0-9](?:[-a-z0-9]*[a-z0-9])?)  #6 domain as hostname
579          \.((?:([^- ])[-a-z]*[-a-z]))) #7 TLD
580          $&xi';
581
582         //checks if exists the domain (MX or A)
583         if ($use_rfc822? Validate::__emailRFC822($email, $options) :
584                 preg_match($regex, $email)) {
585             if ($check_domain && function_exists('checkdnsrr')) {
586                 $domain = preg_replace('/[^-a-z.0-9]/i', '', array_pop(explode('@', $email)));
587                 if (checkdnsrr($domain, 'MX') || checkdnsrr($domain, 'A')) {
588                     return true;
589                 }
590                 return false;
591             }
592             return true;
593         }
594         return false;
595     }
596
597     /**
598      * Validate a string using the given format 'format'
599      *
600      * @param string $string  String to validate
601      * @param array  $options Options array where:
602      *                          'format' is the format of the string
603      *                              Ex:VALIDATE_NUM . VALIDATE_ALPHA (see constants)
604      *                          'min_length' minimum length
605      *                          'max_length' maximum length
606      *
607      * @return boolean true if valid string, false if not
608      *
609      * @access public
610      */
611     function string($string, $options)
612     {
613         $format     = null;
614         $min_length = 0;
615         $max_length = 0;
616
617         if (is_array($options)) {
618             extract($options);
619         }
620
621         if ($format && !preg_match("|^[$format]*\$|s", $string)) {
622             return false;
623         }
624
625         if ($min_length && strlen($string) < $min_length) {
626             return false;
627         }
628
629         if ($max_length && strlen($string) > $max_length) {
630             return false;
631         }
632
633         return true;
634     }
635
636     /**
637      * Validate an URI (RFC2396)
638      * This function will validate 'foobarstring' by default, to get it to validate
639      * only http, https, ftp and such you have to pass it in the allowed_schemes
640      * option, like this:
641      * <code>
642      * $options = array('allowed_schemes' => array('http', 'https', 'ftp'))
643      * var_dump(Validate::uri('http://www.example.org', $options));
644      * </code>
645      *
646      * NOTE 1: The rfc2396 normally allows middle '-' in the top domain
647      *         e.g. http://example.co-m should be valid
648      *         However, as '-' is not used in any known TLD, it is invalid
649      * NOTE 2: As double shlashes // are allowed in the path part, only full URIs
650      *         including an authority can be valid, no relative URIs
651      *         the // are mandatory (optionally preceeded by the 'sheme:' )
652      * NOTE 3: the full complience to rfc2396 is not achieved by default
653      *         the characters ';/?:@$,' will not be accepted in the query part
654      *         if not urlencoded, refer to the option "strict'"
655      *
656      * @param string $url     URI to validate
657      * @param array  $options Options used by the validation method.
658      *                          key => type
659      *                          'domain_check' => boolean
660      *                              Whether to check the DNS entry or not
661      *                          'allowed_schemes' => array, list of protocols
662      *                              List of allowed schemes ('http',
663      *                              'ssh+svn', 'mms')
664      *                          'strict' => string the refused chars
665      *                              in query and fragment parts
666      *                              default: ';/?:@$,'
667      *                              empty: accept all rfc2396 foreseen chars
668      *
669      * @return boolean true if valid uri, false if not
670      *
671      * @access public
672      */
673     function uri($url, $options = null)
674     {
675         $strict = ';/?:@$,';
676         $domain_check = false;
677         $allowed_schemes = null;
678         if (is_array($options)) {
679             extract($options);
680         }
681         if (is_array($allowed_schemes) &&
682             in_array("tag", $allowed_schemes)
683         ) {
684             if (strpos($url, "tag:") === 0) {
685                 return self::__uriRFC4151($url);
686             }
687         }
688
689         if (preg_match(
690              '&^(?:([a-z][-+.a-z0-9]*):)?                             # 1. scheme
691               (?://                                                   # authority start
692               (?:((?:%[0-9a-f]{2}|[-a-z0-9_.!~*\'();:\&=+$,])*)@)?    # 2. authority-userinfo
693               (?:((?:[a-z0-9](?:[-a-z0-9]*[a-z0-9])?\.)*[a-z](?:[a-z0-9]+)?\.?)  # 3. authority-hostname OR
694               |([0-9]{1,3}(?:\.[0-9]{1,3}){3}))                       # 4. authority-ipv4
695               (?::([0-9]*))?)                                        # 5. authority-port
696               ((?:/(?:%[0-9a-f]{2}|[-a-z0-9_.!~*\'():@\&=+$,;])*)*/?)? # 6. path
697               (?:\?([^#]*))?                                          # 7. query
698               (?:\#((?:%[0-9a-f]{2}|[-a-z0-9_.!~*\'();/?:@\&=+$,])*))? # 8. fragment
699               $&xi', $url, $matches)) {
700             $scheme = isset($matches[1]) ? $matches[1] : '';
701             $authority = isset($matches[3]) ? $matches[3] : '' ;
702             if (is_array($allowed_schemes) &&
703                 !in_array($scheme, $allowed_schemes)
704             ) {
705                 return false;
706             }
707             if (!empty($matches[4])) {
708                 $parts = explode('.', $matches[4]);
709                 foreach ($parts as $part) {
710                     if ($part > 255) {
711                         return false;
712                     }
713                 }
714             } elseif ($domain_check && function_exists('checkdnsrr')) {
715                 if (!checkdnsrr($authority, 'A')) {
716                     return false;
717                 }
718             }
719             if ($strict) {
720                 $strict = '#[' . preg_quote($strict, '#') . ']#';
721                 if ((!empty($matches[7]) && preg_match($strict, $matches[7]))
722                  || (!empty($matches[8]) && preg_match($strict, $matches[8]))) {
723                     return false;
724                 }
725             }
726             return true;
727         }
728         return false;
729     }
730
731     /**
732      * Validate date and times. Note that this method need the Date_Calc class
733      *
734      * @param string $date    Date to validate
735      * @param array  $options array options where :
736      *                          'format' The format of the date (%d-%m-%Y)
737      *                                   or rfc822_compliant
738      *                          'min'    The date has to be greater
739      *                                   than this array($day, $month, $year)
740      *                                   or PEAR::Date object
741      *                          'max'    The date has to be smaller than
742      *                                   this array($day, $month, $year)
743      *                                   or PEAR::Date object
744      *
745      * @return boolean true if valid date/time, false if not
746      *
747      * @access public
748      */
749     function date($date, $options)
750     {
751         $max    = false;
752         $min    = false;
753         $format = '';
754
755         if (is_array($options)) {
756             extract($options);
757         }
758
759         if (strtolower($format) == 'rfc822_compliant') {
760             $preg = '&^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),) \s+
761                     (?:(\d{2})?) \s+
762                     (?:(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)?) \s+
763                     (?:(\d{2}(\d{2})?)?) \s+
764                     (?:(\d{2}?)):(?:(\d{2}?))(:(?:(\d{2}?)))? \s+
765                     (?:[+-]\d{4}|UT|GMT|EST|EDT|CST|CDT|MST|MDT|PST|PDT|[A-IK-Za-ik-z])$&xi';
766
767             if (!preg_match($preg, $date, $matches)) {
768                 return false;
769             }
770
771             $year    = (int)$matches[4];
772             $months  = array('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
773                              'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec');
774             $month   = array_keys($months, $matches[3]);
775             $month   = (int)$month[0]+1;
776             $day     = (int)$matches[2];
777             $weekday = $matches[1];
778             $hour    = (int)$matches[6];
779             $minute  = (int)$matches[7];
780             isset($matches[9]) ? $second = (int)$matches[9] : $second = 0;
781
782             if ((strlen($year) != 4)        ||
783                 ($day    > 31   || $day < 1)||
784                 ($hour   > 23)  ||
785                 ($minute > 59)  ||
786                 ($second > 59)) {
787                     return false;
788             }
789         } else {
790             $date_len = strlen($format);
791             for ($i = 0; $i < $date_len; $i++) {
792                 $c = $format{$i};
793                 if ($c == '%') {
794                     $next = $format{$i + 1};
795                     switch ($next) {
796                     case 'j':
797                     case 'd':
798                         if ($next == 'j') {
799                             $day = (int)Validate::_substr($date, 1, 2);
800                         } else {
801                             $day = (int)Validate::_substr($date, 0, 2);
802                         }
803                         if ($day < 1 || $day > 31) {
804                             return false;
805                         }
806                         break;
807                     case 'm':
808                     case 'n':
809                         if ($next == 'm') {
810                             $month = (int)Validate::_substr($date, 0, 2);
811                         } else {
812                             $month = (int)Validate::_substr($date, 1, 2);
813                         }
814                         if ($month < 1 || $month > 12) {
815                             return false;
816                         }
817                         break;
818                     case 'Y':
819                     case 'y':
820                         if ($next == 'Y') {
821                             $year = Validate::_substr($date, 4);
822                             $year = (int)$year?$year:'';
823                         } else {
824                             $year = (int)(substr(date('Y'), 0, 2) .
825                                               Validate::_substr($date, 2));
826                         }
827                         if (strlen($year) != 4 || $year < 0 || $year > 9999) {
828                             return false;
829                         }
830                         break;
831                     case 'g':
832                     case 'h':
833                         if ($next == 'g') {
834                             $hour = Validate::_substr($date, 1, 2);
835                         } else {
836                             $hour = Validate::_substr($date, 2);
837                         }
838                         if (!preg_match('/^\d+$/', $hour) || $hour < 0 || $hour > 12) {
839                             return false;
840                         }
841                         break;
842                     case 'G':
843                     case 'H':
844                         if ($next == 'G') {
845                             $hour = Validate::_substr($date, 1, 2);
846                         } else {
847                             $hour = Validate::_substr($date, 2);
848                         }
849                         if (!preg_match('/^\d+$/', $hour) || $hour < 0 || $hour > 24) {
850                             return false;
851                         }
852                         break;
853                     case 's':
854                     case 'i':
855                         $t = Validate::_substr($date, 2);
856                         if (!preg_match('/^\d+$/', $t) || $t < 0 || $t > 59) {
857                             return false;
858                         }
859                         break;
860                     default:
861                         trigger_error("Not supported char `$next' after % in offset " . ($i+2), E_USER_WARNING);
862                     }
863                     $i++;
864                 } else {
865                     //literal
866                     if (Validate::_substr($date, 1) != $c) {
867                         return false;
868                     }
869                 }
870             }
871         }
872         // there is remaing data, we don't want it
873         if (strlen($date) && (strtolower($format) != 'rfc822_compliant')) {
874             return false;
875         }
876
877         if (isset($day) && isset($month) && isset($year)) {
878             if (!checkdate($month, $day, $year)) {
879                 return false;
880             }
881
882             if (strtolower($format) == 'rfc822_compliant') {
883                 if ($weekday != date("D", mktime(0, 0, 0, $month, $day, $year))) {
884                     return false;
885                 }
886             }
887
888             if ($min) {
889                 include_once 'Date/Calc.php';
890                 if (is_a($min, 'Date') &&
891                     (Date_Calc::compareDates($day, $month, $year,
892                         $min->getDay(), $min->getMonth(), $min->getYear()) < 0)
893                 ) {
894                     return false;
895                 } elseif (is_array($min) &&
896                         (Date_Calc::compareDates($day, $month, $year,
897                             $min[0], $min[1], $min[2]) < 0)
898                 ) {
899                     return false;
900                 }
901             }
902
903             if ($max) {
904                 include_once 'Date/Calc.php';
905                 if (is_a($max, 'Date') &&
906                     (Date_Calc::compareDates($day, $month, $year,
907                         $max->getDay(), $max->getMonth(), $max->getYear()) > 0)
908                 ) {
909                     return false;
910                 } elseif (is_array($max) &&
911                         (Date_Calc::compareDates($day, $month, $year,
912                             $max[0], $max[1], $max[2]) > 0)
913                 ) {
914                     return false;
915                 }
916             }
917         }
918
919         return true;
920     }
921
922     /**
923      * Substr
924      *
925      * @param string &$date Date
926      * @param string $num   Length
927      * @param string $opt   Unknown
928      *
929      * @access private
930      * @return string
931      */
932     function _substr(&$date, $num, $opt = false)
933     {
934         if ($opt && strlen($date) >= $opt && preg_match('/^[0-9]{'.$opt.'}/', $date, $m)) {
935             $ret = $m[0];
936         } else {
937             $ret = substr($date, 0, $num);
938         }
939         $date = substr($date, strlen($ret));
940         return $ret;
941     }
942
943     function _modf($val, $div)
944     {
945         if (function_exists('bcmod')) {
946             return bcmod($val, $div);
947         } elseif (function_exists('fmod')) {
948             return fmod($val, $div);
949         }
950         $r = $val / $div;
951         $i = intval($r);
952         return intval($val - $i * $div + .1);
953     }
954
955     /**
956      * Calculates sum of product of number digits with weights
957      *
958      * @param string $number  number string
959      * @param array  $weights reference to array of weights
960      *
961      * @access protected
962      *
963      * @return int returns product of number digits with weights
964      */
965     function _multWeights($number, &$weights)
966     {
967         if (!is_array($weights)) {
968             return -1;
969         }
970         $sum = 0;
971
972         $count = min(count($weights), strlen($number));
973         if ($count == 0) { // empty string or weights array
974             return -1;
975         }
976         for ($i = 0; $i < $count; ++$i) {
977             $sum += intval(substr($number, $i, 1)) * $weights[$i];
978         }
979
980         return $sum;
981     }
982
983     /**
984      * Calculates control digit for a given number
985      *
986      * @param string $number     number string
987      * @param array  $weights    reference to array of weights
988      * @param int    $modulo     (optionsl) number
989      * @param int    $subtract   (optional) number
990      * @param bool   $allow_high (optional) true if function can return number higher than 10
991      *
992      * @access protected
993      *
994      * @return  int -1 calculated control number is returned
995      */
996     function _getControlNumber($number, &$weights, $modulo = 10, $subtract = 0, $allow_high = false)
997     {
998         // calc sum
999         $sum = Validate::_multWeights($number, $weights);
1000         if ($sum == -1) {
1001             return -1;
1002         }
1003         $mod = Validate::_modf($sum, $modulo);  // calculate control digit
1004
1005         if ($subtract > $mod && $mod > 0) {
1006             $mod = $subtract - $mod;
1007         }
1008         if ($allow_high === false) {
1009             $mod %= 10;           // change 10 to zero
1010         }
1011         return $mod;
1012     }
1013
1014     /**
1015      * Validates a number
1016      *
1017      * @param string $number   number to validate
1018      * @param array  $weights  reference to array of weights
1019      * @param int    $modulo   (optional) number
1020      * @param int    $subtract (optional) number
1021      *
1022      * @access protected
1023      *
1024      * @return  bool true if valid, false if not
1025      */
1026     function _checkControlNumber($number, &$weights, $modulo = 10, $subtract = 0)
1027     {
1028         if (strlen($number) < count($weights)) {
1029             return false;
1030         }
1031         $target_digit  = substr($number, count($weights), 1);
1032         $control_digit = Validate::_getControlNumber($number, $weights, $modulo, $subtract, $modulo > 10);
1033
1034         if ($control_digit == -1) {
1035             return false;
1036         }
1037         if ($target_digit === 'X' && $control_digit == 10) {
1038             return true;
1039         }
1040         if ($control_digit != $target_digit) {
1041             return false;
1042         }
1043         return true;
1044     }
1045
1046     /**
1047      * Bulk data validation for data introduced in the form of an
1048      * assoc array in the form $var_name => $value.
1049      * Can be used on any of Validate subpackages
1050      *
1051      * @param array   $data     Ex: array('name' => 'toto', 'email' => 'toto@thing.info');
1052      * @param array   $val_type Contains the validation type and all parameters used in.
1053      *                          'val_type' is not optional
1054      *                          others validations properties must have the same name as the function
1055      *                          parameters.
1056      *                          Ex: array('toto'=>array('type'=>'string','format'='toto@thing.info','min_length'=>5));
1057      * @param boolean $remove   if set, the elements not listed in data will be removed
1058      *
1059      * @return array   value name => true|false    the value name comes from the data key
1060      *
1061      * @access public
1062      */
1063     function multiple(&$data, &$val_type, $remove = false)
1064     {
1065         $keys  = array_keys($data);
1066         $valid = array();
1067
1068         foreach ($keys as $var_name) {
1069             if (!isset($val_type[$var_name])) {
1070                 if ($remove) {
1071                     unset($data[$var_name]);
1072                 }
1073                 continue;
1074             }
1075             $opt       = $val_type[$var_name];
1076             $methods   = get_class_methods('Validate');
1077             $val2check = $data[$var_name];
1078             // core validation method
1079             if (in_array(strtolower($opt['type']), $methods)) {
1080                 //$opt[$opt['type']] = $data[$var_name];
1081                 $method = $opt['type'];
1082                 unset($opt['type']);
1083
1084                 if (sizeof($opt) == 1 && is_array(reset($opt))) {
1085                     $opt = array_pop($opt);
1086                 }
1087                 $valid[$var_name] = call_user_func(array('Validate', $method), $val2check, $opt);
1088
1089                 /**
1090                  * external validation method in the form:
1091                  * "<class name><underscore><method name>"
1092                  * Ex: us_ssn will include class Validate/US.php and call method ssn()
1093                  */
1094             } elseif (strpos($opt['type'], '_') !== false) {
1095                 $validateType = explode('_', $opt['type']);
1096                 $method       = array_pop($validateType);
1097                 $class        = implode('_', $validateType);
1098                 $classPath    = str_replace('_', DIRECTORY_SEPARATOR, $class);
1099                 $class        = 'Validate_' . $class;
1100                 if (Validate::_includePathFileExists("Validate/$classPath.php")) {
1101                     include_once "Validate/$classPath.php";
1102                 } else {
1103                     trigger_error("$class isn't installed or you may have some permission issues", E_USER_ERROR);
1104                 }
1105
1106                 $ce = substr(phpversion(), 0, 1) > 4 ?
1107                     class_exists($class, false) : class_exists($class);
1108                 if (!$ce ||
1109                     !in_array($method, get_class_methods($class))
1110                 ) {
1111                     trigger_error("Invalid validation type $class::$method",
1112                         E_USER_WARNING);
1113                     continue;
1114                 }
1115                 unset($opt['type']);
1116                 if (sizeof($opt) == 1) {
1117                     $opt = array_pop($opt);
1118                 }
1119                 $valid[$var_name] = call_user_func(array($class, $method),
1120                     $data[$var_name], $opt);
1121             } else {
1122                 trigger_error("Invalid validation type {$opt['type']}",
1123                     E_USER_WARNING);
1124             }
1125         }
1126         return $valid;
1127     }
1128
1129     /**
1130      * Determine whether specified file exists along the include path.
1131      *
1132      * @param string $filename file to search for
1133      *
1134      * @access private
1135      *
1136      * @return bool true if file exists
1137      */
1138     function _includePathFileExists($filename)
1139     {
1140         $paths = explode(":", ini_get("include_path"));
1141         $result = false;
1142
1143         while ((!($result)) && (list($key,$val) = each($paths))) {
1144             $result = file_exists($val . "/" . $filename);
1145         }
1146         return $result;
1147     }
1148 }
1149