PageSourceSearch

https://grey-mosquito-207461.hostingersite.com/wp-content/plugins/lw-cookie/assets/js/consent.js?ver=1.8.3

js grey-mosquito-207461.hostingersite.com collected 2026-10-03 04:26:57 UTC 12,162 bytes, 498 lines download raw bytes

1/**
2 * LW Cookie — Consent Manager (v2.0 — cache-proof)
3 *
4 * Reads consent state from the browser cookie (not from PHP config).
5 * Saves consent via REST API (not admin-ajax).
6 * Integrates with guard.js via window.__lwGuard.refresh().
7 *
8 * @package LightweightPlugins\Cookie
9 */
10
11(function () {
12	'use strict';
13
14	var config      = window.lwCookieConfig || {};
15	var COOKIE_NAME = config.cookieName || 'lw_cookie_consent';
16
17	/**
18	 * Read consent data from the browser cookie.
19	 *
20	 * @return {Object|null} Parsed consent or null.
21	 */
22	function readConsentCookie() {
23		var match = document.cookie.match( '(?:^|; )' + COOKIE_NAME + '=([^;]*)' );
24		if ( ! match ) {
25			return null;
26		}
27		try {
28			return JSON.parse( atob( match[1] ) );
29		} catch ( e ) {
30			return null;
31		}
32	}
33
34	/**
35	 * Check if stored consent matches current policy version.
36	 */
37	function isConsentValid() {
38		var data = readConsentCookie();
39		return data && data.version === config.policyVersion && data.categories;
40	}
41
42	/**
43	 * Get current consent categories from cookie.
44	 *
45	 * @return {Object} categories or empty fallback.
46	 */
47	function getConsentCategories() {
48		var data = readConsentCookie();
49		if ( data && data.version === config.policyVersion && data.categories ) {
50			return data.categories;
51		}
52		return { necessary: true };
53	}
54
55	/**
56	 * Initialize the consent manager.
57	 */
58	function init() {
59		bindEvents();
60	}
61
62	/**
63	 * Bind event listeners.
64	 */
65	function bindEvents() {
66		document.querySelectorAll( '[data-lw-cookie-accept]' ).forEach(
67			function ( btn ) {
68				btn.addEventListener( 'click', acceptAll );
69			}
70		);
71
72		document.querySelectorAll( '[data-lw-cookie-reject]' ).forEach(
73			function ( btn ) {
74				btn.addEventListener( 'click', rejectAll );
75			}
76		);
77
78		document.querySelectorAll( '[data-lw-cookie-customize]' ).forEach(
79			function ( btn ) {
80				btn.addEventListener( 'click', openPreferences );
81			}
82		);
83
84		document.querySelectorAll( '[data-lw-cookie-save]' ).forEach(
85			function ( btn ) {
86				btn.addEventListener( 'click', savePreferences );
87			}
88		);
89
90		document.querySelectorAll( '[data-lw-cookie-close-modal]' ).forEach(
91			function ( el ) {
92				el.addEventListener( 'click', closePreferences );
93			}
94		);
95
96		document.querySelectorAll( '[data-lw-cookie-open-preferences]' ).forEach(
97			function ( btn ) {
98				btn.addEventListener( 'click', openPreferences );
99			}
100		);
101
102		document.addEventListener(
103			'keydown',
104			function ( e ) {
105				if ( e.key === 'Escape' ) {
106					closePreferences();
107				}
108			}
109		);
110	}
111
112	/**
113	 * Show the cookie banner.
114	 */
115	function showBanner() {
116		var banner = document.getElementById( 'lw-cookie-notice' );
117		if ( banner ) {
118			banner.classList.remove( 'lw-cookie-hidden' );
119			banner.style.display = '';
120		}
121	}
122
123	/**
124	 * Hide the cookie banner.
125	 */
126	function hideBanner() {
127		var banner = document.getElementById( 'lw-cookie-notice' );
128		if ( banner ) {
129			banner.classList.add( 'lw-cookie-hidden' );
130		}
131	}
132
133	/**
134	 * Open preferences modal.
135	 */
136	function openPreferences() {
137		hideBanner();
138		var modal = document.getElementById( 'lw-cookie-preferences' );
139		if ( modal ) {
140			modal.style.display = 'flex';
141			modal.classList.remove( 'lw-cookie-hidden' );
142
143			setCheckboxStates();
144
145			var firstCheckbox = modal.querySelector( 'input[type="checkbox"]:not(:disabled)' );
146			if ( firstCheckbox ) {
147				firstCheckbox.focus();
148			}
149		}
150	}
151
152	/**
153	 * Close preferences modal.
154	 */
155	function closePreferences() {
156		var modal = document.getElementById( 'lw-cookie-preferences' );
157		if ( modal ) {
158			modal.style.display = 'none';
159			modal.classList.add( 'lw-cookie-hidden' );
160		}
161
162		// Show banner again if no valid consent.
163		if ( ! isConsentValid() ) {
164			showBanner();
165		}
166	}
167
168	/**
169	 * Set checkbox states from cookie (not from PHP config).
170	 */
171	function setCheckboxStates() {
172		var categories = getConsentCategories();
173
174		Object.keys( categories ).forEach(
175			function ( key ) {
176				var checkbox = document.querySelector( '[data-category="' + key + '"]' );
177				if ( checkbox && ! checkbox.disabled ) {
178					checkbox.checked = categories[key];
179				}
180			}
181		);
182	}
183
184	/**
185	 * Accept all cookies.
186	 */
187	function acceptAll() {
188		var categories = {
189			necessary: true,
190			functional: true,
191			analytics: true,
192			marketing: true
193		};
194
195		saveConsent( categories, 'accept_all' );
196	}
197
198	/**
199	 * Reject all optional cookies.
200	 */
201	function rejectAll() {
202		var categories = {
203			necessary: true,
204			functional: false,
205			analytics: false,
206			marketing: false
207		};
208
209		saveConsent( categories, 'reject_all' );
210	}
211
212	/**
213	 * Save custom preferences.
214	 */
215	function savePreferences() {
216		var categories = {
217			necessary: true,
218			functional: isChecked( 'functional' ),
219			analytics: isChecked( 'analytics' ),
220			marketing: isChecked( 'marketing' )
221		};
222
223		saveConsent( categories, 'customize' );
224		closePreferences();
225	}
226
227	/**
228	 * Check if a category checkbox is checked.
229	 *
230	 * @param {string} category Category key.
231	 * @return {boolean}
232	 */
233	function isChecked( category ) {
234		var checkbox = document.querySelector( '[data-category="' + category + '"]' );
235		return checkbox ? checkbox.checked : false;
236	}
237
238	/**
239	 * Save consent to cookie and server.
240	 *
241	 * @param {Object}  categories  Category consent states.
242	 * @param {string}  actionType  Action type.
243	 * @param {boolean} skipReload  Skip page reload.
244	 */
245	function saveConsent( categories, actionType, skipReload ) {
246		// Save to cookie immediately.
247		saveCookie( categories );
248
249		// Hide banner.
250		hideBanner();
251
252		// Notify guard.js to update SW, GCM, observer, etc.
253		var synced = null;
254		if ( window.__lwGuard && window.__lwGuard.refresh ) {
255			synced = window.__lwGuard.refresh( categories );
256		}
257
258		// Send to server via REST API (fire-and-forget; keepalive lets it outlive a reload).
259		if ( config.restUrl ) {
260			fetch(
261				config.restUrl,
262				{
263					method: 'POST',
264					headers: { 'Content-Type': 'application/json' },
265					body: JSON.stringify(
266						{
267							categories: categories,
268							action_type: actionType
269						}
270					),
271				credentials: 'same-origin',
272				// Survive the reload that may follow right after accepting.
273				keepalive: true
274				}
275			).catch(
276				function ( error ) {
277					console.error( 'LW Cookie: Failed to log consent', error );
278				}
279			);
280		}
281
282		// Dispatch events for other scripts.
283		dispatchConsentEvent( categories, actionType );
284
285		// Reload only if scripts were previously blocked and now need loading.
286		// The SW can block but cannot retroactively load scripts.
287		if ( ! skipReload && needsReload( categories ) ) {
288			reloadWhenSynced( synced );
289		}
290	}
291
292	/**
293	 * Reload once the Service Worker holds the new consent state.
294	 *
295	 * The reloaded page requests its scripts before guard.js can message the
296	 * worker, so the worker must know about the change beforehand. Waits for
297	 * guard.js's sync (which caps the wait itself), and at least 100 ms so the
298	 * consent log request is on its way.
299	 *
300	 * @param {Promise|null} synced Settles when the SW has the new state.
301	 */
302	function reloadWhenSynced( synced ) {
303		var reload = function () {
304			window.location.reload();
305		};
306
307		if ( typeof Promise === 'undefined' ) {
308			setTimeout( reload, 100 );
309			return;
310		}
311
312		var minDelay = new Promise(
313			function ( resolve ) {
314				setTimeout( resolve, 100 );
315			}
316		);
317
318		Promise.all( [ synced, minDelay ] ).then( reload, reload );
319	}
320
321	/**
322	 * Check if a page reload is needed.
323	 *
324	 * Reload if any previously blocked category is now allowed,
325	 * since blocked scripts cannot be un-blocked without reload.
326	 *
327	 * @param {Object} categories New categories.
328	 * @return {boolean}
329	 */
330	function needsReload( categories ) {
331		return categories.analytics || categories.marketing || categories.functional;
332	}
333
334	/**
335	 * Save consent to cookie.
336	 *
337	 * @param {Object} categories Category consent states.
338	 */
339	function saveCookie( categories ) {
340		var consent = {
341			id: generateUUID(),
342			version: config.policyVersion,
343			timestamp: Math.floor( Date.now() / 1000 ),
344			categories: categories
345		};
346
347		var cookieValue = btoa( JSON.stringify( consent ) );
348		var duration    = config.consentDuration || 365;
349		var expires     = new Date();
350		expires.setDate( expires.getDate() + duration );
351
352		document.cookie = COOKIE_NAME + '=' + cookieValue +
353			';expires=' + expires.toUTCString() +
354			';path=/;SameSite=Lax';
355	}
356
357	/**
358	 * Delete all cookies for the current domain.
359	 */
360	function deleteAllCookies() {
361		var cookies       = document.cookie.split( ';' );
362		var domain        = window.location.hostname;
363		var paths         = ['/', window.location.pathname];
364		var cookiesLength = cookies.length;
365		var pathsLength   = paths.length;
366
367		for ( var i = 0; i < cookiesLength; i++ ) {
368			var cookie     = cookies[i];
369			var eqPos      = cookie.indexOf( '=' );
370			var cookieName = eqPos > -1 ? cookie.substr( 0, eqPos ).trim() : cookie.trim();
371
372			if ( ! cookieName ) {
373				continue;
374			}
375
376			for ( var j = 0; j < pathsLength; j++ ) {
377				document.cookie = cookieName + '=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=' + paths[j];
378				document.cookie = cookieName + '=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=' + paths[j] + ';domain=' + domain;
379
380				if ( domain.indexOf( 'www.' ) !== 0 ) {
381					document.cookie = cookieName + '=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=' + paths[j] + ';domain=.' + domain;
382				}
383			}
384		}
385
386		window.location.reload();
387	}
388
389	/**
390	 * Generate a UUID v4.
391	 *
392	 * @return {string}
393	 */
394	function generateUUID() {
395		return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(
396			/[xy]/g,
397			function ( c ) {
398				var r = Math.random() * 16 | 0;
399				var v = c === 'x' ? r : (r & 0x3 | 0x8);
400				return v.toString( 16 );
401			}
402		);
403	}
404
405	/**
406	 * Dispatch consent event for other scripts.
407	 *
408	 * @param {Object} categories Category consent states.
409	 * @param {string} actionType Action type.
410	 */
411	function dispatchConsentEvent( categories, actionType ) {
412		var event = new CustomEvent(
413			'lwCookieConsent',
414			{
415				detail: {
416					categories: categories,
417					action: actionType
418				}
419			}
420		);
421		window.dispatchEvent( event );
422
423		window.dataLayer = window.dataLayer || [];
424		window.dataLayer.push(
425			{
426				'event': 'lw_cookie_consent_update',
427				'lw_cookie_consent': {
428					'necessary': true,
429					'functional': categories.functional || false,
430					'analytics': categories.analytics || false,
431					'marketing': categories.marketing || false
432				},
433				'lw_cookie_action': actionType
434			}
435		);
436
437		// GCM v2 update is handled by guard.js.refresh() above.
438
439		// Meta Pixel consent API.
440		if ( typeof fbq === 'function' ) {
441			if ( categories.marketing ) {
442				fbq( 'consent', 'grant' );
443			} else {
444				fbq( 'consent', 'revoke' );
445			}
446		}
447	}
448
449	// Initialize on DOM ready.
450	if ( document.readyState === 'loading' ) {
451		document.addEventListener( 'DOMContentLoaded', init );
452	} else {
453		init();
454	}
455
456	/**
457	 * Grant a single category and persist it.
458	 *
459	 * Used by the blocked-embed placeholder (guard.js): the visitor consents
460	 * to just the category the embed needs, and guard.js restores it in place
461	 * via __lwGuard.refresh() (called from saveConsent). When a script of that
462	 * category is blocked on the page, the page reloads instead: a blocked
463	 * script cannot be restored in place, since code relying on it (an SDK's
464	 * init call, say) has already run without it.
465	 *
466	 * @param {string} category Category key to grant.
467	 */
468	function acceptCategory( category ) {
469		var categories       = getConsentCategories();
470		categories.necessary = true;
471		if ( category ) {
472			categories[category] = true;
473		}
474
475		var guard  = window.__lwGuard;
476		var reload = ! ! ( guard && guard.hasBlockedScripts && guard.hasBlockedScripts( category ) );
477
478		saveConsent( categories, 'load_embed', ! reload );
479	}
480
481	// Expose API globally.
482	window.LWCookie = {
483		acceptAll: acceptAll,
484		rejectAll: rejectAll,
485		openPreferences: openPreferences,
486		deleteAllCookies: deleteAllCookies,
487		saveConsent: saveConsent,
488		acceptCategory: acceptCategory,
489		getConsent: function () {
490			return getConsentCategories();
491		},
492		isAllowed: function ( category ) {
493			var cats = getConsentCategories();
494			return cats[category] === true;
495		}
496	};
497
498})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.