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

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

Q - Changed <strong> to <em> in raiseMsg() calls.

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