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

Last change on this file since 403 was 403, checked in by anonymous, 12 years ago
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.