Skip to content

Instantly share code, notes, and snippets.

@spivurno
Last active November 14, 2022 21:16
Show Gist options
  • Save spivurno/8ca209fbb5068e62655ddf91678830a3 to your computer and use it in GitHub Desktop.
Save spivurno/8ca209fbb5068e62655ddf91678830a3 to your computer and use it in GitHub Desktop.
Gravity Perks // GP Limit Choices // Field Groups
<?php
/**
* Gravity Perks // GP Limit Choices // Field Group
*
* Specify a group of fields that should create a unique choice to limited. For example, a Date field and Radio Button field could be
* combined to sell x tickets per date where the ticket type is controlled by the Radio Button field and the date is
* selected in the Date field.
*
* @version 1.4
* @author David Smith <david@gravitywiz.com>
* @license GPL-2.0+
* @link http://gravitywiz.com/documentation/gp-limit-choices/
*
* Plugin Name: GP Limit Choices - Field Groups
* Plugin URI: http://gravitywiz.com/documentation/gp-limit-choices/
* Description: Specify a group of fields that should create a unique choice to be limited.
* Author: Gravity Wiz
* Version: 1.4
* Author URI: http://gravitywiz.com
*/
class GP_Limit_Choices_Field_Group {
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(
'form_id' => false,
'field_ids' => array(),
) );
$this->_args['hash'] = hash_hmac( 'sha256', serialize( $this->_args ), 'gplc_field_group' );
add_filter( 'gwlc_choice_counts_query', array( $this, 'limit_by_field_group' ), 10, 2 );
add_action( 'wp_ajax_gplcfg_refresh_field', array( $this, 'ajax_refresh' ) );
add_action( 'wp_ajax_nopriv_gplcfg_refresh_field', array( $this, 'ajax_refresh' ) );
add_filter( 'gform_pre_render', array( $this, 'load_form_script' ), 10, 2 );
add_filter( 'gform_register_init_scripts', array( $this, 'add_init_script' ), 10, 2 );
if( isset( $_POST['action'] ) && $_POST['action'] == 'gplcfg_refresh_field' ) {
remove_action( 'wp', array( 'GFForms', 'maybe_process_form' ), 9 );
remove_action( 'admin_init', array( 'GFForms', 'maybe_process_form' ), 9 );
}
}
public function limit_by_field_group( $query, $field ) {
global $wpdb;
$field_ids = $this->_args['field_ids'];
if( ! $this->is_applicable_form( $field->formId ) || ! in_array( $field->id, $field_ids ) ) {
return $query;
}
unset( $field_ids[ array_search( $field->id, $field_ids ) ] );
$field_ids = array_values( $field_ids );
$form = GFAPI::get_form( $field->formId );
$join = $where = array();
$select = $from = '';
foreach( $field_ids as $index => $field_id ) {
$field = GFFormsModel::get_field( $form, $field_id );
$alias = sprintf( 'fgem%d', $index + 1 );
$_alias = null;
if( $index == 0 ) {
$_alias = $alias;
$select = "SELECT DISTINCT {$alias}.entry_id";
$from = "FROM {$wpdb->prefix}gf_entry_meta {$alias}";
$value = $field->get_value_save_entry( GFFormsModel::get_field_value( $field ), $form, null, null, null );
$where[] = $wpdb->prepare( "( {$alias}.form_id = %d AND {$alias}.meta_key = %s AND {$alias}.meta_value = %s )", $field->formId, $field_id, $value );
} else {
$join[] = "INNER JOIN {$wpdb->prefix}gf_entry_meta {$alias} ON {$_alias}.entry_id = {$alias}.entry_id";
}
}
$field_group_query = array(
'select' => $select,
'from' => $from,
'join' => implode( ' ', $join ),
'where' => sprintf( 'WHERE %s', implode( "\nAND ", $where ) )
);
$query['where'] .= sprintf( ' AND e.id IN( %s )', implode( "\n", $field_group_query ) );
return $query;
}
public function is_applicable_form( $form ) {
$form_id = isset( $form['id'] ) ? $form['id'] : $form;
return empty( $this->_args['form_id'] ) || $form_id == $this->_args['form_id'];
}
public function load_form_script( $form, $is_ajax_enabled ) {
$func = array( 'GP_Limit_Choices_Field_Group', 'output_script' );
if( $this->is_applicable_form( $form ) && ! has_action( 'wp_footer', $func ) ) {
add_action( 'wp_footer', $func );
add_action( 'gform_preview_footer', $func );
}
return $form;
}
public function add_init_script( $form ) {
if( ! $this->is_applicable_form( $form ) ) {
return;
}
$target_field_id = $this->get_target_field_id( $form, $this->_args['field_ids'] );
$args = array(
'formId' => $this->_args['form_id'],
'targetFieldId' => $target_field_id,
'triggerFieldIds' => $this->get_trigger_field_ids( $form, $this->_args['field_ids'] ),
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'hash' => $this->_args['hash'],
);
$script = 'new GPLCFieldGroup( ' . json_encode( $args ) . ' );';
$slug = implode( '_', array( 'gplc_field_group', $this->_args['form_id'], $target_field_id ) );
GFFormDisplay::add_init_script( $this->_args['form_id'], $slug, GFFormDisplay::ON_PAGE_RENDER, $script );
}
public function get_target_field_id( $form, $field_ids ) {
foreach( $field_ids as $field_id ) {
$field = GFAPI::get_field( $form, $field_id );
if ( gp_limit_choices()->is_applicable_field( $field ) ) {
return $field_id;
}
}
return false;
}
public function get_trigger_field_ids( $form, $field_ids ) {
$target_field_id = $this->get_target_field_id( $form, $field_ids );
return array_values( array_filter( $field_ids, function( $field_id ) use ( $target_field_id ) {
return $field_id != $target_field_id;
} ) );
}
public function ajax_refresh() {
// Object can be instantiated multiple times. Only listen for this specific configuration's hash.
if ( rgpost( 'hash' ) !== $this->_args['hash'] ) {
/**
* Return out if the hash doesn't match. If we exit here, other ajax_refresh() calls in other instances
* won't have the chance to run.
*/
return;
}
$entry = GFFormsModel::get_current_lead();
if( ! $entry ) {
wp_send_json_error();
}
$form = gf_apply_filters( array( 'gform_pre_render', $entry['form_id'] ), GFAPI::get_form( $entry['form_id'] ), false, array() );
$field = GFFormsModel::get_field( $form, $this->get_target_field_id( $form, $this->_args['field_ids'] ) );
if( $field->get_input_type() == 'html' ) {
$content = GWPreviewConfirmation::preview_replace_variables( $field->content, $form );
} else {
$value = rgpost( 'input_' . $field->id );
$content = $field->get_field_content( $value, true, $form );
$content = str_replace( '{FIELD}', $field->get_field_input( $form, $value, $entry ), $content );
}
wp_send_json_success( $content );
}
public static function output_script() {
?>
<script type="text/javascript">
( function( $ ) {
window.GPLCFieldGroup = 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.$form = $( '#gform_wrapper_{0}'.format( self.formId ) );
self.$targetField = $( '#field_{0}_{1}'.format( self.formId, self.targetFieldId ) );
self.$triggerFields = false;
for( var i = 0; i < self.triggerFieldIds.length; i++ ) {
var $field = $( '#field_{0}_{1}'.format( self.formId, self.triggerFieldIds[ i ] ) );
if ( ! self.$triggerFields ) {
self.$triggerFields = $().add( $field );
}
// @todo Test with multiple triggers.
else {
self.$triggerFields.add( $field );
}
}
self.$triggerFields.on( 'change', function() {
self.refresh();
} );
self.refresh();
};
self.refresh = function() {
if( ! self.$targetField.is( ':visible' ) ) {
return;
}
var data = {
action: 'gplcfg_refresh_field',
hash: self.hash
};
self.$form.find( 'input, select, textarea' ).each( function() {
if ( this.type === 'radio' ) {
if ( this.checked ) {
data[ $( this ).attr( 'name' ) ] = $( this ).val();
}
} else {
data[ $( this ).attr( 'name' ) ] = $( this ).val();
}
} );
// Prevent AJAX-enabled forms from intercepting our AJAX request.
delete data['gform_ajax'];
$.post( self.ajaxUrl, data, function( response ) {
if( response.success ) {
self.$targetField.html( response.data );
}
} );
};
self.init();
}
} )( jQuery );
</script>
<?php
}
}
# Configuration
new GP_Limit_Choices_Field_Group( array(
'form_id' => 1485,
'field_ids' => array( 1, 2 )
) );
<?php
new GP_Limit_Choices_Field_Group( array(
'form_id' => 12,
'field_ids' => array( 3, 4 )
) );
@joshjenkinsAR
Copy link

