prefix . 'honeypot_log'; } public static function install() { global $wpdb; $table = self::table(); $charset_collate = $wpdb->get_charset_collate(); $sql = "CREATE TABLE {$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_collate};"; require_once ABSPATH . 'wp-admin/includes/upgrade.php'; dbDelta($sql); update_option(self::TABLE_VERSION_OPTION, self::TABLE_VERSION); } public static function insert(array $data) { global $wpdb; $wpdb->insert( self::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_rows(array $args = []): array { global $wpdb; $table = self::table(); $limit = max(1, intval($args['per_page'] ?? 25)); $offset = max(0, intval($args['offset'] ?? 0)); $where = '1=1'; $params = []; if (!empty($args['ip'])) { $where .= ' AND ip_address = %s'; $params[] = sanitize_text_field($args['ip']); } if (!empty($args['form'])) { $where .= ' AND form_type = %s'; $params[] = sanitize_text_field($args['form']); } 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"; if ($params) { $sql = $wpdb->prepare($sql, $params); } return $wpdb->get_results($sql) ?: []; } public static function count(array $args = []): int { global $wpdb; $table = self::table(); $where = '1=1'; $params = []; if (!empty($args['ip'])) { $where .= ' AND ip_address = %s'; $params[] = sanitize_text_field($args['ip']); } if (!empty($args['form'])) { $where .= ' AND form_type = %s'; $params[] = sanitize_text_field($args['form']); } 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}"; if ($params) { $sql = $wpdb->prepare($sql, $params); } return (int) $wpdb->get_var($sql); } public static function get_form_types(): array { global $wpdb; return $wpdb->get_col("SELECT DISTINCT form_type FROM " . self::table() . " ORDER BY form_type ASC") ?: []; } public static function clear(): int { global $wpdb; return (int) $wpdb->query("TRUNCATE TABLE " . self::table()); } public static function delete_older_than_days(int $days) { global $wpdb; $wpdb->query( $wpdb->prepare( "DELETE FROM " . self::table() . " WHERE blocked_at < DATE_SUB(NOW(), INTERVAL %d DAY)", $days ) ); } } /* ====================================================================== * ADMIN PAGE * ====================================================================*/ class SmartHoneypotAdmin { const MENU_SLUG = 'honeypot-logs'; const NONCE_ACTION = 'hp_admin_action'; const PER_PAGE = 25; public static function register() { add_action('admin_menu', [self::class, 'add_menu']); add_action('admin_init', [self::class, 'handle_actions']); add_action('admin_enqueue_scripts', [self::class, 'enqueue_styles']); add_filter('plugin_action_links_' . plugin_basename(HP_PLUGIN_FILE), [self::class, 'plugin_links']); } public static function plugin_links($links) { $log_link = 'View Logs'; array_unshift($links, $log_link); array_push($links, 'Documentation'); return $links; } public static function add_menu() { add_menu_page( 'Honeypot Logs', 'Honeypot Logs', 'manage_options', self::MENU_SLUG, [self::class, 'render_page'], 'dashicons-shield-alt', 81 ); } public static function enqueue_styles($hook) { if ($hook !== 'toplevel_page_' . self::MENU_SLUG) { return; } // Inline styles — no external file needed $css = ' #hp-log-wrap { max-width: 1400px; } #hp-log-wrap .hp-stats { display:flex; gap:16px; margin:16px 0; flex-wrap:wrap; } #hp-log-wrap .hp-stat-card { background:#fff; border:1px solid #c3c4c7; border-radius:4px; padding:16px 24px; min-width:140px; text-align:center; } #hp-log-wrap .hp-stat-card .hp-stat-num { font-size:2em; font-weight:700; color:#2271b1; line-height:1.2; } #hp-log-wrap .hp-stat-card .hp-stat-lbl { color:#646970; font-size:12px; } #hp-log-wrap .hp-filters { display:flex; gap:8px; align-items:center; flex-wrap:wrap; margin-bottom:12px; } #hp-log-wrap .hp-filters input, #hp-log-wrap .hp-filters select { height:32px; } #hp-log-wrap table.hp-log-table { width:100%; border-collapse:collapse; background:#fff; } #hp-log-wrap table.hp-log-table th { background:#f0f0f1; padding:8px 12px; text-align:left; border-bottom:2px solid #c3c4c7; white-space:nowrap; } #hp-log-wrap table.hp-log-table td { padding:8px 12px; border-bottom:1px solid #f0f0f1; vertical-align:top; } #hp-log-wrap table.hp-log-table tr:hover td { background:#f6f7f7; } #hp-log-wrap .hp-ua { font-size:11px; color:#646970; max-width:300px; word-break:break-all; } #hp-log-wrap .hp-badge { display:inline-block; padding:2px 8px; border-radius:3px; font-size:11px; font-weight:600; background:#ffecec; color:#b32d2e; border:1px solid #f7c5c5; } #hp-log-wrap .hp-pagination { margin:12px 0; display:flex; align-items:center; gap:8px; } #hp-log-wrap .hp-pagination a, #hp-log-wrap .hp-pagination span { display:inline-block; padding:4px 10px; border:1px solid #c3c4c7; border-radius:3px; background:#fff; text-decoration:none; color:#2271b1; } #hp-log-wrap .hp-pagination span.current { background:#2271b1; color:#fff; border-color:#2271b1; } #hp-log-wrap .hp-clear-btn { color:#b32d2e; } '; wp_add_inline_style('common', $css); } public static function handle_actions() { if (!isset($_POST['hp_action']) || !check_admin_referer(self::NONCE_ACTION)) { return; } if (!current_user_can('manage_options')) { wp_die('Unauthorized'); } if ($_POST['hp_action'] === 'clear_logs') { SmartHoneypotDB::clear(); wp_redirect(add_query_arg(['page' => self::MENU_SLUG, 'cleared' => 1], admin_url('admin.php'))); exit; } } public static function render_page() { if (!current_user_can('manage_options')) { return; } // Filters from query string $search = sanitize_text_field($_GET['hp_search'] ?? ''); $filter_ip = sanitize_text_field($_GET['hp_ip'] ?? ''); $filter_form = sanitize_text_field($_GET['hp_form'] ?? ''); $paged = max(1, intval($_GET['paged'] ?? 1)); $per_page = self::PER_PAGE; $offset = ($paged - 1) * $per_page; $query_args = array_filter([ 'ip' => $filter_ip, 'form' => $filter_form, 'search' => $search, 'per_page' => $per_page, 'offset' => $offset, ]); $rows = SmartHoneypotDB::get_rows($query_args); $total = SmartHoneypotDB::count($query_args); $total_ever = SmartHoneypotDB::count(); $form_types = SmartHoneypotDB::get_form_types(); $total_pages = max(1, ceil($total / $per_page)); // Unique IPs total global $wpdb; $unique_ips = (int) $wpdb->get_var("SELECT COUNT(DISTINCT ip_address) FROM " . SmartHoneypotDB::table()); $today = (int) $wpdb->get_var( "SELECT COUNT(*) FROM " . SmartHoneypotDB::table() . " WHERE blocked_at >= CURDATE()" ); $base_url = admin_url('admin.php?page=' . self::MENU_SLUG); ?>
All logs have been cleared.
Showing = number_format($total) ?> result= $total !== 1 ? 's' : '' ?> (page = $paged ?> of = $total_pages ?>)
| # | Date / Time | IP Address | Form Type | Reason | URI | User Agent |
|---|---|---|---|---|---|---|
| No blocked attempts recorded yet. | ||||||
| = esc_html($row->id) ?> | = esc_html($row->blocked_at) ?> |
= esc_html($row->ip_address) ?>filter lookup ↗ |
= esc_html($row->form_type) ?> | = esc_html($row->reason) ?> | = esc_html($row->request_uri) ?> | = esc_html($row->user_agent) ?> |