=== Working Detail Cookies ===
Contributors: workingdetail
Tags: cookies, gdpr, consent, cookie banner, accessibility
Requires at least: 6.5
Tested up to: 7.1
Requires PHP: 7.4
Stable tag: 1.2.0
License: GPL-2.0-or-later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Lightweight GDPR cookie consent: an accessible banner, scripts held back until opt-in, Google Consent Mode v2, and a cookie list.

== Description ==

A self-hosted replacement for SaaS consent tools on straightforward WordPress sites.

* **Banner** with Accept all, Reject non-essential (same prominence) and Manage preferences.
* **Preferences dialog** with a switch per category: Strictly necessary (always on), Functional, Analytics, Marketing. Turn off the categories a site doesn't use; with none on, no banner is shown.
* **Prior consent.** Paste tracking snippets into Settings → Cookie Consent → Categories & scripts and they are printed as `type="text/plain"`, then run in order once the visitor accepts, without a page reload. Withdrawing consent deletes that category's listed cookies and reloads the page.
* **Google Consent Mode v2.** All signals denied before any tag loads; returning visitors' choices applied in the same inline script.
* **WP Consent API** support, so plugins that use it (Site Kit, WooCommerce and others) respect the choice.
* **Cookie registry** (name, category, provider, purpose, expiry) with a **scanner** that fetches up to eight pages of the site and suggests entries for GA4, Tag Manager, Google Ads, Meta Pixel, Hotjar, Clarity, LinkedIn, TikTok, Pinterest, Matomo, Cloudflare Web Analytics, YouTube, Vimeo, HubSpot, OpenFreeMap and OpenStreetMap tiles, reCAPTCHA, Turnstile and Stripe. It reports whether each one waits for consent, lists registry rows whose service is no longer on the site, and remembers when it last ran. It scans when you press the button and at no other time: there is no scheduled or cloud scanning.
* **`[cookie_list]` shortcode and Cookie List block** for the privacy or cookie policy page, plus `[cookie_settings]` for a "Change cookie settings" button.
* The choice lives only in the visitor's browser (`wp_consent_prefs`, 365 days). Nothing is written to the database per visitor, and the page HTML is the same for everyone, so page caches and CDNs are safe.
* About 10KB of JavaScript (3.5KB gzipped), no jQuery, no external requests.

= Accessibility (WCAG 2.2 AA) =

* Native `<dialog>` opened with `showModal()`: the page behind is inert, Tab cycles inside, Escape closes, focus returns to the control that opened it.
* The banner is a labelled region moved to the start of `<body>`, so it is first in the reading and tab order without stealing focus.
* A spacer and `scroll-padding-bottom` stop the fixed bar covering the end of the page or a focused control (2.4.11 Focus Not Obscured).
* Colours are contrast-checked when saved (text 4.5:1, button shape 3:1) and rejected if they fail.
* 3px focus ring in the text colour; buttons and switches at least 44px; switches are real checkboxes with `role="switch"`; saving is announced in a status region; forced-colours and reduced-motion handled.

= Reopening the dialog =

Any of these, anywhere on the site:

* a link to `#cookie-settings` (also works from another page: `/privacy-policy/#cookie-settings`)
* any element with the class `open-cookie-settings`
* the `[cookie_settings]` shortcode, or the button option on the Cookie List block
* the optional corner button (Settings → Cookie Consent → Banner)
* `window.WDCookies.open()`

= Marking your own scripts =

    <script type="text/plain" data-category="analytics" src="https://example.com/tracker.js"></script>
    <iframe data-category="marketing" data-src="https://www.youtube.com/embed/…"></iframe>

Enqueued scripts can be held back by handle:

    add_filter( 'working_detail_cookies_blocked_script_handles', function ( $handles ) {
        $handles['my-analytics'] = 'analytics';
        return $handles;
    } );

= JavaScript =

* `WDCookies.get()` — `{ functional, analytics, marketing }` or `null` before a choice.
* `WDCookies.granted( 'analytics' )`, `WDCookies.acceptAll()`, `WDCookies.rejectAll()`.
* `document.addEventListener( 'wdc:consent', e => e.detail.categories )` fires after each choice and on load for returning visitors.

= Filters =

`working_detail_cookies_cookie_name`, `working_detail_cookies_categories`, `working_detail_cookies_is_active`, `working_detail_cookies_consent_mode_defaults`, `working_detail_cookies_consent_mode_map`, `working_detail_cookies_blocked_script_handles`, `working_detail_cookies_js_config`, `working_detail_cookies_scanner_signatures`, `working_detail_cookies_scan_urls`.

= Theming =

Colours come from the settings. A theme can also set `--wdc-font`, `--wdc-heading-font`, `--wdc-heading-weight` and `--wdc-radius` on `:root`.

== Frequently Asked Questions ==

= Is consent logged? =

No. The plugin stores nothing about visitors on the server. If you need a server-side consent log for audits, this plugin does not provide one.

= My theme has no wp_footer() call =

The banner is printed in `wp_footer`, so templates that skip it (some standalone landing pages) show no banner. Scripts marked `text/plain` on those pages simply never run.

= Why is the banner not showing? =

It shows only when it is switched on and at least one optional category is on, and only until the visitor chooses. Clear the `wp_consent_prefs` cookie to see it again, or use "Ask all visitors again".

== Changelog ==

= 1.2.0 =
* **Breaking for custom code:** every filter and constant now uses the full `working_detail_cookies_` / `WORKING_DETAIL_COOKIES_` prefix, because WordPress coding standards reject three-letter prefixes. Rename `wdc_cookie_name`, `wdc_categories`, `wdc_is_active`, `wdc_consent_mode_defaults`, `wdc_consent_mode_map`, `wdc_blocked_script_handles`, `wdc_js_config`, `wdc_scanner_signatures` and `wdc_scan_urls` to the same name with `working_detail_cookies_` in front. Saved settings, the consent cookie, CSS classes and the JavaScript API are unchanged.
* Plugin Check fixes: escaping on the policy page dropdown and the Cookie List block, the settings helper renamed so it no longer shadows a deprecated core function, and a bounded time limit for the scanner.

= 1.1.0 =
* Scan: up to eight pages in one run, listed one per line, suggested from the front page, the privacy policy and the newest entry of each public post type.
* Scan: new signatures for Cloudflare Web Analytics, OpenFreeMap and OpenStreetMap tiles, each cookieless with a note about what they do reach.
* Scan: a "Listed, but not found" section for registry rows whose service was not seen on any scanned page.
* Cookies: shows when the list was last checked against the site.
* New `working_detail_cookies_scan_urls` filter for the suggested page set.

= 1.0.0 =
* First release.
