tigerstyle-whiskers/includes/whiskers/class-audit-trail.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

419 lines
13 KiB
PHP

<?php
/**
* TigerStyle Whiskers Audit Trail
*
* Track every whisker twitch and boundary crossing - complete privacy compliance logging!
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
class TigerStyleWhiskers_AuditTrail {
/**
* Single instance
*/
private static $instance = null;
/**
* Log retention period (days)
*/
private $retention_period = 2555; // 7 years for GDPR compliance
/**
* Get instance
*/
public static function instance() {
if (is_null(self::$instance)) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Constructor
*/
private function __construct() {
$this->init_audit_trail();
}
/**
* Initialize audit trail
*/
private function init_audit_trail() {
// Hook into privacy events
add_action('tigerstyle_whiskers_consent_granted', array($this, 'log_consent_granted'), 10, 2);
add_action('tigerstyle_whiskers_consent_withdrawn', array($this, 'log_consent_withdrawn'), 10, 2);
add_action('tigerstyle_whiskers_data_request', array($this, 'log_data_request'), 10, 3);
add_action('tigerstyle_whiskers_data_processed', array($this, 'log_data_processed'), 10, 2);
add_action('tigerstyle_whiskers_data_deleted', array($this, 'log_data_deleted'), 10, 2);
// Schedule cleanup
add_action('tigerstyle_whiskers_cleanup', array($this, 'cleanup_old_logs'));
// Admin hooks
if (is_admin()) {
add_action('wp_ajax_whiskers_export_audit_log', array($this, 'ajax_export_audit_log'));
}
}
/**
* Log consent granted event
*/
public function log_consent_granted($consent_data, $user_info) {
$this->log_event('consent_granted', array(
'consent_categories' => $consent_data,
'user_id' => $user_info['user_id'] ?? null,
'session_id' => $user_info['session_id'] ?? '',
'ip_address' => $user_info['ip_address'] ?? '',
'user_agent' => $user_info['user_agent'] ?? '',
'country' => $user_info['country'] ?? '',
'timestamp' => current_time('mysql')
));
}
/**
* Log consent withdrawn event
*/
public function log_consent_withdrawn($consent_categories, $user_info) {
$this->log_event('consent_withdrawn', array(
'consent_categories' => $consent_categories,
'user_id' => $user_info['user_id'] ?? null,
'session_id' => $user_info['session_id'] ?? '',
'ip_address' => $user_info['ip_address'] ?? '',
'user_agent' => $user_info['user_agent'] ?? '',
'country' => $user_info['country'] ?? '',
'timestamp' => current_time('mysql')
));
}
/**
* Log data request event
*/
public function log_data_request($request_type, $email, $verification_token) {
$this->log_event('data_request', array(
'request_type' => $request_type,
'email' => wp_hash($email), // Store hashed for privacy
'verification_token' => $verification_token,
'ip_address' => $this->get_client_ip(),
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
'timestamp' => current_time('mysql')
));
}
/**
* Log data processed event
*/
public function log_data_processed($process_type, $details) {
$this->log_event('data_processed', array(
'process_type' => $process_type,
'details' => $details,
'user_id' => get_current_user_id() ?: null,
'timestamp' => current_time('mysql')
));
}
/**
* Log data deleted event
*/
public function log_data_deleted($deletion_type, $details) {
$this->log_event('data_deleted', array(
'deletion_type' => $deletion_type,
'details' => $details,
'user_id' => get_current_user_id() ?: null,
'timestamp' => current_time('mysql')
));
}
/**
* Generic event logging
*/
public function log_event($event_type, $data) {
global $wpdb;
$table_name = $wpdb->prefix . 'whiskers_audit_log';
$wpdb->insert(
$table_name,
array(
'event_type' => $event_type,
'event_data' => wp_json_encode($data),
'ip_address' => $this->get_client_ip(),
'user_agent' => substr($_SERVER['HTTP_USER_AGENT'] ?? '', 0, 500),
'user_id' => get_current_user_id() ?: null,
'created_at' => current_time('mysql')
),
array('%s', '%s', '%s', '%s', '%d', '%s')
);
}
/**
* Get client IP address
*/
private function get_client_ip() {
$ip_keys = array('HTTP_X_FORWARDED_FOR', 'HTTP_X_REAL_IP', 'HTTP_CLIENT_IP', 'REMOTE_ADDR');
foreach ($ip_keys as $key) {
if (!empty($_SERVER[$key])) {
$ip = $_SERVER[$key];
if (strpos($ip, ',') !== false) {
$ip = trim(explode(',', $ip)[0]);
}
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
return $ip;
}
}
}
return $_SERVER['REMOTE_ADDR'] ?? '127.0.0.1';
}
/**
* Get audit log entries
*/
public function get_audit_log($filters = array()) {
global $wpdb;
$table_name = $wpdb->prefix . 'whiskers_audit_log';
$where_clauses = array('1=1');
$where_values = array();
// Apply filters
if (!empty($filters['event_type'])) {
$where_clauses[] = 'event_type = %s';
$where_values[] = $filters['event_type'];
}
if (!empty($filters['user_id'])) {
$where_clauses[] = 'user_id = %d';
$where_values[] = $filters['user_id'];
}
if (!empty($filters['date_from'])) {
$where_clauses[] = 'created_at >= %s';
$where_values[] = $filters['date_from'];
}
if (!empty($filters['date_to'])) {
$where_clauses[] = 'created_at <= %s';
$where_values[] = $filters['date_to'];
}
$limit = isset($filters['limit']) ? intval($filters['limit']) : 100;
$offset = isset($filters['offset']) ? intval($filters['offset']) : 0;
$where_sql = implode(' AND ', $where_clauses);
if (!empty($where_values)) {
$query = $wpdb->prepare(
"SELECT * FROM {$table_name} WHERE {$where_sql} ORDER BY created_at DESC LIMIT %d OFFSET %d",
array_merge($where_values, array($limit, $offset))
);
} else {
$query = $wpdb->prepare(
"SELECT * FROM {$table_name} WHERE {$where_sql} ORDER BY created_at DESC LIMIT %d OFFSET %d",
$limit,
$offset
);
}
return $wpdb->get_results($query);
}
/**
* Get audit log statistics
*/
public function get_audit_statistics($period = '30 days') {
global $wpdb;
$table_name = $wpdb->prefix . 'whiskers_audit_log';
$date_from = date('Y-m-d H:i:s', strtotime("-{$period}"));
$stats = array();
// Event type distribution
$event_types = $wpdb->get_results($wpdb->prepare(
"SELECT event_type, COUNT(*) as count
FROM {$table_name}
WHERE created_at >= %s
GROUP BY event_type
ORDER BY count DESC",
$date_from
));
$stats['event_types'] = $event_types;
// Daily activity
$daily_activity = $wpdb->get_results($wpdb->prepare(
"SELECT DATE(created_at) as date, COUNT(*) as count
FROM {$table_name}
WHERE created_at >= %s
GROUP BY DATE(created_at)
ORDER BY date DESC",
$date_from
));
$stats['daily_activity'] = $daily_activity;
// Total events
$total_events = $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(*) FROM {$table_name} WHERE created_at >= %s",
$date_from
));
$stats['total_events'] = $total_events;
return $stats;
}
/**
* Export audit log
*/
public function export_audit_log($format = 'csv', $filters = array()) {
$logs = $this->get_audit_log($filters);
switch ($format) {
case 'json':
return $this->export_as_json($logs);
case 'xml':
return $this->export_as_xml($logs);
default:
return $this->export_as_csv($logs);
}
}
/**
* Export as CSV
*/
private function export_as_csv($logs) {
$csv_data = "Date,Event Type,User ID,IP Address,User Agent,Event Data\n";
foreach ($logs as $log) {
$csv_data .= sprintf(
"%s,%s,%s,%s,%s,%s\n",
$log->created_at,
$log->event_type,
$log->user_id ?: 'N/A',
$log->ip_address,
'"' . str_replace('"', '""', $log->user_agent) . '"',
'"' . str_replace('"', '""', $log->event_data) . '"'
);
}
return $csv_data;
}
/**
* Export as JSON
*/
private function export_as_json($logs) {
return wp_json_encode($logs, JSON_PRETTY_PRINT);
}
/**
* Export as XML
*/
private function export_as_xml($logs) {
$xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<audit_log>\n";
foreach ($logs as $log) {
$xml .= " <entry>\n";
$xml .= " <date>" . htmlspecialchars($log->created_at) . "</date>\n";
$xml .= " <event_type>" . htmlspecialchars($log->event_type) . "</event_type>\n";
$xml .= " <user_id>" . htmlspecialchars($log->user_id ?: 'N/A') . "</user_id>\n";
$xml .= " <ip_address>" . htmlspecialchars($log->ip_address) . "</ip_address>\n";
$xml .= " <user_agent>" . htmlspecialchars($log->user_agent) . "</user_agent>\n";
$xml .= " <event_data>" . htmlspecialchars($log->event_data) . "</event_data>\n";
$xml .= " </entry>\n";
}
$xml .= "</audit_log>";
return $xml;
}
/**
* Cleanup old log entries
*/
public function cleanup_old_logs() {
global $wpdb;
$table_name = $wpdb->prefix . 'whiskers_audit_log';
$cutoff_date = date('Y-m-d H:i:s', strtotime("-{$this->retention_period} days"));
$deleted = $wpdb->query($wpdb->prepare(
"DELETE FROM {$table_name} WHERE created_at < %s",
$cutoff_date
));
if ($deleted > 0) {
$this->log_event('system_cleanup', array(
'deleted_entries' => $deleted,
'cutoff_date' => $cutoff_date
));
}
}
/**
* AJAX: Export audit log
*/
public function ajax_export_audit_log() {
check_ajax_referer('whiskers_admin_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_die(__('Insufficient permissions', 'tigerstyle-whiskers'));
}
$format = sanitize_text_field($_POST['format'] ?? 'csv');
$filters = array();
if (!empty($_POST['event_type'])) {
$filters['event_type'] = sanitize_text_field($_POST['event_type']);
}
if (!empty($_POST['date_from'])) {
$filters['date_from'] = sanitize_text_field($_POST['date_from']);
}
if (!empty($_POST['date_to'])) {
$filters['date_to'] = sanitize_text_field($_POST['date_to']);
}
$export_data = $this->export_audit_log($format, $filters);
wp_send_json_success(array(
'data' => $export_data,
'format' => $format,
'filename' => 'whiskers_audit_log_' . date('Y-m-d_H-i-s') . '.' . $format
));
}
/**
* Create audit log table
*/
public static function create_table() {
global $wpdb;
$table_name = $wpdb->prefix . 'whiskers_audit_log';
$charset_collate = $wpdb->get_charset_collate();
$sql = "CREATE TABLE $table_name (
id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
event_type varchar(50) NOT NULL,
event_data longtext NOT NULL,
ip_address varchar(45) NOT NULL,
user_agent text NOT NULL,
user_id bigint(20) unsigned DEFAULT NULL,
created_at datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY event_type (event_type),
KEY user_id (user_id),
KEY created_at (created_at),
KEY ip_address (ip_address)
) $charset_collate;";
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
dbDelta($sql);
}
}