Validate that a Value Exists

Ensure that a value entered in Form A has been previously submitted on Form B. This is useful if you’re generating a reference number of some sort on Form B and would like the user to enter it on Form A.

Code

Filename: gw-validate-that-a-value-exists.php

<?php
/**
 * Gravity Wiz // Gravity Forms // Validate that a Value Exists
 *
 * Ensure that a value entered in Form A has been previously submitted on Form B. This is useful if you're generating a reference number of some sort
 * on Form B and would like the user to enter it on Form A.
 *
 * @version   1.10
 * @author    David Smith <david@gravitywiz.com>
 * @license   GPL-2.0+
 * @link      https://gravitywiz.com/require-existing-value-submission-gravity-forms/
 */
class GW_Value_Exists_Validation {

	protected static $is_script_output = false;

	public function __construct( $args = array() ) {

		// set our default arguments, parse against the provided arguments, and store for use throughout the class
		$this->_args = wp_parse_args( $args, array(
			'target_form_id'          => false,
			'target_field_id'         => false,
			'source_form_id'          => false,
			'source_field_id'         => false,
			'validation_message'      => __( 'Please enter a valid value.' ),
			'field_map'               => array(),
			'disable_ajax_validation' => false,
			'validate_blank_values'   => true,
		) );

		// Map source and target fields to field map if field map is not set.
		if ( empty( $this->_args['field_map'] ) ) {
			$this->_args['field_map'] = array( $this->_args['source_field_id'] => $this->_args['target_field_id'] );
		}

		// do version check in the init to make sure if GF is going to be loaded, it is already loaded
		add_action( 'init', array( $this, 'init' ) );

	}

	public function init() {

		// make sure we're running the required minimum version of Gravity Forms
		if ( ! property_exists( 'GFCommon', 'version' ) || ! version_compare( GFCommon::$version, '1.8', '>=' ) ) {
			return;
		}

		add_filter( 'gform_validation', array( $this, 'validation' ) );

		if ( ! $this->_args['disable_ajax_validation'] ) {

			add_action( 'gform_enqueue_scripts', array( $this, 'enqueue_form_script' ) );
			add_filter( 'gform_pre_render', array( $this, 'load_form_script' ), 10, 2 );
			add_action( 'gform_register_init_scripts', array( $this, 'add_init_script' ) );

			add_action( 'wp_ajax_gwvev_does_value_exist', array( $this, 'ajax_does_value_exist' ) );
			add_action( 'wp_ajax_nopriv_gwvev_does_value_exist', array( $this, 'ajax_does_value_exist' ) );

		}

	}

	public function enqueue_form_script( $form ) {
		if ( $this->is_applicable_form( $form ) ) {
			wp_enqueue_script( 'gform_gravityforms' );
		}
		return $form;
	}

	public function validation( $result ) {

		if ( ! $this->is_applicable_form( $result['form'] ) ) {
			return $result;
		}

		foreach ( $result['form']['fields'] as &$field ) {

			if ( $this->is_applicable_field( $field ) && ! GFFormsModel::is_field_hidden( $result['form'], $field, array() ) ) {

				if ( ! $this->do_values_exists( $this->get_values(), $this->_args['source_form_id'] ) ) {
					$field['failed_validation']  = true;
					$field['validation_message'] = $this->_args['validation_message'];
					$result['is_valid']          = false;
				}
			}
		}

		return $result;
	}

	public function ajax_does_value_exist() {

		if ( ! wp_verify_nonce( rgpost( 'nonce' ), 'gwvev_does_value_exist' ) ) {
			die( __( 'Invalid nonce.' ) );
		}

		$form_id = rgpost( 'form_id' );
		$values  = rgpost( 'values' );
		$entries = $this->get_matching_entry( $values, $form_id );

		echo json_encode( array(
			'doesValueExist' => ! empty( $entries ),
			'entries'        => $entries,
		) );

		die();
	}

	public function does_value_exist( $value, $form_id, $field_id ) {

		$entries = $this->get_matching_entry( array( $field_id => $value ), $form_id );

		return count( $entries ) > 0;
	}

	public function do_values_exists( $values, $form_id ) {

		$entries = $this->get_matching_entry( $values, $form_id );

		return $entries && count( $entries ) > 0;
	}

