=== RnkCookie Consent ===
Contributors: rnkcookie
Tags: cookie consent, gdpr, consent mode, cookie banner, privacy
Requires at least: 5.2
Tested up to: 6.8
Requires PHP: 7.2
Stable tag: 1.2.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Cookie consent for WordPress. Works out of the box with no configuration, survives caching and optimisation plugins, and can block other plugins' scripts before they are downloaded.

== Description ==

RnkCookie Consent puts a cookie banner on your site and stops third-party scripts
from running until your visitor agrees to them.

Activate it and it works. There is no key to paste and no required setting —
RnkCookie identifies your site by its hostname.

= What it adds to your pages =

Two tags, at the very top of `<head>`, in this order:

1. **An inline Google Consent Mode v2 "denied" default.** This tells Google's tags
   to hold off before anything else on the page runs. It is inline and
   first-party, so it still works if RnkCookie's CDN is unreachable — which is
   the one moment it matters most.
2. **The RnkCookie loader.** A small script that blocks third-party tags on
   sight, then loads the consent banner and your settings.

Both carry the standard "do not optimise" attributes so caching and minification
plugins leave them alone. That is not decoration — see **Caching and optimisation
plugins** below.

= Blocking other plugins' scripts, server-side =

Optional, off by default. When you turn it on, RnkCookie marks third-party
scripts added by your other plugins as consent-blocked **in the HTML itself**,
before the page is sent.

This is stronger than blocking in the browser. A script marked this way is never
downloaded at all, so the third party never sees your visitor's IP address, the
page they were on, or any cookies they already had — none of which browser-based
blocking can prevent, because by then the browser has usually already fetched the
file.

It reaches scripts added the standard WordPress way (`wp_enqueue_script`), which
is most plugins. It cannot reach scripts hard-coded into your theme or injected
later by a page builder; those are still handled in the browser, as before.

Anything it holds is released the moment your visitor consents.

== Caching and optimisation plugins ==

A consent banner is the one script on your site that must not be optimised.
We tested against the four most common offenders and found that, left alone,
they break consent in ways that leave **no visible symptom** — the banner still
appears, the page still looks fine, and the blocking is dead.

This plugin opts itself out of all of them automatically. You do not need to
configure anything. Specifically:

* **Autoptimize** — "defer" and "aggregate" both skip our tags, and the inline
  Consent Mode block stays in `<head>` instead of being moved to the bottom.
* **LiteSpeed Cache** — excluded from JS Combine, JS Defer and Delay JS.
* **W3 Total Cache** — our tags, and *any* consent-blocked script on your page,
  are excluded from Minify.
* **WP Fastest Cache** — our tags and anything we tag are excluded from Combine JS.
* **Cloudflare Rocket Loader** and **mod_pagespeed** — opted out by attribute.

= Two cases we cannot fix for you =

The plugin will show a warning in wp-admin if it detects either of these. It does
**not** disable itself — a banner that switches itself off protects nobody.

* **LiteSpeed Cache → Delay JS.** RnkCookie is excluded and works normally, but
  LiteSpeed restores delayed scripts one at a time, waiting for each to finish
  before starting the next. When RnkCookie blocks one of those scripts for
  consent, it never finishes, and everything queued behind it never runs — that
  can include your own sliders, forms and carts. Turn Delay JS off, or use
  "Defer" instead. Turning on server-side tagging (above) reduces the exposure,
  because tagged scripts are removed from LiteSpeed's queue entirely.

* **WP Fastest Cache → Combine JS.** WP Fastest Cache offers no way for another
  plugin to exclude a file from combining. Our tags and anything we tag are
  safe, but a consent-blocked script written directly into your theme or another
  plugin will be merged into a combined file, lose its blocking, and run before
  consent. Turn Combine JS off, or add that script to WP Fastest Cache →
  Exclude → JS.

== Known limits ==

Stated plainly, because the previous version of this plugin claimed otherwise.

* **We are early in `<head>`, but not always first.** The plugin runs at the
  earliest priority WordPress offers, which beats every other plugin's hooked
  output. It cannot beat a `<script>` typed directly into your theme's
  `header.php` above the `wp_head()` call — nothing hooked can, because such a
  script is not on the hook at all. Move those into the theme's footer, or below
  the RnkCookie tag.
* **A tracker that runs before us may run twice.** If a script executes before
  RnkCookie loads, RnkCookie marks it blocked and then re-runs it when consent is
  given. Keep hard-coded tags below the RnkCookie output and this does not arise.
