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>
This commit is contained in:
@@ -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'] );
|
||||
|
||||
?>
|
||||
<script>
|
||||
@@ -87,6 +90,24 @@ window.gtag=g;
|
||||
g('js',new Date());
|
||||
if(c.ads_data_redaction){g('set','ads_data_redaction',true);}
|
||||
if(c.url_passthrough){g('set','url_passthrough',true);}
|
||||
<?php if ( $is_tcf ) : ?>
|
||||
// TCF bridge mode: an external CMP will emit gtag('consent','update', ...)
|
||||
// once it has read the user's TC string. We still emit a default-denied
|
||||
// here so gtag.js holds events until the CMP responds — without this,
|
||||
// gtag falls back to its implicit "granted" default and would fire a
|
||||
// fully-tracked hit before the CMP has had a chance to apply consent.
|
||||
g('consent','default',{
|
||||
ad_storage:'denied',
|
||||
analytics_storage:'denied',
|
||||
ad_user_data:'denied',
|
||||
ad_personalization:'denied',
|
||||
functionality_storage:'denied',
|
||||
personalization_storage:'denied',
|
||||
security_storage:'granted',
|
||||
wait_for_update:c.wait_for_update||500
|
||||
});
|
||||
window.attCCHasConsent=false;
|
||||
<?php else : ?>
|
||||
var ck=document.cookie.match(/(?:^|; )att_cc_consent=([^;]*)/);
|
||||
var st=null;
|
||||
if(ck){try{st=JSON.parse(decodeURIComponent(ck[1]));}catch(e){}}
|
||||
@@ -115,6 +136,7 @@ wait_for_update:c.wait_for_update||500
|
||||
});
|
||||
window.attCCHasConsent=false;
|
||||
}
|
||||
<?php endif; ?>
|
||||
if(!sessionStorage.getItem('att_cc_attr')){
|
||||
var p=new URLSearchParams(window.location.search);
|
||||
var a={
|
||||
@@ -134,6 +156,22 @@ if(a.r||a.us||a.gc||a.gl){
|
||||
sessionStorage.setItem('att_cc_attr',JSON.stringify(a));
|
||||
}
|
||||
}
|
||||
<?php if ( $is_tcf ) : ?>
|
||||
// Pre-stage attribution params on dataLayer (no-op if nothing captured).
|
||||
try{
|
||||
var sa=sessionStorage.getItem('att_cc_attr');
|
||||
if(sa){
|
||||
var aa=JSON.parse(sa);
|
||||
var sp={};
|
||||
if(aa.us){sp.campaign_source=aa.us;}
|
||||
if(aa.um){sp.campaign_medium=aa.um;}
|
||||
if(aa.uc){sp.campaign_name=aa.uc;}
|
||||
if(aa.ut){sp.campaign_term=aa.ut;}
|
||||
if(aa.uo){sp.campaign_content=aa.uo;}
|
||||
for(var k in sp){g('set',sp);break;}
|
||||
}
|
||||
}catch(e){}
|
||||
<?php endif; ?>
|
||||
})();
|
||||
</script>
|
||||
<?php
|
||||
@@ -200,6 +238,9 @@ j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
|
||||
* Enqueue frontend assets.
|
||||
*/
|
||||
public function enqueue_assets() {
|
||||
$is_tcf = ( 'tcf_bridge' === $this->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 = '<div id="att-cc-banner" class="' . esc_attr( $position_class ) . '" role="dialog" aria-labelledby="att-cc-banner-heading" aria-hidden="false" tabindex="-1" style="' . $style_vars . '">
|
||||
$banner_html = '';
|
||||
$modal_html = '';
|
||||
|
||||
if ( ! $is_tcf ) {
|
||||
$banner_html = '<div id="att-cc-banner" class="' . esc_attr( $position_class ) . '" role="dialog" aria-labelledby="att-cc-banner-heading" aria-hidden="false" tabindex="-1" style="' . $style_vars . '">
|
||||
<div class="att-cc-banner__inner">
|
||||
<div class="att-cc-banner__content">
|
||||
<h2 id="att-cc-banner-heading" class="att-cc-banner__heading">' . esc_html( $s['banner_heading'] ) . '</h2>
|
||||
@@ -260,7 +322,7 @@ j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
|
||||
</div>
|
||||
</div>';
|
||||
|
||||
$modal_html = '<div id="att-cc-modal" role="dialog" aria-labelledby="att-cc-modal-heading" aria-hidden="true" aria-modal="true" style="' . $style_vars . '">
|
||||
$modal_html = '<div id="att-cc-modal" role="dialog" aria-labelledby="att-cc-modal-heading" aria-hidden="true" aria-modal="true" style="' . $style_vars . '">
|
||||
<div class="att-cc-modal__overlay"></div>
|
||||
<div class="att-cc-modal__dialog">
|
||||
<h2 id="att-cc-modal-heading" class="att-cc-modal__heading">' . esc_html__( 'Manage Cookie Preferences', 'att-consent' ) . '</h2>
|
||||
@@ -312,12 +374,15 @@ j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
|
||||
</div>
|
||||
</div>
|
||||
</div>';
|
||||
}
|
||||
|
||||
$widget_html = '';
|
||||
if ( 'none' !== $s['floating_widget'] ) {
|
||||
$widget_position = 'right' === $s['floating_widget'] ? 'att-cc-widget--right' : 'att-cc-widget--bottom-right';
|
||||
$widget_label = ! empty( $s['floating_widget_label'] ) ? $s['floating_widget_label'] : 'Cookie Settings';
|
||||
$widget_html = '<button type="button" id="att-cc-widget" class="att-cc-widget ' . esc_attr( $widget_position ) . '" aria-label="' . esc_attr( $widget_label ) . '" style="display:none;' . $style_vars . '"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 2a10 10 0 1 0 10 10A10 10 0 0 0 12 2zm0 18a8 8 0 1 1 8-8 8 8 0 0 1-8 8zm-1-13a1.5 1.5 0 1 1-1.5-1.5A1.5 1.5 0 0 1 11 7zm4 2a1 1 0 1 1-1-1 1 1 0 0 1 1 1zm-7 3a1 1 0 1 1-1-1 1 1 0 0 1 1 1zm3 4a1.5 1.5 0 1 1-1.5-1.5A1.5 1.5 0 0 1 11 16zm5-1a1 1 0 1 1-1-1 1 1 0 0 1 1 1z"/></svg> ' . esc_html( $widget_label ) . '</button>';
|
||||
// In TCF bridge mode the widget is shown immediately (no banner to wait for).
|
||||
$widget_display = $is_tcf ? '' : 'display:none;';
|
||||
$widget_html = '<button type="button" id="att-cc-widget" class="att-cc-widget ' . esc_attr( $widget_position ) . '" aria-label="' . esc_attr( $widget_label ) . '" style="' . $widget_display . $style_vars . '"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 2a10 10 0 1 0 10 10A10 10 0 0 0 12 2zm0 18a8 8 0 1 1 8-8 8 8 0 0 1-8 8zm-1-13a1.5 1.5 0 1 1-1.5-1.5A1.5 1.5 0 0 1 11 7zm4 2a1 1 0 1 1-1-1 1 1 0 0 1 1 1zm-7 3a1 1 0 1 1-1-1 1 1 0 0 1 1 1zm3 4a1.5 1.5 0 1 1-1.5-1.5A1.5 1.5 0 0 1 11 16zm5-1a1 1 0 1 1-1-1 1 1 0 0 1 1 1z"/></svg> ' . esc_html( $widget_label ) . '</button>';
|
||||
}
|
||||
|
||||
$html = apply_filters( 'att_consent_banner_html', $banner_html . $modal_html . $widget_html, $s );
|
||||
|
||||
Reference in New Issue
Block a user