=== AccessNow Spam Eater ===
Contributors: accessnow
Tags: spam, anti-spam, honeypot, contact form, woocommerce
Requires at least: 6.0
Tested up to: 7.1
Requires PHP: 7.4
Stable tag: 2.4.1
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Stops form spam without CAPTCHAs, using hidden fields, JavaScript and timing checks. Works with core forms, WooCommerce and popular form plugins.

== Description ==

Spam Eater quietly checks every protected form submission and blocks the ones sent by bots. Real visitors never see a puzzle or a CAPTCHA, and nothing is sent to an outside service.

Each submission goes through up to four checks:

1. **Honeypot** – a hidden field that people never see, but bots fill in.
2. **JavaScript token** – set by a small script, so bots that don't run JavaScript are caught.
3. **Timing** – forms sent within three seconds of loading, or from a page that is too old, are rejected.
4. **Interaction** – a mouse, keyboard, touch or scroll event must have happened on the page.

Login forms only use the honeypot check, so password managers and visitors without JavaScript can still log in.

IP addresses that keep filling in the hidden honeypot field are blocked for a while (5 times and 24 hours by default, both adjustable). While blocked, an address can't send any protected form. It can still log in, so a shared address can never lock you out of your own site.

= Also included, all running on your own server =

* **Your own words and patterns to block**, for spam that people type by hand: words, links or regular expressions, checked in memory as the form arrives and never stored.
* **Turn away throwaway email addresses**, using a list of disposable email domains that comes with the plugin, so no address is ever sent anywhere to be checked.
* **Always allow and Always block lists** of IP addresses and ranges (CIDR, IPv4 and IPv6).
* **Slow down password guessing**: repeated failed logins for one username from one address wait a little longer each time, without ever locking out the real owner.
* **A spam summary** on the Dashboard with the last 30 days, and an optional weekly email.
* **Test a form as a bot**: administrators get a button under each protected form that sends it as a bot would and reports whether Spam Eater refused it.
* **WP-CLI commands** for statistics, unblocking, settings and the allow and block lists.

The optional extras are off until you turn them on.

= Protected forms =

* WordPress comments, login, registration and lost password forms (and plugins that use them, such as Theme My Login and WooCommerce reviews)
* WooCommerce classic checkout and Checkout block, including card-testing bots that post to the Store API (express payment buttons keep working), Pay for Order, and My Account login, registration, lost password and account details
* Contact Form 7, Gravity Forms, WPForms, Formidable Forms, Fluent Forms, Ninja Forms, Forminator, HTML Forms, QuForm, Clean and Simple Contact Form, Jetpack Forms, Kadence Forms, WS Form, JetFormBuilder, SureForms and Everest Forms
* Elementor Pro forms, Bricks Builder forms and the Beaver Builder contact form
* bbPress, BuddyPress (and BuddyBoss, Youzify), Ultimate Member, UsersWP, wpDiscuz, MemberPress and Paid Memberships Pro
* MC4WP (Mailchimp for WordPress), MailPoet, Brevo, Easy Digital Downloads checkout and GiveWP (option-based donation forms)
* Your own forms, through three developer functions (see the FAQ)

Each integration can be turned off under Settings → Spam Eater. Integrations only load when the matching plugin is active, and the small script only loads on pages that show a protected form.

= Privacy =

* No cookies, no tracking and no calls to outside services.
* What people type into forms is never stored.
* The IP block list keeps a one-way hash of each address plus an anonymised version for display, and entries expire automatically.
* The spam log is off by default. When turned on, it records only the time, reason, page and an anonymised IP address, and keeps entries for 30 days.
* The spam summary keeps only daily counts per reason: no addresses or contents.
* The login throttle (off by default) keeps a keyed hash of the address and username of failed logins, for an hour at most.
* Addresses you type into Always allow, Always block and Trusted proxies are kept as you typed them.
* Suggested text is added to Settings → Privacy for your privacy policy.

== Installation ==

1. Upload the `accessnow-spam-eater` folder to `/wp-content/plugins/`, or install the ZIP through Plugins → Add New → Upload Plugin.
2. Activate the plugin.
3. Go to Settings → Spam Eater to check which integrations are on (they are all on by default).
4. If your site sits behind a reverse proxy, load balancer or CDN such as Cloudflare, add the proxy addresses under **Trusted proxies**. Otherwise every visitor looks like they come from the proxy, and blocking one would block everyone.

