Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
90 commits
Select commit Hold shift + click to select a range
0c1e7f4
Add one-time pricing fields modal
truongwp Feb 24, 2026
5fa81a0
Add new pill to pricing fields section
truongwp Feb 24, 2026
7c09d19
Copy product field to Lite
truongwp Feb 26, 2026
89e2dae
Copy quantity and total field to Lite
truongwp Mar 2, 2026
5d94d5b
Fix pricing fields not working
truongwp Mar 6, 2026
6268636
Fix fatal error
truongwp Mar 9, 2026
948977c
Improve info modal
truongwp Mar 9, 2026
8321e20
Merge branch 'master' into rock-enable-pricing-fields
truongwp Mar 9, 2026
ae5e9af
Fix Deepscan
truongwp Mar 9, 2026
eb6ec49
Reuse the info modal for the one time modal
Mar 10, 2026
c48d9fd
Fix workflow errors
truongwp Mar 10, 2026
a508db4
Fix JS error
truongwp Mar 12, 2026
6087e17
Merge branch 'master' into rock-enable-pricing-fields
truongwp Mar 12, 2026
8cb8b61
Fix Deepscan
truongwp Mar 12, 2026
b21d80d
Fix Github checks
truongwp Mar 12, 2026
368664a
Fix PHPStan
truongwp Mar 12, 2026
73b3fe9
Improve product prepare_display_value()
truongwp Mar 13, 2026
631c20e
Merge branch 'master' into rock-enable-pricing-fields
Crabcyborg Mar 16, 2026
982b757
Merge branch 'master' into rock-enable-pricing-fields
truongwp Mar 18, 2026
e27dea8
Merge branch 'rock-enable-pricing-fields' of github.com:Strategy11/fo…
truongwp Mar 18, 2026
c95802c
Merge branch 'master' into rock-enable-pricing-fields
truongwp Mar 18, 2026
452a805
Add back .htaccess file
truongwp Mar 18, 2026
a853a77
Fix the wrong string concat
truongwp Mar 18, 2026
a717cd4
Fix deepsourceio
truongwp Mar 18, 2026
acf03ec
Fix Github check errors
truongwp Mar 18, 2026
4934d8a
Fix pricing fields modal not showing
truongwp Mar 18, 2026
e6540cc
Fix the wrong version of the migration
truongwp Mar 20, 2026
658045b
Fix deepsource and Rector
truongwp Mar 20, 2026
631fb35
Fix eslint
truongwp Mar 20, 2026
cf291a5
Fix eslint
truongwp Mar 20, 2026
03bc614
Fix PHPCS
truongwp Mar 20, 2026
241687c
Fix PHPCS
truongwp Mar 20, 2026
f5a7b52
Fix PHPCS
truongwp Mar 20, 2026
fe4c7fe
Fix Rector
truongwp Mar 20, 2026
8d06983
Fix PHPStan
truongwp Mar 20, 2026
7d237e9
Fix PHPStan
truongwp Mar 20, 2026
aa15197
Fix PHPStan
truongwp Mar 20, 2026
d04fd5a
Fix stylelint
truongwp Mar 20, 2026
6bd0d9d
Fix PHPCS
truongwp Mar 20, 2026
e04cd6b
Fix PHP CS Fixer
truongwp Mar 20, 2026
ab5f74f
Fix PHPStan
truongwp Mar 20, 2026
5de5b51
Fix PHP CS Fixer
truongwp Mar 20, 2026
36fd1ff
Merge branch 'master' into rock-enable-pricing-fields
Crabcyborg Mar 23, 2026
a993291
Merge branch 'master' into rock-enable-pricing-fields
Crabcyborg Mar 23, 2026
9d8cb8d
Merge branch 'master' into rock-enable-pricing-fields
Crabcyborg Mar 23, 2026
4eb7901
Merge branch 'master' into rock-enable-pricing-fields
Crabcyborg Mar 23, 2026
2b94c5c
Add support format=number for product
truongwp Mar 24, 2026
7f5eefc
Update the check for payment gateways
truongwp Mar 24, 2026
e533757
Improve JS code and remove user-defined product code
truongwp Mar 24, 2026
0ebc53a
Improve code
truongwp Mar 24, 2026
e51fc6f
Use less jQuery
truongwp Mar 24, 2026
705aa9b
Fix PHP CS Fixer
truongwp Mar 24, 2026
4707227
Fix Eslint
truongwp Mar 24, 2026
4d3e776
Fix Psalm
truongwp Mar 24, 2026
b158434
Revert the last commit
truongwp Mar 24, 2026
5bd5159
Merge branch 'master' into rock-enable-pricing-fields
Crabcyborg Mar 25, 2026
107cd7e
Move pricing fields above advanced fields
Crabcyborg Mar 25, 2026
b5c2fed
Use early continue
Crabcyborg Mar 25, 2026
bc25ec6
Merge branch 'master' into rock-enable-pricing-fields
Crabcyborg Mar 25, 2026
30f78ae
Apply new sniff update
Crabcyborg Mar 25, 2026
a237162
Use boolean
truongwp Mar 25, 2026
f24ba65
Update JS doc
truongwp Mar 25, 2026
ad6ee31
Merge branch 'master' into rock-enable-pricing-fields
Crabcyborg Mar 25, 2026
35ee4e4
Run new eslint rule (remove redundant parens)
Crabcyborg Mar 25, 2026
a49832e
Fix fields issue when Stripe add-on is active
Crabcyborg Mar 25, 2026
674d213
Remove the new flag from coupon fields since the whole section has one
Crabcyborg Mar 25, 2026
4a1ea7d
Fix JS error
truongwp Mar 26, 2026
2e1cc60
Merge branch 'rock-enable-pricing-fields' of github.com:Strategy11/fo…
truongwp Mar 26, 2026
a5bf62b
Fix the wrong text domain
truongwp Mar 26, 2026
42dbc70
Purify info modal HTML
truongwp Mar 26, 2026
46ee323
Reuse the include_label variable in views
truongwp Mar 26, 2026
88d218c
Reuse the separate-values view
truongwp Mar 26, 2026
3b32e4c
Remove 'cos of'
truongwp Mar 26, 2026
bf3bd75
Update since tags
truongwp Mar 26, 2026
78f55bb
Fix PHPCS
truongwp Mar 26, 2026
da79ad0
Fix PHPStan
truongwp Mar 26, 2026
36dccdb
Merge branch 'master' into rock-enable-pricing-fields
Crabcyborg Mar 26, 2026
2d98651
Add a var comment
Crabcyborg Mar 26, 2026
f0dd805
Merge branch 'master' into rock-enable-pricing-fields
Crabcyborg Mar 26, 2026
8678416
Apply new sniff
Crabcyborg Mar 26, 2026
7a41fb5
Merge branch 'master' into rock-enable-pricing-fields
Crabcyborg Mar 26, 2026
f9925c0
Merge branch 'master' into rock-enable-pricing-fields
Crabcyborg Mar 26, 2026
8988d07
Merge branch 'master' into rock-enable-pricing-fields
Crabcyborg Mar 26, 2026
c7ba9aa
Improve code
truongwp Mar 26, 2026
85a1263
Copy some CSS from Pro
truongwp Mar 26, 2026
071184c
Fix error when comparing with undefined
truongwp Mar 26, 2026
99c5361
Merge branch 'rock-enable-pricing-fields' of github.com:Strategy11/fo…
truongwp Mar 26, 2026
756c32c
Fix PHPCS
truongwp Mar 26, 2026
cedd7e5
Exclude PayPal from message for now
Crabcyborg Mar 27, 2026
ced42b5
Show formatted price for single product
truongwp Mar 27, 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
8 changes: 4 additions & 4 deletions .htaccess
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<IfModule !mod_authz_core.c>
Allow from all
</IfModule>

<IfModule mod_authz_core.c>
Require all granted
</IfModule>
Expand All @@ -27,7 +27,7 @@
<IfModule !mod_authz_core.c>
Allow from all
</IfModule>

<IfModule mod_authz_core.c>
Require all granted
</IfModule>
Expand All @@ -38,7 +38,7 @@
<IfModule !mod_authz_core.c>
Allow from all
</IfModule>

<IfModule mod_authz_core.c>
Require all granted
</IfModule>
Expand All @@ -49,7 +49,7 @@
<IfModule !mod_authz_core.c>
Allow from all
</IfModule>

<IfModule mod_authz_core.c>
Require all granted
</IfModule>
Expand Down
55 changes: 54 additions & 1 deletion classes/controllers/FrmFieldsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,7 @@ public static function change_type( $type ) {
$pro_fields = FrmField::pro_field_selection();
// We want to keep credit_card types as credit card types for Stripe Lite.
// The credit_card key is set for backward compatibility.
unset( $pro_fields['credit_card'] );
FrmField::remove_moved_field_types_from_pro( $pro_fields );

return array_key_exists( $type, $pro_fields ) ? 'text' : $type;
}
Expand Down Expand Up @@ -535,6 +535,7 @@ public static function input_html( $field, $echo = true ) {

self::add_shortcodes_to_html( $field, $add_html );
self::add_pattern_attribute( $field, $add_html );
self::add_currency_field_attributes( $field, $add_html );

$add_html = apply_filters( 'frm_field_extra_html', $add_html, $field );
$add_html = ' ' . implode( ' ', $add_html ) . ' ';
Expand Down Expand Up @@ -1049,6 +1050,58 @@ private static function add_pattern_attribute( $field, array &$add_html ) {
$add_html['pattern'] = 'pattern="' . esc_attr( $format ) . '"';
}

/**
* Add product attributes to fields in multi-paged forms.
*
* @param array $field
* @param array $add_html
*
* @return void
*/
private static function add_currency_field_attributes( $field, &$add_html ) {
$type = $field['original_type'] ?? $field['type'];
$is_product_field = in_array( $type, array( 'total', 'quantity', 'product' ), true );

if ( ! $is_product_field ) {
return;
}

if ( $type === 'total' ) {
$add_html['data-frmtotal'] = 'data-frmtotal';
} elseif ( $type === 'quantity' ) {
$product_field = FrmField::get_option( $field, 'product_field' );
$add_html['data-frmproduct'] = 'data-frmproduct="' . esc_attr( json_encode( $product_field ) ) . '"';
} elseif ( $type === 'product' && 'hidden' === $field['type'] ) {
// We want to do this only for fields that are hidden because it's
// not their page, hence the check : 'hidden' === $field['type'].
$price = empty( $field['value'] ) ? 0 : self::get_product_price( $field );

$add_html['data-frmprice'] = 'data-frmprice="' . esc_attr( $price ) . '"';
}
}

/**
* @param array $field
*/
private static function get_product_price( $field ) {
if ( is_array( $field['value'] ) ) {
// '' is unlikely though, let's just do it to prevent warnings
$value = isset( $field['opt_key'] ) && isset( $field['value'][ $field['opt_key'] ] )
? $field['value'][ $field['opt_key'] ]
: '';
} else {
$value = $field['value'];
}

$field_obj = FrmFieldFactory::get_field_object( $field['id'] );

if ( method_exists( $field_obj, 'get_posted_price' ) ) {
return $field_obj->get_posted_price( $value );
}

return $value;
}

/**
* @param mixed $opt
* @param mixed $opt_key
Expand Down
5 changes: 5 additions & 0 deletions classes/controllers/FrmFormsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -3540,6 +3540,11 @@ public static function footer_js( $location = 'footer' ) {
wp_enqueue_script( 'formidable' );

FrmHoneypot::maybe_print_honeypot_js();

if ( ! method_exists( 'FrmProFormsHelper', 'load_currency_js' ) ) {
// Load currency JS if Pro is not installed.
FrmFormsHelper::load_currency_js();
}
}

/**
Expand Down
3 changes: 3 additions & 0 deletions classes/factories/FrmFieldFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ private static function get_field_type_class( $field_type ) {
// Submit button field.
FrmSubmitHelper::FIELD_TYPE => 'FrmFieldSubmit',
FrmFieldGdprHelper::FIELD_TYPE => FrmFieldGdprHelper::get_gdpr_field_class( $field_type ),
'product' => 'FrmFieldProduct',
'quantity' => 'FrmFieldQuantity',
'total' => 'FrmFieldTotal',
);

$class = $type_classes[ $field_type ] ?? '';
Expand Down
23 changes: 22 additions & 1 deletion classes/helpers/FrmAppHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class FrmAppHelper {
*
* @var int
*/
public static $db_version = 104;
public static $db_version = 105;

/**
* Used by the API add-on.
Expand Down Expand Up @@ -3946,7 +3946,19 @@ public static function localize_script( $location ) {
// In older versions this event listener causes the section to immediately close again
// When the h3 element is clicked. It's only required in WP 6.7+.
'requireAccordionTitleClickListener' => version_compare( $wp_version, '6.7', '>=' ),
'shouldShowPaymentsSettingsModal' => ! self::at_least_one_payment_gateway_is_setup(),
'pricingFieldsImg' => esc_url( self::plugin_url() . '/images/upsell/pricing-fields.png' ),
'paymentsSettingsUrl' => FrmStrpLiteAppController::get_payments_settings_url(),
);

if ( self::is_form_builder_page() ) {
$admin_script_strings['shouldShowPricingFieldsModal'] = get_option( 'frm_show_pricing_fields_modal' );

if ( $admin_script_strings['shouldShowPricingFieldsModal'] ) {
delete_option( 'frm_show_pricing_fields_modal' );
}
}

/**
* @param array $admin_script_strings
*/
Expand All @@ -3960,6 +3972,15 @@ public static function localize_script( $location ) {
}//end if
}

