=== Wiwi Cookie Consent ===
Contributors: naiches
Tags: cookie consent, gdpr, avg, privacy, cookie banner
Requires at least: 6.0
Tested up to: 7.1
Requires PHP: 8.0
Stable tag: 1.0.2
License: GPL-2.0-or-later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

A simple, lightweight cookie consent banner for GDPR/AVG compliance — no bloat, no phone-home, no consent database.

== Description ==

Wiwi Cookie Consent is a small, dev-friendly consent banner. Add your tracking scripts (Google Analytics 4, Meta Pixel, Google Ads, or anything else) through the settings page, and the plugin blocks them from running until a visitor actually consents to that category — nothing more, nothing less.

Built for typical brochure/marketing sites without visitor accounts or logins — not for membership sites, and not a full cookie-compliance suite. See "What it deliberately does not do" below for the exact boundary.

= What it does =

* Opt-in by default — every category starts OFF; nothing tracks until a visitor chooses to allow it.
* Real script blocking — tracking code is wrapped in an inert `<template>` element and only activated after consent, not just hidden with CSS.
* Per-category consent — Marketing, Analytics, and Functional, each independently toggleable in the preferences modal, plus an always-on Necessary section that lists the essential cookies.
* Quick-add templates for Google Analytics 4, Meta Pixel, and Google Ads — enter your ID, done.
* Fully accessible — keyboard navigation, focus trap, ARIA attributes on the preferences modal.
* Style it yourself — an optional toggle disables the plugin's own CSS entirely while keeping every ID and class in place, so you can theme the banner from your own stylesheet.
* Dutch and English out of the box.

= What it deliberately does not do =

* **No consent logging.** Nothing about your visitors is stored server-side — no IP, no timestamp, no consent database. Built for anonymous-visitor sites without accounts/logins; there's no identified user to tie a server-side consent record to in the first place. See the notice on the plugin's own settings page for the full reasoning.
* **No validation of how you classify a cookie.** You decide which category (Marketing/Analytics/Functional) each cookie belongs to, and the plugin blocks/unblocks it accordingly — correctly and reliably. It cannot know whether *you* classified it correctly (e.g. labelling an analytics cookie as "Functional" to skip consent is a real, commonly-fined mistake). Getting the categories right is your legal responsibility.
* **No site-wide cookie scanning or auditing.** The plugin doesn't crawl your site to discover what cookies exist — you declare them yourself in the cookie table. Automatic cookie discovery is a fundamentally different (and much heavier) category of tool; see the FAQ.
* **No scanning or filtering of other plugins, themes, or embedded content.** This plugin only gates the tracking code you configure through its own settings — it will not detect or block a hardcoded script elsewhere on your site, or a YouTube/Maps/social-widget embed. Continuously scanning and filtering arbitrary page content would need to watch the DOM on every page (e.g. a `MutationObserver`), which runs against this plugin's zero-front-end-impact design goal. That stays your responsibility — self-host fonts, use privacy-enhanced embed modes, or click-to-load placeholders.
* **No Terms, Privacy Policy, or Cookie Policy content.** The Banner & Modal Text settings let you *link* to existing WordPress pages for these — the plugin does not write, generate, or validate their content. You (or your agency) must write and maintain Algemene voorwaarden, Privacybeleid/Privacyverklaring, and Cookiebeleid yourselves.
* **No bundled updater, no telemetry, no phone-home.** Updates come from the WordPress.org repository like any other plugin.

== Installation ==

1. Upload the `wiwi-cookie-consent` folder to `/wp-content/plugins/`, or install the zip via Plugins → Add New → Upload Plugin.
2. Activate the plugin through the Plugins menu in WordPress.
3. Open the new **Cookies** menu in the admin sidebar.
4. Add your tracking code per category (or use the quick-add templates), customise the banner text, and save.

Requires PHP 8.0 or higher and WordPress 6.0 or higher.

== Frequently Asked Questions ==

= Does this plugin store or log what visitors consented to? =

No. There is no server-side consent database. See "What it deliberately does not do" above and the notice shown at the top of the plugin's own settings page.

= Does it block cookies from other plugins or themes? =

No. It only gates tracking code entered through its own settings screen. If you have a tracking script hardcoded in your theme or added by another plugin, route it through Wiwi Cookie Consent's settings if you want it consent-gated, or handle it separately.

= Does it check whether I've categorised my cookies correctly? =

