=== Bodholdt Contact ===
Contributors: bodholdtlabs
Tags: contact form, contact widget, support, anti-spam, customer
Requires at least: 6.5
Tested up to: 7.0
Requires PHP: 8.0
Stable tag: 5.1.0
License: GPL-2.0-or-later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

A secure contact widget with email verification and optional recognition for signed-in customers.

== Description ==

Bodholdt Contact adds a small floating contact button to your site. New installations keep the widget hidden until an administrator reviews the settings and enables it. When a visitor opens the enabled widget and enters an email address, one of two things happens:

* **A securely recognized, signed-in customer can skip the code.** The visitor must be signed in to a WordPress account whose email exactly matches the address entered. In Bodholdt Licensing mode, that same account email must also have a currently active, non-expired license. Merely entering a customer's email never bypasses verification.
* **Everyone else verifies by email.** The visitor receives a one-time 6-digit code before the message form opens. Challenges are short-lived, bound to the browser flow, attempt-limited, and single-use. A honeypot and atomic rate limits further constrain automated abuse.

This keeps the signed-in customer path quick without treating a known email address as proof of identity.

**Features**

* Floating contact widget (footer) with a distinctive, themeable look.
* Session-bound one-time-code email verification for unsigned-in and unrecognized senders, with resend and retry controls.
* Honeypot protection plus atomic source, destination, and global rate limits.
* "Recognize returning customers via": choose none, matching signed-in WordPress users, or matching signed-in users with an active Bodholdt Licensing entitlement.
* A settings page that opens with a plain-language status card ("is it working?") and a live preview you can step through all three visitor screens: brand name, accent color with curated swatches, light/dark/auto appearance, corner style, header/intro/success copy, and the email that messages are sent to.
* Operator controls for an optional weekly activity email and the private "Working the Door" Fun Pass, including its sound and confetti effects.
* Translation-ready visitor widget with configurable brand and copy.
* Multisite ready: network activation prepares every existing site and every site added later, each with its own settings and storage.

== Installation ==

1. Upload the `bodholdt-contact` folder to `/wp-content/plugins/`, or install the ZIP via Plugins → Add New → Upload.
2. Activate the plugin.
3. Go to **Settings → Bodholdt Contact** to set your brand name, accent color, copy, notification email, customer-recognition mode, and optional operator reports.
4. Review the live preview, select **Show the widget**, and save. The widget then appears in your site footer.

== Frequently Asked Questions ==

= Do my customers have to verify by email? =
Usually, unless they are securely recognized. In WordPress-user mode, a visitor can skip the code only while signed in to an account whose email exactly matches the address entered. In Bodholdt Licensing mode, that signed-in account must also have an active, non-expired license for the same email. Signed-out visitors always verify, even if they enter a customer's address.

= Why is the widget not visible immediately after activation? =
Fresh installations are deliberately disabled so an unfinished contact form is not exposed with an unreviewed destination address or recognition policy. Open **Settings → Bodholdt Contact**, review the configuration, enable **Show the widget**, and save. Upgrading from an earlier version preserves the site's existing enabled or disabled setting.

= Does it require Bodholdt Licensing? =
No. The Bodholdt Licensing recognition mode needs it, but the widget works without it using matching signed-in WordPress-user recognition or email verification for everyone.

= Where do submitted messages go? =
To the email address you set in Settings → Bodholdt Contact (defaults to your site admin email), with the sender's address as Reply-To.

= Does it work with the other Bodholdt Labs plugins? =
Yes. With a compatible **Bodholdt Tickets** release active, Contact attempts to route accepted messages into tracked support tickets you reply to from the Support Desk. If the compatible ticket API is unavailable or ticket creation cleanly fails, Contact uses its configured email route. See https://bodholdtlabs.com/software/bodholdt-ticketing/?utm_source=contact&utm_medium=readme&utm_campaign=xsell . **Bodholdt Licensing** can recognize a matching signed-in account with an active, non-expired license so that user skips the emailed code. See https://bodholdtlabs.com/software/bodholdt-licensing/?utm_source=contact&utm_medium=readme&utm_campaign=xsell .

= Does it work on a multisite network? =
Yes. Activating it for the whole network prepares every existing site, and any site created later is prepared automatically the moment it is added, so you do not have to visit each site's admin. Every site keeps its own settings, its own verification tables, and its own scheduled cleanup. Deactivating across the network clears the scheduled tasks on every site, and deleting the plugin from the network removes its data site by site.

