# ClearConsent (WordPress plugin)

**ClearConsent** is a lightweight, GDPR-friendly cookie consent manager for WordPress. It loads a consent banner and preferences modal on the front end and exposes a **Settings → ClearConsent** screen to configure behavior, copy, and services without editing JavaScript by hand.

## Requirements

- WordPress **5.8+**
- PHP **7.4+**

## Installation

1. Copy this plugin folder (`clearconsent`) into `wp-content/plugins/`.
2. Activate **ClearConsent** in the WordPress admin.
3. Go to **Settings → ClearConsent**, enable **Load ClearConsent on the front end**, adjust options, and save.

A **Settings** link is also available on the Plugins list.

## What this plugin does

- Enqueues ClearConsent after an inline `window.clearConsentConfig = { ... }` so configuration is always defined before the script runs.
- Loads the script **synchronously** in the header (with config as a dependency) so initialization is reliable even when themes omit `wp_footer()`.
- Adds `data-clearconsent-config="clearConsentConfig"` on the script tag for an explicit global name.
- Ships original assets under `assets/js/clearconsent.js` and `assets/css/clearconsent.css`.

## Settings overview

| Area | Purpose |
|------|---------|
| **General** | Enable/disable, hide CMP for administrators (useful while testing). |
| **Behavior** | Must consent, notice as modal, group by purpose, HTML in texts, optional services on by default (opt-out), etc. |
| **Storage** | Cookie vs `localStorage`, name, expiry. |
| **Consent notice text** | Title, description, Learn more, Reject, Accept — merged into translations for active locales. |
| **Content** | Privacy policy URL, UI theme tokens, services JSON, extra translations JSON, optional credit line. |

Empty notice fields fall back to sensible plugin defaults. **Extra translations JSON** wins over the same keys as the form fields.

### UI theme (position)

Comma-separated theme tokens, e.g. `light,bottom,right` for a bottom-right notice. Use `dark` for a dark theme. `top` and `bottom` conflict; `wide` conflicts with `right` / `left`.

## Services JSON

Third-party tools must be listed in the **Services (JSON array)** field. Each service needs a `name` that matches `data-cc-name` on gated markup.

Use the **(?)** help panels under the JSON textareas for examples and field reference.

## Automatic blocking (Layer 1 & Layer 2)

ClearConsent ships a bundled signature map at `assets/data/signatures.json` (Google Analytics, Meta Pixel, YouTube, Hotjar, HubSpot, Matomo, and more).

| Layer | Scope | How it works |
|-------|-------|--------------|
| **Layer 1** | Free + Pro | `script_loader_tag` rewrites enqueued scripts whose `src` matches a signature **and** whose slug is already in your Services list. |
| **Layer 2** | Pro only | Output-buffer rewriter tags hardcoded `<script>` / `<iframe>` markup in the final HTML (theme files, DB-injected snippets, etc.). |

Layer 2 also auto-adds newly detected services to Settings → Services and shows an admin notice. Use **Scan results** (Pro) to review matched patterns/URLs.

### Browser scan (Pro)

**Run browser scan** loads your homepage in a hidden iframe inside wp-admin and reports what a real browser actually executes — hardcoded scripts, GTM-loaded tags, plugin-injected code, and DB-option scripts. No external service; works on localhost and staging.

| Limitation | Detail |
|------------|--------|
| **Homepage only (v1)** | Multi-page scan is planned for a future release. |
| **HttpOnly cookies** | Not visible to JavaScript; won't appear in cookie findings. |
| **Login walls** | If the homepage requires login, the scan sees the login page. |
| **Cross-origin timing** | ResourceTiming may redact timing data without `Timing-Allow-Origin`; URL matching still works in most cases. |
| **Admin session** | The scan runs in your logged-in browser. If the site serves different content to admins, results may differ from real visitors. Use **Hide banner for administrators** and paste the scan URL in a private window for advanced testing. |

**Run server scan** fetches homepage HTML server-side and scans known DB injection options (header/footer plugins, snippets).

### Caching plugins

- **Consent JS + inline config** should not be cached per-user. The plugin sends `Cache-Control: no-store` when active.
- **Inert `type="text/plain"` tags in HTML** can be cached — re-activation reads the consent cookie client-side.

Exclude the consent cookie from page-cache “logged-in cookie” rules:

| Plugin | Cookie to exclude |
|--------|-------------------|
| WP Rocket | `clearconsent` (or your custom cookie name) |
| W3 Total Cache | `clearconsent` |
| LiteSpeed Cache | `clearconsent` |
| WP Super Cache | `clearconsent` |

## Gating scripts manually (still supported)

ClearConsent only controls tags you mark up correctly. A normal analytics snippet runs immediately; it is **not** blocked by consent.

Use inert placeholders:

```html
<script
  type="text/plain"
  data-cc-name="google-analytics"
  src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX">
</script>
<script type="text/plain" data-cc-name="google-analytics">
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-XXXXXXXXXX');
</script>
```

