table_name = $wpdb->prefix . 'asbfg_blocked_logs'; add_action( 'admin_menu', [ $this, 'add_logs_page' ], 20 ); add_action( 'admin_post_asbfg_clear_logs', [ $this, 'handle_clear_logs' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_log_styles' ] ); // Schedule automatic log cleanup if ( ! wp_next_scheduled( 'asbfg_cleanup_old_logs' ) ) { wp_schedule_event( time(), 'daily', 'asbfg_cleanup_old_logs' ); } add_action( 'asbfg_cleanup_old_logs', [ $this, 'cleanup_old_logs' ] ); } public function enqueue_log_styles( $hook ) { // Only load on our logs page if ( strpos( $hook, 'asbfg_blocked_logs' ) === false ) { return; } wp_enqueue_style( 'asbfg-admin-styles', plugin_dir_url( dirname( __FILE__ ) ) . 'assets/admin-styles.css', [], '1.6.5' ); } public function create_table() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.SchemaChange -- Custom table creation required for logging functionality $sql = "CREATE TABLE IF NOT EXISTS {$this->table_name} ( id bigint(20) NOT NULL AUTO_INCREMENT, form_id bigint(20) NOT NULL, block_type varchar(50) NOT NULL, blocked_value varchar(255) NOT NULL, ip_address varchar(100) NOT NULL, email varchar(255) DEFAULT NULL, user_agent text DEFAULT NULL, blocked_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY form_id (form_id), KEY block_type (block_type), KEY blocked_at (blocked_at) ) $charset_collate;"; require_once ABSPATH . 'wp-admin/includes/upgrade.php'; dbDelta( $sql ); } public function log_blocked_submission( $form_id, $block_type, $blocked_value, $ip_address, $email = '' ) { global $wpdb; $user_agent = isset( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : ''; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Custom table requires direct query $wpdb->insert( $this->table_name, [ 'form_id' => $form_id, 'block_type' => $block_type, 'blocked_value' => $blocked_value, 'ip_address' => $ip_address, 'email' => $email, 'user_agent' => $user_agent, 'blocked_at' => current_time( 'mysql' ), ], [ '%d', '%s', '%s', '%s', '%s', '%s', '%s' ] ); // Invalidate stats cache wp_cache_delete( 'asbfg_stats_cache', 'asbfg' ); } public function get_logs( $limit = 100, $offset = 0 ) { global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table requires direct query, caching not needed for admin display return $wpdb->get_results( $wpdb->prepare( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is safe, set in constructor with wpdb prefix "SELECT * FROM {$this->table_name} ORDER BY blocked_at DESC LIMIT %d OFFSET %d", $limit, $offset ) ); } public function get_total_logs() { global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Custom table requires direct query, caching not needed for admin display, table name is safe return $wpdb->get_var( "SELECT COUNT(*) FROM {$this->table_name}" ); } public function get_stats() { global $wpdb; // Try to get cached stats (cache for 5 minutes) $cache_key = 'asbfg_stats_cache'; $cached_stats = wp_cache_get( $cache_key, 'asbfg' ); if ( false !== $cached_stats ) { return $cached_stats; } // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Custom table requires direct query, caching not needed for admin display, table name is safe $total = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$this->table_name}" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table requires direct query, caching not needed for admin display $today = (int) $wpdb->get_var( $wpdb->prepare( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is safe, set in constructor with wpdb prefix "SELECT COUNT(*) FROM {$this->table_name} WHERE DATE(blocked_at) = %s", current_time( 'Y-m-d' ) ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table requires direct query, caching not needed for admin display $this_week = (int) $wpdb->get_var( $wpdb->prepare( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is safe, set in constructor with wpdb prefix "SELECT COUNT(*) FROM {$this->table_name} WHERE blocked_at >= %s", gmdate( 'Y-m-d', strtotime( '-7 days' ) ) ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table requires direct query, caching not needed for admin display $this_month = (int) $wpdb->get_var( $wpdb->prepare( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is safe, set in constructor with wpdb prefix "SELECT COUNT(*) FROM {$this->table_name} WHERE blocked_at >= %s", gmdate( 'Y-m-d', strtotime( '-30 days' ) ) ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table requires direct query, caching not needed for admin display $by_type = $wpdb->get_results( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is safe, set in constructor with wpdb prefix "SELECT block_type, COUNT(*) as count FROM {$this->table_name} GROUP BY block_type" ); if ( ! $by_type ) { $by_type = []; } $stats = [ 'total' => $total, 'today' => $today, 'this_week' => $this_week, 'this_month' => $this_month, 'by_type' => $by_type, ]; // Cache stats for 5 minutes wp_cache_set( $cache_key, $stats, 'asbfg', 300 ); return $stats; } public function add_logs_page() { if ( ! class_exists( 'GFForms' ) ) { return; } add_submenu_page( 'gf_edit_forms', __( 'Blocked Submissions Log', 'addon-submission-blocker-for-gravityforms' ), __( 'Blocked Log', 'addon-submission-blocker-for-gravityforms' ), 'gform_full_access', 'asbfg_blocked_logs', [ $this, 'render_logs_page' ] ); } public function render_logs_page() { // Ensure table exists $this->create_table(); $stats = $this->get_stats(); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Pagination parameter, not form processing $page = isset( $_GET['paged'] ) ? max( 1, absint( $_GET['paged'] ) ) : 1; $per_page = 20; $offset = ( $page - 1 ) * $per_page; $logs = $this->get_logs( $per_page, $offset ); $total = $this->get_total_logs(); $total_pages = ceil( $total / $per_page ); ?>

📊

📅

📈

📆

block_type ) ); // Fix IP capitalization $type_label = str_replace( 'Ip', 'IP', $type_label ); // Get icon for each type $icons = [ 'ip' => '🌐', 'email' => '📧', 'domain' => '🏢', 'country' => '🌍', 'text' => '📝', ]; $icon = isset( $icons[ $type_stat->block_type ] ) ? $icons[ $type_stat->block_type ] : '🚫'; ?>
count ) ); ?>

1 ) : ?>
add_query_arg( 'paged', '%#%' ), 'format' => '', 'prev_text' => '‹', 'next_text' => '›', 'total' => $total_pages, 'current' => $page, 'type' => 'plain', ] ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- paginate_links output is safe echo $page_links; ?>
🛡️

blocked_at ) ) ); ?>
blocked_at ) ) ); ?>
form_id ); if ( $form ) { echo ''; echo esc_html( $form['title'] ); echo ''; } else { echo esc_html( $log->form_id ); } } else { echo esc_html( $log->form_id ); } ?> block_type ) ); $type_label = str_replace( 'Ip', 'IP', $type_label ); ?> blocked_value ); ?> ip_address ); ?> email ? esc_html( $log->email ) : '—'; ?>
1 ) : ?>
add_query_arg( 'paged', '%#%' ), 'format' => '', 'prev_text' => '‹', 'next_text' => '›', 'total' => $total_pages, 'current' => $page, 'type' => 'plain', ] ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- paginate_links output is safe echo $page_links; ?>
query( "TRUNCATE TABLE {$this->table_name}" ); // Invalidate stats cache wp_cache_delete( 'asbfg_stats_cache', 'asbfg' ); wp_safe_redirect( add_query_arg( 'message', 'cleared', admin_url( 'admin.php?page=asbfg_blocked_logs' ) ) ); exit; } public function cleanup_old_logs() { $retention_days = (int) get_option( 'asbfg_log_retention_days', 90 ); // If set to 0, keep logs forever if ( 0 === $retention_days ) { return; } global $wpdb; // Delete logs older than retention period // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table requires direct query, caching not needed for cleanup $deleted = $wpdb->query( $wpdb->prepare( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is safe, set in constructor with wpdb prefix "DELETE FROM {$this->table_name} WHERE blocked_at < DATE_SUB(NOW(), INTERVAL %d DAY)", $retention_days ) ); // Invalidate stats cache if any logs were deleted if ( $deleted > 0 ) { wp_cache_delete( 'asbfg_stats_cache', 'asbfg' ); } } }