Skip to content

Instantly share code, notes, and snippets.

@spivurno
Last active November 14, 2022 21:16
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • 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 )
) );
@marcre5
Copy link

marcre5 commented Jun 13, 2018

Hi,

I have a question:
I combined a date field with a select field. On the select field i enabled limits and added available slots them (for example 10 on each selection).
When the form is submited it works fine. It detects when the slots are used and display a message and the selection is gone.

The Problem is, that when the form is loaded selection with 0 slots available are still there.
Even when all selections are used.

How can I do that, so the selection will not be displayed when the slots are used?

BR
Marc

@marcre5
Copy link

marcre5 commented Jun 13, 2018

I added this hook for displaying the available slots, but its not working as well. I tihnk its not made for field groups.

add_filter( 'gplc_remove_choices', '__return_false' );

add_filter( 'gplc_pre_render_choice', 'my_add_how_many_left_message', 10, 5 );
function my_add_how_many_left_message( $choice, $exceeded_limit, $field, $form ) {

if( ! current_user_can( 'administrator' ) ) {
	return $choice;
}
$choice_counts = GWLimitChoices::get_choice_counts( $form['id'], $field );
$count = rgar( $choice_counts, $choice['value'] ) ? rgar( $choice_counts, $choice['value'] ) : 0;
$limit = rgar($choice, 'limit');    
$how_many_left = $limit - $count;
    
$message = "($how_many_left spots left)";  

$choice['text'] = $choice['text'] . " $message";

return $choice;

}

@Wordna1
Copy link

Wordna1 commented Jan 13, 2020

Why does this snippet include
add_filter( 'gplc_disable_choices', '__return_false' );

@spivurno
Copy link
Author

@Wordna1 Doesn't appear to be necessary for this snippet to work. Must have been the original required configuration of the user for whom the original snippet was developed.

@Wordna1
Copy link

Wordna1 commented Jan 15, 2020

@Wordna1 Doesn't appear to be necessary for this snippet to work. Must have been the original required configuration of the user for whom the original snippet was developed.

Thank you for another VERY helpful snippet.

For what it's worth, I would suggest removing add_filter( 'gplc_disable_choices', '__return_false' ); if it isn't actually required to group fields.

On a separate note, since the "Spots Remaining" message doesn't dynamically update for grouped fields, this snippet shouldn't really be combined with add_filter( 'gplc_disable_choices', '__return_false' );, which is used in, for example, the Waiting List snippet for the Limit Choices perk.

@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