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 = "\n\n"; foreach ($logs as $log) { $xml .= " \n"; $xml .= " " . htmlspecialchars($log->created_at) . "\n"; $xml .= " " . htmlspecialchars($log->event_type) . "\n"; $xml .= " " . htmlspecialchars($log->user_id ?: 'N/A') . "\n"; $xml .= " " . htmlspecialchars($log->ip_address) . "\n"; $xml .= " " . htmlspecialchars($log->user_agent) . "\n"; $xml .= " " . htmlspecialchars($log->event_data) . "\n"; $xml .= " \n"; } $xml .= ""; 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); } }