Compare commits

...

1 Commits

Author SHA1 Message Date
be9d00565d Advanced third party cookie manager 2025-04-09 01:25:08 +02:00
5 changed files with 811 additions and 39 deletions

View File

@ -0,0 +1,214 @@
<?php
/**
* GDPR Cookie Consent Module for PrestaShop
*
* @author Walzen665
* @copyright Copyright (c) 2025
* @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0)
*/
if (!defined('_PS_VERSION_')) {
exit;
}
/**
* Helper class for detecting third-party scripts and assigning them to cookie categories
*/
class GdprScriptDetector
{
// Common script signatures and their cookie categories
private static $scriptPatterns = [
'analytics' => [
'/google-analytics\.com/i',
'/googletagmanager\.com/i',
'/gtag/i',
'/analytics/i',
'/matomo/i',
'/piwik/i',
'/statcounter/i',
'/hotjar/i',
'/clarity\.ms/i',
'/stats/i'
],
'marketing' => [
'/facebook\.net/i',
'/connect\.facebook\.com/i',
'/fbevents\.js/i',
'/doubleclick\.net/i',
'/googlesyndication\.com/i',
'/google.*ads/i',
'/twitter\.com\/widgets/i',
'/platform\.twitter\.com/i',
'/pixel/i',
'/ads/i',
'/adservice/i',
'/criteo/i',
'/pinterest/i',
'/taboola/i',
'/outbrain/i'
],
'functional' => [
'/recaptcha/i',
'/hcaptcha/i',
'/fonts\.googleapis\.com/i',
'/cloudflare/i',
'/unpkg\.com/i',
'/cdn/i',
'/chat/i',
'/live.*/i',
'/support/i',
'/feedback/i',
'/maps\.google/i',
'/disqus/i',
'/zendesk/i',
'/intercom/i'
]
];
/**
* Check if a script URL or content matches any pattern
*
* @param string $scriptText The script URL or content to check
* @return string The cookie category ('necessary', 'functional', 'analytics', 'marketing')
*/
public static function detectCategory($scriptText)
{
foreach (self::$scriptPatterns as $category => $patterns) {
foreach ($patterns as $pattern) {
if (preg_match($pattern, $scriptText)) {
return $category;
}
}
}
return 'necessary'; // Default category if no match found
}
/**
* Get common third-party domains and their categories
*
* @return array Associative array of domains and their categories
*/
public static function getCommonThirdPartyDomains()
{
return [
// Analytics
'google-analytics.com' => 'analytics',
'googletagmanager.com' => 'analytics',
'analytics.google.com' => 'analytics',
'matomo.org' => 'analytics',
'matomo.cloud' => 'analytics',
'piwik.pro' => 'analytics',
'statcounter.com' => 'analytics',
'hotjar.com' => 'analytics',
'clarity.ms' => 'analytics',
'mixpanel.com' => 'analytics',
// Marketing
'facebook.com' => 'marketing',
'facebook.net' => 'marketing',
'fbcdn.net' => 'marketing',
'doubleclick.net' => 'marketing',
'googlesyndication.com' => 'marketing',
'adservice.google.com' => 'marketing',
'twitter.com' => 'marketing',
'linkedin.com' => 'marketing',
'pinterest.com' => 'marketing',
'ads-twitter.com' => 'marketing',
'criteo.com' => 'marketing',
'criteo.net' => 'marketing',
'taboola.com' => 'marketing',
'outbrain.com' => 'marketing',
// Functional
'google.com/recaptcha' => 'functional',
'gstatic.com/recaptcha' => 'functional',
'hcaptcha.com' => 'functional',
'fonts.googleapis.com' => 'functional',
'cloudflare.com' => 'functional',
'cdn.jsdelivr.net' => 'functional',
'unpkg.com' => 'functional',
'cdnjs.cloudflare.com' => 'functional',
'maps.google.com' => 'functional',
'maps.googleapis.com' => 'functional',
'disqus.com' => 'functional',
'zendesk.com' => 'functional',
'intercom.io' => 'functional'
];
}
/**
* Check if a domain matches any known third-party domain
*
* @param string $domain The domain to check
* @return string|false The cookie category or false if no match
*/
public static function matchDomain($domain)
{
$thirdPartyDomains = self::getCommonThirdPartyDomains();
foreach ($thirdPartyDomains as $thirdPartyDomain => $category) {
if (strpos($domain, $thirdPartyDomain) !== false) {
return $category;
}
}
return false;
}
/**
* Detect inline script content category
*
* @param string $content The script content to analyze
* @return string The cookie category
*/
public static function detectInlineScriptCategory($content)
{
// Common script signatures in inline scripts
$inlinePatterns = [
'analytics' => [
'/googleAnalytics/i',
'/gtag\s*\(/i',
'/_gaq\s*\./i',
'/ga\s*\(\s*[\'"]create/i',
'/analytics/i',
'/matomo/i',
'/piwik/i',
'/hotjar/i',
'/clarity/i'
],
'marketing' => [
'/fbq\s*\(/i',
'/FB\.init/i',
'/facebook-jssdk/i',
'/twttr\s*\./i',
'/twitter-widgets/i',
'/pintrk/i',
'/adsbygoogle/i',
'/googletag/i',
'/pixel/i',
'/track/i'
],
'functional' => [
'/grecaptcha/i',
'/hcaptcha/i',
'/maps\.googleapis/i',
'/gapi\.load/i',
'/disqus/i',
'/LiveChat/i',
'/intercom/i',
'/zendesk/i'
]
];
foreach ($inlinePatterns as $category => $patterns) {
foreach ($patterns as $pattern) {
if (preg_match($pattern, $content)) {
return $category;
}
}
}
return 'necessary';
}
}

