source: branches/1.1dev/lib/FormValidator.inc.php @ 382

Last change on this file since 382 was 308, checked in by quinn, 16 years ago

General bug fixes. Backported email checking regex from codebase 2.1.2. Some css mods.

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