Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 41 additions & 18 deletions gp-inventory/gpi-waiting-list.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ public function add_hooks() {
add_filter( 'gform_entries_field_value', array( $this, 'entries_field_value_with_waitlist_message' ), 10, 4 );
add_filter( 'gform_entry_field_value', array( $this, 'add_waitlist_message_to_entry_value' ), 10, 4 );

// Add support for order summary in entry details
add_filter( 'gform_product_info', array( $this, 'add_waitlist_message_to_product_info' ), 10, 3 );

Comment on lines +52 to +54
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n gp-inventory/gpi-waiting-list.php | head -200

Repository: gravitywiz/snippet-library

Length of output: 8795


🏁 Script executed:

rg "gform_product_info" --type php -B 3 -A 3 | head -100

Repository: gravitywiz/snippet-library

Length of output: 9008


🏁 Script executed:

rg "apply_filters.*gform_product_info|do_action.*gform_product_info" --type php -B 5 -A 5

Repository: gravitywiz/snippet-library

Length of output: 52


🌐 Web query:

Gravity Forms gform_product_info hook documentation entry parameter

💡 Result:

The gform_product_info filter receives three parameters: (1) $product_info (array), (2) $form (Form object), and (3) $entry — the Entry object for the entry being processed (examples sometimes name it $lead). [1]

Source: Gravity Forms docs for gform_product_info. [1]


🏁 Script executed:

cat -n gp-inventory/gpi-waiting-list.php | sed -n '189,250p'

Repository: gravitywiz/snippet-library

Length of output: 2439


🏁 Script executed:

rg "gform_product_info" -A 10 gravity-forms/gw-gravity-forms-filter-out-zero-dollar-products.php | head -30

Repository: gravitywiz/snippet-library

Length of output: 559


🏁 Script executed:

rg "gform_product_info" -A 15 gs-product-configurator/gspc-display-discounts-on-entry.php | head -25

Repository: gravitywiz/snippet-library

Length of output: 831


🌐 Web query:

Gravity Forms gform_product_info hook when called contexts entry detail order summary

💡 Result:

  • The gform_product_info filter is fired whenever Gravity Forms "requests the list of products saved in an entry" — i.e., any time GF builds the product/line‑item data for an entry. [1]

  • Typical contexts where it runs: Entry Detail order grid / order summary, the {all_fields} and {pricing_fields} merge tags (order summary output), notification/confirmation generation, and when Add‑Ons (e.g. PayPal) prepare product data to send. Use gform_order_summary / gform_product hooks to alter markup/labels. [2][3]

  • Important: gform_product_info applies when creating/assembling product data; it does NOT fire for already cached entry product data unless you clear that cached data first. [1]

Sources:
[1] Gravity Forms — gform_product_info documentation.
[2] Gravity Forms — gform_order_summary documentation.
[3] Gravity Forms — gform_product documentation.


🌐 Web query:

Gravity Forms gform_product_info preview mode draft entry null missing

💡 Result:

Short answer: This is expected — gform_product_info runs when Gravity Forms "requests the list of products saved in an entry", but Preview / Save‑and‑Continue uses an incomplete (draft) submission where the cached entry/product data can be null, so the product list may be missing in preview mode. Use the Save & Continue filters or the preview init hook to rebuild/inject the product info for draft submissions before the preview renders. [1][2]

What to do (concise):

  • Confirm the problem: gform_product_info will not fire with complete product data when Gravity Forms loads a cached/draft entry. See docs. [1]
  • Solution A — rebuild the draft submission product data: hook gform_incomplete_submission_post_get, decode the submission JSON, populate the partial_entry / submitted_values (or add the missing product info) and re-encode it. This ensures the preview has product data available. [2]
  • Solution B — if you only need to alter product output on preview, use gform_preview_init and run code to generate the products from the form/posted values or from the incomplete submission token. [2][7] (gform_preview_init doc).
  • Also check for third‑party Previewer/GravityView/Preview add‑ons (they sometimes cache or alter preview behavior) and test with other plugins disabled.

Minimal example (outline — adapt to your field IDs and form):

  • add_filter('gform_incomplete_submission_post_get', 'my_fix_draft_products', 10, 3);
  • in function: $obj = json_decode($submission_json); update $obj->partial_entry (or $obj->submitted_values) to include pricing/product values; return json_encode($obj);

References:

  • gform_product_info (behavior and note about cached entries). [1]
  • gform_incomplete_submission_post_get (how to modify draft submission after retrieval). [2]

If you want, paste your form ID and product field IDs and I’ll provide a ready‑to‑drop PHP snippet.


Add guards in add_waitlist_message_to_product_info() for incomplete entry data in preview/draft contexts.

The gform_product_info hook can fire when processing incomplete entries (e.g., Save & Continue previews, draft submissions) where the entry structure may be incomplete or missing the id key. The callback at line 169-187 immediately calls is_entry_item_waitlisted(), which accesses $entry['id'] without checking if it exists. Add a guard before processing:

 public function add_waitlist_message_to_product_info( $product_info, $form, $entry ) {
 	if ( ! $this->is_applicable_form( $form ) ) {
 		return $product_info;
 	}
+	if ( empty( $entry['id'] ) ) {
+		return $product_info;
+	}

This prevents notices/fatals when the entry is not fully persisted.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In gp-inventory/gpi-waiting-list.php around lines 52-54 and the callback at
lines ~169-187, the gform_product_info handler calls is_entry_item_waitlisted()
which assumes $entry['id'] exists; add a guard at the start of
add_waitlist_message_to_product_info() that verifies $entry is an array and
isset($entry['id']) (or a similar truthy check) and return early if not present
(this covers preview/draft/incomplete entries), so the function skips processing
when entry data is incomplete and avoids notices/fatals.

/**
* Single products
*/
Expand Down Expand Up @@ -134,35 +137,53 @@ public function apply_waitlist_message_to_choice( $choice, $field, $form, $how_m
return $choice;
}

private function is_entry_item_waitlisted( $entry, $field, $value ) {
if ( gp_inventory_type_choices()->is_applicable_field( $field ) ) {
foreach ( $field->choices as $choice ) {
if ( $choice['text'] != $value && $choice['value'] != $value ) {
continue;
}
return (bool) gform_get_meta( $entry['id'], sprintf( 'gpi_is_waitlisted_%d_%s', $field->id, sanitize_title( $choice['value'] ) ) );
}
}

if ( gp_inventory_type_simple()->is_applicable_field( $field ) || gp_inventory_type_advanced()->is_applicable_field( $field ) ) {
return (bool) gform_get_meta( $entry['id'], sprintf( 'gpi_is_waitlisted_%d', $field->id ) );
}

return false;
}

public function add_waitlist_message_to_entry_value( $value, $field, $entry, $form ) {
if ( ! $this->is_applicable_form( $form ) || ! $this->is_applicable_field( $field ) ) {
return $value;
}

if ( gp_inventory_type_choices()->is_applicable_field( $field ) ) {
foreach ( $field->choices as $choice ) {
if ( $choice['text'] != $value ) {
continue;
}
if ( $this->is_entry_item_waitlisted( $entry, $field, $value ) && strpos( $value, $this->waitlist_message ) === false ) {
$value .= ' ' . $this->waitlist_message;
}

$is_waitlisted = gform_get_meta( $entry['id'], sprintf( 'gpi_is_waitlisted_%d_%s', $field->id, sanitize_title( $choice['value'] ) ) );
return $value;
}
Comment on lines +162 to +167
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Avoid strpos() on non-strings.
If $value is ever non-string (or null), strpos() can throw a TypeError on newer PHP versions. A lightweight cast (or is_string() check) keeps this robust.

-	if ( $this->is_entry_item_waitlisted( $entry, $field, $value ) && strpos( $value, $this->waitlist_message ) === false ) {
+	$value_str = (string) $value;
+	if ( $this->is_entry_item_waitlisted( $entry, $field, $value_str ) && strpos( $value_str, $this->waitlist_message ) === false ) {
-		$value .= ' ' . $this->waitlist_message;
+		$value_str .= ' ' . $this->waitlist_message;
 	}
 
-	return $value;
+	return $value_str;

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In gp-inventory/gpi-waiting-list.php around lines 162 to 167, the code calls
strpos($value, $this->waitlist_message) which can throw a TypeError if $value is
not a string or is null; guard the call by either casting $value to string
before using strpos (e.g. $valStr = (string) $value) or checking
is_string($value) first, then append the waitlist message only when appropriate;
ensure the function still returns the original $value type when no change is
needed.


if ( $is_waitlisted ) {
$choice = $this->apply_waitlist_message_to_choice( $choice, $field, $form );
$value = $choice['text'];
}
}
public function add_waitlist_message_to_product_info( $product_info, $form, $entry ) {
if ( ! $this->is_applicable_form( $form ) ) {
return $product_info;
}

if ( gp_inventory_type_simple()->is_applicable_field( $field ) || gp_inventory_type_advanced()->is_applicable_field( $field ) ) {
$is_waitlisted = gform_get_meta( $entry['id'], sprintf( 'gpi_is_waitlisted_%d', $field->id ) );
foreach ( $product_info['products'] as $field_id => &$product ) {
$field = GFAPI::get_field( $form, $field_id );

if ( $is_waitlisted ) {
$value .= ' ' . $this->waitlist_message;
if ( ! $this->is_applicable_field( $field ) ) {
continue;
}

if ( $this->is_entry_item_waitlisted( $entry, $field, $product['name'] ) && strpos( $product['name'], $this->waitlist_message ) === false ) {
$product['name'] .= ' ' . $this->waitlist_message;
}
}

return $value;
return $product_info;
}
Comment on lines +169 to 187
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Guard $entry, $product_info['products'], and missing fields; unset foreach reference.

This callback assumes (1) $entry['id'] exists, (2) $product_info['products'] is populated, and (3) GFAPI::get_field() always returns a valid field. Missing these can cause PHP warnings or fatal errors. Additionally, the foreach reference should be unset after the loop to prevent accidental mutations if code is modified later.

 public function add_waitlist_message_to_product_info( $product_info, $form, $entry ) {
 	if ( ! $this->is_applicable_form( $form ) ) {
 		return $product_info;
 	}
 
+	if ( empty( $entry ) || ! is_array( $entry ) || empty( $entry['id'] ) ) {
+		return $product_info;
+	}
+
+	if ( empty( $product_info['products'] ) || ! is_array( $product_info['products'] ) ) {
+		return $product_info;
+	}
+
 	foreach ( $product_info['products'] as $field_id => &$product ) {
 		$field = GFAPI::get_field( $form, $field_id );
+		if ( ! $field ) {
+			continue;
+		}
 
 		if ( ! $this->is_applicable_field( $field ) ) {
 			continue;
 		}
 
 		if ( $this->is_entry_item_waitlisted( $entry, $field, $product['name'] ) && strpos( $product['name'], $this->waitlist_message ) === false ) {
 			$product['name'] .= ' ' . $this->waitlist_message;
 		}
 	}
+	unset( $product );
 
 	return $product_info;
 }

Note: $product['name'] is the correct field for matching products across all product field types (single, dropdown, radio, select, checkbox).


public function add_entry_meta( $entry, $form ) {
Expand Down Expand Up @@ -253,8 +274,10 @@ public function add_waiting_list_to_single_product( $form ) {
$available = (int) $gpi_instance->get_available_stock( $field );

if ( $available <= 0 ) {
$message = $this->waitlist_message;
$field->description = '<div class="gpi-available-inventory-message" style="padding-bottom: 13px;">' . $message . '</div>' . $field->description;
$message = $this->waitlist_message;
if ( strpos( $field->description, $this->waitlist_message ) === false ) {
$field->description = '<div class="gpi-available-inventory-message" style="padding-bottom: 13px;">' . $message . '</div>' . $field->description;
}
Comment on lines 276 to +280
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

strpos( $field->description, ... ) can TypeError if description is null.
Cast to string (or default to empty string) before strpos(). Also, if waitlist_message is meant to be plain text, escape it when injecting into HTML.

 	if ( $available <= 0 ) {
 		$message = $this->waitlist_message;
-		if ( strpos( $field->description, $this->waitlist_message ) === false ) {
-			$field->description = '<div class="gpi-available-inventory-message" style="padding-bottom: 13px;">' . $message . '</div>' . $field->description;
+		$description = (string) $field->description;
+		if ( strpos( $description, $this->waitlist_message ) === false ) {
+			$field->description = '<div class="gpi-available-inventory-message" style="padding-bottom: 13px;">' . esc_html( $message ) . '</div>' . $description;
 		}
 		$field->isWaitlisted = true;
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if ( $available <= 0 ) {
$message = $this->waitlist_message;
$field->description = '<div class="gpi-available-inventory-message" style="padding-bottom: 13px;">' . $message . '</div>' . $field->description;
$message = $this->waitlist_message;
if ( strpos( $field->description, $this->waitlist_message ) === false ) {
$field->description = '<div class="gpi-available-inventory-message" style="padding-bottom: 13px;">' . $message . '</div>' . $field->description;
}
if ( $available <= 0 ) {
$message = $this->waitlist_message;
$description = (string) $field->description;
if ( strpos( $description, $this->waitlist_message ) === false ) {
$field->description = '<div class="gpi-available-inventory-message" style="padding-bottom: 13px;">' . esc_html( $message ) . '</div>' . $description;
}
🤖 Prompt for AI Agents
In gp-inventory/gpi-waiting-list.php around lines 276 to 280,
strpos($field->description, $this->waitlist_message) can throw a TypeError when
description is null and the waitlist_message is injected into HTML unescaped;
ensure $field->description is treated as a string (e.g., default to '' or cast
to (string)) before calling strpos, and escape the waitlist message when
concatenating into HTML (use the appropriate escaping function such as esc_html
or htmlspecialchars) so you pass safe text into the markup.

$field->isWaitlisted = true;
}
}
Expand Down