View File

@ -18,7 +18,7 @@ class GdprCookieConsent extends Module
{
$this->name = 'gdprcookieconsent';
$this->tab = 'front_office_features';
$this->version = '1.0.0';
$this->version = '1.0.1';
$this->author = 'Walzen665';
$this->need_instance = 0;
$this->ps_versions_compliancy = [
@ -53,7 +53,11 @@ class GdprCookieConsent extends Module
Configuration::updateValue('GDPR_COOKIE_RETENTION_PERIOD', '365 days') &&
Configuration::updateValue('GDPR_COOKIE_THIRD_PARTIES', 'Google Analytics, Facebook, etc.') &&
Configuration::updateValue('GDPR_COOKIE_MANAGE_TEXT', 'Manage Cookie Preferences') &&
Configuration::updateValue('GDPR_COOKIE_ONLY_REQUIRED', 1);
Configuration::updateValue('GDPR_COOKIE_ONLY_REQUIRED', 1) &&
Configuration::updateValue('GDPR_COOKIE_NECESSARY_DESC', 'Necessary cookies help make a website usable by enabling basic functions like page navigation and access to secure areas. The website cannot function properly without these cookies.') &&
Configuration::updateValue('GDPR_COOKIE_FUNCTIONAL_DESC', 'Functional cookies enable a website to remember information that changes the way the website behaves or looks, like your preferred language or the region you are in.') &&
Configuration::updateValue('GDPR_COOKIE_ANALYTICS_DESC', 'Analytics cookies help website owners understand how visitors interact with websites by collecting and reporting information anonymously.') &&
Configuration::updateValue('GDPR_COOKIE_MARKETING_DESC', 'Marketing cookies are used to track visitors across websites. The intention is to display ads that are relevant and engaging for the individual user.');
}
@ -74,7 +78,11 @@ class GdprCookieConsent extends Module
Configuration::deleteByName('GDPR_COOKIE_RETENTION_PERIOD') &&
Configuration::deleteByName('GDPR_COOKIE_THIRD_PARTIES') &&
Configuration::deleteByName('GDPR_COOKIE_MANAGE_TEXT') &&
Configuration::deleteByName('GDPR_COOKIE_ONLY_REQUIRED');
Configuration::deleteByName('GDPR_COOKIE_ONLY_REQUIRED') &&
Configuration::deleteByName('GDPR_COOKIE_NECESSARY_DESC') &&
Configuration::deleteByName('GDPR_COOKIE_FUNCTIONAL_DESC') &&
Configuration::deleteByName('GDPR_COOKIE_ANALYTICS_DESC') &&
Configuration::deleteByName('GDPR_COOKIE_MARKETING_DESC');
}
/**
@ -99,6 +107,10 @@ class GdprCookieConsent extends Module
$thirdParties = Tools::getValue('GDPR_COOKIE_THIRD_PARTIES');
$manageText = Tools::getValue('GDPR_COOKIE_MANAGE_TEXT');
$onlyRequired = Tools::getValue('GDPR_COOKIE_ONLY_REQUIRED');
$necessaryDesc = Tools::getValue('GDPR_COOKIE_NECESSARY_DESC');
$functionalDesc = Tools::getValue('GDPR_COOKIE_FUNCTIONAL_DESC');
$analyticsDesc = Tools::getValue('GDPR_COOKIE_ANALYTICS_DESC');
$marketingDesc = Tools::getValue('GDPR_COOKIE_MARKETING_DESC');
// Update configuration values
Configuration::updateValue('GDPR_COOKIE_ENABLED', $enabled);
@ -113,6 +125,10 @@ class GdprCookieConsent extends Module
Configuration::updateValue('GDPR_COOKIE_THIRD_PARTIES', $thirdParties);
Configuration::updateValue('GDPR_COOKIE_MANAGE_TEXT', $manageText);
Configuration::updateValue('GDPR_COOKIE_ONLY_REQUIRED', $onlyRequired);
Configuration::updateValue('GDPR_COOKIE_NECESSARY_DESC', $necessaryDesc);
Configuration::updateValue('GDPR_COOKIE_FUNCTIONAL_DESC', $functionalDesc);
Configuration::updateValue('GDPR_COOKIE_ANALYTICS_DESC', $analyticsDesc);
Configuration::updateValue('GDPR_COOKIE_MARKETING_DESC', $marketingDesc);
// Display confirmation
$output .= $this->displayConfirmation($this->l('Settings updated'));
@ -238,6 +254,34 @@ class GdprCookieConsent extends Module
]
],
],
[
'type' => 'textarea',
'label' => $this->l('Necessary Cookies Description'),
'name' => 'GDPR_COOKIE_NECESSARY_DESC',
'desc' => $this->l('Description for necessary cookies shown in the modal'),
'required' => true,
],
[
'type' => 'textarea',
'label' => $this->l('Functional Cookies Description'),
'name' => 'GDPR_COOKIE_FUNCTIONAL_DESC',
'desc' => $this->l('Description for functional cookies shown in the modal'),
'required' => true,
],
[
'type' => 'textarea',
'label' => $this->l('Analytics Cookies Description'),
'name' => 'GDPR_COOKIE_ANALYTICS_DESC',
'desc' => $this->l('Description for analytics cookies shown in the modal'),
'required' => true,
],
[
'type' => 'textarea',
'label' => $this->l('Marketing Cookies Description'),
'name' => 'GDPR_COOKIE_MARKETING_DESC',
'desc' => $this->l('Description for marketing cookies shown in the modal'),
'required' => true,
],
],
'submit' => [
'title' => $this->l('Save'),
@ -277,6 +321,10 @@ class GdprCookieConsent extends Module
$helper->fields_value['GDPR_COOKIE_THIRD_PARTIES'] = Configuration::get('GDPR_COOKIE_THIRD_PARTIES');
$helper->fields_value['GDPR_COOKIE_MANAGE_TEXT'] = Configuration::get('GDPR_COOKIE_MANAGE_TEXT');
$helper->fields_value['GDPR_COOKIE_ONLY_REQUIRED'] = Configuration::get('GDPR_COOKIE_ONLY_REQUIRED');
$helper->fields_value['GDPR_COOKIE_NECESSARY_DESC'] = Configuration::get('GDPR_COOKIE_NECESSARY_DESC');
$helper->fields_value['GDPR_COOKIE_FUNCTIONAL_DESC'] = Configuration::get('GDPR_COOKIE_FUNCTIONAL_DESC');
$helper->fields_value['GDPR_COOKIE_ANALYTICS_DESC'] = Configuration::get('GDPR_COOKIE_ANALYTICS_DESC');
$helper->fields_value['GDPR_COOKIE_MARKETING_DESC'] = Configuration::get('GDPR_COOKIE_MARKETING_DESC');
return $helper->generateForm([$form]);
}
@ -315,7 +363,12 @@ class GdprCookieConsent extends Module
$this->context->smarty->assign([
'gdprCookieManageText' => Configuration::get('GDPR_COOKIE_MANAGE_TEXT', 'Manage Cookies'),
]);
// If not in required-only mode, add script for tagging known third-party scripts
if (!Configuration::get('GDPR_COOKIE_ONLY_REQUIRED')) {
$this->tagThirdPartyScripts();
}
// Return the template content - but don't throw an error if it doesn't exist yet
if (file_exists(_PS_MODULE_DIR_ . $this->name . '/views/templates/hook/manage_button.tpl')) {
return $this->display(__FILE__, 'views/templates/hook/manage_button.tpl');
@ -343,8 +396,80 @@ class GdprCookieConsent extends Module
'gdprCookieThirdParties' => Configuration::get('GDPR_COOKIE_THIRD_PARTIES'),
'gdprCookieManageText' => Configuration::get('GDPR_COOKIE_MANAGE_TEXT'),
'gdprCookieOnlyRequired' => Configuration::get('GDPR_COOKIE_ONLY_REQUIRED'),
'gdprCookieNecessaryDesc' => Configuration::get('GDPR_COOKIE_NECESSARY_DESC'),
'gdprCookieFunctionalDesc' => Configuration::get('GDPR_COOKIE_FUNCTIONAL_DESC'),
'gdprCookieAnalyticsDesc' => Configuration::get('GDPR_COOKIE_ANALYTICS_DESC'),
'gdprCookieMarketingDesc' => Configuration::get('GDPR_COOKIE_MARKETING_DESC'),
]);
return $this->display(__FILE__, 'views/templates/hook/footer.tpl');
}
/**
* Helper function to tag third-party scripts with appropriate consent categories
*/
protected function tagThirdPartyScripts()
{
// Require the script detector class
require_once(dirname(__FILE__).'/classes/GdprScriptDetector.php');
// Get PrestaShop's currently registered JS files
$jsFiles = $this->context->controller->js_files;
// Loop through JS files and add consent attributes based on patterns
foreach ($jsFiles as $key => $jsFile) {
// Use the detector class
$category = GdprScriptDetector::detectCategory($jsFile);
// Add attribute to the script
$this->context->smarty->assign([
'js_' . md5($jsFile) . '_attributes' => 'data-cookieconsent="' . $category . '"'
]);
}
// Inject a helper script to tag inline scripts as well
$this->context->controller->registerJavascript(
'gdpr-cookie-helper',
$this->_path . 'views/js/gdpr_cookie_helper.js',
['position' => 'head', 'priority' => 1]
);
}
/**
* Modifies scripts in the HTML head
*/
public function hookActionHtmlHeadFooter($params)
{
if (!Configuration::get('GDPR_COOKIE_ENABLED') || Configuration::get('GDPR_COOKIE_ONLY_REQUIRED')) {
return;
}
// Get the current HTML content
$html = $params['html_content'];
// Modify script tags to add data-cookieconsent attribute
$patterns = [
// Google Analytics pattern
'/<script([^>]*)(gtag|googletagmanager|google-analytics)([^>]*)>/' => '<script$1$2$3 data-cookieconsent="analytics">',
// Facebook Pixel pattern
'/<script([^>]*)(connect\.facebook\.net|fbevents\.js)([^>]*)>/' => '<script$1$2$3 data-cookieconsent="marketing">',
// Generic analytics patterns
'/<script([^>]*)(analytics|piwik|matomo|stats)([^>]*)>/' => '<script$1$2$3 data-cookieconsent="analytics">',
// Marketing patterns
'/<script([^>]*)(ads|adsbygoogle|doubleclick|googlesyndication)([^>]*)>/' => '<script$1$2$3 data-cookieconsent="marketing">',
// Functional patterns (more conservative, as these might be necessary)
'/<script([^>]*)(recaptcha|chat)([^>]*)>/' => '<script$1$2$3 data-cookieconsent="functional">',
];
foreach ($patterns as $pattern => $replacement) {
$html = preg_replace($pattern, $replacement, $html);
}
// Update the HTML content
$params['html_content'] = $html;
}
}