* **Server-side tagging only sees enqueued scripts.** Scripts printed directly on
  a hook, or injected at runtime by a page builder, are handled in the browser.
* **Multisite is not supported.** The plugin will run, but it is untested and the
  settings are per-site.

== Installation ==

1. Download the plugin ZIP from **https://plugins.rnkcookie.com/rnkcookie-consent/**
   (also linked from your RnkCookie dashboard).
2. wp-admin → **Plugins → Add New → Upload Plugin**, choose the file, **Install Now**.
3. Activate **RnkCookie Consent** through the **Plugins** menu.
4. That is the whole installation. Reload your homepage and the banner appears.

Everything under **Settings → RnkCookie** is optional.

This plugin is not in the WordPress.org directory, so you cannot find it by
searching inside wp-admin. Everything *after* the first install works normally,
though — see **Updates** below.

= Content-Security-Policy (CSP) note =

If your site sends a CSP header, allow RnkCookie's CDN so the loader and the core
it injects can load:

`Content-Security-Policy: script-src 'self' 'unsafe-inline' https://cdn.rnkcookie.com; connect-src 'self' https://api.rnkcookie.com;`

The inline Consent Mode block requires `'unsafe-inline'` (or a nonce/hash) in
`script-src`. If you cannot allow inline scripts, turn the Consent Mode default
off under **Settings → RnkCookie** and emit your own with a nonce — but be aware
that removing it reintroduces the fail-open described above.

CSP does not affect updates: those are fetched by PHP on your server, not by the
browser, so no policy of yours needs to mention plugins.rnkcookie.com.

== Updates ==

Updates arrive through WordPress itself. New versions appear under
**Dashboard → Updates** and on the **Plugins** screen, "Update now" works, and
WordPress's per-plugin **Enable auto-updates** toggle is honoured. Nothing needs
to be re-downloaded by hand, and nothing needs a licence key.

Two things are worth knowing about how that is done, because a plugin that
updates itself is a plugin that can run new code on your server:

* **Every release is signed.** The update manifest carries an Ed25519 signature,
  and the copy of RnkCookie already installed on your site verifies it against a
  public key built into the plugin. The signing key is held offline and is never
  on the server that hosts the downloads — so even if that server were taken
  over, your site would refuse anything published from it.
* **The package is checked before it is unpacked.** The signed manifest contains
  the SHA-256 of the ZIP, and the download is hashed and compared before
  WordPress is allowed to touch your plugin directory. If it does not match, the
  update is refused with an explanatory message and nothing on your site changes.

If our update server is slow or unreachable, nothing happens: the check is
capped at three seconds, cached, and fails silently. Your admin does not wait
for us.

The `Update URI` header in the plugin also stops WordPress from ever offering
you a *different* plugin's update because it happens to share our folder name.

= Which WordPress and PHP versions? =

**WordPress 5.2+ and PHP 7.2+.** Those are the oldest versions the plugin is
actually installed on, activated on, and updated on before every release — not
the oldest we think it might work on. The current ceiling is WordPress 6.8.

One caveat at the bottom of that range: WordPress's per-plugin auto-update toggle
arrived in **5.5**. On 5.2 to 5.4 updates are offered and apply normally, but you
click the button yourself, as you do for every other plugin on those versions.

== Frequently Asked Questions ==

= Where do I get my Site Key? =

There isn't one. Earlier versions of this plugin asked for a "Site Key" and
printed nothing until you filled it in. That field was a mistake: RnkCookie
resolves your configuration from your site's hostname, and the key was never
used for anything. It has been removed, and the plugin now works immediately on
activation. If you upgraded from 1.0.0, any value you saved is ignored.

= Why is there no SRI / integrity setting any more? =

Because pinning an integrity hash to the loader was backwards, and dangerous.
The loader is the small, frequently-updated file; the large file it loads is the
one that gets integrity-checked, and the loader does that itself, automatically.
A hash pinned to the loader goes stale the next time we ship an update, and when
it does the browser refuses to run the script — meaning no banner, no Consent
Mode default and no blocking at all, silently. The setting is gone and any saved
value is ignored. Your install is still integrity-protected; that now happens
where it belongs.

= Why is there no API URL setting any more? =

It is no longer needed — the API address is built into RnkCookie. Setting it
pinned sites to an address that was going to change.

= Do I need to do anything after activating? =

No. Reload your site and check the banner appears.

