Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 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 302518 2010-08-20 01:58:15Z clockwerx $
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
        $self = new Validate;
453
 
454
        $toValidate = array();
455
 
456
        foreach ($validate as $valid) {
457
            $tmpVar = '_' . (string)$valid;
458
 
459
            $toValidate[$valid] = $self->{$tmpVar};
460
        }
461
 
462
        $e = $self->executeFullEmailValidation($email, $toValidate);
463
 
464
        return $e;
465
    }
466
 
467
    /**
468
     * Execute the validation
469
     *
470
     * This function will execute the full email vs tld
471
     * validation using an array of tlds passed to it.
472
     *
473
     * @param string $email       The email to validate.
474
     * @param array  $arrayOfTLDs The array of the TLDs to validate
475
     *
476
     * @access public
477
     *
478
     * @return true or false (Depending on if it validates or if it does not)
479
     */
480
    function executeFullEmailValidation($email, $arrayOfTLDs)
481
    {
482
        $emailEnding = explode('.', $email);
483
        $emailEnding = $emailEnding[count($emailEnding)-1];
484
        foreach ($arrayOfTLDs as $validator => $keys) {
485
            if (in_array($emailEnding, $keys)) {
486
                return true;
487
            }
488
        }
489
        return false;
490
    }
491
 
492
    /**
493
     * Validate an email
494
     *
495
     * @param string $email  email to validate
496
     * @param mixed  boolean (BC) $check_domain Check or not if the domain exists
497
     *              array $options associative array of options
498
     *              'check_domain' boolean Check or not if the domain exists
499
     *              'use_rfc822' boolean Apply the full RFC822 grammar
500
     *
501
     * Ex.
502
     *  $options = array(
503
     *      'check_domain' => 'true',
504
     *      'fullTLDValidation' => 'true',
505
     *      'use_rfc822' => 'true',
506
     *      'VALIDATE_GTLD_EMAILS' => 'true',
507
     *      'VALIDATE_CCTLD_EMAILS' => 'true',
508
     *      'VALIDATE_ITLD_EMAILS' => 'true',
509
     *      );
510
     *
511
     * @return boolean true if valid email, false if not
512
     *
513
     * @access public
514
     */
515
    function email($email, $options = null)
516
    {
517
        $check_domain = false;
518
        $use_rfc822   = false;
519
        if (is_bool($options)) {
520
            $check_domain = $options;
521
        } elseif (is_array($options)) {
522
            extract($options);
523
        }
524
 
525
        /**
526
         * Check for IDN usage so we can encode the domain as Punycode
527
         * before continuing.
528
         */
529
        $hasIDNA = false;
530
 
531
        if (Validate::_includePathFileExists('Net/IDNA.php')) {
532
            include_once('Net/IDNA.php');
533
            $hasIDNA = true;
534
        }
535
 
536
        if ($hasIDNA === true) {
537
            if (strpos($email, '@') !== false) {
538
                $tmpEmail = explode('@', $email);
539
                $domain = array_pop($tmpEmail);
540
 
541
                // Check if the domain contains characters > 127 which means
542
                // it's an idn domain name.
543
                $chars = count_chars($domain, 1);
544
                if (!empty($chars) && max(array_keys($chars)) > 127) {
545
                    $idna   =& Net_IDNA::singleton();
546
                    $domain = $idna->encode($domain);
547
                }
548
 
549
                array_push($tmpEmail, $domain);
550
                $email = implode('@', $tmpEmail);
551
            }
552
        }
553
 
554
        /**
555
         * @todo Fix bug here.. even if it passes this, it won't be passing
556
         *       The regular expression below
557
         */
558
        if (isset($fullTLDValidation)) {
559
            //$valid = Validate::_fullTLDValidation($email, $fullTLDValidation);
560
            $valid = Validate::_fullTLDValidation($email, $options);
561
 
562
            if (!$valid) {
563
                return false;
564
            }
565
        }
566
 
567
        // the base regexp for address
568
        $regex = '&^(?:                                               # recipient:
569
         ("\s*(?:[^"\f\n\r\t\v\b\s]+\s*)+")|                          #1 quoted name
