From eb6df9794045d4ecdc0510a21396b7f98b9cf17e Mon Sep 17 00:00:00 2001 From: Abdi Tolessa <41271840+AbdiTolesa@users.noreply.github.com> Date: Fri, 8 Aug 2025 13:31:56 +0300 Subject: [PATCH 01/20] Improve form errors accessibility --- classes/helpers/FrmFormsHelper.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/classes/helpers/FrmFormsHelper.php b/classes/helpers/FrmFormsHelper.php index d496d523eb..36c7f875a1 100644 --- a/classes/helpers/FrmFormsHelper.php +++ b/classes/helpers/FrmFormsHelper.php @@ -263,6 +263,12 @@ public static function get_invalid_error_message( $args ) { $frm_settings = FrmAppHelper::get_settings( $settings_args ); $invalid_msg = do_shortcode( $frm_settings->invalid_msg ); + $field_error_messages = ''; + foreach ( $args['errors'] as $field_id => $error ) { + $field = (array) FrmField::getOne( str_replace( 'field', '', $field_id ) ); + $field_error_messages .= '
  • ' . $error . '
  • '; + } + $invalid_msg .= $field_error_messages ? '' : ''; return apply_filters( 'frm_invalid_error_message', $invalid_msg, $args ); } From 88c94349489b17978ec8791d138d4bac83caf610 Mon Sep 17 00:00:00 2001 From: Abdi Tolessa <41271840+AbdiTolesa@users.noreply.github.com> Date: Fri, 8 Aug 2025 14:40:53 +0300 Subject: [PATCH 02/20] Move logic that creates field error messages with links to a new function --- classes/helpers/FrmFormsHelper.php | 51 +++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/classes/helpers/FrmFormsHelper.php b/classes/helpers/FrmFormsHelper.php index 36c7f875a1..fe16dbcf41 100644 --- a/classes/helpers/FrmFormsHelper.php +++ b/classes/helpers/FrmFormsHelper.php @@ -263,15 +263,56 @@ public static function get_invalid_error_message( $args ) { $frm_settings = FrmAppHelper::get_settings( $settings_args ); $invalid_msg = do_shortcode( $frm_settings->invalid_msg ); - $field_error_messages = ''; - foreach ( $args['errors'] as $field_id => $error ) { - $field = (array) FrmField::getOne( str_replace( 'field', '', $field_id ) ); - $field_error_messages .= '
  • ' . $error . '
  • '; - } + $field_error_messages = self::get_field_error_messages( $args ); $invalid_msg .= $field_error_messages ? '' : ''; return apply_filters( 'frm_invalid_error_message', $invalid_msg, $args ); } + /** + * @since x.x + * + * @param array $args + * @return string + */ + private static function get_field_error_messages( $args ) { + $field_error_messages = ''; + if ( empty( $args['errors'] ) ) { + return $field_error_messages; + } + $field_ids = array_map( + function ( $field_plus_id ) { + $field_id = str_replace( 'field', '', $field_plus_id ); + if ( strpos( $field_id, '-' ) !== false ) { + $field_id = explode( '-', $field_id )[0]; + } + return $field_id; + }, + array_keys( $args['errors'] ) + ); + $field_keys = FrmDb::get_results( 'frm_fields', array( 'id' => $field_ids ), 'id,field_key,type' ); + foreach ( $args['errors'] as $field_plus_id => $error ) { + $field_id = str_replace( 'field', '', $field_plus_id ); + $row = ''; + if ( strpos( $field_id, '-' ) !== false ) { + $field_id_parts = explode( '-', $field_id ); + if ( count( $field_id_parts ) === 3 ) { + $field_id = $field_id_parts[0]; + $row = '-' . $field_id_parts[2]; + } + } + $index = array_search( $field_id, array_column( $field_keys, 'id' ), true ); + if ( false === $index ) { + continue; + } + $html_id = 'field_' . $field_keys[ $index ]->field_key . $row; + if ( in_array( $field_keys[ $index ]->type, array( 'checkbox', 'radio' ), true ) ) { + $html_id .= '-0'; // Focus on the first option. + } + $field_error_messages .= '
  • ' . $error . '
  • '; + } + return $field_error_messages; + } + /** * @param array $atts { * The success message details. From 924ccf8b0f2433bba1e3c3b5cd2d378ee0d44e93 Mon Sep 17 00:00:00 2001 From: Abdi Tolessa <41271840+AbdiTolesa@users.noreply.github.com> Date: Fri, 8 Aug 2025 14:45:30 +0300 Subject: [PATCH 03/20] Fix PHPCS errpr --- classes/helpers/FrmFormsHelper.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/classes/helpers/FrmFormsHelper.php b/classes/helpers/FrmFormsHelper.php index fe16dbcf41..ab3adfca7f 100644 --- a/classes/helpers/FrmFormsHelper.php +++ b/classes/helpers/FrmFormsHelper.php @@ -306,10 +306,11 @@ function ( $field_plus_id ) { } $html_id = 'field_' . $field_keys[ $index ]->field_key . $row; if ( in_array( $field_keys[ $index ]->type, array( 'checkbox', 'radio' ), true ) ) { - $html_id .= '-0'; // Focus on the first option. + // Focus on the first option. + $html_id .= '-0'; } $field_error_messages .= '
  • ' . $error . '
  • '; - } + }//end foreach return $field_error_messages; } From d79ac345403b8933fef6e1673aa39f8fc8a5f306 Mon Sep 17 00:00:00 2001 From: Abdi Tolessa <41271840+AbdiTolesa@users.noreply.github.com> Date: Fri, 8 Aug 2025 15:43:29 +0300 Subject: [PATCH 04/20] Fix equal sign alignment --- classes/helpers/FrmFormsHelper.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/classes/helpers/FrmFormsHelper.php b/classes/helpers/FrmFormsHelper.php index ab3adfca7f..04f040063b 100644 --- a/classes/helpers/FrmFormsHelper.php +++ b/classes/helpers/FrmFormsHelper.php @@ -263,8 +263,9 @@ public static function get_invalid_error_message( $args ) { $frm_settings = FrmAppHelper::get_settings( $settings_args ); $invalid_msg = do_shortcode( $frm_settings->invalid_msg ); + $field_error_messages = self::get_field_error_messages( $args ); - $invalid_msg .= $field_error_messages ? '' : ''; + $invalid_msg .= $field_error_messages ? '' : ''; return apply_filters( 'frm_invalid_error_message', $invalid_msg, $args ); } From ce3144c0e4022bcd77370f6a7c21e4d205b7b025 Mon Sep 17 00:00:00 2001 From: Abdi Tolessa <41271840+AbdiTolesa@users.noreply.github.com> Date: Fri, 8 Aug 2025 21:45:24 +0300 Subject: [PATCH 05/20] Add comment to function --- classes/helpers/FrmFormsHelper.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/classes/helpers/FrmFormsHelper.php b/classes/helpers/FrmFormsHelper.php index 04f040063b..e8bfd13fc6 100644 --- a/classes/helpers/FrmFormsHelper.php +++ b/classes/helpers/FrmFormsHelper.php @@ -264,18 +264,20 @@ public static function get_invalid_error_message( $args ) { $frm_settings = FrmAppHelper::get_settings( $settings_args ); $invalid_msg = do_shortcode( $frm_settings->invalid_msg ); - $field_error_messages = self::get_field_error_messages( $args ); + $field_error_messages = self::get_clickable_field_error_messages( $args ); $invalid_msg .= $field_error_messages ? '' : ''; return apply_filters( 'frm_invalid_error_message', $invalid_msg, $args ); } /** + * Get clickable field error messages. + * * @since x.x * * @param array $args * @return string */ - private static function get_field_error_messages( $args ) { + private static function get_clickable_field_error_messages( $args ) { $field_error_messages = ''; if ( empty( $args['errors'] ) ) { return $field_error_messages; @@ -307,7 +309,7 @@ function ( $field_plus_id ) { } $html_id = 'field_' . $field_keys[ $index ]->field_key . $row; if ( in_array( $field_keys[ $index ]->type, array( 'checkbox', 'radio' ), true ) ) { - // Focus on the first option. + // Needed to focus on the first option when error link is clicked. $html_id .= '-0'; } $field_error_messages .= '
  • ' . $error . '
  • '; From e7df4102bc058d1f09ec9f5953668b1afec53eae Mon Sep 17 00:00:00 2001 From: Abdi Tolessa <41271840+AbdiTolesa@users.noreply.github.com> Date: Fri, 5 Dec 2025 15:19:01 +0300 Subject: [PATCH 06/20] Add CSS rules for form errors list --- classes/helpers/FrmFormsHelper.php | 2 +- css/_single_theme.css.php | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/classes/helpers/FrmFormsHelper.php b/classes/helpers/FrmFormsHelper.php index e8bfd13fc6..5d26f507bf 100644 --- a/classes/helpers/FrmFormsHelper.php +++ b/classes/helpers/FrmFormsHelper.php @@ -262,7 +262,7 @@ public static function get_invalid_error_message( $args ) { } $frm_settings = FrmAppHelper::get_settings( $settings_args ); - $invalid_msg = do_shortcode( $frm_settings->invalid_msg ); + $invalid_msg = '' . do_shortcode( $frm_settings->invalid_msg ) . ''; $field_error_messages = self::get_clickable_field_error_messages( $args ); $invalid_msg .= $field_error_messages ? '' : ''; diff --git a/css/_single_theme.css.php b/css/_single_theme.css.php index 7c45459cc5..d5b9d5ee3d 100644 --- a/css/_single_theme.css.php +++ b/css/_single_theme.css.php @@ -379,6 +379,23 @@ margin-bottom:; } +. .frm_error_style span{ + font-weight: bold;; +} + +. .frm_error_style ul{ + list-style: inside;; + color: ; +} + +. .frm_error_style ul li a{ + color: ; +} + +. .frm_error_style ul li a:hover{ + text-decoration: underline;; +} + . #frm_loading .progress-striped .progress-bar{ background-image:linear-gradient(45deg, 25%, rgba(0, 0, 0, 0) 25%, rgba(0, 0, 0, 0) 50%, 50%, 75%, rgba(0, 0, 0, 0) 75%, rgba(0, 0, 0, 0)); } From ba68f4aed709ff813617b0bade547fc747ed7014 Mon Sep 17 00:00:00 2001 From: Abdi Tolessa <41271840+AbdiTolesa@users.noreply.github.com> Date: Fri, 5 Dec 2025 15:23:55 +0300 Subject: [PATCH 07/20] Fix PHP CS errors --- classes/helpers/FrmFormsHelper.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/classes/helpers/FrmFormsHelper.php b/classes/helpers/FrmFormsHelper.php index 4eeb7fb66a..11c106c261 100644 --- a/classes/helpers/FrmFormsHelper.php +++ b/classes/helpers/FrmFormsHelper.php @@ -312,16 +312,20 @@ public static function get_invalid_error_message( $args ) { * @since x.x * * @param array $args + * * @return string */ private static function get_clickable_field_error_messages( $args ) { $field_error_messages = ''; + if ( empty( $args['errors'] ) ) { return $field_error_messages; } + $field_ids = array_map( function ( $field_plus_id ) { $field_id = str_replace( 'field', '', $field_plus_id ); + if ( strpos( $field_id, '-' ) !== false ) { $field_id = explode( '-', $field_id )[0]; } @@ -330,25 +334,33 @@ function ( $field_plus_id ) { array_keys( $args['errors'] ) ); $field_keys = FrmDb::get_results( 'frm_fields', array( 'id' => $field_ids ), 'id,field_key,type' ); + foreach ( $args['errors'] as $field_plus_id => $error ) { $field_id = str_replace( 'field', '', $field_plus_id ); $row = ''; + if ( strpos( $field_id, '-' ) !== false ) { $field_id_parts = explode( '-', $field_id ); + if ( count( $field_id_parts ) === 3 ) { $field_id = $field_id_parts[0]; $row = '-' . $field_id_parts[2]; } } + $index = array_search( $field_id, array_column( $field_keys, 'id' ), true ); + if ( false === $index ) { continue; } + $html_id = 'field_' . $field_keys[ $index ]->field_key . $row; + if ( in_array( $field_keys[ $index ]->type, array( 'checkbox', 'radio' ), true ) ) { // Needed to focus on the first option when error link is clicked. $html_id .= '-0'; } + $field_error_messages .= '
  • ' . $error . '
  • '; }//end foreach return $field_error_messages; From 401b972e88d30a3659ce141b49d519771d2d1000 Mon Sep 17 00:00:00 2001 From: Abdi Tolessa <41271840+AbdiTolesa@users.noreply.github.com> Date: Fri, 5 Dec 2025 18:14:40 +0300 Subject: [PATCH 08/20] Make code more readable --- classes/helpers/FrmFormsHelper.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/classes/helpers/FrmFormsHelper.php b/classes/helpers/FrmFormsHelper.php index 11c106c261..a538fb60df 100644 --- a/classes/helpers/FrmFormsHelper.php +++ b/classes/helpers/FrmFormsHelper.php @@ -299,10 +299,13 @@ public static function get_invalid_error_message( $args ) { } $frm_settings = FrmAppHelper::get_settings( $settings_args ); - $invalid_msg = '' . do_shortcode( $frm_settings->invalid_msg ) . ''; $field_error_messages = self::get_clickable_field_error_messages( $args ); - $invalid_msg .= $field_error_messages ? '' : ''; + + $invalid_msg = '' . do_shortcode( $frm_settings->invalid_msg ) . ''; + if ( $field_error_messages ) { + $invalid_msg .= ""; + } return apply_filters( 'frm_invalid_error_message', $invalid_msg, $args ); } From 4a03c7da951c6a7eca4a071a1a016e6f132c3280 Mon Sep 17 00:00:00 2001 From: Abdi Tolessa <41271840+AbdiTolesa@users.noreply.github.com> Date: Fri, 5 Dec 2025 20:15:15 +0300 Subject: [PATCH 09/20] Fix PHP CS errors --- classes/helpers/FrmFormsHelper.php | 1 + 1 file changed, 1 insertion(+) diff --git a/classes/helpers/FrmFormsHelper.php b/classes/helpers/FrmFormsHelper.php index a538fb60df..34c71f5df5 100644 --- a/classes/helpers/FrmFormsHelper.php +++ b/classes/helpers/FrmFormsHelper.php @@ -303,6 +303,7 @@ public static function get_invalid_error_message( $args ) { $field_error_messages = self::get_clickable_field_error_messages( $args ); $invalid_msg = '' . do_shortcode( $frm_settings->invalid_msg ) . ''; + if ( $field_error_messages ) { $invalid_msg .= ""; } From b2603a4d9912aa7a1de639804900dc28f8206466 Mon Sep 17 00:00:00 2001 From: Abdi Tolessa <41271840+AbdiTolesa@users.noreply.github.com> Date: Fri, 5 Dec 2025 20:35:25 +0300 Subject: [PATCH 10/20] Show field errors summary for ajax submit --- classes/controllers/FrmEntriesAJAXSubmitController.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/classes/controllers/FrmEntriesAJAXSubmitController.php b/classes/controllers/FrmEntriesAJAXSubmitController.php index 33b1af4b17..91c4fc99ba 100644 --- a/classes/controllers/FrmEntriesAJAXSubmitController.php +++ b/classes/controllers/FrmEntriesAJAXSubmitController.php @@ -96,7 +96,12 @@ public static function ajax_create() { } $response['errors'] = $obj; - $invalid_msg = FrmFormsHelper::get_invalid_error_message( array( 'form' => $form ) ); + $invalid_msg = FrmFormsHelper::get_invalid_error_message( + array( + 'form' => $form, + 'errors' => $errors, + ) + ); $response['error_message'] = FrmFormsHelper::get_success_message( array( 'message' => $invalid_msg, From b28445d791d5b75d36b19e4f235de396005d0937 Mon Sep 17 00:00:00 2001 From: Mike Letellier Date: Wed, 5 Aug 2026 14:32:50 -0300 Subject: [PATCH 11/20] Issue 5886 improvements --- classes/helpers/FrmFormsHelper.php | 58 +++++---- css/_single_theme.css.php | 10 +- tests/phpunit/forms/test_FrmFormsHelper.php | 133 ++++++++++++++++++++ 3 files changed, 171 insertions(+), 30 deletions(-) diff --git a/classes/helpers/FrmFormsHelper.php b/classes/helpers/FrmFormsHelper.php index 0499d44384..a632b729a2 100644 --- a/classes/helpers/FrmFormsHelper.php +++ b/classes/helpers/FrmFormsHelper.php @@ -288,15 +288,14 @@ public static function get_invalid_error_message( $args ) { $settings_args['current_form'] = $args['form']->id; } - $frm_settings = FrmAppHelper::get_settings( $settings_args ); - + $frm_settings = FrmAppHelper::get_settings( $settings_args ); $field_error_messages = self::get_clickable_field_error_messages( $args ); - - $invalid_msg = '' . do_shortcode( $frm_settings->invalid_msg ) . ''; + $invalid_msg = '' . do_shortcode( $frm_settings->invalid_msg ) . ''; if ( $field_error_messages ) { $invalid_msg .= ""; } + return apply_filters( 'frm_invalid_error_message', $invalid_msg, $args ); } @@ -310,27 +309,17 @@ public static function get_invalid_error_message( $args ) { * @return string */ private static function get_clickable_field_error_messages( $args ) { - $field_error_messages = ''; - if ( empty( $args['errors'] ) ) { - return $field_error_messages; + return ''; } - $field_ids = array_map( - function ( $field_plus_id ) { - $field_id = str_replace( 'field', '', $field_plus_id ); - - if ( strpos( $field_id, '-' ) !== false ) { - $field_id = explode( '-', $field_id )[0]; - } - return $field_id; - }, - array_keys( $args['errors'] ) - ); - $field_keys = FrmDb::get_results( 'frm_fields', array( 'id' => $field_ids ), 'id,field_key,type' ); + // Parse each error key once into its field ID and repeater row suffix, skipping + // non-field errors like 'form' or 'spam' that have no input to link to. + $parsed_errors = array(); + $field_ids = array(); foreach ( $args['errors'] as $field_plus_id => $error ) { - $field_id = str_replace( 'field', '', $field_plus_id ); + $field_id = preg_replace( '/^field/', '', $field_plus_id ); $row = ''; if ( strpos( $field_id, '-' ) !== false ) { @@ -342,21 +331,40 @@ function ( $field_plus_id ) { } } - $index = array_search( $field_id, array_column( $field_keys, 'id' ), true ); + if ( ! is_numeric( $field_id ) ) { + continue; + } + + $field_ids[] = (int) $field_id; + $parsed_errors[] = compact( 'field_id', 'row', 'error' ); + } + + if ( ! $field_ids ) { + return ''; + } + + $fields = FrmDb::get_results( 'frm_fields', array( 'id' => $field_ids ), 'id,field_key,type' ); + $ids = array_map( 'intval', array_column( $fields, 'id' ) ); + $field_error_messages = ''; + + foreach ( $parsed_errors as $parsed_error ) { + $index = array_search( (int) $parsed_error['field_id'], $ids, true ); if ( false === $index ) { continue; } - $html_id = 'field_' . $field_keys[ $index ]->field_key . $row; + $field = $fields[ $index ]; + $html_id = 'field_' . $field->field_key . $parsed_error['row']; - if ( in_array( $field_keys[ $index ]->type, array( 'checkbox', 'radio' ), true ) ) { - // Needed to focus on the first option when error link is clicked. + if ( in_array( $field->type, array( 'checkbox', 'radio' ), true ) ) { + // Needed to focus on the first option when the error link is clicked. $html_id .= '-0'; } - $field_error_messages .= '
  • ' . $error . '
  • '; + $field_error_messages .= '
  • ' . esc_html( $parsed_error['error'] ) . '
  • '; }//end foreach + return $field_error_messages; } diff --git a/css/_single_theme.css.php b/css/_single_theme.css.php index 90ed2bf442..3906dcc6b6 100644 --- a/css/_single_theme.css.php +++ b/css/_single_theme.css.php @@ -423,20 +423,20 @@ } . .frm_error_style span{ - font-weight: bold;; + font-weight: bold; } . .frm_error_style ul{ - list-style: inside;; - color: ; + list-style: inside; + color: var(--error-text); } . .frm_error_style ul li a{ - color: ; + color: var(--error-text); } . .frm_error_style ul li a:hover{ - text-decoration: underline;; + text-decoration: underline; } diff --git a/tests/phpunit/forms/test_FrmFormsHelper.php b/tests/phpunit/forms/test_FrmFormsHelper.php index 41a294d59d..0204b3e833 100644 --- a/tests/phpunit/forms/test_FrmFormsHelper.php +++ b/tests/phpunit/forms/test_FrmFormsHelper.php @@ -146,4 +146,137 @@ private function create_form_with_custom_style_value( $custom_style ) { ) ); } + + /** + * The invalid error message should include a list of links that jump to each field that failed validation. + * + * @covers FrmFormsHelper::get_invalid_error_message + */ + public function test_get_invalid_error_message_builds_clickable_field_links() { + $this->form = $this->factory->form->create_and_get(); + + $text_id = $this->create_field_with_key( 'text', 'my_text' ); + $select_id = $this->create_field_with_key( 'select', 'my_select' ); + $checkbox_id = $this->create_field_with_key( 'checkbox', 'my_checkbox' ); + $radio_id = $this->create_field_with_key( 'radio', 'my_radio' ); + + $message = FrmFormsHelper::get_invalid_error_message( + array( + 'form' => $this->form, + 'errors' => array( + 'field' . $text_id => 'Text is required', + 'field' . $select_id => 'Select is required', + 'field' . $checkbox_id => 'Checkbox is required', + 'field' . $radio_id => 'Radio is required', + ), + ) + ); + + // The base invalid message is wrapped in a span, and the links are inside a list. + $this->assertStringContainsString( '