The Premise
Let’s imagine that we’re building some sort of car related website. On this fictional website we have a Gravity Form that allows you to register cars you own and part of that registration is a field for the VIN number of the vehicle. So how do we ensure that the user is entering a valid VIN number? Yup. The gform_validation hook.
Getting Started
The first step is to tie our custom validation function to the gform_validation filter.
// 1 - Tie our validation function to the 'gform_validation' hook
add_filter( 'gform_validation_9', 'validate_vin' );
This line is just us telling Gravity Forms that whenever it validates the form submission, we’d like it to also run our custom validate_vin function as well. In addition, since this is a filter and not an action (read more about the difference here) we’re also telling Gravity Forms that we’d like to modify the validation result and continuing processing the rest of the validation with those modifications preserved.
- Note: You’ll notice we’ve appended a ‘9’ to the end of the ‘gform_validation’ filter name. This means that we only want to trigger our custom validation function from form ID 9. No sense in running an extra function on every form submission if we only need it on one specific form. Just replace the 9 with the ID of the form you’d like to target.*
The Validation Result
When Gravity Forms calls our custom function it will pass an array containing two important items for form validation.
- $validation_result[‘is_valid’] This property will be either true or false and indicates whether or not the form has failed any of Gravity Forms’ default validation checks. You’ll see this again on step 13.
- $validation_result[‘form’] This property contains the Form Object which itself contains an array of all the fields created for this form.
// 2 - Get the form object from the validation result
$form = $validation_result['form'];
With that said, all we’re doing here is setting the form object from the validation result to a local variable that will be easier to read and use.
Getting the Current Page
// 3 - Get the current page being validated
$current_page = rgpost( 'gform_source_page_number_' . $form['id'] ) ? rgpost( 'gform_source_page_number_' . $form['id'] ) : 1;
If you aren’t using multi-page forms, this step doesn’t apply to you but it wouldn’t hurt to know how it works.
When you submit a page on a multi-page form, Gravity Forms will populate some additional info in the $_POST global (read more here if you’re unfamiliar with the $_POST). One of those helpful bits of information is the source page number (aka the current page you are validating). We’ll use the rgpost function to retrieve the source page number from the $_POST.
So why is this important? Because if the field we want to validate is on page 2 of our form and the user just submitted page 1, that field will be blank and will fail the validation for the entire form preventing the user form progressing to the next page.
Looping Through the Fields
// 4 - Loop through the form fields
foreach( $form['fields'] as &$field ) {
As mentioned earlier, the Form Object contains an array of fields created for that form. Using a loop we can go through each field one by one and run a series of checks and conditions to determine if it is a field that should be validated for a VIN number.
- Note: You’ll see continue in a couple places in the code within the loop. This means that we want to skip the rest of the loop for whatever field we are on and move on to the next field.*
Find Field by CSS Class
// 5 - If the field does not have our designated CSS class, skip it
if ( strpos( $field->cssClass, 'validate-vin' ) === false ) {
continue;
}
In this example, we’re looking for a specific string based on the field’s CSS class property. We could change this to check by field ID or any other field property, but I’ve found that the CSS class is a user friendly way of quickly adding custom validation functionality to most fields. Basing this check on field ID would have the drawback of requiring a code change if you ever needed to remove the field and apply the custom validation to a different field. With the CSS class technique, you can simply add the designated CSS class to the new field from the Gravity Form editor. The designated class we’re using here is validate-vin.
If validate-vin CSS class is not present on the current field, we will skip it and move on to the next field. If the field does have the validate-vin class, we know that this is a field we’ve marked for VIN validation and we’ll proceed to the next step.
Getting the Field Page Number
// 6 - Get the field's page number
$field_page = $field->pageNumber;
This step is also multi-page form specific.
When using the multi-page functionality, each field has a pageNumber property which stores the page number on which that field is displayed. We’ll be using this variable on step 8.
Is the Field Hidden?
// 7 - Check if the field is hidden by GF conditional logic
$is_hidden = RGFormsModel::is_field_hidden( $form, $field, array() );
It’s important to know whether or not the field we are validating is hidden by Gravity Form’s conditional logic. Hidden fields should not be validated because they are not part of the form submission.
Gravity Forms provides a handy public function that you can use to determine if a field is hidden: RGFormsModel::is_field_hidden(). To use this function all you need is a Form Object and a field to check.
Skip Field If…
// 8 - If the field is not on the current page OR if the field is hidden, skip it
if ( $field_page != $current_page || $is_hidden ) {
continue;
}
The previous two steps we gathered some information about the current field. Now we’re putting that information to use. If the field is not on the current page OR if the field is hidden, we don’t want to run our VIN validation function on it. Instead, we’ll continue on to the next field in the loop.
Retrieve & Validate the Submitted Value
// 9 - Get the submitted value from the $_POST
$field_value = rgpost( "input_{$field['id']}" );
// 10 - Make a call to your validation function to validate the value
$is_valid = is_vin( $field_value );
So… if the field gets to this point we know that it:
- has the designated validation CSS class
- is on the current page being validated
- and is not hidden
We’re now ready to retrieve and validate the submitted value for this field. Let’s retrieve the value of of the field using the rgpost function (which is a clean way of retrieving values from the $_POST). The string we’re passing to this function would look something like “input_48” if we weren’t dynamically populating the field ID using the current field’s ID property.
- Note: This method for retrieving the field value will work for most fields. Known exceptions are checkboxes and fields with multiple inputs. We will cover those later as an addition to this walk-through.*
Next, we create an $is_valid variable and assign it the result of our VIN validation function. We pass is_vin() our field value and it returns a true/false value indicating whether the submitted value is a valid VIN number.
Check If the Field Value is Valid
// 11 - If the field is valid we don't need to do anything, skip it
if ( $is_valid ) {
continue;
}
If the submitted value is valid, then we don’t need to update any of the validation properties for this field. It’s good to go!
If you are only validating one field for the VIN number (which is likely the case) you could technically stop here and return the unmodified validation result; however, for the sake of explanation and making this code sample as useful as possible, let’s assume that you’re validating multiple fields. That means we’d skip the rest of the code for this field and start back with the next field at the very top of the loop.
Uh, oh! It Failed Validation!
Ok, so we made it all the back to the is_vin() validation function on the next VIN field… but this one failed validation. What do we do now?
// 12 - The field failed validation, so first we'll need to fail the validation for the entire form
$validation_result['is_valid'] = false;
// 13 - Next we'll mark the specific field that failed and add a custom validation message
$field->failed_validation = true;
$field->validation_message = 'The VIN number you have entered is not valid.';
Well, first since we know that there is a validation error on the form, let’s make sure we mark the $validation_result is_valid property as false. When we return our modified validation result, this property is how Gravity Forms knows to look for validation errors on the individual fields.
Next, we’re going to mark the $field failed_validation property as true, because, well… it did! In addition, we’ll want to provide a custom validation message so the user filling out the form will know what the problem is.
Assign the Modified Form Back to the Validation Result
// 14 - Assign our modified $form object back to the validation result
$validation_result['form'] = $form;
This step is super important! We’ve updated a field of this form with validation failure details but if we don’t assign the Form Object back to the form property of the $validation_result, Gravity Forms won’t know which field had the error.
Return the Validation Result
// 15 - Return the validation result
return $validation_result;
And finally, we return the modified (or perhaps unmodified if no validation errors were found) $validation_result back to Gravity Forms. Assuming that a field did fail validation, Gravity Forms will now prevent the form from submitting successfully and mark each field that has a validation error with its corresponding validation message.
Grab the Code
add_filter( 'gform_validation_9', 'validate_vin' );
function validate_vin( $validation_result ) {
// 2 - Get the form object from the validation result
$form = $validation_result['form'];
// 3 - Get the current page being validated
$current_page = rgpost( 'gform_source_page_number_' . $form['id'] ) ? rgpost( 'gform_source_page_number_' . $form['id'] ) : 1;
// 4 - Loop through the form fields
foreach( $form['fields'] as &$field ) {
// 5 - If the field does not have our designated CSS class, skip it
if ( strpos( $field->cssClass, 'validate-vin' ) === false ) {
continue;
}
// 6 - Get the field's page number
$field_page = $field->pageNumber;
// 7 - Check if the field is hidden by GF conditional logic
$is_hidden = RGFormsModel::is_field_hidden( $form, $field, array() );
// 8 - If the field is not on the current page OR if the field is hidden, skip it
if ( $field_page != $current_page || $is_hidden ) {
continue;
}
// 9 - Get the submitted value from the $_POST
$field_value = rgpost( "input_{$field['id']}" );
// 10 - Make a call to your validation function to validate the value
$is_valid = is_vin( $field_value );
// 11 - If the field is valid we don't need to do anything, skip it
if ( $is_valid ) {
continue;
}
// 12 - The field failed validation, so first we'll need to fail the validation for the entire form
$validation_result['is_valid'] = false;
// 13 - Next we'll mark the specific field that failed and add a custom validation message
$field->failed_validation = true;
$field->validation_message = 'The VIN number you have entered is not valid.';
}
// 14 - Assign our modified $form object back to the validation result
$validation_result['form'] = $form;
// 15 - Return the validation result
return $validation_result;
}
// Dummy is_vin function for testing
function is_vin( $vin ) {
// For testing purposes, we'll return true if the VIN is exactly 17 characters long
// This is just an example and not a real VIN validation
return strlen( $vin ) === 17;
}