No. The plugin blocks and unblocks exactly what you tell it to, exactly as configured — that mechanism is reliable. Whether a given cookie genuinely belongs in Marketing, Analytics, or Functional (and whether "Functional" is even the correct exemption for it) is a legal judgement call only you can make. Mislabelling a tracking cookie as "Functional" to avoid asking for consent is one of the most common cookie-banner violations regulators actually fine — the plugin can't catch that for you.

= Can this plugin scan my site and find all my cookies for me? =

No, and that's intentional. Automatic cookie/tracker discovery is a genuinely different, much heavier category of tool (the kind built by dedicated consent-management platforms). This plugin stays a lightweight blocker for the tracking code you explicitly configure — it does not crawl pages, inspect third-party scripts, or maintain its own cookie database.

= Do I still need to write my own Privacy Policy / Terms / Cookie Policy? =

Yes, always. The plugin lets you link the banner to existing WordPress pages for Terms & Conditions, Privacy Policy, and Cookie Policy — it does not write or supply any legal content. That text is your (or your agency's) responsibility.

= Does it collect any data or phone home? =

No. There is no usage tracking, analytics, telemetry, or licensing call-home.

= Can I fully restyle the banner? =

Yes. Enable "Don't load the plugin's default stylesheet" under Styling in the settings page, and style `#wcc-cookie-banner`, `#cookie-preferences-modal`, `.wcc-btn`, `.wcc-toggle`, and the other existing classes/IDs from your own theme CSS. The banner's show/hide and open/close behaviour works independently of the stylesheet.

== External services ==

This plugin does not connect to any external service on its own. It stores no data off-site, has no telemetry, and phones home to nothing. It is a consent gate, not a tracker.

Its purpose, however, is to let a site administrator add their *own* third-party tracking code and have it load only after a visitor consents. A third-party service is contacted **only when both** of these are true: (1) the administrator has configured that service (via a built-in quick-add template or by pasting equivalent code), **and** (2) a visitor has granted consent to the relevant cookie category. With no configuration, the plugin makes no external requests at all.

The services offered by the built-in quick-add templates are:

**Google Analytics 4 / Google Ads (Google Tag — gtag.js)**
Loaded from `https://www.googletagmanager.com/gtag/js` only when the administrator enters a Google Analytics Measurement ID (Analytics category) or a Google Ads Conversion ID (Marketing category) and the visitor consents to that category. Once loaded, Google's script may set cookies and send the visitor's IP address, page URL, and interaction events to Google to measure site usage and advertising conversions.
Terms of Service: https://policies.google.com/terms — Privacy Policy: https://policies.google.com/privacy

**Meta Pixel (Facebook)**
Loaded from `https://connect.facebook.net/en_US/fbevents.js` only when the administrator enters a Meta Pixel ID and the visitor consents to the Marketing category. Once loaded, Meta's script may set cookies and send the visitor's IP address, page URL, and events (e.g. PageView) to Meta to measure advertising effectiveness.
Terms of Service: https://www.facebook.com/legal/terms — Data Policy: https://www.facebook.com/privacy/policy/

The administrator may also paste any other tracking code of their choosing; in that case the external service contacted is whichever one that code refers to, and disclosing it is the site administrator's responsibility.

== Screenshots ==

1. The cookie banner as shown to visitors.
2. The preferences modal with per-category toggles.
3. The Cookie Management admin page.

== Upgrade Notice ==

= 1.0.1 =
Stops text entered in a category's code field from being published to visitors, and gives category explanations a field of their own.

== Changelog ==

= 1.0.2 =
* Security: editing a category's tracking code so that a commented-out tracker became live again did not count as a change, so returning visitors were never asked again and the tracker started under consent they gave before it existed. The check that decides "has what I am consenting to changed?" treated a line break as ordinary spacing — and a line break is exactly what ends a JavaScript comment. Line breaks are now significant; reindenting or reformatting still does not re-prompt anyone.
* Fixed: when visitors were asked again because the configuration had changed, the Functional switch could come up already ticked, filled in from a cookie left by an older version of this plugin. Pressing Save without touching anything then granted exactly the tracking they were being asked about. Being asked again now always starts from everything off, as a first visit does, while visitors whose consent still stands keep their existing choices.
* Security: a link in the banner description with a crafted `rel` value could inject markup into the page for every visitor (the reverse-tabnabbing rewrite used the admin's own `rel` tokens as a regex replacement pattern). `rel` tokens are now allow-listed and the rewrite is literal. On a stock single site the admin already has `unfiltered_html`, so the practical impact is on multisite and `DISALLOW_UNFILTERED_HTML` sites, where it bypassed the restriction core deliberately places on site administrators.
* Fixed: backslashes in a category's code were stripped on every save (a regex `/\d+/` became `/d+/`, then `d+`), because the code was unslashed twice. A pasted snippet now survives any number of saves unchanged.
* Added: visitors are asked again when what they are being asked to consent to changes — a category gains tracking code, a tracker is added to a category, or the declared cookies change. The banner returns and nothing activates until they confirm; existing consent is otherwise untouched. Nothing per-visitor is stored: a single fingerprint of the site's configuration travels with the consent cookies. Rewording a description does not re-ask. Consent recorded before this release carries no fingerprint at all, so there is no way to tell which configuration it was given under — including whether a tracker has been added since. Those visitors are therefore asked once more after this update, and nothing activates for them until they answer; every choice from then on is anchored and only a real change asks again.
* Added: an always-on "Necessary" section leads the Manage cookies window, so the essential cookies the banner text mentions are actually listed and explained rather than only asserted. It has its own "What visitors are told" field and declared cookies on the settings screen, no switch and no code field.
* Changed: the default Dutch copy is informal (je/jouw) throughout, and the service descriptions and cookie durations offered by "Add a known service" are English in the source and translated, instead of Dutch text on every site regardless of language. Sites activated before this change keep whatever text they already have.
* Changed: the default banner text now points to "the cookie icon in the corner of the page" — the reopen control is an icon, so the old "'Cookie settings' link" named something sighted visitors could not find.
* Fixed: per-service descriptions in the Manage cookies window fell just short of WCAG AA contrast on the category card background.
* Fixed: the policy-page pickers offered every draft and trashed page as a plain title; only published pages are offered now, and a selected page that is no longer published stays selectable but is labelled as not visible to visitors.
* Changed: script/style handles and JavaScript globals are prefixed. Every front-end element ID and class is unchanged, so custom CSS targeting the banner, the preferences window, the buttons or the switches keeps working exactly as before.
* Changed: the front-end script and stylesheet are no longer loaded on a site with nothing configured to consent to, and the skip link's critical CSS is attached through the enqueue API instead of a raw style tag.
* Added: a `wicc_tracking_templates` filter so a theme or site plugin can add its own service to the "Add a known service" picker.
* Fixed: on the settings screen, a declared cookie name or field label containing `$&`, `$'` or a backreference was mangled in the "Add a known service" description instead of shown as typed.
* Fixed: the Dutch translation that ships with the plugin was never loaded. WordPress only picks up translations from its own languages folder automatically, so until translate.wordpress.org carries this plugin, Dutch sites saw English. The bundled translation is now registered and loads; a translation from translate.wordpress.org still takes precedence when one exists.
* Fixed: the three Save buttons on the settings screen shared one element ID, which confused assistive technology and made the page fail an accessibility check. Each now has its own.
* Fixed: Google Consent Mode could be told a visitor had consented after they pressed Reject All. The signal was sent based on the individual category cookie alone, without checking whether consent had been given at all — unlike the tracking code itself, which did check and correctly stayed blocked. A category cookie outlives the category it belongs to (Reject All only rewrites the categories currently on screen), so a visitor who accepted once, and later rejected after that category was removed, kept sending "granted" on every page load.

= 1.0.1 =
* Fixed: text pasted into a category's tracking code field was inserted into the page as visible content the moment a visitor accepted that category. Anything without a script or stylesheet tag is now ignored at insertion time, so it can never reach a visitor.
* Added: each category now has a "What visitors are told" field, shown under that category in the Manage cookies window. Category explanations previously had nowhere to go, which is what led people to put them in the code field.
* Changed: the settings screen is rebuilt around one card per category, holding that category's explanation, its code, and the cookies it declares. Banner and modal copy, styling and consent settings move to their own tabs.
* Added: saving a code field that contains no script or stylesheet tag now warns, and the category is flagged on screen until it is resolved.
* Added: "Add a known service" now asks for whatever that service actually needs — Adobe Fonts for a kit ID, Google Analytics for a measurement ID — with a note on where to find it and which cookies it will declare. Adobe Fonts and Google Fonts are now included.
* Fixed: a stylesheet or meta tag activated on consent no longer announces "additional content has been added to the page" to screen readers, since nothing visible was added.

= 1.0.0 =
* Initial release.
