Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
b2f1a18
Start working on using Lite for the Stripe add-on
Crabcyborg Oct 28, 2025
d246952
Fix issue with new page routing
Crabcyborg Oct 28, 2025
9ddabbd
Add the bottom border below the tabs
Crabcyborg Oct 28, 2025
4695dec
Add a new hook and change the coupons URL
Crabcyborg Oct 28, 2025
e49d998
Make route handling more flexible
Crabcyborg Oct 28, 2025
304762d
Improve tabs logic
Crabcyborg Oct 28, 2025
e9c69f3
Add more filters
Crabcyborg Oct 28, 2025
ddbe5c7
Add a coupon field
Crabcyborg Oct 28, 2025
8952467
Add a trans lite route handled hook
Crabcyborg Oct 29, 2025
ebeb13f
Use payments as the page title
Crabcyborg Oct 29, 2025
20b07ac
Merge branch 'master' into coupons
Crabcyborg Oct 30, 2025
3e4a640
Merge branch 'master' into coupons
Crabcyborg Nov 4, 2025
b1a592a
Update the coupon field icon
Crabcyborg Nov 7, 2025
b436b30
Add support for a limit attribute for field selection
Crabcyborg Nov 7, 2025
f4fff05
Line up vars better
Crabcyborg Nov 7, 2025
c04122c
Translate the admin string
Crabcyborg Nov 7, 2025
f10a6e1
Show coupon field as new
Crabcyborg Nov 7, 2025
886dca1
Add new clipboard icon
Crabcyborg Nov 7, 2025
b4947ab
Add the new tab icon
Crabcyborg Nov 10, 2025
50e94d4
Build js
Crabcyborg Nov 10, 2025
598c5ad
Working on adding upsells for coupons
Crabcyborg Nov 13, 2025
68ad201
Add the conditional action button to the upsell page
Crabcyborg Nov 13, 2025
abcb3c5
Use h2
Crabcyborg Nov 13, 2025
895760a
Fix coupon field icon size issues in views customize sidebar
Crabcyborg Nov 14, 2025
60ef728
Merge branch 'master' into coupons
Crabcyborg Nov 14, 2025
694baa2
Prevent dragging gdpr into field group in repeater, and add a new fil…
Crabcyborg Nov 14, 2025
08c0525
Commit compiled Js
Crabcyborg Nov 14, 2025
2bb57f8
Add the cb column
Crabcyborg Nov 14, 2025
6a6b515
Fallback to payments submodule for bulk deleting payments
Crabcyborg Nov 14, 2025
45446a5
Fix fatal error issue
Crabcyborg Nov 14, 2025
87b1050
Remove PayPal fallback logic
Crabcyborg Nov 14, 2025
18b141a
Improve compatibility with PayPal
Crabcyborg Nov 14, 2025
353e945
Improve compatibility with PayPal
Crabcyborg Nov 14, 2025
4b4dc3e
Improve compatibility with PayPal
Crabcyborg Nov 14, 2025
fcd7f74
Add PayPal check before excluding the mode column
Crabcyborg Nov 14, 2025
19c59cf
Add comments
Crabcyborg Nov 14, 2025
e18d51f
Add an edit link in the payments sidebar
Crabcyborg Nov 14, 2025
d04f7fb
Get capture a Stripe payment working
Crabcyborg Nov 14, 2025
d98cf62
Always call add_list_hooks
Crabcyborg Nov 14, 2025
233b1d1
Merge branch 'master' into coupons
Crabcyborg Nov 17, 2025
877866f
Merge branch 'master' into coupons
Crabcyborg Nov 19, 2025
2615511
Update compiled files
Crabcyborg Nov 19, 2025
1317164
Merge branch 'master' into coupons
Crabcyborg Nov 20, 2025
9dda552
Exclude unminified coupon scripts from zip file
Crabcyborg Nov 20, 2025
c3e13b8
Merge branch 'master' into coupons
Crabcyborg Dec 1, 2025
d241d00
Enforce permissions
Crabcyborg Dec 1, 2025
858eebc
Remove unused function
Crabcyborg Dec 1, 2025
66ccde0
Return false like other logic in function does
Crabcyborg Dec 1, 2025
7f5c5a5
Merge branch 'master' into coupons
Crabcyborg Dec 16, 2025
f87e194
Set the coupons release date
Crabcyborg Dec 16, 2025
9876586
Fix a bug with adding slider fields
Crabcyborg Dec 17, 2025
fd8ac94
Run phpcbf and php cs fixer
Crabcyborg Dec 17, 2025
398df4f
Merge branch 'master' into coupons
Crabcyborg Dec 17, 2025
a3e0256
Merge branch 'master' into coupons
Crabcyborg Dec 18, 2025
7b79378
Run rector
Crabcyborg Dec 18, 2025
877de18
Clean up
Crabcyborg Dec 18, 2025
27db091
Remove empty scss file
Crabcyborg Dec 18, 2025
8f6138e
Merge branch 'master' into coupons
Crabcyborg Dec 18, 2025
79792cc
Merge branch 'master' into coupons
Crabcyborg Dec 18, 2025
f065c14
Merge branch 'master' into coupons
Crabcyborg Dec 22, 2025
8950567
Pass gateways as an arg and use gateway label like we do in Payments …
Crabcyborg Dec 22, 2025
a7b6c73
Merge branch 'master' into coupons
Crabcyborg Dec 30, 2025
bcd4eb5
Add some function descriptions
Crabcyborg Dec 30, 2025
2b8a067
Merge branch 'master' into coupons
Crabcyborg Dec 30, 2025
72962a1
Merge branch 'master' into coupons
Crabcyborg Jan 2, 2026
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
2 changes: 2 additions & 0 deletions bin/zip-plugin.sh
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ zip -r $zipname $destination \
-x "formidable-api/js/embed.js" \
-x "formidable-api/js/iframe-embed.js" \
-x "formidable-hubspot/js/admin.js" \
-x "formidable-coupons/js/frontend.js" \
-x "formidable-coupons/js/admin.js" \
-x "*/webpack.config.js" \
-x "*.zip" \
-x "*/rector.php" \
Expand Down
27 changes: 18 additions & 9 deletions classes/controllers/FrmAppController.php
Original file line number Diff line number Diff line change
Expand Up @@ -166,15 +166,8 @@ private static function is_white_page() {
FrmOnboardingWizardController::PAGE_SLUG,
);

