source: trunk/lib/FormValidator.inc.php @ 96

Last change on this file since 96 was 96, checked in by scdev, 18 years ago

Q - added default marker of ' class="error"'. This can be redefined before calling $fv->err().

File size: 21.3 KB
Line 
1<?php
2/**
3 * FormValidator.inc.php
4 * Code by Strangecode :: www.strangecode.com :: This document contains copyrighted information
5 *
6 * The FormValidator:: class provides a method for validating input from
7 * http requests and displaying errors.
8 *
9 * @author    Quinn Comendant <quinn@strangecode.com>
10 * @version   1.7
11 *
12 * Examples of use:
13 *
14 * require_once 'codebase/lib/FormValidator.inc.php';
15 * $fv = new FormValidator();
16 *
17 * $fv->isEmpty('location_name', _("<strong>Location name</strong> cannot be blank."));
18 * $fv->checkRegex('cc_exp', '/^\d{4}$|^$/', true, _("CC exp date must be in MMYY format."));
19 * $fv->isInteger('client_id', _("<strong>Client id</strong> must be an integer."));
20 * $fv->numericRange('client_id', -32768, 32767, _("<strong>Client id</strong> must be a number between -32768 and 32767."));
21 * $fv->stringLength('zip', 0, 255, _("<strong>Zip</strong> must contain less than 256 characters."));
22 * $fv->validateEmail('invoice_email');
23 * $fv->validatePhone('phone1');
24 */
25class FormValidator
26{
27
28    /**
29     * Array filling with errors. The key will be the name of the form where
30     * the data came from. The value will be a message to print to the user.
31     */
32    var $errors = array();
33   
34    // Default error marker;
35    var $marker = ' class="error"';
36
37    /**
38     * Return the current list of errors.
39     *
40     * @return array    an array of errors in the following arrangement:
41     *                  keys: the name of the variable with an error
42     *                  vals: the message to display for that error
43     */
44    function getErrorList()
45    {
46        return $this->errors;
47    }
48
49    /**
50     * Add an error to the errors stack.
51     *
52     * @param   string $form_name   The name of the incoming form variable.
53     * @param   string $msg         The error message for that form.
54     * @param   int    $type        The type of message: MSG_NOTICE,
55     *                              MSG_SUCCESS, MSG_WARNING, or MSG_ERR.
56     * @param   string $file        __FILE__.
57     * @param   string $line        __LINE__.
58     */
59    function addError($form_name, $msg='', $type=MSG_ERR, $file=null, $line=null)
60    {
61        $this->errors[] = array(
62            'name' => $form_name,
63            'message' => $msg,
64            'type' => $type,
65            'file' => $file,
66            'line' => $line
67        );
68    }
69
70    /**
71     * Check whether any errors have been triggered.
72     *
73     * @param  string $form_name the name of the incoming form variable
74     *
75     * @return bool   true if any errors were found, or if found for
76     *                a variable of $form_name, false otherwise
77     */
78    function anyErrors($form_name=null)
79    {
80        if (isset($form_name)) {
81            foreach ($this->errors as $err) {
82                if ($err['name'] == $form_name) {
83                    return true;
84                }
85            }
86            return false;
87        }
88        return (sizeof($this->errors) > 0);
89    }
90
91    /**
92     * Reset the error list.
93     */
94    function resetErrorList()
95    {
96        $this->errors = array();
97    }
98
99    /**
100     * Prints the HTML for displaying error messages.
101     *
102     * @access  public
103     * @author  Quinn Comendant <quinn@strangecode.com>
104     * @since   15 Jul 2005 01:39:14
105     */
106    function printErrorMessages()
107    {
108        if ($this->anyErrors()) {
109            ?><div class="codebasemsg"><?php
110            $errors = $this->getErrorList();
111            foreach ($errors as $err) {
112                if (!empty($err['message']) && is_string($err['message'])) {
113                    if (error_reporting() > 0 && isset($err['file']) && isset($err['line'])) {
114                        echo "\n<!-- [" . $err['file'] . ' : ' . $err['line'] . '] -->';
115                    }
116                    switch ($err['type']) {
117                    case MSG_ERR:
118                        echo '<div class="error">' . $err['message'] . '</div>';
119                        break;
120
121                    case MSG_WARNING:
122                        echo '<div class="warning">' . $err['message'] . '</div>';
123                        break;
124
125                    case MSG_SUCCESS:
126                        echo '<div class="success">' . $err['message'] . '</div>';
127                        break;
128
129                    case MSG_NOTICE:
130                    default:
131                        echo '<div class="notice">' . $err['message'] . '</div>';
132                        break;
133                    }
134                }
135            }
136            ?></div><?php
137        }
138    }
139
140    /**
141     * If this form has an error, print an error marker like "<<".
142     *
143     * @param  string $form_name the name of the incoming form variable
144     * @param  string $marker    A string to print if there is an error. if
145     *                           not provided, use default.
146     */
147    function err($form_name, $marker=null)
148    {
149        if ($this->anyErrors($form_name)) {
150            if (isset($marker)) {
151                echo $marker;
152            } else {
153                echo $this->marker;
154            }
155        }
156    }
157
158    /**
159     * Check whether input has a value. To be used when a value must be empty
160     * under certain circumstances.
161     *
162     * @param  string $form_name the name of the incoming form variable
163     * @param  string $msg       the message to display on error
164     *
165     * @return bool   true if form is not empty, false otherwise.
166     */
167    function notEmpty($form_name, $msg='')
168    {
169
170        $val = trim(getFormData($form_name));
171        if ($val != '') {
172            $this->addError($form_name, $msg);
173            return true;
174        } else {
175            return false;
176        }
177    }
178
179    /**
180     * Check whether input is blank.
181     *
182     * @param  string $form_name the name of the incoming form variable
183     * @param  string $msg       the message to display on error
184     *
185     * @return bool   true if form is empty, false otherwise.
186     */
187    function isEmpty($form_name, $msg='')
188    {
189
190        $val = trim(getFormData($form_name));
191        if ($val == '') {
192            $this->addError($form_name, $msg);
193            return true;
194        } else {
195            return false;
196        }
197    }
198
199    /**
200     * Check whether input is a string.
201     *
202     * @param  string $form_name the name of the incoming form variable
203     * @param  string $msg       the message to display on error
204     *
205     * @return bool   true if form is a string, false otherwise.
206     */
207    function isString($form_name, $msg='')
208    {
209        $val = getFormData($form_name);
210        if (!is_string($val) && $val != '') {
211            $this->addError($form_name, $msg);
212            return false;
213        } else {
214            return true;
215        }
216    }
217
218    /**
219     * Check whether input is a number. Allows negative numbers.
220     *
221     * @param  string $form_name the name of the incoming form variable
222     * @param  string $msg       the message to display on error
223     *
224     * @return bool   true if no errors found, false otherwise
225     */
226    function isNumber($form_name, $msg='')
227    {
228        $val = getFormData($form_name);
229        if (!is_numeric($val) && $val != '') {
230            $this->addError($form_name, $msg);
231            return false;
232        } else {
233            return true;
234        }
235    }
236
237    /**
238     * addError if input is NOT an integer. Don't just use is_int() because the
239     * data coming from the user is *really* a string.
240     *
241     * @param  string $form_name the name of the incoming form variable
242     * @param  string $msg       the message to display on error
243     *
244     * @return bool   true if value is an integer
245     */
246    function isInteger($form_name, $msg='', $negative_ok=false)
247    {
248        $val = getFormData($form_name);
249        $pattern = $negative_ok ? '/^-?[[:digit:]]+$/' : '/^[[:digit:]]+$/';
250        if ((!is_numeric($val) || !preg_match($pattern, $val)) && $val != '') {
251            $this->addError($form_name, $msg);
252            return false;
253        } else {
254            return true;
255        }
256    }
257
258    /**
259     * Check whether input is a float. Don't just use is_float() because the
260     * data coming from the user is *really* a string. Integers will also
261     * pass this test.
262     *
263     * @param  string $form_name the name of the incoming form variable
264     * @param  string $msg       the message to display on error
265     *
266     * @return bool   true if value is a float
267     */
268    function isFloat($form_name, $msg='', $negative_ok=false)
269    {
270        $val = getFormData($form_name);
271        $pattern = $negative_ok ? '/^-?[[:digit:]]*(?:\.?[[:digit:]]+)$/' : '/^[[:digit:]]*(?:\.?[[:digit:]]+)$/';
272        if ((!is_numeric($val) || !preg_match($pattern, $val)) && $val != '') {
273            $this->addError($form_name, $msg);
274            return false;
275        } else {
276            return true;
277        }
278    }
279
280    /**
281     * Check whether input is an array.
282     *
283     * @param  string $form_name the name of the incoming form variable
284     * @param  string $msg       the message to display on error
285     *
286     * @return bool   true if value is a float
287     */
288    function isArray($form_name, $msg='')
289    {
290        $val = getFormData($form_name);
291        if (!is_array($val) && !empty($val)) {
292            $this->addError($form_name, $msg);
293            return false;
294        } else {
295            return true;
296        }
297    }
298
299    /**
300     * Check whether input matches the specified perl regular expression
301     * pattern.
302     *
303     * @param  string $form_name the name of the incoming form variable
304     * @param  int    $regex     perl regex that the string must match
305     * @param  bool   $not       set to false to be valid if match, or true
306     *                           to be valid on no match
307     * @param  string $msg       the message to display on error
308     *
309     * @return bool   true if value passes regex test
310     */
311    function checkRegex($form_name, $regex, $not, $msg='')
312    {
313        $val = getFormData($form_name);
314        if ($not) {
315            if (!preg_match($regex, $val))  {
316                $this->addError($form_name, $msg);
317                return false;
318            } else {
319                return true;
320            }
321        } else {
322            if (preg_match($regex, $val))  {
323                $this->addError($form_name, $msg);
324                return false;
325            } else {
326                return true;
327            }
328        }
329    }
330
331    /**
332     * Tests if the string length is between specified values. Whitespace excluded for min.
333     *
334     * @param  string $form_name the name of the incoming form variable
335     * @param  int    $min       minimum length of string, inclusive
336     * @param  int    $max       maximum length of string, inclusive
337     * @param  string $msg       the message to display on error
338     *
339     * @return bool   true if string length is within given boundaries
340     */
341    function stringLength($form_name, $min, $max, $msg='')
342    {
343        $val = getFormData($form_name);
344
345        if (strlen(trim($val)) < $min || strlen($val) > $max) {
346            $this->addError($form_name, $msg);
347            return false;
348        } else {
349            return true;
350        }
351    }
352
353    /**
354     * Check whether input is within a valid numeric range.
355     *
356     * @param  string $form_name the name of the incoming form variable
357     * @param  int    $min       minimum value of number, inclusive
358     * @param  int    $max       maximum value of number, inclusive
359     * @param  string $msg       the message to display on error
360     *
361     * @return bool   true if no errors found, false otherwise
362     */
363    function numericRange($form_name, $min, $max, $msg='')
364    {
365        $val = getFormData($form_name);
366        if ($val != '' && is_numeric($val)) {
367            if ($val < $min || $val > $max) {
368                $this->addError($form_name, $msg);
369                return false;
370            }
371            return true;
372        } else {
373            // Not a number!
374            return false;
375        }
376    }
377
378    /**
379     * Validates an email address based on the recommendations in RFC 3696.
380     * Is more loose than restrictive, to allow the many valid variants of
381     * email addresses while catching the most common mistakes.
382     * http://www.faqs.org/rfcs/rfc822.html
383     * http://www.faqs.org/rfcs/rfc2822.html
384     * http://www.faqs.org/rfcs/rfc3696.html
385     * http://www.faqs.org/rfcs/rfc1035.html
386     *
387     * @access  public
388     * @param   string  $form_name  The name of the incoming form variable.
389     * @return  bool    Validity of address.
390     * @author  Quinn Comendant <quinn@strangecode.com>
391     */
392    function validateEmail($form_name)
393    {
394        $email = getFormData($form_name);
395        if ('' == trim($email)) {
396            return false;
397        }
398
399        require_once 'codebase/lib/Email.inc.php';
400        $e = new Email();
401
402        // Test email address format.
403        if (!preg_match($e->getParam('regex'), $email, $e_parts)) {
404            $this->addError($form_name, sprintf(_("<strong>%s</strong> is not a valid email address."), oTxt($email)));
405            App::logMsg(sprintf('The email address %s is not valid.', getFormData($form_name)), LOG_DEBUG, __FILE__, __LINE__);
406            return false;
407        }
408
409        // We have a match! Here are the captured subpatterns, on which further tests are run.
410        $local = $e_parts[2];
411        // If domain is an IP [XXX.XXX.XXX.XXX] strip off the brackets.
412        $domain = $e_parts[3]{0} == '[' ? substr($e_parts[3], 1, -1) : $e_parts[3];
413
414        // Test length.
415        if (strlen($local) > 64 || strlen($domain) > 191) {
416            $this->addError($form_name, sprintf(_("<strong>Email address</strong> must contain less than 256 characters."), oTxt($email)));
417            App::logMsg(sprintf('The email address %s must contain less than 256 characters.', getFormData($form_name)), LOG_DEBUG, __FILE__, __LINE__);
418            return false;
419        }
420
421        // Check domain exists: It's a domain if ip2long fails; Checkdnsrr ensures a MX record exists; Gethostbyname() ensures the domain exists.
422        if (ip2long($domain) == '-1' && function_exists('checkdnsrr') && !checkdnsrr($domain . '.', 'MX') && gethostbyname($domain) == $domain) {
423            $this->addError($form_name, sprintf(_("<strong>%s</strong> is not a valid email domain name"), oTxt($domain)));
424            App::logMsg(sprintf('The email address %s contains an invalid email domain name (%s).', getFormData($form_name), $domain), LOG_INFO, __FILE__, __LINE__);
425            return false;
426        }
427
428        return true;
429    }
430//     function validateEmail($form_name, $allow_fullname=false)
431//     {
432//         $email = getFormData($form_name);
433//         if ('' == trim($email)) {
434//             return false;
435//         }
436//
437//         // Test email address format.
438//         if ($allow_fullname) {
439//             if (!$this->checkRegex($form_name, '/^[^<>@]*<?[^\s@\[\]<>()]+\@[A-Za-z0-9.-]+\.[A-Za-z]{2,5}>?$/i', true, sprintf(_("<strong>%s</strong> is not a valid email address."), $email))) {
440//                 App::logMsg(sprintf('The email address %s is not valid.', getFormData($form_name)), LOG_DEBUG, __FILE__, __LINE__);
441//                 return false;
442//             }
443//         } else {
444//             if (!$this->checkRegex($form_name, '/^[^\s@\[\]<>()]+\@[A-Za-z0-9.-]+\.[A-Za-z]{2,5}$/i', true, sprintf(_("<strong>%s</strong> is not a valid email address."), $email))) {
445//                 App::logMsg(sprintf('The email address %s is not valid.', getFormData($form_name)), LOG_DEBUG, __FILE__, __LINE__);
446//                 return false;
447//             }
448//         }
449//
450//         // Test length.
451//         if (!$this->stringLength($form_name, 0, 255, sprintf(_("<strong>Email address</strong> must contain less than 256 characters."), $email))) {
452//             App::logMsg(sprintf('The email address %s must contain less than 256 characters.', getFormData($form_name)), LOG_DEBUG, __FILE__, __LINE__);
453//             return false;
454//         }
455//
456//         // Check domain exists and has valid MX record.
457//         preg_match('/^[^<>@]*<?[^\s@\[\]<>()]+\@([A-Za-z0-9.-]+\.[A-Za-z]{2,5})>?$/i', $email, $matches);
458//         if (!empty($matches[1])) {
459//             if (!checkdnsrr($matches[1] . '.', 'MX') && gethostbyname($matches[1]) == $matches[1]) {
460//                 $this->addError($form_name, sprintf(_("<strong>%s</strong> is not a valid email domain name"), $matches[1]));
461//                 App::logMsg(sprintf('The email address %s contains an invalid email domain name (%s).', getFormData($form_name), $matches[1]), LOG_DEBUG, __FILE__, __LINE__);
462//                 return false;
463//             }
464//         }
465//
466//         return true;
467//     }
468
469    /**
470     * Check whether input is a valid phone number. Notice: it is now set
471     * to allow characters like - or () or + so people can type in a phone
472     * number that looks like: +1 (530) 624-4410
473     *
474     * @param  string  $form_name the name of the incoming form variable
475     *
476     * @return bool    true if no errors found, false otherwise
477     */
478    function validatePhone($form_name)
479    {
480        $phone = getFormData($form_name);
481
482        return $this->checkRegex($form_name, '/^[0-9 +().-]*$/', true, sprintf(_("The phone number <strong>%s</strong> is not valid."), $phone))
483        && $this->stringLength($form_name, 0, 25, sprintf(_("The phone number <strong>%s</strong> is too long"), $phone));
484    }
485
486    /**
487     * Verifies that date can be processed by the strtotime function.
488     *
489     * @param  string  $form_name the name of the incoming form variable
490     * @param  string  $msg       the message to display on error
491     *
492     * @return bool    true if no errors found, false otherwise
493     */
494    function validateStrDate($form_name, $msg='')
495    {
496        if (($timestamp = strtotime(getFormData($form_name, '0'))) === -1) {
497            $this->addError($form_name, $msg);
498            App::logMsg(sprintf('The string date %s is not valid.', getFormData($form_name)), LOG_DEBUG, __FILE__, __LINE__);
499            return false;
500        } else {
501            return true;
502        }
503    }
504
505
506    /**
507     * Verifies credit card number.
508     *
509     * @param  string  $form_name   The name of the incoming form variable.
510     * @param  string  $cc_num      Card number to verify.
511     * @param  string  $cc_type     Optional, card type to do specific checks.
512     *
513     * @return bool    true if no errors found, false otherwise
514     */
515    function validateCCNumber($form_name, $cc_num=null, $cc_type=null)
516    {
517        if (!isset($cc_num)) {
518            $cc_num = getFormData($form_name);
519        }
520
521        if ('' == $cc_num) {
522            return false;
523        }
524
525        // Innocent until proven guilty
526        $card_is_valid = true;
527
528        // Get rid of any non-digits
529        $cc_num = preg_replace('/[^\d]/', '', $cc_num);
530
531        // Perform card-specific checks, if applicable
532        switch (strtolower($cc_type)) {
533            case 'visa' :
534                $card_is_valid = preg_match('/^4\d{15}$|^4\d{12}$/', $cc_num);
535                break;
536            case 'mastercard' :
537            case 'mc' :
538                $card_is_valid = preg_match('/^5[1-5]\d{14}$/', $cc_num);
539                break;
540            case 'american_express' :
541            case 'american_ex' :
542            case 'americanexpress' :
543            case 'americanex' :
544            case 'am_ex' :
545            case 'amex' :
546            case 'ae' :
547                $card_is_valid = preg_match('/^3[47]\d{13}$/', $cc_num);
548                break;
549            case 'discover' :
550                $card_is_valid = preg_match('/^6011\d{12}$/', $cc_num);
551                break;
552            case 'diners_club' :
553            case 'dinersclub' :
554            case 'diners' :
555            case 'diner' :
556            case 'dc' :
557                $card_is_valid = preg_match('/^30[0-5]\d{11}$|^3[68]\d{12}$/', $cc_num);
558                break;
559            case 'jcb' :
560                $card_is_valid = preg_match('/^3\d{15}$|^2131|1800\d{11}$/', $cc_num);
561                break;
562        }
563
564        // The Luhn formula works right to left, so reverse the number.
565        $cc_num = strrev($cc_num);
566
567        $luhn_total = 0;
568
569        $num = strlen($cc_num);
570        for ($i=0; $i<$num; $i++) {
571            // Get each digit.
572            $digit = substr($cc_num,$i,1);
573
574            //  If it's an odd digit, double it.
575            if ($i / 2 != floor($i / 2)) {
576                $digit *= 2;
577            }
578
579            //  If the result is two digits, add them.
580            if (strlen($digit) == 2) {
581                $digit = substr($digit,0,1) + substr($digit,1,1);
582            }
583
584            //  Add the current digit to the $luhn_total.
585            $luhn_total += $digit;
586        }
587
588        // If it passed (or bypassed) the card-specific check and the Total is evenly divisible by 10, it's cool!
589        if ($card_is_valid && $luhn_total % 10 == 0) {
590            return true;
591        } else {
592            $this->addError($form_name, _("The <strong>credit card number</strong> you entered is not valid."));
593            return false;
594        }
595    }
596
597    /**
598     * Check whether uploaded file is valid.
599     *
600     * @param  string $form_name the name of the incoming form variable
601     * @param  string $msg       the message to display on error
602     *
603     * @return bool   true if no errors found, false otherwise
604     */
605    function validateFile($form_name, $msg='')
606    {
607        if (isset($_FILES[$form_name]['tmp_name']) && '' == trim($_FILES[$form_name]['tmp_name'])) {
608            $this->addError($form_name, $msg);
609            return false;
610        } else {
611            return true;
612        }
613    }
614
615} // THE END
616
617?>
Note: See TracBrowser for help on using the repository browser.