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

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

Added Email() class to work with verification, and sending of emails and templates. Updated Formvalidator to use the regex in Email().

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