570
         ([-\w!\#\$%\&\'*+~/^`|{}]+(?:\.[-\w!\#\$%\&\'*+~/^`|{}]+)*)) #2 OR dot-atom
571
         @(((\[)?                     #3 domain, 4 as IPv4, 5 optionally bracketed
572
         (?:(?:(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:[0-1]?[0-9]?[0-9]))\.){3}
573
               (?:(?:25[0-5])|(?:2[0-4][0-9])|(?:[0-1]?[0-9]?[0-9]))))(?(5)\])|
574
         ((?:[a-z0-9](?:[-a-z0-9]*[a-z0-9])?\.)*[a-z0-9](?:[-a-z0-9]*[a-z0-9])?)  #6 domain as hostname
575
         \.((?:([^- ])[-a-z]*[-a-z]))) #7 TLD
576
         $&xi';
577
 
578
        //checks if exists the domain (MX or A)
579
        if ($use_rfc822? Validate::__emailRFC822($email, $options) :
580
                preg_match($regex, $email)) {
581
            if ($check_domain && function_exists('checkdnsrr')) {
582
                $domain = preg_replace('/[^-a-z.0-9]/i', '', array_pop(explode('@', $email)));
583
                if (checkdnsrr($domain, 'MX') || checkdnsrr($domain, 'A')) {
584
                    return true;
585
                }
586
                return false;
587
            }
588
            return true;
589
        }
590
        return false;
591
    }
592
 
593
    /**
594
     * Validate a string using the given format 'format'
595
     *
596
     * @param string $string  String to validate
597
     * @param array  $options Options array where:
598
     *                          'format' is the format of the string
599
     *                              Ex:VALIDATE_NUM . VALIDATE_ALPHA (see constants)
600
     *                          'min_length' minimum length
601
     *                          'max_length' maximum length
602
     *
603
     * @return boolean true if valid string, false if not
604
     *
605
     * @access public
606
     */
607
    function string($string, $options)
608
    {
609
        $format     = null;
610
        $min_length = 0;
611
        $max_length = 0;
612
 
613
        if (is_array($options)) {
614
            extract($options);
615
        }
616
 
617
        if ($format && !preg_match("|^[$format]*\$|s", $string)) {
618
            return false;
619
        }
620
 
621
        if ($min_length && strlen($string) < $min_length) {
622
            return false;
623
        }
624
 
625
        if ($max_length && strlen($string) > $max_length) {
626
            return false;
627
        }
628
 
629
        return true;
630
    }
631
 
632
    /**
633
     * Validate an URI (RFC2396)
634
     * This function will validate 'foobarstring' by default, to get it to validate
635
     * only http, https, ftp and such you have to pass it in the allowed_schemes
636
     * option, like this:
637
     * <code>
638
     * $options = array('allowed_schemes' => array('http', 'https', 'ftp'))
639
     * var_dump(Validate::uri('http://www.example.org', $options));
640
     * </code>
641
     *
642
     * NOTE 1: The rfc2396 normally allows middle '-' in the top domain
643
     *         e.g. http://example.co-m should be valid
644
     *         However, as '-' is not used in any known TLD, it is invalid
645
     * NOTE 2: As double shlashes // are allowed in the path part, only full URIs
646
     *         including an authority can be valid, no relative URIs
647
     *         the // are mandatory (optionally preceeded by the 'sheme:' )
648
     * NOTE 3: the full complience to rfc2396 is not achieved by default
649
     *         the characters ';/?:@$,' will not be accepted in the query part
650
     *         if not urlencoded, refer to the option "strict'"
651
     *
652
     * @param string $url     URI to validate
653
     * @param array  $options Options used by the validation method.
654
     *                          key => type
655
     *                          'domain_check' => boolean
656
     *                              Whether to check the DNS entry or not
657
     *                          'allowed_schemes' => array, list of protocols
658
     *                              List of allowed schemes ('http',
659
     *                              'ssh+svn', 'mms')
660
     *                          'strict' => string the refused chars
661
     *                              in query and fragment parts
662
     *                              default: ';/?:@$,'
663
     *                              empty: accept all rfc2396 foreseen chars
664
     *
665
     * @return boolean true if valid uri, false if not
666
     *
667
     * @access public
668
     */
669
    function uri($url, $options = null)