= What database tables does it create? =
Two of its own, both prefixed with your WordPress table prefix: `bodholdt_contact_challenges` (the short-lived verification state, all HMAC-hashed, no raw codes or tokens) and `bodholdt_contact_rate_limits` (abuse counters, keyed by salted hash, not by raw email or IP). The optional Working the Door arcade adds four shared `bod_arcade_*` tables (`bod_arcade_progress`, `bod_arcade_achievements`, `bod_arcade_events`, `bod_arcade_celebrations`) that are shared with any other Bodholdt plugin using the arcade. These hold only operator gamification data (points, unlocked achievements, a scoring log, pending celebration cards), never visitor personal data. Uninstall drops the two Contact tables and removes only Contact's own rows from the shared arcade tables. The shared tables themselves are left in place, since another Bodholdt plugin may still be using them, so on a site where Contact was the only one you will find those four tables sitting empty afterwards.

= What personal data does it store, and how do I handle export and erase requests? =
The only personal data the plugin stores is the email address a visitor enters, kept briefly in the `bodholdt_contact_challenges` table with the state of that verification (for example, whether a code was sent or confirmed) and then deleted automatically after a short time. Where the message itself ends up depends on how it is delivered. With a compatible **Bodholdt Tickets** release active, the message is saved as a support ticket in that plugin's tables, so the sender can follow it and reply, and it stays there until you delete it. With no ticket destination available, the message is emailed to you and this plugin writes no copy of it to the site database. The plugin integrates with the standard WordPress privacy tools, so a request under GDPR or a similar law is handled the normal way: **Tools → Export Personal Data** returns the verification records held for an address, and **Tools → Erase Personal Data** deletes them. When messages are being stored as tickets, the erase result says so and points you at the Support Desk, since the message text lives in the ticket store rather than here. The plugin also contributes suggested text to **Settings → Privacy** for your site's privacy policy, worded to match whichever delivery path your site uses.

== Screenshots ==

1. The floating contact widget open on a site, ready for an email address.
2. A recognized visitor signed in to the matching account skips the code and goes straight to the message box (with an "account recognized" badge).
3. Unknown senders verify with a one-time 6-digit email code before their message goes through.
4. The settings page, with a live preview that updates as you change the brand name, accent color, and copy.

== Changelog ==

= 5.1.0 (2026-07-19) =
* **Messages now reach you exactly as they were typed.** A link containing encoded characters used to lose part of itself on the way to you, so a pasted address could arrive broken. Quoted markup, such as a customer showing you the HTML on their page, could vanish from the message entirely. Both are fixed. Messages routed into Bodholdt Tickets are now escaped as well, so nothing a visitor sends can render as live content in your Support Desk.
* **Asking for a fresh code works again.** The hourly limit on codes sent to one address was exactly the number the widget itself could spend, so "start again to request a new code" was always refused. The limit now leaves room to start over, and when a visitor does have to wait, the widget says roughly how long.
* **Clearer answers when something is wrong.** If the plugin cannot create its database tables, the settings page and an admin notice now say so plainly instead of reporting that the widget is live. If a saved setting had to be shortened, or had formatting removed, the page now names the field that changed instead of only saying "Settings saved".
* **Friendlier recovery in the widget.** After too many incorrect codes the widget returns the visitor to the email step, rather than offering a Resend button that could never work. The resend countdown now follows real time, so it no longer looks stuck after someone switches to their mail app on a phone.
* **Sites behind a proxy or CDN.** Added a `bodholdt_contact_client_ip` filter so operators whose web server does not pass through the real visitor address can supply it. Without it, every visitor could end up sharing a single rate limit bucket. Forwarded headers are still never trusted by default.
* **Smaller fixes.** A three-digit colour such as #0af no longer produces invalid styling. The message box now shows its length limit before you send. The suggested privacy policy wording and the erase result describe the ticket store accurately when Bodholdt Tickets is handling messages. The Licensing note no longer contradicts the status card. Gamification labels can now be translated. A failed table creation no longer repeats a full migration on every page load, and no longer disables the activity report permanently.

= 5.0.2 (2026-07-19) =
* **Privacy tools (GDPR export and erase).** The plugin now integrates with the built-in WordPress privacy tools. **Tools → Export Personal Data** returns the verification records held for an email address, and **Tools → Erase Personal Data** deletes them, so you can answer data subject requests the standard way. The plugin also adds suggested wording to **Settings → Privacy** for your privacy policy. No change to how the contact form works.

= 5.0.1 (2026-07-19) =
* **Accessible contact form (WCAG 2.1 AA).** The visitor widget now passes accessibility review: readable success and error text in dark mode, visible field borders and focus outlines, autocomplete on the email and verification-code fields, a properly announced delivery confirmation, and correct focus handling on Resend. This matters if your site serves visitors in the EU, where the European Accessibility Act applies to consumer-facing forms.
* **Licensing and packaging.** The bundled confetti library now ships its required copyright notice, and the release archive uses correct, non-world-writable file permissions. The readme now names the database tables the plugin creates.