/**
* Checks if at least one payment gateway is configured.
*
* @return bool
*/
private static function at_least_one_payment_gateway_is_setup() {
return FrmStrpLiteConnectHelper::at_least_one_mode_is_setup() || FrmSquareLiteConnectHelper::at_least_one_mode_is_setup();
}

/**
* Get the no label text.
*
Expand Down
117 changes: 115 additions & 2 deletions classes/helpers/FrmCurrencyHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,23 @@
class FrmCurrencyHelper {

/**
* @param string $currency
* Gets the currency data from the currency code.
*
* @since x.x The first parameter is optional.
*
* @param string|null $currency Currency code. Default is `null`, which use the currency in the global settings.
*
* @return array
*/
public static function get_currency( $currency ) {
public static function get_currency( $currency = null ) {
if ( ! $currency ) {
$currency = trim( FrmAppHelper::get_settings()->currency );
}

if ( ! $currency ) {
$currency = 'USD';
}

$currency = strtoupper( $currency );
$currencies = self::get_currencies();

Expand Down Expand Up @@ -42,6 +54,107 @@ public static function is_currency_format( $format_value ) {
return in_array( $format_value, array( 'currency', 'number' ), true );
}

/**
* Adds the currency symbol to the given amount.
*
* @param float|string $amount
* @param array|null $currency
*
* @return string
*/
public static function format_price( $amount, $currency = null ) {
if ( ! $currency ) {
$currency = self::get_currency();
}

if ( is_string( $amount ) ) {
$amount = floatval( self::prepare_price( $amount, $currency ) );
}

$amount = number_format( $amount, $currency['decimals'], $currency['decimal_separator'], $currency['thousand_separator'] );

if ( '' !== $currency['symbol_left'] ) {
$amount = $currency['symbol_left'] . $currency['symbol_padding'] . $amount;
}

if ( '' !== $currency['symbol_right'] ) {
$amount .= $currency['symbol_padding'] . $currency['symbol_right'];
}

return $amount;
}

/**
* @since x.x
*
* @param string $price
* @param array $currency
*
* @return float|string
*/
public static function prepare_price( $price, $currency ) {
$price = trim( $price );

if ( ! $price ) {
return 0;
}

preg_match_all( '/[\-]*[0-9,.]*\.?\,?[0-9]+/', $price, $matches );
$price = $matches ? end( $matches[0] ) : 0;

if ( ! $price ) {
return 0;
}
Comment on lines +102 to +107
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix price parsing for currencies that use spaces or apostrophes as thousand separators.

Line 89 only matches digits, commas, and dots, so inputs like 1 234,56 or 1'234.56 get truncated to the last chunk before Line 94 ever strips the configured separator. That breaks several currencies this helper explicitly supports.

💡 One way to keep the configured separators intact
-		preg_match_all( '/[\-]*[0-9,.]*\.?\,?[0-9]+/', $price, $matches );
+		$allowed_separators = preg_quote( $currency['thousand_separator'] . $currency['decimal_separator'], '/' );
+		preg_match_all( '/-?[0-9' . $allowed_separators . ']+/u', $price, $matches );
 		$price = $matches ? end( $matches[0] ) : 0;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@classes/helpers/FrmCurrencyHelper.php` around lines 89 - 95, The regex in
FrmCurrencyHelper that extracts numeric chunks using preg_match_all incorrectly
excludes space and apostrophe thousand separators, so update the pattern used
where preg_match_all is called (the block that sets $price and then calls
self::maybe_use_decimal and str_replace) to allow the configured thousand
separator(s) (including space and apostrophe) and the currency decimal
separator; specifically include \s and ' (and also dynamically include
$currency['thousand_separator'] and $currency['decimal_separator'] when building
the pattern), ensure proper escaping of those separators in the regex, and keep
the existing fallback logic ($matches ? end($matches[0]) : 0) and subsequent
maybe_use_decimal/str_replace flow.


$price = self::maybe_use_decimal( $price, $currency );
return str_replace( $currency['decimal_separator'], '.', str_replace( $currency['thousand_separator'], '', $price ) );
}

/**
* @since x.x
*
* @param string $amount
* @param array $currency
*
* @return string
*/
private static function maybe_use_decimal( $amount, $currency ) {
if ( $currency['thousand_separator'] !== '.' ) {
return $amount;
}

$amount_parts = explode( '.', $amount );
$used_for_decimal = count( $amount_parts ) === 2 && in_array( strlen( $amount_parts[1] ), array( 1, 2 ), true );

if ( $used_for_decimal ) {
return str_replace( '.', $currency['decimal_separator'], $amount );
}

return $amount;
}

/**
* If the currency is needed for this form, add it to the global.
* This is later included in the footer.
*
* @since x.x
*
* @param int|string $form_id Form ID. This is used for Pro compatibility.
*
* @return void
*/
public static function add_currency_to_global( $form_id ) {
global $frm_vars;

if ( ! isset( $frm_vars['currency'] ) ) {
$frm_vars['currency'] = array();
}

if ( ! isset( $frm_vars['currency'][ $form_id ] ) ) {
$frm_vars['currency'][ $form_id ] = self::get_currency();
}
}

/**
* Get a list of all supported currencies.
*
Expand Down
31 changes: 31 additions & 0 deletions classes/helpers/FrmFieldsHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -2360,6 +2360,37 @@ public static function convert_field_object_to_flat_array( $field ) {
return $field_array + $field_options;
}

/**
* Shows add field link.
*
* @since x.x
*
* @param array $field_type See file `classes/views/frm-forms/add_field_links.php`.
*
* @return void
*/
public static function show_add_field_link( $field_type ) {
$field_label = FrmFormsHelper::get_field_link_name( $field_type );
$classes = 'frmbutton frm6 frm_t' . $field_type['key'];

if ( ! empty( $field_type['hide'] ) ) {
$classes .= ' frm_hidden';
}
?>
<li class="<?php echo esc_attr( $classes ); ?>" id="<?php echo esc_attr( $field_type['key'] ); ?>">
<a href="#" class="frm_add_field" title="<?php echo esc_attr( $field_label ); ?>" role="button" aria-label="<?php echo esc_attr( $field_label ); ?>">
<?php FrmAppHelper::icon_by_class( FrmFormsHelper::get_field_link_icon( $field_type ) ); ?>
<span><?php echo esc_html( $field_label ); ?></span>
<?php
if ( 'credit_card' === $field_type['key'] && ! FrmTransLiteAppHelper::payments_table_exists() ) {
FrmAppHelper::show_pill_text();
}
?>
</a>
</li>
<?php
}

/**
* @since 4.04
*
Expand Down
19 changes: 19 additions & 0 deletions classes/helpers/FrmFormsHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -2151,6 +2151,25 @@ private static function is_formidable_api_form() {
return false;
}

/**
* @since x.x
*
* @return void
*/
public static function load_currency_js() {
global $frm_vars;

if ( empty( $frm_vars['currency'] ) ) {
return;
}

echo '<script>';
echo 'var frmcurrency=' . json_encode( $frm_vars['currency'] ) . ";\n";
echo 'if(typeof __FRMCURR == "undefined"){__FRMCURR=frmcurrency;}';
echo 'else{__FRMCURR=jQuery.extend(true,{},__FRMCURR,frmcurrency);}';
echo '</script>';
}

/**
* Returns the form name or the no title text if the form name is empty.
*
Expand Down
Loading
Loading