Posts: 2,456
Threads: 39
Joined: Dec 2008
Reputation:
6
Ah, okay. Yeah, this is a bug with the validation script. I'll look at it right now & get back to you.
- Ben
Posts: 2,456
Threads: 39
Joined: Dec 2008
Reputation:
6
Jan 13th, 2012, 11:04 AM
(This post was last modified: Jan 13th, 2012, 11:06 AM by Ben.)
Hey Brandon,
Thanks for spotting this, btw! Neat little bug.
Try this code. Replace the if-else section we've been discussing...
PHP Code: if ($comparison == "equal" && $fields[$field_to_check] != $value_to_check) { $satisfies_if_conditions = false; break; } else if ($comparison == "not_equal" && $fields[$field_to_check] == $value_to_check) { $satisfies_if_conditions = false; break; }
With this:
PHP Code: if ($comparison == "equal") { $fail = false;
// if the field being compared against doesn't exist, we reasonably say that the value won't be the same if (!array_key_exists($field_to_check, $fields)) $fail = true;
// if the value being passed is an array (e.g. a checkbox / multi-select field), what would be considered // "equal"? I think as long as the array contains AT LEAST that value, it's fair to pass this test else if (is_array($fields[$field_to_check]) && in_array($field_to_check, $fields)) $fail = true;
// lastly, do a straight string test else if ($fields[$field_to_check] != $value_to_check) $fail = true;
if ($fail) { $satisfies_if_conditions = false; break; } } else if ($comparison == "not_equal") { $fail = true;
if (!array_key_exists($field_to_check, $fields)) $fail = false;
else if (is_array($fields[$field_to_check]) && !in_array($field_to_check, $fields)) $fail = false;
else if ($fields[$field_to_check] != $value_to_check) $fail = false;
if ($fail) { $satisfies_if_conditions = false; break; }
$satisfies_if_conditions = false; break; }
It checks for all scenarios and makes some educated decisions about what should be considered "equal" and "not equal" in cases where the value is actually an array.
I confess I haven't tested it, but it looks okay to me!
- Ben
Posts: 143
Threads: 19
Joined: Dec 2010
Reputation:
5
When submitting the form without checking the checkbox in question, I no longer get the funky error message output, which is nice.
When selecting the checkbox in question, the form submits without telling me that the next field is required. It skips the validation.
Not a FT employee, just a FT user lending a hand. If I've been helpful, please rate.
Posts: 2,456
Threads: 39
Joined: Dec 2008
Reputation:
6
Ah, sorry. I inverted the logic. Try this:
PHP Code: if ($comparison == "equal") { $fail = false;
// if the field being compared against doesn't exist, we reasonably say that the value won't be the same if (!array_key_exists($field_to_check, $fields)) $fail = true;
// if the value being passed is an array (e.g. a checkbox / multi-select field), what would be considered // "equal"? I think as long as the array contains AT LEAST that value, it's fair to pass this test else if (is_array($fields[$field_to_check])) { if (!in_array($field_to_check, $fields)) $fail = true; }
// lastly, do a straight string test else if ($fields[$field_to_check] != $value_to_check) $fail = true;
if ($fail) { $satisfies_if_conditions = false; break; } } else if ($comparison == "not_equal") { $fail = true;
// if the field doesn't even exist, we can say they're not equal (i.e. doesn't fail!) if (!array_key_exists($field_to_check, $fields)) $fail = false;
else if (is_array($fields[$field_to_check])) { if (!in_array($field_to_check, $fields)) $fail = false; }
else if ($fields[$field_to_check] != $value_to_check) $fail = false;
if ($fail) { $satisfies_if_conditions = false; break; }
$satisfies_if_conditions = false; break; }
Posts: 143
Threads: 19
Joined: Dec 2010
Reputation:
5
Jan 13th, 2012, 11:25 AM
(This post was last modified: Jan 13th, 2012, 11:35 AM by michatmaster7.)
Exactly same result.
What I find maddening (as do you, I'm sure), is that this worked with the previous version of FormTools core. However, when comparing the validation.php file from the new version to the old version... I don't see any differences. So that's utterly confusing to me.
Not a FT employee, just a FT user lending a hand. If I've been helpful, please rate.
Posts: 2,456
Threads: 39
Joined: Dec 2008
Reputation:
6
Hmm... first off: rats. I thought that code would work. I'll have to test it out myself & post back later today.
But secondly, I really can't see how it could have worked with a previous version of FT... that doesn't really make sense to me because it's isolated to this chunk of validation code, which hasn't changed in a long time. More likely, the server-side validation simply wasn't working & not throwing an error. If you had client-side validation, that would have caught it in 99.9% of cases.
But I'll get back to you once I've tested out the code!
- Ben
Posts: 2,456
Threads: 39
Joined: Dec 2008
Reputation:
6
Jan 13th, 2012, 12:25 PM
(This post was last modified: Jan 13th, 2012, 12:32 PM by Ben.)
Hi michatmaster7,
Okay, I've attached an actual tested version of the code. :-) I'll include this in the next Core version.
Let me know if you have any problems.
- Ben
EDIT: Weird, the file attachment option seems to have disappeared for me since I upgraded the forums a couple of days ago. Jeez. I'll just post the entire page code below.
PHP Code: <?php
/** * validation.php * -------------- * * v2.3.4, Jan 2012 * * This script provides generic validation for any web form. For a discussion and example usage * of this script, go to http://www.benjaminkeen.com/software/php_validation * * This script is written by Ben Keen with additional code contributed by Mihai Ionescu and * Nathan Howard. It is free to distribute, to re-write - to do what ever you want with it. The RSV * script, found in /global/scripts is the client-side counterpart to this script. Both work exactly * the same way, so once you write validation for one, you can literally & paste them to use them in * the other. See the user doc for more info. */
// ------------------------------------------------------------------------------------------------
/** * Generic form field validation. * * @param array fields the POST / GET fields from a form which need to be validated * @param array rules an array of the validation rules. Each rule is a string of the form: * * "[if:FIELDNAME=VALUE,]REQUIREMENT,fieldname[,fieldname2 [,fieldname3, date_flag]],error message" * * if:FIELDNAME=VALUE, This allows us to only validate a field * only if a fieldname FIELDNAME has a value VALUE. This * option allows for nesting; i.e. you can have multiple * if clauses, separated by a comma. They will be examined * in the order in which they appear in the line. * * Valid REQUIREMENT strings are: * "required" - field must be filled in * "digits_only" - field must contain digits only * "is_alpha" - field must only contain alphanumeric characters (0-9, a-Z) * "custom_alpha" - field must be of the custom format specified. * fieldname: the name of the field * fieldname2: a character or sequence of special characters. These characters are: * L An uppercase Letter. V An uppercase Vowel. * l A lowercase letter. v A lowercase vowel. * D A letter (upper or lower). F A vowel (upper or lower). * C An uppercase Consonant. x Any number, 0-9. * c A lowercase consonant. X Any number, 1-9. * E A consonant (upper or lower). * "reg_exp" - field must match the supplied regular expression. * fieldname: the name of the field * fieldname2: the regular expression * fieldname3: (optional) flags for the reg exp (like i for case insensitive * "letters_only" - field must only contains letters (a-Z) * * "length=X" - field has to be X characters long * "length=X-Y" - field has to be between X and Y (inclusive) characters long * "length>X" - field has to be greater than X characters long * "length>=X" - field has to be greater than or equal to X characters long * "length<X" - field has to be less than X characters long * "length<=X" - field has to be less than or equal to X characters long* * * "valid_email" - field has to be valid email address * "valid_date" - field has to be a valid date * fieldname: MONTH * fieldname2: DAY * fieldname3: YEAR * date_flag: "later_date" / "any_date" * "same_as" - fieldname is the same as fieldname2 (for password comparison) * * "range=X-Y" - field must be a number between the range of X and Y inclusive * "range>X" - field must be a number greater than X * "range>=X" - field must be a number greater than or equal to X * "range<X" - field must be a number less than X * "range<=X" - field must be a number less than or equal to X * * * Comments: With both digits_only, valid_email and is_alpha options, if the empty string is passed * in it won't generate an error, thus allowing validation of non-required fields. So, * for example, if you want a field to be a valid email address, provide validation for * both "required" and "valid_email". */ function validate_fields($fields, $rules) { $errors = array();
// loop through rules for ($i=0; $i<count($rules); $i++) { // split row into component parts $row = explode(",", $rules[$i]);
// while the row begins with "if:..." test the condition. If true, strip the if:..., part and // continue evaluating the rest of the line. Keep repeating this while the line begins with an // if-condition. If it fails any of the conditions, don't bother validating the rest of the line $satisfies_if_conditions = true; while (preg_match("/^if:/", $row[0])) { $condition = preg_replace("/^if:/", "", $row[0]);
// check if it's a = or != test $comparison = "equal"; $parts = array(); if (preg_match("/!=/", $condition)) { $parts = explode("!=", $condition); $comparison = "not_equal"; } else $parts = explode("=", $condition);
$field_to_check = $parts[0]; $value_to_check = $parts[1];
if ($comparison == "equal") { $fail = false;
// if the field being compared against doesn't exist, we reasonably say that the value won't be the same if (!array_key_exists($field_to_check, $fields)) $fail = true;
// if the value being passed is an array (e.g. a checkbox / multi-select field), what would be considered // "equal"? I think as long as the array contains AT LEAST that value, it's fair to pass this test else if (is_array($fields[$field_to_check])) { if (!in_array($fields[$field_to_check], $fields)) $fail = true; }
// lastly, do a straight string test else if ($fields[$field_to_check] != $value_to_check) $fail = true;
if ($fail) { $satisfies_if_conditions = false; break; } else { array_shift($row); } } else if ($comparison == "not_equal") { $fail = true;
// if the field doesn't even exist, we can say they're not equal (i.e. doesn't fail!) if (!array_key_exists($field_to_check, $fields)) $fail = false;
else if (is_array($fields[$field_to_check])) { if (!in_array($fields[$field_to_check], $fields)) $fail = false; }
else if ($fields[$field_to_check] != $value_to_check) $fail = false;
if ($fail) { $satisfies_if_conditions = false; break; } else { array_shift($row); } } else array_shift($row); // remove this if-condition from line, and continue validating line }
if (!$satisfies_if_conditions) continue;
$requirement = $row[0]; $field_name = $row[1];
// depending on the validation test, store the incoming strings for use later... if (count($row) == 6) // valid_date { $field_name2 = $row[2]; $field_name3 = $row[3]; $date_flag = $row[4]; $error_message = $row[5]; } else if (count($row) == 5) // reg_exp (WITH flags like g, i, m) { $field_name2 = $row[2]; $field_name3 = $row[3]; $error_message = $row[4]; } else if (count($row) == 4) // same_as, custom_alpha, reg_exp (without flags like g, i, m) { $field_name2 = $row[2]; $error_message = $row[3]; } else $error_message = $row[2]; // everything else!
// if the requirement is "length=...", rename requirement to "length" for switch statement if (preg_match("/^length/", $requirement)) { $length_requirements = $requirement; $requirement = "length"; }
// if the requirement is "range=...", rename requirement to "range" for switch statement if (preg_match("/^range/", $requirement)) { $range_requirements = $requirement; $requirement = "range"; }
// now, validate whatever is required of the field switch ($requirement) { case "required": if (!isset($fields[$field_name]) || $fields[$field_name] == "") $errors[] = $error_message; break;
case "digits_only": if (isset($fields[$field_name]) && preg_match("/\D/", $fields[$field_name])) $errors[] = $error_message; break;
case "letters_only": if (isset($fields[$field_name]) && preg_match("/[^a-zA-Z]/", $fields[$field_name])) $errors[] = $error_message; break;
// doesn't fail if field is empty case "valid_email": $regexp="/^[a-z0-9]+([_\\.-][a-z0-9]+)*@([a-z0-9]+([\.-][a-z0-9]+)*)+\\.[a-z]{2,}$/i"; if (isset($fields[$field_name]) && !empty($fields[$field_name]) && !preg_match($regexp, $fields[$field_name])) $errors[] = $error_message; break;
case "length": $comparison_rule = ""; $rule_string = "";
if (preg_match("/length=/", $length_requirements)) { $comparison_rule = "equal"; $rule_string = preg_replace("/length=/", "", $length_requirements); } else if (preg_match("/length>=/", $length_requirements)) { $comparison_rule = "greater_than_or_equal"; $rule_string = preg_replace("/length>=/", "", $length_requirements); } else if (preg_match("/length<=/", $length_requirements)) { $comparison_rule = "less_than_or_equal"; $rule_string = preg_replace("/length<=/", "", $length_requirements); } else if (preg_match("/length>/", $length_requirements)) { $comparison_rule = "greater_than"; $rule_string = preg_replace("/length>/", "", $length_requirements); } else if (preg_match("/length</", $length_requirements)) { $comparison_rule = "less_than"; $rule_string = preg_replace("/length</", "", $length_requirements); }
switch ($comparison_rule) { case "greater_than_or_equal": if (!(strlen($fields[$field_name]) >= $rule_string)) $errors[] = $error_message; break; case "less_than_or_equal": if (!(strlen($fields[$field_name]) <= $rule_string)) $errors[] = $error_message; break; case "greater_than": if (!(strlen($fields[$field_name]) > $rule_string)) $errors[] = $error_message; break; case "less_than": if (!(strlen($fields[$field_name]) < $rule_string)) $errors[] = $error_message; break; case "equal": // if the user supplied two length fields, make sure the field is within that range if (preg_match("/-/", $rule_string)) { list($start, $end) = explode("-", $rule_string); if (strlen($fields[$field_name]) < $start || strlen($fields[$field_name]) > $end) $errors[] = $error_message; } // otherwise, check it's EXACTLY the size the user specified else { if (strlen($fields[$field_name]) != $rule_string) $errors[] = $error_message; } break; } break;
case "range": $comparison_rule = ""; $rule_string = "";
if (preg_match("/range=/", $range_requirements)) { $comparison_rule = "equal"; $rule_string = preg_replace("/range=/", "", $range_requirements); } else if (preg_match("/range>=/", $range_requirements)) { $comparison_rule = "greater_than_or_equal"; $rule_string = preg_replace("/range>=/", "", $range_requirements); } else if (preg_match("/range<=/", $range_requirements)) { $comparison_rule = "less_than_or_equal"; $rule_string = preg_replace("/range<=/", "", $range_requirements); } else if (preg_match("/range>/", $range_requirements)) { $comparison_rule = "greater_than"; $rule_string = preg_replace("/range>/", "", $range_requirements); } else if (preg_match("/range</", $range_requirements)) { $comparison_rule = "less_than"; $rule_string = preg_replace("/range</", "", $range_requirements); }
switch ($comparison_rule) { case "greater_than": if (!($fields[$field_name] > $rule_string)) $errors[] = $error_message; break; case "less_than": if (!($fields[$field_name] < $rule_string)) $errors[] = $error_message; break; case "greater_than_or_equal": if (!($fields[$field_name] >= $rule_string)) $errors[] = $error_message; break; case "less_than_or_equal": if (!($fields[$field_name] <= $rule_string)) $errors[] = $error_message; break; case "equal": list($start, $end) = explode("-", $rule_string);
if (($fields[$field_name] < $start) || ($fields[$field_name] > $end)) $errors[] = $error_message; break; } break;
case "same_as": if ($fields[$field_name] != $fields[$field_name2]) $errors[] = $error_message; break;
case "valid_date": // this is written for future extensibility of isValidDate function to allow // checking for dates BEFORE today, AFTER today, IS today and ANY day. $is_later_date = false; if ($date_flag == "later_date") $is_later_date = true; else if ($date_flag == "any_date") $is_later_date = false;
if (!is_valid_date($fields[$field_name], $fields[$field_name2], $fields[$field_name3], $is_later_date)) $errors[] = $error_message; break;
case "is_alpha": if (preg_match('/[^A-Za-z0-9]/', $fields[$field_name])) $errors[] = $error_message; break;
case "custom_alpha": $chars = array(); $chars["L"] = "[A-Z]"; $chars["V"] = "[AEIOU]"; $chars["l"] = "[a-z]"; $chars["v"] = "[aeiou]"; $chars["D"] = "[a-zA-Z]"; $chars["F"] = "[aeiouAEIOU]"; $chars["C"] = "[BCDFGHJKLMNPQRSTVWXYZ]"; $chars["x"] = "[0-9]"; $chars["c"] = "[bcdfghjklmnpqrstvwxyz]"; $chars["X"] = "[1-9]"; $chars["E"] = "[bcdfghjklmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZ]";
$reg_exp_str = ""; for ($j=0; $j<strlen($field_name2); $j++) { if (array_key_exists($field_name2[$j], $chars)) $reg_exp_str .= $chars[$field_name2[$j]]; else $reg_exp_str .= $field_name2[$j]; }
if (!empty($fields[$field_name]) && !preg_match("/$reg_exp_str/", $fields[$field_name])) $errors[] = $error_message; break;
case "reg_exp": $reg_exp_str = $field_name2;
// rather crumby, but... if (count($row) == 5) $reg_exp = "/" . $reg_exp_str . "/" . $row[3]; else $reg_exp = "/" . $reg_exp_str . "/";
if (!empty($fields[$field_name]) && !preg_match($reg_exp, $fields[$field_name])) $errors[] = $error_message; break;
default: die("Unknown requirement flag in validate_fields(): $requirement"); break; } }
return $errors; }
/** * Checks a date is valid / is later than current date * * @param integer $month an integer between 1 and 12 * @param integer $day an integer between 1 and 31 (depending on month) * @param integer $year a 4-digit integer value * @param boolean $is_later_date if true, the function verifies the date being passed in is LATER * than the current date. */ function is_valid_date($month, $day, $year, $is_later_date) { // depending on the year, calculate the number of days in the month if ($year % 4 == 0) // LEAP YEAR $days_in_month = array(31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31); else $days_in_month = array(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
// first, check the incoming month and year are valid. if (!$month || !$day || !$year) return false; if (1 > $month || $month > 12) return false; if ($year < 0) return false; if (1 > $day || $day > $days_in_month[$month-1]) return false;
// if required, verify the incoming date is LATER than the current date. if ($is_later_date) { // get current date $today = date("U"); $date = mktime(0, 0, 0, $month, $day, $year); if ($date < $today) return false; }
return true; }
Posts: 143
Threads: 19
Joined: Dec 2010
Reputation:
5
Jan 13th, 2012, 1:17 PM
(This post was last modified: Jan 13th, 2012, 1:22 PM by michatmaster7.)
(Jan 13th, 2012, 11:40 AM)Ben Wrote: But secondly, I really can't see how it could have worked with a previous version of FT... that doesn't really make sense to me because it's isolated to this chunk of validation code, which hasn't changed in a long time. More likely, the server-side validation simply wasn't working & not throwing an error. If you had client-side validation, that would have caught it in 99.9% of cases.
I am only assuming it worked with the previous version, as I haven't changed my validation rules. We opted to use server-side validation via the API because of the NoScript addon in Firefox. LOTS of people were submitting the form without being prompted for any validation. In fact, I HATE JS for that reason and try to avoid it 99.9% of the time. I want my pages all to load cross-browser regardless of addons or platform.
(Jan 13th, 2012, 12:25 PM)Ben Wrote: Okay, I've attached an actual tested version of the code. :-) I'll include this in the next Core version.
This works flawlessly. THANK YOU! I'll be testing out my previous validation rules on Tuesday (gotta get my hair did on Monday). I'm going to try the if:FIELDNAME!=,required,FIELDNAME option for some checkboxes. I'm actually "excited" about this...lol. Thank you for ALL your hard work on this. I've been testing and submitting these forms all week, trying to find alternative solutions, so this is an extraordinary bit of code! Perhaps this post should be made into a sticky or something?
(Jan 13th, 2012, 12:25 PM)Ben Wrote: EDIT: Weird, the file attachment option seems to have disappeared for me since I upgraded the forums a couple of days ago. Jeez. I'll just post the entire page code below.
Somehow, I think not having it is still a wise choice for now
Mercury was in retrograde until December 14th and it's effects last until January 14th. Of course, today is Friday the 13th... So, I can totally relate!
- Brandon
Not a FT employee, just a FT user lending a hand. If I've been helpful, please rate.
Posts: 2,456
Threads: 39
Joined: Dec 2008
Reputation:
6
Haha
Excellent - glad it all works properly. Very neat bug. I've updated the FT validation code and it'll be included in 2.2.1 (which will be rolled out HOPEFULLY later this month with the updated site and the Form Builder module).
- Ben
Posts: 143
Threads: 19
Joined: Dec 2010
Reputation:
5
Mar 28th, 2012, 8:32 AM
(This post was last modified: Mar 28th, 2012, 8:44 AM by michatmaster7.)
So, I am just getting around to implementing this (after the client complained yesterday that the validation wasn't in place). Oops.
Here is the validation script I am trying to implement:
PHP Code: $rules[] = "if:good_sam_member!=,required,good_sam_number,Please enter your Good Sam MEMBERSHIP NUMBER."; $rules[] = "if:escapees_member!=,required,escapees_number,Please enter your Escapees MEMBERSHIP NUMBER.";
Here are the code snippets from the form:
Code: ...
<input name="good_sam_member" type="checkbox" value="yes" <?php if (@$fields["good_sam_member"] == "yes") echo "checked"; ?> />
...
<input name="good_sam_number" size="36" type="text" value="<?=htmlspecialchars(@$fields["good_sam_number"])?>" />
...
and:
Code: ...
<input name="escapees_member" type="checkbox" value="yes" <?php if (@$fields["escapees_member"] == "yes") echo "checked"; ?> />
...
<input name="escapees_number" size="36" type="text" value="<?=htmlspecialchars(@$fields["escapees_number"])?>" />
...
When I select the checkboxes, but do not enter a number in the text field, I get the appropriate error (from the validation script). However, when I DO NOT select the checkboxes, I get the same error (from the validation script).
I will again remove the validation script until I can get this to work properly.... Any help would be appreciated. Perhaps it's my own fault in coding?
Brandon
Disregard my last reply, I updated the code as below and all works without any hitches. Thank you again Ben for all your hard work on this!
PHP Code: $rules[] = "if:good_sam_member=yes,required,good_sam_number,Please enter your Good Sam MEMBERSHIP NUMBER."; $rules[] = "if:good_sam_number!=,required,good_sam_member,Please check the box for Good Sam DISCOUNT."; $rules[] = "if:escapees_member=yes,required,escapees_number,Please enter your Escapees MEMBERSHIP NUMBER."; $rules[] = "if:escapees_number!=,required,escapees_member,Please check the box for Escapees DISCOUNT.";
Code: ...
<input name="good_sam_member" type="checkbox" value="yes" <?php if (@$fields["good_sam_member"] == "yes") echo "checked"; ?> />
...
<input name="good_sam_number" size="36" type="text" value="<?=htmlspecialchars(@$fields["good_sam_number"])?>" />
...
Code: ...
<input name="escapees_member" type="checkbox" value="yes" <?php if (@$fields["escapees_member"] == "yes") echo "checked"; ?> />
...
<input name="escapees_number" size="36" type="text" value="<?=htmlspecialchars(@$fields["escapees_number"])?>" />
...
Because I had given a "value" to the checkbox upon being selected, it affects the validation script syntax.
Thanks again!
Not a FT employee, just a FT user lending a hand. If I've been helpful, please rate.
|