=== TillFoundry Wishlist Bot Shield ===
Contributors: lijnam
Tags: woocommerce, wishlist, bots, security, spam
Requires at least: 6.0
Tested up to: 7.1
Requires PHP: 7.4
Stable tag: 0.1.2
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Reject nonce-less and known-crawler wishlist requests, challenge anything that does not look like a browser, and rate-limit the rest before your wishlist plugin runs.

== Description ==

Wishlist plugins render the wishlist button as an ordinary link: `?add_to_wishlist=123&_wpnonce=...`. Search engines, social scrapers and AI crawlers follow that link on every paginated shop and category page. Each follow is a real request, and on many wishlist plugins it is a real database write, so a wishlist row is created for a visitor that does not exist. Stores with large catalogues have reported thousands of bot hits a day, database servers pinned at 100% CPU and, in the worst cases, a shop that becomes unreachable.

Wishlist Bot Shield inspects the wishlist action request *before* your wishlist plugin processes it. There is nothing to change in the wishlist plugin and no template to edit.

**What it does**

* Rejects wishlist action requests with a missing or malformed nonce.
* Blocks known crawler, scraper and automation user agents on wishlist action URLs.
* Challenges anything that does not look like a browser with a small JavaScript and signed-cookie test that a spoofed user agent cannot pass.
* Applies per-visitor and site-wide rate limits to wishlist action requests, with HTTP 429 and a `Retry-After` header.
* Records blocked and challenged requests in a log with the offending user agent and a salted address hash, plus a WordPress dashboard widget.
* Adds `robots.txt` Disallow rules, an `X-Robots-Tag: noindex, nofollow` header, a `noindex` robots meta tag and a canonical link with every wishlist argument removed.
* Runs in monitor mode first if you want to confirm the rules match real traffic before enforcing.
* Prunes the log daily, seven days by default.

**What it does not do**

* It does not modify your wishlist plugin, its storage or its templates.
* It does not claim to stop every crawler. A crawler that runs JavaScript in a full headless browser can still pass the challenge; the user-agent list and the rate limits are filterable so you can tighten the rules without a code change.
* It does not help when a full-page cache or CDN serves the wishlist URL without running PHP. No wishlist write happens in that case, but the request is invisible to the log.

**Privacy**

The log stores a truncated user-agent string and hashed visitor values. The raw IP address is never stored: both the address hash and the visitor fingerprint are keyed with your site's authentication salt. Retention defaults to seven days, admin screens show only hashes, and the "Delete data on uninstall" option removes everything when the plugin is deleted.

== Installation ==

1. Upload the plugin folder to `/wp-content/plugins/`, or install the ZIP through **Plugins → Add New → Upload Plugin**.
2. Activate the plugin through the **Plugins** screen in WordPress.
3. Open **Wishlist Bot Shield** in the admin menu. It is the plugin's own top-level menu; **Blocked Requests** and **Settings** are its submenus, so every screen is reachable whether or not WooCommerce is active.
4. Review the settings. The defaults protect wishlist URLs immediately; switch to monitor mode first if you want to observe traffic before blocking it.

== Frequently Asked Questions ==

= Does this plugin require WooCommerce? =

WooCommerce is expected on a store, but it is not a hard requirement. Without WooCommerce the plugin still protects wishlist action URLs, its own **Wishlist Bot Shield** menu stays in place, and the required capability becomes `manage_options` instead of `manage_woocommerce`.

= Will this break my wishlist plugin? =

No. The shield only looks at requests that carry a recognized wishlist query argument and leaves every other request untouched. Allowed requests reach your wishlist plugin exactly as before.

= What happens to a real customer? =

A real browser receives the challenge once, sets the cookie and is then allowed. A shopper whose browser blocks cookies is blocked after one reload rather than being caught in a loop.

= Where is the log stored? =

In a single custom table, `{prefix}_wbs_requests`, so the rate limiter and the blocked-request log share one indexed write instead of writing an option on every request.

= Can I tune the list of blocked user agents? =

Yes. The `wbs_bot_user_agents` filter replaces or extends the built-in list, and `wbs_watched_query_args` changes which query arguments are treated as wishlist actions.

= Does the plugin send my data anywhere? =

Not during normal operation. The free build stores no licence key and makes no outbound request on activation, on a schedule or on any of its own admin screens. A dormant licence client is bundled and is off by default; it only contacts the TillFoundry licence server if a site owner deliberately opts in, and the free build provides no screen that asks for a key. See "External Services" for the endpoints and the exact payload.

== Screenshots ==

1. The overview screen, showing blocked and challenged request counts for the last seven days.
2. The Blocked Requests log, filtered by reason.
3. The settings screen with monitor mode selected.
4. The dashboard widget showing the last seven days.

== External Services ==

The plugin is dormant by default and sends nothing off your site during normal operation. The licence client shipped in `includes/License/` makes no request unless a site owner explicitly opts in by storing a licence key or by returning `true` from the `wishlist_bot_shield_license_requests_enabled` filter. The free build provides no screen that accepts a key, so a default install never reaches the service.

If you opt in, the client contacts the TillFoundry licence server:

* **Service and provider:** TillFoundry, https://tillfoundry.com
* **Endpoints:** https://tillfoundry.com/api/v1/activate, https://tillfoundry.com/api/v1/validate, https://tillfoundry.com/api/v1/deactivate
* **When:** on saving a licence key (activate), on a cached validation expiring (validate, using a 12-hour cache), and on removing the key (deactivate).
* **Data sent:** the licence key, `site_url`, `home_url`, the plugin slug and version, the WordPress version, the PHP version, whether the site is multisite, and the site locale. The plugin slug, plugin version and site URL are also sent as request headers.
* **What is never sent:** visitor IP addresses, the blocked-request log, user data, or any content from your site.
* **Provider terms:** https://tillfoundry.com/terms/
* **Provider privacy policy:** https://tillfoundry.com/privacy-policy/

No other external service is used. There are no external assets, no tracking and no analytics.

== Changelog ==

= 0.1.2 =
* The plugin now owns a single top-level **Wishlist Bot Shield** admin menu, with **Blocked Requests** and **Settings** as its submenus. The screens no longer move between the WooCommerce and Tools menus, and the menu always registers, so every screen stays reachable whether or not WooCommerce is active. Page slugs are unchanged, so existing links and the plugin's Settings action link keep working.
* The plugin's own version is now shown on its own screens: next to the title on the Overview screen and in the admin footer on all three screens.
* Prepared for distribution through the WordPress.org plugin directory. No functionality is held back or changed by the listing.

= 0.1.1 =
* Fixed the WordPress.org Plugin Check `outdated_tested_upto_header` error by declaring `Tested up to: 7.1`. Compatibility declaration only; no runtime change.
* Corrected the readme's external-services disclosure: the bundled licence client is dormant and opt-in, and the endpoints and payload it can use are now named under "External Services".

= 0.1.0 =
* Initial release.
* Nonce guard, known-crawler blocklist, signed-cookie browser challenge and per-visitor/site-wide rate limiting.
* Blocked-request log with CSV export and a dashboard widget.
* robots.txt rules, noindex headers and canonical cleanup for wishlist action URLs.
* Monitor mode and daily log pruning.

== Upgrade Notice ==

= 0.1.2 =
* The admin screens now live under the plugin's own top-level Wishlist Bot Shield menu instead of moving between the WooCommerce and Tools menus. Page slugs are unchanged, so no link or bookmark breaks.

= 0.1.1 =
* Compatibility update: declares support for WordPress 7.1. No functional or behavioural change.

= 0.1.0 =
* Initial release.
