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

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

Q - Added "sc-" to all css class selectors; Finished reworking Upload and ImageThumb? (now with GD support!); More PHP 5 upgrades.

File size: 19.2 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 * 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    // Array filling with error messages.
29    var $errors = array();
30   
31    // Default error marker;
32    var $marker = ' class="sc-msg-error"';
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="sc-msg"><?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="sc-msg-error">' . $err['message'] . '</div>';
116                        break;
117
118                    case MSG_WARNING:
119                        echo '<div class="sc-msg-warning">' . $err['message'] . '</div>';
120                        break;
121
122                    case MSG_SUCCESS:
123                        echo '<div class="sc-msg-success">' . $err['message'] . '</div>';
124                        break;
125
126                    case MSG_NOTICE:
127                    default:
128                        echo '<div class="sc-msg-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 $this->marker;
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        // Compare ip2long twice for php4 backwards compat.
420        if ((ip2long($domain) == '-1' || ip2long($domain) === false) && function_exists('checkdnsrr') && !checkdnsrr($domain . '.', 'MX') && gethostbyname($domain) == $domain) {
421            $this->addError($form_name, sprintf(_("<strong>%s</strong> is not a valid email domain name"), oTxt($domain)));
422            App::logMsg(sprintf('The email address %s contains an invalid email domain name (%s).', getFormData($form_name), $domain), LOG_INFO, __FILE__, __LINE__);
423            return false;
424        }
425
426        return true;
427    }
428
429    /**
430     * Check whether input is a valid phone number. Notice: it is now set
431     * to allow characters like - or () or + so people can type in a phone
432     * number that looks like: +1 (530) 624-4410
433     *
434     * @param  string  $form_name the name of the incoming form variable
435     *
436     * @return bool    true if no errors found, false otherwise
437     */
438    function validatePhone($form_name)
439    {
440        $phone = getFormData($form_name);
441
442        return $this->checkRegex($form_name, '/^[0-9 +().-]*$/', true, sprintf(_("The phone number <strong>%s</strong> is not valid."), $phone))
443        && $this->stringLength($form_name, 0, 25, sprintf(_("The phone number <strong>%s</strong> is too long"), $phone));
444    }
445
446    /**
447     * Verifies that date can be processed by the strtotime function.
448     *
449     * @param  string  $form_name the name of the incoming form variable
450     * @param  string  $msg       the message to display on error
451     *
452     * @return bool    true if no errors found, false otherwise
453     */
454    function validateStrDate($form_name, $msg='')
455    {
456        if (($timestamp = strtotime(getFormData($form_name, '0'))) === -1) {
457            $this->addError($form_name, $msg);
458            App::logMsg(sprintf('The string date %s is not valid.', getFormData($form_name)), LOG_DEBUG, __FILE__, __LINE__);
459            return false;
460        } else {
461            return true;
462        }
463    }
464
465
466    /**
467     * Verifies credit card number.
468     *
469     * @param  string  $form_name   The name of the incoming form variable.
470     * @param  string  $cc_num      Card number to verify.
471     * @param  string  $cc_type     Optional, card type to do specific checks.
472     *
473     * @return bool    true if no errors found, false otherwise
474     */
475    function validateCCNumber($form_name, $cc_num=null, $cc_type=null)
476    {
477        if (!isset($cc_num)) {
478            $cc_num = getFormData($form_name);
479        }
480
481        if ('' == $cc_num) {
482            return false;
483        }
484
485        // Innocent until proven guilty
486        $card_is_valid = true;
487
488        // Get rid of any non-digits
489        $cc_num = preg_replace('/[^\d]/', '', $cc_num);
490
491        // Perform card-specific checks, if applicable
492        switch (strtolower($cc_type)) {
493            case 'visa' :
494                $card_is_valid = preg_match('/^4\d{15}$|^4\d{12}$/', $cc_num);
495                break;
496            case 'mastercard' :
497            case 'mc' :
498                $card_is_valid = preg_match('/^5[1-5]\d{14}$/', $cc_num);
499                break;
500            case 'american_express' :
501            case 'american_ex' :
502            case 'americanexpress' :
503            case 'americanex' :
504            case 'am_ex' :
505            case 'amex' :
506            case 'ae' :
507                $card_is_valid = preg_match('/^3[47]\d{13}$/', $cc_num);
508                break;
509            case 'discover' :
510                $card_is_valid = preg_match('/^6011\d{12}$/', $cc_num);
511                break;
512            case 'diners_club' :
513            case 'dinersclub' :
514            case 'diners' :
515            case 'diner' :
516            case 'dc' :
517                $card_is_valid = preg_match('/^30[0-5]\d{11}$|^3[68]\d{12}$/', $cc_num);
518                break;
519            case 'jcb' :
520                $card_is_valid = preg_match('/^3\d{15}$|^2131|1800\d{11}$/', $cc_num);
521                break;
522        }
523
524        // The Luhn formula works right to left, so reverse the number.
525        $cc_num = strrev($cc_num);
526
527        $luhn_total = 0;
528
529        $num = strlen($cc_num);
530        for ($i=0; $i<$num; $i++) {
531            // Get each digit.
532            $digit = substr($cc_num,$i,1);
533
534            //  If it's an odd digit, double it.
535            if ($i / 2 != floor($i / 2)) {
536                $digit *= 2;
537            }
538
539            //  If the result is two digits, add them.
540            if (strlen($digit) == 2) {
541                $digit = substr($digit,0,1) + substr($digit,1,1);
542            }
543
544            //  Add the current digit to the $luhn_total.
545            $luhn_total += $digit;
546        }
547
548        // If it passed (or bypassed) the card-specific check and the Total is evenly divisible by 10, it's cool!
549        if ($card_is_valid && $luhn_total % 10 == 0) {
550            return true;
551        } else {
552            $this->addError($form_name, _("The <strong>credit card number</strong> you entered is not valid."));
553            return false;
554        }
555    }
556
557    /**
558     * Check whether uploaded file is valid.
559     *
560     * @param  string $form_name the name of the incoming form variable
561     * @param  string $msg       the message to display on error
562     *
563     * @return bool   true if no errors found, false otherwise
564     */
565    function validateFile($form_name, $msg='')
566    {
567        if (isset($_FILES[$form_name]['tmp_name']) && '' == trim($_FILES[$form_name]['tmp_name'])) {
568            $this->addError($form_name, $msg);
569            return false;
570        } else {
571            return true;
572        }
573    }
574
575} // THE END
576
577?>
Note: See TracBrowser for help on using the repository browser.