From b28445d791d5b75d36b19e4f235de396005d0937 Mon Sep 17 00:00:00 2001 From: Mike Letellier Date: Wed, 5 Aug 2026 14:32:50 -0300 Subject: [PATCH 1/7] 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( '