feat: initial InformatiQ Toolkit plugin
Merges informatiq-wp-secure + informatiq-utils + HoneypotFields into
a single unified plugin with the following improvements:
- Fixed deactivation bug: all protection methods now guard themselves
with their own option check so toggling off via AJAX takes effect
immediately without any hook re-registration.
- Added rate-limiting for good/legitimate bots (Googlebot, Bingbot,
DuckDuckBot, Yandex, etc.) via transient sliding-window counters;
configurable per-bot limits in goodbots.conf (BotName|req/min);
returns HTTP 429 with Retry-After: 60 when over limit.
- Unified MySQL-backed logging (itk_bot_log + itk_honeypot_log tables)
replaces the old wp_options-based 100-entry cap.
- New Dashboard tab with terminal-style bot activity monitor: total
blocked, today's count, rate-limited hits, top threat sources
(bar chart), top IPs, top honeypot form types, active-module
status panel.
- All optimizations from utils.php merged into Optimization tab as
toggleable settings (was always-on before).
- Single admin page (Settings → InformatiQ Toolkit) with 8 tabs:
Dashboard | Bot Blocker | Protection | Optimization | Honeypot |
Bot Logs | Honeypot Logs | Config Files.
- Config file editor for badbots.conf, goodbots.conf, referrers.conf,
networks.conf, allowed-ips.conf with AJAX save and transient flush.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 11:45:26 +02:00
< ? php
if ( ! defined ( 'ABSPATH' )) exit ;
/**
* Database helper for InformatiQ Toolkit .
* Manages two log tables : bot_log and honeypot_log .
*/
class ITK_Database {
2026-04-10 09:37:31 +02:00
const DB_VERSION = 2 ;
feat: initial InformatiQ Toolkit plugin
Merges informatiq-wp-secure + informatiq-utils + HoneypotFields into
a single unified plugin with the following improvements:
- Fixed deactivation bug: all protection methods now guard themselves
with their own option check so toggling off via AJAX takes effect
immediately without any hook re-registration.
- Added rate-limiting for good/legitimate bots (Googlebot, Bingbot,
DuckDuckBot, Yandex, etc.) via transient sliding-window counters;
configurable per-bot limits in goodbots.conf (BotName|req/min);
returns HTTP 429 with Retry-After: 60 when over limit.
- Unified MySQL-backed logging (itk_bot_log + itk_honeypot_log tables)
replaces the old wp_options-based 100-entry cap.
- New Dashboard tab with terminal-style bot activity monitor: total
blocked, today's count, rate-limited hits, top threat sources
(bar chart), top IPs, top honeypot form types, active-module
status panel.
- All optimizations from utils.php merged into Optimization tab as
toggleable settings (was always-on before).
- Single admin page (Settings → InformatiQ Toolkit) with 8 tabs:
Dashboard | Bot Blocker | Protection | Optimization | Honeypot |
Bot Logs | Honeypot Logs | Config Files.
- Config file editor for badbots.conf, goodbots.conf, referrers.conf,
networks.conf, allowed-ips.conf with AJAX save and transient flush.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 11:45:26 +02:00
const DB_VERSION_OPTION = 'itk_db_version' ;
/* ── Table names ──────────────────────────────────────────── */
public static function bot_table () : string {
global $wpdb ;
return $wpdb -> prefix . 'itk_bot_log' ;
}
public static function honeypot_table () : string {
global $wpdb ;
return $wpdb -> prefix . 'itk_honeypot_log' ;
}
2026-04-10 09:37:31 +02:00
public static function attack_table () : string {
global $wpdb ;
return $wpdb -> prefix . 'itk_attack_log' ;
}
feat: initial InformatiQ Toolkit plugin
Merges informatiq-wp-secure + informatiq-utils + HoneypotFields into
a single unified plugin with the following improvements:
- Fixed deactivation bug: all protection methods now guard themselves
with their own option check so toggling off via AJAX takes effect
immediately without any hook re-registration.
- Added rate-limiting for good/legitimate bots (Googlebot, Bingbot,
DuckDuckBot, Yandex, etc.) via transient sliding-window counters;
configurable per-bot limits in goodbots.conf (BotName|req/min);
returns HTTP 429 with Retry-After: 60 when over limit.
- Unified MySQL-backed logging (itk_bot_log + itk_honeypot_log tables)
replaces the old wp_options-based 100-entry cap.
- New Dashboard tab with terminal-style bot activity monitor: total
blocked, today's count, rate-limited hits, top threat sources
(bar chart), top IPs, top honeypot form types, active-module
status panel.
- All optimizations from utils.php merged into Optimization tab as
toggleable settings (was always-on before).
- Single admin page (Settings → InformatiQ Toolkit) with 8 tabs:
Dashboard | Bot Blocker | Protection | Optimization | Honeypot |
Bot Logs | Honeypot Logs | Config Files.
- Config file editor for badbots.conf, goodbots.conf, referrers.conf,
networks.conf, allowed-ips.conf with AJAX save and transient flush.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 11:45:26 +02:00
/* ── 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 }; " ;
2026-04-10 09:37:31 +02:00
$sql_atk = " CREATE TABLE " . self :: attack_table () . " (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT ,
logged_at DATETIME NOT NULL ,
ip_address VARCHAR ( 45 ) NOT NULL DEFAULT '' ,
attack_type VARCHAR ( 50 ) NOT NULL DEFAULT '' ,
rule_desc VARCHAR ( 255 ) NOT NULL DEFAULT '' ,
source VARCHAR ( 20 ) NOT NULL DEFAULT '' ,
param VARCHAR ( 200 ) NOT NULL DEFAULT '' ,
payload TEXT NOT NULL ,
request_uri VARCHAR ( 1000 ) NOT NULL DEFAULT '' ,
method VARCHAR ( 10 ) NOT NULL DEFAULT '' ,
user_agent TEXT NOT NULL ,
PRIMARY KEY ( id ),
KEY ip_address ( ip_address ),
KEY logged_at ( logged_at ),
KEY attack_type ( attack_type )
) { $charset }; " ;
feat: initial InformatiQ Toolkit plugin
Merges informatiq-wp-secure + informatiq-utils + HoneypotFields into
a single unified plugin with the following improvements:
- Fixed deactivation bug: all protection methods now guard themselves
with their own option check so toggling off via AJAX takes effect
immediately without any hook re-registration.
- Added rate-limiting for good/legitimate bots (Googlebot, Bingbot,
DuckDuckBot, Yandex, etc.) via transient sliding-window counters;
configurable per-bot limits in goodbots.conf (BotName|req/min);
returns HTTP 429 with Retry-After: 60 when over limit.
- Unified MySQL-backed logging (itk_bot_log + itk_honeypot_log tables)
replaces the old wp_options-based 100-entry cap.
- New Dashboard tab with terminal-style bot activity monitor: total
blocked, today's count, rate-limited hits, top threat sources
(bar chart), top IPs, top honeypot form types, active-module
status panel.
- All optimizations from utils.php merged into Optimization tab as
toggleable settings (was always-on before).
- Single admin page (Settings → InformatiQ Toolkit) with 8 tabs:
Dashboard | Bot Blocker | Protection | Optimization | Honeypot |
Bot Logs | Honeypot Logs | Config Files.
- Config file editor for badbots.conf, goodbots.conf, referrers.conf,
networks.conf, allowed-ips.conf with AJAX save and transient flush.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 11:45:26 +02:00
require_once ABSPATH . 'wp-admin/includes/upgrade.php' ;
dbDelta ( $sql_bot );
dbDelta ( $sql_hp );
2026-04-10 09:37:31 +02:00
dbDelta ( $sql_atk );
feat: initial InformatiQ Toolkit plugin
Merges informatiq-wp-secure + informatiq-utils + HoneypotFields into
a single unified plugin with the following improvements:
- Fixed deactivation bug: all protection methods now guard themselves
with their own option check so toggling off via AJAX takes effect
immediately without any hook re-registration.
- Added rate-limiting for good/legitimate bots (Googlebot, Bingbot,
DuckDuckBot, Yandex, etc.) via transient sliding-window counters;
configurable per-bot limits in goodbots.conf (BotName|req/min);
returns HTTP 429 with Retry-After: 60 when over limit.
- Unified MySQL-backed logging (itk_bot_log + itk_honeypot_log tables)
replaces the old wp_options-based 100-entry cap.
- New Dashboard tab with terminal-style bot activity monitor: total
blocked, today's count, rate-limited hits, top threat sources
(bar chart), top IPs, top honeypot form types, active-module
status panel.
- All optimizations from utils.php merged into Optimization tab as
toggleable settings (was always-on before).
- Single admin page (Settings → InformatiQ Toolkit) with 8 tabs:
Dashboard | Bot Blocker | Protection | Optimization | Honeypot |
Bot Logs | Honeypot Logs | Config Files.
- Config file editor for badbots.conf, goodbots.conf, referrers.conf,
networks.conf, allowed-ips.conf with AJAX save and transient flush.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 11:45:26 +02:00
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
));
}
2026-04-10 09:37:31 +02:00
/* ── Attack log ───────────────────────────────────────────── */
public static function log_attack ( array $data ) : void {
global $wpdb ;
$wpdb -> insert (
self :: attack_table (),
[
'logged_at' => current_time ( 'mysql' ),
'ip_address' => sanitize_text_field ( $data [ 'ip' ] ? ? '' ),
'attack_type' => sanitize_text_field ( $data [ 'attack_type' ] ? ? '' ),
'rule_desc' => sanitize_text_field ( $data [ 'rule_desc' ] ? ? '' ),
'source' => sanitize_text_field ( $data [ 'source' ] ? ? '' ),
'param' => sanitize_text_field ( $data [ 'param' ] ? ? '' ),
'payload' => sanitize_textarea_field ( substr ( $data [ 'payload' ] ? ? '' , 0 , 500 )),
'request_uri' => sanitize_text_field ( substr ( $data [ 'uri' ] ? ? '' , 0 , 1000 )),
'method' => sanitize_text_field ( $data [ 'method' ] ? ? '' ),
'user_agent' => sanitize_textarea_field ( $data [ 'ua' ] ? ? '' ),
],
[ '%s' , '%s' , '%s' , '%s' , '%s' , '%s' , '%s' , '%s' , '%s' , '%s' ]
);
}
public static function get_attack_rows ( array $args = []) : array {
global $wpdb ;
$table = self :: attack_table ();
$limit = max ( 1 , ( int )( $args [ 'per_page' ] ? ? 25 ));
$offset = max ( 0 , ( int )( $args [ 'offset' ] ? ? 0 ));
$where = '1=1' ;
$params = [];
if ( ! empty ( $args [ 'attack_type' ])) {
$where .= ' AND attack_type = %s' ;
$params [] = $args [ 'attack_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 param LIKE %s OR payload LIKE %s OR rule_desc LIKE %s)' ;
$params [] = $like ; $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_attack_rows ( array $args = []) : int {
global $wpdb ;
$table = self :: attack_table ();
$where = '1=1' ;
$params = [];
if ( ! empty ( $args [ 'attack_type' ])) {
$where .= ' AND attack_type = %s' ;
$params [] = $args [ 'attack_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 param LIKE %s OR payload LIKE %s OR rule_desc LIKE %s)' ;
$params [] = $like ; $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_attack_types () : array {
global $wpdb ;
return $wpdb -> get_col ( " SELECT DISTINCT attack_type FROM " . self :: attack_table () . " WHERE attack_type != '' ORDER BY attack_type ASC " ) ? : [];
}
public static function clear_attack_log () : void {
global $wpdb ;
$wpdb -> query ( " TRUNCATE TABLE " . self :: attack_table ());
}
feat: initial InformatiQ Toolkit plugin
Merges informatiq-wp-secure + informatiq-utils + HoneypotFields into
a single unified plugin with the following improvements:
- Fixed deactivation bug: all protection methods now guard themselves
with their own option check so toggling off via AJAX takes effect
immediately without any hook re-registration.
- Added rate-limiting for good/legitimate bots (Googlebot, Bingbot,
DuckDuckBot, Yandex, etc.) via transient sliding-window counters;
configurable per-bot limits in goodbots.conf (BotName|req/min);
returns HTTP 429 with Retry-After: 60 when over limit.
- Unified MySQL-backed logging (itk_bot_log + itk_honeypot_log tables)
replaces the old wp_options-based 100-entry cap.
- New Dashboard tab with terminal-style bot activity monitor: total
blocked, today's count, rate-limited hits, top threat sources
(bar chart), top IPs, top honeypot form types, active-module
status panel.
- All optimizations from utils.php merged into Optimization tab as
toggleable settings (was always-on before).
- Single admin page (Settings → InformatiQ Toolkit) with 8 tabs:
Dashboard | Bot Blocker | Protection | Optimization | Honeypot |
Bot Logs | Honeypot Logs | Config Files.
- Config file editor for badbots.conf, goodbots.conf, referrers.conf,
networks.conf, allowed-ips.conf with AJAX save and transient flush.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 11:45:26 +02:00
}