# Native Forms CAPTCHA

Help4 Forms supports optional Cloudflare Turnstile while retaining its first-party honeypot and minimum-submit-time checks. CAPTCHA is disabled by default and configured separately for each WordPress site.

## Configure a site

1. Open **Help4 Builder > Form Security**. This separate settings screen is visible only to administrators and roles explicitly granted `manage_help4_form_settings`.
2. Review **CAPTCHA and bot protection**.
3. Choose **Cloudflare Turnstile**, enter that site's site key and secret key, choose a responsive widget size and theme, then save.
4. Edit each form only when it should override the site default. Existing forms use **Use site setting**.

The secret field is write-only. Leaving it blank preserves the stored value; clearing it requires the explicit checkbox. On multisite, every site stores its own option through WordPress's site-scoped option API. No network-level credential is created.

## Daily staff workflow

The Forms inventory leads with Create, Submissions, and Edit. Creating or editing opens a focused workspace with ordered visual question cards. Staff can add, duplicate, remove, drag, or keyboard-reorder questions; removal and ordering remain undoable until Save. Common labels, choices, required state, width, and default answers are visual controls. Internal keys and raw extension data remain under clearly marked expert disclosures, and unknown extension fields round-trip unchanged.

Preview is side-effect free: changing device or visitor-state previews never stores an entry, sends mail, calls a webhook, or requests CAPTCHA. The ready-to-save summary explains visitor availability, staff notices, autoresponder state, effective CAPTCHA/honeypot state, and the webhook hostname. A form-level CAPTCHA choice either inherits the site setting, disables CAPTCHA for that form, or requests Turnstile; the displayed effective state is authoritative for that edit session.

Saving is one explicit transaction. Failed or conflicting saves retain entered values. A conflict never overwrites newer work automatically; staff can copy their unsaved form data and open the latest version in another tab. Archiving is reversible, keeps stored entries, and makes exact ID/slug embeds fail closed until the form is restored and deliberately reopened.

The inventory search matches form name, purpose, availability, and slug. Submission counts open that form's filtered inbox. Entries can be filtered by form, text, and inclusive site date range and paginate 25 rows at a time. CSV export repeats the active filters, requires a fresh WordPress nonce and `export_help4_form_entries`, caps output at 5,000 rows, and prefixes spreadsheet-formula characters so an answer cannot execute when opened in spreadsheet software. Stored entries and accepted mail attempts are separate facts: an accepted `wp_mail()` handoff is not proof that a recipient received the message.

The Privacy & Retention workspace is planning, holds, and exact export only. A draft freezes its UTC age cutoff for site-timezone display, includes only selected current non-archived forms, and can exclude staff statuses. Saving unchanged selections is idempotent and preserves the cutoff. Database failures, stale form IDs, and count/digest mismatches produce an explicit invalid preview, never a usable-looking fingerprint.

An authorized administrator can add a per-entry hold with a required reason. Help4 records actor, site-local time, and a new revision UUID. Add/remove requests use a one-time UUID for replay safety and return HTTP 409 when stale. A hold only excludes an entry from eligibility; it does not archive, delete, or anonymize it.

The dedicated privacy CSV is bound to the exact preview policy revision and eligible-set fingerprint. It is restricted to separately authorized staff, contains the matching records and their personal data for the stated privacy-review purpose, neutralizes spreadsheet-formula prefixes, prepares rows in bounded batches, and rechecks the complete digest before output. This workflow has no Apply action, deletion, anonymization, or scheduler. See `FORMS_PRIVACY_RETENTION.md` for the staff walkthrough and safety boundaries.

Forms access is split so one grant does not silently expose every submission or export. Administrators receive the dedicated Forms capabilities by default:

```php
$role = get_role('editor');
$role?->add_cap('manage_help4_forms');          // Create, edit, archive, restore.
$role?->add_cap('view_help4_form_entries');     // Read stored submissions.
// Grant only when required:
$role?->add_cap('export_help4_form_entries');   // Download filtered PII as CSV.
$role?->add_cap('access_help4_form_privacy');   // Open the read-only privacy workspace.
$role?->add_cap('manage_help4_form_privacy');   // Change the site-defined draft policy.
$role?->add_cap('manage_help4_form_privacy_holds'); // Place and release entry holds.
$role?->add_cap('export_help4_form_privacy');   // Export the exact eligible set.
$role?->add_cap('manage_help4_form_settings');  // Site CAPTCHA and newsletter settings.
```

These grants are intentionally persistent WordPress role capabilities. To revoke one, call `$role?->remove_cap('capability_name')`. Do not grant Entries or Export to a role that should not read or download personal information. A form editor with only `manage_help4_forms` cannot open Entries, export CSV, or change sitewide CAPTCHA/newsletter settings.

Cloudflare's exact official always-pass test site/secret pair is supported for staging and automated acceptance. Siteverify must still return `success: true`; only that exact pair may use Cloudflare test placeholder or empty action/hostname values. Production and mixed credentials continue to require the form-specific action and current site hostname.

## Failure and migration behavior

- **Provider disabled:** no third-party script is loaded or contacted. Native non-JavaScript, AJAX, and REST submission continue using the honeypot and timing protections.
- **Turnstile selected without both keys:** the form falls back to the existing honeypot and timing checks instead of locking out migrated forms. Administrators see a warning.
- **Turnstile configured:** a token is required for non-spam submissions across native POST, AJAX, and REST. Missing or rejected tokens return an accessible retry message. Provider/network failures return a temporary-unavailability message and HTTP 503 for AJAX/REST.
- Honeypot or too-fast submissions retain the existing silent-spam behavior and do not contact Turnstile.

The widget has a visible security-verification label and instructions, a responsive container, and a non-JavaScript explanation. AJAX errors are announced assertively and receive focus; successful submissions use a polite status. Native POST errors return to the form with a stable, non-secret error code and a server-rendered alert.

## Data handling

When Turnstile is active, the browser requests Cloudflare's Turnstile API script. On submission, the server sends the private secret, the short-lived widget response, the submitter IP address when available, and an idempotency identifier to Cloudflare's verification endpoint. The response token and secret are not stored in form entries, localized JavaScript, REST responses, exports, or network settings. Help4 does not solve, bypass, or simulate a visitor's CAPTCHA.