== Frequently Asked Questions ==

= Where do I get help? =

Step-by-step help articles, with screenshots, are at https://support.accessnow.com.au/help/workspace-accessnow/accessnow-spam-eater. The plugin's own screens link to the article for each one, under the Help tab at the top right. To ask a question, use Get support on https://accessnow.com.au/projects/spam-eater/.

= Does it work with page caching? =

Yes. The hidden fields work on cached pages. If a cached page is more than about 12 hours old, the script fetches fresh tokens from your own site (admin-ajax.php) before the form is sent. Tokens last up to two days; developers can change this with the `accessnow_spam_eater_token_lifetime` filter.

= A form in a pop-up is refused with reference SE-C2. Why? =

The script loads only on pages that show a protected form. If a form is fetched after the page loads, for example a pop-up that loads its content when opened, the page had no form to begin with and the script never loaded. Turn on **Load the Spam Eater script on every page** under Settings → Spam Eater.

= Do Apple Pay, Google Pay and other express payment buttons still work? =

Yes. Some payment gateways place the order through their own endpoint, with their own data, instead of the checkout form. Spam Eater recognises those orders and only applies the IP block list and honeypot checks to them.

= Can I protect my own forms? =

Yes. Print the hidden fields inside your form, and check the submission where you handle it:

`<?php echo accessnow_spam_eater_field( 'my-form' ); ?>`

`if ( accessnow_spam_eater_is_spam() ) { /* refuse it, quoting accessnow_spam_eater_fail_code() */ }`

Send the form as normal form data (a regular POST, or FormData through fetch() or a REST route), not as a JSON body. Multi-step forms send the fields with every step. If your form is drawn in the browser after the page loads, call `window.accessnowSpamEater.fill()` once it is in the page. The functions are documented in `includes/api.php`, and can be switched off under Settings → Spam Eater → Custom forms.

= Does it send any data to other services? =

No. All checks run on your own server.

= My site is behind Cloudflare or another proxy. What should I do? =

Add the proxy IP addresses or ranges (for example `173.245.48.0/20`) under Settings → Spam Eater → Trusted proxies, one per line. Spam Eater then reads the visitor's address from the X-Forwarded-For header, but only for requests that come from those proxies. The settings page shows the address your own request came from, which helps you tell whether a proxy is in use.

= A real visitor was blocked. How do I help them? =

The error message includes a short reference code (for example SE-C2). Turn on the spam log to see why submissions are blocked, and use the Unblock button in the blocked IP list if needed. Only a filled-in honeypot field counts towards an IP block, so a visitor whose browser failed another check can simply try again.

= Does it cover the WooCommerce block checkout? =

Yes, since 2.4.0. The script adds its values to the order the Checkout block sends through WooCommerce's Store API, and the order is checked before it is created, so card-testing bots that post straight to the Store API are refused. Express payment buttons in the Checkout and Cart blocks that use WooCommerce's own checkout flow send the same values. If a gateway's express button posts its own data and is refused with SE-C2, developers can give it the lighter checks with the `accessnow_spam_eater_store_api_context` filter.

= Can I block spam that people type themselves? =

Yes. List words, links or /regular expressions/ under Settings → Spam Eater → Your own rules. A matching submission is refused with reference SE-K5. What people type is only checked in memory and never stored.

= Where does the list of throwaway email domains come from? =

