prefix . 'itk_bot_log'; } public static function honeypot_table(): string { global $wpdb; return $wpdb->prefix . 'itk_honeypot_log'; } /* ── Install / upgrade ────────────────────────────────────── */ public static function install() { global $wpdb; $charset = $wpdb->get_charset_collate(); $sql_bot = "CREATE TABLE " . self::bot_table() . " ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, logged_at DATETIME NOT NULL, ip_address VARCHAR(45) NOT NULL DEFAULT '', user_agent TEXT NOT NULL, referrer VARCHAR(1000) NOT NULL DEFAULT '', request_uri VARCHAR(1000) NOT NULL DEFAULT '', bot_type VARCHAR(100) NOT NULL DEFAULT '', reason VARCHAR(255) NOT NULL DEFAULT '', action VARCHAR(20) NOT NULL DEFAULT 'blocked', PRIMARY KEY (id), KEY ip_address (ip_address), KEY logged_at (logged_at), KEY bot_type (bot_type), KEY action (action) ) {$charset};"; $sql_hp = "CREATE TABLE " . self::honeypot_table() . " ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, blocked_at DATETIME NOT NULL, ip_address VARCHAR(45) NOT NULL DEFAULT '', form_type VARCHAR(100) NOT NULL DEFAULT '', reason VARCHAR(255) NOT NULL DEFAULT '', request_uri VARCHAR(1000) NOT NULL DEFAULT '', user_agent TEXT NOT NULL, PRIMARY KEY (id), KEY ip_address (ip_address), KEY blocked_at (blocked_at), KEY form_type (form_type) ) {$charset};"; require_once ABSPATH . 'wp-admin/includes/upgrade.php'; dbDelta($sql_bot); dbDelta($sql_hp); update_option(self::DB_VERSION_OPTION, self::DB_VERSION); } /* ── Bot log ──────────────────────────────────────────────── */ public static function log_bot(array $data): void { global $wpdb; $wpdb->insert( self::bot_table(), [ 'logged_at' => current_time('mysql'), 'ip_address' => sanitize_text_field($data['ip'] ?? ''), 'user_agent' => sanitize_textarea_field($data['ua'] ?? ''), 'referrer' => esc_url_raw(substr($data['referrer'] ?? '', 0, 1000)), 'request_uri' => esc_url_raw(substr($data['uri'] ?? '', 0, 1000)), 'bot_type' => sanitize_text_field($data['bot_type'] ?? ''), 'reason' => sanitize_text_field($data['reason'] ?? ''), 'action' => sanitize_text_field($data['action'] ?? 'blocked'), ], ['%s','%s','%s','%s','%s','%s','%s','%s'] ); } public static function get_bot_rows(array $args = []): array { global $wpdb; $table = self::bot_table(); $limit = max(1, (int)($args['per_page'] ?? 25)); $offset = max(0, (int)($args['offset'] ?? 0)); $where = '1=1'; $params = []; if (!empty($args['action'])) { $where .= ' AND action = %s'; $params[] = $args['action']; } if (!empty($args['bot_type'])) { $where .= ' AND bot_type = %s'; $params[] = $args['bot_type']; } if (!empty($args['ip'])) { $where .= ' AND ip_address = %s'; $params[] = $args['ip']; } if (!empty($args['search'])) { $like = '%' . $wpdb->esc_like($args['search']) . '%'; $where .= ' AND (ip_address LIKE %s OR user_agent LIKE %s OR reason LIKE %s)'; $params[] = $like; $params[] = $like; $params[] = $like; } $params[] = $limit; $params[] = $offset; $sql = "SELECT * FROM {$table} WHERE {$where} ORDER BY logged_at DESC LIMIT %d OFFSET %d"; return $wpdb->get_results($wpdb->prepare($sql, $params)) ?: []; } public static function count_bot_rows(array $args = []): int { global $wpdb; $table = self::bot_table(); $where = '1=1'; $params = []; if (!empty($args['action'])) { $where .= ' AND action = %s'; $params[] = $args['action']; } if (!empty($args['bot_type'])) { $where .= ' AND bot_type = %s'; $params[] = $args['bot_type']; } if (!empty($args['ip'])) { $where .= ' AND ip_address = %s'; $params[] = $args['ip']; } if (!empty($args['search'])) { $like = '%' . $wpdb->esc_like($args['search']) . '%'; $where .= ' AND (ip_address LIKE %s OR user_agent LIKE %s OR reason LIKE %s)'; $params[] = $like; $params[] = $like; $params[] = $like; } $sql = "SELECT COUNT(*) FROM {$table} WHERE {$where}"; return (int)($params ? $wpdb->get_var($wpdb->prepare($sql, $params)) : $wpdb->get_var($sql)); } public static function get_bot_stats(): array { global $wpdb; $table = self::bot_table(); $today = current_time('Y-m-d'); return [ 'total' => (int)$wpdb->get_var("SELECT COUNT(*) FROM {$table}"), 'today' => (int)$wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$table} WHERE DATE(logged_at)=%s", $today)), 'blocked' => (int)$wpdb->get_var("SELECT COUNT(*) FROM {$table} WHERE action='blocked'"), 'rate_limited' => (int)$wpdb->get_var("SELECT COUNT(*) FROM {$table} WHERE action='rate_limited'"), 'top_bot_types' => $wpdb->get_results("SELECT bot_type, COUNT(*) as cnt FROM {$table} WHERE bot_type != '' GROUP BY bot_type ORDER BY cnt DESC LIMIT 8") ?: [], 'top_ips' => $wpdb->get_results("SELECT ip_address, COUNT(*) as cnt FROM {$table} GROUP BY ip_address ORDER BY cnt DESC LIMIT 5") ?: [], 'last_24h_counts' => $wpdb->get_results("SELECT DATE_FORMAT(logged_at,'%H:00') as hour, COUNT(*) as cnt FROM {$table} WHERE logged_at >= DATE_SUB(NOW(), INTERVAL 24 HOUR) GROUP BY hour ORDER BY hour ASC") ?: [], ]; } public static function get_bot_types(): array { global $wpdb; return $wpdb->get_col("SELECT DISTINCT bot_type FROM " . self::bot_table() . " WHERE bot_type != '' ORDER BY bot_type ASC") ?: []; } public static function clear_bot_log(): void { global $wpdb; $wpdb->query("TRUNCATE TABLE " . self::bot_table()); } public static function prune_bot_log(int $days): void { global $wpdb; $wpdb->query($wpdb->prepare( "DELETE FROM " . self::bot_table() . " WHERE logged_at < DATE_SUB(NOW(), INTERVAL %d DAY)", $days )); } /* ── Honeypot log ─────────────────────────────────────────── */ public static function log_honeypot(array $data): void { global $wpdb; $wpdb->insert( self::honeypot_table(), [ 'blocked_at' => current_time('mysql'), 'ip_address' => sanitize_text_field($data['ip'] ?? ''), 'form_type' => sanitize_text_field($data['form'] ?? 'Unknown'), 'reason' => sanitize_text_field($data['reason'] ?? ''), 'request_uri' => esc_url_raw(substr($data['uri'] ?? '', 0, 1000)), 'user_agent' => sanitize_textarea_field($data['ua'] ?? ''), ], ['%s','%s','%s','%s','%s','%s'] ); } public static function get_honeypot_rows(array $args = []): array { global $wpdb; $table = self::honeypot_table(); $limit = max(1, (int)($args['per_page'] ?? 25)); $offset = max(0, (int)($args['offset'] ?? 0)); $where = '1=1'; $params = []; if (!empty($args['form'])) { $where .= ' AND form_type = %s'; $params[] = $args['form']; } if (!empty($args['ip'])) { $where .= ' AND ip_address = %s'; $params[] = $args['ip']; } if (!empty($args['search'])) { $like = '%' . $wpdb->esc_like($args['search']) . '%'; $where .= ' AND (ip_address LIKE %s OR user_agent LIKE %s OR reason LIKE %s)'; $params[] = $like; $params[] = $like; $params[] = $like; } $params[] = $limit; $params[] = $offset; $sql = "SELECT * FROM {$table} WHERE {$where} ORDER BY blocked_at DESC LIMIT %d OFFSET %d"; return $wpdb->get_results($wpdb->prepare($sql, $params)) ?: []; } public static function count_honeypot_rows(array $args = []): int { global $wpdb; $table = self::honeypot_table(); $where = '1=1'; $params = []; if (!empty($args['form'])) { $where .= ' AND form_type = %s'; $params[] = $args['form']; } if (!empty($args['ip'])) { $where .= ' AND ip_address = %s'; $params[] = $args['ip']; } if (!empty($args['search'])) { $like = '%' . $wpdb->esc_like($args['search']) . '%'; $where .= ' AND (ip_address LIKE %s OR user_agent LIKE %s OR reason LIKE %s)'; $params[] = $like; $params[] = $like; $params[] = $like; } $sql = "SELECT COUNT(*) FROM {$table} WHERE {$where}"; return (int)($params ? $wpdb->get_var($wpdb->prepare($sql, $params)) : $wpdb->get_var($sql)); } public static function get_honeypot_stats(): array { global $wpdb; $table = self::honeypot_table(); $today = current_time('Y-m-d'); return [ 'total' => (int)$wpdb->get_var("SELECT COUNT(*) FROM {$table}"), 'today' => (int)$wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$table} WHERE DATE(blocked_at)=%s", $today)), 'top_forms' => $wpdb->get_results("SELECT form_type, COUNT(*) as cnt FROM {$table} GROUP BY form_type ORDER BY cnt DESC LIMIT 8") ?: [], 'top_ips' => $wpdb->get_results("SELECT ip_address, COUNT(*) as cnt FROM {$table} GROUP BY ip_address ORDER BY cnt DESC LIMIT 5") ?: [], ]; } public static function get_honeypot_form_types(): array { global $wpdb; return $wpdb->get_col("SELECT DISTINCT form_type FROM " . self::honeypot_table() . " ORDER BY form_type ASC") ?: []; } public static function clear_honeypot_log(): void { global $wpdb; $wpdb->query("TRUNCATE TABLE " . self::honeypot_table()); } public static function prune_honeypot_log(int $days): void { global $wpdb; $wpdb->query($wpdb->prepare( "DELETE FROM " . self::honeypot_table() . " WHERE blocked_at < DATE_SUB(NOW(), INTERVAL %d DAY)", $days )); } }