source: branches/2.0singleton/lib/FormValidator.inc.php @ 647

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

Q - Finished integrating singleton methods into existing code. Renamed SessionCache? to Cache, and renamed methods in Cache and Prefs

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