Navigate privacy laws with feline precision — detect every boundary, respect every territory! GDPR compliance and privacy protection for WordPress. - Cookie consent management - Privacy boundary detection - GDPR-compliant analytics gating - Cross-plugin consent coordination (integrates with TigerStyle Heat) - Visitor preference tracking - Configurable cookie categories Includes build.sh and .distignore for WordPress-installable release ZIPs.
503 lines
19 KiB
JavaScript
503 lines
19 KiB
JavaScript
/**
|
|
* TigerStyle Whiskers - Cookie Consent JavaScript
|
|
* Feline finesse in consent management
|
|
*/
|
|
|
|
(function($) {
|
|
'use strict';
|
|
|
|
// Main consent management object
|
|
window.tigerstyleWhiskersConsent = {
|
|
|
|
// Configuration
|
|
config: {
|
|
cookieName: 'tigerstyle_whiskers_consent',
|
|
cookieExpiry: 365, // days
|
|
bannerSelector: '#tigerstyle-whiskers-consent-overlay',
|
|
categoriesSelector: '#tw-consent-categories'
|
|
},
|
|
|
|
// Current consent state
|
|
consentState: {
|
|
necessary: true,
|
|
analytics: false,
|
|
marketing: false,
|
|
preferences: false,
|
|
timestamp: null,
|
|
version: null
|
|
},
|
|
|
|
// Initialize consent management
|
|
init: function() {
|
|
this.loadCurrentConsent();
|
|
this.bindEvents();
|
|
this.detectBoundaries();
|
|
|
|
// Update client-side boundary detection
|
|
if (typeof tigerstyleWhiskers !== 'undefined') {
|
|
this.updateBoundaryDetection();
|
|
}
|
|
|
|
console.log('🐱 TigerStyle Whiskers: Consent management initialized with feline precision!');
|
|
},
|
|
|
|
// Load current consent from cookie
|
|
loadCurrentConsent: function() {
|
|
const consentCookie = this.getCookie(this.config.cookieName);
|
|
if (consentCookie) {
|
|
try {
|
|
this.consentState = Object.assign(this.consentState, JSON.parse(consentCookie));
|
|
} catch (e) {
|
|
console.warn('TigerStyle Whiskers: Invalid consent cookie, resetting...');
|
|
this.clearConsent();
|
|
}
|
|
}
|
|
},
|
|
|
|
// Bind event handlers
|
|
bindEvents: function() {
|
|
const self = this;
|
|
|
|
// Category toggle handlers
|
|
$(document).on('change', '.tw-consent-category input[type="checkbox"]', function() {
|
|
const category = $(this).attr('name').replace('consent_', '');
|
|
const isChecked = $(this).is(':checked');
|
|
|
|
// Animate the change
|
|
self.animateToggle($(this).closest('.tw-consent-category'), isChecked);
|
|
});
|
|
|
|
// Keyboard navigation
|
|
$(document).on('keydown', this.config.bannerSelector, function(e) {
|
|
if (e.key === 'Escape') {
|
|
self.acceptNecessary();
|
|
}
|
|
});
|
|
|
|
// Focus management for accessibility
|
|
$(document).on('focus', '.tw-consent-switch input', function() {
|
|
$(this).closest('.tw-consent-category').addClass('tw-focused');
|
|
}).on('blur', '.tw-consent-switch input', function() {
|
|
$(this).closest('.tw-consent-category').removeClass('tw-focused');
|
|
});
|
|
},
|
|
|
|
// Detect client-side boundaries
|
|
detectBoundaries: function() {
|
|
const boundaries = {
|
|
technical: {
|
|
cookies_enabled: navigator.cookieEnabled,
|
|
javascript_enabled: true,
|
|
local_storage_available: typeof(Storage) !== "undefined",
|
|
session_storage_available: typeof(sessionStorage) !== "undefined",
|
|
do_not_track: navigator.doNotTrack === "1" || window.doNotTrack === "1",
|
|
screen_width: screen.width,
|
|
screen_height: screen.height,
|
|
color_depth: screen.colorDepth,
|
|
timezone: this.getTimezone(),
|
|
connection_type: this.getConnectionType(),
|
|
device_memory: navigator.deviceMemory || 'unknown',
|
|
hardware_concurrency: navigator.hardwareConcurrency || 'unknown'
|
|
},
|
|
user_preferences: {
|
|
language: navigator.language,
|
|
languages: navigator.languages || [],
|
|
prefers_reduced_motion: window.matchMedia('(prefers-reduced-motion: reduce)').matches,
|
|
prefers_dark_mode: window.matchMedia('(prefers-color-scheme: dark)').matches,
|
|
prefers_high_contrast: window.matchMedia('(prefers-contrast: high)').matches
|
|
},
|
|
privacy_signals: {
|
|
global_privacy_control: navigator.globalPrivacyControl || false,
|
|
do_not_track: navigator.doNotTrack === "1",
|
|
ad_blocker_detected: this.detectAdBlocker()
|
|
}
|
|
};
|
|
|
|
// Store enhanced boundaries
|
|
if (boundaries.technical.local_storage_available) {
|
|
localStorage.setItem('tigerstyle_whiskers_client_boundaries', JSON.stringify(boundaries));
|
|
}
|
|
|
|
return boundaries;
|
|
},
|
|
|
|
// Update boundary detection with server
|
|
updateBoundaryDetection: function() {
|
|
const boundaries = this.detectBoundaries();
|
|
|
|
// Send to server via AJAX
|
|
$.ajax({
|
|
url: tigerstyleWhiskersConsent.ajaxurl,
|
|
type: 'POST',
|
|
data: {
|
|
action: 'tigerstyle_whiskers_update_boundaries',
|
|
nonce: tigerstyleWhiskersConsent.nonce,
|
|
boundaries: JSON.stringify(boundaries)
|
|
},
|
|
success: function(response) {
|
|
console.log('🐱 TigerStyle Whiskers: Boundary detection updated');
|
|
}
|
|
});
|
|
},
|
|
|
|
// Get timezone
|
|
getTimezone: function() {
|
|
try {
|
|
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
} catch (e) {
|
|
return 'unknown';
|
|
}
|
|
},
|
|
|
|
// Get connection type
|
|
getConnectionType: function() {
|
|
if (navigator.connection) {
|
|
return {
|
|
effective_type: navigator.connection.effectiveType,
|
|
downlink: navigator.connection.downlink,
|
|
rtt: navigator.connection.rtt,
|
|
save_data: navigator.connection.saveData
|
|
};
|
|
}
|
|
return 'unknown';
|
|
},
|
|
|
|
// Detect ad blocker
|
|
detectAdBlocker: function() {
|
|
// Simple ad blocker detection
|
|
const adBlockTest = document.createElement('div');
|
|
adBlockTest.innerHTML = ' ';
|
|
adBlockTest.className = 'adsbox';
|
|
adBlockTest.style.position = 'absolute';
|
|
adBlockTest.style.left = '-999px';
|
|
document.body.appendChild(adBlockTest);
|
|
|
|
const isBlocked = adBlockTest.offsetHeight === 0;
|
|
document.body.removeChild(adBlockTest);
|
|
|
|
return isBlocked;
|
|
},
|
|
|
|
// Show consent banner
|
|
showBanner: function() {
|
|
const $banner = $(this.config.bannerSelector);
|
|
|
|
// Set focus for accessibility
|
|
$banner.attr('role', 'dialog');
|
|
$banner.attr('aria-labelledby', 'tw-consent-title');
|
|
$banner.attr('aria-describedby', 'tw-consent-message');
|
|
|
|
$banner.fadeIn(300, function() {
|
|
// Focus first interactive element
|
|
$banner.find('.tw-btn').first().focus();
|
|
});
|
|
|
|
// Track banner shown event
|
|
this.trackEvent('consent_banner_shown');
|
|
},
|
|
|
|
// Hide consent banner
|
|
hideBanner: function() {
|
|
$(this.config.bannerSelector).fadeOut(300);
|
|
this.trackEvent('consent_banner_hidden');
|
|
},
|
|
|
|
// Show category customization
|
|
showCategories: function() {
|
|
const $categories = $(this.config.categoriesSelector);
|
|
|
|
if ($categories.is(':visible')) {
|
|
$categories.slideUp(300);
|
|
$('.tw-btn-customize').text(tigerstyleWhiskersConsent.strings.customize);
|
|
} else {
|
|
$categories.slideDown(300);
|
|
$('.tw-btn-customize').text(tigerstyleWhiskersConsent.strings.save_preferences);
|
|
$('.tw-btn-customize').off('click').on('click', () => this.saveCustomPreferences());
|
|
|
|
// Set current values
|
|
this.setCurrentValues();
|
|
}
|
|
|
|
this.trackEvent('consent_categories_toggled');
|
|
},
|
|
|
|
// Set current values in form
|
|
setCurrentValues: function() {
|
|
const self = this;
|
|
Object.keys(this.consentState).forEach(function(category) {
|
|
if (typeof self.consentState[category] === 'boolean') {
|
|
const $checkbox = $(`#consent_${category}`);
|
|
$checkbox.prop('checked', self.consentState[category]);
|
|
}
|
|
});
|
|
},
|
|
|
|
// Accept all cookies
|
|
acceptAll: function() {
|
|
this.consentState = {
|
|
necessary: true,
|
|
analytics: true,
|
|
marketing: true,
|
|
preferences: true,
|
|
timestamp: Date.now(),
|
|
version: tigerstyleWhiskersConsent.current_consent.version || '1.0'
|
|
};
|
|
|
|
this.saveConsent();
|
|
this.hideBanner();
|
|
this.triggerConsentCallbacks();
|
|
this.trackEvent('consent_all_accepted');
|
|
|
|
// Show success message
|
|
this.showSuccessMessage(tigerstyleWhiskersConsent.strings.accept_all);
|
|
},
|
|
|
|
// Accept necessary only
|
|
acceptNecessary: function() {
|
|
this.consentState = {
|
|
necessary: true,
|
|
analytics: false,
|
|
marketing: false,
|
|
preferences: false,
|
|
timestamp: Date.now(),
|
|
version: tigerstyleWhiskersConsent.current_consent.version || '1.0'
|
|
};
|
|
|
|
this.saveConsent();
|
|
this.hideBanner();
|
|
this.triggerConsentCallbacks();
|
|
this.trackEvent('consent_necessary_only');
|
|
|
|
// Show success message
|
|
this.showSuccessMessage(tigerstyleWhiskersConsent.strings.accept_necessary);
|
|
},
|
|
|
|
// Save custom preferences
|
|
saveCustomPreferences: function() {
|
|
const self = this;
|
|
|
|
// Get values from form
|
|
Object.keys(this.consentState).forEach(function(category) {
|
|
if (typeof self.consentState[category] === 'boolean') {
|
|
const $checkbox = $(`#consent_${category}`);
|
|
if ($checkbox.length) {
|
|
self.consentState[category] = $checkbox.is(':checked');
|
|
}
|
|
}
|
|
});
|
|
|
|
this.consentState.timestamp = Date.now();
|
|
this.consentState.version = tigerstyleWhiskersConsent.current_consent.version || '1.0';
|
|
|
|
this.saveConsent();
|
|
this.hideBanner();
|
|
this.triggerConsentCallbacks();
|
|
this.trackEvent('consent_custom_saved');
|
|
|
|
// Show success message
|
|
this.showSuccessMessage(tigerstyleWhiskersConsent.strings.save_preferences);
|
|
},
|
|
|
|
// Save consent to cookie and server
|
|
saveConsent: function() {
|
|
// Save to cookie
|
|
this.setCookie(this.config.cookieName, JSON.stringify(this.consentState), this.config.cookieExpiry);
|
|
|
|
// Send to server
|
|
$.ajax({
|
|
url: tigerstyleWhiskersConsent.ajaxurl,
|
|
type: 'POST',
|
|
data: {
|
|
action: 'tigerstyle_whiskers_update_consent',
|
|
nonce: tigerstyleWhiskersConsent.nonce,
|
|
analytics: this.consentState.analytics,
|
|
marketing: this.consentState.marketing,
|
|
preferences: this.consentState.preferences
|
|
},
|
|
success: function(response) {
|
|
console.log('🐱 TigerStyle Whiskers: Consent saved with feline precision!');
|
|
},
|
|
error: function() {
|
|
console.warn('🐱 TigerStyle Whiskers: Failed to save consent to server');
|
|
}
|
|
});
|
|
},
|
|
|
|
// Trigger consent change callbacks
|
|
triggerConsentCallbacks: function() {
|
|
// Dispatch custom event
|
|
const event = new CustomEvent('tigerstyleWhiskersConsentChanged', {
|
|
detail: this.consentState
|
|
});
|
|
document.dispatchEvent(event);
|
|
|
|
// Legacy callback support
|
|
if (typeof window.onTigerstyleWhiskersConsentChanged === 'function') {
|
|
window.onTigerstyleWhiskersConsentChanged(this.consentState);
|
|
}
|
|
|
|
// Initialize analytics if consent given
|
|
if (this.consentState.analytics && typeof tigerstyleWhiskersInitAnalytics === 'function') {
|
|
tigerstyleWhiskersInitAnalytics();
|
|
}
|
|
},
|
|
|
|
// Check if analytics consent is given
|
|
hasAnalyticsConsent: function() {
|
|
return this.consentState.analytics === true;
|
|
},
|
|
|
|
// Check if marketing consent is given
|
|
hasMarketingConsent: function() {
|
|
return this.consentState.marketing === true;
|
|
},
|
|
|
|
// Check if preferences consent is given
|
|
hasPreferencesConsent: function() {
|
|
return this.consentState.preferences === true;
|
|
},
|
|
|
|
// Get consent for specific category
|
|
getConsent: function(category) {
|
|
return this.consentState[category] || false;
|
|
},
|
|
|
|
// Show success message
|
|
showSuccessMessage: function(message) {
|
|
const $message = $('<div class="tw-success-message">')
|
|
.text(message)
|
|
.css({
|
|
position: 'fixed',
|
|
top: '20px',
|
|
right: '20px',
|
|
background: 'linear-gradient(135deg, #28a745 0%, #20c997 100%)',
|
|
color: 'white',
|
|
padding: '12px 20px',
|
|
borderRadius: '8px',
|
|
boxShadow: '0 4px 12px rgba(40, 167, 69, 0.3)',
|
|
zIndex: 999999,
|
|
fontSize: '14px',
|
|
fontWeight: '600',
|
|
opacity: 0
|
|
});
|
|
|
|
$('body').append($message);
|
|
|
|
$message.animate({ opacity: 1 }, 300)
|
|
.delay(3000)
|
|
.animate({ opacity: 0 }, 300, function() {
|
|
$(this).remove();
|
|
});
|
|
},
|
|
|
|
// Animate toggle change
|
|
animateToggle: function($category, isEnabled) {
|
|
if (isEnabled) {
|
|
$category.addClass('tw-enabled');
|
|
} else {
|
|
$category.removeClass('tw-enabled');
|
|
}
|
|
|
|
// Subtle animation
|
|
$category.addClass('tw-changing');
|
|
setTimeout(() => $category.removeClass('tw-changing'), 300);
|
|
},
|
|
|
|
// Track events for analytics (only if consent given)
|
|
trackEvent: function(eventName, eventData = {}) {
|
|
if (this.hasAnalyticsConsent() && typeof gtag === 'function') {
|
|
gtag('event', eventName, Object.assign({
|
|
event_category: 'consent_management',
|
|
event_label: 'tigerstyle_whiskers',
|
|
value: 1
|
|
}, eventData));
|
|
}
|
|
|
|
// Also track locally for audit
|
|
const logEntry = {
|
|
event: eventName,
|
|
data: eventData,
|
|
timestamp: Date.now(),
|
|
consent_state: this.consentState
|
|
};
|
|
|
|
if (localStorage) {
|
|
const logs = JSON.parse(localStorage.getItem('tigerstyle_whiskers_events') || '[]');
|
|
logs.push(logEntry);
|
|
|
|
// Keep only last 100 events
|
|
if (logs.length > 100) {
|
|
logs.splice(0, logs.length - 100);
|
|
}
|
|
|
|
localStorage.setItem('tigerstyle_whiskers_events', JSON.stringify(logs));
|
|
}
|
|
},
|
|
|
|
// Clear consent (for testing)
|
|
clearConsent: function() {
|
|
this.deleteCookie(this.config.cookieName);
|
|
this.consentState = {
|
|
necessary: true,
|
|
analytics: false,
|
|
marketing: false,
|
|
preferences: false,
|
|
timestamp: null,
|
|
version: null
|
|
};
|
|
|
|
if (localStorage) {
|
|
localStorage.removeItem('tigerstyle_whiskers_client_boundaries');
|
|
localStorage.removeItem('tigerstyle_whiskers_events');
|
|
}
|
|
},
|
|
|
|
// Cookie utilities
|
|
setCookie: function(name, value, days) {
|
|
const expires = new Date();
|
|
expires.setTime(expires.getTime() + (days * 24 * 60 * 60 * 1000));
|
|
|
|
document.cookie = name + '=' + encodeURIComponent(value) +
|
|
';expires=' + expires.toUTCString() +
|
|
';path=/' +
|
|
';SameSite=Lax' +
|
|
(location.protocol === 'https:' ? ';Secure' : '');
|
|
},
|
|
|
|
getCookie: function(name) {
|
|
const nameEQ = name + "=";
|
|
const ca = document.cookie.split(';');
|
|
|
|
for (let i = 0; i < ca.length; i++) {
|
|
let c = ca[i];
|
|
while (c.charAt(0) === ' ') c = c.substring(1, c.length);
|
|
if (c.indexOf(nameEQ) === 0) {
|
|
return decodeURIComponent(c.substring(nameEQ.length, c.length));
|
|
}
|
|
}
|
|
return null;
|
|
},
|
|
|
|
deleteCookie: function(name) {
|
|
document.cookie = name + '=;expires=Thu, 01 Jan 1970 00:00:01 GMT;path=/';
|
|
}
|
|
};
|
|
|
|
// Initialize when DOM is ready
|
|
$(document).ready(function() {
|
|
window.tigerstyleWhiskersConsent.init();
|
|
});
|
|
|
|
// Handle Global Privacy Control
|
|
if (navigator.globalPrivacyControl) {
|
|
console.log('🐱 TigerStyle Whiskers: Global Privacy Control detected - respecting user preferences');
|
|
|
|
$(document).ready(function() {
|
|
// If GPC is enabled, default to necessary only
|
|
if (!window.tigerstyleWhiskersConsent.getCookie('tigerstyle_whiskers_consent')) {
|
|
window.tigerstyleWhiskersConsent.acceptNecessary();
|
|
}
|
|
});
|
|
}
|
|
|
|
})(jQuery); |