joshjenkinsAR commented Jun 24, 2020

This is great! I’m hoping to use this field group snippet to manage spots, however, I need to have multiple sets of field groups on the same form. Here’s my desired configuration: I have a location field and then a series of fields that list date and time slots as dropdowns.

new GP_Limit_Choices_Field_Group( array( ‘form_id’ => 10, ‘field_ids’ => array( 7, 30, 31, 32, 33, 34, 35 ) ) );

BUT I need an array of arrays where field 7 (the location) is paired with the conditionally displayed date/time dropdown field (30 – 35). So really, it’s (7, 30) (7, 31) and so on – as only one date/time dropdown will appear at a time based on logic.

Is this possible?

EDIT: Just testing, I tried this config and it's seemed to have worked? Any issue you see with this implementation? Thanks so much for your help!
new GP_Limit_Choices_Field_Group( array(
'form_id' => 10,
'field_ids' => array( 7, 30 ),
'field_ids' => array( 7, 31 ),
'field_ids' => array( 7, 32 ),
'field_ids' => array( 7, 33 ),
'field_ids' => array( 7, 34 ),
'field_ids' => array( 7, 35 )
) );

@spivurno
Copy link
Author

I wouldn't expect that to work but happy to hear it does!

I think you'd probably be safe to just group them all together assuming that only one field between 30 and 35 would be visible at a time.

new GP_Limit_Choices_Field_Group( array(
'form_id' => 10,
'field_ids' => array( 7, 30, 31, 32, 33, 34, 35 ),
) );

@jaimyvangerrevink
Copy link

jaimyvangerrevink commented Aug 6, 2020

Hi @spivurno, First of all thanks for sharing this snippet.

I'm using it for binding gp-lc with a date. Now I have a question. How can I bind an amount field to the limit count. So the amount decreases from the limit instead of the "submit"

I've understood this already works with products choices and their quantity field. Unfortunately I do not want to use this for a product.

@spivurno
Copy link
Author

spivurno commented Aug 6, 2020

@jaimyvangerrevink There is no ready way to do this without using Product fields. The Product + Quantity fields provide an implicit structure that doesn't exist with other field types. Definitely something we'll consider as we develop this functionality into a perk.

@mike-weiner
Copy link

mike-weiner commented Aug 20, 2020

@spivurno Getting a fatal error with this snippet.

Update: Uninstalled the custom site plugin storing this snippet, reinstalled, and things are working again.

Fatal error: Uncaught Error: Call to undefined function rgpost() in /.../public_html/wp-content/plugins/gp-limit-choices-field-groups/gp-limit-choices-field-groups.php:41

This snippet is critical for a client right now.

Technical Information About the Site:
WordPress v5.5
Gravity Forms v2.4.20
PHP v7.2.33

@spivurno
Copy link
Author

@mike-weiner Fixed!

@mike-weiner
Copy link

@spivurno

Thank you for the quick response and fix! It is greatly appreciated.

@claygriffiths
Copy link

🧙 Heads-up! This snippet has been migrated to the Gravity Wiz Snippet Library.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment