Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
3937b09
Move address fields to Lite
Crabcyborg Jun 23, 2026
a6e0d55
Revert a change, add some unit tests
Crabcyborg Jun 23, 2026
6f89c2a
Run fixers
Crabcyborg Jun 23, 2026
5064f67
Try to fix workflow issues
Crabcyborg Jun 23, 2026
5f9e02f
Try to fix tests, remove useless function
Crabcyborg Jun 23, 2026
f50bfa0
Clean up
Crabcyborg Jun 23, 2026
1f427dc
Run fixers
Crabcyborg Jun 23, 2026
67a23bd
Try to fix workflows
Crabcyborg Jun 23, 2026
eb87a06
Prevent duplicate code
Crabcyborg Jun 24, 2026
1d910f9
Run phpcbf
Crabcyborg Jun 24, 2026
d69ed83
Clean up, simplify combo field CSV headings, update tests
Crabcyborg Jun 24, 2026
372908d
Try to fix tests, run rector
Crabcyborg Jun 24, 2026
64e957c
Fix equals alignment
Crabcyborg Jun 24, 2026
cf0fc0c
Update tests
Crabcyborg Jun 24, 2026
3f29e79
Update tests
Crabcyborg Jun 24, 2026
1f36656
Update tests
Crabcyborg Jun 24, 2026
d48c1bf
Use fourth
Crabcyborg Jun 24, 2026
54cc72c
Fix type issue
Crabcyborg Jun 24, 2026
27ee592
Run fixer
Crabcyborg Jun 24, 2026
929b0dc
Only add an extra export column for full value for Address
Crabcyborg Jun 24, 2026
960f704
Try to fix tests
Crabcyborg Jun 24, 2026
daeef13
Use strings
Crabcyborg Jun 24, 2026
9683be3
Fix issues with missing descriptions
Crabcyborg Jun 24, 2026
23097d5
Fix test
Crabcyborg Jun 24, 2026
71ae41e
Merge branch 'master' into move_address_fields_to_lite
Crabcyborg Jun 24, 2026
2cf42da
Merge branch 'master' into move_address_fields_to_lite
Crabcyborg Aug 3, 2026
fc0f091
Try to support Lite address fields in all of the Lite payment gateways
Crabcyborg Aug 3, 2026
99f8c2e
Pass billing address with card holder details (paypal)
Crabcyborg Aug 4, 2026
88d466a
Revert a breaking change
Crabcyborg Aug 4, 2026
7802f82
Add Psalm exceptions
Crabcyborg Aug 4, 2026
1e5135f
Merge branch 'master' into move_address_fields_to_lite
Crabcyborg Aug 4, 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
477 changes: 477 additions & 0 deletions classes/controllers/FrmAddressesController.php

Large diffs are not rendered by default.

189 changes: 189 additions & 0 deletions classes/controllers/FrmComboFieldsController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
<?php

if ( ! defined( 'ABSPATH' ) ) {
die( 'You are not allowed to call this page directly.' );
}

/**
* Base controller for combo fields (fields with multiple sub-fields like Address).
*
* @since x.x
*/
class FrmComboFieldsController {

/**
* Fill values with defaults.
*
* @since x.x
*
* @param mixed $value
* @param array $defaults
*
* @return void
*/
public static function fill_values( &$value, $defaults ) {
$value = $value ? array_merge( $defaults, (array) $value ) : $defaults;
}

/**
* Include placeholder attribute for sub-field.
*
* @since x.x
*
* @param mixed $default_value
* @param string $sub_field
* @param array $field
*
* @return void
*/
public static function include_placeholder( $default_value, $sub_field, $field = array() ) {
if ( ! is_array( $default_value ) ) {
$default_value = array();
}

if ( $field ) {
$use_placeholder = ! FrmField::is_option_empty( $field, 'placeholder' );

if ( ( ! $use_placeholder || empty( $default_value[ $sub_field ] ) ) && $sub_field === 'line1' ) {
// Allow for 'inside' label position.
$default_value[ $sub_field ] = FrmFieldsController::get_default_value_from_name( $field );
$use_placeholder = $default_value[ $sub_field ] !== '';
}

if ( ! $use_placeholder ) {
return;
}
}

if ( ! $default_value ) {
return;
}

$placeholder = $default_value[ $sub_field ] ?? '';
echo ' placeholder="' . esc_attr( $placeholder ) . '" data-placeholder="' . esc_attr( $placeholder ) . '"';
}

/**
* Get dropdown label for select sub-fields.
*
* @since x.x
*
* @param array $atts
*
* @return string
*/
public static function get_dropdown_label( $atts ) {
$default = $atts['sub_field']['placeholder'] ?? ' ';

if ( ! is_string( $default ) ) {
$default = '';
}

/**
* @since x.x
*
* @param string $default
* @param array $atts
*/
$result = apply_filters( 'frm_combo_dropdown_label', $default, $atts );

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Function apply_filters invoked with 3 parameters, 2 required


The function call is not valid, which will result in a fatal runtime error.


return is_string( $result ) ? $result : $default;
}

/**
* Add attributes to input field.
*
* @since x.x
*
* @param array $atts
*
* @return void
*/
public static function add_atts_to_input( $atts ) {
$placeholder = $atts['field']['placeholder'] ?? '';
$default_value = $atts['field']['default_value'];
$field_obj = FrmFieldFactory::get_field_type( $atts['field']['type'], $atts['field'] );

if ( $atts['field']['type'] === 'address' ) {
/**
* @var FrmFieldAddress $field_obj
*/
$placeholder = $field_obj->address_string_to_array( $placeholder );

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Call to an undefined method FrmFieldType::address_string_to_array()


The method you are trying to call is not defined, which can result in a fatal error.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Call to an undefined method FrmFieldType::address_string_to_array()


The method you are trying to call is not defined, which can result in a fatal error.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Call to an undefined method FrmFieldType::address_string_to_array()


The method you are trying to call is not defined, which can result in a fatal error.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Call to an undefined method FrmFieldType::address_string_to_array()


The method you are trying to call is not defined, which can result in a fatal error.

$default_value = $field_obj->address_string_to_array( $default_value );

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Call to an undefined method FrmFieldType::address_string_to_array()


The method you are trying to call is not defined, which can result in a fatal error.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Call to an undefined method FrmFieldType::address_string_to_array()


The method you are trying to call is not defined, which can result in a fatal error.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Call to an undefined method FrmFieldType::address_string_to_array()


The method you are trying to call is not defined, which can result in a fatal error.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Call to an undefined method FrmFieldType::address_string_to_array()


The method you are trying to call is not defined, which can result in a fatal error.

Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

self::include_placeholder( $placeholder, $atts['key'], $atts['field'] );
$atts['field']['placeholder'] = '';

$atts['field']['default_value'] = $default_value[ $atts['key'] ] ?? '';

Comment on lines +115 to +119

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Align sub-field key contract (name vs key) across combo helpers.

These helpers read $atts['key'], but downstream callers pass name, so placeholder/default/error lookup can silently fail and produce undefined-index notices.

Suggested fix
 public static function add_atts_to_input( $atts ) {
+	$sub_field_key = $atts['key'] ?? $atts['name'] ?? '';
+	if ( '' === $sub_field_key ) {
+		return;
+	}
+
 	$placeholder   = $atts['field']['placeholder'] ?? '';
 	$default_value = $atts['field']['default_value'];
 	$field_obj     = FrmFieldFactory::get_field_type( $atts['field']['type'], $atts['field'] );
@@
-	self::include_placeholder( $placeholder, $atts['key'], $atts['field'] );
+	self::include_placeholder( $placeholder, $sub_field_key, $atts['field'] );
 	$atts['field']['placeholder'] = '';
 
-	$atts['field']['default_value'] = $default_value[ $atts['key'] ] ?? '';
+	$atts['field']['default_value'] = $default_value[ $sub_field_key ] ?? '';
@@
 public static function maybe_add_error_class( $atts ) {
+	$sub_field_key = $atts['key'] ?? $atts['name'] ?? '';
+	if ( '' === $sub_field_key ) {
+		return;
+	}
+
 	$temp_id   = ! empty( $atts['atts']['field_id'] ) ? $atts['atts']['field_id'] : $atts['field']['id'];
-	$has_error = isset( $atts['errors'][ 'field' . $temp_id . '-' . $atts['key'] ] );
+	$has_error = isset( $atts['errors'][ 'field' . $temp_id . '-' . $sub_field_key ] );

Also applies to: 179-180

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@classes/controllers/FrmComboFieldsController.php` around lines 112 - 116, The
FrmComboFieldsController is using $atts['key'] to look up sub-field identifiers
in methods like include_placeholder and when accessing the default_value array,
but downstream callers are passing the sub-field identifier under the 'name' key
instead, causing silent lookup failures and undefined index notices. Replace all
references to $atts['key'] with $atts['name'] in the combo field helper methods
to align the sub-field key contract, specifically in the include_placeholder
function call and the default_value array access around lines 112-116, and apply
the same fix to the corresponding code at lines 179-180.

if ( 'select' === $atts['sub_field']['type'] && ! empty( $atts['field']['read_only'] ) ) {
$atts['sub_field']['atts']['disabled'] = 'disabled';
}

if ( ! empty( $atts['sub_field']['optional'] ) ) {
add_filter( 'frm_field_classes', 'FrmAddressesController::add_optional_class', 20, 2 );
do_action( 'frm_field_input_html', $atts['field'] );
remove_filter( 'frm_field_classes', 'FrmAddressesController::add_optional_class', 20 );
} else {
do_action( 'frm_field_input_html', $atts['field'] );
}

if ( ! isset( $atts['sub_field']['atts'] ) ) {
return;
}

foreach ( $atts['sub_field']['atts'] as $att_name => $att_value ) {
echo ' ' . esc_attr( $att_name ) . '="' . esc_attr( $att_value ) . '"';
}
}

/**
* Include sub-label for field.
*
* @since x.x
*
* @param array $atts
*
* @return void
*/
public static function include_sub_label( $atts ) {
self::show_sub_label( $atts );
}

/**
* Show sub-label for field.
*
* @since x.x
*
* @param array $atts
*
* @return void
*/
public static function show_sub_label( $atts ) {
$field = $atts['field'];
$option_name = $atts['option_name'];

if ( $field[ $option_name ] !== '' ) {
echo '<div class="frm_description">' . wp_kses_post( $field[ $option_name ] ) . '</div>';
Comment on lines +167 to +168

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.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Guard sub-label array access before dereferencing.

$field[ $option_name ] is accessed directly; when that key is absent, PHP notices leak into rendering.

Suggested fix
-	if ( $field[ $option_name ] !== '' ) {
+	if ( isset( $field[ $option_name ] ) && '' !== $field[ $option_name ] ) {
 		echo '<div class="frm_description">' . wp_kses_post( $field[ $option_name ] ) . '</div>';
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if ( $field[ $option_name ] !== '' ) {
echo '<div class="frm_description">' . wp_kses_post( $field[ $option_name ] ) . '</div>';
if ( isset( $field[ $option_name ] ) && '' !== $field[ $option_name ] ) {
echo '<div class="frm_description">' . wp_kses_post( $field[ $option_name ] ) . '</div>';
}
🧰 Tools
🪛 ast-grep (0.44.0)

[warning] 164-164: Avoid side effects in a file that defines symbols
Context: echo '

' . wp_kses_post( $field[ $option_name ] ) . '
';
Note: [CWE-710] Improper Adherence to Coding Standards.

(no-side-effect)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@classes/controllers/FrmComboFieldsController.php` around lines 164 - 165, The
code directly accesses `$field[ $option_name ]` without first verifying that the
key exists in the array, which causes PHP notices when the key is absent. Guard
the array access by using isset() to check if the key exists before attempting
to access it. Modify the condition to first verify that the key is present in
the $field array, and only then check if its value is not empty. This will
prevent undefined array key notices from being rendered.

}
}

/**
* Add error class if field has error.
*
* @since x.x
*
* @param array $atts
*
* @return void
*/
public static function maybe_add_error_class( $atts ) {
$temp_id = ! empty( $atts['atts']['field_id'] ) ? $atts['atts']['field_id'] : $atts['field']['id'];
$has_error = isset( $atts['errors'][ 'field' . $temp_id . '-' . $atts['key'] ] );

if ( $has_error ) {
echo ' frm_blank_field';
}
}
}
4 changes: 2 additions & 2 deletions classes/controllers/FrmFieldsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -455,10 +455,10 @@ public static function load_single_field_settings( $atts ) {

$show_upsell_for_unique_value = in_array(
$field['type'],
array( 'address', 'checkbox', 'email', 'name', 'number', 'phone', 'radio', 'text', 'textarea', 'url' ),
array( 'checkbox', 'email', 'name', 'number', 'phone', 'radio', 'text', 'textarea', 'url' ),
true
);
$show_upsell_for_read_only = in_array( $field['type'], array( 'email', 'hidden', 'number', 'phone', 'radio', 'text', 'textarea', 'url' ), true );
$show_upsell_for_read_only = in_array( $field['type'], array( 'address', 'email', 'hidden', 'number', 'phone', 'radio', 'text', 'textarea', 'url' ), true );
$show_upsell_for_before_after_contents = in_array( $field['type'], array( 'email', 'number', 'phone', 'quantity', 'select', 'tag', 'text', 'total', 'url' ), true );
$show_upsell_for_autocomplete = in_array( $field['type'], array( 'text', 'email', 'number' ), true );
$show_upsell_for_visibility = $field['type'] !== 'hidden';
Expand Down
1 change: 1 addition & 0 deletions classes/factories/FrmFieldFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ private static function get_field_type_class( $field_type ) {
'captcha' => 'FrmFieldCaptcha',
'name' => 'FrmFieldName',
'credit_card' => 'FrmFieldCreditCard',
'address' => 'FrmFieldAddress',
// Submit button field.
FrmSubmitHelper::FIELD_TYPE => 'FrmFieldSubmit',
FrmFieldGdprHelper::FIELD_TYPE => FrmFieldGdprHelper::get_gdpr_field_class( $field_type ),
Expand Down
5 changes: 5 additions & 0 deletions classes/models/FrmField.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ public static function field_selection() {
'name' => __( 'Payment', 'formidable' ),
'icon' => 'frmfont frm_credit_card2_icon',
),
'address' => array(
'name' => __( 'Address', 'formidable' ),
'icon' => 'frmfont frm_location2_icon',
),
FrmSubmitHelper::FIELD_TYPE => array(
'name' => __( 'Submit', 'formidable' ),
'hide' => true,
Expand Down Expand Up @@ -439,6 +443,7 @@ public static function remove_moved_field_types_from_pro( &$pro_fields ) {
unset( $pro_fields['product'] );
unset( $pro_fields['quantity'] );
unset( $pro_fields['total'] );
unset( $pro_fields['address'] );
}

/**
Expand Down
Loading
Loading