From the disposable-email-domains project (https://github.com/disposable-email-domains/disposable-email-domains), released under CC0 1.0 (public domain). A copy ships with the plugin in `includes/data/disposable-domains.txt` and is refreshed with each release; nothing is looked up online. Developers can change it with the `accessnow_spam_eater_disposable_domains` filter.

= Can the login throttle lock me out? =

No. It only slows down one username from one address, a successful login clears it, the longest wait is 15 minutes, addresses under Always allow are never made to wait, and Lost your password? keeps working. It is off until you turn it on.

= Which forms can't it protect? =

GiveWP donation forms made with its visual form builder (they only send the fields GiveWP knows about) and the Divi contact form (Divi has no step where another plugin can refuse a message with an error). Option-based GiveWP forms are protected.

= I use an earlier version in the "spam-eater" folder. How do I update? =

Up to version 2.2 the plugin was called Spam Eater by AccessNow and lived in a `spam-eater` folder. Install AccessNow Spam Eater and activate it: it switches the older copy off and uses the same settings, block list and statistics. Then delete the older copy. If "Delete all Spam Eater settings and statistics when the plugin is deleted" is ticked, untick it before deleting the older copy, because the settings are shared.

= What happens to my data if I delete the plugin? =

The IP block list and spam log are always deleted. Settings and statistics are only deleted if you tick "Delete all Spam Eater settings and statistics when the plugin is deleted" on the settings page.

== Credits ==

* Disposable email domains: the disposable-email-domains project, https://github.com/disposable-email-domains/disposable-email-domains, CC0 1.0 Universal.
* Check engine (`includes/engine/`): Spam Eater for PHP forms, https://github.com/scratchy-76/spam-eater-php, by AccessNow, MIT licence (GPL-compatible).

== Screenshots ==

1. Choose which forms to protect. Forms from plugins that aren't active are listed but can't be ticked.
2. IP blocking, trusted proxies for sites behind a CDN, where the script loads, and privacy options.
3. Blocked addresses (anonymised) with an Unblock button, and the optional log of blocked submissions with the reason for each.
4. Visitors see an ordinary form: no CAPTCHA, no puzzle and nothing to tick.

== Changelog ==

= 2.4.1 =
* Improved: the checks now come from the same code as Spam Eater for PHP forms, the free standalone version for sites that don't run WordPress, so a fix to one is a fix to both. The checks work just as before, and pages already in a cache keep working.
* Improved: fields with "csrf" in their name are no longer checked against your own words and patterns, the same as other security tokens.

= 2.4.0 =
* New: protects the WooCommerce Checkout block, including card-testing bots that post straight to WooCommerce's Store API, and the Cart block's express payment buttons. Turn it on or off under WooCommerce → Block checkout.
* New: protects WooCommerce's Pay for Order form and the account details form in My Account.
* New: protects Jetpack Forms, Kadence Forms (Form and Advanced Form blocks), WS Form, JetFormBuilder, SureForms, Everest Forms, MailPoet, Brevo, wpDiscuz, MemberPress, Paid Memberships Pro and GiveWP (option-based forms). WS Form, Everest Forms, JetFormBuilder and MailPoet were removed in 2.2.0 because Spam Eater could not check them; each is now checked where the plugin validates its own submissions. With MailPoet protected, the script loads on every page, because MailPoet can show a form in a pop-up anywhere.
* New: your own words and patterns to block (reference SE-K5). Words match whole words, ignoring capitals; /pattern/ lines are regular expressions, checked when you save. What people type is only checked in memory, never stored or logged.
* New: refuse throwaway email addresses (reference SE-M5), using a list of about 9,000 disposable email domains that comes with the plugin. Off by default, so nobody is turned away by surprise after updating.
* New: Always allow and Always block lists of IP addresses and ranges (CIDR, IPv4 and IPv6). Allowed addresses skip every check; blocked ones are refused (reference SE-H0) but, like automatic blocks, can still log in. These addresses are kept as you type them.
* New: slow down password guessing (off by default). After 5 failed logins in a row for one username from one address, that pair waits 30 seconds, doubling to at most 15 minutes (reference SE-T9). A successful login clears it, other addresses and usernames are not affected, and only a keyed hash is kept.
* New: a Dashboard widget with the last 30 days of refused spam, the same summary on the settings page, and an optional weekly email to the site's administration address (off by default). Only daily counts per reason are kept.
* New: administrators see a "Test this form as a bot" button under each protected form. It sends the form as a simple bot would and reports whether Spam Eater refused it; a refused test is not counted and nothing is sent.
* New: WP-CLI commands: `wp spam-eater stats`, `unblock`, `blocked list|clear`, `settings get|set|list`, `allowlist` and `blocklist add|remove|list`, `login-waits clear` and `check`.
* Fix: with Comment Forms on, comments sent through wpDiscuz were refused, because its form never had Spam Eater's fields. wpDiscuz now has its own setting; when it is off, Spam Eater leaves wpDiscuz comments alone.
* Fix: the Help tab's article links on the settings page pointed at a broken address.
* Accessibility: the "Plugin not installed or not active." note now has enough contrast, and the new settings are labelled and described for screen readers.
* Developers: new filters `accessnow_spam_eater_store_api_context`, `accessnow_spam_eater_disposable_domains`, `accessnow_spam_eater_login_free_attempts` and `accessnow_spam_eater_login_max_wait`.

= 2.3.0 =
* New: protects Clean and Simple Contact Form. Turn it on or off under Form Plugins.
* Fix: with Comment Forms switched on, every message sent through Clean and Simple Contact Form was refused as spam, real ones included. That form, and others like it, run their messages through WordPress's comment spam filter to use Akismet; Spam Eater now only checks real comments and reviews there. The new `accessnow_spam_eater_comment_types` filter changes which comment types are checked.
* New: help articles and Get support links under the plugin on the Plugins screen, and a Help tab on the settings page.

= 2.2.0 =
* Renamed to AccessNow Spam Eater, in an `accessnow-spam-eater` folder. Activating it switches off an older copy in the `spam-eater` folder and keeps its settings.
* New: protect your own forms with `accessnow_spam_eater_field()`, `accessnow_spam_eater_is_spam()` and `accessnow_spam_eater_fail_code()`, switched on and off under Custom forms. Works with multi-step forms and forms sent with fetch().
* Fix: WooCommerce's My Account "Lost your password?" form refused every request.
* Fix: administrators couldn't send password reset links from Users.
* Fix: express payment buttons (such as Apple Pay and Google Pay in the Stripe gateway's Payment Request button) that place orders through the gateway's own endpoint were refused.
* Fix: if a checkout template or checkout plugin leaves out the hook Spam Eater uses, the script now adds the hidden fields itself instead of every order being refused.
* Fix: pingbacks, trackbacks and XML-RPC comments no longer stop with an error page.
* Change: only a filled-in honeypot field counts towards an IP block, so customers who try again after a failed check are no longer blocked for a day.
* Change: IP blocks no longer apply to logging in, so nobody sharing an address can lock you out.
* Change: the checkout error now includes its reference code.
* Change: removed Divi, Strong Testimonials, Everest Forms, JetFormBuilder, MailPoet and Thrive from the settings. Spam Eater added its fields to those forms but never checked them.
* Faster: the script loads only on pages that show a protected form, and the stylesheet is gone. Sites updating from an earlier version keep loading it everywhere; see the new "Load the Spam Eater script on every page" setting.
* Settings for an inactive form plugin are kept when you save.
* The settings page warns when your site is behind a proxy that isn't listed under Trusted proxies.

= 2.1.0 =
* Renamed to Spam Eater by AccessNow.
* Privacy: the spam log no longer stores IP addresses or form contents. It is now off by default and records only the time, reason, page (without query string) and an anonymised IP, for 30 days. The old log is deleted when you update.
* Privacy: the IP block list stores a keyed hash of each address and an anonymised version for display. Attempt counters now expire after the block duration.
* Privacy: suggested privacy policy text under Settings → Privacy.
* Security: anti-spam tokens now rotate and expire, and are compared in constant time. Cached pages fetch fresh tokens when theirs are getting old. Forms from pages cached before the update keep working for 7 days.
* Security: the X-Forwarded-For header is only used for requests from proxies listed in the new Trusted proxies setting.
* Security: a honeypot field sent as a list no longer slips past the check.
* Scripts are now loaded as files instead of being printed inline.
* Hidden fields are filled in on focus and on submit too, which helps forms that load after the page (pop-ups, AJAX).
* New option to delete settings and statistics when the plugin is deleted.
* Settings are moved to new, prefixed option names automatically. The `spam_eater_client_ip` filter is deprecated in favour of `accessnow_spam_eater_client_ip`.
* Translation-ready: all settings labels can now be translated.
* Fix: Toolset Forms, BricksForge Pro Forms and WS Form are no longer checked. Spam Eater couldn't add its hidden fields to them, so genuine submissions were being blocked.

= 2.0.4 =
* Previous release.

== Upgrade Notice ==

= 2.4.0 =
Protects the WooCommerce Checkout block against card-testing bots, and 12 more form plugins. Adds your own words to block, throwaway-email and IP lists, a spam summary, a bot test and WP-CLI commands. If you use wpDiscuz, update: its comments were being refused.

= 2.3.0 =
If you use Clean and Simple Contact Form, update now: Spam Eater was refusing every message sent through it. This version protects it properly.

= 2.2.0 =
Fixes WooCommerce password resets and express payment buttons being refused, and stops IP blocks from locking anyone out of logging in. Adds functions for protecting your own forms.

= 2.1.0 =
Privacy and security update. The old spam log, which held IP addresses and form contents, is deleted. If your site is behind a proxy or CDN, add its addresses under Trusted proxies.
