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