if ( ! class_exists( 'FrmTransHooksController', false ) && ! FrmTransLiteAppHelper::should_fallback_to_paypal() ) {
// Only consider the payments page as a "white page" when the Payments submodule is off.
// Otherwise this causes a lot of styling issues when the Stripe add-on (or Authorize.Net) is active.

// Add an extra check to avoid white page styling on the PayPal "edit" action.
// We fallback to the PayPal add on for the "edit" action since Stripe Lite does not have an edit view.
if ( ! in_array( FrmAppHelper::simple_get( 'action' ), array( 'edit', 'new' ), true ) || ! is_callable( 'FrmPaymentsController::route' ) ) {
$white_pages[] = 'formidable-payments';
}
if ( self::is_white_payments_page() ) {
$white_pages[] = 'formidable-payments';
}

$is_white_page = self::is_page_in_list( $white_pages ) || self::is_grey_page() || FrmAppHelper::is_view_builder_page();
Expand All @@ -191,6 +184,22 @@ private static function is_white_page() {
return $is_white_page;
}

/**
* Check if the payments page should be styled as a white page.
* Fallback to the Stripe, Authorize.Net, or PayPal add on for the "edit" action since
* Stripe Lite does not have an edit view. Also fallback for bulk deleting, since that
* isn't built into Lite. The pages we fall back to should not be styled as white pages.
*
* @since x.x
Comment thread
Crabcyborg marked this conversation as resolved.
*
* @return bool
*/
private static function is_white_payments_page() {
$action = FrmAppHelper::simple_get( 'action', 'sanitize_title' );
$on_edit_page = in_array( $action, array( 'edit', 'new' ), true );
return ! $on_edit_page && 'bulk_delete' !== $action;
}

/**
* Add a grey bg instead of white.
*
Expand Down
7 changes: 6 additions & 1 deletion classes/helpers/FrmAppHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -4607,7 +4607,12 @@ public static function get_landing_page_upgrade_data_params( $medium = 'landing'
*/
public static function show_new_feature( $feature ) {
$link = FrmAddonsController::install_link( $feature );
return array_key_exists( 'status', $link ) || array_key_exists( 'class', $link );

if ( array_key_exists( 'status', $link ) || array_key_exists( 'class', $link ) ) {
return true;
}
Comment thread
Crabcyborg marked this conversation as resolved.

return 'coupons' === $feature && class_exists( 'FrmCouponsAppController' );
}

/**
Expand Down
35 changes: 35 additions & 0 deletions classes/helpers/FrmFieldsHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -2426,6 +2426,10 @@ public static function show_add_field_buttons( $args ) {
$li_params['class'] .= ' frm_hidden';
}

if ( ! $show_upgrade && ! empty( $field_type['limit'] ) ) {
$li_params = self::update_params_with_limit_data( $li_params, $args['id'], $field_key, $field_type['limit'] );
}

if ( ! empty( $field_type['upsell_image'] ) ) {
$li_params['data-upsell-image'] = $field_type['upsell_image'];
}
Expand Down Expand Up @@ -2459,6 +2463,37 @@ public static function show_add_field_buttons( $args ) {
<?php
}

/**
* Updates the params with limit data (the data-limit attribute, and possibly the frm_at_limit class).
* Some field types are limited to a certain number per form, including coupon fields.
*
* @since x.x
*
Comment thread
Crabcyborg marked this conversation as resolved.
* @param array $li_params The params.
* @param int $form_id The form ID.
* @param string $field_type The field type.
* @param int $limit The limit.
*
* @return array The updated params.
*/
private static function update_params_with_limit_data( $li_params, $form_id, $field_type, $limit ) {
$fields_in_form = FrmDb::get_count(
'frm_fields',
array(
'form_id' => $form_id,
'type' => $field_type,
)
);

if ( $fields_in_form >= $limit ) {
$li_params['class'] .= ' frm_at_limit';
}

$li_params['data-limit'] = $limit;

return $li_params;
}

/**
* Shows Display format option.
*
Expand Down
13 changes: 12 additions & 1 deletion classes/models/FrmField.php
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,17 @@ public static function pro_field_selection() {
),
);

if ( FrmAppHelper::show_new_feature( 'coupons' ) ) {
$fields['coupon'] = array(
'name' => __( 'Coupon', 'formidable' ),
'icon' => 'frm_icon_font frm_coupon_icon frm_show_upgrade',
'addon' => 'coupons',
'section' => 'pricing',
'limit' => 1,
'is_new' => self::field_is_new( 'coupon' ),
);
}

// Since the signature field may be in a different section, don't show it twice.
$lite_fields = self::field_selection();

Expand All @@ -349,7 +360,7 @@ public static function pro_field_selection() {
*/
private static function field_is_new( $type ) {
$release_dates = array(
'ranking' => '2024-03-12',
'coupon' => '2026-01-13',
);

if ( ! isset( $release_dates[ $type ] ) ) {
Expand Down
4 changes: 2 additions & 2 deletions css/frm_admin.css

Large diffs are not rendered by default.

Binary file added images/coupons/left-upsell.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/coupons/main-upsell.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/coupons/right-upsell.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions images/icons.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion js/formidable_admin.js

Large diffs are not rendered by default.

102 changes: 98 additions & 4 deletions js/src/admin/admin.js
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,12 @@
return false;
}

// The confirm button is hidden when showLimitModal is called,
// so make sure it is visible every time we open the modal.
if ( continueButton ) {
continueButton.style.display = 'block';
}

verify = link.getAttribute( 'data-frmverify' );
btnClass = verify ? link.getAttribute( 'data-frmverify-btn' ) : '';
$confirmMessage = jQuery( '.frm-confirm-msg' );
Expand Down Expand Up @@ -900,6 +906,11 @@
}

function handleDragStart( event, ui ) {
if ( event.target.classList.contains( 'frm_at_limit' ) ) {
showLimitModal();
return false;
}

Comment thread
Crabcyborg marked this conversation as resolved.
dragState.dragging = true;

const container = postBodyContent;
Expand Down Expand Up @@ -1901,12 +1912,28 @@
return ! draggable.parentElement.querySelector( 'li.frm_field_box:not(.edit_field_type_submit)' );
}

if ( droppable.classList.contains( 'start_divider' ) && ( draggable.classList.contains( 'edit_field_type_gdpr' ) || draggable.id === 'gdpr' ) && droppable.closest( '.repeat_section' ) ) {
// Don't allow GDPR fields in repeaters.
return false;
const droppableIsARepeater = droppable.classList.contains( 'start_divider' ) && null !== droppable.closest( '.repeat_section' );
const droppableIsInsideOfARepeater = null !== droppable.closest( '.repeat_section' );

if ( droppableIsARepeater || droppableIsInsideOfARepeater ) {
const isGdpr = draggable.classList.contains( 'edit_field_type_gdpr' ) || draggable.id === 'gdpr';
if ( isGdpr ) {
return false;
}

/**

Check warning on line 1924 in js/src/admin/admin.js

View workflow job for this annotation

GitHub Actions / Run ESLint

Expected JSDoc block lines to be aligned
* @since x.x
*
* @param {boolean} denyDropInRepeater
* @param {HTMLElement} draggable
*/
const shouldDenyDropInRepeater = wp.hooks.applyFilters( 'frm_deny_drop_in_repeater', false, draggable );
if ( shouldDenyDropInRepeater ) {
return false;
}
}

if ( ! droppable.classList.contains( 'start_divider' ) ) {
if ( ! droppableIsARepeater ) {
const $fieldsInRow = getFieldsInRow( jQuery( droppable ) );
if ( ! groupCanFitAnotherField( $fieldsInRow, jQuery( draggable ) ) ) {
// Field group is full and cannot accept another field.
Expand Down Expand Up @@ -2162,7 +2189,15 @@
const $button = $thisObj.closest( '.frmbutton' );
const fieldType = $button.attr( 'id' );

if ( $button.hasClass( 'frm_at_limit' ) ) {
showLimitModal();
return false;
}

Comment thread
Crabcyborg marked this conversation as resolved.
if ( shouldStopInsertingField( fieldType ) ) {
// We do not want to return false here.
// Otherwise it causes issues with trying to add a new slider field
// when clicking the button.
return;
}

Expand Down Expand Up @@ -2204,6 +2239,25 @@
return false;
}

function showLimitModal() {
const wrapper = document.querySelector( '.frm_wrap' );
if ( ! wrapper ) {
return;
}

const temporaryAnchor = document.createElement( 'a' );
temporaryAnchor.setAttribute( 'data-frmverify', __( 'This field type has reached its limit.', 'formidable' ) );

wrapper.appendChild( temporaryAnchor );
temporaryAnchor.click();
wrapper.removeChild( temporaryAnchor );

const confirmButton = document.getElementById( 'frm-confirmed-click' );
if ( confirmButton ) {
confirmButton.style.display = 'none';
}
}

function handleAddFieldClickResponse( msg ) {
document.getElementById( 'frm_form_editor_container' ).classList.add( 'frm-has-fields' );
const replaceWith = wrapFieldLi( msg );
Expand Down Expand Up @@ -2799,6 +2853,8 @@

document.getElementById( 'frm-show-fields' ).classList.remove( 'frm-over-droppable' );

maybeDisableFieldButtonAtLimit( type );

// Bootstrap 5 uses data-bs-toggle instead of data-toggle, and requires that elements have the dropdown-menu class.
field.querySelectorAll( '[data-toggle]' ).forEach( toggle => toggle.setAttribute( 'data-bs-toggle', toggle.getAttribute( 'data-toggle' ) ) );
field.querySelectorAll( '.frm-dropdown-menu' ).forEach( dropdownMenu => dropdownMenu.classList.add( 'dropdown-menu' ) );
Expand All @@ -2811,6 +2867,19 @@
document.dispatchEvent( addedEvent );
}

/**
* @since x.x
*
* @param {string} type
* @return {void}
*/
function maybeDisableFieldButtonAtLimit( type ) {
const button = document.getElementById( type );
if ( button && button.dataset.limit && countFieldTypeInForm( type ) >= button.dataset.limit ) {
button.classList.add( 'frm_at_limit' );
}
}

function updateLastRowFieldsOrder( fieldsOrder ) {
if ( ! fieldsOrder || 'object' !== typeof fieldsOrder ) {
return;
Expand Down Expand Up @@ -5174,12 +5243,37 @@
} );

if ( $thisField.length ) {
maybeEnableFieldButtonAtLimit( $thisField.data( 'type' ) );
wp.hooks.doAction( 'frm_after_delete_field', $thisField[ 0 ] );
}
}
} );
}

