Skip to content

Use Lite for the payments pages and add coupons tabs#2563

Merged
Crabcyborg merged 65 commits into
masterfrom
coupons
Jan 5, 2026
Merged

Use Lite for the payments pages and add coupons tabs#2563
Crabcyborg merged 65 commits into
masterfrom
coupons

Conversation

@Crabcyborg
Copy link
Copy Markdown
Contributor

@Crabcyborg Crabcyborg commented Oct 28, 2025

This update also includes support for the Coupons add-on.

Main Coupons PR https://github.com/Strategy11/formidable-coupons/pull/1

@Crabcyborg Crabcyborg changed the title Start working on using Lite for the Stripe add-on Start working on using Lite for the payments pages Oct 28, 2025
@Crabcyborg Crabcyborg changed the title Start working on using Lite for the payments pages Start working on using Lite for the payments pages and add coupons tabs Oct 28, 2025
@Crabcyborg Crabcyborg changed the title Start working on using Lite for the payments pages and add coupons tabs Use Lite for the payments pages and add coupons tabs Nov 7, 2025
Copy link
Copy Markdown
Contributor

@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: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
classes/helpers/FrmAppHelper.php (1)

1-4: Pipeline failure: Fix blank line formatting issue.

The static analysis tool reports a blank_line_before_statement violation at the beginning of the file. This is likely a coding standards issue that should be addressed to pass the pipeline checks.

Based on typical PHP coding standards, ensure proper spacing after the opening PHP tag. The current structure may need adjustment to satisfy the linter.

♻️ Duplicate comments (9)
classes/helpers/FrmFieldsHelper.php (1)

2492-2504: The @since x.x placeholder needs to be updated before release.

This was already noted in a previous review.

js/src/admin/admin.js (1)

909-914: Drag-start limit check should key off ui.draggable, not event.target

Using event.target.classList.contains('frm_at_limit') misses the case where the mousedown starts on a child element inside a limited button (the original concern from the earlier review), allowing drags from buttons that should be at their limit. Instead, use ui.draggable[0] to decide whether the source is at the limit.

