PluginProbe
aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder / 2.14.0
aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder v2.14.0
2.14.0 2.13.0 2.13.1 2.12.0 2.11.1 2.11.0 2.10.0 2.9.0 2.7.4 2.7.5 2.7.6 2.7.7 2.8.0 2.8.1 2.9.1 trunk 1.0 1.0-beta1 1.0-beta2 1.0-beta3 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 All 81 releases
ablocks / addons / cookie-consent / consent-mode.php

consent-mode.php in aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder 2.14.0, at addons/cookie-consent/consent-mode.php

152 lines 5.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace ABlocksCookieConsent;
3
4 if ( ! defined( 'ABSPATH' ) ) {
5 exit;
6 }
7
8 /**
9 * Google Consent Mode v2.
10 *
11 * Google's tags read consent signals rather than being blocked outright, and
12 * they read them *at load*. A `consent update` issued after gtag.js has already
13 * initialised does not retroactively change the default state, so this block
14 * has to be the first script in the document — before any enqueued script,
15 * before any `wp_head` snippet a theme prints, before GTM.
16 *
17 * It is exempt from gating by construction: it is the thing that makes the
18 * gating legible to Google, and blocking it would defeat the purpose.
19 *
20 * Note this is complementary to, not a replacement for, blocking the tags.
21 * Consent Mode changes what Google's tags *do*; the gating layers stop them
22 * loading at all. A site can run either or both, and running both is what most
23 * European guidance expects.
24 */
25 class ConsentMode {
26
27 /**
28 * Category → the Consent Mode signals it controls.
29 *
30 * @var array
31 */
32 private static $signal_map = [
33 'analytics' => [ 'analytics_storage' ],
34 'marketing' => [ 'ad_storage', 'ad_user_data', 'ad_personalization' ],
35 'functional' => [ 'functionality_storage', 'personalization_storage' ],
36 ];
37
38 public static function init() {
39 // The settings are read inside the callback rather than here. Reading
40 // them at plugin-load time would evaluate the translated defaults
41 // before `init`, which WordPress rightly complains about.
42 //
43 // Negative priority so this beats anything hooked at 0 or 1, including
44 // core's own head output. Being first is the whole requirement.
45 add_action( 'wp_head', [ new self(), 'print_defaults' ], -9999 );
46 }
47
48 /**
49 * All signals, denied, except the one that is never optional.
50 *
51 * @return array
52 */
53 public static function denied_defaults() {
54 $defaults = [];
55 foreach ( self::$signal_map as $signals ) {
56 foreach ( $signals as $signal ) {
57 $defaults[ $signal ] = 'denied';
58 }
59 }
60 $defaults['security_storage'] = 'granted';
61 return $defaults;
62 }
63
64 /**
65 * @return array Category => signals, for the client to build its updates.
66 */
67 public static function signal_map() {
68 return apply_filters( 'ablocks/cookie_consent/consent_mode_signals', self::$signal_map );
69 }
70
71 public function print_defaults() {
72 if ( ! Helper::get( 'enabled', true ) || ! Helper::get( 'consent_mode', true ) ) {
73 return;
74 }
75 if ( ! Helper::should_render_banner() ) {
76 return;
77 }
78
79 $defaults = self::denied_defaults();
80 $wait = (int) Helper::get( 'consent_mode_wait', 500 );
81 if ( $wait > 0 ) {
82 $defaults['wait_for_update'] = $wait;
83 }
84
85 // Both names, because this runs before the main script and has to find
86 // the decision even on the first page view after a rename.
87 $cookies = array_values(
88 array_filter(
89 [
90 Helper::get( 'cookie_name', 'ablocks_consent' ),
91 Helper::get( 'cookie_name_previous', '' ),
92 ]
93 )
94 );
95 $map = self::signal_map();
96 $ads = (bool) Helper::get( 'consent_mode_ads', true );
97 ?>
98 <script id="ablocks-consent-mode" data-ablocks-consent-skip="1">
99 /* aBlocks Consent Mode v2 — must stay the first script in the document. */
100 window.dataLayer = window.dataLayer || [];
101 function gtag(){ dataLayer.push( arguments ); }
102 <?php if ( $ads ) : ?>
103 <?php
104 // The two settings Google pairs with denied defaults, and the reason a
105 // refusal need not cost the site its conversion reporting.
106 //
107 // `ads_data_redaction` strips ad identifiers from the requests Google Ads
108 // still makes while `ad_storage` is denied. `url_passthrough` carries the
109 // click id in the URL between pages instead of a cookie, so a visitor who
110 // refused is still attributed to the ad they arrived from.
111 //
112 // Both are `set` calls, not consent signals: they have to be in place
113 // before the first tag reads them, which is why they sit here rather than
114 // in the update the banner sends.
115 ?>
116 gtag( 'set', 'ads_data_redaction', true );
117 gtag( 'set', 'url_passthrough', true );
118 <?php endif; ?>
119 gtag( 'consent', 'default', <?php echo wp_json_encode( $defaults ); ?> );
120 ( function () {
121 var names = <?php echo wp_json_encode( $cookies ); ?>;
122 var map = <?php echo wp_json_encode( $map ); ?>;
123 var raw = null;
124 document.cookie.split( ';' ).forEach( function ( part ) {
125 var pair = part.split( '=' );
126 var key = pair.shift().trim();
127 if ( null === raw && names.indexOf( key ) > -1 ) { raw = pair.join( '=' ); }
128 } );
129 if ( ! raw ) { return; }
130 var saved;
131 try { saved = JSON.parse( decodeURIComponent( raw ) ); } catch ( e ) { return; }
132 <?php // Number() rather than a bare strict compare: a cookie written by an older build may hold the version as a string. ?>
133 if ( ! saved || Number( saved.v ) !== <?php echo (int) Helper::get( 'policy_version', 1 ); ?> ) { return; }
134 var granted = saved.c || [];
135 var update = {};
136 Object.keys( map ).forEach( function ( category ) {
137 var state = granted.indexOf( category ) > -1 ? 'granted' : 'denied';
138 map[ category ].forEach( function ( signal ) { update[ signal ] = state; } );
139 } );
140 gtag( 'consent', 'update', update );
141 <?php if ( $ads ) : ?>
142 <?php // Redaction is only wanted while ads are refused; leaving it on after a grant would quietly degrade the reporting the visitor agreed to. ?>
143 if ( 'granted' === update.ad_storage ) { gtag( 'set', 'ads_data_redaction', false ); }
144 <?php endif; ?>
145 <?php // Recorded so the main script does not send the same state a second time on every page view of a visitor who already decided. ?>
146 window.ABlocksConsentSignals = update;
147 } )();
148 </script>
149 <?php
150 }
151 }
152