Skip to content

fix: disable inactive rule checkboxes in delete-for-reasons - #853

Open
faisalahammad wants to merge 4 commits into
pluginkollektiv:v3from
faisalahammad:fix/740-disable-inactive-reason-checkboxes
Open

fix: disable inactive rule checkboxes in delete-for-reasons#853
faisalahammad wants to merge 4 commits into
pluginkollektiv:v3from
faisalahammad:fix/740-disable-inactive-reason-checkboxes

Conversation

@faisalahammad

@faisalahammad faisalahammad commented Aug 24, 2026

Copy link
Copy Markdown

Summary

In the post-processor options, a reason whose underlying rule is inactive in the rules section used to render as an enabled checkbox. A user could tick it, the choice would be stored, but the rule never fires, so the stored reason was dead weight with no UI hint that it can never trigger.

DeleteForReasons::get_options() now builds a disabled_keys map per reaction type. Any rule that implements Controllable and is inactive for that type is marked disabled. CheckboxGroup::render() reads the optional disabled_keys key and emits a disabled input plus a dimmed label. Non-controllable rules (no is_active method) keep their existing behaviour. The sanitize closure still keys on the full option list, so a stored value for a disabled reason survives the round-trip and is restored when the rule is re-enabled.

Fixes #740

Changes

src/PostProcessors/DeleteForReasons.php

Build $disabled_keys per reaction type from controllable rules whose is_active( $reaction_type ) is falsy. The new key is added to the emitted option entry alongside the existing options map. sanitize is unchanged on purpose.

Why: makes the post-processor options section a faithful view of what can actually happen at runtime, without silently dropping stored preferences.

src/Admin/Fields/CheckboxGroup.php

Reads $this->option['disabled_keys'] ?? [] before the row loop. When a row's slug is in the map, the input gets a disabled attribute and the wrapping label gets a new asb-checkbox-group-disabled class.

Why: opt-in renderer change. No other call site sets disabled_keys, so CountrySpam and LangSpam output is byte-equivalent to today.

assets/css/admin.css

One new rule: .asb-checkbox-group-disabled { color: #8c8f94; }. Reuses the existing secondary-text colour used elsewhere in the admin stylesheet.

Tests

  • tests/Unit/PostProcessors/DeleteForReasonsTest.php (new, 6 cases): a controllable rule is disabled only when inactive, an active controllable rule is never disabled, a non-controllable rule is never disabled, an option list with no controllable rules yields an empty disabled_keys, the map is built per reaction type (not collapsed across the form), and sanitize still accepts a posted value for a disabled key.
  • tests/Unit/Admin/Fields/CheckboxGroupTest.php (new, 5 cases): output is unchanged when disabled_keys is absent (regression guard for other call sites), a disabled row renders the disabled attribute plus the label class and only that row, a stored on value still renders as checked="checked" while the row is disabled, a stored off value renders unchecked but still disabled, and the row's name / id are the full controllable path with the slug suffix and are escaped.

Testing

Test 1: Manual round-trip on a fresh WordPress site

  1. Install antispam-bee.740-test.zip (built from this branch) via Plugins → Add New → Upload Plugin, then activate.
  2. Open Settings → Antispam Bee, expand the Rules section, confirm "BBCode link" is active.
  3. Open the "Delete by reasons" section. Tick the BBCode reason. Save.
  4. Re-open the same section. The BBCode row is checked="checked" and enabled (pre-existing baseline).
  5. Go back to the Rules section and deactivate "BBCode link". Save.
  6. Re-open "Delete by reasons". Expected: BBCode row is still listed and still checked="checked", but the checkbox is disabled and the label text is dimmed. Clicking or unchecking the row has no effect.
  7. Re-activate "BBCode link" in the Rules section. Re-open "Delete by reasons". Expected: the BBCode row is enabled again, the stored on value is honoured, no further user action is needed.

Test 2: Per-reaction-type check

  1. In the Rules section, deactivate "BBCode link" for the Comment reaction type only (per-type sub-checkboxes). Save.
  2. Open the "Delete by reasons" section. The BBCode row is disabled under "Comment" and enabled under "Linkback" (if the site has linkbacks).

This proves the disabled_keys map is built per reaction type, not collapsed across the form.

Test 3: Automated checks in the worktree

  • composer test:unit 199 tests, 0 failures, 0 regressions (the 11 errors in the output are pre-existing PHP 8.5 ReflectionProperty::setAccessible() deprecations in tests/Unit/Helpers/DebugModeTest.php, unrelated to this change).
  • composer lint-php clean.
  • composer phpstan level 8 clean.

Edge cases covered by unit tests

  • A rule that does not implement Controllable (e.g. asb-empty, asb-deny-country) is never disabled. is_active() does not exist on it, and ComponentsHelper::filter() treats it as always-on.
  • A rule whose stored value is 'off' (the user explicitly unticked it before deactivation) renders unchecked but still disabled.
  • An option list with zero controllable rules yields an empty disabled_keys array. The disable branch is correctly short-circuited.
  • Sanitization still accepts a posted value for a disabled key, so a direct form POST that re-sends the slug does not silently lose the stored reason.

Notes for reviewer

  • Schema change is additive. disabled_keys defaults to [] in the renderer, no migration needed.
  • Stored data is preserved. Re-enabling a rule restores the user's prior selection without re-ticking.
  • No new i18n strings. The disabled attribute and the asb-checkbox-group-disabled class are not user-facing copy.
  • Backward compatible: no breaking changes to public PHP, JS, or stored option shape.

Screen recording

https://videos.faisalahammad.com/recordings/WblPUVF5Okuc2YYIBfFL

antispam-bee-disable-inactive-rule-checkboxes-in-delete-for-reasons-853.mp4

faisalahammad and others added 4 commits August 24, 2026 16:31
- Use the core is-dismissible close button and drop the hand-rolled one.
- Keep i18n one translatable string per message.
- Persist dismissal keyed to PR PLUGIN_VERSION so it returns on newer builds.
- Fold is_pre_release into PreReleaseNotice and allow build metadata.
- Deny without capability via wp_send_json_error(403) or wp_die('', 403).
- Rename the dismiss script to pre-release-notice.js and persist on X clicks.
Add an input case to the honeypot renderer so themes that output the
comment field as a text input get a working honeypot. The visible input
keeps its id and gets an obfuscated name, and a hidden duplicate with
the comment name is appended as the bait, matching how the textarea
case works.

Fixes pluginkollektiv#738
In the post-processor options, a reason whose underlying rule is
inactive in the rules section used to render as an enabled checkbox.
A user could tick it, the choice would be stored, but the rule
never fires, so the stored reason is dead weight with no UI hint
that it can never trigger.

The DeleteForReasons post-processor now builds a disabled_keys map
per reaction type: any rule that implements Controllable and is
inactive for that type is marked disabled. The CheckboxGroup
renderer reads the optional disabled_keys key and emits a disabled
<input> plus a dimmed label. Non-controllable rules (no is_active
method) keep their existing behaviour. The sanitize closure still
keys on the full option list, so a stored value for a disabled
reason survives the round-trip and is restored when the rule is
re-enabled.

Fixes pluginkollektiv#740
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant