diff --git a/CLAUDE.md b/CLAUDE.md index 476542b..9cf96c7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -12,6 +12,13 @@ A WordPress plugin implementing Google Consent Mode v2 with session attribution 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. diff --git a/admin/views/settings-general.php b/admin/views/settings-general.php index aadac99..b793068 100644 --- a/admin/views/settings-general.php +++ b/admin/views/settings-general.php @@ -16,6 +16,27 @@ if ( ! defined( 'ABSPATH' ) ) {
| + + | +
+
+
+ Built-in banner: this plugin renders the consent banner and owns the consent decision. |
+
|---|---|
| diff --git a/att-consent.php b/att-consent.php index 311a616..2078960 100644 --- a/att-consent.php +++ b/att-consent.php @@ -3,7 +3,7 @@ * Plugin Name: ATT Consent * Plugin URI: https://github.com/att-consent/att-consent * Description: Google Consent Mode v2 cookie consent with session attribution preservation, custom script management, and full gtag.js/GTM support. - * Version: 1.2.0 + * Version: 1.3.0 * Requires at least: 6.0 * Requires PHP: 7.4 * Author: ATT Consent @@ -18,7 +18,7 @@ if ( ! defined( 'ABSPATH' ) ) { exit; } -define( 'ATT_CC_VERSION', '1.2.0' ); +define( 'ATT_CC_VERSION', '1.3.0' ); define( 'ATT_CC_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); define( 'ATT_CC_PLUGIN_URL', plugin_dir_url( __FILE__ ) ); define( 'ATT_CC_PLUGIN_BASENAME', plugin_basename( __FILE__ ) ); diff --git a/includes/class-admin.php b/includes/class-admin.php index 7cfa284..2e31860 100644 --- a/includes/class-admin.php +++ b/includes/class-admin.php @@ -165,6 +165,7 @@ class ATT_Consent_Admin { $settings['ga4_measurement_id'] = sanitize_text_field( $_POST['ga4_measurement_id'] ?? '' ); $settings['gtm_container_id'] = sanitize_text_field( $_POST['gtm_container_id'] ?? '' ); $settings['consent_mode'] = in_array( $_POST['consent_mode'] ?? '', array( 'advanced', 'basic' ), true ) ? $_POST['consent_mode'] : 'advanced'; + $settings['cmp_mode'] = in_array( $_POST['cmp_mode'] ?? '', array( 'self', 'tcf_bridge' ), true ) ? $_POST['cmp_mode'] : 'self'; $settings['banner_position'] = in_array( $_POST['banner_position'] ?? '', array( 'bottom', 'top', 'center' ), true ) ? $_POST['banner_position'] : 'bottom'; $settings['consent_expiry'] = min( 730, max( 1, absint( $_POST['consent_expiry'] ?? 365 ) ) ); $settings['floating_widget'] = in_array( diff --git a/includes/class-att-consent.php b/includes/class-att-consent.php index d1ad50b..ef3d8d2 100644 --- a/includes/class-att-consent.php +++ b/includes/class-att-consent.php @@ -29,6 +29,7 @@ class ATT_Consent { 'ga4_measurement_id' => '', 'gtm_container_id' => '', 'consent_mode' => 'advanced', + 'cmp_mode' => 'self', 'banner_position' => 'bottom', 'consent_expiry' => 365, 'floating_widget' => 'bottom-right', diff --git a/includes/class-frontend.php b/includes/class-frontend.php index 7bba363..e46ea67 100644 --- a/includes/class-frontend.php +++ b/includes/class-frontend.php @@ -50,6 +50,7 @@ class ATT_Consent_Frontend { $config = array( 'consent_mode' => $s['consent_mode'], + 'cmp_mode' => $s['cmp_mode'], 'consent_expiry' => (int) $s['consent_expiry'], 'wait_for_update' => (int) $s['wait_for_update'], 'url_passthrough' => (bool) $s['url_passthrough'], @@ -66,11 +67,13 @@ class ATT_Consent_Frontend { } // In basic mode, pass the tracking snippet for deferred injection. - if ( 'basic' === $s['consent_mode'] ) { + // Basic mode only applies when this plugin owns consent (self mode). + if ( 'basic' === $s['consent_mode'] && 'self' === $s['cmp_mode'] ) { $config['tracking_snippet'] = $this->get_tracking_snippet(); } $config_json = wp_json_encode( $config ); + $is_tcf = ( 'tcf_bridge' === $s['cmp_mode'] ); ?> settings['cmp_mode'] ); + + // Banner CSS is needed in both modes — the floating widget uses it. wp_enqueue_style( 'att-consent-banner', ATT_CC_PLUGIN_URL . 'public/css/banner.css', @@ -207,6 +248,7 @@ j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= ATT_CC_VERSION ); + // consent-manager.js runs in both modes (script gating, cookie I/O, WP Consent API). wp_enqueue_script( 'att-consent-manager', ATT_CC_PLUGIN_URL . 'public/js/consent-manager.js', @@ -215,6 +257,18 @@ j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= true ); + if ( $is_tcf ) { + // In TCF bridge mode the banner.js UI is replaced by the TCF listener. + wp_enqueue_script( + 'att-consent-tcf-bridge', + ATT_CC_PLUGIN_URL . 'public/js/tcf-bridge.js', + array( 'att-consent-manager' ), + ATT_CC_VERSION, + true + ); + return; + } + wp_enqueue_script( 'att-consent-banner', ATT_CC_PLUGIN_URL . 'public/js/banner.js', @@ -226,9 +280,13 @@ j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= /** * Output the banner and preferences modal HTML. + * In TCF bridge mode the banner and modal are suppressed (an external CMP + * renders its own UI); only the optional floating widget is emitted, and + * its click handler is wired to the external CMP via tcf-bridge.js. */ public function output_banner_html() { - $s = $this->settings; + $s = $this->settings; + $is_tcf = ( 'tcf_bridge' === $s['cmp_mode'] ); do_action( 'att_consent_before_banner' ); @@ -246,7 +304,11 @@ j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= esc_attr( $s['btn_preferences_text'] ) ); - $banner_html = ' |