/**
* @since x.x
*
* @param {string} type
* @return {void}
*/
function maybeEnableFieldButtonAtLimit( type ) {
const button = document.getElementById( type );

if ( ! button || ! button.dataset.limit ) {
return;
}

// Subtract one because the field has not really been removed from the page yet.
const fieldTypeCount = countFieldTypeInForm( type ) - 1;
if ( fieldTypeCount < button.dataset.limit ) {
button.classList.remove( 'frm_at_limit' );
}
}

function countFieldTypeInForm( type ) {
return document.getElementById( 'frm-show-fields' ).querySelectorAll( 'li.form-field[data-ftype="' + type + '"]' ).length;
}

function addFieldLogicRow() {
/*jshint validthis:true */
const id = jQuery( this ).closest( '.frm-single-settings' ).data( 'fid' ),
Expand Down
43 changes: 43 additions & 0 deletions resources/scss/admin/components/_coupons.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
.frm-coupons-upsell-wrapper {
display: flex;
flex-direction: column;
align-items: center;

p {
margin-top: var(--gap-xs);
max-width: 364px;
text-align: center;
}

img {
max-width: 100%;
max-height: 300px;
}

h2 {
margin-bottom: 0;
}
}

.frm-coupons-upsell {
background-color: #F5F5F7; // This color blends better with the optimized images.
border-radius: 16px;
border: 6px solid #fff;
box-shadow: 0 0.471px 1.412px 0 rgba(16, 24, 40, 0.10), 0 0.471px 0.941px 0 rgba(16, 24, 40, 0.06);
text-align: center;
width: 100%;
box-sizing: border-box;
}

.frm-main-coupons-upsell {
margin-top: var(--gap-lg);

img {
vertical-align: bottom;
}
}

.frm-secondary-coupons-upsells {
margin-top: var(--gap-md);
width: 100%;
}
Loading