tigerstyle-whiskers/includes/whiskers/class-consent-analytics.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

1405 lines
54 KiB
PHP

<?php
/**
* Consent Analytics Dashboard Whisker for TigerStyle Whiskers
*
* Beautiful analytics with feline precision - track consent patterns like
* a cat observing and understanding behavioral patterns in its territory
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
class TigerStyleWhiskers_ConsentAnalytics {
/**
* Single instance
*/
private static $instance = null;
/**
* Analytics collection enabled
*/
private $analytics_enabled = true;
/**
* Dashboard data cache
*/
private $dashboard_cache = array();
/**
* 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_analytics();
}
/**
* Initialize consent analytics
*/
private function init_consent_analytics() {
// Hook into consent events
add_action('tigerstyle_whiskers_consent_granted', array($this, 'track_consent_event'));
add_action('tigerstyle_whiskers_consent_withdrawn', array($this, 'track_consent_withdrawal'));
add_action('tigerstyle_whiskers_consent_updated', array($this, 'track_consent_update'));
// Admin hooks for dashboard
if (is_admin()) {
add_action('wp_ajax_tigerstyle_whiskers_get_analytics_data', array($this, 'ajax_get_analytics_data'));
add_action('wp_ajax_tigerstyle_whiskers_export_analytics', array($this, 'ajax_export_analytics'));
}
// Daily aggregation
add_action('tigerstyle_whiskers_daily_analytics_aggregation', array($this, 'aggregate_daily_stats'));
if (!wp_next_scheduled('tigerstyle_whiskers_daily_analytics_aggregation')) {
wp_schedule_event(time(), 'daily', 'tigerstyle_whiskers_daily_analytics_aggregation');
}
// Frontend tracking (only with consent)
add_action('wp_footer', array($this, 'inject_analytics_tracking'));
if (defined('WP_DEBUG') && WP_DEBUG) {
error_log('TigerStyle Whiskers: Consent analytics whisker is observing patterns with feline intelligence!');
}
}
/**
* Track consent event
*/
public function track_consent_event($consent_data) {
if (!$this->analytics_enabled) {
return;
}
$event_data = array(
'event_type' => 'consent_granted',
'timestamp' => current_time('timestamp'),
'consent_categories' => $consent_data,
'user_agent_hash' => hash('sha256', $_SERVER['HTTP_USER_AGENT'] ?? ''),
'ip_hash' => hash('sha256', $this->get_visitor_ip()),
'session_id' => $this->get_session_id(),
'geo_data' => $this->get_geo_context(),
'referrer_hash' => hash('sha256', $_SERVER['HTTP_REFERER'] ?? ''),
'page_url_hash' => hash('sha256', $_SERVER['REQUEST_URI'] ?? '')
);
$this->store_analytics_event($event_data);
$this->update_real_time_stats($event_data);
}
/**
* Track consent withdrawal
*/
public function track_consent_withdrawal($withdrawal_data) {
if (!$this->analytics_enabled) {
return;
}
$event_data = array(
'event_type' => 'consent_withdrawn',
'timestamp' => current_time('timestamp'),
'withdrawn_categories' => $withdrawal_data['categories'] ?? array(),
'reason' => $withdrawal_data['reason'] ?? '',
'session_id' => $this->get_session_id(),
'geo_data' => $this->get_geo_context()
);
$this->store_analytics_event($event_data);
$this->update_real_time_stats($event_data);
}
/**
* Store analytics event in database
*/
private function store_analytics_event($event_data) {
global $wpdb;
$table_name = $wpdb->prefix . 'tigerstyle_whiskers_consent_analytics';
// Create table if it doesn't exist
$this->create_analytics_table();
$wpdb->insert(
$table_name,
array(
'event_type' => $event_data['event_type'],
'event_data' => json_encode($event_data),
'timestamp' => date('Y-m-d H:i:s', $event_data['timestamp']),
'date_created' => date('Y-m-d', $event_data['timestamp']),
'hour_created' => date('H', $event_data['timestamp']),
'country_code' => $event_data['geo_data']['country_code'] ?? 'UNKNOWN',
'session_id_hash' => hash('sha256', $event_data['session_id'] ?? '')
),
array('%s', '%s', '%s', '%s', '%s', '%s', '%s')
);
}
/**
* Create analytics table
*/
private function create_analytics_table() {
global $wpdb;
$table_name = $wpdb->prefix . 'tigerstyle_whiskers_consent_analytics';
$charset_collate = $wpdb->get_charset_collate();
$sql = "CREATE TABLE IF NOT EXISTS $table_name (
id bigint(20) NOT NULL AUTO_INCREMENT,
event_type varchar(50) NOT NULL,
event_data longtext NOT NULL,
timestamp datetime NOT NULL,
date_created date NOT NULL,
hour_created tinyint(2) NOT NULL,
country_code varchar(2) NOT NULL,
session_id_hash varchar(64) NOT NULL,
PRIMARY KEY (id),
KEY event_type (event_type),
KEY date_created (date_created),
KEY hour_created (hour_created),
KEY country_code (country_code),
KEY timestamp (timestamp)
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($sql);
// Create daily aggregation table
$aggregation_table = $wpdb->prefix . 'tigerstyle_whiskers_consent_analytics_daily';
$sql_agg = "CREATE TABLE IF NOT EXISTS $aggregation_table (
id bigint(20) NOT NULL AUTO_INCREMENT,
date_aggregated date NOT NULL,
total_consent_requests bigint(20) DEFAULT 0,
total_consent_granted bigint(20) DEFAULT 0,
total_consent_denied bigint(20) DEFAULT 0,
analytics_consent_rate decimal(5,2) DEFAULT 0,
marketing_consent_rate decimal(5,2) DEFAULT 0,
preferences_consent_rate decimal(5,2) DEFAULT 0,
top_countries text,
hourly_distribution text,
consent_categories_breakdown text,
created_at timestamp DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY date_aggregated (date_aggregated)
) $charset_collate;";
dbDelta($sql_agg);
}
/**
* Update real-time statistics
*/
private function update_real_time_stats($event_data) {
$stats_key = 'tigerstyle_whiskers_realtime_stats';
$current_stats = get_option($stats_key, array(
'today_total' => 0,
'today_accepted' => 0,
'today_denied' => 0,
'last_updated' => time(),
'hourly_breakdown' => array_fill(0, 24, 0)
));
$current_hour = date('H', $event_data['timestamp']);
if ($event_data['event_type'] === 'consent_granted') {
$current_stats['today_total']++;
$current_stats['today_accepted']++;
$current_stats['hourly_breakdown'][$current_hour]++;
} elseif ($event_data['event_type'] === 'consent_denied') {
$current_stats['today_total']++;
$current_stats['today_denied']++;
$current_stats['hourly_breakdown'][$current_hour]++;
}
$current_stats['last_updated'] = time();
update_option($stats_key, $current_stats);
}
/**
* Get analytics dashboard data
*/
public function get_dashboard_data($date_range = '30_days') {
$cache_key = 'tigerstyle_whiskers_dashboard_' . $date_range;
$cached_data = get_transient($cache_key);
if ($cached_data !== false) {
return $cached_data;
}
global $wpdb;
$analytics_table = $wpdb->prefix . 'tigerstyle_whiskers_consent_analytics';
$aggregation_table = $wpdb->prefix . 'tigerstyle_whiskers_consent_analytics_daily';
// Calculate date range
$end_date = current_time('Y-m-d');
$start_date = date('Y-m-d', strtotime("-{$date_range}", strtotime($end_date)));
$dashboard_data = array(
'overview' => $this->get_overview_stats($start_date, $end_date),
'consent_trends' => $this->get_consent_trends($start_date, $end_date),
'geographic_distribution' => $this->get_geographic_distribution($start_date, $end_date),
'category_preferences' => $this->get_category_preferences($start_date, $end_date),
'hourly_patterns' => $this->get_hourly_patterns($start_date, $end_date),
'device_analysis' => $this->get_device_analysis($start_date, $end_date),
'compliance_insights' => $this->get_compliance_insights($start_date, $end_date),
'performance_metrics' => $this->get_performance_metrics($start_date, $end_date)
);
// Cache for 1 hour
set_transient($cache_key, $dashboard_data, 3600);
return $dashboard_data;
}
/**
* Get overview statistics
*/
private function get_overview_stats($start_date, $end_date) {
global $wpdb;
$table_name = $wpdb->prefix . 'tigerstyle_whiskers_consent_analytics';
$total_events = $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(*) FROM {$table_name} WHERE date_created BETWEEN %s AND %s",
$start_date, $end_date
));
$consent_granted = $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(*) FROM {$table_name} WHERE event_type = 'consent_granted' AND date_created BETWEEN %s AND %s",
$start_date, $end_date
));
$consent_denied = $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(*) FROM {$table_name} WHERE event_type = 'consent_denied' AND date_created BETWEEN %s AND %s",
$start_date, $end_date
));
$unique_sessions = $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(DISTINCT session_id_hash) FROM {$table_name} WHERE date_created BETWEEN %s AND %s",
$start_date, $end_date
));
$acceptance_rate = $total_events > 0 ? round(($consent_granted / $total_events) * 100, 2) : 0;
return array(
'total_consent_requests' => intval($total_events),
'total_accepted' => intval($consent_granted),
'total_denied' => intval($consent_denied),
'unique_visitors' => intval($unique_sessions),
'acceptance_rate' => $acceptance_rate,
'avg_daily_requests' => $this->calculate_avg_daily_requests($total_events, $start_date, $end_date)
);
}
/**
* Get consent trends over time
*/
private function get_consent_trends($start_date, $end_date) {
global $wpdb;
$table_name = $wpdb->prefix . 'tigerstyle_whiskers_consent_analytics';
$trends = $wpdb->get_results($wpdb->prepare(
"SELECT
date_created,
COUNT(*) as total_requests,
SUM(CASE WHEN event_type = 'consent_granted' THEN 1 ELSE 0 END) as accepted,
SUM(CASE WHEN event_type = 'consent_denied' THEN 1 ELSE 0 END) as denied
FROM {$table_name}
WHERE date_created BETWEEN %s AND %s
GROUP BY date_created
ORDER BY date_created ASC",
$start_date, $end_date
), ARRAY_A);
// Fill in missing dates with zero values
$filled_trends = $this->fill_missing_dates($trends, $start_date, $end_date);
return array(
'daily_trends' => $filled_trends,
'trend_analysis' => $this->analyze_trends($filled_trends)
);
}
/**
* Get geographic distribution
*/
private function get_geographic_distribution($start_date, $end_date) {
global $wpdb;
$table_name = $wpdb->prefix . 'tigerstyle_whiskers_consent_analytics';
$geo_stats = $wpdb->get_results($wpdb->prepare(
"SELECT
country_code,
COUNT(*) as total_requests,
SUM(CASE WHEN event_type = 'consent_granted' THEN 1 ELSE 0 END) as accepted,
ROUND((SUM(CASE WHEN event_type = 'consent_granted' THEN 1 ELSE 0 END) / COUNT(*)) * 100, 2) as acceptance_rate
FROM {$table_name}
WHERE date_created BETWEEN %s AND %s AND country_code != 'UNKNOWN'
GROUP BY country_code
ORDER BY total_requests DESC
LIMIT 20",
$start_date, $end_date
), ARRAY_A);
// Enhance with country names and GDPR status
foreach ($geo_stats as &$stat) {
$stat['country_name'] = $this->get_country_name($stat['country_code']);
$stat['is_gdpr_territory'] = $this->is_gdpr_territory($stat['country_code']);
$stat['privacy_law'] = $this->get_applicable_privacy_law($stat['country_code']);
}
return array(
'top_countries' => $geo_stats,
'gdpr_vs_non_gdpr' => $this->analyze_gdpr_vs_non_gdpr($geo_stats),
'regional_insights' => $this->get_regional_insights($geo_stats)
);
}
/**
* Get category preferences analysis
*/
private function get_category_preferences($start_date, $end_date) {
global $wpdb;
$table_name = $wpdb->prefix . 'tigerstyle_whiskers_consent_analytics';
$category_stats = array(
'analytics' => 0,
'marketing' => 0,
'preferences' => 0
);
$events = $wpdb->get_results($wpdb->prepare(
"SELECT event_data FROM {$table_name}
WHERE event_type = 'consent_granted' AND date_created BETWEEN %s AND %s",
$start_date, $end_date
));
$total_consents = count($events);
foreach ($events as $event) {
$data = json_decode($event->event_data, true);
$consent_categories = $data['consent_categories'] ?? array();
foreach ($category_stats as $category => $count) {
if (!empty($consent_categories[$category])) {
$category_stats[$category]++;
}
}
}
// Calculate percentages
$category_percentages = array();
foreach ($category_stats as $category => $count) {
$category_percentages[$category] = $total_consents > 0 ?
round(($count / $total_consents) * 100, 2) : 0;
}
return array(
'category_counts' => $category_stats,
'category_percentages' => $category_percentages,
'total_consents' => $total_consents,
'insights' => $this->analyze_category_preferences($category_percentages)
);
}
/**
* Get hourly patterns
*/
private function get_hourly_patterns($start_date, $end_date) {
global $wpdb;
$table_name = $wpdb->prefix . 'tigerstyle_whiskers_consent_analytics';
$hourly_stats = $wpdb->get_results($wpdb->prepare(
"SELECT
hour_created as hour,
COUNT(*) as total_requests,
SUM(CASE WHEN event_type = 'consent_granted' THEN 1 ELSE 0 END) as accepted
FROM {$table_name}
WHERE date_created BETWEEN %s AND %s
GROUP BY hour_created
ORDER BY hour_created ASC",
$start_date, $end_date
), ARRAY_A);
// Fill in missing hours
$hourly_data = array_fill(0, 24, array('hour' => 0, 'total_requests' => 0, 'accepted' => 0));
foreach ($hourly_stats as $stat) {
$hour = intval($stat['hour']);
$hourly_data[$hour] = $stat;
}
return array(
'hourly_distribution' => $hourly_data,
'peak_hours' => $this->identify_peak_hours($hourly_data),
'patterns_analysis' => $this->analyze_hourly_patterns($hourly_data)
);
}
/**
* AJAX handler for analytics data
*/
public function ajax_get_analytics_data() {
if (!current_user_can('manage_options')) {
wp_send_json_error('Insufficient permissions');
}
if (!wp_verify_nonce($_POST['nonce'], 'tigerstyle_whiskers_analytics')) {
wp_send_json_error('Security check failed');
}
$date_range = sanitize_text_field($_POST['date_range'] ?? '30_days');
$data_type = sanitize_text_field($_POST['data_type'] ?? 'overview');
$dashboard_data = $this->get_dashboard_data($date_range);
if (isset($dashboard_data[$data_type])) {
wp_send_json_success($dashboard_data[$data_type]);
} else {
wp_send_json_success($dashboard_data);
}
}
/**
* Inject analytics tracking script
*/
public function inject_analytics_tracking() {
// Only inject if consent analytics is enabled and we have consent
if (!$this->analytics_enabled) {
return;
}
?>
<!-- TigerStyle Whiskers: Consent Analytics Tracking -->
<script type="text/javascript">
(function() {
// Track consent banner interactions
document.addEventListener('tigerstyleWhiskersConsentChanged', function(event) {
const consentData = event.detail;
// Send analytics data to server
fetch('<?php echo admin_url('admin-ajax.php'); ?>', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
action: 'tigerstyle_whiskers_track_consent',
nonce: '<?php echo wp_create_nonce('tigerstyle_whiskers_analytics'); ?>',
consent_data: JSON.stringify(consentData),
page_url: window.location.href,
referrer: document.referrer,
timestamp: Date.now()
})
}).catch(function(error) {
console.log('TigerStyle Whiskers: Analytics tracking failed', error);
});
});
// Track banner display time
let bannerShowTime = null;
const banner = document.getElementById('tigerstyle-whiskers-consent-overlay');
if (banner) {
const observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.type === 'attributes' && mutation.attributeName === 'style') {
const isVisible = banner.style.display !== 'none' &&
banner.offsetHeight > 0;
if (isVisible && !bannerShowTime) {
bannerShowTime = Date.now();
} else if (!isVisible && bannerShowTime) {
const displayDuration = Date.now() - bannerShowTime;
// Track banner display duration
fetch('<?php echo admin_url('admin-ajax.php'); ?>', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
action: 'tigerstyle_whiskers_track_banner_interaction',
nonce: '<?php echo wp_create_nonce('tigerstyle_whiskers_analytics'); ?>',
interaction_type: 'banner_hidden',
duration: displayDuration,
timestamp: Date.now()
})
});
bannerShowTime = null;
}
}
});
});
observer.observe(banner, { attributes: true });
}
})();
</script>
<?php
}
/**
* Render analytics dashboard admin page
*/
public function render_admin_page() {
$dashboard_data = $this->get_dashboard_data('30_days');
?>
<div class="tigerstyle-whiskers-admin-section">
<h2><?php _e('Consent Analytics Dashboard', 'tigerstyle-whiskers'); ?></h2>
<p class="description">
<?php _e('Beautiful analytics with feline precision - understand consent patterns and optimize your privacy experience.', 'tigerstyle-whiskers'); ?>
</p>
<?php $this->render_analytics_controls(); ?>
<?php $this->render_overview_cards($dashboard_data['overview']); ?>
<?php $this->render_consent_trends_chart($dashboard_data['consent_trends']); ?>
<?php $this->render_geographic_map($dashboard_data['geographic_distribution']); ?>
<?php $this->render_category_analysis($dashboard_data['category_preferences']); ?>
<?php $this->render_hourly_patterns($dashboard_data['hourly_patterns']); ?>
</div>
<style>
.tw-analytics-dashboard {
background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
border-radius: 12px;
padding: 24px;
margin: 20px 0;
border: 2px solid #ff6b35;
}
.tw-analytics-cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin: 20px 0;
}
.tw-analytics-card {
background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
border-radius: 12px;
padding: 20px;
box-shadow: 0 4px 12px rgba(255, 107, 53, 0.1);
border-left: 4px solid #ff6b35;
}
.tw-analytics-card h3 {
margin: 0 0 10px 0;
color: #2c3e50;
font-size: 14px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.tw-analytics-number {
font-size: 32px;
font-weight: 700;
color: #ff6b35;
margin: 0;
}
.tw-analytics-label {
font-size: 12px;
color: #666;
margin-top: 5px;
}
</style>
<?php
}
/**
* Render overview cards
*/
private function render_overview_cards($overview_data) {
?>
<div class="tw-analytics-cards">
<div class="tw-analytics-card">
<h3><?php _e('Total Requests', 'tigerstyle-whiskers'); ?></h3>
<div class="tw-analytics-number"><?php echo number_format($overview_data['total_consent_requests']); ?></div>
<div class="tw-analytics-label"><?php _e('Consent banners shown', 'tigerstyle-whiskers'); ?></div>
</div>
<div class="tw-analytics-card">
<h3><?php _e('Acceptance Rate', 'tigerstyle-whiskers'); ?></h3>
<div class="tw-analytics-number"><?php echo $overview_data['acceptance_rate']; ?>%</div>
<div class="tw-analytics-label"><?php _e('Users who accepted', 'tigerstyle-whiskers'); ?></div>
</div>
<div class="tw-analytics-card">
<h3><?php _e('Unique Visitors', 'tigerstyle-whiskers'); ?></h3>
<div class="tw-analytics-number"><?php echo number_format($overview_data['unique_visitors']); ?></div>
<div class="tw-analytics-label"><?php _e('Unique sessions tracked', 'tigerstyle-whiskers'); ?></div>
</div>
<div class="tw-analytics-card">
<h3><?php _e('Daily Average', 'tigerstyle-whiskers'); ?></h3>
<div class="tw-analytics-number"><?php echo number_format($overview_data['avg_daily_requests'], 1); ?></div>
<div class="tw-analytics-label"><?php _e('Requests per day', 'tigerstyle-whiskers'); ?></div>
</div>
</div>
<?php
}
// Additional rendering methods would continue here...
// (charts, maps, controls, etc.)
/**
* Helper methods
*/
private function get_visitor_ip() {
if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
return $_SERVER['HTTP_CLIENT_IP'];
} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
return $_SERVER['HTTP_X_FORWARDED_FOR'];
} elseif (!empty($_SERVER['REMOTE_ADDR'])) {
return $_SERVER['REMOTE_ADDR'];
}
return '127.0.0.1';
}
private function get_session_id() {
if (!session_id()) {
session_start();
}
return session_id();
}
private function get_geo_context() {
if (class_exists('TigerStyleWhiskers_AdvancedGeoDetector')) {
$geo_detector = TigerStyleWhiskers_AdvancedGeoDetector::instance();
return $geo_detector->get_current_detection();
}
return array('country_code' => 'UNKNOWN');
}
/**
* Calculate average daily requests
*/
private function calculate_avg_daily_requests($total_events, $start_date, $end_date) {
$start = new DateTime($start_date);
$end = new DateTime($end_date);
$days = $start->diff($end)->days + 1;
return $days > 0 ? round($total_events / $days, 2) : 0;
}
/**
* Fill in missing dates with zero values
*/
private function fill_missing_dates($trends, $start_date, $end_date) {
$filled_trends = array();
$start = new DateTime($start_date);
$end = new DateTime($end_date);
while ($start <= $end) {
$date = $start->format('Y-m-d');
$found = false;
foreach ($trends as $trend) {
if ($trend['date_created'] === $date) {
$filled_trends[] = $trend;
$found = true;
break;
}
}
if (!$found) {
$filled_trends[] = array(
'date_created' => $date,
'total_requests' => 0,
'accepted' => 0,
'denied' => 0
);
}
$start->modify('+1 day');
}
return $filled_trends;
}
/**
* Analyze trends data
*/
private function analyze_trends($trends) {
if (empty($trends)) {
return array(
'trend_direction' => 'stable',
'growth_rate' => 0,
'insights' => array()
);
}
$total_requests = array_sum(array_column($trends, 'total_requests'));
$total_accepted = array_sum(array_column($trends, 'accepted'));
// Calculate week-over-week change
$weeks = array_chunk($trends, 7);
$growth_rate = 0;
if (count($weeks) >= 2) {
$first_week = array_sum(array_column($weeks[0], 'total_requests'));
$last_week = array_sum(array_column(end($weeks), 'total_requests'));
if ($first_week > 0) {
$growth_rate = round((($last_week - $first_week) / $first_week) * 100, 2);
}
}
$trend_direction = 'stable';
if ($growth_rate > 5) {
$trend_direction = 'increasing';
} elseif ($growth_rate < -5) {
$trend_direction = 'decreasing';
}
return array(
'trend_direction' => $trend_direction,
'growth_rate' => $growth_rate,
'insights' => array(
'total_requests' => $total_requests,
'total_accepted' => $total_accepted,
'acceptance_rate' => $total_requests > 0 ? round(($total_accepted / $total_requests) * 100, 2) : 0
)
);
}
/**
* Analyze category preferences
*/
private function analyze_category_preferences($category_percentages) {
$insights = array();
foreach ($category_percentages as $category => $percentage) {
if ($percentage > 80) {
$insights[] = sprintf(__('%s cookies are widely accepted (%s%%)', 'tigerstyle-whiskers'), ucfirst($category), $percentage);
} elseif ($percentage < 20) {
$insights[] = sprintf(__('%s cookies have low acceptance (%s%%)', 'tigerstyle-whiskers'), ucfirst($category), $percentage);
}
}
if (empty($insights)) {
$insights[] = __('Category preferences are evenly distributed', 'tigerstyle-whiskers');
}
return $insights;
}
/**
* Identify peak hours
*/
private function identify_peak_hours($hourly_data) {
$peak_hours = array();
$max_requests = max(array_column($hourly_data, 'total_requests'));
if ($max_requests > 0) {
foreach ($hourly_data as $hour_data) {
if ($hour_data['total_requests'] >= $max_requests * 0.8) {
$peak_hours[] = $hour_data['hour'];
}
}
}
return $peak_hours;
}
/**
* Analyze hourly patterns
*/
private function analyze_hourly_patterns($hourly_data) {
$morning_requests = 0; // 6-12
$afternoon_requests = 0; // 12-18
$evening_requests = 0; // 18-24
$night_requests = 0; // 0-6
foreach ($hourly_data as $hour_data) {
$hour = intval($hour_data['hour']);
$requests = intval($hour_data['total_requests']);
if ($hour >= 6 && $hour < 12) {
$morning_requests += $requests;
} elseif ($hour >= 12 && $hour < 18) {
$afternoon_requests += $requests;
} elseif ($hour >= 18 && $hour < 24) {
$evening_requests += $requests;
} else {
$night_requests += $requests;
}
}
$total = $morning_requests + $afternoon_requests + $evening_requests + $night_requests;
return array(
'periods' => array(
'morning' => array('requests' => $morning_requests, 'percentage' => $total > 0 ? round(($morning_requests / $total) * 100, 2) : 0),
'afternoon' => array('requests' => $afternoon_requests, 'percentage' => $total > 0 ? round(($afternoon_requests / $total) * 100, 2) : 0),
'evening' => array('requests' => $evening_requests, 'percentage' => $total > 0 ? round(($evening_requests / $total) * 100, 2) : 0),
'night' => array('requests' => $night_requests, 'percentage' => $total > 0 ? round(($night_requests / $total) * 100, 2) : 0)
),
'peak_period' => $this->get_peak_period($morning_requests, $afternoon_requests, $evening_requests, $night_requests)
);
}
/**
* Get peak period
*/
private function get_peak_period($morning, $afternoon, $evening, $night) {
$periods = array(
'morning' => $morning,
'afternoon' => $afternoon,
'evening' => $evening,
'night' => $night
);
return array_search(max($periods), $periods);
}
/**
* Get country name from country code
*/
private function get_country_name($country_code) {
$countries = array(
'US' => 'United States',
'CA' => 'Canada',
'GB' => 'United Kingdom',
'DE' => 'Germany',
'FR' => 'France',
'IT' => 'Italy',
'ES' => 'Spain',
'NL' => 'Netherlands',
'BE' => 'Belgium',
'AU' => 'Australia',
'JP' => 'Japan',
'CN' => 'China',
'IN' => 'India',
'BR' => 'Brazil',
'MX' => 'Mexico',
'AR' => 'Argentina',
'RU' => 'Russia',
'KR' => 'South Korea',
'SG' => 'Singapore',
'HK' => 'Hong Kong',
'TW' => 'Taiwan',
'TH' => 'Thailand',
'MY' => 'Malaysia',
'ID' => 'Indonesia',
'PH' => 'Philippines',
'VN' => 'Vietnam',
'ZA' => 'South Africa',
'EG' => 'Egypt',
'NG' => 'Nigeria',
'KE' => 'Kenya'
);
return isset($countries[$country_code]) ? $countries[$country_code] : $country_code;
}
/**
* Check if country is in GDPR territory
*/
private function is_gdpr_territory($country_code) {
$gdpr_countries = array(
'AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'DK', 'EE', 'FI', 'FR',
'DE', 'GR', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', 'MT', 'NL',
'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'SE', 'IS', 'LI', 'NO'
);
return in_array($country_code, $gdpr_countries);
}
/**
* Get applicable privacy law for country
*/
private function get_applicable_privacy_law($country_code) {
if ($this->is_gdpr_territory($country_code)) {
return 'GDPR';
}
$privacy_laws = array(
'US' => 'CCPA/CPRA',
'CA' => 'PIPEDA',
'AU' => 'Privacy Act',
'JP' => 'APPI',
'KR' => 'PIPA',
'BR' => 'LGPD',
'IN' => 'PDPB',
'ZA' => 'POPIA',
'SG' => 'PDPA',
'MY' => 'PDPA',
'TH' => 'PDPA',
'PH' => 'DPA',
'VN' => 'Cybersecurity Law',
'CN' => 'PIPL',
'RU' => 'Personal Data Law'
);
return isset($privacy_laws[$country_code]) ? $privacy_laws[$country_code] : 'General Privacy Laws';
}
/**
* Analyze GDPR vs non-GDPR statistics
*/
private function analyze_gdpr_vs_non_gdpr($geo_stats) {
$gdpr_stats = array(
'total_requests' => 0,
'total_accepted' => 0,
'acceptance_rate' => 0
);
$non_gdpr_stats = array(
'total_requests' => 0,
'total_accepted' => 0,
'acceptance_rate' => 0
);
foreach ($geo_stats as $stat) {
if ($this->is_gdpr_territory($stat['country_code'])) {
$gdpr_stats['total_requests'] += intval($stat['total_requests']);
$gdpr_stats['total_accepted'] += intval($stat['accepted']);
} else {
$non_gdpr_stats['total_requests'] += intval($stat['total_requests']);
$non_gdpr_stats['total_accepted'] += intval($stat['accepted']);
}
}
$gdpr_stats['acceptance_rate'] = $gdpr_stats['total_requests'] > 0 ?
round(($gdpr_stats['total_accepted'] / $gdpr_stats['total_requests']) * 100, 2) : 0;
$non_gdpr_stats['acceptance_rate'] = $non_gdpr_stats['total_requests'] > 0 ?
round(($non_gdpr_stats['total_accepted'] / $non_gdpr_stats['total_requests']) * 100, 2) : 0;
return array(
'gdpr' => $gdpr_stats,
'non_gdpr' => $non_gdpr_stats,
'comparison' => array(
'acceptance_difference' => $gdpr_stats['acceptance_rate'] - $non_gdpr_stats['acceptance_rate'],
'total_requests_ratio' => $non_gdpr_stats['total_requests'] > 0 ?
round($gdpr_stats['total_requests'] / $non_gdpr_stats['total_requests'], 2) : 0
)
);
}
/**
* Get regional insights
*/
private function get_regional_insights($geo_stats) {
$insights = array();
$total_requests = array_sum(array_column($geo_stats, 'total_requests'));
$total_accepted = array_sum(array_column($geo_stats, 'accepted'));
if ($total_requests > 0) {
$overall_acceptance = round(($total_accepted / $total_requests) * 100, 2);
// Find countries with significantly different acceptance rates
foreach ($geo_stats as $stat) {
$country_acceptance = floatval($stat['acceptance_rate']);
$difference = $country_acceptance - $overall_acceptance;
if (abs($difference) > 10 && intval($stat['total_requests']) > 10) { // Only for statistically significant data
if ($difference > 0) {
$insights[] = sprintf(
__('%s shows %s%% higher consent acceptance than average (%s%%)', 'tigerstyle-whiskers'),
$this->get_country_name($stat['country_code']),
round($difference, 1),
$country_acceptance
);
} else {
$insights[] = sprintf(
__('%s shows %s%% lower consent acceptance than average (%s%%)', 'tigerstyle-whiskers'),
$this->get_country_name($stat['country_code']),
round(abs($difference), 1),
$country_acceptance
);
}
}
}
}
if (empty($insights)) {
$insights[] = __('Regional consent patterns are consistent across territories', 'tigerstyle-whiskers');
}
return $insights;
}
/**
* Get device analysis data
*/
private function get_device_analysis($start_date, $end_date) {
global $wpdb;
$table_name = $wpdb->prefix . 'tigerstyle_whiskers_consent_analytics';
// Get device type data from user agent analysis
$device_data = $wpdb->get_results($wpdb->prepare(
"SELECT
COUNT(*) as total_requests,
SUM(CASE WHEN event_type = 'consent_granted' THEN 1 ELSE 0 END) as accepted,
CASE
WHEN event_data LIKE '%%Mobile%%' OR event_data LIKE '%%Android%%' OR event_data LIKE '%%iPhone%%' THEN 'Mobile'
WHEN event_data LIKE '%%Tablet%%' OR event_data LIKE '%%iPad%%' THEN 'Tablet'
ELSE 'Desktop'
END as device_type
FROM {$table_name}
WHERE date_created BETWEEN %s AND %s
GROUP BY device_type
ORDER BY total_requests DESC",
$start_date, $end_date
), ARRAY_A);
$formatted_data = array();
$total_all_devices = 0;
// Check if we have data before iterating
if (!empty($device_data)) {
foreach ($device_data as $device) {
$total_requests = intval($device['total_requests']);
$accepted = intval($device['accepted']);
$acceptance_rate = $total_requests > 0 ? round(($accepted / $total_requests) * 100, 2) : 0;
$formatted_data[] = array(
'device_type' => $device['device_type'],
'total_requests' => $total_requests,
'accepted' => $accepted,
'denied' => $total_requests - $accepted,
'acceptance_rate' => $acceptance_rate
);
$total_all_devices += $total_requests;
}
}
// Add percentage of total traffic for each device
foreach ($formatted_data as &$device) {
$device['traffic_percentage'] = $total_all_devices > 0 ?
round(($device['total_requests'] / $total_all_devices) * 100, 2) : 0;
}
return array(
'devices' => $formatted_data,
'insights' => $this->get_device_insights($formatted_data)
);
}
/**
* Get device-specific insights
*/
private function get_device_insights($device_data) {
$insights = array();
if (empty($device_data)) {
$insights[] = __('No device data available for the selected period', 'tigerstyle-whiskers');
return $insights;
}
// Find device with highest acceptance rate
$highest_acceptance = 0;
$highest_device = '';
// Find device with most traffic
$highest_traffic = 0;
$traffic_leader = '';
foreach ($device_data as $device) {
if ($device['acceptance_rate'] > $highest_acceptance) {
$highest_acceptance = $device['acceptance_rate'];
$highest_device = $device['device_type'];
}
if ($device['traffic_percentage'] > $highest_traffic) {
$highest_traffic = $device['traffic_percentage'];
$traffic_leader = $device['device_type'];
}
}
if ($highest_device) {
$insights[] = sprintf(
__('%s users show the highest consent acceptance rate at %s%%', 'tigerstyle-whiskers'),
$highest_device,
$highest_acceptance
);
}
if ($traffic_leader) {
$insights[] = sprintf(
__('%s devices account for %s%% of total traffic', 'tigerstyle-whiskers'),
$traffic_leader,
$highest_traffic
);
}
// Check for mobile-specific patterns
foreach ($device_data as $device) {
if ($device['device_type'] === 'Mobile' && $device['acceptance_rate'] < 50) {
$insights[] = __('Mobile users show lower consent acceptance - consider optimizing mobile consent experience', 'tigerstyle-whiskers');
break;
}
}
return $insights;
}
/**
* Get compliance insights
*/
private function get_compliance_insights($start_date, $end_date) {
global $wpdb;
$table_name = $wpdb->prefix . 'tigerstyle_whiskers_consent_analytics';
$insights = array();
// Get overall statistics
$total_requests = $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(*) FROM {$table_name} WHERE date_created BETWEEN %s AND %s",
$start_date, $end_date
));
$consent_granted = $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(*) FROM {$table_name} WHERE event_type = 'consent_granted' AND date_created BETWEEN %s AND %s",
$start_date, $end_date
));
if ($total_requests == 0) {
$insights[] = __('No consent data available for the selected period', 'tigerstyle-whiskers');
return $insights;
}
$acceptance_rate = round(($consent_granted / $total_requests) * 100, 2);
// Compliance insights based on acceptance rates
if ($acceptance_rate >= 75) {
$insights[] = sprintf(
__('Excellent compliance: %s%% consent acceptance rate indicates clear privacy communication', 'tigerstyle-whiskers'),
$acceptance_rate
);
} elseif ($acceptance_rate >= 50) {
$insights[] = sprintf(
__('Good compliance: %s%% acceptance rate. Consider reviewing consent banner clarity', 'tigerstyle-whiskers'),
$acceptance_rate
);
} else {
$insights[] = sprintf(
__('Low acceptance rate (%s%%). Review consent banner design and messaging for GDPR compliance', 'tigerstyle-whiskers'),
$acceptance_rate
);
}
// Check for GDPR territory compliance
$gdpr_countries = $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(DISTINCT country_code) FROM {$table_name}
WHERE country_code IN ('DE', 'FR', 'IT', 'ES', 'NL', 'BE', 'AT', 'SE', 'DK', 'FI', 'IE', 'PT', 'GR', 'LU', 'CY', 'MT', 'SI', 'SK', 'EE', 'LV', 'LT', 'PL', 'CZ', 'HU', 'RO', 'BG', 'HR')
AND date_created BETWEEN %s AND %s",
$start_date, $end_date
));
if ($gdpr_countries > 0) {
$insights[] = sprintf(
__('GDPR compliance active: Serving users from %d EU territories', 'tigerstyle-whiskers'),
$gdpr_countries
);
}
// Check consent withdrawal patterns
$consent_withdrawn = $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(*) FROM {$table_name} WHERE event_type = 'consent_withdrawn' AND date_created BETWEEN %s AND %s",
$start_date, $end_date
));
if ($consent_withdrawn > 0) {
$withdrawal_rate = round(($consent_withdrawn / $total_requests) * 100, 2);
if ($withdrawal_rate > 10) {
$insights[] = sprintf(
__('High withdrawal rate (%s%%). Consider reviewing data processing transparency', 'tigerstyle-whiskers'),
$withdrawal_rate
);
}
}
// Performance insight
if ($total_requests > 1000) {
$insights[] = __('High-volume consent processing - ensure optimal performance monitoring', 'tigerstyle-whiskers');
}
return $insights;
}
/**
* Get performance metrics
*/
private function get_performance_metrics($start_date, $end_date) {
global $wpdb;
$table_name = $wpdb->prefix . 'tigerstyle_whiskers_consent_analytics';
// Get basic performance metrics
$total_events = $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(*) FROM {$table_name} WHERE date_created BETWEEN %s AND %s",
$start_date, $end_date
));
$unique_sessions = $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(DISTINCT session_id_hash) FROM {$table_name} WHERE date_created BETWEEN %s AND %s",
$start_date, $end_date
));
$avg_events_per_session = $unique_sessions > 0 ? round($total_events / $unique_sessions, 2) : 0;
return array(
'total_events' => intval($total_events),
'unique_sessions' => intval($unique_sessions),
'events_per_session' => $avg_events_per_session,
'data_points_collected' => intval($total_events),
'performance_score' => $this->calculate_performance_score($total_events, $unique_sessions)
);
}
/**
* Calculate performance score
*/
private function calculate_performance_score($total_events, $unique_sessions) {
if ($unique_sessions == 0) {
return 0;
}
$events_per_session = $total_events / $unique_sessions;
// Score based on data collection efficiency
if ($events_per_session <= 2) {
return 95; // Excellent - minimal data collection
} elseif ($events_per_session <= 5) {
return 85; // Good
} elseif ($events_per_session <= 10) {
return 70; // Average
} else {
return 50; // High data collection - review for privacy compliance
}
}
/**
* Render analytics controls (time period selector, etc.)
*/
private function render_analytics_controls() {
?>
<div class="tigerstyle-whiskers-analytics-controls" style="margin: 20px 0; padding: 15px; background: #f9f9f9; border-radius: 4px;">
<form method="get" action="" style="display: flex; align-items: center; gap: 15px;">
<input type="hidden" name="page" value="whiskers-consent-analytics" />
<label for="time_period" style="font-weight: 600;">
<?php _e('Time Period:', 'tigerstyle-whiskers'); ?>
</label>
<select name="time_period" id="time_period" onchange="this.form.submit()" style="padding: 5px 10px;">
<option value="7_days" <?php selected(isset($_GET['time_period']) ? $_GET['time_period'] : '30_days', '7_days'); ?>>
<?php _e('Last 7 Days', 'tigerstyle-whiskers'); ?>
</option>
<option value="30_days" <?php selected(isset($_GET['time_period']) ? $_GET['time_period'] : '30_days', '30_days'); ?>>
<?php _e('Last 30 Days', 'tigerstyle-whiskers'); ?>
</option>
<option value="90_days" <?php selected(isset($_GET['time_period']) ? $_GET['time_period'] : '30_days', '90_days'); ?>>
<?php _e('Last 90 Days', 'tigerstyle-whiskers'); ?>
</option>
<option value="365_days" <?php selected(isset($_GET['time_period']) ? $_GET['time_period'] : '30_days', '365_days'); ?>>
<?php _e('Last Year', 'tigerstyle-whiskers'); ?>
</option>
</select>
<button type="button" onclick="location.reload()" class="button" style="margin-left: auto;">
<?php _e('Refresh Data', 'tigerstyle-whiskers'); ?>
</button>
<a href="<?php echo admin_url('admin.php?page=whiskers-consent-analytics&export=csv&time_period=' . (isset($_GET['time_period']) ? $_GET['time_period'] : '30_days')); ?>"
class="button button-secondary">
<?php _e('Export CSV', 'tigerstyle-whiskers'); ?>
</a>
</form>
</div>
<style>
.tigerstyle-whiskers-analytics-controls select:focus,
.tigerstyle-whiskers-analytics-controls button:focus {
outline: 2px solid #2271b1;
outline-offset: 1px;
}
.tigerstyle-whiskers-analytics-controls .button {
transition: background-color 0.15s ease-in-out;
}
.tigerstyle-whiskers-analytics-controls .button:hover {
background-color: #f0f0f1;
}
</style>
<?php
}
/**
* Render consent trends chart
*/
private function render_consent_trends_chart($trends_data) {
?>
<div class="tigerstyle-whiskers-chart-section" style="margin: 30px 0; padding: 20px; background: #fff; border: 1px solid #ddd; border-radius: 4px;">
<h3><?php _e('Consent Trends', 'tigerstyle-whiskers'); ?></h3>
<?php if (empty($trends_data['daily_trends'])) : ?>
<div style="text-align: center; padding: 40px; color: #666;">
<p><?php _e('No consent trend data available for the selected period.', 'tigerstyle-whiskers'); ?></p>
<p style="font-size: 0.9em;"><?php _e('Data will appear here once users start interacting with your consent banner.', 'tigerstyle-whiskers'); ?></p>
</div>
<?php else : ?>
<div class="chart-placeholder" style="height: 300px; background: #f9f9f9; border: 1px dashed #ccc; display: flex; align-items: center; justify-content: center; border-radius: 4px;">
<div style="text-align: center; color: #666;">
<p><strong><?php _e('Interactive Chart Coming Soon', 'tigerstyle-whiskers'); ?></strong></p>
<p><?php _e('Chart.js integration planned for data visualization', 'tigerstyle-whiskers'); ?></p>
<p style="font-size: 0.9em;"><?php printf(__('Found %d data points to display', 'tigerstyle-whiskers'), count($trends_data['daily_trends'])); ?></p>
</div>
</div>
<!-- Fallback data table -->
<div style="margin-top: 20px;">
<h4><?php _e('Daily Consent Summary', 'tigerstyle-whiskers'); ?></h4>
<table class="widefat striped" style="margin-top: 10px;">
<thead>
<tr>
<th><?php _e('Date', 'tigerstyle-whiskers'); ?></th>
<th><?php _e('Requests', 'tigerstyle-whiskers'); ?></th>
<th><?php _e('Accepted', 'tigerstyle-whiskers'); ?></th>
<th><?php _e('Denied', 'tigerstyle-whiskers'); ?></th>
<th><?php _e('Rate', 'tigerstyle-whiskers'); ?></th>
</tr>
</thead>
<tbody>
<?php foreach (array_slice($trends_data['daily_trends'], 0, 10) as $trend) : ?>
<tr>
<td><?php echo esc_html($trend['date']); ?></td>
<td><?php echo intval($trend['total_requests']); ?></td>
<td><?php echo intval($trend['accepted']); ?></td>
<td><?php echo intval($trend['denied']); ?></td>
<td><?php echo esc_html($trend['acceptance_rate']); ?>%</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php if (count($trends_data['daily_trends']) > 10) : ?>
<p style="margin-top: 10px; font-style: italic; color: #666;">
<?php printf(__('Showing 10 of %d days. Use Export CSV for complete data.', 'tigerstyle-whiskers'), count($trends_data['daily_trends'])); ?>
</p>
<?php endif; ?>
</div>
<?php endif; ?>
</div>
<?php
}
}