Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions classes/controllers/FrmEntriesAJAXSubmitController.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ public static function ajax_create() {
'form' => $form,
'entry_id' => 0,
'class' => FrmFormsHelper::form_error_class(),
'role' => 'alert',
)
);
} else {
Expand Down
116 changes: 88 additions & 28 deletions classes/helpers/FrmFormsHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 = '<span>' . do_shortcode( $frm_settings->invalid_msg ) . '</span>';
$invalid_msg = '<span>' . do_shortcode( $frm_settings->invalid_msg ) . '</span>';

if ( $field_error_messages ) {
$invalid_msg .= "<ul>$field_error_messages</ul>";
}

return apply_filters( 'frm_invalid_error_message', $invalid_msg, $args );

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.

}

Expand All @@ -310,30 +309,20 @@ 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 ) {
if ( str_contains( $field_id, '-' ) ) {
$field_id_parts = explode( '-', $field_id );

if ( count( $field_id_parts ) === 3 ) {
Expand All @@ -342,24 +331,90 @@ 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,field_order,form_id' );
$fields_by_id = array();

foreach ( $fields as $field ) {
$fields_by_id[ (int) $field->id ] = $field;
}

// Error messages are admin configured and may include shortcodes, so allow the same
// inline formatting Formidable permits elsewhere while stripping anything unsafe. Anchors
// are intentionally excluded so a message link cannot nest inside the summary link.
$allowed_tags = array( 'strong', 'b', 'em', 'i', 'u', 'span', 'code', 'br', 'sub', 'sup', 'mark', 'small' );
$field_error_messages = '';

foreach ( $parsed_errors as $parsed_error ) {
$field_id = (int) $parsed_error['field_id'];

if ( false === $index ) {
if ( ! isset( $fields_by_id[ $field_id ] ) ) {
continue;
}

$html_id = 'field_' . $field_keys[ $index ]->field_key . $row;
$field = $fields_by_id[ $field_id ];
$error = FrmAppHelper::kses( $parsed_error['error'], $allowed_tags );

if ( in_array( $field_keys[ $index ]->type, array( 'checkbox', 'radio' ), true ) ) {
// Needed to focus on the first option when error link is clicked.
if ( ! self::error_field_is_linkable( $field ) ) {
// The field has no focusable input on the page being shown (a hidden field, or a
// field on another page of a multi-page form), so list the error as plain text
// rather than a link that would go nowhere when clicked.
$field_error_messages .= '<li>' . $error . '</li>';
continue;
}

$html_id = 'field_' . $field->field_key . $parsed_error['row'];

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 .= '<li><a href="#' . $html_id . '">' . $error . '</a></li>';
$field_error_messages .= '<li><a href="#' . esc_attr( $html_id ) . '">' . $error . '</a></li>';
}//end foreach

return $field_error_messages;
}

/**
* Whether an error summary should link to the field, or just list its message as plain text.
*
* A link is only useful when the field renders a focusable input that is actually on the page
* being shown. Hidden and user ID fields render as hidden inputs that cannot receive focus, and
* a field on another page of a multi-page form is not visible, so neither should be linked.
*
* @since x.x
*
* @param stdClass $field Field row with at least type, field_order and form_id.
*
* @return bool
*/
private static function error_field_is_linkable( $field ) {
if ( in_array( $field->type, array( 'hidden', 'user_id' ), true ) ) {
// Hidden inputs cannot receive focus, so there is nothing to link to.
return false;
}

if ( ! is_callable( 'FrmProFieldsHelper::field_on_current_page' ) ) {
// Multi-page forms are a Pro feature; in Lite every field is on the only page.
return true;
}

// On a multi-page form, do not link a field that is on a page other than the one being shown.
return FrmProFieldsHelper::field_on_current_page( $field );
}

/**
* @param array $atts {
* The success message details.
Expand All @@ -368,6 +423,9 @@ function ( $field_plus_id ) {
* @type stdClass $form
* @type int $entry_id
* @type string $class
* @type string $role Optional. ARIA live region role for the wrapper. Defaults to 'status'.
* Pass 'alert' when the message reports a validation error so it is
* announced assertively, matching the non-ajax error wrapper.
* }
*
* @return string
Expand Down Expand Up @@ -395,7 +453,9 @@ public static function get_success_message( $atts ) {
}

$message = do_shortcode( $message );
return '<div class="' . esc_attr( $atts['class'] ) . '" role="status">' . $message . '</div>';
$role = $atts['role'] ?? 'status';

return '<div class="' . esc_attr( $atts['class'] ) . '" role="' . esc_attr( $role ) . '">' . $message . '</div>';
}

/**
Expand Down
11 changes: 6 additions & 5 deletions css/_single_theme.css.php
Original file line number Diff line number Diff line change
Expand Up @@ -423,20 +423,21 @@
}

.<?php echo esc_html( $style_class ); ?> .frm_error_style span{
font-weight: bold<?php echo esc_html( $important ); ?>;;
font-weight: bold<?php echo esc_html( $important ); ?>;
}

.<?php echo esc_html( $style_class ); ?> .frm_error_style ul{
list-style: inside<?php echo esc_html( $important ); ?>;;
color: <?php echo esc_html( $error_text . $important ); ?>;
list-style: inside<?php echo esc_html( $important ); ?>;
color: var(--error-text)<?php echo esc_html( $important ); ?>;
margin-bottom: 0<?php echo esc_html( $important ); ?>;
}

.<?php echo esc_html( $style_class ); ?> .frm_error_style ul li a{
color: <?php echo esc_html( $error_text . $important ); ?>;
color: var(--error-text)<?php echo esc_html( $important ); ?>;
}

.<?php echo esc_html( $style_class ); ?> .frm_error_style ul li a:hover{
text-decoration: underline<?php echo esc_html( $important ); ?>;;
text-decoration: underline<?php echo esc_html( $important ); ?>;
}

<?php if ( $pro_is_installed ) { ?>
Expand Down
7 changes: 7 additions & 0 deletions stubs.php
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,13 @@ public static function replace_non_standard_formidable_shortcodes( $args, &$valu
*/
public static function is_field_visible_to_user( $field ) {
}
/**
* @param array|int|object $field
*
* @return bool
*/
public static function field_on_current_page( $field ) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Method FrmProFieldsHelper::field_on_current_page() should return bool but return statement is missing


This issue is raised if a method with a return type does not have a return statement of an appropriate type.

}
}
class FrmViewsAppHelper {
/**
Expand Down
Loading
Loading