670
    {
671
        $strict = ';/?:@$,';
672
        $domain_check = false;
673
        $allowed_schemes = null;
674
        if (is_array($options)) {
675
            extract($options);
676
        }
677
        if (is_array($allowed_schemes) &&
678
            in_array("tag", $allowed_schemes)
679
        ) {
680
            if (strpos($url, "tag:") === 0) {
681
                return self::__uriRFC4151($url);
682
            }
683
        }
684
 
685
        if (preg_match(
686
             '&^(?:([a-z][-+.a-z0-9]*):)?                             # 1. scheme
687
              (?://                                                   # authority start
688
              (?:((?:%[0-9a-f]{2}|[-a-z0-9_.!~*\'();:\&=+$,])*)@)?    # 2. authority-userinfo
689
              (?:((?:[a-z0-9](?:[-a-z0-9]*[a-z0-9])?\.)*[a-z](?:[a-z0-9]+)?\.?)  # 3. authority-hostname OR
690
              |([0-9]{1,3}(?:\.[0-9]{1,3}){3}))                       # 4. authority-ipv4
691
              (?::([0-9]*))?)                                        # 5. authority-port
692
              ((?:/(?:%[0-9a-f]{2}|[-a-z0-9_.!~*\'():@\&=+$,;])*)*/?)? # 6. path
693
              (?:\?([^#]*))?                                          # 7. query
694
              (?:\#((?:%[0-9a-f]{2}|[-a-z0-9_.!~*\'();/?:@\&=+$,])*))? # 8. fragment
695
              $&xi', $url, $matches)) {
696
            $scheme = isset($matches[1]) ? $matches[1] : '';
697
            $authority = isset($matches[3]) ? $matches[3] : '' ;
698
            if (is_array($allowed_schemes) &&
699
                !in_array($scheme, $allowed_schemes)
700
            ) {
701
                return false;
702
            }
703
            if (!empty($matches[4])) {
704
                $parts = explode('.', $matches[4]);
705
                foreach ($parts as $part) {
706
                    if ($part > 255) {
707
                        return false;
708
                    }
709
                }
710
            } elseif ($domain_check && function_exists('checkdnsrr')) {
711
                if (!checkdnsrr($authority, 'A')) {
712
                    return false;
713
                }
714
            }
715
            if ($strict) {
716
                $strict = '#[' . preg_quote($strict, '#') . ']#';
717
                if ((!empty($matches[7]) && preg_match($strict, $matches[7]))
718
                 || (!empty($matches[8]) && preg_match($strict, $matches[8]))) {
719
                    return false;
720
                }
721
            }
722
            return true;
723
        }
724
        return false;
725
    }
726
 
727
    /**
728
     * Validate date and times. Note that this method need the Date_Calc class
729
     *
730
     * @param string $date    Date to validate
731
     * @param array  $options array options where :
732
     *                          'format' The format of the date (%d-%m-%Y)
733
     *                                   or rfc822_compliant
734
     *                          'min'    The date has to be greater
735
     *                                   than this array($day, $month, $year)
736
     *                                   or PEAR::Date object
737
     *                          'max'    The date has to be smaller than
738
     *                                   this array($day, $month, $year)
739
     *                                   or PEAR::Date object
740
     *
741
     * @return boolean true if valid date/time, false if not
742
     *
743
     * @access public
744
     */
745
    function date($date, $options)
746
    {
747
        $max    = false;
748
        $min    = false;
749
        $format = '';
750
 
751
        if (is_array($options)) {
752
            extract($options);
753
        }
754
 
755
        if (strtolower($format) == 'rfc822_compliant') {
756
            $preg = '&^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),) \s+
757
                    (?:(\d{2})?) \s+
758
                    (?:(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)?) \s+
759
                    (?:(\d{2}(\d{2})?)?) \s+
760
                    (?:(\d{2}?)):(?:(\d{2}?))(:(?:(\d{2}?)))? \s+
761
                    (?:[+-]\d{4}|UT|GMT|EST|EDT|CST|CDT|MST|MDT|PST|PDT|[A-IK-Za-ik-z])$&xi';
762
 
763
            if (!preg_match($preg, $date, $matches)) {
764
                return false;
765
            }
766
 
767
            $year    = (int)$matches[4];
768
            $months  = array('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
769
                             'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec');
770
            $month   = array_keys($months, $matches[3]);
771
            $month   = (int)$month[0]+1;
772
            $day     = (int)$matches[2];
773
            $weekday = $matches[1];
774
            $hour    = (int)$matches[6];
775
            $minute  = (int)$matches[7];
776
            isset($matches[9]) ? $second = (int)$matches[9] : $second = 0;
777
 
778
            if ((strlen($year) != 4)        ||
779
                ($day    > 31   || $day < 1)||
780
                ($hour   > 23)  ||
781
                ($minute > 59)  ||
782
                ($second > 59)) {
783
                    return false;
784
            }
785
        } else {
786
            $date_len = strlen($format);
787
            for ($i = 0; $i < $date_len; $i++) {
788
                $c = $format{$i};
789
                if ($c == '%') {
790
                    $next = $format{$i + 1};
791
                    switch ($next) {
792
                    case 'j':
793
                    case 'd':
794
                        if ($next == 'j') {
795
                            $day = (int)Validate::_substr($date, 1, 2);
796
                        } else {
797
                            $day = (int)Validate::_substr($date, 0, 2);
798
                        }
799
                        if ($day < 1 || $day > 31) {
800
                            return false;
801
                        }
802
                        break;
803
                    case 'm':
804
                    case 'n':
805
                        if ($next == 'm') {
806
                            $month = (int)Validate::_substr($date, 0, 2);
807
                        } else {
808
                            $month = (int)Validate::_substr($date, 1, 2);
809
                        }
810
                        if ($month < 1 || $month > 12) {
811
                            return false;
812
                        }
813
                        break;
814
                    case 'Y':
815
                    case 'y':
816
                        if ($next == 'Y') {
817
                            $year = Validate::_substr($date, 4);
818
                            $year = (int)$year?$year:'';
819
                        } else {
820
                            $year = (int)(substr(date('Y'), 0, 2) .
821
                                              Validate::_substr($date, 2));
822
                        }
823
                        if (strlen($year) != 4 || $year < 0 || $year > 9999) {
824
                            return false;
825
                        }
826
                        break;
827
                    case 'g':
828
                    case 'h':
829
                        if ($next == 'g') {
830
                            $hour = Validate::_substr($date, 1, 2);
831
                        } else {
832
                            $hour = Validate::_substr($date, 2);
833
                        }
834
                        if (!preg_match('/^\d+$/', $hour) || $hour < 0 || $hour > 12) {
835
                            return false;
836
                        }
837
                        break;
838
                    case 'G':
839
                    case 'H':
840
                        if ($next == 'G') {
841
                            $hour = Validate::_substr($date, 1, 2);
842
                        } else {
843
                            $hour = Validate::_substr($date, 2);
844
                        }
845
                        if (!preg_match('/^\d+$/', $hour) || $hour < 0 || $hour > 24) {
846
                            return false;
847
                        }
848
                        break;
849
                    case 's':
850
                    case 'i':
851
                        $t = Validate::_substr($date, 2);
852
                        if (!preg_match('/^\d+$/', $t) || $t < 0 || $t > 59) {
853
                            return false;
854
                        }
855
                        break;
856
                    default:
857
                        trigger_error("Not supported char `$next' after % in offset " . ($i+2), E_USER_WARNING);
858
                    }
859
                    $i++;
860
                } else {
861
                    //literal
862
                    if (Validate::_substr($date, 1) != $c) {
863
                        return false;
864
                    }
865
                }
866
            }
867
        }
868
        // there is remaing data, we don't want it
869
        if (strlen($date) && (strtolower($format) != 'rfc822_compliant')) {
870
            return false;
871
        }
872
 
873
        if (isset($day) && isset($month) && isset($year)) {
874
            if (!checkdate($month, $day, $year)) {
875
                return false;
876
            }
877
 
878
            if (strtolower($format) == 'rfc822_compliant') {
879
                if ($weekday != date("D", mktime(0, 0, 0, $month, $day, $year))) {
880
                    return false;
881
                }
882
            }
883
 
884
            if ($min) {
885
                include_once 'Date/Calc.php';
886
                if (is_a($min, 'Date') &&
887
                    (Date_Calc::compareDates($day, $month, $year,
888
                        $min->getDay(), $min->getMonth(), $min->getYear()) < 0)
889
                ) {
890
                    return false;
891
                } elseif (is_array($min) &&
892
                        (Date_Calc::compareDates($day, $month, $year,
893
                            $min[0], $min[1], $min[2]) < 0)
894
                ) {
895
                    return false;
896
                }
897
            }
898
 
899
            if ($max) {
900
                include_once 'Date/Calc.php';
901
                if (is_a($max, 'Date') &&
902
                    (Date_Calc::compareDates($day, $month, $year,
903
                        $max->getDay(), $max->getMonth(), $max->getYear()) > 0)
904
                ) {
905
                    return false;
906
                } elseif (is_array($max) &&
907
                        (Date_Calc::compareDates($day, $month, $year,
908
                            $max[0], $max[1], $max[2]) > 0)
909
                ) {
910
                    return false;
911
                }
912
            }
913
        }
914
 
915
        return true;
916
    }
917
 
918
    /**
919
     * Substr
920
     *
921
     * @param string &$date Date
922
     * @param string $num   Length
923
     * @param string $opt   Unknown
924
     *
925
     * @access private
926
     * @return string
927
     */
928
    function _substr(&$date, $num, $opt = false)
929
    {
930
        if ($opt && strlen($date) >= $opt && preg_match('/^[0-9]{'.$opt.'}/', $date, $m)) {
931
            $ret = $m[0];
932
        } else {
933
            $ret = substr($date, 0, $num);
934
        }
935
        $date = substr($date, strlen($ret));
936
        return $ret;
937
    }
938
 
939
    function _modf($val, $div)
940
    {
941
        if (function_exists('bcmod')) {
942
            return bcmod($val, $div);
943
        } elseif (function_exists('fmod')) {
944
            return fmod($val, $div);
945
        }
946
        $r = $val / $div;
947
        $i = intval($r);
948
        return intval($val - $i * $div + .1);
949
    }
950
 
951
    /**
952
     * Calculates sum of product of number digits with weights
953
     *
954
     * @param string $number  number string
955
     * @param array  $weights reference to array of weights
956
     *
957
     * @access protected
958
     *
959
     * @return int returns product of number digits with weights
960
     */
961
    function _multWeights($number, &$weights)
962
    {
963
        if (!is_array($weights)) {
964
            return -1;
965
        }
966
        $sum = 0;
967
 
968
        $count = min(count($weights), strlen($number));
969
        if ($count == 0) { // empty string or weights array
970
            return -1;
971
        }
972
        for ($i = 0; $i < $count; ++$i) {
973
            $sum += intval(substr($number, $i, 1)) * $weights[$i];
974
        }
975
 
976
        return $sum;
977
    }
978
 
979
    /**
980
     * Calculates control digit for a given number
981
     *
982
     * @param string $number     number string
983
     * @param array  $weights    reference to array of weights
984
     * @param int    $modulo     (optionsl) number
985
     * @param int    $subtract   (optional) number
986
     * @param bool   $allow_high (optional) true if function can return number higher than 10
987
     *
988
     * @access protected
989
     *
990
     * @return  int -1 calculated control number is returned
991
     */
992
    function _getControlNumber($number, &$weights, $modulo = 10, $subtract = 0, $allow_high = false)
993
    {
994
        // calc sum
995
        $sum = Validate::_multWeights($number, $weights);
996
        if ($sum == -1) {
997
            return -1;
998
        }
999
        $mod = Validate::_modf($sum, $modulo);  // calculate control digit
1000
 
1001
        if ($subtract > $mod && $mod > 0) {
1002
            $mod = $subtract - $mod;
1003
        }
1004
        if ($allow_high === false) {
1005
            $mod %= 10;           // change 10 to zero
1006
        }
1007
        return $mod;
1008
    }
1009
 
1010
    /**
1011
     * Validates a number
1012
     *
1013
     * @param string $number   number to validate
1014
     * @param array  $weights  reference to array of weights
1015
     * @param int    $modulo   (optional) number
1016
     * @param int    $subtract (optional) number
1017
     *
1018
     * @access protected
1019
     *
1020
     * @return  bool true if valid, false if not
1021
     */
1022
    function _checkControlNumber($number, &$weights, $modulo = 10, $subtract = 0)
1023
    {
1024
        if (strlen($number) < count($weights)) {
1025
            return false;
1026
        }
1027
        $target_digit  = substr($number, count($weights), 1);
1028
        $control_digit = Validate::_getControlNumber($number, $weights, $modulo, $subtract, $modulo > 10);
1029
 
1030
        if ($control_digit == -1) {
1031
            return false;
1032
        }
1033
        if ($target_digit === 'X' && $control_digit == 10) {
1034
            return true;
1035
        }
1036
        if ($control_digit != $target_digit) {
1037
            return false;
1038
        }
1039
        return true;
1040
    }
1041
 
1042
    /**
1043
     * Bulk data validation for data introduced in the form of an
1044
     * assoc array in the form $var_name => $value.
1045
     * Can be used on any of Validate subpackages
1046
     *
1047
     * @param array   $data     Ex: array('name' => 'toto', 'email' => 'toto@thing.info');
1048
     * @param array   $val_type Contains the validation type and all parameters used in.
1049
     *                          'val_type' is not optional
1050
     *                          others validations properties must have the same name as the function
1051
     *                          parameters.
1052
     *                          Ex: array('toto'=>array('type'=>'string','format'='toto@thing.info','min_length'=>5));
1053
     * @param boolean $remove   if set, the elements not listed in data will be removed
1054
     *
1055
     * @return array   value name => true|false    the value name comes from the data key
1056
     *
1057
     * @access public
1058
     */
1059
    function multiple(&$data, &$val_type, $remove = false)
1060
    {
1061
        $keys  = array_keys($data);
1062
        $valid = array();
1063
 
1064
        foreach ($keys as $var_name) {
1065
            if (!isset($val_type[$var_name])) {
1066
                if ($remove) {
1067
                    unset($data[$var_name]);
1068
                }
1069
                continue;
1070
            }
1071
            $opt       = $val_type[$var_name];
1072
            $methods   = get_class_methods('Validate');
1073
            $val2check = $data[$var_name];
1074
            // core validation method
1075
            if (in_array(strtolower($opt['type']), $methods)) {
1076
                //$opt[$opt['type']] = $data[$var_name];
1077
                $method = $opt['type'];
1078
                unset($opt['type']);
1079
 
1080
                if (sizeof($opt) == 1 && is_array(reset($opt))) {
1081
                    $opt = array_pop($opt);
1082
                }
1083
                $valid[$var_name] = call_user_func(array('Validate', $method), $val2check, $opt);
1084
 
1085
                /**
1086
                 * external validation method in the form:
1087
                 * "<class name><underscore><method name>"
1088
                 * Ex: us_ssn will include class Validate/US.php and call method ssn()
1089
                 */
1090
            } elseif (strpos($opt['type'], '_') !== false) {
1091
                $validateType = explode('_', $opt['type']);
1092
                $method       = array_pop($validateType);
1093
                $class        = implode('_', $validateType);
1094
                $classPath    = str_replace('_', DIRECTORY_SEPARATOR, $class);
1095
                $class        = 'Validate_' . $class;
1096
                if (!Validate::_includePathFileExists("Validate/$classPath.php")) {
1097
                    trigger_error("$class isn't installed or you may have some permission issues", E_USER_ERROR);
1098
                }
1099
 
1100
                $ce = substr(phpversion(), 0, 1) > 4 ?
1101
                    class_exists($class, false) : class_exists($class);
1102
                if (!$ce ||
1103
                    !in_array($method, get_class_methods($class))
1104
                ) {
1105
                    trigger_error("Invalid validation type $class::$method",
1106
                        E_USER_WARNING);
1107
                    continue;
1108
                }
1109
                unset($opt['type']);
1110
                if (sizeof($opt) == 1) {
1111
                    $opt = array_pop($opt);
1112
                }
1113
                $valid[$var_name] = call_user_func(array($class, $method),
1114
                    $data[$var_name], $opt);
1115
            } else {
1116
                trigger_error("Invalid validation type {$opt['type']}",
1117
                    E_USER_WARNING);
1118
            }
1119
        }
1120
        return $valid;
1121
    }
1122
 
1123
    /**
1124
     * Determine whether specified file exists along the include path.
1125
     *
1126
     * @param string $filename file to search for
1127
     *
1128
     * @access private
1129
     *
1130
     * @return bool true if file exists
1131
     */
1132
    function _includePathFileExists($filename)
1133
    {
1134
        $paths = explode(":", ini_get("include_path"));
1135
        $result = false;
1136
 
1137
        while ((!($result)) && (list($key,$val) = each($paths))) {
1138
            $result = file_exists($val . "/" . $filename);
1139
        }
1140
        return $result;
1141
    }
1142
}
1143