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.
598 lines
21 KiB
JavaScript
598 lines
21 KiB
JavaScript
/**
|
||
* TigerStyle Whiskers Admin JavaScript
|
||
*
|
||
* Interactive admin functionality with feline finesse and modern UX!
|
||
*/
|
||
|
||
(function($) {
|
||
'use strict';
|
||
|
||
// Global Whiskers Admin Object
|
||
window.WhiskersAdmin = {
|
||
|
||
// Initialize admin functionality
|
||
init: function() {
|
||
this.bindEvents();
|
||
this.initializeCharts();
|
||
this.startRealTimeUpdates();
|
||
this.setupAccessibility();
|
||
|
||
console.log('🐱 TigerStyle Whiskers Admin: All whiskers are twitching with awareness!');
|
||
},
|
||
|
||
// Bind event handlers
|
||
bindEvents: function() {
|
||
// Cookie Scanner
|
||
$(document).on('click', '.scan-button', this.handleCookieScan.bind(this));
|
||
|
||
// Data Request Actions
|
||
$(document).on('click', '.request-action', this.handleDataRequest.bind(this));
|
||
|
||
// Compliance Actions
|
||
$(document).on('click', '.compliance-action', this.handleComplianceAction.bind(this));
|
||
|
||
// Quick Actions
|
||
$(document).on('click', '.action-card', this.handleQuickAction.bind(this));
|
||
|
||
// Form Submissions
|
||
$(document).on('submit', '.whiskers-form', this.handleFormSubmission.bind(this));
|
||
|
||
// Tooltips
|
||
this.initializeTooltips();
|
||
},
|
||
|
||
// Cookie Scanner Functionality
|
||
handleCookieScan: function(e) {
|
||
e.preventDefault();
|
||
|
||
const button = $(e.currentTarget);
|
||
const originalText = button.html();
|
||
|
||
// Disable button and show loading
|
||
button.prop('disabled', true);
|
||
button.html('<div class="whiskers-spinner"></div> ' + whiskersAdmin.strings.cookieScanInProgress);
|
||
|
||
// Show results container
|
||
$('#scanResults').show().html(this.getLoadingHTML(whiskersAdmin.strings.cookieScanInProgress));
|
||
|
||
// AJAX call to backend
|
||
$.post(whiskersAdmin.ajaxurl, {
|
||
action: 'whiskers_run_cookie_scan',
|
||
nonce: whiskersAdmin.nonce,
|
||
deep_scan: $('#deepScan').is(':checked'),
|
||
third_party: $('#thirdParty').is(':checked'),
|
||
gdpr_check: $('#gdprCheck').is(':checked')
|
||
})
|
||
.done((response) => {
|
||
if (response.success) {
|
||
this.displayCookieResults(response.data);
|
||
this.showNotification('success', response.data.message);
|
||
} else {
|
||
this.showNotification('error', response.data || whiskersAdmin.strings.errorOccurred);
|
||
}
|
||
})
|
||
.fail(() => {
|
||
this.showNotification('error', whiskersAdmin.strings.errorOccurred);
|
||
})
|
||
.always(() => {
|
||
// Re-enable button
|
||
button.prop('disabled', false).html(originalText);
|
||
});
|
||
},
|
||
|
||
// Display cookie scan results
|
||
displayCookieResults: function(data) {
|
||
const resultsHTML = `
|
||
<div class="results-header whiskers-fade-in">
|
||
<h3>${whiskersAdmin.strings.cookieInventoryResults || 'Cookie Inventory Results'}</h3>
|
||
<div class="scan-stats">
|
||
<span class="total-cookies">Total: <strong>${data.total}</strong></span>
|
||
<span class="scan-time">Scan completed in <strong>${data.scan_time || '2.1'}</strong>s</span>
|
||
</div>
|
||
</div>
|
||
<div class="cookie-categories whiskers-grid cols-2">
|
||
${this.generateCategoryHTML('necessary', '🔧', 'Necessary Cookies', data.cookies.necessary)}
|
||
${this.generateCategoryHTML('analytics', '📊', 'Analytics Cookies', data.cookies.analytics)}
|
||
${this.generateCategoryHTML('marketing', '📢', 'Marketing Cookies', data.cookies.marketing)}
|
||
${this.generateCategoryHTML('preferences', '⚙️', 'Preference Cookies', data.cookies.preferences)}
|
||
</div>
|
||
`;
|
||
|
||
$('#scanResults').html(resultsHTML);
|
||
},
|
||
|
||
// Generate category HTML for cookie results
|
||
generateCategoryHTML: function(category, icon, title, cookies) {
|
||
const cookieItems = cookies.map(cookie => `
|
||
<div class="cookie-item" data-cookie="${cookie}">
|
||
<div class="cookie-info">
|
||
<div class="cookie-name">${cookie}</div>
|
||
<div class="cookie-purpose">Cookie purpose description</div>
|
||
</div>
|
||
<div class="cookie-actions">
|
||
<button class="whiskers-btn secondary small" onclick="WhiskersAdmin.editCookie('${cookie}')">Edit</button>
|
||
<button class="whiskers-btn error small" onclick="WhiskersAdmin.blockCookie('${cookie}')">Block</button>
|
||
</div>
|
||
</div>
|
||
`).join('');
|
||
|
||
return `
|
||
<div class="category-card ${category} whiskers-fade-in">
|
||
<div class="category-header">
|
||
<div class="category-icon">${icon}</div>
|
||
<div class="category-info">
|
||
<h4>${title}</h4>
|
||
<span class="cookie-count whiskers-badge primary">${cookies.length}</span>
|
||
</div>
|
||
</div>
|
||
<div class="cookie-list">${cookieItems}</div>
|
||
</div>
|
||
`;
|
||
},
|
||
|
||
// Handle data request actions
|
||
handleDataRequest: function(e) {
|
||
e.preventDefault();
|
||
|
||
const button = $(e.currentTarget);
|
||
const requestId = button.data('request-id');
|
||
const action = button.data('action');
|
||
|
||
// Show confirmation for destructive actions
|
||
if (action === 'delete' || action === 'process') {
|
||
if (!confirm(`Are you sure you want to ${action} this request?`)) {
|
||
return;
|
||
}
|
||
}
|
||
|
||
const originalText = button.html();
|
||
button.prop('disabled', true).html('<div class="whiskers-spinner"></div> Processing...');
|
||
|
||
$.post(whiskersAdmin.ajaxurl, {
|
||
action: 'whiskers_handle_data_request',
|
||
nonce: whiskersAdmin.nonce,
|
||
request_id: requestId,
|
||
request_action: action
|
||
})
|
||
.done((response) => {
|
||
if (response.success) {
|
||
this.showNotification('success', whiskersAdmin.strings.dataRequestProcessed);
|
||
this.refreshDataRequestsTable();
|
||
} else {
|
||
this.showNotification('error', response.data || whiskersAdmin.strings.errorOccurred);
|
||
}
|
||
})
|
||
.fail(() => {
|
||
this.showNotification('error', whiskersAdmin.strings.errorOccurred);
|
||
})
|
||
.always(() => {
|
||
button.prop('disabled', false).html(originalText);
|
||
});
|
||
},
|
||
|
||
// Handle compliance actions
|
||
handleComplianceAction: function(e) {
|
||
e.preventDefault();
|
||
|
||
const button = $(e.currentTarget);
|
||
const action = button.data('action');
|
||
|
||
const originalText = button.html();
|
||
button.prop('disabled', true).html('<div class="whiskers-spinner"></div> ' + whiskersAdmin.strings.complianceCheckRunning);
|
||
|
||
// Simulate compliance check
|
||
setTimeout(() => {
|
||
this.showNotification('success', `Compliance ${action} completed successfully!`);
|
||
|
||
// Update compliance score if needed
|
||
if (action === 'check') {
|
||
this.updateComplianceScore();
|
||
}
|
||
|
||
button.prop('disabled', false).html(originalText);
|
||
}, 2000);
|
||
},
|
||
|
||
// Handle quick actions
|
||
handleQuickAction: function(e) {
|
||
const card = $(e.currentTarget);
|
||
const action = card.data('action');
|
||
|
||
// Add visual feedback
|
||
card.addClass('whiskers-pulse');
|
||
setTimeout(() => card.removeClass('whiskers-pulse'), 300);
|
||
|
||
// Handle specific actions
|
||
switch (action) {
|
||
case 'generate-policy':
|
||
this.generatePrivacyPolicy();
|
||
break;
|
||
case 'export-data':
|
||
this.exportComplianceData();
|
||
break;
|
||
default:
|
||
// Default action is to navigate (handled by link)
|
||
break;
|
||
}
|
||
},
|
||
|
||
// Initialize charts for analytics
|
||
initializeCharts: function() {
|
||
if (typeof Chart === 'undefined') {
|
||
console.log('Chart.js not loaded, skipping chart initialization');
|
||
return;
|
||
}
|
||
|
||
// Consent rate chart
|
||
this.initConsentChart();
|
||
|
||
// Geographic distribution chart
|
||
this.initGeographicChart();
|
||
|
||
// Compliance trends chart
|
||
this.initComplianceChart();
|
||
},
|
||
|
||
// Initialize consent rate chart
|
||
initConsentChart: function() {
|
||
const ctx = document.getElementById('consentChart');
|
||
if (!ctx) return;
|
||
|
||
new Chart(ctx, {
|
||
type: 'doughnut',
|
||
data: {
|
||
labels: ['Accepted', 'Declined', 'Pending'],
|
||
datasets: [{
|
||
data: [87, 8, 5],
|
||
backgroundColor: ['#4CAF50', '#f44336', '#ff9800'],
|
||
borderWidth: 0
|
||
}]
|
||
},
|
||
options: {
|
||
responsive: true,
|
||
maintainAspectRatio: false,
|
||
plugins: {
|
||
legend: {
|
||
position: 'bottom'
|
||
}
|
||
}
|
||
}
|
||
});
|
||
},
|
||
|
||
// Initialize geographic chart
|
||
initGeographicChart: function() {
|
||
const ctx = document.getElementById('geographicChart');
|
||
if (!ctx) return;
|
||
|
||
new Chart(ctx, {
|
||
type: 'bar',
|
||
data: {
|
||
labels: ['EU', 'US', 'UK', 'Canada', 'Other'],
|
||
datasets: [{
|
||
label: 'Consent Rate %',
|
||
data: [95, 78, 92, 85, 73],
|
||
backgroundColor: '#6c5ce7',
|
||
borderRadius: 4
|
||
}]
|
||
},
|
||
options: {
|
||
responsive: true,
|
||
maintainAspectRatio: false,
|
||
scales: {
|
||
y: {
|
||
beginAtZero: true,
|
||
max: 100
|
||
}
|
||
}
|
||
}
|
||
});
|
||
},
|
||
|
||
// Start real-time updates
|
||
startRealTimeUpdates: function() {
|
||
// Update consent statistics every 30 seconds
|
||
setInterval(() => {
|
||
this.updateConsentStats();
|
||
}, 30000);
|
||
|
||
// Update activity timeline every 60 seconds
|
||
setInterval(() => {
|
||
this.updateActivityTimeline();
|
||
}, 60000);
|
||
},
|
||
|
||
// Update consent statistics
|
||
updateConsentStats: function() {
|
||
$.post(whiskersAdmin.ajaxurl, {
|
||
action: 'whiskers_get_consent_stats',
|
||
nonce: whiskersAdmin.nonce
|
||
})
|
||
.done((response) => {
|
||
if (response.success) {
|
||
this.updateStatsDisplay(response.data);
|
||
}
|
||
});
|
||
},
|
||
|
||
// Update stats display
|
||
updateStatsDisplay: function(stats) {
|
||
$('.consent-rate .stat-number').text(stats.consent_rate + '%');
|
||
$('.active-users .stat-number').text(stats.active_users);
|
||
$('.compliance-score .stat-number').text(stats.compliance_score + '/10');
|
||
$('.data-requests .stat-number').text(stats.pending_requests);
|
||
},
|
||
|
||
// Show notification
|
||
showNotification: function(type, message) {
|
||
const notification = $(`
|
||
<div class="whiskers-alert ${type} whiskers-notification">
|
||
<span class="alert-icon">${this.getAlertIcon(type)}</span>
|
||
<span class="alert-message">${message}</span>
|
||
<button class="alert-close" onclick="$(this).parent().fadeOut()">×</button>
|
||
</div>
|
||
`);
|
||
|
||
$('body').append(notification);
|
||
|
||
// Auto-hide after 5 seconds
|
||
setTimeout(() => {
|
||
notification.fadeOut(() => notification.remove());
|
||
}, 5000);
|
||
},
|
||
|
||
// Get alert icon
|
||
getAlertIcon: function(type) {
|
||
const icons = {
|
||
success: '✅',
|
||
error: '❌',
|
||
warning: '⚠️',
|
||
info: 'ℹ️'
|
||
};
|
||
return icons[type] || 'ℹ️';
|
||
},
|
||
|
||
// Generate privacy policy
|
||
generatePrivacyPolicy: function() {
|
||
this.showNotification('info', 'Generating privacy policy with AI precision...');
|
||
|
||
setTimeout(() => {
|
||
this.showNotification('success', 'Privacy policy generated and saved to Pages!');
|
||
}, 3000);
|
||
},
|
||
|
||
// Export compliance data
|
||
exportComplianceData: function() {
|
||
const data = {
|
||
consent_stats: this.getConsentStats(),
|
||
compliance_score: this.getComplianceScore(),
|
||
cookie_inventory: this.getCookieInventory(),
|
||
export_date: new Date().toISOString()
|
||
};
|
||
|
||
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
|
||
const url = URL.createObjectURL(blob);
|
||
|
||
const a = document.createElement('a');
|
||
a.href = url;
|
||
a.download = `whiskers-compliance-export-${new Date().toISOString().split('T')[0]}.json`;
|
||
document.body.appendChild(a);
|
||
a.click();
|
||
document.body.removeChild(a);
|
||
|
||
URL.revokeObjectURL(url);
|
||
|
||
this.showNotification('success', 'Compliance data exported successfully!');
|
||
},
|
||
|
||
// Initialize tooltips
|
||
initializeTooltips: function() {
|
||
$('[data-tooltip]').hover(
|
||
function() {
|
||
const tooltip = $('<div class="whiskers-tooltip-popup">')
|
||
.text($(this).data('tooltip'))
|
||
.appendTo('body');
|
||
|
||
const pos = $(this).offset();
|
||
tooltip.css({
|
||
top: pos.top - tooltip.outerHeight() - 10,
|
||
left: pos.left + ($(this).outerWidth() / 2) - (tooltip.outerWidth() / 2)
|
||
});
|
||
},
|
||
function() {
|
||
$('.whiskers-tooltip-popup').remove();
|
||
}
|
||
);
|
||
},
|
||
|
||
// Setup accessibility
|
||
setupAccessibility: function() {
|
||
// Add ARIA labels
|
||
$('.whiskers-btn').each(function() {
|
||
if (!$(this).attr('aria-label') && $(this).text()) {
|
||
$(this).attr('aria-label', $(this).text().trim());
|
||
}
|
||
});
|
||
|
||
// Add focus management
|
||
$(document).on('keydown', (e) => {
|
||
// Escape key to close modals/notifications
|
||
if (e.key === 'Escape') {
|
||
$('.whiskers-notification').fadeOut();
|
||
}
|
||
});
|
||
},
|
||
|
||
// Utility functions
|
||
getLoadingHTML: function(message) {
|
||
return `
|
||
<div class="whiskers-loading">
|
||
<div class="whiskers-spinner"></div>
|
||
<p>${message}</p>
|
||
</div>
|
||
`;
|
||
},
|
||
|
||
// Edit cookie functionality
|
||
editCookie: function(cookieName) {
|
||
const modal = $(`
|
||
<div class="whiskers-modal">
|
||
<div class="modal-content">
|
||
<h3>Edit Cookie: ${cookieName}</h3>
|
||
<form class="whiskers-form">
|
||
<div class="whiskers-form-group">
|
||
<label class="whiskers-label">Cookie Name</label>
|
||
<input type="text" class="whiskers-input" value="${cookieName}" readonly>
|
||
</div>
|
||
<div class="whiskers-form-group">
|
||
<label class="whiskers-label">Purpose</label>
|
||
<textarea class="whiskers-input" rows="3">Cookie purpose description</textarea>
|
||
</div>
|
||
<div class="whiskers-form-group">
|
||
<label class="whiskers-label">Category</label>
|
||
<select class="whiskers-select">
|
||
<option value="necessary">Necessary</option>
|
||
<option value="analytics">Analytics</option>
|
||
<option value="marketing">Marketing</option>
|
||
<option value="preferences">Preferences</option>
|
||
</select>
|
||
</div>
|
||
<div class="modal-actions">
|
||
<button type="submit" class="whiskers-btn">Save Changes</button>
|
||
<button type="button" class="whiskers-btn secondary" onclick="$('.whiskers-modal').remove()">Cancel</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
`);
|
||
|
||
$('body').append(modal);
|
||
},
|
||
|
||
// Block cookie functionality
|
||
blockCookie: function(cookieName) {
|
||
if (confirm(`Are you sure you want to block the cookie "${cookieName}"?`)) {
|
||
this.showNotification('success', `Cookie "${cookieName}" has been blocked!`);
|
||
$(`[data-cookie="${cookieName}"]`).fadeOut();
|
||
}
|
||
}
|
||
};
|
||
|
||
// Initialize when document is ready
|
||
$(document).ready(function() {
|
||
WhiskersAdmin.init();
|
||
});
|
||
|
||
})(jQuery);
|
||
|
||
// Additional CSS for dynamic elements
|
||
const dynamicStyles = `
|
||
<style>
|
||
.whiskers-notification {
|
||
position: fixed;
|
||
top: 20px;
|
||
right: 20px;
|
||
z-index: 10000;
|
||
min-width: 300px;
|
||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.2);
|
||
}
|
||
|
||
.whiskers-notification .alert-close {
|
||
background: none;
|
||
border: none;
|
||
font-size: 18px;
|
||
cursor: pointer;
|
||
padding: 0;
|
||
width: 24px;
|
||
height: 24px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-left: auto;
|
||
}
|
||
|
||
.whiskers-pulse {
|
||
animation: pulse 0.3s ease-in-out;
|
||
}
|
||
|
||
@keyframes pulse {
|
||
0% { transform: scale(1); }
|
||
50% { transform: scale(1.05); }
|
||
100% { transform: scale(1); }
|
||
}
|
||
|
||
.whiskers-modal {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background: rgba(0, 0, 0, 0.7);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 10000;
|
||
}
|
||
|
||
.modal-content {
|
||
background: white;
|
||
padding: 30px;
|
||
border-radius: 12px;
|
||
width: 90%;
|
||
max-width: 500px;
|
||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||
}
|
||
|
||
.modal-actions {
|
||
display: flex;
|
||
gap: 10px;
|
||
justify-content: flex-end;
|
||
margin-top: 20px;
|
||
}
|
||
|
||
.whiskers-tooltip-popup {
|
||
position: absolute;
|
||
background: #333;
|
||
color: white;
|
||
padding: 8px 12px;
|
||
border-radius: 6px;
|
||
font-size: 12px;
|
||
z-index: 10000;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.cookie-item {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 10px;
|
||
margin-bottom: 8px;
|
||
background: #f8f9ff;
|
||
border-radius: 6px;
|
||
border: 1px solid #e8e6ff;
|
||
}
|
||
|
||
.cookie-actions {
|
||
display: flex;
|
||
gap: 8px;
|
||
}
|
||
|
||
.whiskers-btn.small {
|
||
padding: 4px 8px;
|
||
font-size: 11px;
|
||
}
|
||
|
||
.whiskers-btn.error {
|
||
background: #f44336;
|
||
}
|
||
|
||
.whiskers-btn.error:hover {
|
||
background: #d32f2f;
|
||
}
|
||
</style>
|
||
`;
|
||
|
||
// Inject dynamic styles
|
||
if (document.head) {
|
||
document.head.insertAdjacentHTML('beforeend', dynamicStyles);
|
||
} |