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.
Code
Filename: gw-submission-limit.php
<?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 https://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();
private $_args = 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";
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$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
} elseif ( 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";
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
$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 );
} elseif ( 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.',
) );