= Does this plugin store any visitor data? =

No. It stores your settings and, if you use the script scan, a list of the script
handles your other plugins register. All consent handling happens in the
CDN-hosted RnkCookie core. Deleting the plugin removes everything it stored.

= Can I stop the banner on certain pages? =

Yes, with the `rnkcookie_suppress_output` filter. Be careful: suppressing output
removes the Consent Mode default as well as the banner, so those pages have no
consent protection at all.

== Changelog ==

= 1.2.0 =

Adds updates. Until this release the only way to get a fix onto a site was to
download a ZIP and upload it by hand, which meant most sites would simply never
get one.

* **Added: updates through WordPress's own interface.** New versions show up
  under Dashboard → Updates, "Update now" works, and the per-plugin auto-update
  toggle on the Plugins screen is honoured. RnkCookie is not in the
  WordPress.org directory, so the update information is served from
  plugins.rnkcookie.com rather than from wordpress.org.
* **Added: signed releases.** The update manifest is Ed25519-signed and verified
  against a key built into the plugin, and the ZIP is checked against the
  SHA-256 in that signed manifest before WordPress unpacks it. An update that
  fails either check is refused and nothing changes.
* **Added: an `Update URI` header.** As well as being how updates are found on
  WordPress 5.8 and newer, this stops WordPress from overwriting this plugin
  with an unrelated one that happens to share the folder name
  `rnkcookie-consent`. On WordPress 5.7 and older, where that header did not
  exist yet, the plugin removes itself from the wordpress.org update check to
  the same effect.
* **Added: a published download page** at
  https://plugins.rnkcookie.com/rnkcookie-consent/, with the SHA-256 of the ZIP
  so you can check what you downloaded. Previously the documentation told people
  to get the ZIP from their dashboard, and there was no such download.
* Compatibility with WordPress 5.2 through 6.8, on PHP 7.2 through 8.3, is now
  measured on every combination at the edges rather than asserted. The declared
  minimums are enforced by the release build against those measurements.

= 1.1.0 =

Substantial correctness release. Every change below was verified against a real
WordPress install running the affected plugins.

* **Fixed: the plugin emitted nothing at all unless a "Site Key" was set.** A site
  that installed and activated it, and did not fill in a field that should never
  have existed, got no banner, no Consent Mode default and no blocking, with no
  error anywhere. The field has been removed and the plugin now works with no
  configuration.
* **Removed: the SRI hash setting.** Pinning an integrity hash to the loader
  breaks every install using it the next time the loader is updated, and it fails
  open — no banner and no blocking. Any saved value is now ignored.
* **Removed: the API Base URL setting**, and the `data-api-url` attribute. No
  longer required, and it pinned sites to an address due to change.
* **Removed: the `data-site-id` attribute.** It was never read by anything.
* **Added: the inline Consent Mode v2 denied default**, emitted before the loader.
  This is a required part of the install and its absence meant every site using
  this plugin was fail-open whenever our CDN was unreachable.
* **Added: a release channel setting** (stable / canary). Defaults to sending no
  channel at all, so existing installs do not move.
* **Added: opt-out attributes and filters for Autoptimize, LiteSpeed Cache, W3
  Total Cache, WP Fastest Cache, Cloudflare Rocket Loader and mod_pagespeed.**
  Without these, "defer all JS" silently disabled consent blocking entirely, and
  LiteSpeed's JS Combine removed RnkCookie from the page altogether.
* **Added: server-side script tagging** (optional, off by default), which blocks
  third-party scripts before the browser downloads them.
* **Added: a warning when a performance plugin is configured in a way that breaks
  consent and that we cannot work around.**
* **Fixed: output now runs at the earliest available `wp_head` priority.** It
  previously ran at priority 1, and the documentation claimed that made it first
  in `<head>`. It did not — any plugin hooking priority 0 landed above it, and
  its trackers ran before RnkCookie could block them.
* Documentation rewritten to state the plugin's real limits.

= 1.0.0 =
* Initial release. Never published.

== Upgrade Notice ==

= 1.2.0 =
This is the last update you will have to install by hand. From 1.2.0 onwards
RnkCookie Consent updates itself through WordPress, with signed and
hash-verified releases.

= 1.1.0 =
Fixes a defect that left some installs with no banner and no consent blocking at
all, and removes a setting that would have broken every install using it. If you
previously set a Site Key, SRI hash or API URL, those values are now ignored and
you do not need to do anything.