= 5.0.0 (2026-07-16) =
* **Stronger verification state.** One-time codes and opaque flow tokens are stored as HMAC hashes in plugin-owned per-site tables. Each challenge is bound to its browser flow, expires quickly, enforces exact six-digit input and attempt/resend limits, and can authorize at most one delivered message through atomic state transitions.
* **Bounded abuse handling.** Atomic database-backed limits now cover source addresses, destination emails, global mail volume, widget opens, and message attempts. An hourly security task removes expired challenges and counters; a failed delivery releases the verified flow for a safe retry instead of silently consuming it.
* **Precise customer recognition.** Skipping the emailed code requires a signed-in WordPress account whose email exactly matches the address entered. Licensing mode additionally requires a currently active, non-expired Bodholdt license, checked again when the message is submitted. A known email address alone is never proof of identity.
* **Safer request boundaries.** Each public action has its own nonce, cached pages can refresh the complete nonce set, malformed non-scalar fields are rejected cleanly, and message length is validated rather than silently truncated.
* **Safer delivery.** A support ticket is considered delivered only after its first message is stored. If that partial write fails, direct-email fallback occurs only after the empty ticket is confirmed removed, preventing duplicate or orphaned customer conversations.
* **Deliberate first run and upgrades.** Fresh installations start with the widget hidden and weekly summary email off, then show a direct setup notice. Existing installations retain their prior live/hidden behavior and automatic reports even if they never opened the old settings page. In-place and automatic updates create required storage on the first request instead of waiting for an administrator visit.
* **Operator controls that match the documentation.** The settings page now exposes the weekly activity email and the private Fun Pass, sound, and confetti switches. Blank display copy falls back consistently in both the preview and live accessible dialog.
* **Complete lifecycle support.** Activation creates or upgrades per-site security storage and schedules cleanup; network activation covers existing sites and sites added later. Deactivation clears short-lived security state and scheduled tasks without deleting configuration. Uninstall removes this plugin's settings, owned tables, legacy verification remnants, scheduled tasks, and only its own rows from shared Bodholdt Arcade tables.
* Requires WordPress 6.5 or newer and PHP 8.0 or newer.

= 4.7.4 (2026-07-11) =
* **Sturdier under odd input.** The contact endpoints and the settings save now shrug off malformed requests (for example a field sent in the wrong shape) and return a normal validation response instead of erroring. This only ever affected hand crafted requests, never a real visitor or a normal save, but it keeps your error log clean.
* **Precise multisite cleanup.** On a multisite network, turning the plugin off on a single site now leaves the other sites' weekly report untouched; only a full network deactivation clears them all.
* **Housekeeping.** Trimmed a few unused entries from the "Working the Door" scoreboard so its catalog only lists rewards you can actually earn.

= 4.7.3 (2026-07-11) =
* **Cleaner activity readout.** The "last 7 days" line and the weekly email always show sensible figures now. Percentages stay between 0 and 100, the counts never read backwards, and the wording reads correctly even when a number is 1.
* **Tidier multisite cleanup.** On a multisite network, turning the plugin off now clears its scheduled weekly report on every site, leaving nothing behind.

= 4.7.2 (2026-07-11) =
* **True open-rate.** The activity summary now also counts how many people opened the widget, so you can see the whole funnel: opened, entered an email, and finished. It reports the overall share of opens that turned into a message, which tells you at a glance whether people are dropping off before they even start.

= 4.7.1 (2026-07-11) =
* **See how your contact form is doing.** The settings page now shows a short "last 7 days" activity line (how many people started, how many were sent a code, and how many finished after verifying), and you get an optional weekly email summary. This makes it easy to tell whether the email step is turning visitors away, so you can switch recognition mode if it is.

= 4.7.0 (2026-07-11) =
* **Stronger proof before the code is skipped.** A returning customer now skips the email code only when they are signed in to the matching account. A visitor who simply types a known customer's email address is asked to verify by code like anyone else. This closes a way someone could send you a message posing as one of your customers.
* **Tougher on abuse.** Verification codes are now capped per destination address as well as per sender, a valid code can be used only once even under a rapid burst of requests, and automated "bot" traffic is rate limited before it can touch anything. Together these bound spam and reduce the widget's usefulness for sending unwanted code emails to other people.
* **Off means off.** When you switch the widget off, its background endpoints stop responding too, not just the visible button.
* **Accurate tallies.** The "Working the Door" counts tick over only after a message is actually delivered, so your Door Report reflects reality even when mail is misconfigured.
* **Polish.** Very long messages are trimmed to a sensible length, the celebration animations stay within the screen, and the arcade labels are translatable (the shared level-up HUD badges stay in English, since a shared component cannot carry a fixed text domain).

