Skip to content

Conversation

@SebastianWiz
Copy link
Contributor

Context

⛑️ Ticket(s): https://secure.helpscout.net/conversation/3165078090/94962?viewId=3808239

Summary

Issue: The waiting list message was not displaying consistently across entry views. Choice-based fields with "Show value" enabled were missing the message on the Entry List page because only the choice text was being compared, not the value. The Entry Details order summary never showed the message because it uses its own display logic. Additionally, single product fields displayed the message twice on the form due to the filter running multiple times.

Fix: Updated add_waitlist_message_to_entry_value() to compare against both choice text and value. Added a gform_product_info filter to handle the order summary section. Extracted the waitlist check logic into a shared is_entry_item_waitlisted() helper to reduce duplication between the two methods. Added a duplicate check in add_waiting_list_to_single_product() to prevent double messages.

… was not displaying correctly in entry views.
@coderabbitai
Copy link

coderabbitai bot commented Dec 12, 2025

Walkthrough

A new filter handler add_waitlist_message_to_product_info displays waitlist messages in order summaries by iterating products and appending waitlist indicators to product names when applicable. A centralized helper method is_entry_item_waitlisted consolidates waitlist state retrieval logic, and existing methods are updated to use it while preventing message duplication.

Changes

Cohort / File(s) Summary
Waitlist Message Display & State Management
gp-inventory/gpi-waiting-list.php
Added new public filter handler add_waitlist_message_to_product_info to display waitlist messages in order product info; introduced private helper is_entry_item_waitlisted to centralize waitlist state checking for entries, fields, and values across choice-based and inventory field types; updated add_waitlist_message_to_entry_value to use the new helper; modified add_waiting_list_to_single_product to prevent message duplication

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Review the new add_waitlist_message_to_product_info filter handler implementation and product info iteration logic
  • Verify the is_entry_item_waitlisted helper correctly centralizes waitlist state detection across different field types (choice-based, simple, and advanced inventory)
  • Confirm the refactored methods (add_waitlist_message_to_entry_value, add_waiting_list_to_single_product) properly utilize the helper and eliminate duplication
  • Validate that message injection logic maintains correct entry and field semantics

Suggested reviewers

  • saifsultanc

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and accurately summarizes the main fix: resolving incorrect display of waiting list messages in entry views.
Description check ✅ Passed The description includes all required sections with complete information: context (ticket reference), detailed summary of issues and fixes.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch seb/fix/94962-waiting-list-entry-display

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link

Warnings
⚠️ When ready, don't forget to request reviews on this pull request from your fellow wizards.

Generated by 🚫 dangerJS against f7c1912

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
gp-inventory/gpi-waiting-list.php (1)

140-155: Prefer strict comparisons + guard against missing choices.
$choice['text'] != $value / $choice['value'] != $value can behave oddly with type juggling; and foreach ( $field->choices as $choice ) can warn if choices is null. Consider strict compares + a quick empty( $field->choices ) early return.

 private function is_entry_item_waitlisted( $entry, $field, $value ) {
 	if ( gp_inventory_type_choices()->is_applicable_field( $field ) ) {
+		if ( empty( $field->choices ) || ! is_array( $field->choices ) ) {
+			return false;
+		}
 		foreach ( $field->choices as $choice ) {
-			if ( $choice['text'] != $value && $choice['value'] != $value ) {
+			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'] ) ) );
 		}
 	}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e5b156c and f7c1912.

📒 Files selected for processing (1)
  • gp-inventory/gpi-waiting-list.php (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
gp-inventory/gpi-waiting-list.php (3)
gp-inventory/gpi-weighted-inventory.php (1)
  • is_applicable_field (102-104)
gp-nested-forms/gpnf-require-unique-values.php (1)
  • is_applicable_field (80-85)
gravity-forms/gw-simple-approval.php (1)
  • is_applicable_form (80-85)

Comment on lines +52 to +54
// Add support for order summary in entry details
add_filter( 'gform_product_info', array( $this, 'add_waitlist_message_to_product_info' ), 10, 3 );

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.

Comment on lines +162 to +167
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;
}
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.

Comment on lines +169 to 187
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;
}
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).

Comment on lines 276 to +280
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;
}
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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants