feat: add a public spam check API for third-party integrations - #795
Open
2ndkauboy wants to merge 4 commits into
Open
feat: add a public spam check API for third-party integrations#7952ndkauboy wants to merge 4 commits into
2ndkauboy wants to merge 4 commits into
Conversation
Member
|
Related #768 |
9 tasks
MatzeKitt
reviewed
Aug 16, 2026
MatzeKitt
left a comment
Member
There was a problem hiding this comment.
Successfully tested with epiphyt/form-block#83 🎉
Plugins that handle their own content had no supported way to have their submissions classified. The only entry point was `new Rules( $reaction_type )`, an internal handler that `Reaction::process()` instantiates, so documenting it would have frozen its constructor as third-party API. `AntispamBee\Api\SpamCheck` is now that entry point: * `check()` normalizes a loose item into the payload the rules expect. It derives `host` from `url` via `DataHelper::parse_url()` and defaults `ip` and `useragent` from the current request, so integrators no longer have to know that `RegexpSpam` reads `host` separately from `rawurl`. The `antispam_bee_api_payload` filter is the escape hatch. * `CheckResult` carries the verdict, the reason slugs and the payload. `was_evaluated()` distinguishes "checked and clean" from "no rule was active", which is otherwise indistinguishable from a bare boolean and is the most common reason an integration appears to do nothing. * `has_active_rules()` lets an integration warn an administrator before any submission arrives. * `post_process()` runs the post-processors registered for a reaction type without the comment-specific behaviour of `Reaction::handle_spam()`. * `get_reason_texts()` resolves stored slugs, so an integration needs a single import. `Rules::apply()` now logs when no rule is active, so that case leaves a trace instead of silently reporting no spam. `Handlers\Rules` and `Handlers\PostProcessors` are marked `@internal`.
`Settings::$defaults` only covered `comment`, `linkback` and `general`, so every `ControllableBase` rule started inactive for a reaction type registered by another plugin. Nothing was checked until an administrator found the new settings tab and enabled rules there, which is easy to miss because the integration otherwise looks configured. `Settings::get_defaults()` applies the new `antispam_bee_default_options` filter, and `get_options()` fills in the defaults of reaction types that are absent from the stored options. Defaults deliberately apply only to reaction types that were never saved. An unticked checkbox is removed from the stored options by `Sanitize::sanitize_controllables()`, so merging defaults underneath the stored state would re-enable a rule an administrator had deliberately disabled.
`SaveReason` and `SendEmail` defer their work to the `comment_post` action. For an item that never becomes a comment the callback not only never runs, it stays attached for the rest of the request, so an unrelated comment inserted later would be stamped with that item's spam reasons or trigger its notification. Both now bail unless `comment_post_ID` is set and record the failure in `asb_post_processors_failed`, mirroring what `UpdateSpamLog::process()` already does. The check is deliberately structural rather than based on the reaction type: `comment_type` is legitimately empty for a regular comment, and gating on `reaction_type` would only re-block what an integration explicitly opted in via `antispam_bee_post_processor_supported_types`. Only reachable today by opting these post-processors into a custom reaction type, which the documentation advises against, but the guard is cheap.
Adds `docs/integrating-own-content.md`, covering `Api\SpamCheck`, the payload attributes, registering a custom reaction type with `antispam_bee_reaction_types`, `antispam_bee_rule_supported_types` and `antispam_bee_default_options`, and how to register your own post-processor via `antispam_bee_post_processors`. Two things that are easy to get wrong are called out explicitly: * Rules register themselves during module initialization, which is skipped for Ajax requests, so a plugin submitting over Ajax has to allow it via `antispam_bee_disallow_ajax_calls` and register the filter early. REST requests are unaffected, because `wp_doing_ajax()` is false for them. * The post-processors that ship with Antispam Bee support comments and linkbacks by design: they write comment meta, build the notification from a stored comment, and feed a spam counter that is displayed next to the comment counts in the At a Glance widget. They should not be opted into a custom reaction type. `docs/adding-rules.md` gains the registration step, the reserved `asb-` slug prefix and a note on the payload; `docs/program-flow.md` now points at the API instead of leaving `Handlers\Rules` looking like the way in.
2ndkauboy
force-pushed
the
feat/public-spam-check-api
branch
from
August 21, 2026 20:36
ccdca40 to
acd7dc4
Compare
🔍 WordPress Plugin Check Report
📊 Report
❌ Errors (1)📁 readme.txt (1 error)
🤖 Generated by WordPress Plugin Check Action • Learn more about Plugin Check |
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Closes #507.
Adds a documented, stable entry point for plugins that want their own content checked, so
AntispamBee\Handlers\Rulescan stay internal.The trigger was the first real-world integration, epiphyt/form-block on its
antispam-beebranch. Reviewing it surfaced three things worth settling before writing documentation, because the docs freeze whatever we describe.1.
new Rules( $reaction_type )was the only way inRulesis an internal handler thatReaction::process()instantiates. Documenting it would have made its constructor permanent third-party API.AntispamBee\Api\SpamCheckis now the entry point, andHandlers\RulesandHandlers\PostProcessorsare marked@internal:CheckResultcarries the verdict, the reason slugs and the payload. It is an object rather than a boolean mainly because ofwas_evaluated(): a result can report "no spam" simply because no rule was active, and that is the most common reason an integration appears to do nothing.has_active_rules()lets an integration warn an administrator before any submission arrives, andget_reason_texts()resolves stored slugs so integrators need a single import.2. The payload contract was easy to get wrong
form-block hand-built the item and left
hostempty.RegexpSpamreadshostseparately fromrawurl, so a good number of our own patterns could never match. Nobody should have to know thathostiswp_parse_url( $url, PHP_URL_HOST ).SpamCheck::check()normalizes: it deriveshostfromurlviaDataHelper::parse_url()and defaultsipanduseragentfrom the current request, so a form plugin passes only what it actually has. The newantispam_bee_api_payloadfilter is the escape hatch.3. Nothing was checked until an administrator ticked a box
Settings::$defaultsonly coveredcomment,linkbackandgeneral, so everyControllableBaserule started inactive for a reaction type registered by another plugin — including rules the integration had explicitly opted in. Verified on a local install: form-block's integration worked, but only because its settings tab had been visited and BBCode ticked.rule_asb_regexp_activewas still unset there, soRegexpSpamsilently never ran.Settings::get_defaults()applies a newantispam_bee_default_optionsfilter, andget_options()fills in the defaults of reaction types absent from the stored options.Defaults deliberately apply only to reaction types that were never saved. An unticked checkbox is removed from the stored options by
Sanitize::sanitize_controllables(), so merging defaults underneath the stored state would re-enable a rule an administrator had deliberately disabled — forcommentandlinkbacktoo. There is a unit test for exactly this.Rules::apply()now logs when no rule is active, so the "nothing ran" case leaves a trace instead of silently reporting no spam.Also: comment-only post-processors
SaveReasonandSendEmaildefer their work tocomment_post. For an item that never becomes a comment the callback not only never runs, it stays attached for the rest of the request — so an unrelated comment inserted later would be stamped with that item's spam reasons, or trigger its notification email. Both now bail unlesscomment_post_IDis set, mirroring whatUpdateSpamLog::process()already does.The check is structural on purpose:
comment_typeis legitimately empty for a regular comment, and gating onreaction_typewould only re-block what an integration explicitly opted in viaantispam_bee_post_processor_supported_types.Documentation
New
docs/integrating-own-content.mdcovers the API, the payload attributes, registering a custom reaction type, and registering your own post-processor viaantispam_bee_post_processors(already supported, previously undocumented). Two traps are called out explicitly:antispam_bee_disallow_ajax_callsand register that filter early. REST requests are unaffected, becausewp_doing_ajax()is false for them.docs/adding-rules.mdgains the registration step, the reservedasb-slug prefix and a note on the payload.docs/program-flow.mdno longer leavesHandlers\Ruleslooking like the way in.Testing
composer test:unit— 56 tests pass, 20 of them new.phpstanandphpcsclean.Verified end to end against a local install with form-block ported to the new API, submitting real forms over HTTP:
asb-bbcodehostderived fromurlspam.example.comRegexpSpamcan catch, on defaults aloneasb-regexpspam_countthroughout0— no post-processor ran, the comment counter is untouchedNotes for review
SpamCheck::check()fails open, matching current behaviour for an empty rule set: it catches theReflectionExceptionthatRules::get()/apply()declare, logs, and returns a not-evaluated result rather than surfacing a Reflection error to integrators.API_VERSIONis there so integrations can adapt to a breaking change; everything outsideAntispamBee\Apiand the documented hooks is stated to be internal.spam_count, and nothing requires it yet.