You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Fix duplicate label/for association on checkbox and GDPR fields - #3465
Both the general checkbox field and the GDPR/privacy checkbox wrap their <input> inside a <label> that also carries a for attribute pointing at that same input's id. Safari VoiceOver announces the label twice under this double-association pattern.
Dropped the redundant for attribute from both wrapping labels -- the wrap alone already associates label and input, so only one association method is needed (per the issue's own suggested fix).
Verified
Ran locally via the real WP-core PHPUnit test lib (~/Claude/test-sites/formidable/wordpress-develop), not just inferred from reading the code:
Wrote a regression test for each field asserting the rendered HTML has a wrapping <label> with no for attribute before the <input>.
Confirmed red against the unfixed markup (both new tests failed on the actual pre-fix HTML, matching the reported bug exactly).
Applied the fix, confirmed green.
Ran the full --group fields suite (117 tests) after the fix -- everything green except one pre-existing, unrelated failure (test_FrmFieldCombo::test_print_input_atts, a PHP 8.5 deprecation-notice-pollution issue reproducible on a clean origin/master checkout with none of this PR's changes applied).
CI: PHPCS/PHP CS Fixer/ESLint/Oxlint/Rector/PHPStan/Mago/Psalm/Scrutinizer all pass as of this update. run tests requested for real CI PHPUnit signal (PHP 7.4/8.0 against WP trunk) -- local verification above ran PHP 8.5 only, and Lite's local PHPUnit is known to drift from the CI matrix.
DeepSource: PHP currently reports 4 findings, all analyzer stub gaps rather than real issues: two flag FrmSettings::$enable_gdpr as an undefined property, but FrmSettings::__construct() assigns settings dynamically ($this->{$setting_name} = $setting;), a pre-existing pattern static analysis can't see through; the other two flag assertMatchesRegularExpression/assertDoesNotMatchRegularExpression as undefined, but both are real PHPUnit 9.1+ methods (confirmed executing locally) -- this PR is just the first place in the repo to use them, so there's no earlier precedent.
Grepped tests/cypress/ for a label[for=...] selector that could target either changed field -- none found, so no e2e breakage expected from dropping the attribute.
Known related instances not covered by this PR
classes/views/frm-fields/front-end/radio-field.php and classes/views/frm-fields/front-end/product-radio.php have the same wrap+for/id double-association pattern on their own option labels. The linked issue only reports checkbox/GDPR, so this PR stays scoped to those two files -- flagging here rather than silently leaving it unmentioned, in case a follow-up issue is wanted for radio/product-radio.
The option label wraps the checkbox input and also carries a matching
for/id pair, so Safari VoiceOver announces the label twice. Drop the
for attribute since the wrap alone already associates the label with
its input.
FixesStrategy11/formidable-pro#6750
Both new checkbox/GDPR tests checked the same thing (label wraps
input with no redundant for) via a byte-identical regex pair --
factor it into a helper next to this base class's other rendered-HTML
a11y assertions instead of keeping two copies in lockstep.
We reviewed changes in cbbdf70...d235dc9 on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.
AI Review is run only on demand for your team. We're only showing results of static analysis review right now. To trigger AI Review, comment @deepsourcebot review on this thread.
The reason will be displayed to describe this comment to others. Learn more.
Access to an undefined property FrmSettings::$enable_gdpr
This issue is raised when an attempt is made to access an undefined property.
This may not have been intended, and it is advisable to give the code another look to make sure the property is defined in the scope it is used in.
CI PHPCS flagged Squiz.Commenting.PostStatementComment on the
inline <?php // ... ?> comment right after the alternative-syntax
if-statement. Move it up into the existing PHP block above instead.
The reason will be displayed to describe this comment to others. Learn more.
Approve (not a clean approve — 3 non-blocking notes below).
The fix is correct and well-verified. Both checkbox-field.php and gdpr-field.php dropped the redundant for attribute from a <label> that already wraps its <input> — that double association is what makes Safari VoiceOver announce the label twice. Confirmed by reading the surrounding render logic in both files (the <label> only opens when hide_label is empty / GDPR is enabled, and always directly wraps the matching input).
Independently re-ran the two new regression tests myself against the real WP-core PHPUnit harness (not just trusting the PR description's own claim): both pass at the PR's head, and both genuinely fail against the pre-fix markup when I reverted just the two view files back to origin/master while keeping the new tests — real red→green proof, not a test that can't fail. enable_gdpr/assertMatchesRegularExpression DeepSource findings are false positives exactly as the PR description explains: enable_gdpr is a dynamically-assigned FrmSettings property used the same way in 3+ other places already in this codebase, and assertMatchesRegularExpression/assertDoesNotMatchRegularExpression are native PHPUnit 9.1+ methods (confirmed — this repo runs PHPUnit 9.6.36).
Non-blocking notes:
The PR description already flags radio-field.php/product-radio.php as having the identical wrap+for double-association pattern, scoped out because the linked issue only reports checkbox/GDPR. Confirmed that disclosure is accurate — both files build the label's for and the input's id from the same $html_id . '-' . $opt_key expression, same shape as the two files this PR fixes. Worth a follow-up issue so it doesn't get lost.
New one, not mentioned in the PR: gdpr-field.php's other branch — the "GDPR field is disabled" admin notice shown to users who can edit forms (current_user_can( 'frm_edit_forms' ), line 33) — still has <label for="<?php echo esc_attr( $label_id ); ?>">, but that branch never renders any element with that id (no <input> at all in this branch). That's a plain dangling for (points at nothing), a different bug class from the duplicate-announcement one this PR fixes, but same file/area/concern. Simplest fix: drop the for there too, since the label has no input to associate with.
CI's PHPUnit jobs (PHP 8/PHP 7.4 against WP trunk) are still pending as of this review — gated behind a run tests request per the PR description, not auto-run. Local verification above (PHP 8.5, real WP-core harness) is real signal but isn't the CI matrix; noting as a scope note, not a blocker, since PHPCS/PHPStan/Psalm/Mago/Rector/ESLint/Oxlint are all green and the local run is a genuine red/green regression proof.
The label in this branch has no input to associate with (per
franky-review note 2 on this PR) -- same simplest fix already applied to
the two duplicate-association instances this PR fixes.
Regression test: red against the pre-fix markup (real WP-core PHPUnit
harness), green after. Full --group fields suite (117 tests) still green
except the pre-existing, unrelated test_FrmFieldCombo::test_print_input_atts
PHP 8.5 deprecation-noise failure already documented on this PR.
Filed formidable-forms#3475 for the radio-field.php/product-radio.php instances so it doesn't get lost.
Fixed — dropped the dangling for on the GDPR-disabled admin notice label. Red test against the pre-fix markup (real WP-core PHPUnit harness), green after; full --group fields suite (117 tests) still green except the same pre-existing test_FrmFieldCombo::test_print_input_atts deprecation-noise failure.
PHP 7.4/8 test jobs are pending again on the new push — same run tests gate as before, no action needed here.
The reason will be displayed to describe this comment to others. Learn more.
Access to an undefined property FrmSettings::$enable_gdpr
This issue is raised when an attempt is made to access an undefined property.
This may not have been intended, and it is advisable to give the code another look to make sure the property is defined in the scope it is used in.
$this->assertStringContainsString( 'GDPR field is disabled', $html, 'Expected the disabled-notice branch to render' );
$this->assertDoesNotMatchRegularExpression( '/<label[^>]*\bfor=/', $html, 'The disabled-notice label has no input to associate with, so it should not carry a for attribute' );
The reason will be displayed to describe this comment to others. Learn more.
Re-review at the new head. Note #2 from the prior review (dangling for on the GDPR-disabled admin notice) is correctly fixed, note #1's follow-up issue (#3475) is filed and open, and note #3's PHPUnit CI is now green (PHP 7.4/8 both pass). The new tests (test_FrmFieldCheckbox, test_FrmFieldGdpr) are real, non-hollow regressions — assert_label_wraps_input_without_for in FrmUnitTest.php asserts both the wrap and the absence of a duplicate for, and test_disabled_notice_label_has_no_dangling_for_attribute correctly exercises the admin-only disabled branch (with the wp_get_current_user()->get_role_caps() cache-refresh workaround noted inline).
No visual/UX surface on this one — it's an attribute-level (DOM) accessibility change, source- and test-verified only, not exercised in a browser.
Two things before this can merge:
Blocking — real CI failure, not a false positive.Inspections / Run PHPCS inspection is failing for a genuine reason this time: tests/phpunit/fields/test_FrmFieldGdpr.php:78 exceeds the 180-char line limit (183 chars). Trivial fix — wrap the assertion call across lines (see inline suggestion).
Non-blocking, same shape as note #1's follow-up. Neither this PR nor its new tests touch the aria-labelledby="<?php echo esc_attr( $label_id ); ?>" on the wrapping <div> in gdpr-field.php's disabled-notice branch (line 32). That's a pre-existing bug (confirmed present in origin/master before this PR too, so not introduced here) — the disabled-notice branch has no <input id="..."> or any element carrying $label_id at all, so the aria-labelledby reference is dangling and the group has no accessible name for AT. Same file/area as this PR's own a11y fix; worth a follow-up issue like #3475 so it doesn't get lost.
$this->assertStringContainsString( 'GDPR field is disabled', $html, 'Expected the disabled-notice branch to render' );
$this->assertDoesNotMatchRegularExpression( '/<label[^>]*\bfor=/', $html, 'The disabled-notice label has no input to associate with, so it should not carry a for attribute' );
The reason will be displayed to describe this comment to others. Learn more.
This line is 183 characters, over this repo's 180-char PHPCS limit — that's the real, currently-failing Run PHPCS inspection check (not one of the DeepSource false positives from the earlier review). Wrap the call:
Suggested change
$this->assertDoesNotMatchRegularExpression('/<label[^>]*\bfor=/', $html, 'The disabled-notice label has no input to associate with, so it should not carry a for attribute' );
$this->assertDoesNotMatchRegularExpression(
'/<label[^>]*\bfor=/',
$html,
'The disabled-notice label has no input to associate with, so it should not carry a for attribute'
The reason will be displayed to describe this comment to others. Learn more.
Non-blocking, out of this PR's stated scope (confirmed present in origin/master before this PR, not introduced by this fix): this <div>'s aria-labelledby="<?php echo esc_attr( $label_id ); ?>" points at frm-gdpr-accept-<field_id>, but this disabled-notice branch never renders any element with that id (only the other, enabled branch's <input> on line 24 carries it, and the two branches are mutually exclusive). So the group has a dangling aria-labelledby reference here — no accessible name for AT on the "GDPR field is disabled" notice. Same file/area as this PR's own fix; worth its own follow-up issue the way #3475 covers radio/product-radio.
Line 78 was 183 chars, over this repo's 180-char PHPCS limit -- the
currently-failing Run PHPCS inspection check. Wraps the call across
lines, no behavior change.
The reason will be displayed to describe this comment to others. Learn more.
Re-review at the new head. Both open items from my last Request Changes are resolved:
The PHPCS line-length failure (test_FrmFieldGdpr.php:78) is fixed -- the suggested wrap was taken verbatim. Confirmed the fix is whitespace-only (no assertion/behavior change) by diffing the two versions, and confirmed real green CI for this exact commit (d235dc9): PHPCS, PHPStan, Psalm, Mago, both PHPUnit matrix jobs (PHP 7.4 and PHP 8 against WP trunk), and the syntax/spell/DeepScan/Scrutinizer checks all show success for this head SHA specifically (the PR checks snapshot shows several as skipping because of a second, later labeled event that correctly no-ops per this repo's run-analysis label-gated workflow condition -- the actual synchronize-triggered runs for this commit are the real, currently-passing ones).
The dangling aria-labelledby on the disabled-notice branch (my non-blocking note) is filed as a proper follow-up, formidable-forms#3478, same treatment as note #1's #3475 -- confirmed both issues are real and open.
Nothing outstanding. No visual/UX surface here (attribute-level DOM change, not pixel-visible) -- verification is source- and regression-test-based, consistent with the prior review.
Fixes#3475. The wrap alone already associates each option's <label>
with its <input>; adding a for/id pair on top makes Safari VoiceOver
announce the label twice, same bug #3465 fixed on checkbox/GDPR.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What was broken
Both the general checkbox field and the GDPR/privacy checkbox wrap their
<input>inside a<label>that also carries aforattribute pointing at that same input'sid. Safari VoiceOver announces the label twice under this double-association pattern.Files:
classes/views/frm-fields/front-end/checkbox-field.phpclasses/views/frm-fields/front-end/gdpr/gdpr-field.phpWhat changed
Dropped the redundant
forattribute from both wrapping labels -- the wrap alone already associates label and input, so only one association method is needed (per the issue's own suggested fix).Verified
Ran locally via the real WP-core PHPUnit test lib (
~/Claude/test-sites/formidable/wordpress-develop), not just inferred from reading the code:<label>with noforattribute before the<input>.--group fieldssuite (117 tests) after the fix -- everything green except one pre-existing, unrelated failure (test_FrmFieldCombo::test_print_input_atts, a PHP 8.5 deprecation-notice-pollution issue reproducible on a cleanorigin/mastercheckout with none of this PR's changes applied).run testsrequested for real CI PHPUnit signal (PHP 7.4/8.0 against WP trunk) -- local verification above ran PHP 8.5 only, and Lite's local PHPUnit is known to drift from the CI matrix.DeepSource: PHPcurrently reports 4 findings, all analyzer stub gaps rather than real issues: two flagFrmSettings::$enable_gdpras an undefined property, butFrmSettings::__construct()assigns settings dynamically ($this->{$setting_name} = $setting;), a pre-existing pattern static analysis can't see through; the other two flagassertMatchesRegularExpression/assertDoesNotMatchRegularExpressionas undefined, but both are real PHPUnit 9.1+ methods (confirmed executing locally) -- this PR is just the first place in the repo to use them, so there's no earlier precedent.tests/cypress/for alabel[for=...]selector that could target either changed field -- none found, so no e2e breakage expected from dropping the attribute.Known related instances not covered by this PR
classes/views/frm-fields/front-end/radio-field.phpandclasses/views/frm-fields/front-end/product-radio.phphave the same wrap+for/id double-association pattern on their own option labels. The linked issue only reports checkbox/GDPR, so this PR stays scoped to those two files -- flagging here rather than silently leaving it unmentioned, in case a follow-up issue is wanted for radio/product-radio.Fixes Strategy11/formidable-pro#6750