Better Inventory with Gravity Forms

Gravity Forms does not support the concept of inventory out of the box. This snippet provides an easy method for setting up simple, one-off inventory limits on a per field basis.
Updated on November 11, 2012 to fix issue where database prefix was not applied via $wpdb->prefix.

Let’s dive into the code first.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
<?php
/**
* Gravity Wiz // Better Inventory with Gravity Forms
*
* Implements the concept of "inventory" with Gravity Forms by allowing the specification of a limit determined by the sum of a specific field, typically a quanity field.
*
* @version 1.0
* @author David Smith <david@gravitywiz.com>
* @license GPL-2.0+
* @link http://gravitywiz.com/2012/09/19/better-inventory-with-gravity-forms/
* @copyright 2014 Gravity Wiz
*/
class GWLimitBySum {
 
private $_args;
 
function __construct($args) {
 
$this->_args = wp_parse_args($args, array(
'form_id' => false,
'field_id' => false,
'limit' => 20,
'limit_message' => __('Sorry, this item is sold out.'),
'validation_message' => __('You ordered %1$s of this item. There are only %2$s of this item left.'),
'approved_payments_only' => false,
'hide_form' => false
));
 
$this->_args['input_id'] = $this->_args['field_id'];
extract($this->_args);
 
add_filter("gform_pre_render_$form_id", array(&$this, 'limit_by_field_values'));
add_filter("gform_validation_$form_id", array(&$this, 'limit_by_field_values_validation'));
 
if($approved_payments_only)
add_filter('gwlimitbysum_query', array(&$this, 'limit_by_approved_only'));
 
}
 
public function limit_by_field_values($form) {
 
$sum = self::get_field_values_sum($form['id'], $this->_args['input_id']);
if($sum < $this->_args['limit'])
return $form;
 
if($this->_args['hide_form']) {
add_filter('gform_get_form_filter', create_function('', 'return "' . $this->_args['limit_message'] . '";'));
} else {
add_filter('gform_field_input', array(&$this, 'hide_field'), 10, 2);
}
 
return $form;
}
 
public function limit_by_field_values_validation($validation_result) {
 
extract($this->_args);
 
$form = $validation_result['form'];
$exceeded_limit = false;
 
foreach($form['fields'] as &$field) {
 
if($field['id'] != intval($input_id))
continue;
 
$requested_value = rgpost("input_" . str_replace('.', '_', $input_id));
$field_sum = self::get_field_values_sum($form['id'], $input_id);
 
if($field_sum + $requested_value <= $limit)
continue;
 
$exceeded_limit = true;
$number_left = $limit - $field_sum >= 0 ? $limit - $field_sum : 0;
 
$field['failed_validation'] = true;
$field['validation_message'] = sprintf($validation_message, $requested_value, $number_left);
 
}
 
$validation_result['form'] = $form;
$validation_result['is_valid'] = !$validation_result['is_valid'] ? false : !$exceeded_limit;
 
return $validation_result;
}
 
public function hide_field($field_content, $field) {
 
if($field['id'] == intval($this->_args['input_id']))
return "<div class=\"ginput_container\">{$this->_args['limit_message']}</div>";
 
return $field_content;
}
 
public static function get_field_values_sum($form_id, $input_id) {
global $wpdb;
 
$query = apply_filters( 'gwlimitbysum_query', array(
'select' => 'SELECT sum( ld.value )',
'from' => "FROM {$wpdb->prefix}rg_lead_detail ld",
'join' => '',
'where' => $wpdb->prepare( "WHERE ld.form_id = %d AND CAST( ld.field_number as unsigned ) = %d", $form_id, $input_id )
), $form_id, $input_id );
 
$wpdb->show_errors();
 
$sql = implode( ' ', $query );
$result = $wpdb->get_var( $sql );
 
return intval( $result );
}
 
public static function limit_by_approved_only( $query ) {
global $wpdb;
$query['from'] .= " INNER JOIN {$wpdb->prefix}rg_lead l ON l.id = ld.lead_id";
$query['where'] .= ' AND l.payment_status = \'Approved\'';
return $query;
}
 
}
 
# Configuration
 
