PluginProbe ʕ •ᴥ•ʔ
FrontBlocks for Gutenberg/GeneratePress / 1.5.0
FrontBlocks for Gutenberg/GeneratePress v1.5.0
1.5.2 1.5.1 1.4.0 1.5.0 trunk 0.2.0 0.2.1 0.2.2 0.2.3 0.2.4 0.2.5 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.1.0 1.2.0 1.2.1 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 ci-artifacts
frontblocks / assets / cookie-notice / frontblocks-cookie-notice.js
frontblocks / assets / cookie-notice Last commit date
frontblocks-cookie-notice.css 1 week ago frontblocks-cookie-notice.js 1 week ago
frontblocks-cookie-notice.js
293 lines
1 /**
2 * FrontBlocks Cookie Notice
3 *
4 * @package FrontBlocks
5 * @version 1.0.0
6 */
7
8 (function () {
9 'use strict';
10
11 if (document.readyState === 'loading') {
12 document.addEventListener('DOMContentLoaded', init);
13 } else {
14 init();
15 }
16
17 function readCookie(name) {
18 var match = document.cookie.match(new RegExp('(?:^|; )' + name + '=([^;]*)'));
19
20 if (!match) {
21 return '';
22 }
23
24 try {
25 return decodeURIComponent(match[1]);
26 } catch (e) {
27 // Malformed percent-encoding: treat it the same as no cookie at all.
28 return '';
29 }
30 }
31
32 function defineInjectHelper() {
33 window.frblCookieNoticeInject = window.frblCookieNoticeInject || function (gtmId, ga4Id) {
34 if (gtmId) {
35 window.dataLayer = window.dataLayer || [];
36 window.dataLayer.push({ 'gtm.start': new Date().getTime(), event: 'gtm.js' });
37
38 var gtmScript = document.createElement('script');
39 gtmScript.async = true;
40 gtmScript.src = 'https://www.googletagmanager.com/gtm.js?id=' + encodeURIComponent(gtmId);
41 document.head.appendChild(gtmScript);
42 }
43
44 if (ga4Id) {
45 var ga4Script = document.createElement('script');
46 ga4Script.async = true;
47 ga4Script.src = 'https://www.googletagmanager.com/gtag/js?id=' + encodeURIComponent(ga4Id);
48 document.head.appendChild(ga4Script);
49
50 window.dataLayer = window.dataLayer || [];
51 window.gtag = window.gtag || function () {
52 window.dataLayer.push(arguments);
53 };
54 window.gtag('js', new Date());
55 window.gtag('config', ga4Id);
56 }
57 };
58 }
59
60 function fetchAndInjectScripts() {
61 var formData = new FormData();
62 formData.append('action', 'frbl_get_cookie_notice_config');
63
64 fetch(frblCookieNotice.ajaxUrl, {
65 method: 'POST',
66 credentials: 'same-origin',
67 body: formData
68 })
69 .then(function (response) {
70 return response.json();
71 })
72 .then(function (response) {
73 if (response && response.success && response.data && window.frblCookieNoticeInject) {
74 window.frblCookieNoticeInject(response.data.gtmId, response.data.ga4Id);
75 }
76 })
77 .catch(function () {
78 // Network hiccup: consent is already stored locally; nothing else to do here.
79 });
80 }
81
82 /**
83 * For an already-decided visitor, request the tracking scripts (accepted)
84 * or simply do nothing further (rejected). Normally an inline bootstrap
85 * script printed on wp_head already does this as early as possible, well
86 * before this file even loads — this is the fallback for sites whose
87 * Content Security Policy blocks that unnonced inline script, so tracking
88 * still starts there too, just later.
89 *
90 * Deliberately separate from hiding the banner (see hideBannerIfDecided()):
91 * that inline copy runs on wp_head, before '#frbl-cookie-notice' exists in
92 * the DOM at all, so it can never do the hiding itself — only this file,
93 * running once the DOM is ready, can.
94 */
95 function requestTrackingIfNeeded() {
96 if (window.frblCookieNoticeBootstrapped) {
97 return;
98 }
99
100 defineInjectHelper();
101
102 if (readCookie(frblCookieNotice.cookieName) === 'accepted') {
103 fetchAndInjectScripts();
104 }
105
106 window.frblCookieNoticeBootstrapped = true;
107 }
108
109 function hideBannerIfDecided(banner) {
110 var consent = readCookie(frblCookieNotice.cookieName);
111
112 if (banner && (consent === 'accepted' || consent === 'rejected')) {
113 banner.style.display = 'none';
114 return true;
115 }
116
117 return false;
118 }
119
120 function init() {
121 if (typeof frblCookieNotice === 'undefined') {
122 return;
123 }
124
125 requestTrackingIfNeeded();
126
127 var banner = document.getElementById('frbl-cookie-notice');
128
129 if (!banner) {
130 return;
131 }
132
133 if (hideBannerIfDecided(banner)) {
134 // Already decided: nothing left to wire up.
135 return;
136 }
137
138 var acceptBtn = banner.querySelector('[data-frbl-cookie-action="accept"]');
139 var rejectBtn = banner.querySelector('[data-frbl-cookie-action="reject"]');
140 var isPopup = banner.classList.contains('frbl-cookie-notice--popup');
141 var previouslyFocused = document.activeElement;
142
143 if (isPopup) {
144 document.body.classList.add('frbl-cookie-notice-lock-scroll');
145
146 if (acceptBtn) {
147 acceptBtn.focus({ preventScroll: true });
148 }
149
150 document.addEventListener('keydown', trapFocus);
151 }
152
153 if (acceptBtn) {
154 acceptBtn.addEventListener('click', function () {
155 handleDecision('accepted');
156 });
157 }
158
159 if (rejectBtn) {
160 rejectBtn.addEventListener('click', function () {
161 handleDecision('rejected');
162 });
163 }
164
165 function trapFocus(event) {
166 if (event.key !== 'Tab') {
167 return;
168 }
169
170 var focusable = Array.prototype.slice.call(
171 banner.querySelectorAll('a[href], button')
172 );
173
174 if (!focusable.length) {
175 return;
176 }
177
178 var first = focusable[0];
179 var last = focusable[focusable.length - 1];
180
181 if (event.shiftKey && document.activeElement === first) {
182 event.preventDefault();
183 last.focus();
184 } else if (!event.shiftKey && document.activeElement === last) {
185 event.preventDefault();
186 first.focus();
187 }
188 }
189
190 var decided = false;
191
192 function handleDecision(decision) {
193 if (decided) {
194 return;
195 }
196
197 decided = true;
198
199 setConsentCookie(decision);
200 updateConsentMode(decision);
201 hideBanner();
202 dispatchConsentEvent(decision);
203 logDecision(decision);
204
205 if (decision === 'accepted') {
206 fetchAndInjectScripts();
207 }
208 }
209
210 function updateConsentMode(decision) {
211 var granted = decision === 'accepted' ? 'granted' : 'denied';
212
213 window.dataLayer = window.dataLayer || [];
214 window.dataLayer.push(['consent', 'update', {
215 ad_storage: granted,
216 ad_user_data: granted,
217 ad_personalization: granted,
218 analytics_storage: granted
219 }]);
220 }
221
222 function setConsentCookie(decision) {
223 var maxAge = parseInt(frblCookieNotice.expirationDays, 10) * 24 * 60 * 60;
224 var secure = window.location.protocol === 'https:' ? '; Secure' : '';
225
226 document.cookie = frblCookieNotice.cookieName + '=' + decision +
227 '; path=' + frblCookieNotice.cookiePath + '; max-age=' + maxAge + '; SameSite=Lax' + secure;
228 }
229
230 function hideBanner() {
231 banner.classList.add('frbl-cookie-notice--hidden');
232 document.body.classList.remove('frbl-cookie-notice-lock-scroll');
233 document.removeEventListener('keydown', trapFocus);
234
235 if (isPopup && previouslyFocused && typeof previouslyFocused.focus === 'function') {
236 previouslyFocused.focus({ preventScroll: true });
237 }
238
239 window.setTimeout(function () {
240 if (banner.parentNode) {
241 banner.parentNode.removeChild(banner);
242 }
243 }, 300);
244 }
245
246 function dispatchConsentEvent(decision) {
247 var event;
248
249 try {
250 event = new CustomEvent('frblCookieConsent', { detail: { consent: decision } });
251 } catch (e) {
252 event = document.createEvent('CustomEvent');
253 event.initCustomEvent('frblCookieConsent', true, true, { consent: decision });
254 }
255
256 document.dispatchEvent(event);
257 }
258
259 function logDecision(decision) {
260 var nonceForm = new FormData();
261 nonceForm.append('action', 'frbl_get_cookie_notice_log_nonce');
262
263 fetch(frblCookieNotice.ajaxUrl, {
264 method: 'POST',
265 credentials: 'same-origin',
266 body: nonceForm
267 })
268 .then(function (response) {
269 return response.json();
270 })
271 .then(function (response) {
272 if (!response || !response.success || !response.data) {
273 return;
274 }
275
276 var formData = new FormData();
277 formData.append('action', 'frbl_log_cookie_consent');
278 formData.append('nonce', response.data.nonce);
279 formData.append('decision', decision);
280
281 return fetch(frblCookieNotice.ajaxUrl, {
282 method: 'POST',
283 credentials: 'same-origin',
284 body: formData
285 });
286 })
287 .catch(function () {
288 // Best-effort: the aggregate stat is not critical to the consent flow.
289 });
290 }
291 }
292 })();
293