	public function get_matching_entry( $values, $form_id ) {

		$field_filters = array();

		foreach ( $values as $field_id => $value ) {
			$field_filters[] = array(
				'key'   => $field_id,
				'value' => $value,
			);
		}

		$args = apply_filters( 'gwvev_get_entries_args', array(
			$form_id,
			array(
				'status'        => 'active',
				'field_filters' => $field_filters,
			),
		) );

		$entries = GFAPI::get_entries( $args[0], $args[1] );

		return reset( $entries );
	}

	public function get_field_map() {

		if ( ! empty( $this->_args['field_map'] ) ) {
			$field_map = $this->_args['field_map'];
		} else {
			$field_map = array( $this->_args['target_field_id'] => $this->_args['source_field_id'] );
		}

		return $field_map;
	}

	public function get_values() {

		$field_map = $this->get_field_map();
		$values    = array();

		foreach ( $field_map as $source_field_id => $target_field_id ) {
			$value = rgpost( 'input_' . $target_field_id );

			if ( ! rgblank( $value ) || $this->_args['validate_blank_values'] ) {
				$values[ $source_field_id ] = $value;
			}
		}

		return $values;
	}

	public function load_form_script( $form, $is_ajax_enabled ) {

		// Do not output main script if AJAX is enabled
		if ( ! $is_ajax_enabled && $this->is_applicable_form( $form ) && ! self::$is_script_output && ! $this->is_ajax_submission( $form['id'], $is_ajax_enabled ) ) {
			$this->output_script();
		}

		return $form;
	}

	public function output_script() {
		?>

		<script type="text/javascript">

			( function( $ ) {

				window.GWValueExistsValidation = function( args ) {

					var self = this;

					// copy all args to current object: (list expected props)
					for( prop in args ) {
						if( args.hasOwnProperty( prop ) ) {
							self[ prop ] = args[ prop ];
						}
					}

					self.init = function() {

						self.$elems().on( 'change', function() {
							var inputId = gf_get_input_id_by_html_id( $( this ).attr( 'id' ) );
							for( var sourceFieldId in self.fieldMap ) {
								if( self.fieldMap.hasOwnProperty( sourceFieldId ) && sourceFieldId == inputId ) {
									break;
								}
							}
							self.doesValueExist( $( this ) )
						} );

					};

					self.getAllValues = function() {
						var values = {};
						self.$elems().each( function() {
							var inputId = gf_get_input_id_by_html_id( $( this ).attr( 'id' ) );
							for( var sourceFieldId in self.fieldMap ) {
								if( self.fieldMap.hasOwnProperty( sourceFieldId ) && sourceFieldId == inputId ) {
									break;
								}
							}
							values[ inputId ] = $( this ).val();
						} );
						return values;
					}

					self.doAllFieldsHaveValue = function() {
						var values = Object.values( self.getAllValues() );
						return values.length === values.filter( Boolean ).length;
					}

					self.doesValueExist = function( $elem ) {

						if ( ! self.doAllFieldsHaveValue() ) {
							self.removeIndicators();
							return;
						}

						self.removeIndicators();

						var spinner  = new self.spinner( $elem, false, 'position:relative;top:2px;left:-25px;' ),
							$buttons = $( '.gform_button' );

						$buttons.prop( 'disabled', true );
						self.$elems().prop( 'disabled', true );

						$.post( self.ajaxUrl, {
							nonce:    self.nonce,
							action:   'gwvev_does_value_exist',
							values:   self.getAllValues(),
							form_id:  self.sourceFormId
						}, function( response ) {

							self.$elems().prop( 'disabled', false );
							$buttons.prop( 'disabled', false );
							spinner.destroy();

							if( ! response ) {
								return;
							}

							response = $.parseJSON( response );

							if( response.doesValueExist ) {
								self.addIndicators( 'gwvev-response-success', '&#10004;' );
							} else {
								self.addIndicators( 'gwvev-response-error', '&#10008;' )
							}

							gform.doAction( 'gwvev_post_ajax_validation', self, response );

						} );

					};

					self.$elems = function() {
						return $( self.selectors.join( ', ' ) );
					}

					self.getIndicatorId = function( inputId ) {
						return 'response_{0}_{1}'.gformFormat( self.targetFormId, inputId );
					}

					self.getIndicatorTemplate = function() {
						return '<span id="{0}" class="gwvev-response {1}">{2}</span>';
					}

					self.removeIndicators = function() {
						self.$elems().each( function() {
							var inputId = gf_get_input_id_by_html_id( $( this ).attr( 'id' ) );
							self.removeIndicator( inputId );
						} );
					}

					self.removeIndicator = function( inputId ) {
						$( '#' + self.getIndicatorId( inputId ) ).remove();
					}

					self.addIndicators = function( cssClass, icon ) {
						self.$elems().each( function() {
							var inputId = gf_get_input_id_by_html_id( $( this ).attr( 'id' ) );
							self.addIndicator( $( this ), inputId, cssClass, icon );
						} );
					}

					self.addIndicator = function( $elem, inputId, cssClass, icon ) {
						$elem.after( self.getIndicatorTemplate().gformFormat( self.getIndicatorId( inputId ), cssClass, icon ) );
					}

					self.spinner = function( elem, imageSrc, inlineStyles ) {

						imageSrc     = typeof imageSrc == 'undefined' || ! imageSrc ? window.gf_global.spinnerUrl : imageSrc;
						inlineStyles = typeof inlineStyles != 'undefined' ? inlineStyles : '';

						this.elem = elem;
						this.image = '<img class="gfspinner" src="' + imageSrc + '" style="' + inlineStyles + '" />';

						this.init = function() {
							this.spinner = jQuery(this.image);
							jQuery(this.elem).after(this.spinner);
							return this;
						};

						this.destroy = function() {
							jQuery(this.spinner).remove();
						};

						return this.init();
					};

					self.init();

				}

			} )( jQuery );

		</script>

		<?php

		self::$is_script_output = true;

	}

