tigerstyle-whiskers/includes/whiskers/class-cookie-consent.php
Ryan Malloy adbdae19c8 Initial commit: TigerStyle Whiskers v1.0.0
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.
2026-05-27 14:31:51 -06:00

597 lines
24 KiB
PHP

<?php
/**
* Cookie Consent Whisker for TigerStyle Whiskers
*
* Smart consent banners with feline finesse - precise, elegant, and respectful
* Like a cat asking permission before entering a room!
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
class TigerStyleWhiskers_CookieConsent {
/**
* Single instance
*/
private static $instance = null;
/**
* Consent status cache
*/
private $consent_status = array();
/**
* Cookie categories
*/
private $cookie_categories = array(
'necessary' => array(
'name' => 'Necessary',
'description' => 'Essential for website functionality',
'required' => true,
'color' => '#2c3e50'
),
'analytics' => array(
'name' => 'Analytics',
'description' => 'Help us understand how visitors use our site',
'required' => false,
'color' => '#ff6b35'
),
'marketing' => array(
'name' => 'Marketing',
'description' => 'Used to track visitors and display relevant ads',
'required' => false,
'color' => '#f7931e'
),
'preferences' => array(
'name' => 'Preferences',
'description' => 'Remember your settings and preferences',
'required' => false,
'color' => '#3498db'
)
);
/**
* Get instance
*/
public static function instance() {
if (is_null(self::$instance)) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Constructor
*/
private function __construct() {
$this->init_consent_whisker();
}
/**
* Initialize cookie consent whisker
*/
private function init_consent_whisker() {
// Frontend hooks
add_action('wp_footer', array($this, 'render_consent_banner'), 999);
add_action('wp_enqueue_scripts', array($this, 'enqueue_consent_assets'));
// AJAX handlers
add_action('wp_ajax_tigerstyle_whiskers_update_consent', array($this, 'handle_consent_update'));
add_action('wp_ajax_nopriv_tigerstyle_whiskers_update_consent', array($this, 'handle_consent_update'));
// Admin hooks
if (is_admin()) {
add_action('admin_post_update_cookie_consent_settings', array($this, 'handle_settings_update'));
}
// Check consent status on init
add_action('init', array($this, 'check_consent_status'));
// Integrate with TigerStyle Heat Analytics
add_action('init', array($this, 'integrate_with_analytics'));
if (defined('WP_DEBUG') && WP_DEBUG) {
error_log('TigerStyle Whiskers: Cookie consent whisker is alert and ready!');
}
}
/**
* Check current consent status
*/
public function check_consent_status() {
$this->consent_status = $this->get_visitor_consent();
// If no consent recorded and GDPR applies, show banner
if (TigerStyleWhiskers_BoundaryDetector::is_gdpr_territory() && !$this->has_consent_choice()) {
$this->consent_status['show_banner'] = true;
}
}
/**
* Get visitor consent from cookies
*/
private function get_visitor_consent() {
$default_consent = array(
'necessary' => true, // Always true
'analytics' => false,
'marketing' => false,
'preferences' => false,
'timestamp' => null,
'version' => null,
'show_banner' => false
);
// Check for consent cookie
$consent_cookie = isset($_COOKIE['tigerstyle_whiskers_consent']) ?
json_decode(stripslashes($_COOKIE['tigerstyle_whiskers_consent']), true) :
array();
return array_merge($default_consent, $consent_cookie ?: array());
}
/**
* Check if visitor has made a consent choice
*/
private function has_consent_choice() {
return !empty($this->consent_status['timestamp']);
}
/**
* Enqueue consent assets
*/
public function enqueue_consent_assets() {
// Only enqueue if we need to show consent UI
if (!$this->should_show_consent_ui()) {
return;
}
wp_enqueue_style(
'tigerstyle-whiskers-consent',
TIGERSTYLE_WHISKERS_PLUGIN_URL . 'assets/css/consent.css',
array(),
TIGERSTYLE_WHISKERS_VERSION
);
wp_enqueue_script(
'tigerstyle-whiskers-consent',
TIGERSTYLE_WHISKERS_PLUGIN_URL . 'assets/js/consent.js',
array('jquery'),
TIGERSTYLE_WHISKERS_VERSION,
true
);
// Localize script
wp_localize_script('tigerstyle-whiskers-consent', 'tigerstyleWhiskersConsent', array(
'ajaxurl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('tigerstyle_whiskers_consent'),
'categories' => $this->cookie_categories,
'current_consent' => $this->consent_status,
'strings' => array(
'accept_all' => __('Accept All Cookies', 'tigerstyle-whiskers'),
'accept_necessary' => __('Accept Necessary Only', 'tigerstyle-whiskers'),
'customize' => __('Customize Settings', 'tigerstyle-whiskers'),
'save_preferences' => __('Save My Preferences', 'tigerstyle-whiskers'),
'privacy_policy' => __('Privacy Policy', 'tigerstyle-whiskers'),
'learn_more' => __('Learn More', 'tigerstyle-whiskers'),
)
));
}
/**
* Should we show consent UI?
*/
private function should_show_consent_ui() {
// Show if GDPR applies and no consent recorded
return TigerStyleWhiskers_BoundaryDetector::is_gdpr_territory() && !$this->has_consent_choice();
}
/**
* Render consent banner
*/
public function render_consent_banner() {
if (!$this->should_show_consent_ui()) {
return;
}
$settings = $this->get_consent_settings();
?>
<!-- TigerStyle Whiskers: Cookie Consent Banner -->
<div id="tigerstyle-whiskers-consent-overlay" class="tw-consent-overlay" style="display: none;">
<div class="tw-consent-banner">
<div class="tw-consent-header">
<div class="tw-consent-icon">
<?php echo $this->get_whiskers_icon(); ?>
</div>
<h3 class="tw-consent-title">
<?php echo esc_html($settings['banner_title'] ?: __('We Respect Your Privacy', 'tigerstyle-whiskers')); ?>
</h3>
</div>
<div class="tw-consent-content">
<p class="tw-consent-message">
<?php echo wp_kses_post($settings['banner_message'] ?: $this->get_default_banner_message()); ?>
</p>
<div class="tw-consent-categories" id="tw-consent-categories" style="display: none;">
<?php foreach ($this->cookie_categories as $key => $category): ?>
<div class="tw-consent-category">
<div class="tw-consent-category-header">
<label class="tw-consent-switch">
<input
type="checkbox"
name="consent_<?php echo esc_attr($key); ?>"
id="consent_<?php echo esc_attr($key); ?>"
<?php checked($category['required'] || $this->consent_status[$key]); ?>
<?php disabled($category['required']); ?>
>
<span class="tw-consent-slider" style="background-color: <?php echo esc_attr($category['color']); ?>"></span>
</label>
<div class="tw-consent-category-info">
<h4><?php echo esc_html($category['name']); ?></h4>
<p><?php echo esc_html($category['description']); ?></p>
<?php if ($category['required']): ?>
<small class="tw-required"><?php _e('Required', 'tigerstyle-whiskers'); ?></small>
<?php endif; ?>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<div class="tw-consent-actions">
<button type="button" class="tw-btn tw-btn-customize" onclick="tigerstyleWhiskersConsent.showCategories()">
<?php _e('Customize Settings', 'tigerstyle-whiskers'); ?>
</button>
<button type="button" class="tw-btn tw-btn-necessary" onclick="tigerstyleWhiskersConsent.acceptNecessary()">
<?php _e('Necessary Only', 'tigerstyle-whiskers'); ?>
</button>
<button type="button" class="tw-btn tw-btn-accept" onclick="tigerstyleWhiskersConsent.acceptAll()">
<?php _e('Accept All', 'tigerstyle-whiskers'); ?>
</button>
</div>
<div class="tw-consent-footer">
<a href="<?php echo esc_url($settings['privacy_policy_url'] ?: get_privacy_policy_url()); ?>" target="_blank">
<?php _e('Privacy Policy', 'tigerstyle-whiskers'); ?>
</a>
<?php if (!empty($settings['cookie_policy_url'])): ?>
• <a href="<?php echo esc_url($settings['cookie_policy_url']); ?>" target="_blank">
<?php _e('Cookie Policy', 'tigerstyle-whiskers'); ?>
</a>
<?php endif; ?>
</div>
</div>
</div>
<!-- Consent Management Button (for users to change preferences) -->
<?php if ($this->has_consent_choice()): ?>
<div id="tigerstyle-whiskers-consent-manager" class="tw-consent-manager">
<button type="button" class="tw-consent-manage-btn" onclick="tigerstyleWhiskersConsent.showBanner()">
<?php echo $this->get_whiskers_icon(); ?>
<span><?php _e('Cookie Settings', 'tigerstyle-whiskers'); ?></span>
</button>
</div>
<?php endif; ?>
<script type="text/javascript">
// Show consent banner if needed
document.addEventListener('DOMContentLoaded', function() {
<?php if ($this->should_show_consent_ui()): ?>
tigerstyleWhiskersConsent.showBanner();
<?php endif; ?>
});
</script>
<?php
}
/**
* Get whiskers icon SVG
*/
private function get_whiskers_icon() {
return '<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<!-- Cat face with whiskers -->
<ellipse cx="12" cy="14" rx="8" ry="7" fill="currentColor" opacity="0.9"/>
<!-- Cat ears -->
<ellipse cx="8" cy="8" rx="2.5" ry="3" fill="currentColor"/>
<ellipse cx="16" cy="8" rx="2.5" ry="3" fill="currentColor"/>
<!-- Cat eyes -->
<ellipse cx="9.5" cy="12" rx="1.2" ry="1.5" fill="white"/>
<ellipse cx="14.5" cy="12" rx="1.2" ry="1.5" fill="white"/>
<!-- Cat nose -->
<ellipse cx="12" cy="14.5" rx="0.8" ry="0.6" fill="currentColor"/>
<!-- Whiskers (the most important part!) -->
<path d="M4 11 L7 12 M17 12 L20 11 M4 13 L7 14 M17 14 L20 13 M4 15 L7 16 M17 16 L20 15"
stroke="currentColor" stroke-width="1.5" fill="none" opacity="0.8"/>
</svg>';
}
/**
* Get default banner message
*/
private function get_default_banner_message() {
return sprintf(
__('We use cookies to enhance your browsing experience and analyze our traffic. Some cookies are essential for our website to function properly. %s', 'tigerstyle-whiskers'),
'<strong>' . __('Your choice matters to us - just like a cat choosing where to sit!', 'tigerstyle-whiskers') . '</strong>'
);
}
/**
* Handle consent update via AJAX
*/
public function handle_consent_update() {
// Verify nonce
if (!wp_verify_nonce($_POST['nonce'], 'tigerstyle_whiskers_consent')) {
wp_die(__('Security check failed', 'tigerstyle-whiskers'));
}
$consent_data = array(
'necessary' => true, // Always true
'analytics' => isset($_POST['analytics']) && $_POST['analytics'] === 'true',
'marketing' => isset($_POST['marketing']) && $_POST['marketing'] === 'true',
'preferences' => isset($_POST['preferences']) && $_POST['preferences'] === 'true',
'timestamp' => current_time('timestamp'),
'version' => TIGERSTYLE_WHISKERS_VERSION,
'ip_hash' => hash('sha256', TigerStyleWhiskers_BoundaryDetector::instance()->get_visitor_ip()),
'user_agent_hash' => hash('sha256', $_SERVER['HTTP_USER_AGENT'] ?? ''),
);
// Set consent cookie (365 days)
setcookie(
'tigerstyle_whiskers_consent',
json_encode($consent_data),
time() + (365 * 24 * 60 * 60),
'/',
'',
is_ssl(),
false // Not HTTP-only so JavaScript can read it
);
// Log consent (for audit trail)
$this->log_consent_change($consent_data, 'user_choice');
// Trigger analytics integration if consent given
if ($consent_data['analytics']) {
$this->trigger_analytics_activation();
}
wp_send_json_success(array(
'message' => __('Your preferences have been saved with feline precision!', 'tigerstyle-whiskers'),
'consent' => $consent_data
));
}
/**
* Log consent change for audit trail
*/
private function log_consent_change($consent_data, $trigger) {
if (class_exists('TigerStyleWhiskers_AuditTrail')) {
TigerStyleWhiskers_AuditTrail::log_event(array(
'event_type' => 'consent_change',
'trigger' => $trigger,
'consent_data' => $consent_data,
'ip_hash' => $consent_data['ip_hash'],
'user_agent_hash' => $consent_data['user_agent_hash'],
'timestamp' => $consent_data['timestamp']
));
}
}
/**
* Trigger analytics activation
*/
private function trigger_analytics_activation() {
// Integrate with TigerStyle Heat if available
if (class_exists('TigerStyleSEO_Google_setup')) {
// Analytics can now be activated
do_action('tigerstyle_whiskers_analytics_consent_granted');
}
}
/**
* Integrate with analytics
*/
public function integrate_with_analytics() {
// Check if analytics consent is given
if ($this->has_analytics_consent()) {
// Allow analytics to run
add_filter('tigerstyle_heat_analytics_allowed', '__return_true');
} else {
// Block analytics
add_filter('tigerstyle_heat_analytics_allowed', '__return_false');
// Remove analytics hooks if GDPR applies
if (TigerStyleWhiskers_BoundaryDetector::is_gdpr_territory()) {
$this->block_analytics_until_consent();
}
}
}
/**
* Check if analytics consent is given
*/
public function has_analytics_consent() {
return isset($this->consent_status['analytics']) && $this->consent_status['analytics'] === true;
}
/**
* Block analytics until consent
*/
private function block_analytics_until_consent() {
// Remove Google Analytics injection
if (class_exists('TigerStyleSEO_Google_setup')) {
$google_setup = TigerStyleSEO_Google_setup::instance();
remove_action('wp_head', array($google_setup, 'inject_google_analytics'), 2);
}
// Add placeholder script that activates after consent
add_action('wp_head', array($this, 'inject_consent_conditional_analytics'), 2);
}
/**
* Inject consent-conditional analytics
*/
public function inject_consent_conditional_analytics() {
if (!class_exists('TigerStyleSEO_Google_setup')) {
return;
}
$analytics_id = get_option('google_analytics_id', '');
if (empty($analytics_id)) {
return;
}
?>
<!-- TigerStyle Whiskers: Consent-Conditional Analytics -->
<script type="text/javascript">
// Wait for consent before initializing analytics
function tigerstyleWhiskersInitAnalytics() {
if (tigerstyleWhiskersConsent.hasAnalyticsConsent()) {
// Initialize Google Analytics
<?php if (strpos($analytics_id, 'G-') === 0): ?>
// GA4 Implementation
var script = document.createElement('script');
script.async = true;
script.src = 'https://www.googletagmanager.com/gtag/js?id=<?php echo esc_js($analytics_id); ?>';
document.head.appendChild(script);
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '<?php echo esc_js($analytics_id); ?>');
<?php endif; ?>
console.log('TigerStyle Whiskers: Analytics activated with user consent!');
}
}
// Check consent on page load
document.addEventListener('DOMContentLoaded', tigerstyleWhiskersInitAnalytics);
// Listen for consent changes
document.addEventListener('tigerstyleWhiskersConsentChanged', tigerstyleWhiskersInitAnalytics);
</script>
<?php
}
/**
* Get consent settings
*/
private function get_consent_settings() {
return array_merge(array(
'banner_title' => '',
'banner_message' => '',
'privacy_policy_url' => '',
'cookie_policy_url' => '',
'banner_position' => 'bottom',
'banner_style' => 'modern',
'auto_hide_delay' => 0,
), get_option('tigerstyle_whiskers_consent_settings', array()));
}
/**
* Handle settings update from admin
*/
public function handle_settings_update() {
// Verify nonce and permissions
if (!wp_verify_nonce($_POST['consent_settings_nonce'], 'update_consent_settings') ||
!current_user_can('manage_options')) {
wp_die(__('Security check failed', 'tigerstyle-whiskers'));
}
$settings = array(
'banner_title' => sanitize_text_field($_POST['banner_title'] ?? ''),
'banner_message' => wp_kses_post($_POST['banner_message'] ?? ''),
'privacy_policy_url' => esc_url_raw($_POST['privacy_policy_url'] ?? ''),
'cookie_policy_url' => esc_url_raw($_POST['cookie_policy_url'] ?? ''),
'banner_position' => sanitize_text_field($_POST['banner_position'] ?? 'bottom'),
'banner_style' => sanitize_text_field($_POST['banner_style'] ?? 'modern'),
'auto_hide_delay' => intval($_POST['auto_hide_delay'] ?? 0),
);
update_option('tigerstyle_whiskers_consent_settings', $settings);
wp_redirect(admin_url('admin.php?page=tigerstyle-whiskers&tab=consent&message=settings_updated'));
exit;
}
/**
* Render admin settings page
*/
public function render_admin_page() {
$settings = $this->get_consent_settings();
?>
<div class="tigerstyle-whiskers-admin-section">
<h2><?php _e('Cookie Consent Management', 'tigerstyle-whiskers'); ?></h2>
<p class="description">
<?php _e('Configure cookie consent banners with feline finesse. These settings control how visitors give consent for data processing.', 'tigerstyle-whiskers'); ?>
</p>
<form method="post" action="<?php echo admin_url('admin-post.php'); ?>">
<input type="hidden" name="action" value="update_cookie_consent_settings">
<?php wp_nonce_field('update_consent_settings', 'consent_settings_nonce'); ?>
<table class="form-table">
<tr>
<th scope="row">
<label for="banner_title"><?php _e('Banner Title', 'tigerstyle-whiskers'); ?></label>
</th>
<td>
<input type="text"
id="banner_title"
name="banner_title"
value="<?php echo esc_attr($settings['banner_title']); ?>"
class="regular-text"
placeholder="<?php _e('We Respect Your Privacy', 'tigerstyle-whiskers'); ?>">
</td>
</tr>
<tr>
<th scope="row">
<label for="banner_message"><?php _e('Banner Message', 'tigerstyle-whiskers'); ?></label>
</th>
<td>
<textarea id="banner_message"
name="banner_message"
rows="4"
class="large-text"
placeholder="<?php echo esc_attr($this->get_default_banner_message()); ?>"><?php echo esc_textarea($settings['banner_message']); ?></textarea>
</td>
</tr>
<tr>
<th scope="row">
<label for="privacy_policy_url"><?php _e('Privacy Policy URL', 'tigerstyle-whiskers'); ?></label>
</th>
<td>
<input type="url"
id="privacy_policy_url"
name="privacy_policy_url"
value="<?php echo esc_attr($settings['privacy_policy_url']); ?>"
class="regular-text"
placeholder="<?php echo esc_attr(get_privacy_policy_url()); ?>">
</td>
</tr>
</table>
<?php submit_button(__('Save Consent Settings', 'tigerstyle-whiskers')); ?>
</form>
</div>
<?php
}
/**
* Get current consent status for external use
*/
public function get_consent_status() {
return $this->consent_status;
}
/**
* Check if specific consent is given
*/
public function has_consent_for($category) {
return isset($this->consent_status[$category]) && $this->consent_status[$category] === true;
}
}