new GWLimitBySum( array(
'form_id' => 363,
'field_id' => 2.3,
'limit' => 2,
'limit_message' => 'Sorry, there are no more tickets!',
'validation_message' => 'You ordered %1$s tickets. There are only %2$s tickets left.',
'approved_payments_only' => false,
'hide_form' => false
) );

How do I install this snippet?

Easy peasy. Just copy and paste the code above into your theme's functions.php file.

Do I need to modify this snippet to work with my form?

You don’t have to modify the snippet itself. Just the parameters with which you initialize it.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
<?php
/**
* Gravity Wiz // Better Inventory with Gravity Forms
*
* Implements the concept of "inventory" with Gravity Forms by allowing the specification of a limit determined by the sum of a specific field, typically a quanity field.
*
* @version 1.0
* @author David Smith <david@gravitywiz.com>
* @license GPL-2.0+
* @link http://gravitywiz.com/2012/09/19/better-inventory-with-gravity-forms/
* @copyright 2014 Gravity Wiz
*/
class GWLimitBySum {
 
private $_args;
 
function __construct($args) {
 
$this->_args = wp_parse_args($args, array(
'form_id' => false,
'field_id' => false,
'limit' => 20,
'limit_message' => __('Sorry, this item is sold out.'),
'validation_message' => __('You ordered %1$s of this item. There are only %2$s of this item left.'),
'approved_payments_only' => false,
'hide_form' => false
));
 
$this->_args['input_id'] = $this->_args['field_id'];
extract($this->_args);
 
add_filter("gform_pre_render_$form_id", array(&$this, 'limit_by_field_values'));
add_filter("gform_validation_$form_id", array(&$this, 'limit_by_field_values_validation'));
 
if($approved_payments_only)
add_filter('gwlimitbysum_query', array(&$this, 'limit_by_approved_only'));
 
}
 
public function limit_by_field_values($form) {
 
$sum = self::get_field_values_sum($form['id'], $this->_args['input_id']);
if($sum < $this->_args['limit'])
return $form;
 
if($this->_args['hide_form']) {
add_filter('gform_get_form_filter', create_function('', 'return "' . $this->_args['limit_message'] . '";'));
} else {
add_filter('gform_field_input', array(&$this, 'hide_field'), 10, 2);
}
 
return $form;
}
 
public function limit_by_field_values_validation($validation_result) {
 
extract($this->_args);
 
$form = $validation_result['form'];
$exceeded_limit = false;
 
foreach($form['fields'] as &$field) {
 
if($field['id'] != intval($input_id))
continue;
 
$requested_value = rgpost("input_" . str_replace('.', '_', $input_id));
$field_sum = self::get_field_values_sum($form['id'], $input_id);
 
if($field_sum + $requested_value <= $limit)
continue;
 
$exceeded_limit = true;
$number_left = $limit - $field_sum >= 0 ? $limit - $field_sum : 0;
 
$field['failed_validation'] = true;
$field['validation_message'] = sprintf($validation_message, $requested_value, $number_left);
 
}
 
$validation_result['form'] = $form;
$validation_result['is_valid'] = !$validation_result['is_valid'] ? false : !$exceeded_limit;
 
return $validation_result;
}
 
public function hide_field($field_content, $field) {
 
if($field['id'] == intval($this->_args['input_id']))
return "<div class=\"ginput_container\">{$this->_args['limit_message']}</div>";
 
return $field_content;
}
 
public static function get_field_values_sum($form_id, $input_id) {
global $wpdb;
 
$query = apply_filters( 'gwlimitbysum_query', array(
'select' => 'SELECT sum( ld.value )',
'from' => "FROM {$wpdb->prefix}rg_lead_detail ld",
'join' => '',
'where' => $wpdb->prepare( "WHERE ld.form_id = %d AND CAST( ld.field_number as unsigned ) = %d", $form_id, $input_id )
), $form_id, $input_id );
 
$wpdb->show_errors();
 
$sql = implode( ' ', $query );
$result = $wpdb->get_var( $sql );
 
return intval( $result );
}
 
public static function limit_by_approved_only( $query ) {
global $wpdb;
$query['from'] .= " INNER JOIN {$wpdb->prefix}rg_lead l ON l.id = ld.lead_id";
$query['where'] .= ' AND l.payment_status = \'Approved\'';
return $query;
}
 
}
 
