Files
att-consent/CLAUDE.md
Steve Hanlon eb35fdabb6 Add TCF v2.2 bridge mode for external CMP integration (v1.3.0)
New cmp_mode setting toggles between the built-in banner (default,
unchanged) and a bridge mode that defers banner UX to an external
IAB TCF v2.2 certified CMP — e.g. Google's free Privacy & messaging
(Funding Choices) or Tarteaucitron.js — and listens to __tcfapi to
keep custom-script gating, attribution preservation, the att_cc_consent
cookie cache and the WP Consent API bridge in sync. Lets sites that
need a Google-certified CMP for EEA/UK AdSense/AdMob serving keep
using this plugin for everything except the consent UI itself.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-30 14:10:58 +01:00

5.1 KiB

ATT Consent - Developer Context

What This Is

A WordPress plugin implementing Google Consent Mode v2 with session attribution preservation. It replaces paid CMv2 plugins (e.g., CookieYes) with a self-contained, distributable alternative.

Architecture

Core Flow

  1. Inline <head> script (priority 1) - Sets consent defaults and captures attribution to sessionStorage. Must run before any Google tags.
  2. Tracking script (priority 2) - Loads gtag.js or GTM. In Advanced mode, loads immediately. In Basic mode, deferred until consent.
  3. Footer - Banner HTML + enqueued banner.js and banner.css.

CMP Mode

The cmp_mode setting toggles between two operating modes:

  • self (default) - this plugin renders the banner, owns the consent decision, and writes the att_cc_consent cookie. Original behaviour.
  • tcf_bridge - an external IAB TCF v2.2 certified CMP (e.g. Google's free Privacy & messaging / Funding Choices banner, Tarteaucitron.js) renders the banner and emits the official TC string + gtag('consent', ...) calls. The plugin suppresses its own banner/modal/consent default, and instead loads public/js/tcf-bridge.js, which listens to __tcfapi('addEventListener', 2, …), maps TCF purposes back to the three categories, and feeds AttConsent.update() so custom-script gating, attribution preservation and the WP Consent API bridge continue to work. Used when the site needs to satisfy Google's CMP requirement for EEA/UK/CH AdSense/AdMob traffic.

Key Files

  • includes/class-frontend.php - Orchestrates the output order. The output_consent_defaults() method generates the critical inline script.
  • public/js/consent-manager.js - Core logic: AttConsent.update() replays attribution, sends consent signals, injects scripts, sets cookie. This is the public API.
  • public/js/banner.js - UI only. Calls AttConsent.update() on button clicks.
  • includes/class-admin.php - Admin settings with 5 tabs. Handles save via admin_post, scripts CRUD via AJAX.
  • includes/class-scripts-manager.php - CRUD for custom JS snippets stored in {prefix}att_cc_scripts table.

Settings

Single serialized option: att_consent_settings. Access via ATT_Consent::get_settings() which merges with defaults.

Custom Scripts Table

{prefix}att_cc_scripts (id, name, snippet, category, placement, status, priority, created_at, updated_at)

Scripts are rendered in PHP as inert <script type="text/plain" data-att-cc-category="{category}"> tags (head scripts at priority 99, footer scripts at priority 99). Non-script HTML (pixels, iframes) is wrapped in <template data-att-cc-category="..." data-att-cc-type="html">. On consent, consent-manager.js scans the DOM for matching elements and activates them by creating fresh executable copies.

Third-party plugins (e.g. HFCM) can output consent-gated scripts without any coupling to this plugin — they just need to use type="text/plain" and the data-att-cc-category attribute with a valid category (functional, analytics, marketing).

Name: att_cc_consent. Value: JSON {"functional":bool,"analytics":bool,"marketing":bool}. First-party, SameSite=Lax.

Attribution Storage

sessionStorage key att_cc_attr stores referrer, UTMs, GCLID/DCLID on first page of session. Key att_cc_consent_given tracks whether attribution has been replayed. Attribution is replayed via gtag('set') BEFORE gtag('consent','update') so GA4 session_start gets correct source.

Category-to-Signal Mapping

Category Google Signals
Necessary security_storage (always granted)
Functional functionality_storage, personalization_storage
Analytics analytics_storage
Marketing ad_storage, ad_user_data, ad_personalization

WordPress Hooks

Filters: att_consent_settings_save, att_consent_banner_html Actions: att_consent_before_banner, att_consent_after_banner JS Event: att_consent_update (CustomEvent with detail: {functional, analytics, marketing})

Coding Standards

  • WordPress PHPCS (WordPress-Extra)
  • All admin inputs sanitized: sanitize_text_field, sanitize_hex_color, absint, wp_kses_post
  • Nonce verification on all forms and AJAX
  • Capability check: manage_options for all admin operations
  • Script snippets stored raw (admin-only input, intentionally executable)
  • Frontend JS is vanilla - no jQuery dependency
  • Text domain: att-consent

Testing

Activate plugin, enter a GA4 Measurement ID in General settings, visit frontend. Verify:

  • window.dataLayer contains consent default with wait_for_update
  • Clicking Accept All fires gtag('consent','update') with all granted
  • Cookie att_cc_consent is set
  • Return visit: no banner shown, consent applied from cookie
  • Attribution test: visit with ?utm_source=test, navigate without consenting, consent on page 2, check GA4 DebugView for correct source

Environment

  • WordPress 6.8.3, Astra child theme
  • Local Sites development environment
  • WooCommerce + LearnPress site
  • Previously used CookieYes (cookie-law-info plugin)