View File

@ -7,6 +7,13 @@
*/
document.addEventListener('DOMContentLoaded', function() {
// Store for blocked scripts
const scriptStore = {
functional: [],
analytics: [],
marketing: []
};
// Cookie functions
function setCookie(name, value, days) {
var expires = '';
@ -39,14 +46,166 @@ document.addEventListener('DOMContentLoaded', function() {
// Hide banner and modal
hideModal();
document.getElementById('gdpr-cookie-banner').style.display = 'none';
// Show manage button
showManageButton();
}
// Block scripts based on type
function blockScripts() {
// Find and process script tags with data-cookieconsent attribute
const scripts = document.querySelectorAll('script[data-cookieconsent]');
scripts.forEach(script => {
const consentType = script.getAttribute('data-cookieconsent');
if (!consentType || consentType === 'necessary') {
// Necessary scripts always run
return;
}
// Store script information
const scriptData = {
src: script.getAttribute('src'),
content: script.innerHTML,
type: script.getAttribute('type') || 'text/javascript',
async: script.async,
defer: script.defer
};
// Add to appropriate store
if (scriptStore[consentType]) {
scriptStore[consentType].push(scriptData);
}
// Remove the script from DOM
script.parentNode.removeChild(script);
});
// Also block iframe embeds with data-cookieconsent attribute
const iframes = document.querySelectorAll('iframe[data-cookieconsent]');
iframes.forEach(iframe => {
const consentType = iframe.getAttribute('data-cookieconsent');
if (!consentType || consentType === 'necessary') {
// Necessary iframes always load
return;
}
// Create placeholder
const placeholder = document.createElement('div');
placeholder.className = 'gdpr-blocked-content-placeholder';
placeholder.setAttribute('data-cookieconsent', consentType);
placeholder.setAttribute('data-src', iframe.getAttribute('src'));
placeholder.style.width = iframe.width + 'px' || '100%';
placeholder.style.height = iframe.height + 'px' || '150px';
placeholder.style.border = '1px dashed #ccc';
placeholder.style.display = 'flex';
placeholder.style.alignItems = 'center';
placeholder.style.justifyContent = 'center';
placeholder.style.backgroundColor = '#f9f9f9';
placeholder.style.color = '#666';
placeholder.innerHTML = `<div>
<p>Content blocked due to ${consentType} cookies preferences</p>
<button class="gdpr-load-blocked-content" data-type="${consentType}">Load content</button>
</div>`;
// Replace iframe with placeholder
iframe.parentNode.replaceChild(placeholder, iframe);
});
// Add event listeners to load content buttons
document.querySelectorAll('.gdpr-load-blocked-content').forEach(button => {
button.addEventListener('click', function(e) {
const type = this.getAttribute('data-type');
const placeholder = this.closest('.gdpr-blocked-content-placeholder');
if (placeholder) {
// Get iframe src
const src = placeholder.getAttribute('data-src');
// Create iframe
const iframe = document.createElement('iframe');
iframe.src = src;
iframe.width = placeholder.style.width;
iframe.height = placeholder.style.height;
iframe.frameBorder = '0';
// Replace placeholder with iframe
placeholder.parentNode.replaceChild(iframe, placeholder);
// Update consent for this content type
const preferences = JSON.parse(getCookie('gdpr_cookie_consent') || '{"necessary":true}');
preferences[type] = true;
setCookie('gdpr_cookie_consent', JSON.stringify(preferences), 365);
// Update checkboxes in modal if it exists
const checkbox = document.querySelector(`#gdpr-cookie-${type}`);
if (checkbox) {
checkbox.checked = true;
}
}
});
});
}
// Load scripts based on consent
function loadConsentedScripts(preferences) {
// For each script type
Object.keys(scriptStore).forEach(type => {
if (preferences[type]) {
// Load all scripts of this type
scriptStore[type].forEach(scriptData => {
const script = document.createElement('script');
if (scriptData.src) {
script.src = scriptData.src;
}
script.type = scriptData.type;
script.async = scriptData.async;
script.defer = scriptData.defer;
if (scriptData.content) {
script.innerHTML = scriptData.content;
}
document.head.appendChild(script);
});
// Clear the store for this type
scriptStore[type] = [];
}
});
// Also load blocked iframes
document.querySelectorAll('.gdpr-blocked-content-placeholder').forEach(placeholder => {
const type = placeholder.getAttribute('data-cookieconsent');
if (preferences[type]) {
// Get iframe src
const src = placeholder.getAttribute('data-src');
// Create iframe
const iframe = document.createElement('iframe');
iframe.src = src;
iframe.width = placeholder.style.width;
iframe.height = placeholder.style.height;
iframe.frameBorder = '0';
// Replace placeholder with iframe
placeholder.parentNode.replaceChild(iframe, placeholder);
}
});
}
// Check if cookie consent is already set
var cookieConsent = getCookie('gdpr_cookie_consent');
// Always block non-necessary scripts first
blockScripts();
if (!cookieConsent) {
// Show the cookie banner if consent is not set
document.getElementById('gdpr-cookie-banner').style.display = 'block';
@ -54,7 +213,7 @@ document.addEventListener('DOMContentLoaded', function() {
// Apply cookie preferences
var consentPreferences = JSON.parse(cookieConsent);
applyConsentPreferences(consentPreferences);
// Show the manage button since consent was already given
showManageButton();
}
@ -98,12 +257,12 @@ document.addEventListener('DOMContentLoaded', function() {
function showModal() {
document.getElementById('gdpr-cookie-banner').style.display = 'none';
document.getElementById('gdpr-cookie-modal').style.display = 'block';
// Load saved preferences if they exist
var cookieConsent = getCookie('gdpr_cookie_consent');
if (cookieConsent) {
var preferences = JSON.parse(cookieConsent);
// Set checkboxes based on saved preferences
document.querySelectorAll('.gdpr-cookie-checkbox').forEach(function(checkbox) {
var category = checkbox.getAttribute('data-cookie-category');
@ -127,7 +286,7 @@ document.addEventListener('DOMContentLoaded', function() {
analytics: true,
marketing: true
};
setCookie('gdpr_cookie_consent', JSON.stringify(preferences), 365);
applyConsentPreferences(preferences);
afterConsent(); // Use the new function for consistent post-consent behavior
@ -140,7 +299,7 @@ document.addEventListener('DOMContentLoaded', function() {
analytics: false,
marketing: false
};
setCookie('gdpr_cookie_consent', JSON.stringify(preferences), 365);
applyConsentPreferences(preferences);
afterConsent(); // Use the new function for consistent post-consent behavior
@ -150,28 +309,28 @@ document.addEventListener('DOMContentLoaded', function() {
var preferences = {
necessary: true // Necessary cookies are always accepted
};
// Get selected preferences
document.querySelectorAll('.gdpr-cookie-checkbox').forEach(function(checkbox) {
var category = checkbox.getAttribute('data-cookie-category');
preferences[category] = checkbox.checked;
});
setCookie('gdpr_cookie_consent', JSON.stringify(preferences), 365);
applyConsentPreferences(preferences);
afterConsent(); // Use the new function for consistent post-consent behavior
}
function applyConsentPreferences(preferences) {
// Example implementation
// You would need to adapt this based on your specific cookie usage
// Load consented scripts
loadConsentedScripts(preferences);
// Functional cookies
if (!preferences.functional) {
// Disable functional cookies
removeFunctionalCookies();
}
// Analytics cookies
if (!preferences.analytics) {
// Disable analytics cookies (like Google Analytics)
@ -180,7 +339,7 @@ document.addEventListener('DOMContentLoaded', function() {
// Enable analytics
enableAnalytics();
}
// Marketing cookies
if (!preferences.marketing) {
// Disable marketing cookies
@ -194,32 +353,43 @@ document.addEventListener('DOMContentLoaded', function() {
// Helper functions to implement consent preferences
function removeFunctionalCookies() {
// This is just an example - implement based on your specific needs
var functionalCookies = ['prefs', 'language', 'theme'];
var functionalCookies = ['prefs', 'language', 'theme', 'user_preferences'];
var domains = [window.location.hostname, '.' + window.location.hostname];
functionalCookies.forEach(function(cookie) {
document.cookie = cookie + '=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;';
domains.forEach(function(domain) {
document.cookie = cookie + '=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT; Domain=' + domain;
});
});
}
function disableAnalytics() {
// Example: Disable Google Analytics
// Google Analytics
window['ga-disable-UA-XXXXXXXX-X'] = true;
window['ga-disable-G-XXXXXXXX'] = true;
// Remove existing GA cookies
document.cookie = '_ga=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT; Domain=.' + window.location.hostname;
document.cookie = '_gid=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT; Domain=.' + window.location.hostname;
document.cookie = '_gat=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT; Domain=.' + window.location.hostname;
var analyticsCookies = ['_ga', '_gid', '_gat', '__utma', '__utmb', '__utmc', '__utmt', '__utmz', '_hjid', '_hjAbsoluteSessionInProgress'];
var domains = [window.location.hostname, '.' + window.location.hostname];
analyticsCookies.forEach(function(cookie) {
domains.forEach(function(domain) {
document.cookie = cookie + '=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT; Domain=' + domain;
});
});
}
function enableAnalytics() {
// Example: Enable Google Analytics
// Google Analytics
window['ga-disable-UA-XXXXXXXX-X'] = false;
window['ga-disable-G-XXXXXXXX'] = false;
}
function disableMarketing() {
// Example implementation for common marketing cookies
var marketingCookies = ['_fbp', 'fr', 'IDE', 'MUID', 'personalization_id'];
var domains = [window.location.hostname, '.' + window.location.hostname];
// Common marketing cookies
var marketingCookies = ['_fbp', 'fr', 'IDE', 'MUID', 'personalization_id', 'VISITOR_INFO1_LIVE', 'YSC', 'NID'];
var domains = [window.location.hostname, '.' + window.location.hostname, '.google.com', '.facebook.com', '.youtube.com'];
marketingCookies.forEach(function(cookie) {
domains.forEach(function(domain) {
document.cookie = cookie + '=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT; Domain=' + domain;
@ -228,8 +398,60 @@ document.addEventListener('DOMContentLoaded', function() {
}
function enableMarketing() {
// For enabling marketing, we typically don't need to do anything special
// The marketing scripts will set their cookies when they load
// Just ensure the marketing scripts are loaded after checking consent
// For enabling marketing, we load the scripts that were blocked
// This happens in the loadConsentedScripts function
}
// Function to detect which script is associated with each cookie category
function detectThirdPartyScripts() {
// This function would scan the page for known third-party scripts
// and auto-assign data-cookieconsent attributes
const knownScriptPatterns = {
analytics: [
/google-analytics\.com\/analytics\.js/i,
/googletagmanager\.com\/gtag/i,
/google-analytics\.com\/ga\.js/i,
/hotjar\.com/i,
/analytics/i,
/matomo/i,
/stats/i
],
marketing: [
/facebook\.net/i,
/doubleclick\.net/i,
/googlesyndication\.com/i,
/ads/i,
/adservices/i,
/pixel/i,
/track/i
],
functional: [
/recaptcha/i,
/fonts\.googleapis\.com/i,
/cloudflare/i,
/cdn/i,
/livechat/i,
/chat/i,
/support/i
]
};
const scripts = document.querySelectorAll('script:not([data-cookieconsent])');
scripts.forEach(script => {
if (!script.src) return; // Skip inline scripts
for (const [category, patterns] of Object.entries(knownScriptPatterns)) {
for (const pattern of patterns) {
if (pattern.test(script.src)) {
script.setAttribute('data-cookieconsent', category);
break;
}
}
}
});
}
// Run script detection
detectThirdPartyScripts();
});

View File

@ -0,0 +1,211 @@
/**
* GDPR Cookie Consent Helper for PrestaShop
*
* @author Walzen665
* @copyright Copyright (c) 2025
* @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0)
*/
// This script runs early in the page load to tag inline scripts with appropriate consent categories
(function() {
// Known script patterns to identify analytics, marketing and functional scripts
const scriptPatterns = {
analytics: [
/google[\s\-_]?analytics/i,
/ga\s*\(\s*['"]create/i,
/googletagmanager/i,
/gtag/i,
/\_gaq/i,
/matomo/i,
/piwik/i,
/mixpanel/i,
/hotjar/i,
/clarity/i
],
marketing: [
/facebook[\s\-_]?pixel/i,
/fbq\s*\(\s*['"]init/i,
/doubleclick/i,
/adwords/i,
/google[\s\-_]?ad[\s\-_]?services/i,
/google[\s\-_]?tag[\s\-_]?manager/i,
/gtm/i,
/twitter[\s\-_]?pixel/i,
/pinterest[\s\-_]?tag/i
],
functional: [
/recaptcha/i,
/chat/i,
/livechat/i,
/support/i,
/feedback/i,
/preference/i,
/usercentrics/i
]
};
// Function to process and tag a script element
function processScript(script) {
// Skip if it already has a consent attribute
if (script.hasAttribute('data-cookieconsent')) {
return;
}
// Skip our own scripts
if (script.src && script.src.indexOf('gdpr_cookie') !== -1) {
script.setAttribute('data-cookieconsent', 'necessary');
return;
}
// Check content for patterns
const content = script.innerHTML || '';
const src = script.src || '';
// Determine script category
let category = 'necessary'; // Default category
for (const [cat, patterns] of Object.entries(scriptPatterns)) {
for (const pattern of patterns) {
if (pattern.test(content) || pattern.test(src)) {
category = cat;
break;
}
}
if (category !== 'necessary') {
break;
}
}
// Tag the script
script.setAttribute('data-cookieconsent', category);
// For non-necessary scripts, we'll create a duplicate with the correct attribute
// but disabled until consent is given
if (category !== 'necessary') {
const originalScript = script;
const newScript = document.createElement('script');
// Copy attributes
Array.from(originalScript.attributes).forEach(attr => {
if (attr.name !== 'data-cookieconsent') {
newScript.setAttribute(attr.name, attr.value);
}
});
// Set consent attribute
newScript.setAttribute('data-cookieconsent', category);
// Copy content if it's an inline script
if (!originalScript.src && originalScript.innerHTML) {
newScript.innerHTML = originalScript.innerHTML;
}
// Replace the original script
originalScript.parentNode.replaceChild(newScript, originalScript);
// Prevent execution by removing src and content
newScript.removeAttribute('src');
newScript.innerHTML = '';
}
}
// Process existing scripts
document.querySelectorAll('script').forEach(processScript);
// Use a MutationObserver to catch dynamically added scripts
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
mutation.addedNodes.forEach(node => {
if (node.tagName === 'SCRIPT') {
processScript(node);
} else if (node.querySelectorAll) {
node.querySelectorAll('script').forEach(processScript);
}
});
});
});
// Start observing the document
observer.observe(document.documentElement, {
childList: true,
subtree: true
});
// Also process iframes
function processIframe(iframe) {
// Skip if it already has a consent attribute
if (iframe.hasAttribute('data-cookieconsent')) {
return;
}
const src = iframe.src || '';
// Common third-party iframe sources
const iframePatterns = {
marketing: [
/youtube/i,
/vimeo/i,
/facebook\.com\/plugins/i,
/twitter\.com\/widgets/i,
/instagram\.com/i,
/pinterest\.com/i,
/doubleclick/i,
/ads/i
],
analytics: [
/googletagmanager/i,
/analytics/i
],
functional: [
/recaptcha/i,
/maps\.google/i,
/google\.com\/maps/i,
/disqus/i,
/livechat/i,
/chat/i
]
};
// Determine iframe category
let category = 'necessary'; // Default category
for (const [cat, patterns] of Object.entries(iframePatterns)) {
for (const pattern of patterns) {
if (pattern.test(src)) {
category = cat;
break;
}
}
if (category !== 'necessary') {
break;
}
}
// Tag the iframe
iframe.setAttribute('data-cookieconsent', category);
}
// Process existing iframes
document.querySelectorAll('iframe').forEach(processIframe);
// Use the observer to catch dynamically added iframes
const iframeObserver = new MutationObserver(mutations => {
mutations.forEach(mutation => {
mutation.addedNodes.forEach(node => {
if (node.tagName === 'IFRAME') {
processIframe(node);
} else if (node.querySelectorAll) {
node.querySelectorAll('iframe').forEach(processIframe);
}
});
});
});
// Start observing for iframes
iframeObserver.observe(document.documentElement, {
childList: true,
subtree: true
});
})();

View File

@ -13,35 +13,35 @@
</div>
<div class="gdpr-cookie-modal-body">
<p>{$gdprCookieMessage}</p>
<div class="gdpr-cookie-info-block">
<p><strong>{l s='Data Controller:' mod='gdprcookieconsent'}</strong> {$gdprCookieDataController}</p>
<p><strong>{l s='Cookie Retention Period:' mod='gdprcookieconsent'}</strong> {$gdprCookieRetentionPeriod}</p>
<p><strong>{l s='Third-Party Recipients:' mod='gdprcookieconsent'}</strong> {$gdprCookieThirdParties}</p>
<p><strong>{l s='Your Rights:' mod='gdprcookieconsent'}</strong> {l s='You can withdraw your consent at any time by clicking the "Manage Cookie Preferences" button or by deleting cookies in your browser settings.' mod='gdprcookieconsent'}</p>
</div>
<div class="gdpr-cookie-categories">
<div class="gdpr-cookie-category">
<input type="checkbox" id="gdpr-cookie-necessary" checked disabled>
<label for="gdpr-cookie-necessary">{l s='Necessary' mod='gdprcookieconsent'}</label>
<p>{l s='Necessary cookies help make a website usable by enabling basic functions like page navigation. The website cannot function properly without these cookies.' mod='gdprcookieconsent'}</p>
<p>{$gdprCookieNecessaryDesc}</p>
</div>
{if !$gdprCookieOnlyRequired}
<div class="gdpr-cookie-category">
<input type="checkbox" id="gdpr-cookie-functional" class="gdpr-cookie-checkbox" data-cookie-category="functional">
<label for="gdpr-cookie-functional">{l s='Functional' mod='gdprcookieconsent'}</label>
<p>{l s='Functional cookies enable a website to remember information that changes the way the website behaves or looks, like your preferred language or the region you are in.' mod='gdprcookieconsent'}</p>
<p>{$gdprCookieFunctionalDesc}</p>
</div>
<div class="gdpr-cookie-category">
<input type="checkbox" id="gdpr-cookie-analytics" class="gdpr-cookie-checkbox" data-cookie-category="analytics">
<label for="gdpr-cookie-analytics">{l s='Analytics' mod='gdprcookieconsent'}</label>
<p>{l s='Analytics cookies help website owners understand how visitors interact with websites by collecting and reporting information anonymously.' mod='gdprcookieconsent'}</p>
<p>{$gdprCookieAnalyticsDesc}</p>
</div>
<div class="gdpr-cookie-category">
<input type="checkbox" id="gdpr-cookie-marketing" class="gdpr-cookie-checkbox" data-cookie-category="marketing">
<label for="gdpr-cookie-marketing">{l s='Marketing' mod='gdprcookieconsent'}</label>
<p>{l s='Marketing cookies are used to track visitors across websites. The intention is to display ads that are relevant and engaging for the individual user.' mod='gdprcookieconsent'}</p>
<p>{$gdprCookieMarketingDesc}</p>
</div>
{/if}
</div>