# Configuration
 
new GWLimitBySum( array(
'form_id' => 363,
'field_id' => 2.3,
'limit' => 2,
'limit_message' => 'Sorry, there are no more tickets!',
'validation_message' => 'You ordered %1$s tickets. There are only %2$s tickets left.',
'approved_payments_only' => false,
'hide_form' => false
) );

Parameters

  • $form_id the ID of the form you are working with.
  • $field_id the ID of your product or quantity field. If using a Single Product field, you’ll need to specify the quantity input ID which will always be {field ID}.3 (ie if the field ID is 12, the quantity input ID will be 12.3). If using a separate Quantity field, simply specify the field ID.
  • $limit the number of this item you have available.
  • $limit_message the message which should be displayed to users when the product limit has been reached. You can get fancy with this and add HTML as well. Be sure to escape any double quotes with a backslash. Here’s a more advanced example:
    1
    $sum_limit_message = '<div style="border: 1px solid #e6db55; background-color: #FFFFE0; padding: 10px;">Sorry, this show is sold out.</div>';
    view raw gistfile1.php hosted with ❤ by GitHub
    Better Inventory: Limit Message
  • $validation_message the validation message which should be displayed on the field if the limit has not been reached, but the user’s requested quantity would exceed the product limit.
    Better Inventory: Limit Validation Message
  • $approved_payments_only true/false indicate whether all submissions for this field should be counted against the limit or only submissions with an approved payment. You’ll want to be careful here when using PayPal Standard due to the delay between the form submission and the payment being approved. It could lead to users exceeding the limit.
  • $hide_form by default this snippet will only hide the field when the limit has been reached. If you would rather hide the entire form, set this to true.

