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

Last change on this file since 584 was 584, checked in by anonymous, 7 years ago
File size: 19.5 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 = getFormData($form_name);
138        if (is_array($val)) {
139            if (!empty($val)) {
140                $this->addError($form_name, $msg);
141                return true;
142            } else {
143                return false;
144            }
145        } else {
146            if (trim($val) != '') {
147                $this->addError($form_name, $msg);
148                return true;
149            } else {
150                return false;
151            }
152        }
153    }
154
155    /**
156     * Check whether input is blank.
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 empty, false otherwise.
162     */
163    function isEmpty($form_name, $msg='')
164    {
165        $val = getFormData($form_name);
166        if (is_array($val)) {
167            if (empty($val)) {
168                $this->addError($form_name, $msg);
169                return true;
170            } else {
171                return false;
172            }
173        } else {
174            if (trim($val) == '') {
175                $this->addError($form_name, $msg);
176                return true;
177            } else {
178                return false;
179            }
180        }
181    }
182
183    /**
184     * Check whether input is a string.
185     *
186     * @param  string $form_name the name of the incoming form variable
187     * @param  string $msg       the message to display on error
188     *
189     * @return bool   true if form is a string, false otherwise.
190     */
191    function isString($form_name, $msg='')
192    {
193        $val = getFormData($form_name);
194        if (!is_string($val) && $val != '') {
195            $this->addError($form_name, $msg);
196            return false;
197        } else {
198            return true;
199        }
200    }
201
202    /**
203     * Check whether input is a number. Allows negative numbers.
204     *
205     * @param  string $form_name the name of the incoming form variable
206     * @param  string $msg       the message to display on error
207     *
208     * @return bool   true if no errors found, false otherwise
209     */
210    function isNumber($form_name, $msg='')
211    {
212        $val = getFormData($form_name);
213        if (!is_numeric($val) && $val != '') {
214            $this->addError($form_name, $msg);
215            return false;
216        } else {
217            return true;
218        }
219    }
220
221    /**
222     * addError if input is NOT an integer. Don't just use is_int() because the
223     * data coming from the user is *really* a string.
224     *
225     * @param  string $form_name the name of the incoming form variable
226     * @param  string $msg       the message to display on error
227     *
228     * @return bool   true if value is an integer
229     */
230    function isInteger($form_name, $msg='', $negative_ok=false)
231    {
232        $val = getFormData($form_name);
233        $pattern = $negative_ok ? '/^-?[[:digit:]]+$/' : '/^[[:digit:]]+$/';
234        if ((!is_numeric($val) || !preg_match($pattern, $val)) && $val != '') {
235            $this->addError($form_name, $msg);
236            return false;
237        } else {
238            return true;
239        }
240    }
241
242    /**
243     * Check whether input is a float. Don't just use is_float() because the
244     * data coming from the user is *really* a string. Integers will also
245     * pass this test.
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 a float
251     */
252    function isFloat($form_name, $msg='', $negative_ok=false)
253    {
254        $val = getFormData($form_name);
255        $pattern = $negative_ok ? '/^-?[[:digit:]]*(?:\.?[[:digit:]]+)$/' : '/^[[: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 an array.
266     *
267     * @param  string $form_name the name of the incoming form variable
268     * @param  string $msg       the message to display on error
269     *
270     * @return bool   true if value is a float
271     */
272    function isArray($form_name, $msg='')
273    {
274        $val = getFormData($form_name);
275        if (!is_array($val) && !empty($val)) {
276            $this->addError($form_name, $msg);
277            return false;
278        } else {
279            return true;
280        }
281    }
282
283    /**
284     * Check whether input matches the specified perl regular expression
285     * pattern.
286     *
287     * @param  string $form_name the name of the incoming form variable
288     * @param  int    $regex     perl regex that the string must match
289     * @param  bool   $not       set to false to be valid if match, or true
290     *                           to be valid on no match
291     * @param  string $msg       the message to display on error
292     *
293     * @return bool   true if value passes regex test
294     */
295    function checkRegex($form_name, $regex, $not, $msg='')
296    {
297        $val = getFormData($form_name);
298        if ($not) {
299            if (!preg_match($regex, $val))  {
300                $this->addError($form_name, $msg);
301                return false;
302            } else {
303                return true;
304            }
305        } else {
306            if (preg_match($regex, $val))  {
307                $this->addError($form_name, $msg);
308                return false;
309            } else {
310                return true;
311            }
312        }
313    }
314
315    /**
316     * Tests if the string length is between specified values. Whitespace excluded for min.
317     *
318     * @param  string $form_name the name of the incoming form variable
319     * @param  int    $min       minimum length of string, inclusive
320     * @param  int    $max       maximum length of string, inclusive
321     * @param  string $msg       the message to display on error
322     *
323     * @return bool   true if string length is within given boundaries
324     */
325    function stringLength($form_name, $min, $max, $msg='')
326    {
327        $val = getFormData($form_name);
328
329        if (strlen(trim($val)) < $min || strlen($val) > $max) {
330            $this->addError($form_name, $msg);
331            return false;
332        } else {
333            return true;
334        }
335    }
336
337    /**
338     * Check whether input is within a valid numeric range.
339     *
340     * @param  string $form_name the name of the incoming form variable
341     * @param  int    $min       minimum value of number, inclusive
342     * @param  int    $max       maximum value of number, inclusive
343     * @param  string $msg       the message to display on error
344     *
345     * @return bool   true if no errors found, false otherwise
346     */
347    function numericRange($form_name, $min, $max, $msg='')
348    {
349        $val = getFormData($form_name);
350        if ($val != '' && is_numeric($val)) {
351            if ($val < $min || $val > $max) {
352                $this->addError($form_name, $msg);
353                return false;
354            }
355            return true;
356        } else {
357            // Not a number!
358            return false;
359        }
360    }
361
362    /**
363     * Validates email address length, domain name existence, format.
364     *
365     * @param  string  $form_name       The name of the incoming form variable
366     * @param   bool   $strict Run strict tests (check if the domain exists and has an MX record assigned)
367     * @return bool    true if no errors found, false otherwise
368     */
369    function validateEmail($form_name, $strict=false)
370    {
371        $email = getFormData($form_name);
372        if ('' == trim($email)) {
373            return false;
374        }
375
376        $regex = '/^(?:[^,@]*\s+|[^,@]*(<)|)'                           // Display name
377        . '((?:[^.<>\s@\",\[\]]+[^<>\s@\",\[\]])*[^.<>\s@\",\[\]]+)'    // Local-part
378        . '@'                                                           // @
379        . '((?:(\[)|[A-Z0-9]?)'                                         // Domain, first char
380        . '(?(4)'                                                       // Domain conditional for if first domain char is [
381        . '(?:[0-9]{1,3}\.){3}[0-9]{1,3}\]'                             // TRUE, matches IP address
382        . '|'
383        . '[.-]?(?:[A-Z0-9]+[-.])*(?:[A-Z0-9]+\.)+[A-Z]{2,6}))'         // FALSE, matches domain name
384        . '(?(1)'                                                       // Comment conditional for if initial < exists
385        . '(?:>\s*|>\s+\([^,@]+\)\s*)'                                  // TRUE, ensure ending >
386        . '|'
387        . '(?:|\s*|\s+\([^,@]+\)\s*))$/i';
388
389        // Test email address format.
390        if (!preg_match($regex, getFormData($form_name), $e_parts)) {
391            $this->addError($form_name, sprintf(_("The email address <strong>%s</strong> is formatted incorrectly."), oTxt(getFormData($form_name))), MSG_ERR, __FILE__, __LINE__);
392            logMsg(sprintf('The email address %s is not valid.', getFormData($form_name)), LOG_DEBUG, __FILE__, __LINE__);
393            return false;
394        }
395
396        // We have a match! Here are the captured subpatterns, on which further tests are run.
397        // The part before the @.
398        $local = $e_parts[2];
399
400        // The part after the @.
401        // If domain is an IP [XXX.XXX.XXX.XXX] strip off the brackets.
402        $domain = $e_parts[3]{0} == '[' ? mb_substr($e_parts[3], 1, -1) : $e_parts[3];
403
404        // Test length.
405        if (mb_strlen($local) > 64 || mb_strlen($domain) > 191) {
406            $this->addError($form_name, sprintf(_("The email address <strong>%s</strong> is too long."), oTxt(getFormData($form_name))), MSG_ERR, __FILE__, __LINE__);
407            logMsg(sprintf('The email address %s is too long.', getFormData($form_name)), LOG_DEBUG, __FILE__, __LINE__);
408            return false;
409        }
410
411        if ($strict) {
412            // Strict tests.
413            if (ip2long($domain) === false && function_exists('checkdnsrr') && !checkdnsrr($domain . '.', 'MX') && gethostbyname($domain) == $domain) {
414                // Check domain exists: It's a domain if ip2long fails; checkdnsrr ensures a MX record exists; gethostbyname() ensures the domain exists.
415                $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__);
416                logMsg(sprintf('%s (line %s) failed: %s', __METHOD__, __LINE__, getDump($email)));
417                return false;
418            }
419        }
420
421        return true;
422    }
423
424    /**
425     * Check whether input is a valid phone number. Notice: it is now set
426     * to allow characters like - or () or + so people can type in a phone
427     * number that looks like: +1 (530) 624-4410
428     *
429     * @param  string  $form_name the name of the incoming form variable
430     *
431     * @return bool    true if no errors found, false otherwise
432     */
433    function validatePhone($form_name)
434    {
435        $phone = getFormData($form_name);
436
437        if ($this->checkRegex($form_name, '/^[0-9 +().-]*$/', true, sprintf(_("The phone number <strong>%s</strong> is not valid."), $phone))
438        && $this->stringLength($form_name, 0, 25, sprintf(_("The phone number <strong>%s</strong> is too long"), $phone))) {
439            return true;
440        }
441        return false;
442    }
443
444    /**
445     * Verifies that date can be processed by the strtotime function.
446     *
447     * @param  string  $form_name the name of the incoming form variable
448     * @param  string  $msg       the message to display on error
449     *
450     * @return bool    true if no errors found, false otherwise
451     */
452    function validateStrDate($form_name, $msg='')
453    {
454        if (($timestamp = strtotime(getFormData($form_name))) === -1) {
455            $this->addError($form_name, $msg);
456            logMsg(sprintf('The string date %s is not valid.', getFormData($form_name)), LOG_DEBUG, __FILE__, __LINE__);
457            return false;
458        } else {
459            return true;
460        }
461    }
462
463
464    /**
465     * Verifies credit card number.
466     *
467     * @param  string  $form_name   The name of the incoming form variable.
468     * @param  string  $cc_num      Card number to verify.
469     * @param  string  $cc_type     Optional, card type to do specific checks.
470     *
471     * @return bool    true if no errors found, false otherwise
472     */
473    function validateCCNumber($form_name, $cc_num=null, $cc_type=null)
474    {
475        if (!isset($cc_num)) {
476            $cc_num = getFormData($form_name);
477        }
478
479        if ('' == $cc_num) {
480            return false;
481        }
482
483        // Innocent until proven guilty
484        $card_is_valid = true;
485
486        // Get rid of any non-digits
487        $cc_num = preg_replace('/[^\d]/', '', $cc_num);
488
489        // Perform card-specific checks, if applicable
490        switch (strtolower($cc_type)) {
491            case 'visa' :
492                $card_is_valid = preg_match('/^4\d{15}$|^4\d{12}$/', $cc_num);
493                break;
494            case 'mastercard' :
495            case 'mc' :
496                $card_is_valid = preg_match('/^5[1-5]\d{14}$/', $cc_num);
497                break;
498            case 'american_express' :
499            case 'american_ex' :
500            case 'americanexpress' :
501            case 'americanex' :
502            case 'am_ex' :
503            case 'amex' :
504            case 'ae' :
505                $card_is_valid = preg_match('/^3[47]\d{13}$/', $cc_num);
506                break;
507            case 'discover' :
508                $card_is_valid = preg_match('/^6011\d{12}$/', $cc_num);
509                break;
510            case 'diners_club' :
511            case 'dinersclub' :
512            case 'diners' :
513            case 'diner' :
514            case 'dc' :
515                $card_is_valid = preg_match('/^30[0-5]\d{11}$|^3[68]\d{12}$/', $cc_num);
516                break;
517            case 'jcb' :
518                $card_is_valid = preg_match('/^3\d{15}$|^2131|1800\d{11}$/', $cc_num);
519                break;
520        }
521
522        // The Luhn formula works right to left, so reverse the number.
523        $cc_num = strrev($cc_num);
524
525        $luhn_total = 0;
526
527        $num = strlen($cc_num);
528        for ($i=0; $i<$num; $i++) {
529            // Get each digit.
530            $digit = substr($cc_num,$i,1);
531
532            //  If it's an odd digit, double it.
533            if ($i / 2 != floor($i / 2)) {
534                $digit *= 2;
535            }
536
537            //  If the result is two digits, add them.
538            if (strlen($digit) == 2) {
539                $digit = substr($digit,0,1) + substr($digit,1,1);
540            }
541
542            //  Add the current digit to the $luhn_total.
543            $luhn_total += $digit;
544        }
545
546        // If it passed (or bypassed) the card-specific check and the Total is evenly divisible by 10, it's cool!
547        if ($card_is_valid && $luhn_total % 10 == 0) {
548            return true;
549        } else {
550            $this->addError($form_name, _("The <strong>credit card number</strong> you entered is not valid."));
551            return false;
552        }
553    }
554
555    /**
556     * Check whether uploaded file is valid.
557     *
558     * @param  string $form_name the name of the incoming form variable
559     * @param  string $msg       the message to display on error
560     *
561     * @return bool   true if no errors found, false otherwise
562     */
563    function validateFile($form_name, $msg='')
564    {
565        if (!isset($_FILES[$form_name]['tmp_name']) || '' == trim($_FILES[$form_name]['tmp_name'])) {
566            $this->addError($form_name, $msg);
567            return false;
568        } else {
569            return true;
570        }
571    }
572
573} // THE END
574
575?>
Note: See TracBrowser for help on using the repository browser.