	public function add_init_script( $form ) {

		if ( ! $this->is_applicable_form( $form ) ) {
			return;
		}

		$args = array(
			'ajaxUrl'      => admin_url( 'admin-ajax.php' ),
			'nonce'        => wp_create_nonce( 'gwvev_does_value_exist' ),
			'targetFormId' => $this->_args['target_form_id'],
			'sourceFormId' => $this->_args['source_form_id'],
			'selectors'    => $this->get_selectors( $form ),
			'fieldMap'     => $this->get_field_map(),
			'gfBaseUrl'    => GFCommon::get_base_url(),
		);

		$script = 'new GWValueExistsValidation( ' . json_encode( $args ) . ' );';
		$slug   = implode( '_', array( 'gw_value_exists_validation', $this->_args['target_form_id'], $this->_args['target_field_id'] ) );

		GFFormDisplay::add_init_script( $this->_args['target_form_id'], $slug, GFFormDisplay::ON_PAGE_RENDER, $script );

	}

	public function get_selectors( $form ) {

		$selectors = array();

		foreach ( $form['fields'] as $field ) {

			if ( ! $this->is_applicable_field( $field ) ) {
				continue;
			}

			$prefix = sprintf( '#input_%d_%d', $form['id'], $field->id );

			if ( is_array( $field->inputs ) ) {
				foreach ( $field->inputs as $input ) {
					$bits        = explode( '.', $input['id'] );
					$input_id    = $bits[1];
					$selectors[] = "{$prefix}_{$input_id}";
				}
			} else {
				$selectors[] = $prefix;
			}
		}

		return $selectors;
	}

	public function is_applicable_form( $form ) {

		$form_id = isset( $form['id'] ) ? $form['id'] : $form;

		return $form_id == $this->_args['target_form_id'];
	}

	public function is_applicable_field( $field ) {
		$field_map = $this->get_field_map();
		return $this->is_applicable_form( $field->formId ) && in_array( $field->id, $field_map );
	}

	public function is_ajax_submission( $form_id, $is_ajax_enabled ) {
		// Ensure GFFormDisplay is available before continuing to check (edge case with GPPA loading a pseudo form. See HS#25828)
		return class_exists( 'GFFormDisplay' ) && isset( GFFormDisplay::$submission[ $form_id ] ) && $is_ajax_enabled;
	}

}

# Configuration

new GW_Value_Exists_Validation( array(
	'target_form_id'     => 123,
	'target_field_id'    => 1,
	'source_form_id'     => 124,
	'source_field_id'    => 1,
	'validation_message' => 'Hey! This isn't a valid reference number.',
) );

Leave a Reply

Your email address will not be published. Required fields are marked *

  • Trouble installing this snippet? See our troubleshooting tips.
  • Need to include code? Create a gist and link to it in your comment.
  • Reporting a bug? Provide a URL where this issue can be recreated.

By commenting, I understand that I may receive emails related to Gravity Wiz and can unsubscribe at any time.