- Remove duplicate loaders (theme, other plugins, Tag Manager) or gate the **container** snippet the same way.
- `data-cc-name` must match the service `name` in your Services JSON.

### Opt-in vs opt-out

- **Default (opt-in):** scripts run only after the user accepts (for that service).
- **Opt-out:** enable **Optional services on by default** or set `"default": true` on individual services so they start enabled until the visitor turns them off — only if that matches your legal setup.

### Withdrawing consent

Scripts that have already executed cannot be “un-run.” After withdrawal, new page loads will leave placeholders inert. A full reload may be needed to clear in-memory effects.

## Public JavaScript API

After the script loads, `window.ClearConsent` exposes:

| Method | Description |
|--------|-------------|
| `show()` | Open the preferences modal |
| `showNotice()` | Show the consent notice banner |
| `getConsents()` | Return a copy of the current consent map |
| `setConsent(name, bool)` | Set consent for one service (not persisted until `saveAndApply`) |
| `saveAndApply()` | Persist choices, activate scripts, hide UI |
| `acceptAll()` | Enable all services and save |
| `declineAll()` | Disable optional services and save |
| `resetConsents()` | Clear storage and reset to defaults |
| `init()` | Initialize UI (used when auto-load is off) |

Listen for changes:

```javascript
document.addEventListener('clearconsent:change', function (e) {
  console.log(e.detail.consents);
});
```

## Developer hooks

| Hook | Description |
|------|-------------|
| `clearconsent_should_load` | Pass `false` as the first filtered value to skip loading on a request. Second argument: merged settings array. |
| `clearconsent_default_services_if_empty` | Replace the fallback services list when the Services JSON decodes to an empty array. |
| `clearconsent_freemius_id` | Freemius product ID (see `includes/freemius.php`). |
| `clearconsent_freemius_public_key` | Freemius public key. |

## Licensing & freemium (Freemius)

Provider: **Freemius** (SDK ≥ 2.5.0 under `freemius/`). All SDK calls stay in `includes/freemius.php`; entitlement policy lives in `ClearConsent_License`.

### Two gates

| Gate | Method | Controls |
|------|--------|----------|
| **Protection** (lenient) | `protection_enabled()` | Banner + `data-cc-name` blocking — stays on for **free**, **grace**, and **expired** |
| **Premium** (strict) | `premium_enabled()` | `cc_fs()->can_use_premium_code() && cc_fs()->is_paying()` |

### States

| State | Meaning |
|-------|---------|
| `active` | Paid and current |
| `grace` | Freemius sync issue but within its grace window — premium stays on |
| `expired` | Paid license lapsed — protection on, config read-only |
| `free` | Registered free plan — full protection for up to 3 services |
| `unlicensed` | Not registered / not set up |

Freemius owns validation caching and background sync. ClearConsent never calls the license API on front-end page loads.

### Free vs paid

| Capability | Free | Paid |
|------------|------|------|
| Consent banner + blocking | ✅ (up to 3 services) | ✅ unlimited |
| Manual services | ✅ max 3 | ✅ unlimited |
| Cookie DB auto-fill on manual add | ✅ | ✅ |
| Automatic scanning | ❌ | ✅ |
| Browser scan (iframe) | ❌ | ✅ |
| Cloud Playwright scan | ❌ | ✅ |
| Plugin updates | ❌ (org free build) | ✅ via Freemius |

The 3-service cap is enforced in `sanitize()` and defensively in `Config::build()`.

### Staging / seats

Freemius binds licenses to the WordPress install (`home_url()`). Root + www = one activation. Separate subdomain installs = separate activations. Staging/localhost handling is delegated to Freemius (SDK ≥ 2.5.0 clone resolution).

### Setup

1. Add the Freemius SDK to `freemius/`.
2. Set product ID and public key in `includes/freemius.php` (or via filters).
3. Distribute free build on WordPress.org; paid upgrades via Freemius in wp-admin.

## HTML in consent texts

When **Allow HTML in consent texts** is enabled, notice copy from the settings page may be rendered as HTML. Only trusted administrators should edit those fields — this is an intentional XSS tradeoff for rich formatting.

## Uninstall

Deleting the plugin from WordPress runs `uninstall.php`, which removes the `clearconsent_settings` option.

## Troubleshooting

- **Banner or scripts missing:** Ensure the theme calls `wp_head()`. Clear caches. Confirm `assets/js/clearconsent.js` exists.
- **Custom copy not showing:** Overrides are applied to `en` as well as `zz` so they beat defaults.
- **Still seeing analytics before consent:** An untagged snippet or a second plugin is still loading the tag — find and remove or gate it.

## License

GPL-2.0-or-later (see `clearconsent.php` plugin header). Adjust the license in the plugin header if you distribute under different terms.

## Credits / history

This project was originally inspired by the general idea of a lightweight consent banner. **ClearConsent** is independent original code with no shared implementation from third-party consent libraries.
