424 lines
15 KiB
JavaScript
424 lines
15 KiB
JavaScript
/**
|
|
* Cookie Consent Management System
|
|
* All sensitive data (tracking IDs, URLs) are loaded from server-side PHP
|
|
* Includes fallback for local file access (no server)
|
|
*
|
|
* @version 2.1.0
|
|
*/
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
|
|
// ==========================================
|
|
// CONFIGURATION
|
|
// ==========================================
|
|
|
|
const cookieCategories = ['necessary', 'analytics', 'marketing'];
|
|
const API_ENDPOINT = 'scripts/add/send.php';
|
|
|
|
// Detect if running from file:// protocol (no server)
|
|
const isLocalFile = window.location.protocol === 'file:';
|
|
|
|
let consentState = {
|
|
hasConsented: false,
|
|
preferences: {}
|
|
};
|
|
|
|
let previousPreferences = {};
|
|
let trackingScripts = {};
|
|
|
|
// ==========================================
|
|
// DATALAYER INTEGRATION
|
|
// ==========================================
|
|
|
|
window.dataLayer = window.dataLayer || [];
|
|
|
|
function pushConsentToDataLayer(preferences) {
|
|
window.dataLayer.push({
|
|
'event': 'consent_update',
|
|
'consent': {
|
|
'analytics_storage': preferences.analytics ? 'granted' : 'denied',
|
|
'ad_storage': preferences.marketing ? 'granted' : 'denied',
|
|
'functionality_storage': preferences.necessary ? 'granted' : 'denied',
|
|
'personalization_storage': preferences.marketing ? 'granted' : 'denied',
|
|
'security_storage': 'granted'
|
|
}
|
|
});
|
|
}
|
|
|
|
function setDefaultConsent() {
|
|
window.dataLayer.push({
|
|
'event': 'consent_default',
|
|
'consent': {
|
|
'analytics_storage': 'denied',
|
|
'ad_storage': 'denied',
|
|
'functionality_storage': 'granted',
|
|
'personalization_storage': 'denied',
|
|
'security_storage': 'granted',
|
|
'wait_for_update': 500
|
|
}
|
|
});
|
|
}
|
|
|
|
// ==========================================
|
|
// STORAGE (API + LOCALSTORAGE FALLBACK)
|
|
// ==========================================
|
|
|
|
async function saveConsentToStorage(preferences) {
|
|
// Always save to localStorage as backup
|
|
const consentData = {
|
|
hasConsented: true,
|
|
preferences: preferences,
|
|
timestamp: new Date().toISOString()
|
|
};
|
|
localStorage.setItem('cookieConsent', JSON.stringify(consentData));
|
|
|
|
// Try API if not local file
|
|
if (!isLocalFile) {
|
|
try {
|
|
const response = await fetch(API_ENDPOINT, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ type: 'cookie_consent', preferences })
|
|
});
|
|
const result = await response.json();
|
|
if (result.success && result.data && result.data.scripts) {
|
|
trackingScripts = result.data.scripts;
|
|
}
|
|
return result.success;
|
|
} catch (e) {
|
|
// API failed, localStorage already saved
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
async function getConsentFromStorage() {
|
|
// Try localStorage first (faster)
|
|
const localConsent = localStorage.getItem('cookieConsent');
|
|
if (localConsent) {
|
|
try {
|
|
return JSON.parse(localConsent);
|
|
} catch (e) {
|
|
localStorage.removeItem('cookieConsent');
|
|
}
|
|
}
|
|
|
|
// Try API if not local file
|
|
if (!isLocalFile) {
|
|
try {
|
|
const response = await fetch(API_ENDPOINT, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ type: 'get_cookie_consent' })
|
|
});
|
|
const result = await response.json();
|
|
if (result.success && result.data) {
|
|
if (result.data.scripts) {
|
|
trackingScripts = result.data.scripts;
|
|
}
|
|
// Cache in localStorage
|
|
localStorage.setItem('cookieConsent', JSON.stringify(result.data));
|
|
return result.data;
|
|
}
|
|
} catch (e) {
|
|
// API unavailable
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// ==========================================
|
|
// SCRIPT LOADING
|
|
// ==========================================
|
|
|
|
function loadTrackingScripts() {
|
|
if (!trackingScripts || Object.keys(trackingScripts).length === 0) return;
|
|
|
|
if (trackingScripts.ga && consentState.preferences.analytics) {
|
|
loadGoogleAnalytics(trackingScripts.ga);
|
|
}
|
|
if (trackingScripts.gtm && consentState.preferences.analytics) {
|
|
loadGTM(trackingScripts.gtm);
|
|
}
|
|
if (trackingScripts.fb && consentState.preferences.marketing) {
|
|
loadFacebookPixel(trackingScripts.fb);
|
|
}
|
|
if (trackingScripts.gads && consentState.preferences.marketing) {
|
|
loadGoogleAds(trackingScripts.gads);
|
|
}
|
|
if (trackingScripts.linkedin && consentState.preferences.marketing) {
|
|
loadLinkedIn(trackingScripts.linkedin);
|
|
}
|
|
}
|
|
|
|
function loadScript(src, callback) {
|
|
const script = document.createElement('script');
|
|
script.src = src;
|
|
script.async = true;
|
|
if (callback) script.onload = callback;
|
|
document.head.appendChild(script);
|
|
}
|
|
|
|
function loadGoogleAnalytics(config) {
|
|
if (!config.src || !config.config || !config.config.id) return;
|
|
loadScript(config.src, function() {
|
|
window.dataLayer = window.dataLayer || [];
|
|
function gtag(){dataLayer.push(arguments);}
|
|
window.gtag = gtag;
|
|
gtag('js', new Date());
|
|
gtag('config', config.config.id);
|
|
});
|
|
}
|
|
|
|
function loadGTM(config) {
|
|
if (!config.id) return;
|
|
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
|
|
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
|
|
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
|
|
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
|
|
})(window,document,'script','dataLayer',config.id);
|
|
}
|
|
|
|
function loadFacebookPixel(config) {
|
|
if (!config.id) return;
|
|
!function(f,b,e,v,n,t,s){if(f.fbq)return;n=f.fbq=function(){n.callMethod?
|
|
n.callMethod.apply(n,arguments):n.queue.push(arguments)};if(!f._fbq)f._fbq=n;
|
|
n.push=n;n.loaded=!0;n.version='2.0';n.queue=[];t=b.createElement(e);t.async=!0;
|
|
t.src=v;s=b.getElementsByTagName(e)[0];s.parentNode.insertBefore(t,s)}(window,
|
|
document,'script','https://connect.facebook.net/en_US/fbevents.js');
|
|
fbq('init', config.id);
|
|
fbq('track', 'PageView');
|
|
}
|
|
|
|
function loadGoogleAds(config) {
|
|
if (!config.id) return;
|
|
loadScript('https://www.googletagmanager.com/gtag/js?id=' + config.id, function() {
|
|
window.dataLayer = window.dataLayer || [];
|
|
function gtag(){dataLayer.push(arguments);}
|
|
gtag('js', new Date());
|
|
gtag('config', config.id);
|
|
});
|
|
}
|
|
|
|
function loadLinkedIn(config) {
|
|
if (!config.id) return;
|
|
window._linkedin_partner_id = config.id;
|
|
window._linkedin_data_partner_ids = window._linkedin_data_partner_ids || [];
|
|
window._linkedin_data_partner_ids.push(config.id);
|
|
loadScript('https://snap.licdn.com/li.lms-analytics/insight.min.js');
|
|
}
|
|
|
|
// ==========================================
|
|
// COOKIE DELETION
|
|
// ==========================================
|
|
|
|
const cookiePatterns = {
|
|
analytics: ['_ga', '_gid', '_gat', '__utma', '__utmb', '__utmc', '__utmz'],
|
|
marketing: ['_fbp', '_fbc', 'fr', '_gcl_au', '_gcl_aw', 'IDE', 'DSID', 'NID']
|
|
};
|
|
|
|
function deleteCookie(name) {
|
|
const paths = ['/', window.location.pathname];
|
|
const domains = ['', window.location.hostname, '.' + window.location.hostname];
|
|
|
|
paths.forEach(path => {
|
|
domains.forEach(domain => {
|
|
document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=${path}${domain ? '; domain=' + domain : ''}`;
|
|
});
|
|
});
|
|
}
|
|
|
|
function deleteCookiesForCategory(category) {
|
|
const patterns = cookiePatterns[category] || [];
|
|
const allCookies = document.cookie.split(';');
|
|
|
|
allCookies.forEach(cookie => {
|
|
const cookieName = cookie.split('=')[0].trim();
|
|
patterns.forEach(pattern => {
|
|
if (cookieName.startsWith(pattern)) {
|
|
deleteCookie(cookieName);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
function handlePreferenceChanges(newPreferences) {
|
|
Object.keys(cookiePatterns).forEach(category => {
|
|
if (previousPreferences[category] === true && newPreferences[category] === false) {
|
|
deleteCookiesForCategory(category);
|
|
window.dataLayer.push({ 'event': 'consent_revoked', 'consent_category': category });
|
|
}
|
|
});
|
|
previousPreferences = { ...newPreferences };
|
|
}
|
|
|
|
// ==========================================
|
|
// DOM ELEMENTS
|
|
// ==========================================
|
|
|
|
const cookieBanner = document.getElementById('cookieBanner');
|
|
const cookieModal = document.getElementById('cookieModal');
|
|
const acceptAllBtn = document.getElementById('cookieAcceptAll');
|
|
const rejectAllBtn = document.getElementById('cookieRejectAll');
|
|
const settingsBtn = document.getElementById('cookieSettings');
|
|
const modalCloseBtn = document.getElementById('cookieModalClose');
|
|
const savePreferencesBtn = document.getElementById('cookieSavePreferences');
|
|
|
|
// ==========================================
|
|
// CORE FUNCTIONS
|
|
// ==========================================
|
|
|
|
async function initCookieConsent() {
|
|
setDefaultConsent();
|
|
|
|
const savedConsent = await getConsentFromStorage();
|
|
|
|
if (savedConsent && savedConsent.hasConsented) {
|
|
consentState = {
|
|
hasConsented: true,
|
|
preferences: savedConsent.preferences || {}
|
|
};
|
|
previousPreferences = { ...consentState.preferences };
|
|
applyConsentPreferences();
|
|
return;
|
|
}
|
|
|
|
// Show banner after short delay
|
|
setTimeout(() => {
|
|
if (cookieBanner) cookieBanner.classList.add('show');
|
|
}, 500);
|
|
}
|
|
|
|
function applyConsentPreferences() {
|
|
pushConsentToDataLayer(consentState.preferences);
|
|
loadTrackingScripts();
|
|
window.dispatchEvent(new CustomEvent('cookieConsentUpdated', { detail: consentState.preferences }));
|
|
}
|
|
|
|
async function saveConsent(preferences) {
|
|
handlePreferenceChanges(preferences);
|
|
await saveConsentToStorage(preferences);
|
|
|
|
consentState = {
|
|
hasConsented: true,
|
|
preferences: preferences,
|
|
timestamp: new Date().toISOString()
|
|
};
|
|
|
|
applyConsentPreferences();
|
|
}
|
|
|
|
function hideBanner() {
|
|
if (cookieBanner) cookieBanner.classList.remove('show');
|
|
}
|
|
|
|
function showSettingsModal() {
|
|
if (cookieModal) {
|
|
cookieModal.classList.add('show');
|
|
populateSettingsModal();
|
|
}
|
|
}
|
|
|
|
function hideSettingsModal() {
|
|
if (cookieModal) cookieModal.classList.remove('show');
|
|
}
|
|
|
|
function populateSettingsModal() {
|
|
cookieCategories.forEach(key => {
|
|
const toggle = document.getElementById(`cookie-toggle-${key}`);
|
|
if (!toggle) return;
|
|
|
|
const isEnabled = consentState.preferences[key] || false;
|
|
const isRequired = key === 'necessary';
|
|
|
|
if (isRequired) {
|
|
toggle.classList.add('active', 'disabled');
|
|
toggle.setAttribute('aria-checked', 'true');
|
|
toggle.setAttribute('aria-disabled', 'true');
|
|
} else {
|
|
toggle.classList.toggle('active', isEnabled);
|
|
toggle.setAttribute('aria-checked', isEnabled ? 'true' : 'false');
|
|
toggle.onclick = () => {
|
|
toggle.classList.toggle('active');
|
|
toggle.setAttribute('aria-checked', toggle.classList.contains('active') ? 'true' : 'false');
|
|
};
|
|
}
|
|
});
|
|
}
|
|
|
|
function getModalPreferences() {
|
|
const preferences = {};
|
|
cookieCategories.forEach(key => {
|
|
const toggle = document.getElementById(`cookie-toggle-${key}`);
|
|
preferences[key] = key === 'necessary' ? true : (toggle ? toggle.classList.contains('active') : false);
|
|
});
|
|
return preferences;
|
|
}
|
|
|
|
// ==========================================
|
|
// EVENT LISTENERS
|
|
// ==========================================
|
|
|
|
if (acceptAllBtn) {
|
|
acceptAllBtn.addEventListener('click', () => {
|
|
const preferences = {};
|
|
cookieCategories.forEach(key => preferences[key] = true);
|
|
saveConsent(preferences);
|
|
hideBanner();
|
|
});
|
|
}
|
|
|
|
if (rejectAllBtn) {
|
|
rejectAllBtn.addEventListener('click', () => {
|
|
saveConsent({ necessary: true, analytics: false, marketing: false });
|
|
hideBanner();
|
|
});
|
|
}
|
|
|
|
if (settingsBtn) {
|
|
settingsBtn.addEventListener('click', showSettingsModal);
|
|
}
|
|
|
|
if (modalCloseBtn) {
|
|
modalCloseBtn.addEventListener('click', hideSettingsModal);
|
|
}
|
|
|
|
if (savePreferencesBtn) {
|
|
savePreferencesBtn.addEventListener('click', () => {
|
|
saveConsent(getModalPreferences());
|
|
hideSettingsModal();
|
|
hideBanner();
|
|
});
|
|
}
|
|
|
|
if (cookieModal) {
|
|
cookieModal.addEventListener('click', (e) => {
|
|
if (e.target === cookieModal) hideSettingsModal();
|
|
});
|
|
}
|
|
|
|
document.addEventListener('keydown', (e) => {
|
|
if (e.key === 'Escape' && cookieModal && cookieModal.classList.contains('show')) {
|
|
hideSettingsModal();
|
|
}
|
|
});
|
|
|
|
// ==========================================
|
|
// PUBLIC API
|
|
// ==========================================
|
|
|
|
window.CookieConsent = {
|
|
getConsent: () => consentState,
|
|
hasConsent: (category) => consentState.preferences[category] === true,
|
|
updateConsent: saveConsent,
|
|
showSettings: showSettingsModal
|
|
};
|
|
|
|
// ==========================================
|
|
// INITIALIZE
|
|
// ==========================================
|
|
|
|
initCookieConsent();
|
|
});
|