-	function handleDragStart( event, ui ) {
-		if ( event.target.classList.contains( 'frm_at_limit' ) ) {
+	function handleDragStart( event, ui ) {
+		if ( ui.draggable && ui.draggable[ 0 ].classList.contains( 'frm_at_limit' ) ) {
 			showLimitModal();
 			return false;
 		}

This reliably blocks drags whenever the draggable button itself is marked frm_at_limit, regardless of which child was clicked.

stripe/controllers/FrmTransLiteListsController.php (1)

118-134: Well-structured coupons routing with extensibility.

The implementation correctly handles the coupons action with proper permission checks and provides an extensible routing mechanism via the frm_trans_lite_route filter.

However, the version placeholder x.x at line 126 still needs to be updated before release (previously flagged for similar placeholders in FrmTransLiteAppHelper.php).

stripe/helpers/FrmTransLiteAppHelper.php (1)

604-621: Clean feature detection and proper deprecation.

The new payments_submodule_or_paypal_is_active() method provides clear feature detection, and should_fallback_to_paypal() is properly deprecated with a deprecation notice.

The version placeholders (x.x at lines 605, 614, 619) need to be updated before release, as previously noted.

stripe/helpers/FrmTransLiteListHelper.php (5)

183-195: LGTM: Conditional bulk actions.

The method correctly gates bulk delete actions based on the presence of payment addon helpers, aligning with the Lite vs full feature distinction.

Version placeholder at line 184 needs updating before release.


287-296: LGTM: Feature detection and dynamic column rendering.

The payments_addon_list_helper_exists() helper provides clean feature detection. The get_cb_column() method is called dynamically via the get_column_value() pattern at line 306 (method name constructed as 'get_' . $column_name . '_column'), so the static analysis warning is a false positive.

Version placeholders at lines 290 and 379 need updating before release.

Also applies to: 375-387


404-423: LGTM: Improved link generation encapsulation.

The refactoring centralizes payment link generation through get_view_payment_link() and get_url_to_payment(), improving maintainability and reducing duplication.

Version placeholder at line 410 needs updating.


456-456: LGTM: Clean edit link feature detection.

The supports_edit_link() method properly encapsulates the logic for determining edit link availability, excluding subscriptions and checking for the presence of required controllers.

Version placeholder at line 467 needs updating.

Also applies to: 466-477


648-663: LGTM: Conditional tabs rendering.

The render_tabs() method properly gates the new coupons feature behind permissions and a feature flag. The $active_tab parameter is used by the included view file (tabs.php) via variable scope, so the static analysis warning is a false positive.

Version placeholder at line 649 needs updating before release.

🧹 Nitpick comments (4)
classes/helpers/FrmAppHelper.php (1)

4662-4685: Consider adding json_encode error handling.

The method logic is solid, but there's a minor concern on line 4670 where json_encode( $link ) is called without error handling. While unlikely to fail in this context, encoding errors could occur if $link contains invalid UTF-8 sequences or if there are circular references.

Consider adding error handling:

 if ( ! empty( $link['url'] ) && self::pro_is_installed() ) {
-	$params['oneclick'] = json_encode( $link );
+	$encoded = json_encode( $link );
+	if ( false !== $encoded ) {
+		$params['oneclick'] = $encoded;
+	}
 	unset( $params['message'] );

Alternatively, you could use wp_json_encode() which provides some additional safeguards, though in this case the difference is minimal.

js/src/admin/admin.js (3)

1916-1937: Repeater drop restrictions and filter hook look good; tidy up JSDoc and signature now

The new droppableIsARepeater / droppableIsInsideOfARepeater checks plus the GDPR guard and frm_deny_drop_in_repeater filter are a sensible way to centralize repeater‑specific restrictions, and skipping the group‑size check when dropping on a repeater section is consistent with the rest of allowDrop. No functional issues stand out.

Two small follow‑ups:

  • The JSDoc block for frm_deny_drop_in_repeater currently has @since x.x and is tripping ESLint (“Expected JSDoc block lines to be aligned”). Before release, set the real version and align the */@ lines to satisfy the linter.
  • If you think consumers of frm_deny_drop_in_repeater might ever need the droppable context, consider adding it to the hook signature now (e.g. applyFilters( 'frm_deny_drop_in_repeater', false, draggable, droppable )) while it’s still easy to change.

2241-2257: showLimitModal correctly reuses the confirm modal and hides the confirm action

The temporary anchor + confirmLinkClick reuse pattern is a clean way to surface the limit message, and explicitly hiding #frm-confirmed-click ensures the dialog is informational only. Combined with the confirmModal change that restores the button’s display on the next open, this keeps other confirm flows intact.

If you later add additional confirm dialogs, consider tagging the limit modal instance (e.g. a class on #frm_confirm_modal) instead of relying solely on toggling the button’s display to make the state more explicit, but this isn’t blocking.


5244-5273: Re-enabling limited field buttons after delete is correct but timing-sensitive

Hooking maybeEnableFieldButtonAtLimit( $thisField.data( 'type' ) ) in deleteField’s success handler and computing countFieldTypeInForm( type ) - 1 (because the DOM node hasn’t been removed yet) correctly clears frm_at_limit once the count drops below the button’s data-limit. The implementation is safe given the current ordering (helper called before fadeOut’s removal callback).

Just be aware that this helper assumes it runs before the field is actually removed from #frm-show-fields; if you ever refactor the delete flow, you may want to move the call into the fadeOut callback and drop the - 1 adjustment to make it less order‑dependent.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 858eebc and 7f5c5a5.

⛔ Files ignored due to path filters (1)
  • images/icons.svg is excluded by !**/*.svg, !**/*.svg
📒 Files selected for processing (12)
  • bin/zip-plugin.sh (1 hunks)
  • classes/controllers/FrmAppController.php (2 hunks)
  • classes/helpers/FrmAppHelper.php (1 hunks)
  • classes/helpers/FrmFieldsHelper.php (2 hunks)
  • classes/models/FrmField.php (2 hunks)
  • js/src/admin/admin.js (8 hunks)
  • stripe/controllers/FrmStrpLiteAppController.php (1 hunks)
  • stripe/controllers/FrmTransLiteEntriesController.php (0 hunks)
  • stripe/controllers/FrmTransLiteListsController.php (2 hunks)
  • stripe/controllers/FrmTransLitePaymentsController.php (1 hunks)
  • stripe/helpers/FrmTransLiteAppHelper.php (2 hunks)
  • stripe/helpers/FrmTransLiteListHelper.php (8 hunks)
💤 Files with no reviewable changes (1)
  • stripe/controllers/FrmTransLiteEntriesController.php
🚧 Files skipped from review as they are similar to previous changes (2)
  • classes/controllers/FrmAppController.php
  • classes/models/FrmField.php
🧰 Additional context used
🧬 Code graph analysis (4)
classes/helpers/FrmFieldsHelper.php (2)
js/src/admin/admin.js (1)
  • limit (7907-7907)
classes/models/FrmDb.php (2)
  • FrmDb (6-870)
  • get_count (231-235)
stripe/controllers/FrmStrpLiteAppController.php (1)
stripe/helpers/FrmTransLiteAppHelper.php (2)
  • FrmTransLiteAppHelper (6-622)
  • payments_table_exists (39-56)
stripe/controllers/FrmTransLiteListsController.php (1)
stripe/helpers/FrmTransLiteAppHelper.php (2)
  • FrmTransLiteAppHelper (6-622)
  • plugin_path (11-13)
stripe/controllers/FrmTransLitePaymentsController.php (1)
classes/helpers/FrmAppHelper.php (2)
  • FrmAppHelper (6-5092)
  • simple_get (815-824)
🪛 GitHub Actions: Inspections
stripe/helpers/FrmTransLiteListHelper.php

[error] 1-1: phpdoc_separation, blank_line_before_statement

stripe/controllers/FrmTransLiteListsController.php

[error] 1-1: blank_line_before_statement

classes/helpers/FrmAppHelper.php

[error] 1-1: blank_line_before_statement

🪛 GitHub Check: Run ESLint
js/src/admin/admin.js

[warning] 1925-1925:
Expected JSDoc block lines to be aligned

🪛 PHPMD (2.15.0)
stripe/helpers/FrmTransLiteListHelper.php

385-387: Avoid unused private methods such as 'get_cb_column'. (undefined)

(UnusedPrivateMethod)


654-654: Avoid unused parameters such as '$active_tab'. (undefined)

(UnusedFormalParameter)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: PHP 7.4 tests in WP trunk
  • GitHub Check: PHP 8 tests in WP trunk
  • GitHub Check: Cypress
🔇 Additional comments (14)
classes/helpers/FrmAppHelper.php (1)

4638-4649: LGTM! Coupons feature flag logic is appropriate.

The addition of the coupons-specific check using class_exists( 'FrmCouponsAppController' ) is a clean approach for feature detection. The method correctly returns true when the feature should be shown based on either the install_link status or the presence of the coupons controller class.

bin/zip-plugin.sh (1)

105-106: LGTM!

The exclusion of formidable-coupons/js/frontend.js and formidable-coupons/js/admin.js follows the established pattern for other addon JS source files (e.g., formidable-hubspot/js/admin.js).

stripe/controllers/FrmTransLitePaymentsController.php (1)

17-24: LGTM!

The addition of 'bulk_delete' to the action list ensures that bulk delete operations are correctly routed through the PayPal addon when active. The is_callable check provides a safe fallback.

stripe/controllers/FrmStrpLiteAppController.php (1)

66-82: LGTM!

The refactoring into segmented early-return guards improves readability. The new guard for FrmPaymentsController correctly prevents unwanted redirects when the PayPal add-on is active, allowing it to handle its own routing.

classes/helpers/FrmFieldsHelper.php (2)

2455-2457: LGTM!

The conditional correctly gates the limit-check logic to only run when the field is not an upgrade UI and has a defined limit, preventing unnecessary database queries.


2505-2521: LGTM!

The implementation correctly counts existing fields of the specified type within the form using FrmDb::get_count() and applies the frm_at_limit class when the limit is reached. The data-limit attribute enables JavaScript to enforce limits client-side.

js/src/admin/admin.js (3)

332-336: Confirm button visibility reset looks correct

Re‑showing #frm-confirmed-click on every confirmModal open prevents it staying hidden after showLimitModal() hides it; this keeps other confirm flows working as expected.


2193-2200: Click guard for field-type limits is consistent with modal behavior

Short‑circuiting addFieldClick when $button.hasClass('frm_at_limit') and showing showLimitModal() (with return false to suppress the link’s default) correctly mirrors the drag‑start guard and prevents further insertions via sidebar clicks once a type has reached its limit.


2854-2879: Per-type button disabling on reaching the field limit is straightforward

Calling maybeDisableFieldButtonAtLimit( type ) from afterAddField and implementing it as a simple data-limit + countFieldTypeInForm check is clear and self‑contained. The guard on button && button.dataset.limit makes it safe for types without limits, and the logic will behave as expected as long as the builder markup keeps #frm-show-fields li.form-field[data-ftype="{type}"] in sync with the sidebar button IDs.

stripe/controllers/FrmTransLiteListsController.php (1)

72-74: LGTM: Conditional mode column rendering.

The logic correctly adds the mode column only in appropriate contexts by checking the action and the presence of specific list helper classes.

stripe/helpers/FrmTransLiteAppHelper.php (1)

78-78: LGTM: Safer status check.

The change from if ( $payment->status ) to if ( ! empty( $payment->status ) ) is more explicit and avoids potential issues with falsy but valid values.

stripe/helpers/FrmTransLiteListHelper.php (3)

276-285: LGTM: Consistent checkbox column gating.

Removing the checkbox column for Lite builds (when payment addons aren't present) is consistent with the conditional bulk actions logic.


425-441: LGTM: Centralized URL generation.

The get_url_to_payment() helper properly encapsulates payment URL generation, ensuring consistency across the list UI.


576-576: LGTM: Safer status check.

Using the null coalescing operator prevents undefined property warnings when the status property doesn't exist.

@Crabcyborg Crabcyborg added this to the 6.27 milestone Dec 17, 2025
Copy link
Copy Markdown
Contributor

@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: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
js/src/admin/admin.js (1)

323-355: Confirm modal button reset is good, but stale data-* attributes are never cleared

The new block that forces continueButton.style.display = 'block' when opening the confirm modal is a good fix so the button isn’t permanently hidden after showLimitModal() hides it. However, in the same function the attribute‑clearing loop appears to use the wrong variable:

removeAtts = continueButton.dataset;
for ( i in dataAtts ) {
    continueButton.removeAttribute( 'data-' + i );
}
dataAtts = link.dataset;

This never iterates over removeAtts, so old data-* attributes on the confirm button can linger across uses.

Consider:

-        removeAtts = continueButton.dataset;
-        for ( i in dataAtts ) {
-            continueButton.removeAttribute( 'data-' + i );
-        }
-
-        dataAtts = link.dataset;
+        removeAtts = continueButton.dataset;
+        for ( i in removeAtts ) {
+            continueButton.removeAttribute( 'data-' + i );
+        }
+
+        dataAtts = link.dataset;

This preserves your new visibility fix while ensuring each open starts from a clean data-* state.

♻️ Duplicate comments (1)
js/src/admin/admin.js (1)

909-914: Drag-start limit check should use closest('.frm_at_limit') on the event target

Right now the drag guard only fires if the direct event.target has frm_at_limit, which will often be a child inside the button (e.g., <span>, <svg>). Those drags won’t be blocked.

To reliably intercept drags on a limited field button and avoid errors when event.target isn’t an Element, use closest with a guard:

function handleDragStart( event, ui ) {
-    if ( event.target.classList.contains( 'frm_at_limit' ) ) {
-        showLimitModal();
-        return false;
-    }
+    const atLimitEl = event.target && event.target.closest && event.target.closest( '.frm_at_limit' );
+    if ( atLimitEl ) {
+        showLimitModal();
+        return false;
+    }

This matches the intent from the earlier review and ensures all clicks within a limited button are caught.

🧹 Nitpick comments (4)
js/src/admin/admin.js (4)

1916-1938: Repeater drop restrictions and hook look correct; consider small polish

The new logic to:

  • Detect repeater context (droppableIsARepeater / droppableIsInsideOfARepeater),
  • Block GDPR fields in repeaters, and
  • Expose a frm_deny_drop_in_repeater filter

is consistent and will effectively prevent unsupported drops into repeaters.

Minor follow‑ups you might consider:

  • Explicitly coerce the filter’s return value to boolean to be defensive against truthy strings:
- const shouldDenyDropInRepeater = wp.hooks.applyFilters( 'frm_deny_drop_in_repeater', false, draggable );
- if ( shouldDenyDropInRepeater ) {
+ const shouldDenyDropInRepeater = !! wp.hooks.applyFilters(
+     'frm_deny_drop_in_repeater',
+     false,
+     draggable
+ );
+ if ( shouldDenyDropInRepeater ) {
     return false;
 }
  • Fix the ESLint JSDoc alignment warning on the @since block above this filter to keep checks green (align the * columns with the opening /**). This is purely cosmetic.

2243-2260: showLimitModal() works, but you could avoid the temporary anchor indirection

showLimitModal() correctly:

  • Reuses the existing confirm modal UI to display a “field type has reached its limit” message, and
  • Hides the confirm button afterward so it’s informational-only.

The temporary anchor click approach is a clever reuse, but slightly indirect. If you ever want to simplify, you could:

  • Call confirmModal() (or confirmLinkClick()) directly with a constructed link-like object, or
  • Expose a small helper like openConfirmModalWithMessage(message) that internally wires up the confirm modal without needing to inject a DOM node.

Not urgent, just an opportunity to reduce DOM churn and indirection later.


2857-2882: Button disabling on reaching a per-type limit is sound; consider explicit number coercion

Hooking maybeDisableFieldButtonAtLimit( type ) from afterAddField() is a good place to enforce per‑type limits. The logic of:

  • Finding the button by id (type),
  • Checking button.dataset.limit, and
  • Comparing countFieldTypeInForm( type ) against that limit

is correct.

For clarity and to avoid any surprises from string comparison, you might coerce dataset.limit once:

function maybeDisableFieldButtonAtLimit( type ) {
    const button = document.getElementById( type );
-   if ( button && button.dataset.limit && countFieldTypeInForm( type ) >= button.dataset.limit ) {
+   if ( ! button || ! button.dataset.limit ) {
+       return;
+   }
+
+   const limit = parseInt( button.dataset.limit, 10 );
+   if ( Number.isFinite( limit ) && countFieldTypeInForm( type ) >= limit ) {
        button.classList.add( 'frm_at_limit' );
    }
}

This keeps behavior the same while making the intent explicit.


5247-5276: Re-enabling limited field buttons after delete is correct; small safety guard suggested

Calling maybeEnableFieldButtonAtLimit( $thisField.data( 'type' ) ) after a successful frm_delete_field is the right place to re-enable buttons when counts drop below the limit. The countFieldTypeInForm( type ) - 1 adjustment accounts for the still-present DOM node during the fade-out.

Two small improvements to consider:

  1. Guard for missing builder DOM so this helper is safe if deleteField() is ever reused outside the builder:
function countFieldTypeInForm( type ) {
-   return document.getElementById( 'frm-show-fields' ).querySelectorAll(
-       'li.form-field[data-ftype="' + type + '"]'
-   ).length;
+   const container = document.getElementById( 'frm-show-fields' );
+   if ( ! container ) {
+       return 0;
+   }
+   return container.querySelectorAll(
+       'li.form-field[data-ftype="' + type + '"]'
+   ).length;
}
  1. As with the disable path, parse button.dataset.limit once to a number before comparing, to avoid implicit coercion.

Behavior stays the same but the helpers become more robust and self‑documenting.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f87e194 and 9876586.

📒 Files selected for processing (1)
  • js/src/admin/admin.js (8 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
js/src/admin/admin.js (1)
js/src/form/modal.js (1)
  • wp (5-5)
🪛 GitHub Check: Run ESLint
js/src/admin/admin.js

[warning] 1925-1925:
Expected JSDoc block lines to be aligned

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: PHP 7.4 tests in WP trunk
  • GitHub Check: PHP 8 tests in WP trunk
  • GitHub Check: Cypress
🔇 Additional comments (1)
js/src/admin/admin.js (1)

2193-2202: Click guard when at field-type limit behaves correctly

The new guard in addFieldClick():

  • Checks $button.hasClass( 'frm_at_limit' ),
  • Shows showLimitModal(), and
  • Returns false to suppress the default anchor behavior,

which fixes the earlier issue where the link still navigated when at limit. The explanatory comment around shouldStopInsertingField() is also helpful for future readers. No changes needed here.

Copy link
Copy Markdown
Contributor

@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: 0

🧹 Nitpick comments (1)
stripe/views/lists/coupons.php (1)

25-42: Replace placeholder version numbers before release.

The @since x.x placeholders on lines 10 and 32 should be replaced with the actual version number before this PR is merged.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9876586 and fd8ac94.

📒 Files selected for processing (6)
  • classes/controllers/FrmAppController.php (2 hunks)
  • classes/helpers/FrmAppHelper.php (1 hunks)
  • stripe/controllers/FrmTransLiteListsController.php (2 hunks)
  • stripe/helpers/FrmTransLiteListHelper.php (8 hunks)
  • stripe/views/lists/coupons.php (1 hunks)
  • stripe/views/lists/tabs.php (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • classes/controllers/FrmAppController.php
  • classes/helpers/FrmAppHelper.php
🧰 Additional context used
🧬 Code graph analysis (1)
stripe/helpers/FrmTransLiteListHelper.php (1)
stripe/helpers/FrmTransLiteAppHelper.php (2)
  • FrmTransLiteAppHelper (6-622)
  • plugin_path (11-13)
🪛 PHPMD (2.15.0)
stripe/helpers/FrmTransLiteListHelper.php

386-388: Avoid unused private methods such as 'get_cb_column'. (undefined)

(UnusedPrivateMethod)


657-657: Avoid unused parameters such as '$active_tab'. (undefined)

(UnusedFormalParameter)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cypress
🔇 Additional comments (15)
stripe/views/lists/tabs.php (2)

6-15: LGTM! Clean tab structure with proper escaping and localization.

The tabs array is well-structured with clear URL and label definitions for both payments and coupons tabs. Proper use of admin_url(), __() for i18n, and action-based routing.


18-40: No issue found$active_tab is properly available through the function parameter scope.

The variable is passed as a parameter to render_tabs($active_tab = 'payments') in stripe/helpers/FrmTransLiteListHelper.php, and when the template is included within that function, it automatically has access to all function-scope variables. The template also defines $tabs locally, so both variables are available.

Likely an incorrect or invalid review comment.

stripe/views/lists/coupons.php (1)

7-23: LGTM! Proper header rendering with extensibility hook.

The admin header is correctly configured with label, form parameter, and publish flag sourced from the frm_coupons_list_button filter for extensibility.

stripe/controllers/FrmTransLiteListsController.php (2)

118-135: LGTM! Clean routing with proper permission checks and extensibility.

The new coupons route handling includes:

  • Proper permission check using frm_change_settings
  • Clean include path construction
  • Early return to prevent further processing
  • Extensible routing via frm_trans_lite_route filter with fallback to display_list()

This follows good patterns for WordPress admin routing and maintains security.


72-74: Clarify the Mode column conditional logic - it differs from addon presence checks elsewhere in code.

The Mode column is added only when:

  1. Action is not 'bulk_delete' AND
  2. FrmTransListHelper does NOT exist AND
  3. FrmPaymentsListHelper exists

This conditional logic is inconsistent with the payments_addon_list_helper_exists() helper method in FrmTransLiteListHelper.php, which checks for addon presence using OR logic: class_exists( 'FrmTransListHelper' ) || class_exists( 'FrmPaymentsListHelper' ). The Mode column's AND condition is significantly narrower and will only appear when the Payments addon is active with a specific class presence pattern. Confirm whether this is intentional for version-specific compatibility or if it should align with the helper method's pattern.

stripe/helpers/FrmTransLiteListHelper.php (10)

183-196: LGTM! Bulk actions properly gated by addon presence.

The get_bulk_actions() method correctly returns a bulk_delete action only when the Payments addon list helper exists, ensuring bulk operations are only available in the appropriate context.


277-286: LGTM! Conditional checkbox column removal for Lite context.

The modified get_column_info() correctly removes the checkbox column when the Payments addon list helper doesn't exist, ensuring bulk action checkboxes only appear when bulk operations are supported.


288-297: LGTM! Clear helper method for detecting addon presence.

The payments_addon_list_helper_exists() method provides a clean abstraction for checking whether the Payments submodule (Stripe, Authorize.Net) or PayPal is active.


376-388: LGTM! Checkbox column correctly implements WP_List_Table pattern.

The get_cb_column() method follows the WordPress list table convention where column methods are called via magic naming (get_{$column}_column). The static analysis warning about it being unused is a false positive.


405-425: LGTM! Centralized link generation improves maintainability.

The refactored get_action_column() and new get_view_payment_link() method centralize URL generation logic, reducing code duplication and making future changes easier to maintain.


427-443: LGTM! Consolidated URL builder with proper escaping.

The get_url_to_payment() method centralizes payment URL generation with consistent parameter handling. The method supports both 'show' and 'edit' actions and properly uses admin_url() and add_query_arg().


450-466: LGTM! Row actions respect edit capability.

The modified get_row_actions() method now conditionally includes the Edit action based on supports_edit_link(), ensuring edit links only appear when the necessary controllers are available.


468-479: LGTM! Edit link support properly scoped.

The supports_edit_link() method correctly determines when edit functionality is available by:

  • Excluding subscriptions (read-only)
  • Checking for the presence of FrmTransAppController or FrmPaymentsController

575-583: LGTM! Defensive null coalescing prevents undefined property warnings.

The null coalescing operator on line 578 ($item->status ?? '') prevents potential undefined property notices when status is not set, improving code robustness.


650-666: Code correctly gates rendering with capability and feature flag integration.

The render_tabs() method implements proper access control through:

  1. frm_change_settings capability check (consistent with codebase usage patterns)
  2. FrmAppHelper::show_new_feature('coupons') feature flag

The $active_tab parameter is correctly extracted for use in the included template (tabs.php, line 22: $is_active = $tab_key === $active_tab;), so the static analysis warning about unused parameters is a false positive. Integration and capability usage follow established patterns throughout the codebase.

Copy link
Copy Markdown
Contributor

@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: 0

♻️ Duplicate comments (1)
stripe/helpers/FrmTransLiteAppHelper.php (1)

599-617: Version placeholders need updating before release.

The @since x.x and @deprecated x.x annotations, as well as the second argument to _deprecated_function(), still contain placeholder version strings.

🧹 Nitpick comments (2)
classes/helpers/FrmAppHelper.php (1)

4623-4657: LGTM! Well-structured method with proper edge case handling.

The new get_upgrade_data_params method is well-implemented:

  • Properly checks for empty $link before proceeding
  • Safely accesses array keys with appropriate conditionals
  • Correctly uses the null coalescing operator on line 4649
  • Handles both Pro and non-Pro scenarios appropriately

Optional: Consider adding type hints for better type safety.

-public static function get_upgrade_data_params( $plugin, $params, $detailed = false ) {
+public static function get_upgrade_data_params( string $plugin, array $params, bool $detailed = false ): array {

This would provide better IDE support and catch type-related issues earlier, though it's not critical for functionality.

stripe/helpers/FrmTransLiteListHelper.php (1)

179-181: Update version placeholders before release.

Multiple @since x.x annotations throughout this file (lines 180, 287, 376, 407, 423, 465, 645) need to be updated to the actual release version number.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fd8ac94 and 398df4f.

📒 Files selected for processing (6)
  • classes/controllers/FrmAppController.php (2 hunks)
  • classes/helpers/FrmAppHelper.php (1 hunks)
  • classes/helpers/FrmFieldsHelper.php (2 hunks)
  • classes/models/FrmField.php (2 hunks)
  • stripe/helpers/FrmTransLiteAppHelper.php (2 hunks)
  • stripe/helpers/FrmTransLiteListHelper.php (8 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • classes/models/FrmField.php
  • classes/controllers/FrmAppController.php
  • classes/helpers/FrmFieldsHelper.php
🧰 Additional context used
🧬 Code graph analysis (1)
stripe/helpers/FrmTransLiteListHelper.php (1)
stripe/helpers/FrmTransLiteAppHelper.php (1)
  • FrmTransLiteAppHelper (6-618)
🪛 PHPMD (2.15.0)
stripe/helpers/FrmTransLiteListHelper.php

382-384: Avoid unused private methods such as 'get_cb_column'. (undefined)

(UnusedPrivateMethod)


651-651: Avoid unused parameters such as '$active_tab'. (undefined)

(UnusedFormalParameter)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: PHP 7.4 tests in WP trunk
  • GitHub Check: PHP 8 tests in WP trunk
  • GitHub Check: Cypress
🔇 Additional comments (9)
classes/helpers/FrmAppHelper.php (1)

4609-4621: LGTM! Clean feature gating for coupons.

The addition of the coupons-specific check is well-implemented. Using class_exists('FrmCouponsAppController') is the appropriate way to gate this feature based on the presence of the Coupons add-on.

stripe/helpers/FrmTransLiteAppHelper.php (1)

78-83: LGTM!

The change from a truthy check to !empty($payment->status) is more defensive and correctly handles edge cases where status might be an empty string or 0.

stripe/helpers/FrmTransLiteListHelper.php (7)

179-192: LGTM!

The bulk actions implementation correctly gates the delete action behind the presence of the payments addon list helper, preventing Lite users from accessing functionality they can't use.


273-293: LGTM with minor note.

The conditional removal of the checkbox column for Lite-only contexts is appropriate. However, note the @since x.x placeholder at line 287 needs to be updated before release.


372-384: Static analysis false positive - method is used via dynamic dispatch.

PHPMD flags get_cb_column() as unused, but this is a false positive. The method is invoked dynamically by get_column_value() (line 305-306) when rendering the cb column, following the same pattern as other column methods like get_receipt_id_column() and get_item_id_column().


401-439: LGTM!

Good refactoring to centralize URL generation with get_url_to_payment() and link rendering with get_view_payment_link(). The proper use of esc_url() and esc_html() ensures safe output.


569-577: LGTM!

The null coalescing operator at line 572 is a good defensive coding practice that prevents potential undefined property warnings.


643-660: Static analysis false positive - $active_tab is used in the included view.

PHPMD flags $active_tab as unused, but this is a false positive. The variable is in scope when tabs.php is included at line 658 and is used by that view to render the active tab state.

The permission check and feature flag gating are appropriate.


464-475: Verify whether the class_exists autoload parameter difference is intentional.

Both supports_edit_link() and payments_submodule_or_paypal_is_active() check for the same controller classes (FrmTransAppController and FrmPaymentsController), but use different autoload parameters: supports_edit_link() uses class_exists(..., false) to check only already-loaded classes, while payments_submodule_or_paypal_is_active() uses the default autoload behavior. Since both methods serve similar gating purposes, this inconsistency could lead to different behavior depending on class loading order. Either align the autoload parameter usage, or document why they differ.

Copy link
Copy Markdown
Contributor

@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: 1

♻️ Duplicate comments (2)
classes/models/FrmField.php (1)

363-363: Update the placeholder release date before merging.

The release date '2026-01-13' is still a placeholder in the future. This will cause the "NEW" badge to display on the coupon field until April 13, 2026 (90 days after the release date). This should be updated to the actual release date when the feature ships.

Note: A previous reviewer flagged the same issue with date '2026-01-01'.

stripe/helpers/FrmTransLiteAppHelper.php (1)

605-613: Verify migration from deprecated function.

This deprecation represents a significant behavioral change—the function previously contained complex fallback logic and now simply returns false. Confirm that all call sites have been updated to use payments_submodule_or_paypal_is_active() or otherwise no longer rely on the old behavior.

Note: The version placeholder issue (x.x) has already been flagged in a previous review.

#!/bin/bash
# Description: Find all usages of the deprecated should_fallback_to_paypal function.

# Search for calls to the deprecated function
echo "=== Calls to deprecated should_fallback_to_paypal ==="
rg -nP '\bshould_fallback_to_paypal\s*\(' --type=php -C3

# Check for any conditional logic based on this function
echo -e "\n=== Conditional logic using should_fallback_to_paypal ==="
rg -nP 'if\s*\([^)]*should_fallback_to_paypal' --type=php -C2
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 398df4f and 877de18.

📒 Files selected for processing (5)
  • classes/controllers/FrmAppController.php (2 hunks)
  • classes/helpers/FrmAppHelper.php (1 hunks)
  • classes/helpers/FrmFieldsHelper.php (2 hunks)
  • classes/models/FrmField.php (2 hunks)
  • stripe/helpers/FrmTransLiteAppHelper.php (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • classes/controllers/FrmAppController.php
  • classes/helpers/FrmFieldsHelper.php
🧰 Additional context used
🧬 Code graph analysis (1)
classes/models/FrmField.php (1)
classes/helpers/FrmAppHelper.php (2)
  • FrmAppHelper (6-5054)
  • show_new_feature (4608-4616)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Psalm
  • GitHub Check: PHP 7.4 tests in WP trunk
  • GitHub Check: PHP 8 tests in WP trunk
🔇 Additional comments (4)
classes/helpers/FrmAppHelper.php (2)

4610-4615: LGTM!

The feature gating logic is well-structured. The dual check approach (install link status/class OR coupons-specific controller check) provides appropriate flexibility for different addon states.


4629-4652: LGTM!

The upgrade data enhancement logic is solid. The method properly handles both Pro-installed and Lite scenarios, embedding appropriate data for each case.

classes/models/FrmField.php (1)

331-340: LGTM!

The coupon field configuration follows the established pattern for pro fields. The feature gating via show_new_feature('coupons'), placement in the 'pricing' section, and limit of 1 per form are all appropriate for this field type.

stripe/helpers/FrmTransLiteAppHelper.php (1)

78-78: LGTM: More defensive status check.

The change from if ( $payment->status ) to if ( ! empty( $payment->status ) ) is a better defensive check that explicitly handles edge cases like empty strings, null, and false values more consistently.

Comment thread stripe/helpers/FrmTransLiteAppHelper.php
Copy link
Copy Markdown
Contributor

@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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
classes/helpers/FrmAppHelper.php (1)

4629-4652: Strengthen type checking for $link variable.

The method checks if ( ! $link ) at line 4632 but then assumes $link is an array in multiple places (lines 4636, 4644, 4648) without explicit type verification. If install_link() returns a truthy non-array value (e.g., a string or integer), accessing array keys will cause errors.

Additionally, json_encode() at line 4637 can return false on failure, but the code doesn't handle this case.

🔎 Suggested fix
 public static function get_upgrade_data_params( $plugin, $params, $detailed = false ) {
 	$link = FrmAddonsController::install_link( $plugin );
 
-	if ( ! $link ) {
+	if ( ! $link || ! is_array( $link ) ) {
 		return $params;
 	}
 
 	if ( ! empty( $link['url'] ) && self::pro_is_installed() ) {
-		$params['oneclick'] = json_encode( $link );
+		$encoded = json_encode( $link );
+		if ( false !== $encoded ) {
+			$params['oneclick'] = $encoded;
+		}
 		unset( $params['message'] );
 
 		if ( ! isset( $params['medium'] ) ) {
 			$params['medium'] = $plugin;
 		}
 	} else {
 		$params['requires'] = $params['requires'] ?? FrmFormsHelper::get_plan_required( $link );
 	}
 
 	if ( $detailed ) {
 		$params['plugin-status'] = $link['status'] ?? '';
 	}
 
 	return $params;
 }
🧹 Nitpick comments (1)
stripe/helpers/FrmTransLiteListHelper.php (1)

423-439: Add a fallback for the page parameter.

If FrmAppHelper::simple_get('page') returns an empty value when the page parameter is not set, the generated URL could be malformed. Consider adding a fallback to 'formidable-payments' to ensure URLs are always valid.

🔎 Suggested improvement
 private function get_url_to_payment( $payment_id, $action = 'show' ) {
+	$page = FrmAppHelper::simple_get( 'page' );
+	if ( empty( $page ) ) {
+		$page = 'formidable-payments';
+	}
+
 	return add_query_arg(
 		array(
 			'action' => $action,
 			'id'     => $payment_id,
 			'type'   => $this->table,
-			'page'   => FrmAppHelper::simple_get( 'page' ),
+			'page'   => $page,
 		),
 		admin_url( 'admin.php' )
 	);
 }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 79792cc and f065c14.

📒 Files selected for processing (3)
  • classes/helpers/FrmAppHelper.php
  • classes/models/FrmField.php
  • stripe/helpers/FrmTransLiteListHelper.php
🚧 Files skipped from review as they are similar to previous changes (1)
  • classes/models/FrmField.php
🧰 Additional context used
🧬 Code graph analysis (2)
stripe/helpers/FrmTransLiteListHelper.php (2)
classes/helpers/FrmListHelper.php (2)
  • get_bulk_actions (356-358)
  • row_actions (500-520)
stripe/helpers/FrmTransLiteAppHelper.php (2)
  • FrmTransLiteAppHelper (6-614)
  • plugin_path (11-13)
classes/helpers/FrmAppHelper.php (2)
classes/controllers/FrmSMTPController.php (1)
  • link (82-85)
js/src/admin/admin.js (2)
  • link (5612-5613)
  • link (9439-9439)
🪛 PHPMD (2.15.0)
stripe/helpers/FrmTransLiteListHelper.php

382-384: Avoid unused private methods such as 'get_cb_column'. (undefined)

(UnusedPrivateMethod)


650-650: Avoid unused parameters such as '$active_tab'. (undefined)

(UnusedFormalParameter)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Cypress
  • GitHub Check: PHP 8 tests in WP trunk
  • GitHub Check: PHP 7.4 tests in WP trunk
🔇 Additional comments (6)
stripe/helpers/FrmTransLiteListHelper.php (6)

179-192: LGTM! Clean feature-gating logic.

The bulk actions are appropriately restricted to when the Payments addon is active, and the helper method uses class existence checks to detect the addon. This is consistent with the conditional removal of the checkbox column in get_column_info().

Also applies to: 284-293


372-384: LGTM! Static analysis warning is a false positive.

The get_cb_column() method follows the WordPress list table pattern where column methods are called dynamically based on the column name (e.g., get_{column_name}_column()). When the 'cb' column is present in the columns array (which happens when the Payments addon exists), this method will be invoked automatically by the parent class's column rendering system.


401-421: LGTM! Clean refactoring.

The extraction of link generation logic into get_view_payment_link() and the centralization of URL building in get_url_to_payment() improves code organization and maintainability. Proper escaping is in place.


454-457: LGTM! Well-structured edit link visibility logic.

The supports_edit_link() method appropriately gates the edit functionality based on the table type (no edit for subscriptions) and the availability of required controller classes. Using class_exists(..., false) is good for performance as it avoids unnecessary autoloading.

Also applies to: 464-475


568-576: LGTM! Good defensive coding.

The null coalescing operator (??) prevents potential undefined property warnings for $item->status. This is a good defensive programming practice.


643-659: The $active_tab parameter is actively used in the template at line 22 ($is_active = $tab_key === $active_tab), where it determines which tab should be highlighted as active. No changes needed.

Likely an incorrect or invalid review comment.

Comment thread classes/helpers/FrmAppHelper.php
Copy link
Copy Markdown
Contributor

@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: 0

🧹 Nitpick comments (1)
stripe/helpers/FrmTransLiteListHelper.php (1)

647-663: Consider clarifying the $active_tab parameter usage.

The $active_tab parameter is declared but never used within the method body and is not passed to the included view file. Consider one of the following:

  • If the view file needs this variable, make it available via compact() or extract before the include
  • If the view determines the active tab from request parameters, remove the unused parameter
  • If this is for future extensibility, add a comment explaining the intent
Option 1: Pass to view if needed
 	public static function render_tabs( $active_tab = 'payments' ) {
 		if ( ! current_user_can( 'frm_change_settings' ) ) {
 			// Coupons are only available to people who can access global settings.
 			return;
 		}
 
 		if ( FrmAppHelper::show_new_feature( 'coupons' ) ) {
+			// Make $active_tab available to the view.
 			include FrmTransLiteAppHelper::plugin_path() . '/views/lists/tabs.php';
 		}
 	}

Note: Variables in the current scope are automatically available to included files, so no explicit passing is needed unless you want to be more explicit about the contract.

Option 2: Remove if truly unused
-	public static function render_tabs( $active_tab = 'payments' ) {
+	public static function render_tabs() {
 		if ( ! current_user_can( 'frm_change_settings' ) ) {
 			// Coupons are only available to people who can access global settings.
 			return;
 		}
 
 		if ( FrmAppHelper::show_new_feature( 'coupons' ) ) {
 			include FrmTransLiteAppHelper::plugin_path() . '/views/lists/tabs.php';
 		}
 	}
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f065c14 and 8950567.

📒 Files selected for processing (1)
  • stripe/helpers/FrmTransLiteListHelper.php
🧰 Additional context used
🧬 Code graph analysis (1)
stripe/helpers/FrmTransLiteListHelper.php (1)
stripe/helpers/FrmTransLiteAppHelper.php (3)
  • FrmTransLiteAppHelper (6-614)
  • get_gateways (517-519)
  • plugin_path (11-13)
🪛 PHPMD (2.15.0)
stripe/helpers/FrmTransLiteListHelper.php

383-385: Avoid unused private methods such as 'get_cb_column'. (undefined)

(UnusedPrivateMethod)


654-654: Avoid unused parameters such as '$active_tab'. (undefined)

(UnusedFormalParameter)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: PHP 8 tests in WP trunk
  • GitHub Check: Cypress
  • GitHub Check: PHP 7.4 tests in WP trunk
🔇 Additional comments (11)
stripe/helpers/FrmTransLiteListHelper.php (11)

179-192: LGTM! Clean conditional bulk action implementation.

The bulk delete action is correctly gated behind the presence of the full Payments addon, preventing Lite users from accessing premium functionality.


228-228: LGTM! Gateway data properly initialized and passed to columns.

The gateways array is correctly retrieved and included in the arguments for column rendering, enabling proper processor label display.

Also applies to: 233-233


274-283: LGTM! Proper conditional removal of bulk action checkboxes.

The checkbox column is correctly hidden in Lite-only installations where bulk actions are unavailable, maintaining UI consistency.


285-294: LGTM! Clean abstraction for addon detection.

The helper method properly encapsulates the logic for detecting whether the full Payments addon is active, making the code more maintainable and readable.


373-385: LGTM! Checkbox column correctly implemented.

The method follows the established naming convention (get_{column_name}_column) and is invoked dynamically at line 304. The PHPMD warning is a false positive due to the dynamic method resolution pattern.


402-405: LGTM! Good separation of concerns.

The refactoring properly delegates link generation to a dedicated method, improving code organization and reusability.


407-422: LGTM! Secure link generation with proper escaping.

The method correctly generates view links with proper URL escaping and attribute handling.


424-440: LGTM! Excellent URL generation centralization.

The helper method consolidates URL building logic, eliminating duplication and making the codebase more maintainable.


455-455: LGTM! Appropriate edit link gating.

The method correctly restricts edit functionality to non-subscription payments when the full addon is active, using proper class existence checks.

Also applies to: 465-476


572-572: LGTM! Good defensive programming with null coalescing.

The null coalescing operator properly guards against undefined status properties, preventing potential PHP notices.


619-630: LGTM! Processor column properly handles multiple gateway sources.

The implementation correctly prioritizes gateway labels from the registry, with appropriate fallbacks. Note the comment at lines 625-627 indicating the PayPal special case is temporary technical debt planned for future cleanup.

Copy link
Copy Markdown
Contributor

@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: 0

♻️ Duplicate comments (5)
classes/helpers/FrmFieldsHelper.php (1)

2429-2431: Fix @since x.x placeholder and keep limit logic

The new update_params_with_limit_data() helper correctly adds frm_at_limit and a data-limit attribute based on a per‑form field count, and the call site only applies it to non‑upsell fields with a limit set, which matches the intended behavior for things like coupons. However, the docblock still uses the placeholder @since x.x; please replace this with the actual release version before shipping.

Also applies to: 2466-2495

classes/helpers/FrmAppHelper.php (1)

4608-4615: Add is_array guard around array_key_exists in show_new_feature()

FrmAddonsController::install_link( $feature ) may return a non‑array value; calling array_key_exists() on that in PHP 8+ can throw a TypeError. Wrap the key checks in an is_array( $link ) condition while keeping the new coupons fallback:

Proposed fix
 public static function show_new_feature( $feature ) {
 	$link = FrmAddonsController::install_link( $feature );
 
-	if ( array_key_exists( 'status', $link ) || array_key_exists( 'class', $link ) ) {
+	if ( is_array( $link ) && ( array_key_exists( 'status', $link ) || array_key_exists( 'class', $link ) ) ) {
 		return true;
 	}
 
 	return 'coupons' === $feature && class_exists( 'FrmCouponsAppController' );
 }
stripe/helpers/FrmTransLiteAppHelper.php (2)

596-603: Previous review comments still apply.

The version placeholder and inconsistent usage issues flagged in the previous review remain unresolved. Please address those concerns before merging.


605-613: Verify impact of deprecated function returning false.

The function is correctly deprecated using WordPress patterns. However, since this previously had logic (per the AI summary) and now always returns false, ensure all callers handle this behavior change appropriately.

Run the following script to identify any remaining callers:

#!/bin/bash
# Description: Find all calls to should_fallback_to_paypal to verify they handle the new behavior.

# Search for calls to should_fallback_to_paypal
echo "=== Calls to should_fallback_to_paypal ==="
rg -nP '\bshould_fallback_to_paypal\s*\(' --type=php -C3

# Search for any conditional logic that might depend on this function
echo -e "\n=== Conditional checks using should_fallback_to_paypal ==="
rg -nP 'if\s*\([^)]*should_fallback_to_paypal' --type=php -C2
js/src/admin/admin.js (1)

909-924: Drag-start limit guard still only checks the direct target

handleDragStart still relies on event.target.classList.contains('frm_at_limit'), so starting a drag by mousing down on a child element inside a limited button (icon, span, etc.) will bypass the limit and permit dragging. Using a closest('.frm_at_limit') check is more robust and aligns with how the buttons are structured.

Suggested update for the drag-start guard
 function handleDragStart( event, ui ) {
-		if ( event.target.classList.contains( 'frm_at_limit' ) ) {
+		const atLimitTarget = event.target.closest && event.target.closest( '.frm_at_limit' );
+		if ( atLimitTarget ) {
 			showLimitModal();
 			return false;
 		}
🧹 Nitpick comments (3)
stripe/views/payments/show.php (1)

60-64: Null‑safety and feature‑gated “Edit Payment” link are appropriate

Conditionally calling show_in_table() only when invoice_id is set avoids notices while still relying on its internal empty check, and wrapping the Edit Payment action in FrmTransLiteAppHelper::payments_submodule_or_paypal_is_active() ensures the edit UI only appears when the payments submodule or PayPal add‑on is available; if you want extra hardening, consider wrapping $payment->id in absint() in the edit URL, but it’s not strictly required here.

Also applies to: 159-170

js/src/admin/admin.js (2)

1916-1938: Repeater drop rules and hook look good; align JSDoc and version tag

The new repeater logic cleanly:

  • Blocks GDPR fields in repeaters.
  • Exposes a frm_deny_drop_in_repeater filter for other field types.
  • Skips the field‑group capacity check only when the droppable is the repeater container itself.

Behaviorally this is solid. Two small cleanups:

  • Fix the JSDoc alignment that ESLint is flagging on this block.
  • Replace @since x.x with the actual version before release so docs stay accurate.

2182-2241: Field-type limit UI flow (click, drag, add, delete) is coherent; only minor nits

Overall the field-type limiting behavior hangs together well:

  • addFieldClick now checks $button.hasClass('frm_at_limit'), shows the limit modal, and returns false, correctly preventing the click’s default navigation.
  • showLimitModal() sensibly reuses the existing confirm-modal pipeline and hides the confirm button, while confirmModal() now always re‑shows it for non‑limit confirmations.
  • afterAddField calls maybeDisableFieldButtonAtLimit(type), and deleteField calls maybeEnableFieldButtonAtLimit($thisField.data('type')), keeping the palette button’s .frm_at_limit state synced with the actual field count via countFieldTypeInForm.

A couple of small polish suggestions:

  • Consider explicitly coercing button.dataset.limit to a number in maybeDisableFieldButtonAtLimit / maybeEnableFieldButtonAtLimit (e.g., const limit = Number( button.dataset.limit );) to make the numeric comparison intent clearer.
  • Update the @since x.x tags on maybeDisableFieldButtonAtLimit / maybeEnableFieldButtonAtLimit to the real version before shipping.

Functionally this looks good to ship as-is; the above are minor clarity/maintenance improvements.

Also applies to: 2857-2882, 5247-5276

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8950567 and a7b6c73.

⛔ Files ignored due to path filters (1)
  • images/icons.svg is excluded by !**/*.svg, !**/*.svg
📒 Files selected for processing (13)
  • bin/zip-plugin.sh
  • classes/controllers/FrmAppController.php
  • classes/helpers/FrmAppHelper.php
  • classes/helpers/FrmFieldsHelper.php
  • classes/models/FrmField.php
  • css/frm_admin.css
  • js/formidable_admin.js
  • js/src/admin/admin.js
  • stripe/controllers/FrmStrpLiteAppController.php
  • stripe/helpers/FrmTransLiteAppHelper.php
  • stripe/helpers/FrmTransLiteListHelper.php
  • stripe/views/payments/show.php
  • stripe/views/payments/sidebar_actions.php
🚧 Files skipped from review as they are similar to previous changes (1)
  • classes/controllers/FrmAppController.php
🧰 Additional context used
🧬 Code graph analysis (5)
stripe/controllers/FrmStrpLiteAppController.php (2)
classes/helpers/FrmAppHelper.php (2)
  • FrmAppHelper (6-5054)
  • is_admin_page (495-509)
stripe/helpers/FrmTransLiteAppHelper.php (2)
  • FrmTransLiteAppHelper (6-614)
  • payments_table_exists (39-56)
stripe/views/payments/show.php (1)
stripe/helpers/FrmTransLiteAppHelper.php (3)
  • FrmTransLiteAppHelper (6-614)
  • show_in_table (406-416)
  • payments_submodule_or_paypal_is_active (601-603)
classes/models/FrmField.php (1)
classes/helpers/FrmAppHelper.php (2)
  • FrmAppHelper (6-5054)
  • show_new_feature (4608-4616)
stripe/views/payments/sidebar_actions.php (1)
stripe/controllers/FrmTransLitePaymentsController.php (2)
  • FrmTransLitePaymentsController (6-254)
  • show_refund_link (123-126)
classes/helpers/FrmFieldsHelper.php (1)
classes/models/FrmDb.php (2)
  • FrmDb (6-867)
  • get_count (231-235)
🪛 GitHub Check: Run ESLint
js/src/admin/admin.js

[warning] 1925-1925:
Expected JSDoc block lines to be aligned

🪛 PHPMD (2.15.0)
stripe/helpers/FrmTransLiteListHelper.php

383-385: Avoid unused private methods such as 'get_cb_column'. (undefined)

(UnusedPrivateMethod)


654-654: Avoid unused parameters such as '$active_tab'. (undefined)

(UnusedFormalParameter)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: PHP 8 tests in WP 6.9
  • GitHub Check: PHP 7.4 tests in WP 6.9
  • GitHub Check: Cypress
🔇 Additional comments (19)
bin/zip-plugin.sh (1)

106-107: The exclusions for formidable-coupons/js/frontend.js and admin.js are pre-configured patterns for external add-on plugins, consistent with how other add-ons (formidable-views, formidable-chat, formidable-api, formidable-hubspot) are handled. These directories do not exist in the repository and only appear in the packaging script as part of the standard build configuration. No verification is needed.

Likely an incorrect or invalid review comment.

stripe/controllers/FrmStrpLiteAppController.php (1)

66-77: Redirect guards look correct and safer

The new early-return checks for the payments admin page, PayPal add‑on presence, and existing payments table cleanly separate concerns and avoid unnecessary redirects; no issues spotted here.

classes/models/FrmField.php (1)

331-340: Coupon field registration and “new” flag wiring look consistent

The Coupon pro field entry is correctly gated via FrmAppHelper::show_new_feature( 'coupons' ), placed in the pricing section, limited to one per form, and hooked into field_is_new( 'coupon' ) with a concrete release date; this matches the intended UI behavior with no obvious issues.

Also applies to: 361-364

stripe/views/payments/sidebar_actions.php (1)

20-26: Stricter payment display guard and new sidebar hook look good

Guarding on isset( $payment->status ), checking for 'complete', and requiring a non‑empty receipt_id before rendering the refund section should prevent notices and avoid showing refund UI when it’s not actionable; the new frm_pay_{$payment->paysys}_sidebar hook also provides a clean extension point assuming $payment->paysys is always set in this view.

Also applies to: 28-30

stripe/helpers/FrmTransLiteAppHelper.php (1)

78-78: LGTM!

The change from a truthy check to !empty() is a good defensive programming practice. This ensures that falsy but defined values (empty string, 0, etc.) correctly trigger the PayPal fallback logic.

js/src/admin/admin.js (1)

323-337: Confirm modal reset for reused button looks correct

Re‑showing continueButton on every confirmModal open guards against the hidden state left by showLimitModal, without changing other behavior. This is a clean, localized fix and looks good.

stripe/helpers/FrmTransLiteListHelper.php (13)

179-192: LGTM! Clean bulk actions implementation.

The conditional bulk delete action based on payment addon presence is well-structured and follows WordPress list table conventions.


228-228: LGTM! Efficient gateway data preparation.

Retrieving gateways once and passing them to all rows is a good performance optimization that enables proper gateway label display.

Also applies to: 233-233


274-283: LGTM! Checkbox column properly gated.

The conditional removal of the checkbox column for Lite-only installations is consistent with the bulk actions availability and well-implemented.


285-294: LGTM! Clear addon detection helper.

The method provides a clean abstraction for detecting payment addon presence with a helpful docblock explaining the specific addons checked.


373-385: LGTM! Checkbox column properly implemented.

The static analysis warning about this method being unused is a false positive. The method is called dynamically via get_column_value() which constructs the method name from the column name (line 304: 'get_' . $column_name . '_column'). This follows the established pattern used by other column methods in this class.


402-405: LGTM! Clean action column refactor.

Extracting link generation to get_view_payment_link() improves maintainability and centralizes URL building logic.


407-422: LGTM! Well-structured link builder.

The method properly handles URL generation, escaping, and HTML attribute building with good separation of concerns.


424-440: LGTM! Centralized URL generation.

This helper effectively centralizes payment URL building using standard WordPress functions and improves maintainability across the class.


455-455: LGTM! Improved readability.

Extracting the edit link condition to supports_edit_link() improves code clarity and maintainability.


465-476: LGTM! Appropriate edit link gating.

The logic correctly excludes subscriptions and requires payment controller presence. Good use of class_exists() with autoload=false for performance.


572-572: LGTM! Defensive null-coalescing.

The null-coalescing operator guards against potential undefined status property, preventing PHP notices.


619-630: LGTM! Gateway label lookup with documented fallback.

The implementation properly uses the gateways array for label lookup. The PayPal special case at lines 623-627 is well-documented as temporary and can be removed once PayPal Commerce is integrated into Lite.


647-663: LGTM! Proper tabs rendering with feature gating.

The static analysis warning about the unused $active_tab parameter is a false positive. When PHP includes a file (line 661), all variables in the current scope—including $active_tab—are available to the included template. The capability and feature flag checks properly gate the coupons functionality.

Copy link
Copy Markdown
Contributor

@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: 0

♻️ Duplicate comments (1)
js/src/admin/admin.js (1)

909-912: Drag-start limit guard can be bypassed via child elements

Checking only event.target.classList.contains('frm_at_limit') misses drags that begin on child elements inside the limited button (e.g. SVG, span). In those cases the field can still be dragged, effectively bypassing the type limit.

Consider resolving this by walking up from the event target to a parent with the limit class, and guarding against non-Element targets:

-	function handleDragStart( event, ui ) {
-		if ( event.target.classList.contains( 'frm_at_limit' ) ) {
-			showLimitModal();
-			return false;
-		}
+	function handleDragStart( event, ui ) {
+		const rawTarget = event.target;
+		const elementTarget = rawTarget && rawTarget.closest ? rawTarget : null;
+		const limitButton = elementTarget ? elementTarget.closest( '.frmbutton.frm_at_limit' ) : null;
+
+		if ( limitButton ) {
+			showLimitModal();
+			return false;
+		}
🧹 Nitpick comments (2)
js/src/admin/admin.js (2)

1915-1936: Repeater/GDPR drop restriction logic is sound; tidy up JSDoc

The new repeater-aware logic (droppableIsARepeater / droppableIsInsideOfARepeater) correctly:

  • Blocks GDPR fields (edit_field_type_gdpr and #gdpr) from being dropped into or inside repeaters.
  • Exposes a frm_deny_drop_in_repeater filter for additional denial rules without affecting non-repeater drops.

Two small follow‑ups:

  • The JSDoc block above frm_deny_drop_in_repeater is flagged by ESLint for alignment; adjust the leading * spacing to match the surrounding JSDoc style.
  • Replace @since x.x with the actual version number before release.

2856-2881: Field-type limit bookkeeping is generally correct; ensure type identifiers stay consistent

The new helpers and calls behave coherently:

  • afterAddFieldmaybeDisableFieldButtonAtLimit(type) marks the palette button as frm_at_limit when the count of li.form-field[data-ftype="type"] reaches button.dataset.limit.
  • On delete, deleteField calls maybeEnableFieldButtonAtLimit( $thisField.data('type') ); subtracting 1 from countFieldTypeInForm(type) compensates for the soon‑to‑be-removed field so the limit is only lifted when the post‑delete count is below the limit.
  • countFieldTypeInForm scopes the count to #frm-show-fields, avoiding side effects elsewhere.

One thing to keep in mind going forward: this relies on the inserted field’s data-type matching both the palette button’s id and the field’s data-ftype. If any field type diverges from that convention, its limit won’t be enforced correctly; in that case you may want a small mapping helper rather than using type directly.

Also applies to: 5246-5275

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bcd4eb5 and 2b8a067.

📒 Files selected for processing (2)
  • js/formidable_admin.js
  • js/src/admin/admin.js
🧰 Additional context used
🧬 Code graph analysis (1)
js/src/admin/admin.js (2)
js/admin/style.js (1)
  • wp (13-13)
js/src/form/modal.js (1)
  • wp (5-5)
🪛 GitHub Check: Run ESLint
js/src/admin/admin.js

[warning] 1924-1924:
Expected JSDoc block lines to be aligned

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Cypress
  • GitHub Check: PHP 7.4 tests in WP 6.9
  • GitHub Check: PHP 8 tests in WP 6.9
🔇 Additional comments (2)
js/src/admin/admin.js (2)

332-336: Confirm modal button visibility reset looks correct

Ensuring #frm-confirmed-click is forced visible on every confirmModal open guards against state leaked from showLimitModal, and is a safe, localized change.


2192-2200: Field-type limit handling and showLimitModal wiring look consistent

  • In addFieldClick, short‑circuiting when $button.hasClass('frm_at_limit') and returning false is appropriate for jQuery click handlers and cleanly prevents insertion + shows the limit modal.
  • showLimitModal() reuses the existing confirm modal flow via a temporary <a data-frmverify>…</a>, then hides the confirm button so the dialog is informational-only, which matches the intended UX.
  • The new comment clarifying why shouldStopInsertingField must not return false on click helps avoid regressions around slider fields.

No functional issues spotted in this block.

Also applies to: 2242-2259

@Crabcyborg Crabcyborg merged commit 839a45b into master Jan 5, 2026
16 checks passed
@Crabcyborg Crabcyborg deleted the coupons branch January 5, 2026 13:39
@coderabbitai coderabbitai Bot mentioned this pull request Jan 29, 2026
stephywells pushed a commit that referenced this pull request Apr 4, 2026
Use Lite for the payments pages and add coupons tabs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant