-
Notifications
You must be signed in to change notification settings - Fork 42
Improve subfield error handling #3489
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
1b6e83e
f7e68a6
907767a
dd3a05e
2319efa
c21df3f
85ddc78
f82ae8f
7a3abd0
c6b94c3
5164e8d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -404,7 +404,8 @@ protected function print_input_atts( $args ) { | |
| $field['default_value'] = ''; | ||
|
|
||
| if ( ! empty( $sub_field['name'] ) ) { | ||
| $field['subfield_name'] = $sub_field['name']; | ||
| $field['subfield_name'] = $sub_field['name']; | ||
| $field['subfield_label'] = $this->get_sub_field_label( $sub_field['name'] ); | ||
| } | ||
|
|
||
| do_action( 'frm_field_input_html', $field ); | ||
|
|
@@ -437,22 +438,104 @@ public function validate( $args ) { | |
| return $errors; | ||
| } | ||
|
|
||
| $blank_msg = FrmFieldsHelper::get_error_msg( $this->field, 'blank' ); | ||
| $sub_fields = $this->get_processed_sub_fields(); | ||
| $required_count = 0; | ||
| $missing = array(); | ||
|
|
||
| // Validate not empty. | ||
| foreach ( $sub_fields as $name => $sub_field ) { | ||
| if ( ! empty( $sub_field['optional'] ) || ! empty( $args['value'][ $name ] ) ) { | ||
| foreach ( $this->get_processed_sub_fields() as $name => $sub_field ) { | ||
| if ( ! empty( $sub_field['optional'] ) ) { | ||
| continue; | ||
| } | ||
|
|
||
| $errors[ 'field' . $args['id'] . '-' . $name ] = ''; | ||
| $errors[ 'field' . $args['id'] ] = $blank_msg; | ||
| ++$required_count; | ||
|
|
||
| if ( empty( $args['value'][ $name ] ) ) { | ||
| $missing[] = $name; | ||
| } | ||
| } | ||
|
|
||
| if ( ! $missing ) { | ||
| return $errors; | ||
| } | ||
|
|
||
| if ( count( $missing ) === $required_count ) { | ||
| // Nothing was filled in, so show one error for the whole field. The empty sub field | ||
| // errors flag each required input without repeating the message under every one. | ||
| foreach ( $missing as $name ) { | ||
| $errors[ 'field' . $args['id'] . '-' . $name ] = ''; | ||
| } | ||
|
|
||
| $errors[ 'field' . $args['id'] ] = FrmFieldsHelper::get_error_msg( $this->field, 'blank' ); | ||
|
|
||
| return $errors; | ||
| } | ||
|
|
||
| // Only some sub fields are missing, so name each one in its own error. | ||
| foreach ( $missing as $name ) { | ||
| $errors[ 'field' . $args['id'] . '-' . $name ] = $this->get_sub_field_error_msg( $name, 'blank' ); | ||
| } | ||
|
|
||
| return $errors; | ||
| } | ||
|
|
||
| /** | ||
| * Gets the label a sub field is referred to by in its error messages. | ||
| * | ||
| * The sub field description is used when it is a short phrase, since that is the label shown | ||
| * under the input. Otherwise the sub field label is combined with the field label, like "Address Line 1". | ||
| * | ||
| * @since x.x | ||
| * | ||
| * @param string $name Sub field name, like 'first' or 'line1'. | ||
| * | ||
| * @return string | ||
| */ | ||
| public function get_sub_field_label( $name ) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Not a regression against the old code (which never substituted per-sub-field description text into a sentence at all), so non-blocking — but worth a fallback to the sub-field's plain label when the description doesn't read as a short phrase, or at least a doc note on the authoring convention this now assumes.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed. The description is only used when it is a short phrase (4 words or fewer, no trailing sentence punctuation); otherwise it falls back to the field + sub field label. Added a PHPUnit case. Also shared the clone-and-rename error message code between FrmFieldCombo and FrmFieldsController via FrmFieldsHelper::get_error_msg_for_name, and the AJAX [if error] wrapper now applies to keys like 12-first. |
||
| $desc = FrmField::get_option( $this->field, $name . '_desc' ); | ||
|
|
||
| if ( is_string( $desc ) && $this->is_short_phrase( $desc ) ) { | ||
| return $desc; | ||
| } | ||
|
|
||
| $label = $this->sub_fields[ $name ]['label'] ?? ''; | ||
|
|
||
| if ( ! $label ) { | ||
| return (string) $this->get_field_column( 'name' ); | ||
| } | ||
|
|
||
| /* translators: 1: Field label, 2: Sub field label */ | ||
| return sprintf( __( '%1$s %2$s', 'formidable' ), $this->get_field_column( 'name' ), $label ); | ||
| } | ||
|
|
||
| /** | ||
| * Checks if text reads as a label, so it can be the subject of an error message. A sentence | ||
| * would not. | ||
| * | ||
| * @since x.x | ||
| * | ||
| * @param string $text | ||
| * | ||
| * @return bool | ||
| */ | ||
| private function is_short_phrase( $text ) { | ||
| $text = trim( $text ); | ||
| return '' !== $text && count( (array) preg_split( '/\s+/', $text ) ) <= 4 && ! preg_match( '/[.!?:;]$/', $text ); | ||
| } | ||
|
|
||
| /** | ||
| * Gets an error message for a single sub field, with the sub field label in place of the | ||
| * field label. | ||
| * | ||
| * @since x.x | ||
| * | ||
| * @param string $name Sub field name, like 'first' or 'line1'. | ||
| * @param string $error Error type, like 'blank'. | ||
| * | ||
| * @return string | ||
| */ | ||
| public function get_sub_field_error_msg( $name, $error ) { | ||
| return FrmFieldsHelper::get_error_msg_for_name( $this->field, $error, $this->get_sub_field_label( $name ) ); | ||
| } | ||
|
|
||
| /** | ||
| * Gets export headings. | ||
| * | ||
|
|
@@ -507,10 +590,17 @@ protected function should_print_hidden_sub_fields() { | |
| * @return array | ||
| */ | ||
| public function get_inputs_container_attrs() { | ||
| return array( | ||
| $attrs = array( | ||
| 'class' => 'frm_combo_inputs_container', | ||
| 'id' => 'frm_combo_inputs_container_' . $this->field_id, | ||
| ); | ||
|
|
||
| if ( $this->field && $this->get_field_column( 'required' ) ) { | ||
| // JS validation shows this for the whole field when every required sub field is empty. | ||
| $attrs['data-reqmsg'] = FrmFieldsHelper::get_error_msg( $this->field, 'blank' ); | ||
| } | ||
|
|
||
| return $attrs; | ||
| } | ||
|
|
||
| /** | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -391,6 +391,27 @@ | |
| border-style:var(--border-style-error)<?php echo esc_html( $important ); ?>; | ||
| } | ||
|
|
||
| /* A combo field error only marks the sub fields that failed, not optional or filled ones. */ | ||
| .<?php echo esc_html( $style_class ); ?> .frm_blank_field .frm_combo_inputs_container > .frm_form_field:not(.frm_blank_field) input:not(:focus), | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| .<?php echo esc_html( $style_class ); ?> .frm_blank_field .frm_combo_inputs_container > .frm_form_field:not(.frm_blank_field) select:not(:focus), | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| .<?php echo esc_html( $style_class ); ?> .frm_blank_field .frm_combo_inputs_container > .frm_form_field:not(.frm_blank_field) textarea:not(:focus), | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| <?php if ( $pro_is_installed ) { ?> | ||
| .<?php echo esc_html( $style_class ); ?> .frm_blank_field .frm_combo_inputs_container > .frm_form_field:not(.frm_blank_field) .mce-edit-area iframe, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| <?php } ?> | ||
| <?php if ( $use_chosen_js ) { ?> | ||
| .<?php echo esc_html( $style_class ); ?> .frm_blank_field .frm_combo_inputs_container > .frm_form_field:not(.frm_blank_field) .chosen-container-multi .chosen-choices, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| .<?php echo esc_html( $style_class ); ?> .frm_blank_field .frm_combo_inputs_container > .frm_form_field:not(.frm_blank_field) .chosen-container-single .chosen-single, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| <?php } ?> | ||
| .<?php echo esc_html( $style_class ); ?> .frm_blank_field .frm_combo_inputs_container > .frm_form_field:not(.frm_blank_field) .frm-g-recaptcha iframe, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| .<?php echo esc_html( $style_class ); ?> .frm_blank_field .frm_combo_inputs_container > .frm_form_field:not(.frm_blank_field) .g-recaptcha iframe, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| .<?php echo esc_html( $style_class ); ?> .frm_blank_field .frm_combo_inputs_container > .frm_form_field:not(.frm_blank_field) .frm-card-element.StripeElement { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| color:var(--text-color)<?php echo esc_html( $important ); ?>; | ||
| background-color:var(--bg-color)<?php echo esc_html( $important ); ?>; | ||
| border-color:var(--border-color)<?php echo esc_html( $important ); ?>; | ||
| border-width:var(--field-border-width)<?php echo esc_html( $important ); ?>; | ||
| border-style:var(--field-border-style)<?php echo esc_html( $important ); ?>; | ||
| } | ||
|
|
||
| <?php | ||
| // Only include this style when the signatures add-on is active | ||
| if ( class_exists( 'FrmSigField' ) ) : | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The JS and server messages for a sub field can differ once its description is customised. Reproduced on the preview env with a required Name field whose
last_descis set to "Last":get_sub_field_label()intends).data-reqmsg, which JS validation shows: "Last Name cannot be blank."With the default descriptions (the same text as the sub field label) the two happen to match, so the stated goal of consistent errors holds out of the box but not after someone edits a description. Likely cause, not confirmed: the field array in
$atts['field']at this hook does not carry the*_descoptions or the field name, soget_sub_field_label()falls to the sub field label only. If so, build$field_objfrom the full field (or haveget_sub_field_label()read the saved options) so this line gets the same string asFrmFieldCombo::get_sub_field_error_msg().