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.
1405 lines
54 KiB
PHP
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
|
|
}
|
|
}
|