= 4.6.0 (2026-07-11) =
* **The code is checked the moment you enter it.** When a visitor types their verification code and continues, the widget now confirms it right away, so a wrong or mistyped code is caught before they write their whole message instead of after.
* **The widget keeps working on cached sites.** If your pages are served from a cache and the security token in the page has gone stale, the widget quietly refreshes it and carries on, so the contact form no longer breaks with a "network error" on heavily cached sites.
* **You are told when a code cannot be emailed.** If your site cannot send the verification email (for example mail is not configured yet), the visitor now sees a clear "please try again in a few minutes" message instead of being stranded on a code screen with no code ever arriving. Submitted messages are handled the same way, so a message is never quietly lost.
* **Clean removal.** Deleting the plugin now removes its settings, its "Working the Door" tallies, and its rows in the shared arcade tables. The four shared `bod_arcade_*` tables and the `bod_arcade_db_version` option are deliberately left alone, because uninstall has no safe way to tell whether another Bodholdt plugin still needs them. If Contact was the only Bodholdt plugin on the site, those four tables stay behind empty and you can drop them yourself.
* **Polish.** The notification address now tells you if what you entered was not a valid email instead of silently ignoring it, the header, intro and success text show exactly what you typed, those fields gained sensible length limits, and the "Working the Door" operator text is translatable.

= 4.5.1 (2026-07-11) =
* **The widget stays readable with any accent color.** Button text and links now choose their color from the accent's real contrast, so a light accent (like a bright cyan) gets dark, legible text instead of washing out on the white card. The admin live preview reflects the same result.

= 4.5.0 (2026-07-11) =
* **Working the Door.** A calm, optional arcade for you, the operator. Because contact activity happens while you are away, the settings page now greets you with a Door Report of who you waved through, the strangers who verified, the bots you bounced, and the messages handled since your last visit, plus a rank and XP that grow as your door gets busier. It sits behind a Fun Pass switch (on by default, sound off) and stores everything in your own database.

= 4.4.0 (2026-07-10) =
* **A clearer settings page and a preview you can walk through.** The page now opens with a status card that answers "is my widget live and doing what I set up?" in plain sentences: whether it shows, where messages go, who skips verification, and the address you are notified at. The live preview steps through all three screens a visitor sees (email, then the 6-digit code, then the message and its success note), so the verification flow you configure is no longer invisible. On wide screens the form and preview sit side by side. Accent color gains curated swatches and a readability note, and the appearance and corner-style choices became tidy segmented controls.
* **The verification step no longer dead-ends.** If a visitor's code does not arrive or they typed the wrong address, they can now "Resend code" (with a short countdown that respects the anti-spam limit) or go back and fix their email. Running out of attempts now says so and offers a fresh code instead of failing silently. The opening line also explains why the email is asked for first.

= 4.3.0 (2026-07-09) =
* Added a dismissible panel on the settings page inviting you to turn contact messages into trackable Bodholdt Tickets when that plugin is not active. This mirrors the confirmation shown when it is active. Nothing about the widget changed.

= 4.2.0 (2026-06-05) =
* **White-label appearance.** The widget now offers Light, Dark, or Auto (match the visitor's system) display modes plus a corner-style choice, all derived from your single accent color with readable text and contrast shades computed automatically, so it blends into any site. The settings page got a matching refresh and the live preview reflects every change.

= 4.1.3 (2026-06-04) =
* WordPress.org readiness pass: added translator comments for placeholder strings, removed the manual text-domain load (WordPress loads translations automatically), and hardened a server-variable read. No change to how the widget behaves.

= 4.1.2 (2026-06-03) =
* Renamed the Bodholdt Tickets integration to its current name and refined the verification step layout. The email address and the "enter your code" instruction now sit on their own lines.

= 4.1.1 (2026-05-22) =
* Settings page now shows a **live preview** of the widget that updates as you change the accent color, header, and intro (§7.4 Steve Jobs pass).

= 4.1.0 (2026-05-20) =
* Bodholdt Tickets front-door. With a compatible Bodholdt Tickets release active, contact submissions can create a support ticket (and notify you by email) instead of a plain email, so customers can track and reply to them, and you work them in the Support Desk. If Tickets has categories, the widget shows a “What’s this about?” picker. Without a compatible Tickets API, it behaves exactly as before (emails you). All recognition + anti-spam unchanged.

= 4.0.0 (2026-05-20) =
* Reconfigured for distribution: renamed to Bodholdt Contact, added a multi-tenant settings page (brand name, accent color, copy, notification email, customer-recognition mode), made visitor-facing branding configurable, internationalized the interface (text domain `bodholdt-contact`), and packaged it for release. All anti-spam protections (one-time code, honeypot, rate limiting, brute-force lockout, timing-safe comparison) retained.
