=== Nolgard ===
Contributors: nolgard
Tags: spam, registration, comments, login, security
Requires at least: 6.0
Tested up to: 7.1
Requires PHP: 7.4
Stable tag: 1.0.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Checks WordPress form submissions with the Nolgard service.

== Description ==

Nolgard checks browser form submissions before WordPress processes them. Supported core forms are login, registration, lost password and comments. Also supported: WooCommerce My Account login/registration and classic checkout, Contact Form 7, WPForms Lite, Fluent Forms, Gravity Forms and Elementor Pro Forms. Choose protected forms in Settings → Nolgard. Third-party checkboxes appear only while the matching plugin is active; they are off until selected.

A Nolgard account, site key and secret key are required. The browser loads a script from the configured Nolgard service. When a protected form is submitted, the browser requests a token; WordPress sends that token, the visitor's IP address, the action name and an account ID or email address (when available) to the service for verification. See https://nolgard.com/privacy and https://nolgard.com/terms before enabling the service. This plugin does not send WordPress passwords.

The service determines whether a request may proceed. Limited requests proceed and trigger the nolgard_limit action. Rejected requests receive a generic error. Shadow decisions silently send comments to spam; login, registration and password reset cannot safely simulate success, so those requests receive the same generic error.

Timeouts, 5xx responses and over-quota follow the service-unreachable policy: Allow is the default; choose Block for stricter enforcement. Missing tokens and an invalid secret key (HTTP 401) are always rejected, whatever that policy says; an invalid secret also shows an admin notice until it is fixed. Protection is inactive until both keys are configured.

XML-RPC, REST authentication and application-password logins are not protected. Login protection applies to wp-login.php browser logins, not arbitrary programmatic wp_authenticate calls. Core single-site registration is supported; multisite network signup forms are not included. Logged-in administrators bypass comment verification only. Pingbacks and trackbacks are not browser forms and are not checked. Custom themes must retain the standard core form IDs or supply selectors using the extension filter.

WooCommerce's block checkout (Store API) is not protected. It sends JSON rather than the classic form's serialized fields, and this release does not attach a token to that request. Use the classic [woocommerce_checkout] shortcode to protect checkout. Classic checkout, login and registration reject shadow decisions rather than pretending an account or order exists.

Contact Form 7 shadow submissions skip mail and follow its success path, not its visible spam-error path. WPForms shadow submissions disable mail and entry saving. Fluent Forms and Elementor shadow submissions return success before inserting an entry or running submit actions. Gravity Forms marks shadow entries as spam (its standard silent-success path). Gravity Forms and Elementor Pro Forms are untested against a live install; their commercial plugins are not included in the test environment.

Live integration checks: WooCommerce 11.2.1 My Account registration and classic COD checkout, Contact Form 7 6.2.3 default form, WPForms Lite 2.0.2.2 simple contact form, and Fluent Forms 6.2.16 default contact form. Each was submitted in a real Chrome browser with an observe-mode Nolgard site: a token reached PHP, service verification allowed it, and the submission succeeded. Tokenless requests were rejected for all five paths. Contact mail was delivered to a local SMTP sink. WooCommerce My Account login and third-party shadow behavior were not live-tested. Gravity Forms and Elementor Pro remain untested against a live install.

The browser obtains a fresh single-use token on every submit, including AJAX retries after validation failures. Themes or scripts that bypass submit events by calling form.submit() directly need an explicit integration.

== Installation ==

1. Upload the nolgard directory to wp-content/plugins or install the ZIP through Plugins → Add New.
2. Activate Nolgard.
3. Add http(s) origins for your WordPress site in Nolgard and copy its site and secret keys.
4. Open Settings → Nolgard, enter both keys, choose protected forms and save.
5. Click Test connection. The secret is never displayed again; its last four characters are shown as a hint. Leave the secret input blank to keep it.

== Frequently Asked Questions ==

= What if my site is behind a reverse proxy? =

By default only REMOTE_ADDR is trusted, never X-Forwarded-For. A site-specific plugin may use the nolgard_remote_ip filter to supply an IP from a proxy it explicitly trusts. Do not blindly trust visitor-controlled forwarding headers.

= Can another form plugin integrate? =

Implement Nolgard\Integration, then register an instance with Nolgard\Integrations::register() on the nolgard_integrations action. Supply form IDs/labels, current-page selectors and server hooks. The registry only boots available integrations. nolgard_form_selectors filters the current-page selector/action list.

Server integrations call nolgard_verify($action, $account) for a normalized result or WP_Error. The account array accepts id and email. Nolgard\Guard::decide() additionally applies the unreachable policy and nolgard_decision filter; an invalid secret always returns block, even through the filter. The filter receives decision, normalized result (or WP_Error), and context including action. nolgard_limit receives result and context. Visitor messages must not expose service diagnostics.

= How do I develop with Docker? =

The browser and PHP container can use different local service hostnames. Set Service URL to http://host.docker.internal:8837 and Browser service URL to http://localhost:8837. The browser override is development-only and should be empty in production. Alternatively the nolgard_client_url filter can return the complete browser client.js URL. Never expose your secret in browser configuration.

= Is the secret encrypted? =

It is stored in the WordPress options table with autoload disabled. Protect database access and backups. Only administrators with manage_options may edit settings or test the connection. Uninstalling the plugin deletes its options.

== Changelog ==

= 1.0.0 =
* Initial release with core login, registration, password reset and comment protection, server verification, settings and an integration registry.
* Added active-plugin-only settings and integrations for WooCommerce classic forms, Contact Form 7, WPForms, Fluent Forms, Gravity Forms and Elementor Pro Forms; AJAX submissions request fresh tokens.