83 thoughts on “Better Inventory with Gravity Forms”

  1. Hello and thanks for the addon.

    Just one question. There is a way to add the limit for two products.

    Thus, I have A and B, but I want got the limit of A+B, no the limit of A and the limit of B.

    Any solution?

    Thank you

    1. Hi Mario, it is possible and the hook is available in the code that would make such a modification possible but it would required additional code to accomplish. The hook is “gwlimitbysum_query” which lets you modify the query that is made to retrieve the current quantity of the specified product that has been ordered. You could update the query to include the quantity of a secondary product as well.

      If you’re interested in commissioning me to write such an enhancement, get in touch.

  2. This is a great solution. I was referred to you by Gravity forms support – high praise!

    I have one question.

    What happens if I end up changing the available seat limit in the php once sales have begun? Sounds like a crazy requirement but I’ll explain.

    I have to launch my event early tomorrow morning. I’m selling tickets with limited inventory. After sales begin I’m likely to receive a more accurate limit than the one I have right now. If I start out offering 40 tickets and later find out my real limit is 50 (or worse, 30) can the code/database account for that? Or, would I need to make a new form with different ID and just relaunch with a new form and limit?

    Thanks for any help you may be able to provide.

    1. Hi Julian, you can change the limit at any time and this snippet will continue to work as expected. The “inventory” in this case is entry-based. So if on Day 1 you have a limit of 20 and 5 entries are submitted, the available inventory is now 15. If on Day 2 to you increase the limit to 30, the available inventory would now be 25 with the 5 entries submitted the prior day still deducted from the increased limit.

  3. Would like to request a quote for custom code that would send a notification email when the inventory capacity has been reached for a field. Please contact me.

  4. Is there a way to change the value of the # of submissions available based on a post meta value?

    this is the code that I have:

    ID, 'tickets_available', true); ?> 3, 'field_id' => $post->ID.5, 'limit' => $submit_limit, 'limit_message' => 'Sorry, there are no more tickets!', 'validation_message' => 'You ordered %1$s tickets. There are only %2$s tickets left.', 'approved_payments_only' => false, 'hide_form' => false )); ?>

    but I dont think it’s working properly.

    1. Hey Jessie, just wanted to follow up to your comment here. My brother should be getting back with you in the next day or so with a quote on getting this to work correctly. The basic premise is that you need to limit by two fields rather than one: 1) the quantity field and 2) an additional field that tracks which “event” the form was submitted for.

  5. I was wondering if this could be used in conjunction with the datepicker? i.e. Our inventory is a fixed number that can be rented out per day- so the days when the inventory is all checked out would be grayed out on the datepicker?

    1. Certainly possible but not without additional custom work. This would require modifications to how this script checks for inventory and some extra modifications to gray out the unavailable dates in the datepicker.

  6. I have 6 “products” in my form with a limiter.. 6 dates with 120 places available each date. But as soon as one of the dates is sold out, the form is not working anymore. It gives the message that there is an error without pointing out what the error is. I think it has troubles with dealing with one sold-out product. Any idea how to solve that?

    1. since I didn’t get this working, I made 6 forms, 1 form for each date. But still it has some troubles with the limit. On each form I set the limit. But when I order some and the next time I want to order more I get the message that there are only X items left where X is less than the limit minus what I have ordered before (checked entries… mine was the only entrie) Deleted all entries permanently, tried again. Limit is 120, order 100 first time, ordered 10 next time. Message you’ve ordered 10 items, there are only 5 items left. Or I order 110 items the first time, next time: sold out. And before trying, I always remove the entries permanently.

  7. hello David

    I’m trying to create a list of topics for users , and i want to limit the topics picked by users.

    user will select any of the topics listed (topic 1- 15) after user selects the topic, he or she will have to register for the topic’s chosen. now i will like to limit the selection of each topic by 3, means only 3 users can register for the same topic.

    i don’t understand how to add or set ( field_id ) to each of the topics that i will like to limit inside the form .

    please help

    I have sofar done :

    new GWLimitBySum(array( ‘form_id’ => 8, ‘field_id’ => 13.3, ‘limit’ => 3, ‘limit_message’ => ‘Sorry, this topic is close!’, ‘validation_message’ => ‘You ordered %1$s tickets. There are only %2$s tickets left.’, ‘approved_payments_only’ => false, ‘hide_form’ => false ));

  8. I was wondering if there was any way to have this not limit the registration, but rather trigger a waiting list. So to add a hidden field to the form that denotes everyone signed up after the limit as being on the waiting list?

    1. Hm… with some modification, you could extend this class and instead of adding validation errors to fields in the limit_by_field_values_validation() method, you could populate a hidden field on your form with a flag, indicating whether it should be considered on the waiting list or not.

  9. Have an event limited to 20 guests max. Each guest can be one of two categories, different price for each category. Can I set a combined total of 2 fields with Better Inventory with Gravity Forms? Each form can register anywhere from 1 to all 20 guests.

    1. Hi Randy, do you have a link to the form in question? It would help me understand your request better if I had a visual. :)

  10. I found your snippet and it looks useful. But I have multiple products (the show has multiple dates and every date has it’s own nummer of seats). how can I add this limit on every product?

    thanks

    Karin

    1. Hi Karin, you can create multiple instances of the class like so: http://pastie.org/8722316#5,15 You’ll notice the only difference between to the two instances is the “field_id” parameter. You’ll need to add a new instance for each product on your form and modify the other parameters as needed.

    1. Hi Atlante, I was unable to access that page. Getting an authentication error when I used the provided login.

  11. Thank you for the code. I appreciate your prompt response. My question is would this work in my situation where:

    End users are being asked to reserve 1 of 8 half-hour blocks for a massage at a Valentine Day event.

    In other words, between 10 and 11 am, if 10 slots are opened, I would want gf to remove that spot from the form.

    1. The GP Limit Choices is probably a better fit for this. You can set up each time slot as a choice on the field and then set a limit of “8″ so only 8 massages can be schedule per time slot. Let me know if I misunderstood what you’re trying to do.

  12. Hey David,

    Have there been any updates to this snippet? We have tried it and can’t seem to get it to work. Plus, our form is for a registration for classes at a pre-school and there are multiple options with multiple number availabilities per class.

    Thanks for the response.

    Nate

    1. Hey Nate, nope, this is the most up to date version of this code. I just tested locally and didn’t run into any issues. If you can send me a copy of your functions.php and a link where I can view the form it isn’t working on, I’ll let you know if I can see any issues.

Leave a Reply

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

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>