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
Drop blank options when using Bulk Edit Options - #3345
Using the form builder's "Bulk Edit Options" on a radio/checkbox/select (or Likert, via frm_bulk_edit_field_types) field, a blank line in the textarea (or a label| line with nothing after the separator in separate-value mode) became an option with an empty string value. FrmAppHelper::check_selected() compares the field's current value against each option's value with a loose ==, so when the field has no submitted value yet (''), that blank option matches and renders checked/selected by default.
Related: Strategy11/formidable-pro#3385
What changed
FrmFieldsController::import_options() drops blank lines via parse_bulk_edit_opts(), and blank-value separate-value halves (label|) via remove_blank_separated_values() — except a select field's leading blank line, which is a legitimate, renderer-supported "please select" default (dropdown-field.php's own placeholder/skip handling) rather than the check_selected() collision this PR fixes. Behavior change for dropdowns: re-saving Bulk Edit Options on an existing select field no longer drops a leading blank option it already had.
A blank-label half (|value) is left alone: check_selected() never compares the label, so it doesn't reproduce the bug, and dropdown-field.php renders a blank-label option deliberately.
Mirrors the equivalent blank-line filtering formidable-pro's FrmProFieldProduct bulk edit for Product fields already does.
How it was verified
Red/green locally against the real PHPUnit suite (~/Claude/test-sites/formidable/wordpress-develop): unit tests for both private helpers (blank-line dropping, '0' survives, select keeps a leading blank/radio drops it, blank-value separate halves drop, blank-label halves survive) plus new integration tests driving the real frm_import_options AJAX action end-to-end (tests/phpunit/fields/test_FrmFieldsAjax.php) covering the label|value split ordering and the other_* key surviving the reindex. Full fields (114) and ajax (9) groups green afterward (one pre-existing, unrelated failure in test_FrmFieldCombo::test_print_input_atts, a PHP 8.5 deprecation notice leaking into output buffering, present before this change too).
Self-reviewed (security + correctness/simplify lenses) before pushing — security clean; correctness caught that the blank-label drop I'd added wasn't justified (reverted, see above).
We reviewed changes in 2cb9287...c6e1093 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.
Request Changes.
The collision itself is real and correctly diagnosed — FrmAppHelper::check_selected() compares with a loose ==, so an option whose value is '' matches an unset field value and renders pre-selected. The '0' test is the right guard to have written; a bare array_filter() here would have silently eaten a legitimate zero option, and array_values() keeps the reindex from colliding with the other_* string keys merged in at :387. Two blocking items.
Blocking
PHPCS is red, and all three errors are PR-introduced and auto-fixable ([x]) — FrmFieldsController.php:414, test_FrmFieldsController.php:59 and :96. phpcbf clears all three.
The filter runs for every bulk-edit type, but a leading blank option is legitimate and renderer-supported on select. Opening Bulk Edit on such a dropdown and saving now silently drops it, even with no other edit. FrmFieldsController.php:354
Non-blocking
remove_blank_separated_values() only inspects the value half, so a |value line survives as an option with an empty label. FrmFieldsController.php:429
Both tests call the new private helpers directly, so import_options()'s own wiring — the ordering against the label|value split, and the interaction with the other_* merge — is still uncovered. test_FrmFieldsController.php:42
CI otherwise green (PHPUnit on PHP 7.4 and 8, PHP CS Fixer, Rector, ESLint, Oxlint, Stylelint, both syntax legs). Branch is current with master (0 behind). Source review only — no visual surface in the diff itself, though finding 2 is about rendered output and is source-derived rather than browser-confirmed ([Likely], from dropdown-field.php and add_placeholder_to_select()).
The reason will be displayed to describe this comment to others. Learn more.
Blocking: PHPCS is red, and all three errors come from this diff.
classes/controllers/FrmFieldsController.php
414 | ERROR | [x] Unnecessary blank line in short function with only 2 ...
tests/phpunit/fields/test_FrmFieldsController.php
59 | ERROR | [x] Missing docblock with @param tags (detected types from call ...
96 | ERROR | [x] Missing docblock with @param tags (detected types from call ...
:414 is the blank line between the array_map() assignment and the return in parse_bulk_edit_opts(). :59 and :96 are the two new private test helpers. All three are marked [x], so phpcbf fixes them — but confirm what it does to :414 rather than accepting it blind, since collapsing that function is a readability call, not just whitespace.
Worth noting the repo's PHPCS job lints the whole tree, so a red check here doesn't always mean the diff — this time it does. Nothing else in the file or the suite is flagged.
The reason will be displayed to describe this comment to others. Learn more.
Fixed. Also: parse_bulk_edit_opts() grew past the short-function threshold once it took the select-type branch, so PHPCS no longer flags the blank line at all on the current diff (confirmed clean, bare phpcs, no --standard override).
The reason will be displayed to describe this comment to others. Learn more.
Blocking: this drops blank options for every bulk-edit type, but the defect is specific to the types where a blank option is never wanted. On select a leading blank option is a supported choice, not a mistake.
The blank option is skipped only when a placeholder is set — and it's skipped precisely because add_placeholder_to_select() (FrmFieldsController:920) already emitted its own <option value=""> and the renderer is avoiding a duplicate. With no placeholder configured, add_placeholder_to_select() returns false without emitting anything, the guard above doesn't fire, and :92 renders the blank option deliberately:
echoesc_html( $opt === '' ? '' : $opt );
That is the "nothing selected yet" entry on a non-required dropdown with no placeholder. Without it the browser auto-selects the first real option, which is the opposite of what the field author wanted. For a radio or checkbox group there is no equivalent use — a blank choice there is exactly the check_selected() collision this PR is fixing.
The regression doesn't need anyone to type a blank line, either. The Bulk Edit textarea is populated from the field's existing options, so opening Bulk Edit on a dropdown that already has a leading blank option and saving with no changes now silently removes it.
Scope the filtering to where the collision is actually a defect. $bulk_edit_types is already resolved a few lines up at :345, so the type is in hand:
with parse_bulk_edit_opts() keeping a single leading blank for select and dropping blanks everywhere else. If you'd rather not branch on type, the alternative is to drop only duplicate and trailing blanks and keep at most one leading blank — but the type check is the more honest statement of the rule.
Either way this needs saying in the PR description: as written it's a behavior change for dropdowns, not only a fix.
The reason will be displayed to describe this comment to others. Learn more.
Fixed as suggested: parse_bulk_edit_opts() now takes the field type and keeps a single leading blank for select only, dropping elsewhere. Added regression tests (select keeps it, radio drops it), both confirmed red against the prior code. Noted the dropdown behavior change in the PR description.
The reason will be displayed to describe this comment to others. Learn more.
Non-blocking: the predicate only inspects the value half, so the mirror-image malformed line survives.
|value splits to array( 'label' => '', 'value' => 'value' ). '' !== $opt['value'] is true, so it's kept, and the option renders with an empty label against a real value. That isn't the check_selected() collision — the value is non-blank, so nothing pre-selects — but it's the same shape of malformed input arriving through the same split, and the docblock above describes the helper as handling that split generally.
Deciding to keep it is fine; it just isn't stated anywhere. A line in the docblock saying only the value half is checked, and why, would stop the next reader assuming both halves are covered.
Also: explode( '|', $opt ) at :362 keeps only $vals[0] and $vals[1], so a|b|c silently discards c. Pre-existing, not yours, and not worth widening this PR for — noting it because it's in the block you're now filtering.
The reason will be displayed to describe this comment to others. Learn more.
Kept, per your note - and now documented in the docblock why: check_selected() only ever compares the value half, and dropdown-field.php renders a blank-label option deliberately, so a value line survives on purpose rather than by omission. Added a test locking in that a blank-label/real-value line survives.
The reason will be displayed to describe this comment to others. Learn more.
Non-blocking: both tests exercise the helpers in isolation, so import_options()'s own wiring is still uncovered.
parse_bulk_edit_opts() and remove_blank_separated_values() are each called directly through run_private_method(). That verifies the two predicates, which is the right start, but it means nothing covers the parts most likely to break later:
remove_blank_separated_values() runs after the label|value loop at :360-370, so it sees a mix of arrays and plain strings. The unit test hands it that mix by hand; nothing proves import_options() actually produces it in that order.
parse_bulk_edit_opts() now returns array_values()-reindexed keys, which then meet the other_* string keys in array_merge( $opts, $other_array ) at :387. The reindex is correct, but that interaction is exactly what a later refactor would break silently.
One test through import_options() with $_POST['opts'] containing a blank line, separate=true, and a field that has an other option would cover all of it, and would have caught either wiring mistake.
The PR description says the three helper tests were confirmed red against the old code — that's the right discipline and worth keeping for this one too.
The reason will be displayed to describe this comment to others. Learn more.
Added integration tests driving the real frm_import_options AJAX action end to end (test_FrmFieldsAjax.php): blank-line dropping, the select leading-blank case, the label|value split ordering, and the other_ key surviving the reindex/merge.
❌ Patch coverage is 94.73684% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 28.69%. Comparing base (d0ffb93) to head (fb2c862). ⚠️ Report is 1158 commits behind head on master.
…alues, add wiring coverage
Franky round-1 review: parse_bulk_edit_opts() dropped a select field's
legitimate leading blank option (dropdown-field.php's own placeholder/skip
handling treats it as a manual "please select" default) along with genuine
bugs on radio/checkbox. Now preserves a single leading blank only for
select. remove_blank_separated_values() also drops a blank-label half
(mirroring the existing blank-value check) and import_options() itself
gets integration coverage via the real frm_import_options AJAX action,
not just the private helpers in isolation.
Self-review caught this: FrmAppHelper::check_selected() only ever
compares an option's value half, never its label, so a blank label with
a real value doesn't reproduce #3385's collision - and dropdown-field.php
already renders a blank label as a real, selectable option. Reverted
that part of the previous commit; kept the value-half check and the
select leading-blank preservation. Also drops the now-dead isset() check
in parse_bulk_edit_opts() (explode() always returns at least one element).
The reason will be displayed to describe this comment to others. Learn more.
Re-review of pr-3345 at 911bb43. Both prior blocking items are genuinely fixed: PHPCS is clean now, and the leading-blank-on-select case is preserved via parse_bulk_edit_opts()'s new $keep_leading_blank handling, verified by a real end-to-end test (test_import_options_keeps_leading_blank_for_select) that drives the actual frm_import_options AJAX action rather than just the private helper directly. The other two non-blocking notes from last round are also resolved: remove_blank_separated_values()'s blank-label-with-real-value behavior is now explicit and documented as intentional (matches dropdown-field.php's real rendering support), and the new test_FrmFieldsAjax.php file exercises import_options()'s full wiring end-to-end (blank-line drop + other-key preservation, separate-value blank-value-drop + blank-label-keep, leading-blank-for-select) instead of only the private helpers in isolation. Good coverage, clear test names, and the docblocks explain the why (formidable-pro#3385, the check_selected() collision) rather than just narrating what changed.
One new blocking issue introduced by this round's own fix, though — see inline.
Blocking
classes/controllers/FrmFieldsController.php:425 — CI is currently red: PHPStan fails on this PR's own new code (Parameter #2 $callback of function array_filter expects (callable(string): bool)|null, 'strlen' given.).
CI otherwise green (PHPUnit on PHP 7.4/8, PHP CS Fixer, Rector, ESLint, Oxlint, Stylelint, Psalm, Mago, both syntax legs). Branch is 77 behind master but the diff is still scoped to the same 3 files as last round (2 test files + FrmFieldsController.php), no rebase-inflated scope. No security surface in this diff — same sanitized-$_POST path as before, no new escaping/output surface.
The reason will be displayed to describe this comment to others. Learn more.
Blocking — PHPStan is red on this line.strlen(): int, not bool — phpstan-strict-rules (installed, per this repo's CI log) rejects it as an array_filter callback even though PHP's own truthy/falsy coercion makes it work correctly at runtime (strlen('') === 0 → falsy, any non-empty string → truthy, so '0' is correctly kept per the intent noted in the prior review round).
array_filter()'s $callback is typed callable(string): bool, but strlen()
returns int - PHPStan rejects the implicit int-to-bool coercion even
though it happens to work at runtime. Same '' !== $value semantics,
'0' still survives (falsy but not blank).
The reason will be displayed to describe this comment to others. Learn more.
Re-review of pr-3345 at 1b77750 (round 3). Round 2's two blocking items are fixed (PHPCS clean at 911bb43, and now PHPStan too). But this round surfaces new problems: one mechanical (CI), two substantive (the leading-blank fix itself doesn't hold up in the cases that matter most).
Blocking
CI: PHPCS is red again, on this round's own commit — see inline.
The select leading-blank preservation only prevents the original bug when the field has a placeholder configured — see inline on parse_bulk_edit_opts().
Separate-value selects lose their leading-blank placeholder row entirely (regression vs. the non-separate-value path this round already handles) — see inline on remove_blank_separated_values().
Non-blocking
A wholly-cleared textarea now saves zero options for radio/checkbox instead of one blank option (pre-PR behavior). Might be intended given the PR's title, but no docblock says so — worth a one-line confirmation either way.
parse_bulk_edit_opts() hardcodes the literal string 'select', so a field type added to bulk-edit via the frm_bulk_edit_field_types filter that renders through the same select/check_selected() mechanics doesn't get the same leading-blank carve-out.
Root cause, for context rather than as a blocker: FrmAppHelper::check_selected()'s loose == is what actually conflates "no value submitted" with "a real option whose value is ''". Every special case added across this PR's three rounds (keep_leading_blank, remove_blank_separated_values) is working around that one comparison at a single entry point (Bulk Edit Options) — import, REST, and direct DB writes producing a blank-value option all still hit the same collision. Not asking for that rewrite in this PR, just flagging it since two of this round's own findings are more special-casing on the same root problem.
CI otherwise green (PHPUnit PHP 8, Rector, Oxlint, Stylelint, Psalm, Mago, both syntax legs, PHPStan). PHPUnit PHP 7.4 was still in_progress at review time, not yet resolved either way. PHP CS Fixer/ESLint/DeepSource:PHP are also red, but confirmed unrelated to this PR — same three checks failed identically on an unrelated PR (formidable-forms#3349) against files this PR never touches (classes/views/shared/toggle.php, the Cypress e2e suite), so they're master-level drift, not this diff's doing.
Findings 2 and 3 are source-verified only, not exercised in a live browser this round (traced parse_bulk_edit_opts() → dropdown-field.php's skip logic → FrmAppHelper::check_selected()).
The reason will be displayed to describe this comment to others. Learn more.
Blocking — CI: PHPCS is red on this line at the current head (1b77750).
FOUND 1 ERROR AFFECTING 1 LINE
423 | ERROR | [x] Blank lines found between consecutive simple assignments.
It was green at the prior head (911bb43) — the only file that changed between those two commits is this one, and this array_filter block (converting the 'strlen' callback to an explicit closure) is the only edit. phpcbf clears it automatically.
only swallows the blank option when $placeholder (from FrmFieldsController::add_placeholder_to_select()) is truthy — and that's false whenever the field has no placeholder option set and no default-value-from-name, which is the common case for a plain select with no "Please Select" text. In that case the leading blank option this method now re-inserts renders as a real <option value="">, and FrmAppHelper::check_selected( $field['value'], '' ) loosely matches an unset field value against it — reproducing the exact empty-row/selected-by-default bug this PR (and formidable-pro#3385) is about, for the majority of selects that don't bother configuring a placeholder.
Suggested fix: only set $keep_leading_blank when the field actually has a placeholder configured (mirror add_placeholder_to_select()'s own condition), or handle the no-placeholder case in dropdown-field.php directly. Source-verified only, not exercised in a live browser this round — worth a live check with screen options/no-placeholder select before merging.
The reason will be displayed to describe this comment to others. Learn more.
Fixed. select_has_placeholder() now mirrors add_placeholder_to_select()'s own condition (both delegate to a shared get_select_placeholder() helper), so the leading blank only survives when a placeholder is actually configured. No-placeholder selects drop it like radio/checkbox. Covered by test_import_options_drops_leading_blank_for_select_without_placeholder and the _with_placeholder counterpart.
The reason will be displayed to describe this comment to others. Learn more.
Blocking — this drops a select's leading-blank placeholder row entirely when "separate values" is on, unlike the plain-line path above. For a separate-value select where the textarea starts with "|" (blank label, blank value) meant as the placeholder row: parse_bulk_edit_opts()'s keep_leading_blank check only tests '' === $opts[0] (line 423), but at that point $opts[0] is the string "|", not '', so the leading-blank carve-out never triggers for it. After the label/value split (lines 361-372) it becomes ['label' => '', 'value' => ''], which this method then strips unconditionally — it doesn't check $field_type at all, so there's no equivalent carve-out here for select.
Failure scenario: select field, "separate values" checked, textarea = "|\nYes|1\nNo|0" → the intended blank placeholder row disappears instead of surviving as the (skippable) first option, same as the plain-line case now does.
The reason will be displayed to describe this comment to others. Learn more.
Fixed. remove_blank_separated_values() now takes the same keep_leading_blank flag and keeps a leading blank label/value pair on the same placeholder-configured condition, unless it's the only line. Covered by test_import_options_keeps_leading_blank_pair_for_separate_value_select_with_placeholder.
The reason will be displayed to describe this comment to others. Learn more.
Non-blocking: before this PR, explode( "\n", rtrim( $opts, "\n" ) ) on an empty textarea always produced [''] — one blank option — so a radio/checkbox field always kept at least one option after Bulk Edit Options, even a blank one. Now array_filter strips it and the leading-blank exception is select-only, so a fully-cleared textarea on a radio/checkbox field saves zero options instead of one. FrmFieldsHelper::show_single_option()'s foreach over an empty options array just renders nothing, no error — silent, not loud. Might be intentional given this PR's whole point is dropping blanks, but worth a one-line confirmation since no docblock mentions the zero-options case specifically.
The reason will be displayed to describe this comment to others. Learn more.
Confirmed intentional and now explicit: parse_bulk_edit_opts() drops to zero options for a wholly-blank textarea even when keep_leading_blank is true, since a leading blank only makes sense as the first row of a real list. Covered by test_parse_bulk_edit_opts_wholly_blank_keeps_nothing_even_when_flagged.
…x separate-value regression
Franky's round-3 review found three blocking issues:
- PHPCS red on this round's own PHPStan fix (blank line between consecutive
simple assignments) - phpcbf-equivalent restructuring, no blank line left.
- Keeping a select's leading blank option only prevents the check_selected()
collision when a placeholder is configured. dropdown-field.php's skip
logic never fires without one, so the kept option rendered for real and
reproduced the exact bug this PR fixes for the common no-placeholder case.
- Separate-value selects lost their leading blank/blank ("|") placeholder
row entirely - remove_blank_separated_values() had no equivalent
leading-blank carve-out to parse_bulk_edit_opts()'s own.
Fix: compute whether the select would actually render its own placeholder
(select_has_placeholder(), mirroring add_placeholder_to_select()'s own
truthy check without its markup side effect) once at the call site, and
thread that single $keep_leading_blank flag through both filtering methods
instead of a bare field-type string.
…ea, trim test comments
- Extract get_select_placeholder() so add_placeholder_to_select() and
select_has_placeholder() share one resolution instead of two copies that
could drift.
- A wholly-blank textarea (or a lone separate-value "|" line) now keeps zero
options even when $keep_leading_blank is true, instead of leaving one
phantom blank option behind - the leading blank only makes sense as the
first row of a real list.
- Trim redundant WHY comments in the new tests down to a pointer at the
docblock that already states it, and extract the repeated
POST-and-scrape-labels pattern in the new AJAX tests into one helper.
The reason will be displayed to describe this comment to others. Learn more.
Re-review of pr-3345 at fb2c862 (round 4). Round 3's three blockers are fixed and the fix holds up live. One blocking item, CI only.
Blocking
Rector is red on the new get_select_placeholder() — see inline (one-click suggestion).
Verified live (Playground env, Lite at base 2a2d8c1 vs PR head; Bulk Edit Options on a Dropdown, textarea Alpha, blank line, Beta, saved, then rendered the form):
Base: stored options Alpha / blank / Beta; front end renders the blank row <option value=""> as selected (selectedIndex 1), the original bug reproduced.
PR: blank dropped, options Alpha / Beta only.
PR, select with a saved placeholder + leading blank line: blank kept in the builder, front end shows just the placeholder row, no duplicate blank.
PR, same select with 'Use separate values' (|, Yes|1, No|0, Maybe|): leading | kept, blank-value Maybe| dropped, renders placeholder / Yes / No.
The placeholder is read from the saved field, so a placeholder typed but not yet saved in the same builder session is not seen by Bulk Edit and its leading blank is dropped. That is harmless, since the placeholder renders regardless.
Not exercised live: radio/checkbox, the other_* merge and a 0 value option (covered only by the PHPUnit tests, which are green on PHP 7.4 and 8). Pro in this env is much newer than this Lite commit (version skew warning from the preview script); Bulk Edit doesn't touch Pro code, but treat the Pro pairing as unverified.
CI otherwise green (PHPUnit, PHPCS, PHPStan, Psalm, Mago). PHP CS Fixer and ESLint are red on files this PR doesn't touch (classes/views/shared/toggle.php, js/**), master-level drift as before. DeepSource:PHP flags assertSame as undefined in test_FrmFieldsAjax.php; that is static-analysis noise on the PHPUnit base class, and PHPUnit itself passes. Diff is still 3 files, no rebase inflation. No security surface: same sanitized $_POST path, no new output.
The reason will be displayed to describe this comment to others. Learn more.
Blocking — CI: Rector is red on this round's new code.get_select_placeholder() was extracted in fb2c862; Rector's dry-run flags it (CompleteMissingIfElseBracketRector, ReturnEarlyIfVariableRector) and wants the early-return form:
The reason will be displayed to describe this comment to others. Learn more.
Re-review of pr-3345 at c6e1093 (round 5). Approve. Round 4's one blocker (Rector on get_select_placeholder()) is fixed by the early return in the last commit; the rest of the PR is unchanged apart from a master merge. Diff is still 3 files (controller + 2 test files), no rebase inflation.
Checks I ran on this head. The analysis workflows are label-gated and were all skipped on this commit, so I ran them myself in a scratch checkout:
PHPCS (repo ruleset incl. the custom Formidable sniffs) on the 3 changed files: clean.
PHPStan on FrmFieldsController.php: no errors.
Rector --dry-run: nothing in this PR's files (it flags FrmFieldsHelper.php and mb_insert_fields.php, neither touched here).
PHPUnit 7.4 / 8.0 / 8.2 / 8.4, Psalm, syntax, typos: green in CI at this head.
Verified live (Playground, Lite at base vs. PR head; called the real frm_import_options AJAX action on a Dropdown, Radio and Checkbox field and read the rows it returns):
Base: Alpha, blank line, Beta returns Alpha / (blank row) / Beta; radio and checkbox the same; Yes|1, Maybe| keeps the Maybe row. That is the bug.
PR: blank rows are gone for all three types; a 0 option is kept; Maybe| and Y| are dropped; |3 (blank label, real value) is kept; an all-blank textarea returns no rows.
PR, Dropdown with a placeholder saved: a leading blank line / leading | is kept, other blanks are dropped. Without a placeholder it is dropped.
Not exercised live this round: the saved-form front-end selected state (verified in round 4, and the logic it depends on is unchanged), the other_* merge (PHPUnit only), and Pro. I did not run the review skills (code-review/security-review/simplify) this round; the code delta since round 4 is one line. No security surface: same sanitized $_POST path, no new output.
Non-blocking
DeepSource:PHP is red on assertSame "undefined" in test_FrmFieldsAjax.php; static-analysis noise on the PHPUnit base class, same as round 4, and PHPUnit passes.
Root cause is still FrmAppHelper::check_selected()'s loose ==; this PR works around it at one entry point (Bulk Edit Options). Import, REST and direct DB writes with a blank-value option still collide. Not asked for here.
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
Using the form builder's "Bulk Edit Options" on a radio/checkbox/select (or Likert, via
frm_bulk_edit_field_types) field, a blank line in the textarea (or alabel|line with nothing after the separator in separate-value mode) became an option with an empty string value.FrmAppHelper::check_selected()compares the field's current value against each option's value with a loose==, so when the field has no submitted value yet (''), that blank option matches and renders checked/selected by default.Related: Strategy11/formidable-pro#3385
What changed
FrmFieldsController::import_options()drops blank lines viaparse_bulk_edit_opts(), and blank-value separate-value halves (label|) viaremove_blank_separated_values()— except a select field's leading blank line, which is a legitimate, renderer-supported "please select" default (dropdown-field.php's own placeholder/skip handling) rather than thecheck_selected()collision this PR fixes. Behavior change for dropdowns: re-saving Bulk Edit Options on an existingselectfield no longer drops a leading blank option it already had.|value) is left alone:check_selected()never compares the label, so it doesn't reproduce the bug, anddropdown-field.phprenders a blank-label option deliberately.FrmProFieldProductbulk edit for Product fields already does.How it was verified
Red/green locally against the real PHPUnit suite (
~/Claude/test-sites/formidable/wordpress-develop): unit tests for both private helpers (blank-line dropping,'0'survives, select keeps a leading blank/radio drops it, blank-value separate halves drop, blank-label halves survive) plus new integration tests driving the realfrm_import_optionsAJAX action end-to-end (tests/phpunit/fields/test_FrmFieldsAjax.php) covering the label|value split ordering and theother_*key surviving the reindex. Fullfields(114) andajax(9) groups green afterward (one pre-existing, unrelated failure intest_FrmFieldCombo::test_print_input_atts, a PHP 8.5 deprecation notice leaking into output buffering, present before this change too).Self-reviewed (security + correctness/simplify lenses) before pushing — security clean; correctness caught that the blank-label drop I'd added wasn't justified (reverted, see above).