diff --git a/classes/controllers/FrmAppController.php b/classes/controllers/FrmAppController.php index b00550a184..f7d3ce0f80 100644 --- a/classes/controllers/FrmAppController.php +++ b/classes/controllers/FrmAppController.php @@ -668,7 +668,12 @@ public static function admin_js() { wp_register_script( 'formidable_embed', $plugin_url . '/js/admin/embed.js', array( 'formidable_dom', 'jquery-ui-autocomplete' ), $version, true ); self::register_popper1(); wp_register_script( 'bootstrap_tooltip', $plugin_url . '/js/bootstrap.min.js', array( 'jquery', 'popper' ), '4.6.1', true ); + + $settings_js_vars = array( + 'currencies' => FrmCurrencyHelper::get_currencies(), + ); wp_register_script( 'formidable_settings', $plugin_url . '/js/admin/settings.js', array(), $version, true ); + wp_localize_script( 'formidable_settings', 'frmSettings', $settings_js_vars ); if ( self::should_show_floating_links() ) { self::enqueue_floating_links( $plugin_url, $version ); diff --git a/classes/controllers/FrmFieldsController.php b/classes/controllers/FrmFieldsController.php index 1f4feaa1df..410cc1c8e6 100644 --- a/classes/controllers/FrmFieldsController.php +++ b/classes/controllers/FrmFieldsController.php @@ -468,15 +468,17 @@ public static function display_field_options( $settings, $field_info = null ) { * * @since 3.0 * - * @param array $field + * @param array $field Field data. + * @param bool $is_hidden Whether the format option should be hidden. * @return void */ - public static function show_format_option( $field ) { - $attributes = array(); - $attributes['class'] = 'frm-has-modal'; + public static function show_format_option( $field, $is_hidden = false ) { + $attributes = array( + 'class' => 'frm-has-modal', + 'id' => 'frm-field-format-custom-' . $field['id'], + ); - if ( 'phone' === $field['type'] ) { - $attributes['id'] = 'frm-phone-field-custom-format-' . $field['id']; + if ( $is_hidden ) { $attributes['class'] .= ' frm_hidden'; } @@ -961,7 +963,8 @@ private static function should_allow_input_attribute( $key ) { * @return void */ private static function add_pattern_attribute( $field, array &$add_html ) { - $has_format = FrmField::is_option_true_in_array( $field, 'format' ); + $format_value = FrmField::get_option( $field, 'format' ); + $has_format = $format_value && ! FrmCurrencyHelper::is_currency_format( $format_value ); $format_field = FrmField::is_field_type( $field, 'text' ); if ( $field['type'] === 'phone' || ( $has_format && $format_field ) ) { diff --git a/classes/controllers/FrmSettingsController.php b/classes/controllers/FrmSettingsController.php index ffaf29e0e1..1dda4ee6be 100644 --- a/classes/controllers/FrmSettingsController.php +++ b/classes/controllers/FrmSettingsController.php @@ -207,9 +207,8 @@ public static function general_settings() { * @return void */ public static function maybe_render_currency_selector( $frm_settings, $more_html ) { - if ( false !== strpos( $more_html, 'id="frm_currency"' ) ) { - // Avoid rendering the Currency setting if it gets rendered from the frm_settings_form hook. - // This is for backward compatibility. If Pro is outdated there won't be two currency dropdowns. + if ( is_callable( 'FrmProSettingsController::add_currency_settings' ) ) { + FrmProSettingsController::add_currency_settings(); return; } diff --git a/classes/helpers/FrmAppHelper.php b/classes/helpers/FrmAppHelper.php index 3c84b11c98..cdd0f6df1f 100644 --- a/classes/helpers/FrmAppHelper.php +++ b/classes/helpers/FrmAppHelper.php @@ -870,6 +870,19 @@ public static function kses( $value, $allowed = array() ) { return wp_kses( $value, $allowed_html ); } + /** + * Sanitizes and echoes a given value. + * + * @since x.x + * + * @param string $value The value to sanitize and output. + * @param array|string $allowed Allowed HTML tags and attributes. + * @return void + */ + public static function kses_echo( $value, $allowed = array() ) { + echo self::kses( $value, $allowed ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + /** * The regular kses function strips [button_action] from submit button HTML. * @@ -1089,6 +1102,11 @@ private static function safe_html() { 'legend' => array( 'class' => true, ), + 'option' => array( + 'class' => true, + 'value' => true, + 'selected' => true, + ), ); } diff --git a/classes/helpers/FrmCurrencyHelper.php b/classes/helpers/FrmCurrencyHelper.php index ca4bf87f41..7d360d5d38 100644 --- a/classes/helpers/FrmCurrencyHelper.php +++ b/classes/helpers/FrmCurrencyHelper.php @@ -26,6 +26,18 @@ public static function get_currency( $currency ) { return $currency; } + /** + * Checks if the given format is a valid currency format. + * + * @since x.x + * + * @param string $format_value The format value to check. + * @return bool + */ + public static function is_currency_format( $format_value ) { + return in_array( $format_value, array( 'currency', 'number' ), true ); + } + /** * Get a list of all supported currencies. * @@ -290,7 +302,7 @@ public static function get_currencies() { 'TRY' => array( 'name' => __( 'Turkish Liras', 'formidable' ), 'symbol_left' => '', - 'symbol_right' => '€', + 'symbol_right' => '₺', 'symbol_padding' => ' ', 'thousand_separator' => '.', 'decimal_separator' => ',', diff --git a/classes/models/FrmEntryValidate.php b/classes/models/FrmEntryValidate.php index 35ff7e3a89..69c1f2b05c 100644 --- a/classes/models/FrmEntryValidate.php +++ b/classes/models/FrmEntryValidate.php @@ -228,8 +228,9 @@ public static function validate_field_types( &$errors, $posted_field, $value, $a } public static function validate_phone_field( &$errors, $field, $value, $args ) { - if ( $field->type === 'phone' || ( $field->type === 'text' && FrmField::is_option_true_in_object( $field, 'format' ) ) ) { + $format_value = FrmField::get_option( $field, 'format' ); + if ( $field->type === 'phone' || ( $field->type === 'text' && $format_value && ! FrmCurrencyHelper::is_currency_format( $format_value ) ) ) { $pattern = self::phone_format( $field ); if ( ! preg_match( $pattern, $value ) ) { diff --git a/classes/models/fields/FrmFieldNumber.php b/classes/models/fields/FrmFieldNumber.php index 7c01ff984e..6847e5c64c 100644 --- a/classes/models/fields/FrmFieldNumber.php +++ b/classes/models/fields/FrmFieldNumber.php @@ -65,23 +65,22 @@ public function validate( $args ) { $errors[ 'field' . $args['id'] ] = FrmFieldsHelper::get_error_msg( $this->field, 'invalid' ); } - // validate number settings - if ( $args['value'] != '' ) { - // only check if options are available in settings - $minnum = FrmField::get_option( $this->field, 'minnum' ); - $maxnum = FrmField::get_option( $this->field, 'maxnum' ); - if ( $maxnum !== '' && $minnum !== '' ) { - $value = (float) $args['value']; - if ( $value < $minnum ) { - $errors[ 'field' . $args['id'] ] = __( 'Please select a higher number', 'formidable' ); - } elseif ( $value > $maxnum ) { - $errors[ 'field' . $args['id'] ] = __( 'Please select a lower number', 'formidable' ); - } - } - - $this->validate_step( $errors, $args ); + if ( $args['value'] === '' ) { + return $errors; } + $value = (float) $args['value']; + $minnum = FrmField::get_option( $this->field, 'minnum' ); + $maxnum = FrmField::get_option( $this->field, 'maxnum' ); + + if ( $minnum !== '' && $value < $minnum ) { + $errors[ 'field' . $args['id'] ] = __( 'Please select a higher number', 'formidable' ); + } elseif ( $maxnum !== '' && $value > $maxnum ) { + $errors[ 'field' . $args['id'] ] = __( 'Please select a lower number', 'formidable' ); + } + + $this->validate_step( $errors, $args ); + return $errors; } @@ -95,7 +94,7 @@ public function validate( $args ) { * * @return void */ - private function validate_step( &$errors, $args ) { + protected function validate_step( &$errors, $args ) { if ( isset( $errors[ 'field' . $args['id'] ] ) ) { // Don't need to check if value is invalid before. return; diff --git a/classes/models/fields/FrmFieldPhone.php b/classes/models/fields/FrmFieldPhone.php index 1b9c68c7eb..64ad861901 100644 --- a/classes/models/fields/FrmFieldPhone.php +++ b/classes/models/fields/FrmFieldPhone.php @@ -47,7 +47,7 @@ public function show_primary_options( $args ) { $field = $args['field']; include FrmAppHelper::plugin_path() . '/classes/views/frm-fields/back-end/phone/phone-type.php'; - FrmFieldsController::show_format_option( $field ); + FrmFieldsController::show_format_option( $field, true ); parent::show_primary_options( $args ); } @@ -62,11 +62,10 @@ public function show_primary_options( $args ) { protected function print_international_option() { ?> diff --git a/classes/views/frm-fields/back-end/format-dropdown-options.php b/classes/views/frm-fields/back-end/format-dropdown-options.php new file mode 100644 index 0000000000..2ed73b031f --- /dev/null +++ b/classes/views/frm-fields/back-end/format-dropdown-options.php @@ -0,0 +1,51 @@ + 'none' ) ); + +FrmHtmlHelper::echo_dropdown_option( + in_array( $field_type, array( 'number', 'range' ), true ) ? __( 'Custom', 'formidable' ) : __( 'Number', 'formidable' ), + false, + array( + 'value' => '', + 'class' => 'frm_show_upgrade frm_noallow', + 'data-upgrade' => __( 'Format number field', 'formidable' ), + 'data-medium' => 'format-number-field', + ) +); + +FrmHtmlHelper::echo_dropdown_option( + __( 'Currency', 'formidable' ), + false, + array( + 'value' => '', + 'class' => 'frm_show_upgrade frm_noallow', + 'data-upgrade' => __( 'Format currency field', 'formidable' ), + 'data-medium' => 'format-currency-field', + ) +); + +if ( 'text' === $field_type ) { + FrmHtmlHelper::echo_dropdown_option( + __( 'Custom', 'formidable' ), + ! empty( $format ) && ! FrmCurrencyHelper::is_currency_format( $format ), + array( + 'value' => 'custom', + 'data-dependency' => '#frm-field-format-custom-' . $field_id, + ) + ); +} diff --git a/classes/views/frm-fields/back-end/format-dropdown.php b/classes/views/frm-fields/back-end/format-dropdown.php new file mode 100644 index 0000000000..7b72caa012 --- /dev/null +++ b/classes/views/frm-fields/back-end/format-dropdown.php @@ -0,0 +1,51 @@ + +

+ + + +

+ + print_international_option(); ?> - diff --git a/classes/views/frm-fields/back-end/settings.php b/classes/views/frm-fields/back-end/settings.php index 03929a7424..f7198a6ece 100644 --- a/classes/views/frm-fields/back-end/settings.php +++ b/classes/views/frm-fields/back-end/settings.php @@ -64,35 +64,41 @@ -

+

- +
+ +
- +
+ +
- +
+ +
-

+
show_primary_options( compact( 'field', 'display', 'values' ) ); + if ( $display['format'] ) { + include FrmAppHelper::plugin_path() . '/classes/views/frm-fields/back-end/format-dropdown.php'; + FrmFieldsController::show_format_option( $field, true ); + } ?> @@ -254,13 +264,7 @@

- +

diff --git a/classes/views/frm-settings/_currency.php b/classes/views/frm-settings/_currency.php index f16de50ffc..0a345e0bba 100644 --- a/classes/views/frm-settings/_currency.php +++ b/classes/views/frm-settings/_currency.php @@ -4,8 +4,9 @@ } ?>

-

+ + li.current > a.current:after { grid-column: span 6 / span 6; } -.frm_form_field.frm-phone-type + p > label, -.frm_form_field.frm-phone-type + div > label { +.frm_form_field.frm12_followed + p, +.frm_form_field.frm12_followed + div { + grid-column: span 12 / span 12 !important; +} + +.frm_form_field.frm-phone-type ~ [id*="frm-field-format-custom-"] > label, +.frm_form_field.frm-format-dropdown ~ [id*="frm-field-format-custom-"] > label { opacity: 0; /* Hide the label for the format input in the phone field. */ } +.frm_form_field.frm-format-dropdown ~ [id*="frm-field-format-custom-"] { + grid-column: span 6 / span 6; +} + +/* Adjust "CSS Layout Classes" to full width when the "Format" setting is present. */ +.frm-single-settings.frm-type-textarea > .frm_grid_container [id*="frm-field-format-custom-"] + p { + grid-column: span 12/span 12; +} + .frm-short-list { overflow: auto; max-height: 190px; @@ -1773,6 +1787,11 @@ input.frm_insert_in_template { margin-right: var(--gap-sm) !important; } +.frm-my-xs { + margin-top: var(--gap-xs) !important; + margin-bottom: var(--gap-xs) !important; +} + .frm-p-0 { padding: 0 !important; } @@ -1911,7 +1930,8 @@ input.frm_insert_in_template { display: inline-block !important; } -.frm-inline-flex { +.frm-inline-flex, +#wpbody-content .frm-inline-flex { display: inline-flex; } @@ -1989,6 +2009,10 @@ input.frm_insert_in_template { align-items: center; } +#wpbody-content .frm-flex-wrap { + flex-wrap: wrap; +} + .frm-transition-ease { transition: all .2s ease; } @@ -2033,6 +2057,7 @@ input.frm_insert_in_template { /* End Generic Classes, Start Forced Generic Classes */ .frm-fields p > label.frm_hidden, +#wpbody-content label.frm_hidden, .frm-lookup-modal .dismiss, .frm-right-panel .inside a.frm_hidden, #form_global_settings .frm_hidden, diff --git a/js/admin/settings.js b/js/admin/settings.js index d725830e68..149fbf5508 100644 --- a/js/admin/settings.js +++ b/js/admin/settings.js @@ -9,6 +9,10 @@ if ( 'INPUT' === e.target.nodeName && 'checkbox' === e.target.type && e.target.parentNode.classList.contains( 'frm_toggle_block' ) ) { handleToggleChangeEvent( e ); } + + if ( 'frm_currency' === e.target.id) { + syncCurrencyOptions( e.target ); + } } function handleKeyDownEvent( e ) { @@ -23,6 +27,19 @@ e.target.nextElementSibling.setAttribute( 'aria-checked', e.target.checked ? 'true' : 'false' ); } + /** + * Updates the currency formatting options based on the selected currency. + * + * @param {HTMLSelectElement} currencySelect The currency select element. + */ + function syncCurrencyOptions( currencySelect ) { + const currency = frmSettings.currencies[ currencySelect.value ]; + + document.getElementById( 'frm_thousand_separator' ).setAttribute( 'value', currency.thousand_separator ); + document.getElementById( 'frm_decimal_separator' ).setAttribute( 'value', currency.decimal_separator ); + document.getElementById( 'frm_decimals' ).setAttribute( 'value', currency.decimals ); + } + function handleSpaceDownEvent( e ) { if ( e.target.classList.contains( 'frm_toggle' ) ) { e.preventDefault(); // Prevent automatic browser scroll when space is pressed. diff --git a/js/formidable_admin.js b/js/formidable_admin.js index 353725dec3..6470472fa8 100644 --- a/js/formidable_admin.js +++ b/js/formidable_admin.js @@ -5421,22 +5421,22 @@ function frmAdminBuildJS() { } /** - * Update the phone format input based on the selected phone type. - * - * This function is triggered when a phone type is selected. - * If the selected type is 'custom' and the current format is 'international', - * the format input value is cleared to allow for custom input. + * Update the format input based on the selected format type. * * @since 6.9 * - * @param {Event} event The event object from the phone type selection. + * @param {Event} event The event object from the format type selection. * @return {void} */ - function maybeUpdatePhoneFormatInput( event ) { - const phoneType = event.target; - if ( 'custom' === phoneType.value ) { - const formatInput = phoneType.parentElement.nextElementSibling.querySelector( '.frm_format_opt' ); - if ( 'international' === formatInput.value ) { + function maybeUpdateFormatInput( event ) { + const formatElement = event.target; + const type = formatElement.value + + if ( 'custom' === type ) { + const fieldId = formatElement.dataset.fieldId; + const formatInput = document.getElementById( `frm-field-format-custom-${fieldId}` ).querySelector( '.frm_format_opt' ); + + if ( 'international' === formatInput.value || 'currency' === formatInput.value || 'number' === formatInput.value ) { formatInput.setAttribute( 'value', '' ); } } @@ -6688,32 +6688,26 @@ function frmAdminBuildJS() { } /** - * Updates the format input based on the selected phone type from dropdowns during the form save process. - * - * Triggered within the preFormSave function, this function iterates through all phone type dropdown elements - * and adjusts the format input value accordingly. Specifically, if the phone type is 'custom' but the format input - * is empty, it sets it to 'none'. If the phone type is 'international', it sets the format input value to 'international' - * before the form is saved. + * Updates the format input based on the selected format type from dropdowns during the form save process. * * @since 6.9 * - * @param {HTMLButtonElement} submitButton The button that was submitted. * @return {void} */ - function adjustFormatInputBeforeSave( submitButton ) { - const phoneTypes = document.querySelectorAll( '.frm_phone_type_dropdown' ); - phoneTypes.forEach( phoneType => { - const value = phoneType.value; - if ( ! [ 'none', 'international' ].includes( value ) ) { - return; - } + function adjustFormatInputBeforeSave() { + const formatTypes = document.querySelectorAll( '.frm_format_dropdown, .frm_phone_type_dropdown' ); + const valueMap = { + none: '', + international: 'international', + currency: 'currency', + number: 'number' + }; - const formatInput = phoneType.parentElement.nextElementSibling.querySelector( '.frm_format_opt' ); - if ( 'none' === value ) { - formatInput.setAttribute( 'value', '' ); - } - if ( 'international' === value ) { - formatInput.setAttribute( 'value', 'international' ); + formatTypes.forEach( formatType => { + const value = formatType.value; + if ( value in valueMap ) { + const formatInput = document.getElementById( `frm_format_${formatType.dataset.fieldId}` ); + formatInput.value = valueMap[ value ]; } }); } @@ -10361,7 +10355,7 @@ function frmAdminBuildJS() { */ function toggleDependencyVisibility( select ) { const selectedOption = select.options[ select.selectedIndex ]; - select.querySelectorAll( 'option[data-dependency]' ).forEach( option => { + select.querySelectorAll( 'option[data-dependency]:not([data-dependency-skip])' ).forEach( option => { const dependencyElement = document.querySelector( option.dataset.dependency ); dependencyElement?.classList.toggle( 'frm_hidden', selectedOption !== option ); }); @@ -10704,7 +10698,7 @@ function frmAdminBuildJS() { jQuery( document ).on( 'blur', '.frm-single-settings ul input[type="text"][name^="field_options[options_"]', onOptionTextBlur ); frmDom.util.documentOn( 'click', '.frm-show-field-settings', clickVis ); - frmDom.util.documentOn( 'change', 'select.frm_phone_type_dropdown', maybeUpdatePhoneFormatInput ); + frmDom.util.documentOn( 'change', 'select.frm_format_dropdown, select.frm_phone_type_dropdown', maybeUpdateFormatInput ); initBulkOptionsOverlay(); hideEmptyEle(); diff --git a/stubs.php b/stubs.php index b82cfde206..9305a853dc 100644 --- a/stubs.php +++ b/stubs.php @@ -155,6 +155,9 @@ public static function admin_banner() { public static function get_readable_license_type() { } } + class FrmProCurrencyHelper { + public static function normalize_formatted_numbers( $field, $formatted_value ) {} + } class FrmProDb { public static $plug_version; } @@ -421,8 +424,11 @@ class FrmProSettingsController { * @param string $count * @return string */ - public static function inbox_badge( $count ) { - } + public static function inbox_badge( $count ) {} + /** + * @return void + */ + public static function add_currency_settings() {} } }