November 10, 2018: Updated to work with Gravity Forms 2.3+. Special thanks to Daniel Kelly.
January 30, 2017: Added support for passing an array of forms via the "form_ids" parameter.
January 19, 2017: Improved the "per_{timeperiod}" options account for the WordPress configured timezone. Thanks, John!
May 24, 2016: Added support for limiting per week.
March 4, 2016: Fixed issue where month and week time periods limited across all years.
July 29, 2015: Added support for shortcodes in the "limit_message" parameter.
June 4, 2015: Added better description for setting time periods less than one day.
March 4, 2015: Fixed issue where entries in trash were still counted towards submission limit.
February 10th, 2015: Fixed issue where if limit was reached for one form on a page, all forms were hidden.
September 18, 2014: Added support for new
limit_by
parameters: "role" and "field_value". Added support for new calendar-based time periods viatime_period
. Rewrote much of the article.January 15th, 2012 Updated to provide support for limiting by the embed URL of the form and also specifying multiple "limit_by" parameters. Big thanks to Lee for sponsoring this enhancement!
Stop! There's a better way.
This snippet is available as a plugin with Gravity Perks, a suite of over 40 premium Gravity Forms plugins!
<?php | |
/** | |
* Gravity Wiz // Gravity Forms // Limit Submissions Per Time Period (by IP, User, Role, Form URL, or Field Value) | |
* | |
* Limit the number of times a form can be submitted per a specific time period. You modify this limit to apply to | |
* the visitor's IP address, the user's ID, the user's role, a specific form URL, or the value of a specific field. | |
* These "limiters" can be combined to create more complex limitations. | |
* | |
* @version 3.0 | |
* @author David Smith <david@gravitywiz.com> | |
* @license GPL-2.0+ | |
* @link http://gravitywiz.com/better-limit-submission-per-time-period-by-user-or-ip/ | |
*/ | |
class GW_Submission_Limit { | |
var $_args; | |
var $_notification_event; | |
private static $forms_with_individual_settings = array(); | |
public function __construct( $args ) { | |
// make sure we're running the required minimum version of Gravity Forms | |
if( ! property_exists( 'GFCommon', 'version' ) || ! version_compare( GFCommon::$version, '1.8', '>=' ) ) | |
return; | |
$this->_args = wp_parse_args( $args, array( | |
'form_id' => false, | |
'form_ids' => array(), | |
'limit' => 1, | |
'limit_by' => 'ip', // 'ip', 'user_id', 'role', 'embed_url', 'field_value' | |
'time_period' => 60 * 60 * 24, // integer in seconds or 'day', 'month', 'year' to limit to current day, month, or year respectively | |
'limit_message' => __( 'Sorry, you have reached the submission limit for this form.' ), | |
'apply_limit_per_form' => true, | |
'enable_notifications' => false | |
) ); | |
if( ! is_array( $this->_args['limit_by'] ) ) { | |
$this->_args['limit_by'] = array( $this->_args['limit_by'] ); | |
} | |
if( empty( $this->_args['form_ids'] ) ) { | |
if( $this->_args['form_id'] === false ) { | |
$this->_args['form_ids'] = false; | |
} elseif( ! is_array( $this->_args['form_id'] ) ) { | |
$this->_args['form_ids'] = array( $this->_args['form_id'] ); | |
} else { | |
$this->_args['form_ids'] = $this->_args['form_id']; | |
} | |
} | |
if( $this->_args['form_ids'] ) { | |
foreach( $this->_args['form_ids'] as $form_id ) { | |
self::$forms_with_individual_settings[] = $form_id; | |
} | |
} | |
add_action( 'init', array( $this, 'init' ) ); | |
} | |
public function init() { | |
add_filter( 'gform_pre_render', array( $this, 'pre_render' ) ); | |
add_filter( 'gform_validation', array( $this, 'validate' ) ); | |
if( $this->_args['enable_notifications'] ) { | |
$this->enable_notifications(); | |
add_action( 'gform_after_submission', array( $this, 'maybe_send_limit_reached_notifications' ), 10, 2 ); | |
} | |
} | |
public function pre_render( $form ) { | |
if( ! $this->is_applicable_form( $form ) || ! $this->is_limit_reached( $form['id'] ) ) { | |
return $form; | |
} | |
$submission_info = rgar( GFFormDisplay::$submission, $form['id'] ); | |
// if no submission, hide form | |
// if submission and not valid, hide form | |
// unless 'field_value' limiter is applied | |
if( ( ! $submission_info || ! rgar( $submission_info, 'is_valid' ) ) && ! $this->is_limited_by_field_value() ) { | |
add_filter( 'gform_get_form_filter_' . $form['id'], array( $this, 'get_limit_message' ), 10, 2 ); | |
} | |
return $form; | |
} | |
public function get_limit_message() { | |
ob_start(); | |
?> | |
<div class="limit-message"> | |
<?php echo do_shortcode( $this->_args['limit_message'] ); ?> | |
</div> | |
<?php | |
return ob_get_clean(); | |
} | |
public function validate( $validation_result ) { | |
if( ! $this->is_applicable_form( $validation_result['form'] ) || ! $this->is_limit_reached( $validation_result['form']['id'] ) ) { | |
return $validation_result; | |
} | |
$validation_result['is_valid'] = false; | |
if( $this->is_limited_by_field_value() ) { | |
$field_ids = array_map( 'intval', $this->get_limit_field_ids() ); | |
foreach( $validation_result['form']['fields'] as &$field ) { | |
if( in_array( $field['id'], $field_ids ) ) { | |
$field['failed_validation'] = true; | |
$field['validation_message'] = do_shortcode( $this->_args['limit_message'] ); | |
} | |
} | |
} | |
return $validation_result; | |
} | |
public function is_limit_reached( $form_id ) { | |
return $this->get_entry_count( $form_id ) >= $this->get_limit(); | |
} | |
public function get_entry_count( $form_id ) { | |
global $wpdb; | |
$where = array(); | |
$join = array(); | |
$where[] = 'e.status = "active"'; | |
foreach( $this->_args['limit_by'] as $limiter ) { | |
switch( $limiter ) { | |
case 'role': // user ID is required when limiting by role | |
case 'user_id': | |
$where[] = $wpdb->prepare( 'e.created_by = %s', get_current_user_id() ); | |
break; | |
case 'embed_url': | |
$where[] = $wpdb->prepare( 'e.source_url = %s', GFFormsModel::get_current_page_url()); | |
break; | |
case 'field_value': | |
$values = $this->get_limit_field_values( $form_id, $this->get_limit_field_ids() ); | |
// if there is no value submitted for any of our fields, limit is never reached | |
if( empty( $values ) ) { | |
return false; | |
} | |
foreach( $values as $field_id => $value ) { | |
$table_slug = sprintf( 'em%s', str_replace( '.', '_', $field_id ) ); | |
$join[] = "INNER JOIN {$wpdb->prefix}gf_entry_meta {$table_slug} ON {$table_slug}.entry_id = e.id"; | |
$where[] = $wpdb->prepare( "\n( ( {$table_slug}.meta_key BETWEEN %s AND %s ) AND {$table_slug}.meta_value = %s )", doubleval( $field_id ) - 0.001, doubleval( $field_id ) + 0.001, $value ); | |
} | |
break; | |
default: | |
$where[] = $wpdb->prepare( 'ip = %s', GFFormsModel::get_ip() ); | |
} | |
} | |
if( $this->_args['apply_limit_per_form'] || ( ! $this->is_global( $form_id ) && count( $this->_args['form_ids'] ) <= 1 ) ) { | |
$where[] = $wpdb->prepare( 'e.form_id = %d', $form_id ); | |
} else { | |
$where[] = $wpdb->prepare( 'e.form_id IN( %s )', implode( ', ', $this->_args['form_ids'] ) ); | |
} | |
$time_period = $this->_args['time_period']; | |
$time_period_sql = false; | |
if( $time_period === false ) { | |
// no time period | |
} else if( intval( $time_period ) > 0 ) { | |
$time_period_sql = $wpdb->prepare( 'date_created BETWEEN DATE_SUB(utc_timestamp(), INTERVAL %d SECOND) AND utc_timestamp()', $this->_args['time_period'] ); | |
} else { | |
$gmt_offset = get_option( 'gmt_offset' ); | |
$date_func = $gmt_offset < 0 ? 'DATE_SUB' : 'DATE_ADD'; | |
$hour_offset = abs( $gmt_offset ); | |
$date_created_sql = sprintf( '%s( date_created, INTERVAL %d HOUR )', $date_func, $hour_offset ); | |
$utc_timestamp_sql = sprintf( '%s( utc_timestamp(), INTERVAL %d HOUR )', $date_func, $hour_offset ); | |
switch( $time_period ) { | |
case 'per_day': | |
case 'day': | |
$time_period_sql = "DATE( $date_created_sql ) = DATE( $utc_timestamp_sql )"; | |
break; | |
case 'per_week': | |
case 'week': | |
$time_period_sql = "WEEK( $date_created_sql ) = WEEK( $utc_timestamp_sql )"; | |
$time_period_sql .= "AND YEAR( $date_created_sql ) = YEAR( $utc_timestamp_sql )"; | |
break; | |
case 'per_month': | |
case 'month': | |
$time_period_sql = "MONTH( $date_created_sql ) = MONTH( $utc_timestamp_sql )"; | |
$time_period_sql .= "AND YEAR( $date_created_sql ) = YEAR( $utc_timestamp_sql )"; | |
break; | |
case 'per_year': | |
case 'year': | |
$time_period_sql = "YEAR( $date_created_sql ) = YEAR( $utc_timestamp_sql )"; | |
break; | |
} | |
} | |
if( $time_period_sql ) { | |
$where[] = $time_period_sql; | |
} | |
$where = implode( ' AND ', $where ); | |
$join = implode( "\n", $join ); | |
$sql = "SELECT count( e.id ) | |
FROM {$wpdb->prefix}gf_entry e | |
$join | |
WHERE $where"; | |
$entry_count = $wpdb->get_var( $sql ); | |
return $entry_count; | |
} | |
public function is_limited_by_field_value() { | |
return in_array( 'field_value', $this->_args['limit_by'] ); | |
} | |
public function get_limit_field_ids() { | |
$limit = $this->_args['limit']; | |
if( is_array( $limit ) ) { | |
$field_ids = array_keys( $this->_args['limit'] ); | |
$field_ids = array( array_shift( $field_ids ) ); | |
} else { | |
$field_ids = $this->_args['fields']; | |
} | |
return $field_ids; | |
} | |
public function get_limit_field_values( $form_id, $field_ids ) { | |
$form = GFAPI::get_form( $form_id ); | |
$values = array(); | |
foreach( $field_ids as $field_id ) { | |
$field = GFFormsModel::get_field( $form, $field_id ); | |
if( ! $field ) { | |
continue; | |
} | |
$input_name = 'input_' . str_replace( '.', '_', $field_id ); | |
$value = GFFormsModel::prepare_value( $form, $field, rgpost( $input_name ), $input_name, null ); | |
if( ! rgblank( $value ) ) { | |
$values[ "$field_id" ] = $value; | |
} | |
} | |
return $values; | |
} | |
public function get_limit() { | |
$limit = $this->_args['limit']; | |
if( $this->is_limited_by_field_value() ) { | |
$limit = is_array( $limit ) ? array_shift( $limit ) : intval( $limit ); | |
} else if( in_array( 'role', $this->_args['limit_by'] ) ) { | |
$limit = rgar( $limit, $this->get_user_role() ); | |
} | |
return intval( $limit ); | |
} | |
public function get_user_role() { | |
$user = wp_get_current_user(); | |
$role = reset( $user->roles ); | |
return $role; | |
} | |
public function enable_notifications() { | |
if( ! class_exists( 'GW_Notification_Event' ) ) { | |
_doing_it_wrong( 'GW_Inventory::$enable_notifications', __( 'Inventory notifications require the \'GW_Notification_Event\' class.' ), '1.0' ); | |
} else { | |
$event_slug = implode( array_filter( array( "gw_submission_limit_limit_reached", $this->_args['form_id'] ) ) ); | |
$event_name = GFForms::get_page() == 'notification_edit' ? __( 'Submission limit reached' ) : __( 'Event name is only populated on Notification Edit view; saves a DB call to get the form on every ' ); | |
$this->_notification_event = new GW_Notification_Event( array( | |
'form_id' => $this->_args['form_id'], | |
'event_name' => $event_name, | |
'event_slug' => $event_slug | |
//'trigger' => array( $this, 'notification_event_listener' ) | |
) ); | |
} | |
} | |
public function maybe_send_limit_reached_notifications( $entry, $form ) { | |
if( $this->is_applicable_form( $form ) && $this->is_limit_reached( $form['id'] ) ) { | |
$this->send_limit_reached_notifications( $form, $entry ); | |
} | |
} | |
public function send_limit_reached_notifications( $form, $entry ) { | |
$this->_notification_event->send_notifications( $this->_notification_event->get_event_slug(), $form, $entry, true ); | |
} | |
public function is_applicable_form( $form ) { | |
$form_id = isset( $form['id'] ) ? $form['id'] : $form; | |
$is_specific_form = ! $this->is_global( $form_id ) ? in_array( $form_id, $this->_args['form_ids'] ) : false; | |
return $this->is_global( $form_id ) || $is_specific_form; | |
} | |
public function is_global( $form) { | |
$form_id = isset( $form['id'] ) ? $form['id'] : $form; | |
return empty( $this->_args['form_ids'] ) && ! in_array( $form_id, self::$forms_with_individual_settings ); | |
} | |
} | |
class GWSubmissionLimit extends GW_Submission_Limit { } | |
# Configuration | |
# Basic Usage | |
new GW_Submission_Limit( array( | |
'form_id' => 86, | |
'limit' => 2, | |
'limit_message' => 'Aha! You have been limited.' | |
) ); |
How do I get started?
- Gravity Forms v1.8 is required to use this snippet.
- Already have a license? Download Latest Gravity Forms
- Need a license? Buy Gravity Forms
- Copy and paste the snippet into your theme’s functions.php file.
- Modify the “Configuration” portion of the snippet (at the very bottom of the code) to meet your needs. Full usage instructions are below.
Usage
Basic Usage
Apply a submission limit per 24 hour period (default time period) to a specific form.
new GW_Submission_Limit( array( | |
'form_id' => 86, | |
'limit' => 2, | |
) ); |
Apply to ALL Forms
Apply a submission limit per 24 hours period to ALL forms.
new GW_Submission_Limit( array( | |
'limit' => 5 | |
) ); |
Multiple Limiters
Limit the number of a submissions a logged in user can make to specific form from the same embed URL. This would allow you to embed the same form on multiple pages and allow users to submit that form up to the submission limit on each page.
new GW_Submission_Limit( array( | |
'form_id' => 2, | |
'limit' => 1, | |
'limit_message' => 'Aha! You have been limited.', | |
'limit_by' => array( 'embed_url', 'user_id' ) | |
) ); |
Limit by Role
Limit by the logged in user’s role. The limit
parameter must be specified as an associative array with the role name as the key and the limit as the value.
new GW_Submission_Limit( array( | |
'form_id' => 2, | |
'limit_by' => 'role', | |
// when "limit_by" is set to "role", "limit" must be provided as array with roles and their corresponding limits | |
'limit' => array( | |
'administrator' => 20, | |
'contributor' => 5 | |
) | |
) ); |
Limit by Calendar Time Period
Limit by a calendar time period (i.e. “day”, “month”, “year”). This means that if you set a limit of “5” and the user reaches the limit on December 31st, they would be able to create five new submissions on January 1st. If you set the limit to a month in seconds (i.e. “2678400”, 31 days in seconds) and the user reached their limit on December 31st, they would not be eligible to create another submission until one of their previous submissions expired from the month-long time frame.
new GW_Submission_Limit( array( | |
'form_id' => 3, | |
'time_period' => 'per_month' | |
) ); |
Limit by Field Value (with no time period)
Limit by the value of a specific field. This is similar to Gravity Forms “No Duplicates” functionality except you can specify how many “duplicates” are allowed and can use other limiters (i.e. allowing no duplicates per user and more). Also demonstrated is the false
value for the time_period
parameter which results in the limit applying forever.
new GW_Submission_Limit( array( | |
'form_id' => 1, | |
'limit_by' => 'field_value', | |
'limit' => array( | |
// "2" is your field ID, "6" is your limit for this field ID | |
2 => 6 | |
), | |
'time_period' => false // forever! | |
) ); |
Parameters
form_ids
The form ID(s) of the form(s) you would like to limit. If you want to apply the the same submission limit to all forms, set the
form_id
asfalse
or do not include this parameter at all. If you would like to limit multiple forms, pass the value as an array of form IDs.limit
The number of submissions allowed before the user will no longer be able to make additional submission. If limiting by
role
, the limit should be an array oftime_period
The period of time to which the
limit
applies. The default time period is one day. In any 24 hour period, if the user reaches thelimit
they will no longer be able to make new submissions.If you want to limit by less than a day, you can provide the time period in seconds. A time period of
60
would be one minute (60 seconds). A time period of60 * 60
(or3600
) would be one hour.Also supported are three different calendar periods:
per_day
,per_month
,per_year
. Calendar time periods are more rigid time periods that “reset” when the calendar time period expires (i.e. one month ends and another begins).If you do not want to limit by a time period at all, set the time period to
false
.limit_message
The message which should be displayed to the user once they have reached the specified submission limit.
limit_by
Specify which identifying data the user should be limited by. Supported values:
ip
: limit by the visitor’s IP addressuser_id
: limit by the logged in user’s WordPress user IDembed_url
: limit submissions of a form from a specific embed URLrole
: limit by logged in users role (i.e. “administrator”, “contributor”)field_value
: limit by the value entered into a specific field; works similarly to Gravity Forms’ default “No Duplicates” option, except you can specify how many times the value can be duplicated.
Did this resource help you do something awesome with Gravity Forms? Then you'll absolutely love Gravity Perks; a suite of 39+ essential add-ons for Gravity Forms with support you can count on.
Hi! i’ve tried installing the perks, per IP for only 1 submission per 24 hours. so do i set the submission limit to only 1 or? because if i were to set the submission limit to only 1, then other people who logged on to that form are unable to submit the form?
appriciate if you could assist?
thanks
Hi,
I have sent you a reply via email so we can request more information about the issue you’re experiencing. Please check and reply via email.
Best,
Hey guys, thanks for the great snippet.
I have two questions.
Would it be possible to apply this to all forms except predefined ones? As far as I understand it currently we can limit to “all forms” or “specific ones”, but not “all except for…”.
Additionally I would like to use it with gf polls addon, which works fine. Only thing is that I don’t want to disable the form altogether but rather display the form but have the submit button disabled. Reason for this is that users should still be able to see the results of the poll which they can on the form by hitting the “see results” link. Even better of course would be to simply show the results of the poll if limit has been reached.
Cheers
Joe
Hi Joe,
Both your requests will require some customization to the snippet and snippet customization is only available to our Pro Customers. So if you have an active Gravity Perks Pro license, you can get in touch via our support form so we can get our developers to look into your requests. Regarding your second request, we actually already have a snippet that works together with our GP Limit Submissions Perk, to show the Poll results if the limit has been reached.
Best,