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>
This commit is contained in:
2026-04-09 11:45:26 +02:00
commit 6d4349ff7b
17 changed files with 3739 additions and 0 deletions

310
assets/css/admin.css Normal file
View File

@@ -0,0 +1,310 @@
/* ============================================================
InformatiQ Toolkit Admin CSS
============================================================ */
/* ── Page wrapper ─────────────────────────────────────────── */
.itk-wrap { max-width: 1400px; }
.itk-page-title {
display: flex;
align-items: center;
gap: 10px;
font-size: 22px;
margin: 20px 0 16px;
}
.itk-logo {
display: inline-flex;
align-items: center;
justify-content: center;
width: 36px; height: 36px;
background: #1a4a8a;
color: #fff;
font-weight: 900;
font-size: 14px;
border-radius: 6px;
letter-spacing: -1px;
}
/* ── Tab navigation ───────────────────────────────────────── */
.itk-tabs {
display: flex;
flex-wrap: wrap;
gap: 2px;
border-bottom: 2px solid #c3c4c7;
margin-bottom: 20px;
}
.itk-tab {
padding: 8px 16px;
text-decoration: none;
color: #2c3338;
font-size: 13px;
border-radius: 4px 4px 0 0;
border: 1px solid transparent;
border-bottom: none;
background: #f0f0f1;
margin-bottom: -2px;
}
.itk-tab:hover { background: #fff; color: #1a4a8a; }
.itk-tab-active {
background: #fff;
border-color: #c3c4c7;
color: #1a4a8a;
font-weight: 600;
}
/* ── Settings grid ────────────────────────────────────────── */
.itk-settings-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
gap: 20px;
align-items: start;
}
.itk-card {
background: #fff;
border: 1px solid #c3c4c7;
border-radius: 6px;
padding: 20px 24px;
}
.itk-card h2 {
margin: 0 0 16px;
font-size: 15px;
color: #1a4a8a;
border-bottom: 1px solid #e5e5e5;
padding-bottom: 8px;
}
/* ── Toggle rows ──────────────────────────────────────────── */
.itk-toggle-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 0;
border-bottom: 1px solid #f0f0f1;
gap: 12px;
}
.itk-toggle-row:last-child { border-bottom: none; }
.itk-toggle-info { flex: 1; }
.itk-toggle-label { display: block; font-weight: 600; font-size: 13px; color: #2c3338; }
.itk-toggle-desc { display: block; font-size: 11px; color: #646970; margin-top: 2px; }
/* ── Toggle switch ────────────────────────────────────────── */
.itk-switch {
position: relative;
display: inline-block;
width: 44px;
height: 24px;
flex-shrink: 0;
}
.itk-switch input { opacity: 0; width: 0; height: 0; }
.itk-slider {
position: absolute;
cursor: pointer;
inset: 0;
background: #ccc;
border-radius: 24px;
transition: .25s;
}
.itk-slider:before {
content: '';
position: absolute;
width: 18px; height: 18px;
left: 3px; bottom: 3px;
background: #fff;
border-radius: 50%;
transition: .25s;
}
input:checked + .itk-slider { background: #2271b1; }
input:checked + .itk-slider:before { transform: translateX(20px); }
/* ── Dashboard ────────────────────────────────────────────── */
.itk-dashboard {
display: grid;
grid-template-columns: 1fr 280px;
gap: 20px;
align-items: start;
}
@media (max-width: 960px) { .itk-dashboard { grid-template-columns: 1fr; } }
/* Monitor panel terminal style */
.itk-monitor-panel {
background: #0d1117;
color: #58a6ff;
border-radius: 8px;
padding: 20px 24px;
font-family: 'Courier New', Courier, monospace;
}
.itk-monitor-header {
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #21262d;
padding-bottom: 12px;
margin-bottom: 16px;
}
.itk-monitor-title { font-weight: 700; font-size: 13px; letter-spacing: 1px; color: #79c0ff; }
.itk-monitor-blink {
background: #238636;
color: #fff;
padding: 2px 8px;
border-radius: 3px;
font-size: 11px;
font-weight: 700;
letter-spacing: 1px;
animation: itk-pulse 2s infinite;
}
@keyframes itk-pulse { 0%,100%{opacity:1} 50%{opacity:.5} }
/* Stat cards (inside monitor) */
.itk-stat-row {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin-bottom: 20px;
}
.itk-stat-card {
background: #161b22;
border: 1px solid #21262d;
border-radius: 6px;
padding: 12px 18px;
min-width: 110px;
text-align: center;
flex: 1;
}
.itk-stat-num { font-size: 2em; font-weight: 700; color: #58a6ff; line-height: 1.2; }
.itk-stat-lbl { font-size: 10px; color: #8b949e; letter-spacing: .5px; text-transform: uppercase; }
.itk-green { color: #3fb950; }
.itk-yellow { color: #d29922; }
/* Chart sections */
.itk-chart-section { margin-bottom: 20px; }
.itk-chart-title {
font-size: 10px;
letter-spacing: 1px;
color: #8b949e;
text-transform: uppercase;
margin-bottom: 8px;
}
.itk-bar-chart { display: flex; flex-direction: column; gap: 6px; }
.itk-bar-row { display: flex; align-items: center; gap: 8px; font-size: 12px; }
.itk-bar-label { width: 130px; color: #c9d1d9; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.itk-bar-track { flex: 1; background: #21262d; border-radius: 3px; height: 14px; overflow: hidden; }
.itk-bar-fill { height: 100%; background: linear-gradient(90deg, #1f6feb, #58a6ff); border-radius: 3px; transition: width .4s; }
.itk-bar-hp { background: linear-gradient(90deg, #6e40c9, #a371f7) !important; }
.itk-bar-count { width: 40px; text-align: right; color: #8b949e; font-size: 11px; }
/* Mini table (top IPs) */
.itk-mini-table { width: 100%; border-collapse: collapse; font-size: 12px; }
.itk-mini-table th { color: #8b949e; font-weight: normal; padding: 4px 8px; border-bottom: 1px solid #21262d; }
.itk-mini-table td { color: #c9d1d9; padding: 4px 8px; border-bottom: 1px solid #161b22; }
.itk-mini-table a { color: #58a6ff; }
.itk-lookup { color: #8b949e; font-size: 10px; }
/* Quick status sidebar */
.itk-quick-status {
background: #fff;
border: 1px solid #c3c4c7;
border-radius: 6px;
padding: 16px 20px;
}
.itk-quick-status .itk-chart-title { color: #1a4a8a; }
.itk-module-row {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 0;
border-bottom: 1px solid #f0f0f1;
font-size: 12px;
}
.itk-module-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
.itk-dot-on { background: #00a32a; }
.itk-dot-off { background: #c3c4c7; }
.itk-module-label { flex: 1; color: #2c3338; }
.itk-module-status { font-size: 10px; font-weight: 700; letter-spacing: .5px; }
.itk-dot-on ~ .itk-module-status { color: #00a32a; }
.itk-dot-off ~ .itk-module-status { color: #c3c4c7; }
/* ── Log tables ───────────────────────────────────────────── */
.itk-log-page {}
.itk-filters {
display: flex;
flex-wrap: wrap;
gap: 8px;
align-items: center;
margin-bottom: 12px;
}
.itk-filters input[type=text],
.itk-filters select { height: 32px; padding: 0 8px; }
.itk-count { color: #646970; font-size: 13px; margin: 0 0 8px; }
.itk-log-table { font-size: 12px; }
.itk-log-table th { white-space: nowrap; }
.itk-nowrap { white-space: nowrap; }
.itk-ua { color: #646970; font-size: 11px; max-width: 220px; word-break: break-all; }
.itk-uri { font-size: 11px; color: #646970; max-width: 200px; word-break: break-all; }
.itk-no-results { text-align: center; padding: 20px; color: #646970; }
.itk-filter-link { font-size: 10px; color: #2271b1; text-decoration: none; }
.itk-filter-link:hover { text-decoration: underline; }
/* Badges */
.itk-badge {
display: inline-block;
padding: 2px 8px;
border-radius: 3px;
font-size: 10px;
font-weight: 700;
letter-spacing: .5px;
text-transform: uppercase;
}
.itk-badge-block { background: #ffecec; color: #b32d2e; border: 1px solid #f7c5c5; }
.itk-badge-warn { background: #fef3cd; color: #856404; border: 1px solid #fde68a; }
.itk-badge-hp { background: #f3e8ff; color: #6e40c9; border: 1px solid #d4a9ff; }
/* Pager */
.itk-pager {
display: flex;
align-items: center;
gap: 8px;
margin: 12px 0;
font-size: 13px;
}
.itk-pager span { color: #646970; }
/* Danger button */
.itk-btn-danger { color: #b32d2e !important; border-color: #b32d2e !important; }
.itk-btn-danger:hover { background: #b32d2e !important; color: #fff !important; }
/* ── Config editor ────────────────────────────────────────── */
.itk-config-editor {}
.itk-config-tabs {
display: flex;
gap: 4px;
margin-bottom: 16px;
flex-wrap: wrap;
}
.itk-config-tab {
padding: 6px 14px;
background: #f0f0f1;
border: 1px solid #c3c4c7;
border-radius: 4px;
text-decoration: none;
color: #2c3338;
font-size: 12px;
}
.itk-config-tab.active { background: #2271b1; color: #fff; border-color: #2271b1; }
.itk-config-editor-area { background: #fff; border: 1px solid #c3c4c7; border-radius: 6px; padding: 20px; }
.itk-config-textarea {
width: 100%;
font-family: 'Courier New', Courier, monospace;
font-size: 12px;
background: #0d1117;
color: #c9d1d9;
border: 1px solid #30363d;
border-radius: 4px;
padding: 12px;
resize: vertical;
box-sizing: border-box;
}
/* ── Toggle feedback ──────────────────────────────────────── */
.itk-toggle-saving { opacity: .6; pointer-events: none; }
.itk-toggle-saved { color: #00a32a; font-size: 11px; }
.itk-toggle-error { color: #b32d2e; font-size: 11px; }

79
assets/js/admin.js Normal file
View File

@@ -0,0 +1,79 @@
/* InformatiQ Toolkit Admin JS */
(function ($) {
'use strict';
/* ── Toggle switches (AJAX) ───────────────────────────────── */
$(document).on('change', '.itk-toggle-input', function () {
var $input = $(this);
var $row = $input.closest('.itk-toggle-row');
var option = $input.data('option');
var setting = $input.data('setting');
var value = $input.is(':checked') ? 1 : 0;
$row.addClass('itk-toggle-saving');
$.post(itkAdmin.ajaxUrl, {
action: 'itk_save_setting',
nonce: itkAdmin.nonce,
option: option,
setting: setting,
value: value
})
.done(function (res) {
if (res.success) {
showFeedback($row, 'itk-toggle-saved', 'Saved');
} else {
$input.prop('checked', !$input.is(':checked')); // revert
showFeedback($row, 'itk-toggle-error', 'Error saving');
}
})
.fail(function () {
$input.prop('checked', !$input.is(':checked'));
showFeedback($row, 'itk-toggle-error', 'Request failed');
})
.always(function () {
$row.removeClass('itk-toggle-saving');
});
});
function showFeedback($row, cls, msg) {
$row.find('.itk-feedback').remove();
var $fb = $('<span class="itk-feedback ' + cls + '">' + msg + '</span>');
$row.append($fb);
setTimeout(function () { $fb.fadeOut(400, function () { $(this).remove(); }); }, 2000);
}
/* ── Config file editor (AJAX) ────────────────────────────── */
$('#itk-save-config').on('click', function (e) {
e.preventDefault();
var $btn = $(this);
var file = $btn.data('file');
var content = $('#itk-config-content').val();
var $status = $('#itk-config-status');
$btn.prop('disabled', true).text('Saving…');
$status.hide();
$.post(itkAdmin.ajaxUrl, {
action: 'itk_save_config_file',
nonce: itkAdmin.nonce,
file: file,
content: content
})
.done(function (res) {
if (res.success) {
$status.text('Saved!').css('color', '#00a32a').show();
} else {
$status.text('Error: ' + (res.data || 'unknown')).css('color', '#b32d2e').show();
}
})
.fail(function () {
$status.text('Request failed.').css('color', '#b32d2e').show();
})
.always(function () {
$btn.prop('disabled', false).text('Save File');
setTimeout(function () { $status.fadeOut(); }, 3000);
});
});
})(jQuery);

6
config/allowed-ips.conf Normal file
View File

@@ -0,0 +1,6 @@
194.56.239.153
109.69.48.0
195.154.47.0
127.0.0.1
192.168.0.0/24
192.168.1.1/24

406
config/badbots.conf Normal file
View File

@@ -0,0 +1,406 @@
# OpenAI bots are handled separately in the plugin code
# Common malicious bots and user agents from .htaccess
jorgee
morfeus
firefox/40.1
firefox/34.0
firefox/32.1
firefox/19.0
firefox/38.0
firefox/18.0
wget
curl
libwww-perl
WinHttp
okhttp
python
java
WebReaper
WebSauger
Website eXtractor
Website Quester
Webster
WebStripper
WebWhacker
WebZIP
Whacker
BatchFTP
HTTrack
Harvest
Collector
Copier
Extractor
lftp
libWeb/clsHTTP
Mirror
Net Vampire
Offline Explorer
Offline Navigator
PageGrabber
Sucker
SuperHTTP
Teleport
Vacuum
Web Sucker
WebAuto
WebBandit
Webclipping.com
WebCopier
WebEnhancer
WebFetch
WebLeacher
WWWOFFLE
WWW-Collector-E
Go-Ahead-Got-It
gotit
GrabNet
lwp-trivial
LWP::Simple
Magnet
Mag-Net
moget
MIDown tool
NetSpider
NetZIP
Reaper
Recorder
ReGet
RepoMonkey
Siphon
SiteSnagger
AppsViewer
Lynx
Acunetix
FHscan
Baidu
Yandex
Download Demon
Download Devil
Download Wonder
EirGrabber
EasyDL
Mass Downloader
RealDownload
SmartDownload
EmailCollector
EmailSiphon
EmailWolf
WebEMailExtrac
EmailSiphon
Mail
slurp
MJ12
FastProbe
spbot
dotbot
semrush
Daum
duckduckgo
teoma
Aboundex
80legs
360Spider
Cogentbot
Alexibot
asterias
attach
BackDoorBot
BackWeb
Bandit
Bigfoot
Black.Hole
BlackWidow
BlowFish
BotALot
Buddy
BuiltBotTough
Bullseye
BunnySlippers
Cegbfeieh
CheeseBot
CherryPicker
ChinaClaw
CopyRightCheck
cosmos
Crescent
Custo
AIBOT
DISCo
DIIbot
DittoSpyder
dragonfly
Drip
eCatch
ebingbong
EroCrawler
EyeNetIE
Foobot
flunky
FrontPage
Grafula
hloader
HMView
humanlinks
IlseBot
Indy Library
InfoNaviRobot
InfoTekies
Intelliseek
InterGET
Internet Ninja
Iria
Jakarta
JennyBot
JetCar
JOC
JustView
Jyxobot
Kenjin.Spider
Keyword.Density
larbin
LexiBot
likse
MarkWatch
Mata.Hari
Memo
Microsoft.URL
Microsoft URL Control
MIIxpc
Missigua Locator
Mister PiX
NAMEPROTECT
Navroad
NearSite
NetAnts
Netcraft
NetMechanic
NextGenSearchBot
NICErsPRO
niki-bot
NimbleCrawler
Ninja
NPbot
Octopus
Openfind
OutfoxBot
Papa Foto
pavuk
pcBrowser
PHP version tracker
Pockey
ProPowerBot/2.14
ProWebWalker
psbot
Pump
QueryN.Metasearch
SlySearch
Snake
Snapbot
Snoopy
sogou
SpaceBison
SpankBot
spanner
Sqworm
Stripper
SuperBot
Surfbot
suzuran
Szukacz/1.4
tAkeOut
Telesoft
TurnitinBot/1.5
The.Intraformant
TheNomad
TightTwatBot
Titan
True_Robot
turingos
TurnitinBot
URLy.Warning
VCI
VoidEYE
WebmasterWorldForumBot
WebGo IS
Widow
WISENutbot
Xaldon
Zeus
ZmEu
Zyborg
crawle
igdeSpyder
Robot
Aport
spider
Parser
ahref
zoom
Powermarks
SafeDNS
BLEXBot
aria2
wikido
Qwantify
DotBot
FatBot
grapeshot
Nutch
linkdexbot
Twitterbot
Google-HTTP-Java-Client
MetaCommentBot
Veoozbot
ScoutJet
DomainAppender
Windows 2005
Go-http-client
Drupal
OrangeBot
CCBot
WBSearchBot
SEOkicks
WHR
sqlmap
ltx71
aiHitBot
InfoPath
Superfeedr
rogerbot
Alltop
heritrix
indiensolidaritet
Experibot
magpie
RSSInclude
wp-android
XML-RPC.NET
Synapse
GimmeUSAbot
istellabot
interfax
vebidoobot
oBot
Jetty
mozilla16.2.exe
dataaccessd
(compatible;)
Dalvik
eCairn
istellabot
InetURL
BazQux
Wotbox
null
scrapy-redis
weborama-fetcher
TrapitAgent
UNKNOWN
SeznamBot
Dataprovider
msnbot-Products
masscan
istellabot
BUbiNG
.NET
cliqzbot
Deepnet
Ziba
SMTBot
MojeekBot
linqia
portscout
Dataprovider
ia_archiver
Dalvik
MEGAsync
GroupHigh
Moreover
YisouSpider
YahooCacheSystem
Clickagy
Go-http-client
SMUrlExpander
XoviBot
MSIE3.00
MSIE2.00
MSIE4.00
MSIECrawler
Windows 2005
Windows 2008
Windows 2004
Windows 2003
Windows 2002
XoviBot
Qwantify
BOT for JCE
Jorgee
YaK
iTunes
Mechanize
Mail.RU_Bot
zgrab
Owler
Barkrowler
SearchmetricsBot
extlinks
archive-it
BDCbot
SuperPagesUrlVerifyBot
Siteimprove
Freshbot
WebDAV
ips-agent
PiplBot
coccocbot-web
Alexa Toolbar
scrapinghub
Twingly
sysscan
trendictionbot0
DnyzBot
rogerbot
GridBot
DnyzBot
PiplBot
BoardReader
SafeDNSBot
Insideview
coccocbot
PolycomVVX
^Mozilla/5.0$
^The Knowledge AI
SputnikBot
od-database-crawler
Hype%20Machine
The Hype Machine Engine
Apache-HttpClient
Goodzer
Knowledge
Linguee
serpstatbot
PHP/5
PHP/4
PHP/3
Thumbtack-Thunderdome
Googlebot-Image
Googlebot-Video
bingpreview
msnbot-media
Exabot
Image Stripper
Image Sucker
Express WebPictures
Web Image Collector
Web.Image.Collector
YandexImages
Firefox mutant
Ukraine Local
Mozilla/3.Mozilla/2.01
Mozilla.*NEWT
LinkextractorPro
LinkScan/8.1a.Unix
LNSpiderguy
LinkWalker
Xenu

23
config/goodbots.conf Normal file
View File

@@ -0,0 +1,23 @@
# Good/Legitimate bots - these are rate-limited but NOT blocked
# Format: BotName|rate_per_minute
# Lines starting with # are comments
Googlebot|60
Bingbot|60
DuckDuckBot|60
Baiduspider|30
YandexBot|30
Sogou|20
Applebot|30
facebot|30
ia_archiver|20
Twitterbot|30
LinkedInBot|30
Slurp|30
MJ12bot|20
AhrefsBot|10
SemrushBot|10
DotBot|20
PetalBot|20
Bytespider|20
GPTBot|0

254
config/networks.conf Normal file
View File

@@ -0,0 +1,254 @@
# IP addresses and networks to block extracted from .htaccess
# Aliyun
121.40.0.0/14
121.40.0.0/15
# Cyveillance subnets
38.100.19.8/29
38.100.21.0/24
38.100.41.64/26
38.105.71.0/25
38.105.83.0/27
38.112.21.140/30
38.118.42.32/29
65.213.208.128/27
65.222.176.96/27
65.222.185.72/29
# Poneytelecom subnets
62.4.0.0/19
62.210.0.0/16
195.154.0.0/16
212.47.224.0/19
212.83.128.0/19
212.83.160.0/19
212.129.0.0/18
# Ecatel & Leaseweb subnets
80.82.64.0/24
80.82.65.0/24
80.82.66.0/24
80.82.67.0/24
80.82.68.0/24
80.82.69.0/24
80.82.70.0/24
80.82.76.0/24
80.82.77.0/24
80.82.78.0/24
80.82.79.0/24
89.248.160.0/21
89.248.168.0/24
89.248.169.0/24
89.248.170.0/23
89.248.172.0/23
89.248.174.0/24
93.174.88.0/21
94.102.48.0/20
188.72.106.0/24
188.72.117.0/24
185.56.80.125
# Aboundex
173.192.34.95
# Bluecoat
8.21.4.254
65.46.48.192/30
65.160.238.176/28
85.92.222.0/24
206.51.36.0/22
216.52.23.0/24
# Cyberpatrol
38.103.17.160/27
# Internet Identity - Anti-Phishing
66.113.96.0/20
70.35.113.192/27
# Ironport
204.15.80.0/22
# Lightspeed Systems Security
66.17.15.128/26
69.84.207.32/27
69.84.207.128/25
# Layered Technologies
72.36.128.0/17
72.232.0.0/16
72.233.0.0/17
216.32.0.0/14
# M86
67.192.231.224/29
208.90.236.0/22
# Phish-Inspector.com
209.147.127.208/28
# Prescient Software, Inc. Phishmongers
198.186.190.0/23
198.186.192.0/23
198.186.194.0/24
# urlfilterdb
207.210.99.32/29
# websense-in.car1.sandiego1.level3.net
4.53.120.22
# Websense
66.194.6.0/24
67.117.201.128/28
69.67.32.0/20
131.191.87.0/24
204.15.64.0/21
208.80.192.0/21
212.62.26.64/27
213.168.226.0/24
213.168.241.0/30
213.168.242.0/30
213.236.150.16/28
# IP Strada & co.
162.211.104.0/22
162.218.56.0/21
198.89.232.0/21
199.15.232.0/21
199.15.232.0/24
199.15.233.0/24
199.15.234.0/24
199.15.235.0/24
199.15.237.0/24
199.15.238.0/24
199.15.239.0/24
# DigitalOcean
45.55.100.0/22
45.55.116.0/22
67.207.66.0/24
104.131.192.0/19
104.131.224.0/19
107.170.0.0/17
107.170.128.0/19
107.170.160.0/19
138.197.240.0/22
138.197.252.0/22
159.203.152.0/22
162.243.0.0/17
162.243.191.0/24
162.243.192.0/18
192.241.160.0/19
192.241.240.0/20
# vHoster Ukraine doing WP bruteforce
91.200.12.0/22
# Drake Holdings
192.92.196.0/24
204.79.180.0/24
# Hetzner Denies
193.47.99.0/24
188.40.0.0/16
185.12.64.0/22
178.63.0.0/16
176.9.0.0/16
213.239.192.0/18
213.133.96.0/19
88.198.0.0/16
85.10.192.0/18
78.46.0.0/15
5.9.0.0/17
5.9.0.0/16
46.4.0.0/16
88.99.0.0/16
91.220.49.0/24
91.233.8.0/22
94.130.0.0/16
95.216.0.0/16
95.217.0.0/16
136.243.0.0/16
138.201.0.0/16
144.76.0.0/16
148.251.0.0/16
176.102.168.0/21
185.50.120.0/23
185.107.52.0/22
185.126.28.0/22
185.136.140.0/23
185.141.200.0/24
185.141.202.0/24
185.171.224.0/22
185.185.26.0/23
185.189.228.0/24
185.189.230.0/24
185.189.231.0/24
185.209.124.0/22
185.216.237.0/24
185.228.8.0/22
193.25.170.0/23
193.110.6.0/23
193.223.77.0/24
194.42.180.0/22
194.42.184.0/22
194.145.226.0/24
195.60.226.0/24
195.248.224.0/24
197.242.84.0/22
# Seznam bot
77.75.72.0/23
77.75.74.0/24
77.75.75.0/24
77.75.76.0/23
77.75.78.0/23
185.66.188.0/22
# Quasi Networks - Spammers
145.249.104.0/22
185.216.140.0/23
188.72.103.0/24
188.72.106.0/24
188.72.117.0/24
196.16.0.0/14
213.184.105.0/24
213.184.113.0/24
213.184.115.0/24
213.184.117.0/24
# DataShack / Wholesale Internet / VPN Consumer Network / My Server Planet / VoIP DediNet & co.
63.141.224.0/19
69.30.192.0/24
69.30.204.0/24
69.30.220.0/24
69.30.228.0/24
69.30.235.0/24
69.30.237.0/24
69.197.148.0/24
69.197.152.0/24
69.197.170.0/24
69.197.171.0/24
69.197.173.0/24
69.197.178.0/24
74.91.16.0/20
104.37.30.0/24
107.150.32.0/19
142.54.160.0/19
173.46.91.0/24
173.46.93.0/24
192.151.144.0/20
192.187.96.0/19
198.204.224.0/19
199.168.96.0/21
204.12.199.0/24
204.12.200.0/24
204.12.203.0/24
204.12.205.0/24
204.12.245.0/24
208.67.0.0/24
208.67.1.0/24
208.110.85.0/24
208.110.87.0/24

66
config/payloads.conf Normal file
View File

@@ -0,0 +1,66 @@
# Known attack payload regex patterns
# One pattern per line, these are checked against request parameters and user input
# Lines starting with # are comments
# XSS attack patterns
# Pattern for alert/prompt/confirm execution
/(?:<|%3C|&lt;)(?:script|iframe|svg|img|a).*?(?:alert|prompt|confirm|eval)\s*\(.*?\)/i
# Pattern for script injection
/(?:<|%3C|&lt;)script.*?(?:>|%3E|&gt;)/i
# Pattern for event handlers like onerror, onload, etc.
/\bon(?:error|load|click|mouseover|focus|blur)\s*=\s*["']?(?:alert|prompt|confirm|eval)/i
# Pattern for javascript: protocol
/javascript\s*:\s*(?:alert|prompt|confirm|eval)/i
# Pattern for data URI scheme with script
/data\s*:\s*(?:text|application)\/(?:javascript|html).*?base64/i
# SQL Injection patterns
# Pattern for basic SQL injection attempts
/(?:'\s*OR\s*'[\w\d]+'?\s*=\s*'[\w\d]+)|(?:"\s*OR\s*"[\w\d]+"?\s*=\s*"[\w\d]+")/i
# Pattern for SQL comments
/(?:--|#|\/\*)[^\w\d]*(?:union|select|insert|update|delete|drop|alter)/i
# Pattern for UNION SELECT attempts
/union\s+(?:all\s+)?select/i
# Pattern for SQL batch commands
/;\s*(?:drop|alter|create|truncate|rename|insert|update|delete)/i
# Remote file inclusion patterns
# Pattern for external URL inclusion
/(?:https?|ftp|php|data|file):\/\/[^\s\n"')>]+/i
# Pattern for directory traversal
/(?:\.\.\/|\.\.\\|\.\.\%2f|\.\.\%5c)[^\s\n"')>]+/i
# Pattern for PHP wrapper usage
/php:\/\/(?:filter|input|memory|output|temp)/i
# Command injection patterns
# Pattern for shell command execution
/[;&|`]\s*(?:ls|cat|cd|pwd|echo|rm|cp|mv|sudo|chmod|chown|wget|curl)/i
# Pattern for command substitution
/\$\([^\)]*\)|`[^`]*`/i
# Pattern for direct system command injection
/system\s*\(|exec\s*\(|shell_exec\s*\(|passthru\s*\(|eval\s*\(/i
# Local file inclusion patterns
# Pattern for path traversal
/(?:\/|\\|\.\.|%2f|%5c)(?:etc|bin|usr|home|var|root|windows|system32)/i
# Pattern for sensitive file access
/(?:\/|\\|\.\.|%2f|%5c)(?:passwd|shadow|hosts|config|wp-config|web\.config)/i
# XML/XXE injection patterns
/<!(?:DOCTYPE|ENTITY)[\s\S]*?(?:SYSTEM|PUBLIC)[\s\S]*?["']/i
# CSRF token extraction
/(?:csrf|xsrf|token|auth)["']?\s*[:=]\s*["']?[a-zA-Z0-9_-]+/i
# Serialization attacks
/[ORCo]:[0-9]+:/i
# General suspicious patterns
# Pattern for base64 encoded payloads
/(?:[A-Za-z0-9+\/]{20,}={0,2})/
# Pattern for hex encoded payloads
/(?:0x[A-Fa-f0-9]{10,})/
# Pattern for URL encoded characters sequence
/(?:%[0-9A-Fa-f]{2}){8,}/
# Pattern for large number of special characters
/[!@#$%^&*()_+\-=\[\]{}|;':",./<>?]{10,}/

35
config/referrers.conf Normal file
View File

@@ -0,0 +1,35 @@
# Known spam or malicious referrers from .htaccess
free-social-buttions.com
best-seo-offer.com
buttons-for-your-website.com
www1.free-social-buttons.com
www2.free-social-buttons.com
www3.free-social-buttons.com
100dollars-seo.com.com
anonymizeme.pro
site.ru
www4.free-social-buttons.com
free-social-buttons.com
buttons-for-website.com
social-buttons.com
anticrawler.org
blackhatworth.com
best-seo-offer.com
buttons-for-your-website.com
best-seo-solution.com
adcash.com
darodar.com
priceg.com
hulfingtonpost.com
gobongo.info
slftsdybbg.ru
ilovevitaly.com
ilovevitaly.co
ilovevitaly.ru
webmonetizer.net
make-money-online.com
cenoval.ru
o-o-6-o-o.com
7makemoneyonline.com
semalt.com
keywords-monitoring-success.com

View File

@@ -0,0 +1,862 @@
<?php
if (!defined('ABSPATH')) exit;
/**
* ITK Admin
*
* Single admin page with tabs:
* Dashboard | Bot Blocker | Protection | Optimization | Honeypot | Bot Logs | Honeypot Logs | Config Files
*/
class ITK_Admin {
const MENU_SLUG = 'informatiq-toolkit';
const NONCE_ACTION = 'itk_admin';
const PER_PAGE = 25;
public function __construct() {
add_action('admin_menu', [$this, 'add_menu']);
add_action('admin_enqueue_scripts', [$this, 'enqueue_assets']);
add_action('admin_init', [$this, 'handle_actions']);
add_action('wp_ajax_itk_save_setting', [$this, 'ajax_save_setting']);
add_action('wp_ajax_itk_save_config_file',[$this, 'ajax_save_config_file']);
}
public function add_menu(): void {
add_options_page(
'InformatiQ Toolkit',
'InformatiQ Toolkit',
'manage_options',
self::MENU_SLUG,
[$this, 'render_page']
);
}
public function enqueue_assets(string $hook): void {
if ($hook !== 'settings_page_' . self::MENU_SLUG) return;
wp_enqueue_style('itk-admin', ITK_URL . 'assets/css/admin.css', [], ITK_VERSION);
wp_enqueue_script('itk-admin', ITK_URL . 'assets/js/admin.js', ['jquery'], ITK_VERSION, true);
wp_localize_script('itk-admin', 'itkAdmin', [
'nonce' => wp_create_nonce(self::NONCE_ACTION),
'ajaxUrl' => admin_url('admin-ajax.php'),
]);
}
/* ── Actions (form submissions) ───────────────────────────── */
public function handle_actions(): void {
if (!isset($_POST['itk_action']) || !check_admin_referer(self::NONCE_ACTION)) return;
if (!current_user_can('manage_options')) wp_die('Unauthorized');
switch ($_POST['itk_action']) {
case 'clear_bot_log':
ITK_Database::clear_bot_log();
$this->redirect(['tab' => 'bot-logs', 'cleared' => 1]);
break;
case 'clear_honeypot_log':
ITK_Database::clear_honeypot_log();
$this->redirect(['tab' => 'honeypot-logs', 'cleared' => 1]);
break;
case 'save_settings_security':
$this->save_settings_form('itk_security', [
'response_code', 'redirect_url', 'custom_message',
], [
'log_blocked_attempts',
]);
$this->redirect(['tab' => 'bot-blocker', 'saved' => 1]);
break;
case 'save_settings_login':
$this->save_settings_form('itk_security', [
'custom_login_slug',
], [
'enable_custom_login',
]);
$this->redirect(['tab' => 'protection', 'saved' => 1]);
break;
case 'save_settings_honeypot':
$this->save_settings_form('itk_honeypot', [
'min_time', 'max_time', 'retain_days',
], []);
$this->redirect(['tab' => 'honeypot', 'saved' => 1]);
break;
}
}
/**
* Save a subset of fields from $_POST['itk_*'] into a WP option.
* $text_fields = scalar fields (sanitize_text_field)
* $toggle_fields = checkbox fields (0 or 1)
*/
private function save_settings_form(string $option, array $text_fields, array $toggle_fields): void {
$opts = get_option($option, []);
$posted = $_POST[$option] ?? [];
foreach ($text_fields as $key) {
if (isset($posted[$key])) {
$opts[$key] = sanitize_text_field(wp_unslash($posted[$key]));
}
}
foreach ($toggle_fields as $key) {
$opts[$key] = !empty($posted[$key]) ? 1 : 0;
}
update_option($option, $opts);
}
private function redirect(array $args): void {
wp_redirect(add_query_arg(array_merge(['page' => self::MENU_SLUG], $args), admin_url('options-general.php')));
exit;
}
/* ── AJAX: save single toggle setting ─────────────────────── */
public function ajax_save_setting(): void {
check_ajax_referer(self::NONCE_ACTION, 'nonce');
if (!current_user_can('manage_options')) wp_send_json_error('unauthorized');
$option = sanitize_key($_POST['option'] ?? '');
$setting = sanitize_key($_POST['setting'] ?? '');
$value = (int)($_POST['value'] ?? 0);
$allowed = ['itk_security','itk_optimization','itk_honeypot'];
if (!in_array($option, $allowed, true) || empty($setting)) {
wp_send_json_error('invalid');
}
$opts = get_option($option, []);
$opts[$setting] = $value;
update_option($option, $opts);
wp_send_json_success();
}
/* ── AJAX: save config file ───────────────────────────────── */
public function ajax_save_config_file(): void {
check_ajax_referer(self::NONCE_ACTION, 'nonce');
if (!current_user_can('manage_options')) wp_send_json_error('unauthorized');
$file = sanitize_key($_POST['file'] ?? '');
$content = wp_unslash($_POST['content'] ?? '');
$allowed = [
'badbots' => ITK_PATH . 'config/badbots.conf',
'goodbots' => ITK_PATH . 'config/goodbots.conf',
'referrers' => ITK_PATH . 'config/referrers.conf',
'networks' => ITK_PATH . 'config/networks.conf',
'allowed-ips'=> ITK_PATH . 'config/allowed-ips.conf',
];
if (!isset($allowed[$file])) wp_send_json_error('invalid file');
$result = file_put_contents($allowed[$file], sanitize_textarea_field($content));
if ($result === false) {
wp_send_json_error('write failed');
}
// Clear transient caches
delete_transient('itk_bots_list');
delete_transient('itk_referrers_list');
delete_transient('itk_networks_list');
delete_transient('itk_goodbots_list');
wp_send_json_success();
}
/* ── Main page render ─────────────────────────────────────── */
public function render_page(): void {
if (!current_user_can('manage_options')) return;
$tab = sanitize_key($_GET['tab'] ?? 'dashboard');
?>
<div class="wrap itk-wrap">
<h1 class="itk-page-title">
<span class="itk-logo">IQ</span> InformatiQ Toolkit
</h1>
<?php if (!empty($_GET['cleared'])): ?>
<div class="notice notice-success is-dismissible"><p>Logs cleared successfully.</p></div>
<?php endif; ?>
<?php if (!empty($_GET['saved'])): ?>
<div class="notice notice-success is-dismissible"><p>Settings saved.</p></div>
<?php endif; ?>
<nav class="itk-tabs">
<?php
$tabs = [
'dashboard' => 'Dashboard',
'bot-blocker' => 'Bot Blocker',
'protection' => 'Protection',
'optimization' => 'Optimization',
'honeypot' => 'Honeypot',
'bot-logs' => 'Bot Logs',
'honeypot-logs' => 'Honeypot Logs',
'config-files' => 'Config Files',
];
foreach ($tabs as $slug => $label):
$active = $tab === $slug ? 'itk-tab-active' : '';
$url = admin_url('options-general.php?page=' . self::MENU_SLUG . '&tab=' . $slug);
?>
<a href="<?= esc_url($url) ?>" class="itk-tab <?= $active ?>"><?= esc_html($label) ?></a>
<?php endforeach; ?>
</nav>
<div class="itk-tab-content">
<?php
match ($tab) {
'dashboard' => $this->tab_dashboard(),
'bot-blocker' => $this->tab_bot_blocker(),
'protection' => $this->tab_protection(),
'optimization' => $this->tab_optimization(),
'honeypot' => $this->tab_honeypot(),
'bot-logs' => $this->tab_bot_logs(),
'honeypot-logs' => $this->tab_honeypot_logs(),
'config-files' => $this->tab_config_files(),
default => $this->tab_dashboard(),
};
?>
</div>
</div>
<?php
}
/* ══════════════════════════════════════════════════════════
* TAB: DASHBOARD
* ══════════════════════════════════════════════════════════ */
private function tab_dashboard(): void {
$bot_stats = ITK_Database::get_bot_stats();
$hp_stats = ITK_Database::get_honeypot_stats();
?>
<div class="itk-dashboard">
<!-- ── Bot Activity Panel ── -->
<section class="itk-monitor-panel">
<div class="itk-monitor-header">
<span class="itk-monitor-title">&#9632; BOT ACTIVITY MONITOR</span>
<span class="itk-monitor-blink">LIVE</span>
</div>
<div class="itk-stat-row">
<div class="itk-stat-card">
<div class="itk-stat-num"><?= number_format($bot_stats['total']) ?></div>
<div class="itk-stat-lbl">Total Blocked</div>
</div>
<div class="itk-stat-card">
<div class="itk-stat-num itk-green"><?= number_format($bot_stats['today']) ?></div>
<div class="itk-stat-lbl">Today</div>
</div>
<div class="itk-stat-card">
<div class="itk-stat-num itk-yellow"><?= number_format($bot_stats['rate_limited']) ?></div>
<div class="itk-stat-lbl">Rate Limited</div>
</div>
<div class="itk-stat-card">
<div class="itk-stat-num"><?= number_format($hp_stats['total']) ?></div>
<div class="itk-stat-lbl">Honeypot Catches</div>
</div>
<div class="itk-stat-card">
<div class="itk-stat-num itk-green"><?= number_format($hp_stats['today']) ?></div>
<div class="itk-stat-lbl">Honeypot Today</div>
</div>
</div>
<!-- Top bot types bar chart -->
<?php if (!empty($bot_stats['top_bot_types'])): ?>
<div class="itk-chart-section">
<div class="itk-chart-title">TOP THREAT SOURCES</div>
<div class="itk-bar-chart">
<?php
$max = max(1, (int)$bot_stats['top_bot_types'][0]->cnt);
foreach ($bot_stats['top_bot_types'] as $row):
$pct = round(($row->cnt / $max) * 100);
?>
<div class="itk-bar-row">
<span class="itk-bar-label"><?= esc_html($row->bot_type ?: 'Unknown') ?></span>
<div class="itk-bar-track">
<div class="itk-bar-fill" style="width:<?= $pct ?>%"></div>
</div>
<span class="itk-bar-count"><?= number_format($row->cnt) ?></span>
</div>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
<!-- Top honeypot form types -->
<?php if (!empty($hp_stats['top_forms'])): ?>
<div class="itk-chart-section">
<div class="itk-chart-title">HONEYPOT TOP TARGETED FORMS</div>
<div class="itk-bar-chart">
<?php
$max = max(1, (int)$hp_stats['top_forms'][0]->cnt);
foreach ($hp_stats['top_forms'] as $row):
$pct = round(($row->cnt / $max) * 100);
?>
<div class="itk-bar-row">
<span class="itk-bar-label"><?= esc_html($row->form_type) ?></span>
<div class="itk-bar-track">
<div class="itk-bar-fill itk-bar-hp" style="width:<?= $pct ?>%"></div>
</div>
<span class="itk-bar-count"><?= number_format($row->cnt) ?></span>
</div>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
<!-- Top IPs -->
<?php if (!empty($bot_stats['top_ips'])): ?>
<div class="itk-chart-section">
<div class="itk-chart-title">TOP OFFENDER IPs</div>
<table class="itk-mini-table">
<tr><th>IP Address</th><th>Hits</th></tr>
<?php foreach ($bot_stats['top_ips'] as $row): ?>
<tr>
<td>
<a href="<?= esc_url(admin_url('options-general.php?page=' . self::MENU_SLUG . '&tab=bot-logs&hp_ip=' . urlencode($row->ip_address))) ?>"><?= esc_html($row->ip_address) ?></a>
&nbsp;<a href="https://ipinfo.io/<?= urlencode($row->ip_address) ?>" target="_blank" class="itk-lookup">[lookup]</a>
</td>
<td><?= number_format($row->cnt) ?></td>
</tr>
<?php endforeach; ?>
</table>
</div>
<?php endif; ?>
</section>
<!-- ── Quick Settings Status ── -->
<section class="itk-quick-status">
<div class="itk-chart-title">ACTIVE MODULES</div>
<?php
$sec = get_option('itk_security', []);
$opt = get_option('itk_optimization', []);
$hp = get_option('itk_honeypot', []);
$modules = [
'Bot Blocker' => !empty($sec['block_malicious_bots']),
'OpenAI Block' => !empty($sec['block_openai_bots']),
'Good Bot Rate Limit'=> !empty($sec['rate_limit_good_bots']),
'Network Block' => !empty($sec['block_bad_networks']),
'Login Protection' => !empty($sec['protect_wp_login']),
'Security Headers' => !empty($sec['add_security_headers']),
'Block XML-RPC' => !empty($sec['block_xmlrpc']),
'Honeypot' => !empty($hp['enabled']),
'Remove WP Version' => !empty($opt['remove_wp_version']),
'Disable Emoji' => !empty($opt['remove_emoji']),
'Admin Branding' => !empty($opt['admin_branding']),
];
foreach ($modules as $label => $active):
?>
<div class="itk-module-row">
<span class="itk-module-dot <?= $active ? 'itk-dot-on' : 'itk-dot-off' ?>"></span>
<span class="itk-module-label"><?= esc_html($label) ?></span>
<span class="itk-module-status"><?= $active ? 'ACTIVE' : 'off' ?></span>
</div>
<?php endforeach; ?>
</section>
</div>
<?php
}
/* ══════════════════════════════════════════════════════════
* TAB: BOT BLOCKER
* ══════════════════════════════════════════════════════════ */
private function tab_bot_blocker(): void {
$opts = get_option('itk_security', []);
?>
<div class="itk-settings-grid">
<section class="itk-card">
<h2>Bot Blocking</h2>
<?php
$toggles = [
'block_openai_bots' => ['OpenAI / GPT Bots', 'Block GPTBot, ChatGPT-User, OAI-SearchBot'],
'block_malicious_bots' => ['Malicious Bots', 'Block bots listed in badbots.conf'],
'block_bad_referrers' => ['Bad Referrers', 'Block requests from spam referrer domains'],
'block_bad_networks' => ['Bad Networks', 'Block IP ranges listed in networks.conf'],
'rate_limit_good_bots' => ['Rate-Limit Good Bots', 'Apply crawl-rate limits to Googlebot, Bingbot, etc. (configurable in goodbots.conf)'],
];
foreach ($toggles as $key => [$label, $desc]):
$this->render_toggle('itk_security', $key, $label, $desc, $opts);
endforeach;
?>
</section>
<section class="itk-card">
<h2>Response Settings</h2>
<form method="post" action="options-general.php?page=<?= self::MENU_SLUG ?>&tab=bot-blocker">
<?php wp_nonce_field(self::NONCE_ACTION); ?>
<input type="hidden" name="itk_action" value="save_settings_security">
<table class="form-table">
<tr>
<th>Response Code</th>
<td>
<select name="itk_security[response_code]">
<?php
$codes = ['301_custom' => '301 Redirect to custom URL', '403' => '403 Forbidden', '410' => '410 Gone', '503' => '503 Service Unavailable'];
$cur = $opts['response_code'] ?? '301_custom';
foreach ($codes as $val => $lbl):
?>
<option value="<?= esc_attr($val) ?>" <?= selected($cur, $val, false) ?>><?= esc_html($lbl) ?></option>
<?php endforeach; ?>
</select>
</td>
</tr>
<tr>
<th>Redirect URL</th>
<td><input type="url" name="itk_security[redirect_url]" value="<?= esc_attr($opts['redirect_url'] ?? '') ?>" class="regular-text"></td>
</tr>
<tr>
<th>Custom Message</th>
<td><input type="text" name="itk_security[custom_message]" value="<?= esc_attr($opts['custom_message'] ?? 'Access denied.') ?>" class="regular-text"></td>
</tr>
<tr>
<th>Log Blocked Attempts</th>
<td>
<label>
<input type="checkbox" name="itk_security[log_blocked_attempts]" value="1" <?= checked(!empty($opts['log_blocked_attempts'])) ?>>
Log all blocked attempts to the database
</label>
</td>
</tr>
</table>
<?php submit_button('Save Response Settings'); ?>
</form>
</section>
</div>
<?php
}
/* ══════════════════════════════════════════════════════════
* TAB: PROTECTION
* ══════════════════════════════════════════════════════════ */
private function tab_protection(): void {
$opts = get_option('itk_security', []);
?>
<div class="itk-settings-grid">
<section class="itk-card">
<h2>WordPress Protection</h2>
<?php
$toggles = [
'protect_wp_login' => ['WP Login IP Whitelist', 'Restrict wp-login.php to IPs in allowed-ips.conf'],
'add_security_headers' => ['Security Headers', 'Add X-Content-Type-Options, X-Frame-Options, X-XSS-Protection headers'],
'protect_wp_includes' => ['Protect WP Core Files', 'Block direct access to wp-includes, wp-admin/includes'],
'protect_uploads' => ['Block PHP in Uploads', 'Deny PHP file access and uploads in /wp-content/uploads/'],
'block_xmlrpc' => ['Block XML-RPC', 'Deny all access to xmlrpc.php'],
'block_malicious_queries' => ['Block Malicious Queries', 'Detect and block SQLi, XSS, and command injection in query strings'],
'block_author_scans' => ['Block Author Scans', 'Redirect ?author=N requests to prevent username enumeration'],
];
foreach ($toggles as $key => [$label, $desc]):
$this->render_toggle('itk_security', $key, $label, $desc, $opts);
endforeach;
?>
</section>
<section class="itk-card">
<h2>Custom Login URL</h2>
<form method="post" action="options-general.php?page=<?= self::MENU_SLUG ?>&tab=protection">
<?php wp_nonce_field(self::NONCE_ACTION); ?>
<input type="hidden" name="itk_action" value="save_settings_login">
<?php $this->render_toggle('itk_security', 'enable_custom_login', 'Enable Custom Login URL', 'Replace /wp-login.php with a custom slug', $opts); ?>
<table class="form-table">
<tr>
<th>Login Slug</th>
<td>
<code><?= esc_html(home_url('/')) ?></code>
<input type="text" name="itk_security[custom_login_slug]" value="<?= esc_attr($opts['custom_login_slug'] ?? 'thoushallpass') ?>" style="width:200px">
<p class="description">Characters: letters, numbers, dashes only.</p>
</td>
</tr>
</table>
<?php submit_button('Save Login Settings'); ?>
</form>
</section>
</div>
<?php
}
/* ══════════════════════════════════════════════════════════
* TAB: OPTIMIZATION
* ══════════════════════════════════════════════════════════ */
private function tab_optimization(): void {
$opts = get_option('itk_optimization', []);
?>
<div class="itk-settings-grid">
<section class="itk-card">
<h2>Performance &amp; Cleanup</h2>
<?php
$toggles = [
'remove_wp_version' => ['Remove WP Version', 'Strip WordPress version from <head> and all enqueued assets'],
'remove_script_versions' => ['Remove Asset Versions', 'Remove ?ver= query string from CSS/JS URLs'],
'remove_emoji' => ['Remove Emojis', 'Disable WordPress emoji scripts and styles'],
'deregister_wp_embed' => ['Remove WP Embed', 'Deregister the wp-embed script'],
'remove_wp_head_noise' => ['Clean WP Head', 'Remove RSD, wlwmanifest, feed links, and adjacent post links from <head>'],
'limit_revisions' => ['Limit Revisions', 'Keep only 3 post revisions and autosave every 5 minutes'],
'defer_js' => ['Defer JavaScript', 'Add defer attribute to non-critical scripts'],
'limit_heartbeat' => ['Limit Heartbeat', 'Restrict WordPress heartbeat to post editor pages only'],
'stop_empty_search_redirect'=> ['Fix Empty Search', 'Prevent redirect loop on empty search queries'],
'use_google_jquery' => ['Use Google jQuery', 'Load jQuery from Google CDN instead of local'],
'dns_prefetch' => ['DNS Prefetch', 'Enable DNS prefetching via meta header'],
];
foreach ($toggles as $key => [$label, $desc]):
$this->render_toggle('itk_optimization', $key, $label, $desc, $opts);
endforeach;
?>
</section>
<section class="itk-card">
<h2>Security Tweaks</h2>
<?php
$toggles = [
'hide_login_errors' => ['Hide Login Errors', 'Replace specific login error messages with a generic one'],
'remove_author_class' => ['Remove Author Class', 'Strip admin username from comment CSS classes'],
'remove_default_userfields'=> ['Remove User Fields', 'Remove AIM, Jabber, YIM from user profiles'],
'clean_bad_content' => ['Clean Bad Content', 'Remove empty tags, inline styles, and font tags on save'],
'change_author_base' => ['Change Author URL Base', "Change /author/ to /writer/ in author archive URLs"],
'disable_xml_rpc' => ['Disable XML-RPC (via filter)', 'Filter-based XML-RPC disable (additional to the blocker)'],
];
foreach ($toggles as $key => [$label, $desc]):
$this->render_toggle('itk_optimization', $key, $label, $desc, $opts);
endforeach;
?>
</section>
<section class="itk-card">
<h2>UI / Branding</h2>
<?php
$toggles = [
'disable_dashboard_widgets' => ['Disable Core Widgets', 'Remove WordPress News and WPEngine news dashboard widgets'],
'unregister_default_widgets'=> ['Unregister Sidebar Widgets', 'Remove Calendar, Archives, Meta, Search, Tag Cloud widgets'],
'disable_comments_url' => ['Remove Comment URL Field', 'Hide the website URL field from comment forms'],
'remove_admin_bar_links' => ['Clean Admin Bar', 'Remove WordPress logo, links, and noisy items from the toolbar'],
'admin_branding' => ['InformatiQ Branding', 'Add InformatiQ logo widget, toolbar link, admin notice, and custom footer'],
'disable_floc' => ['Disable FLoC', 'Add Permissions-Policy: interest-cohort=() header'],
'lightbox_images' => ['Lightbox Images', 'Add rel="lightbox" to image links in post content'],
'featured_image_rss' => ['Featured Image in RSS', 'Include featured image in RSS feed entries'],
];
foreach ($toggles as $key => [$label, $desc]):
$this->render_toggle('itk_optimization', $key, $label, $desc, $opts);
endforeach;
?>
</section>
</div>
<?php
}
/* ══════════════════════════════════════════════════════════
* TAB: HONEYPOT
* ══════════════════════════════════════════════════════════ */
private function tab_honeypot(): void {
$opts = get_option('itk_honeypot', []);
?>
<div class="itk-settings-grid">
<section class="itk-card">
<h2>Honeypot Fields</h2>
<?php
$toggles = [
'enabled' => ['Enable Honeypot', 'Inject invisible honeypot fields into all protected forms'],
'protect_comments' => ['Comments', 'Protect comment forms'],
'protect_login' => ['Login Form', 'Protect wp-login.php login'],
'protect_register' => ['Registration', 'Protect user registration'],
'protect_lost_password' => ['Lost Password', 'Protect lost password form'],
'protect_woocommerce' => ['WooCommerce', 'Protect WooCommerce checkout and registration'],
'protect_cf7' => ['Contact Form 7', 'Protect CF7 forms'],
'protect_elementor' => ['Elementor Forms', 'Protect Elementor Pro form widget'],
'protect_gravity' => ['Gravity Forms', 'Protect Gravity Forms'],
'protect_search' => ['Search Form', 'Protect the WordPress search form'],
];
foreach ($toggles as $key => [$label, $desc]):
$this->render_toggle('itk_honeypot', $key, $label, $desc, $opts);
endforeach;
?>
</section>
<section class="itk-card">
<h2>Timing Rules</h2>
<form method="post" action="options-general.php?page=<?= self::MENU_SLUG ?>&tab=honeypot">
<?php wp_nonce_field(self::NONCE_ACTION); ?>
<input type="hidden" name="itk_action" value="save_settings_honeypot">
<table class="form-table">
<tr>
<th>Minimum Submit Time (seconds)</th>
<td>
<input type="number" name="itk_honeypot[min_time]" value="<?= (int)($opts['min_time'] ?? 3) ?>" min="1" max="60">
<p class="description">Block submissions faster than this (bots submit instantly).</p>
</td>
</tr>
<tr>
<th>Maximum Submit Time (seconds)</th>
<td>
<input type="number" name="itk_honeypot[max_time]" value="<?= (int)($opts['max_time'] ?? 7200) ?>" min="60">
<p class="description">Block submissions older than this (stale/replayed forms).</p>
</td>
</tr>
<tr>
<th>Retain Logs (days)</th>
<td>
<input type="number" name="itk_honeypot[retain_days]" value="<?= (int)($opts['retain_days'] ?? 90) ?>" min="1">
<p class="description">Automatically prune logs older than this many days.</p>
</td>
</tr>
</table>
<?php submit_button('Save Honeypot Settings'); ?>
</form>
</section>
</div>
<?php
}
/* ══════════════════════════════════════════════════════════
* TAB: BOT LOGS
* ══════════════════════════════════════════════════════════ */
private function tab_bot_logs(): void {
$search = sanitize_text_field($_GET['hp_search'] ?? '');
$filter_ip= sanitize_text_field($_GET['hp_ip'] ?? '');
$filter_bt= sanitize_text_field($_GET['hp_bot'] ?? '');
$filter_ac= sanitize_key($_GET['hp_action'] ?? '');
$paged = max(1, (int)($_GET['paged'] ?? 1));
$offset = ($paged - 1) * self::PER_PAGE;
$args = ['per_page' => self::PER_PAGE, 'offset' => $offset];
if ($search) $args['search'] = $search;
if ($filter_ip) $args['ip'] = $filter_ip;
if ($filter_bt) $args['bot_type'] = $filter_bt;
if ($filter_ac) $args['action'] = $filter_ac;
$rows = ITK_Database::get_bot_rows($args);
$total = ITK_Database::count_bot_rows($args);
$bot_types = ITK_Database::get_bot_types();
$total_pages= max(1, (int)ceil($total / self::PER_PAGE));
$base_url = admin_url('options-general.php?page=' . self::MENU_SLUG . '&tab=bot-logs');
?>
<div class="itk-log-page">
<!-- Filters -->
<form method="get" class="itk-filters">
<input type="hidden" name="page" value="<?= self::MENU_SLUG ?>">
<input type="hidden" name="tab" value="bot-logs">
<input type="text" name="hp_search" placeholder="Search IP, UA, reason…" value="<?= esc_attr($search) ?>">
<input type="text" name="hp_ip" placeholder="Filter by IP" value="<?= esc_attr($filter_ip) ?>">
<select name="hp_bot">
<option value="">All bot types</option>
<?php foreach ($bot_types as $bt): ?>
<option value="<?= esc_attr($bt) ?>" <?= selected($filter_bt, $bt, false) ?>><?= esc_html($bt) ?></option>
<?php endforeach; ?>
</select>
<select name="hp_action">
<option value="">All actions</option>
<option value="blocked" <?= selected($filter_ac, 'blocked', false) ?>>Blocked</option>
<option value="rate_limited" <?= selected($filter_ac, 'rate_limited', false) ?>>Rate Limited</option>
</select>
<input type="submit" class="button" value="Filter">
<a href="<?= esc_url($base_url) ?>" class="button">Reset</a>
</form>
<p class="itk-count">Showing <?= count($rows) ?> of <?= number_format($total) ?> result(s)</p>
<table class="itk-log-table widefat striped">
<thead>
<tr>
<th>Date / Time</th><th>IP</th><th>Bot Type</th>
<th>Action</th><th>Reason</th><th>URI</th><th>User Agent</th>
</tr>
</thead>
<tbody>
<?php if (empty($rows)): ?>
<tr><td colspan="7" class="itk-no-results">No bot activity logged yet.</td></tr>
<?php else: ?>
<?php foreach ($rows as $row):
$action_class = $row->action === 'rate_limited' ? 'itk-badge-warn' : 'itk-badge-block';
?>
<tr>
<td class="itk-nowrap"><?= esc_html($row->logged_at) ?></td>
<td>
<?= esc_html($row->ip_address) ?>
<a href="<?= esc_url($base_url . '&hp_ip=' . urlencode($row->ip_address)) ?>" class="itk-filter-link">[filter]</a>
<a href="https://ipinfo.io/<?= urlencode($row->ip_address) ?>" target="_blank" class="itk-filter-link">[lookup]</a>
</td>
<td><?= esc_html($row->bot_type) ?></td>
<td><span class="itk-badge <?= $action_class ?>"><?= esc_html($row->action) ?></span></td>
<td><?= esc_html($row->reason) ?></td>
<td class="itk-uri"><?= esc_html(substr($row->request_uri, 0, 80)) ?></td>
<td class="itk-ua"><?= esc_html(substr($row->user_agent, 0, 100)) ?></td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
<?php $this->render_pager($paged, $total_pages, $base_url); ?>
<!-- Clear logs -->
<form method="post" style="margin-top:16px" onsubmit="return confirm('Delete ALL bot log entries?')">
<?php wp_nonce_field(self::NONCE_ACTION); ?>
<input type="hidden" name="itk_action" value="clear_bot_log">
<input type="submit" class="button button-secondary itk-btn-danger" value="Clear All Bot Logs">
</form>
</div>
<?php
}
/* ══════════════════════════════════════════════════════════
* TAB: HONEYPOT LOGS
* ══════════════════════════════════════════════════════════ */
private function tab_honeypot_logs(): void {
$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, (int)($_GET['paged'] ?? 1));
$offset = ($paged - 1) * self::PER_PAGE;
$args = ['per_page' => self::PER_PAGE, 'offset' => $offset];
if ($search) $args['search'] = $search;
if ($filter_ip) $args['ip'] = $filter_ip;
if ($filter_form) $args['form'] = $filter_form;
$rows = ITK_Database::get_honeypot_rows($args);
$total = ITK_Database::count_honeypot_rows($args);
$form_types = ITK_Database::get_honeypot_form_types();
$total_pages = max(1, (int)ceil($total / self::PER_PAGE));
$base_url = admin_url('options-general.php?page=' . self::MENU_SLUG . '&tab=honeypot-logs');
?>
<div class="itk-log-page">
<form method="get" class="itk-filters">
<input type="hidden" name="page" value="<?= self::MENU_SLUG ?>">
<input type="hidden" name="tab" value="honeypot-logs">
<input type="text" name="hp_search" placeholder="Search IP, UA, reason…" value="<?= esc_attr($search) ?>">
<input type="text" name="hp_ip" placeholder="Filter by IP" value="<?= esc_attr($filter_ip) ?>">
<select name="hp_form">
<option value="">All form types</option>
<?php foreach ($form_types as $ft): ?>
<option value="<?= esc_attr($ft) ?>" <?= selected($filter_form, $ft, false) ?>><?= esc_html($ft) ?></option>
<?php endforeach; ?>
</select>
<input type="submit" class="button" value="Filter">
<a href="<?= esc_url($base_url) ?>" class="button">Reset</a>
</form>
<p class="itk-count">Showing <?= count($rows) ?> of <?= number_format($total) ?> result(s)</p>
<table class="itk-log-table widefat striped">
<thead>
<tr>
<th>Date / Time</th><th>IP</th><th>Form Type</th>
<th>Reason</th><th>URI</th><th>User Agent</th>
</tr>
</thead>
<tbody>
<?php if (empty($rows)): ?>
<tr><td colspan="6" class="itk-no-results">No honeypot catches yet.</td></tr>
<?php else: ?>
<?php foreach ($rows as $row): ?>
<tr>
<td class="itk-nowrap"><?= esc_html($row->blocked_at) ?></td>
<td>
<?= esc_html($row->ip_address) ?>
<a href="<?= esc_url($base_url . '&hp_ip=' . urlencode($row->ip_address)) ?>" class="itk-filter-link">[filter]</a>
<a href="https://ipinfo.io/<?= urlencode($row->ip_address) ?>" target="_blank" class="itk-filter-link">[lookup]</a>
</td>
<td><span class="itk-badge itk-badge-hp"><?= esc_html($row->form_type) ?></span></td>
<td><?= esc_html($row->reason) ?></td>
<td class="itk-uri"><?= esc_html(substr($row->request_uri, 0, 80)) ?></td>
<td class="itk-ua"><?= esc_html(substr($row->user_agent, 0, 100)) ?></td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
<?php $this->render_pager($paged, $total_pages, $base_url); ?>
<form method="post" style="margin-top:16px" onsubmit="return confirm('Delete ALL honeypot log entries?')">
<?php wp_nonce_field(self::NONCE_ACTION); ?>
<input type="hidden" name="itk_action" value="clear_honeypot_log">
<input type="submit" class="button button-secondary itk-btn-danger" value="Clear All Honeypot Logs">
</form>
</div>
<?php
}
/* ══════════════════════════════════════════════════════════
* TAB: CONFIG FILES
* ══════════════════════════════════════════════════════════ */
private function tab_config_files(): void {
$files = [
'badbots' => ['Bad Bots', 'config/badbots.conf', 'One bot user-agent substring per line. Lines starting with # are comments.'],
'goodbots' => ['Good Bots', 'config/goodbots.conf', 'Format: BotName|rate_per_minute (0 = always block)'],
'referrers' => ['Bad Referrers', 'config/referrers.conf', 'One domain substring per line.'],
'networks' => ['Bad Networks', 'config/networks.conf', 'One IP or CIDR range per line (e.g. 1.2.3.0/24).'],
'allowed-ips' => ['Allowed IPs', 'config/allowed-ips.conf','IPs/CIDRs allowed to access wp-login.php (one per line).'],
];
$active_file = sanitize_key($_GET['file'] ?? 'badbots');
if (!isset($files[$active_file])) $active_file = 'badbots';
[$title, $path, $desc] = $files[$active_file];
$full_path = ITK_PATH . $path;
$content = file_exists($full_path) ? file_get_contents($full_path) : '';
?>
<div class="itk-config-editor">
<div class="itk-config-tabs">
<?php foreach ($files as $slug => [$label]): ?>
<a href="<?= esc_url(admin_url('options-general.php?page=' . self::MENU_SLUG . '&tab=config-files&file=' . $slug)) ?>"
class="itk-config-tab <?= $slug === $active_file ? 'active' : '' ?>"><?= esc_html($label) ?></a>
<?php endforeach; ?>
</div>
<div class="itk-config-editor-area">
<h3><?= esc_html($title) ?> <code><?= esc_html($path) ?></code></h3>
<p class="description"><?= esc_html($desc) ?></p>
<textarea id="itk-config-content" rows="25" class="itk-config-textarea"><?= esc_textarea($content) ?></textarea>
<p>
<button id="itk-save-config" class="button button-primary" data-file="<?= esc_attr($active_file) ?>">Save File</button>
<span id="itk-config-status" style="margin-left:10px;color:green;display:none">Saved!</span>
</p>
</div>
</div>
<?php
}
/* ── Shared helpers ───────────────────────────────────────── */
private function render_toggle(string $option, string $key, string $label, string $desc, array $opts): void {
$checked = !empty($opts[$key]);
?>
<div class="itk-toggle-row">
<div class="itk-toggle-info">
<span class="itk-toggle-label"><?= esc_html($label) ?></span>
<span class="itk-toggle-desc"><?= esc_html($desc) ?></span>
</div>
<label class="itk-switch">
<input type="checkbox"
class="itk-toggle-input"
data-option="<?= esc_attr($option) ?>"
data-setting="<?= esc_attr($key) ?>"
<?= $checked ? 'checked' : '' ?>>
<span class="itk-slider"></span>
</label>
</div>
<?php
}
private function render_pager(int $paged, int $total_pages, string $base_url): void {
if ($total_pages <= 1) return;
echo '<div class="itk-pager">';
if ($paged > 1) {
echo '<a href="' . esc_url(add_query_arg('paged', $paged - 1, $base_url)) . '" class="button">&laquo; Prev</a>';
}
echo '<span>' . sprintf('Page %d of %d', $paged, $total_pages) . '</span>';
if ($paged < $total_pages) {
echo '<a href="' . esc_url(add_query_arg('paged', $paged + 1, $base_url)) . '" class="button">Next &raquo;</a>';
}
echo '</div>';
}
}

View File

@@ -0,0 +1,305 @@
<?php
if (!defined('ABSPATH')) exit;
/**
* ITK Bot Blocker
*
* Handles detection and blocking of malicious bots, bad referrers, and bad
* networks. Good/legitimate bots are rate-limited instead of blocked.
*
* Deactivation bug fix: every check method reads options at call time so
* toggling a setting via AJAX takes effect immediately without any hook
* re-registration.
*/
class ITK_Bot_Blocker {
private string $badbots_file;
private string $referrers_file;
private string $networks_file;
private string $goodbots_file;
public function __construct() {
$this->badbots_file = ITK_PATH . 'config/badbots.conf';
$this->referrers_file = ITK_PATH . 'config/referrers.conf';
$this->networks_file = ITK_PATH . 'config/networks.conf';
$this->goodbots_file = ITK_PATH . 'config/goodbots.conf';
// Always hook; each method guards itself with its own option check.
add_action('init', [$this, 'check_request'], 1);
add_filter('robots_txt', [$this, 'modify_robots_txt'], 10, 2);
}
/* ── Main entry point ─────────────────────────────────────── */
public function check_request(): void {
// Never block logged-in admins.
if (is_admin() || (function_exists('current_user_can') && current_user_can('manage_options'))) {
return;
}
$options = get_option('itk_security', []);
$ua = $_SERVER['HTTP_USER_AGENT'] ?? '';
$referrer = $_SERVER['HTTP_REFERER'] ?? '';
$ip = $this->get_client_ip();
$uri = $_SERVER['REQUEST_URI'] ?? '';
// ── 1. Rate-limit good/legitimate bots ─────────────────
if (!empty($options['rate_limit_good_bots'])) {
$good_bot = $this->identify_good_bot($ua);
if ($good_bot !== null) {
$this->handle_good_bot($good_bot, $ua, $ip, $uri);
return; // Handled don't fall through to block checks.
}
}
// ── 2. Block OpenAI bots ───────────────────────────────
if (!empty($options['block_openai_bots']) && $this->is_openai_bot($ua)) {
$this->block('OpenAI bot detected', 'openai', $ua, $referrer, $ip, $uri, $options);
}
// ── 3. Block malicious bots ────────────────────────────
if (!empty($options['block_malicious_bots']) && $this->is_malicious_bot($ua)) {
$this->block('Malicious bot detected', 'malicious_bot', $ua, $referrer, $ip, $uri, $options);
}
// ── 4. Block bad referrers ─────────────────────────────
if (!empty($options['block_bad_referrers']) && $this->is_bad_referrer($referrer)) {
$this->block('Bad referrer detected', 'bad_referrer', $ua, $referrer, $ip, $uri, $options);
}
// ── 5. Block bad networks ──────────────────────────────
if (!empty($options['block_bad_networks']) && $this->is_bad_network($ip)) {
$this->block('IP in blocked network', 'bad_network', $ua, $referrer, $ip, $uri, $options);
}
}
/* ── Good-bot rate limiting ───────────────────────────────── */
/**
* Returns ['name' => string, 'limit' => int] or null if not a known good bot.
* A limit of 0 means "never allow" (treat as blocked).
*/
private function identify_good_bot(string $ua): ?array {
if (empty($ua)) return null;
$cache_key = 'itk_goodbots_list';
$list = get_transient($cache_key);
if ($list === false) {
$list = [];
if (file_exists($this->goodbots_file)) {
foreach (file($this->goodbots_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) as $line) {
$line = trim($line);
if ($line === '' || $line[0] === '#') continue;
$parts = explode('|', $line, 2);
$list[] = ['name' => trim($parts[0]), 'limit' => isset($parts[1]) ? (int)$parts[1] : 30];
}
}
set_transient($cache_key, $list, 300);
}
foreach ($list as $entry) {
if (stripos($ua, $entry['name']) !== false) {
return $entry;
}
}
return null;
}
private function handle_good_bot(array $bot, string $ua, string $ip, string $uri): void {
$options = get_option('itk_security', []);
$name = $bot['name'];
$limit = (int)$bot['limit'];
// Limit of 0 = always block this "good" bot (e.g. GPTBot still in goodbots.conf)
if ($limit === 0) {
$this->block("Good bot with limit 0: {$name}", $name, $ua, '', $ip, $uri, $options);
return;
}
// Sliding window: track hits per bot per minute using transients.
$window = (int)(time() / 60); // 1-minute window
$tk_key = 'itk_rl_' . md5($name) . '_' . $window;
$count = (int)get_transient($tk_key);
if ($count >= $limit) {
// Over the limit log and send 429.
if (!empty($options['log_blocked_attempts'])) {
ITK_Database::log_bot([
'ip' => $ip,
'ua' => $ua,
'referrer' => '',
'uri' => $uri,
'bot_type' => $name,
'reason' => "Rate limited: {$count}/{$limit} req/min",
'action' => 'rate_limited',
]);
}
status_header(429);
header('Retry-After: 60');
header('X-ITK-Rate-Limit: ' . $limit);
echo 'Too Many Requests. Crawl-delay: 60';
exit;
}
// Under the limit increment counter and allow through.
set_transient($tk_key, $count + 1, 120);
}
/* ── Blocking ─────────────────────────────────────────────── */
private function block(
string $reason,
string $bot_type,
string $ua,
string $referrer,
string $ip,
string $uri,
array $options
): void {
if (!empty($options['log_blocked_attempts'])) {
ITK_Database::log_bot([
'ip' => $ip,
'ua' => $ua,
'referrer' => $referrer,
'uri' => $uri,
'bot_type' => $bot_type,
'reason' => $reason,
'action' => 'blocked',
]);
}
$code = $options['response_code'] ?? '403';
$message = $options['custom_message'] ?? 'Access denied.';
$redir = $options['redirect_url'] ?? '';
if ($code === '301_custom' && !empty($redir)) {
header('Location: ' . esc_url_raw($redir), true, 301);
} else {
status_header((int)$code ?: 403);
echo esc_html($message);
}
exit;
}
/* ── Detection helpers ────────────────────────────────────── */
private function is_openai_bot(string $ua): bool {
if (empty($ua)) return false;
foreach (['GPTBot', 'ChatGPT-User', 'OAI-SearchBot', 'whisper'] as $b) {
if (stripos($ua, $b) !== false) return true;
}
return false;
}
private function is_malicious_bot(string $ua): bool {
if (empty($ua)) return false;
foreach ($this->load_conf_list($this->badbots_file, 'itk_bots_list') as $bot) {
if (stripos($ua, $bot) !== false) return true;
}
return false;
}
private function is_bad_referrer(string $referrer): bool {
if (empty($referrer)) return false;
foreach ($this->load_conf_list($this->referrers_file, 'itk_referrers_list') as $ref) {
if (stripos($referrer, $ref) !== false) return true;
}
return false;
}
private function is_bad_network(string $ip): bool {
if (empty($ip) || $ip === 'UNKNOWN') return false;
foreach ($this->load_conf_list($this->networks_file, 'itk_networks_list') as $network) {
if (filter_var($network, FILTER_VALIDATE_IP)) {
if ($ip === $network) return true;
} elseif (strpos($network, '/') !== false) {
if ($this->ip_in_cidr($ip, $network)) return true;
}
}
return false;
}
/* ── Robots.txt ───────────────────────────────────────────── */
public function modify_robots_txt(string $output, string $public): string {
if ($public === '0') return $output;
$options = get_option('itk_security', []);
if (empty($options['block_openai_bots'])) return $output;
$output .= "\n# InformatiQ Toolkit AI bot disallow\n";
foreach (['GPTBot', 'ChatGPT-User', 'OAI-SearchBot'] as $bot) {
$output .= "User-agent: {$bot}\nDisallow: /\n\n";
}
return $output;
}
/* ── Config file readers ──────────────────────────────────── */
private function load_conf_list(string $file, string $cache_key): array {
$cached = get_transient($cache_key);
if ($cached !== false) return $cached;
if (!file_exists($file) || filesize($file) > 1048576) return [];
$lines = file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
$list = [];
foreach ($lines as $line) {
$line = trim($line);
if ($line === '' || $line[0] === '#') continue;
if (strlen($line) <= 200 && !preg_match('/[<>"\']/', $line)) {
$list[] = $line;
}
}
set_transient($cache_key, $list, 300);
return $list;
}
public function invalidate_cache(): void {
delete_transient('itk_bots_list');
delete_transient('itk_referrers_list');
delete_transient('itk_networks_list');
delete_transient('itk_goodbots_list');
}
/* ── IP utilities ─────────────────────────────────────────── */
public function get_client_ip(): string {
$keys = [
'HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED',
'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED',
'REMOTE_ADDR',
];
foreach ($keys as $key) {
if (empty($_SERVER[$key])) continue;
$ip = trim(explode(',', $_SERVER[$key])[0]);
if ($key !== 'REMOTE_ADDR' && filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
return $ip;
}
if ($key === 'REMOTE_ADDR' && filter_var($ip, FILTER_VALIDATE_IP)) {
return $ip;
}
}
return 'UNKNOWN';
}
private function ip_in_cidr(string $ip, string $cidr): bool {
if (strpos($cidr, '/') === false) return false;
[$subnet, $mask] = explode('/', $cidr, 2);
if (!filter_var($subnet, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) return false;
if (!is_numeric($mask) || $mask < 0 || $mask > 32) return false;
if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) return false;
$ip_long = ip2long($ip);
$sub_long = ip2long($subnet);
$mask_dec = ~((1 << (32 - (int)$mask)) - 1);
return ($ip_long & $mask_dec) === ($sub_long & $mask_dec);
}
/* ── Accessors for admin ──────────────────────────────────── */
public function get_badbots_file(): string { return $this->badbots_file; }
public function get_referrers_file(): string { return $this->referrers_file; }
public function get_networks_file(): string { return $this->networks_file; }
public function get_goodbots_file(): string { return $this->goodbots_file; }
}

View File

@@ -0,0 +1,283 @@
<?php
if (!defined('ABSPATH')) exit;
/**
* Database helper for InformatiQ Toolkit.
* Manages two log tables: bot_log and honeypot_log.
*/
class ITK_Database {
const DB_VERSION = 1;
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';
}
/* ── 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
));
}
}

View File

@@ -0,0 +1,277 @@
<?php
if (!defined('ABSPATH')) exit;
/**
* ITK Honeypot
*
* Ported from HoneypotFields v2.4.0.
* Injects invisible honeypot fields into all major form types and blocks
* submissions that fill them (bots) or arrive too fast/too slow.
* Uses ITK_Database for logging instead of a separate table.
*/
class ITK_Honeypot {
const FIELD_PREFIX = '_hp_trap_';
const TOKEN_FIELD = '_hp_token';
const TIME_FIELD = '_hp_ts';
private static array $opts = [];
public function __construct() {
self::$opts = get_option('itk_honeypot', []);
if (empty(self::$opts['enabled'])) return;
// Inject honeypot into forms
add_action('comment_form', [$this, 'inject_comment']);
add_action('login_form', [$this, 'inject_generic']);
add_action('register_form', [$this, 'inject_generic']);
add_action('lostpassword_form', [$this, 'inject_generic']);
add_action('wp_head', [$this, 'inject_honeypot_style']);
// Validate on submission
add_filter('preprocess_comment', [$this, 'validate_comment'], 1);
add_action('authenticate', [$this, 'validate_login'], 1, 3);
add_action('register_post', [$this, 'validate_register'], 1, 3);
add_action('lostpassword_post', [$this, 'validate_lost_password'], 1);
// WooCommerce
if (!empty(self::$opts['protect_woocommerce']) && class_exists('WooCommerce')) {
add_action('woocommerce_checkout_before_customer_details', [$this, 'inject_generic']);
add_action('woocommerce_checkout_process', [$this, 'validate_woo_checkout']);
add_action('woocommerce_register_form', [$this, 'inject_generic']);
add_action('woocommerce_process_registration_errors', [$this, 'validate_woo_registration'], 10, 3);
add_action('woocommerce_login_form', [$this, 'inject_generic']);
}
// Contact Form 7
if (!empty(self::$opts['protect_cf7']) && class_exists('WPCF7')) {
add_action('wpcf7_form_elements', [$this, 'inject_cf7']);
add_filter('wpcf7_before_send_mail', [$this, 'validate_cf7'], 10, 3);
}
// Elementor forms
if (!empty(self::$opts['protect_elementor']) && defined('ELEMENTOR_VERSION')) {
add_action('elementor/frontend/after_enqueue_scripts', [$this, 'elementor_enqueue']);
add_action('elementor_pro/forms/validation', [$this, 'validate_elementor'], 10, 2);
}
// Gravity Forms
if (!empty(self::$opts['protect_gravity']) && class_exists('GFForms')) {
add_filter('gform_form_tag', [$this, 'inject_gravity'], 10, 2);
add_filter('gform_validation', [$this, 'validate_gravity']);
}
// Search form
if (!empty(self::$opts['protect_search'])) {
add_action('get_search_form', [$this, 'inject_search']);
}
// Enqueue JS token generator
add_action('wp_enqueue_scripts', [$this, 'enqueue_token_script']);
}
/* ── Style (hide honeypot fields) ─────────────────────────── */
public function inject_honeypot_style(): void {
echo '<style>.itk-hp-field{display:none!important;visibility:hidden!important;opacity:0!important;position:absolute!important;left:-9999px!important;top:-9999px!important;}</style>' . "\n";
}
/* ── JS token (HMAC-based anti-CSRF) ─────────────────────── */
public function enqueue_token_script(): void {
$secret = $this->get_page_secret();
wp_add_inline_script('jquery-core', "
(function(){
var s='" . esc_js($secret) . "',n='" . esc_js(wp_create_nonce('itk_hp')) . "';
document.querySelectorAll('." . self::TOKEN_FIELD . "').forEach(function(f){
f.value=btoa(s+'|'+Date.now()+'|'+n);
});
document.querySelectorAll('." . self::TIME_FIELD . "').forEach(function(f){
f.value=Math.floor(Date.now()/1000);
});
})();
", 'after');
}
/* ── Field HTML generator ─────────────────────────────────── */
private function honeypot_html(string $form_type = ''): string {
$field = self::FIELD_PREFIX . substr(md5(uniqid()), 0, 8);
$ts = time();
$label = ['Your email address', 'Website URL', 'Full name'][array_rand(['a','b','c'])];
return sprintf(
'<div class="itk-hp-field" aria-hidden="true" tabindex="-1" style="display:none!important">
<label>%s <input type="text" name="%s" value="" autocomplete="off" tabindex="-1"></label>
<input type="hidden" name="%s" class="%s" value="">
<input type="hidden" name="%s" class="%s" value="%d">
</div>',
esc_html($label),
esc_attr($field),
self::TOKEN_FIELD, self::TOKEN_FIELD,
self::TIME_FIELD, self::TIME_FIELD,
$ts
);
}
/* ── Injectors ────────────────────────────────────────────── */
public function inject_generic(): void {
if (empty(self::$opts['enabled'])) return;
echo $this->honeypot_html(); // phpcs:ignore
}
public function inject_comment(): void {
if (empty(self::$opts['protect_comments'])) return;
echo $this->honeypot_html('comment'); // phpcs:ignore
}
public function inject_search(string $form): string {
return $form . $this->honeypot_html('search');
}
public function inject_cf7(string $content): string {
return $content . $this->honeypot_html('cf7');
}
public function inject_gravity(string $tag, array $form): string {
return $tag . $this->honeypot_html('gravity');
}
public function elementor_enqueue(): void {
// Elementor injects via JS add hidden fields via wp_footer
add_action('wp_footer', [$this, 'inject_generic']);
}
/* ── Validators ───────────────────────────────────────────── */
private function check_honeypot(string $form_type): bool {
// 1. Honeypot field must be empty
foreach ($_POST as $key => $val) {
if (strpos($key, self::FIELD_PREFIX) === 0 && !empty($val)) {
$this->log_block($form_type, 'Honeypot field filled');
return false;
}
}
// 2. Timing check
$opts = get_option('itk_honeypot', []);
$min_t = max(1, (int)($opts['min_time'] ?? 3));
$max_t = max(60, (int)($opts['max_time'] ?? 7200));
$ts = (int)($_POST[self::TIME_FIELD] ?? 0);
$elapsed = time() - $ts;
if ($ts > 0 && $elapsed < $min_t) {
$this->log_block($form_type, "Submitted too fast ({$elapsed}s)");
return false;
}
if ($ts > 0 && $elapsed > $max_t) {
$this->log_block($form_type, "Submitted too slow ({$elapsed}s > {$max_t}s)");
return false;
}
return true;
}
private function log_block(string $form_type, string $reason): void {
ITK_Database::log_honeypot([
'ip' => $this->get_ip(),
'form' => $form_type,
'reason' => $reason,
'uri' => $_SERVER['REQUEST_URI'] ?? '',
'ua' => $_SERVER['HTTP_USER_AGENT'] ?? '',
]);
}
public function validate_comment(array $comment_data): array {
if (empty(self::$opts['protect_comments'])) return $comment_data;
if (!$this->check_honeypot('comment')) {
wp_die('Spam detected. Please go back and try again.', 'Spam Blocked', ['response' => 403]);
}
return $comment_data;
}
public function validate_login($user, string $username, string $password) {
if (empty(self::$opts['protect_login'])) return $user;
if (!empty($username) && !$this->check_honeypot('login')) {
return new WP_Error('honeypot_blocked', 'Access denied.');
}
return $user;
}
public function validate_register($sanitized_user_login, $user_email, \WP_Error $errors): void {
if (empty(self::$opts['protect_register'])) return;
if (!$this->check_honeypot('register')) {
$errors->add('honeypot_blocked', 'Spam registration detected.');
}
}
public function validate_lost_password(\WP_Error $errors): void {
if (empty(self::$opts['protect_lost_password'])) return;
if (!$this->check_honeypot('lostpassword')) {
$errors->add('honeypot_blocked', 'Access denied.');
}
}
public function validate_woo_checkout(): void {
if (!$this->check_honeypot('woo_checkout')) {
wc_add_notice('Spam submission detected. Please refresh and try again.', 'error');
}
}
public function validate_woo_registration(\WP_Error $errors, $username, $email): \WP_Error {
if (!$this->check_honeypot('woo_register')) {
$errors->add('honeypot_blocked', 'Spam registration detected.');
}
return $errors;
}
public function validate_cf7($contact_form, &$abort, $submission): void {
if (!$this->check_honeypot('cf7')) {
$abort = true;
$contact_form->set_status('spam');
}
}
public function validate_elementor($record, $ajax_handler): void {
if (!$this->check_honeypot('elementor')) {
$ajax_handler->add_error_message('Spam detected. Please try again.');
}
}
public function validate_gravity(array $validation_result): array {
if (!$this->check_honeypot('gravity')) {
$validation_result['is_valid'] = false;
foreach ($validation_result['form']['fields'] as &$field) {
$field->failed_validation = true;
$field->validation_message = 'Spam detected.';
}
}
return $validation_result;
}
/* ── Helpers ──────────────────────────────────────────────── */
private function get_ip(): string {
$keys = [
'HTTP_CLIENT_IP','HTTP_X_FORWARDED_FOR','HTTP_X_FORWARDED',
'HTTP_X_CLUSTER_CLIENT_IP','HTTP_FORWARDED_FOR','HTTP_FORWARDED','REMOTE_ADDR',
];
foreach ($keys as $k) {
if (!empty($_SERVER[$k])) {
$ip = trim(explode(',', $_SERVER[$k])[0]);
if (filter_var($ip, FILTER_VALIDATE_IP)) return $ip;
}
}
return 'UNKNOWN';
}
private function get_page_secret(): string {
$secret = get_option('itk_hp_secret');
if (!$secret) {
$secret = wp_generate_password(32, false);
update_option('itk_hp_secret', $secret);
}
return $secret;
}
}

View File

@@ -0,0 +1,359 @@
<?php
if (!defined('ABSPATH')) exit;
/**
* ITK Optimization
*
* Merged from informatiq-wp-secure optimization class and informatiq-utils.
* All features are toggleable via itk_optimization option.
*/
class ITK_Optimization {
private array $opts;
public function __construct() {
$this->opts = get_option('itk_optimization', []);
$this->init();
}
private function on(string $key): bool {
return !empty($this->opts[$key]);
}
private function init(): void {
// ── Version / meta ──────────────────────────────────────
if ($this->on('remove_wp_version')) {
remove_action('wp_head', 'wp_generator');
add_filter('the_generator', '__return_empty_string');
}
// ── Login errors ────────────────────────────────────────
if ($this->on('hide_login_errors')) {
add_filter('login_errors', fn() => 'Something went wrong.');
}
// ── Comment class ───────────────────────────────────────
if ($this->on('remove_author_class')) {
add_filter('comment_class', [$this, 'remove_comment_author_class']);
}
// ── Script / style versions ─────────────────────────────
if ($this->on('remove_script_versions')) {
add_filter('style_loader_src', [$this, 'remove_version_param'], 999);
add_filter('script_loader_src', [$this, 'remove_version_param'], 999);
}
// ── Author base ─────────────────────────────────────────
if ($this->on('change_author_base')) {
add_action('init', [$this, 'change_author_base']);
}
// ── Revisions ───────────────────────────────────────────
if ($this->on('limit_revisions') && !defined('WP_POST_REVISIONS')) {
define('WP_POST_REVISIONS', 3);
}
if (!defined('AUTOSAVE_INTERVAL')) {
define('AUTOSAVE_INTERVAL', 300);
}
// ── Emoji ───────────────────────────────────────────────
if ($this->on('remove_emoji')) {
add_action('init', [$this, 'disable_emojis']);
}
// ── User fields ─────────────────────────────────────────
if ($this->on('remove_default_userfields')) {
add_filter('user_contactmethods', [$this, 'remove_default_userfields']);
}
// ── Content cleanup ─────────────────────────────────────
if ($this->on('clean_bad_content')) {
add_filter('content_save_pre', [$this, 'clean_bad_content']);
}
// ── WP head noise ───────────────────────────────────────
if ($this->on('remove_wp_head_noise')) {
$this->remove_wp_head_noise();
}
// ── XML-RPC ─────────────────────────────────────────────
if ($this->on('disable_xml_rpc')) {
add_filter('xmlrpc_enabled', '__return_false');
}
// ── WP Embed ────────────────────────────────────────────
if ($this->on('deregister_wp_embed')) {
add_action('wp_footer', [$this, 'deregister_wp_embed']);
}
// ── Empty search ────────────────────────────────────────
if ($this->on('stop_empty_search_redirect')) {
add_filter('request', [$this, 'stop_empty_search']);
}
// ── Widgets ─────────────────────────────────────────────
if ($this->on('unregister_default_widgets')) {
add_action('widgets_init', [$this, 'unregister_default_widgets'], 11);
}
// ── Defer JS ────────────────────────────────────────────
if ($this->on('defer_js')) {
add_filter('script_loader_tag', [$this, 'defer_js'], 10);
}
// ── Heartbeat ───────────────────────────────────────────
if ($this->on('limit_heartbeat')) {
add_filter('wpe_heartbeat_allowed_pages', [$this, 'limit_heartbeat_pages']);
}
// ── Dashboard widgets ───────────────────────────────────
if ($this->on('disable_dashboard_widgets')) {
add_action('admin_init', [$this, 'disable_dashboard_widgets'], 9999);
}
add_action('wp_dashboard_setup', [$this, 'add_itq_dashboard_widget']);
// ── Comment URL field ───────────────────────────────────
if ($this->on('disable_comments_url')) {
add_filter('comment_form_default_fields', [$this, 'remove_comment_url']);
}
// ── Google FLoC / Permissions-Policy ────────────────────
if ($this->on('disable_floc')) {
add_filter('wp_headers', [$this, 'disable_floc']);
}
// ── Lightbox images ─────────────────────────────────────
if ($this->on('lightbox_images')) {
add_filter('the_content', [$this, 'add_lightbox_rel']);
}
// ── Admin bar cleanup ───────────────────────────────────
if ($this->on('remove_admin_bar_links')) {
add_action('wp_before_admin_bar_render', [$this, 'remove_admin_bar_links']);
}
// ── Admin branding ──────────────────────────────────────
if ($this->on('admin_branding')) {
add_filter('admin_footer_text', [$this, 'admin_footer_text']);
add_action('admin_bar_menu', [$this, 'toolbar_link'], 999);
add_action('admin_bar_menu', [$this, 'remove_wp_logo'], 999);
add_action('admin_notices', [$this, 'admin_notice']);
}
// ── RSS featured image ──────────────────────────────────
if ($this->on('featured_image_rss')) {
add_filter('the_excerpt_rss', [$this, 'featured_to_rss']);
add_filter('the_content_feed', [$this, 'featured_to_rss']);
}
// ── DNS prefetch ────────────────────────────────────────
if ($this->on('dns_prefetch')) {
add_action('wp_head', [$this, 'dns_prefetch'], 1);
}
// ── Google jQuery ───────────────────────────────────────
if ($this->on('use_google_jquery')) {
add_action('init', [$this, 'use_google_jquery']);
}
}
/* ── Callback implementations ─────────────────────────────── */
public function remove_comment_author_class(array $classes): array {
return array_filter($classes, fn($c) => strpos($c, 'comment-author-') === false);
}
public function remove_version_param(string $src): string {
return strpos($src, 'ver=') ? remove_query_arg('ver', $src) : $src;
}
public function change_author_base(): void {
global $wp_rewrite;
$wp_rewrite->author_base = 'writer';
}
public function disable_emojis(): void {
remove_action('wp_head', 'print_emoji_detection_script', 7);
remove_action('admin_print_scripts','print_emoji_detection_script');
remove_action('wp_print_styles', 'print_emoji_styles');
remove_action('admin_print_styles','print_emoji_styles');
remove_filter('the_content_feed', 'wp_staticize_emoji');
remove_filter('comment_text_rss', 'wp_staticize_emoji');
remove_filter('wp_mail', 'wp_staticize_emoji_for_email');
add_filter('tiny_mce_plugins', fn($p) => is_array($p) ? array_diff($p, ['wpemoji']) : []);
}
public function remove_default_userfields(array $fields): array {
foreach (['aim','jabber','yim'] as $f) unset($fields[$f]);
return $fields;
}
public function clean_bad_content(string $content): string {
return preg_replace([
"~<p[^>]*>\s?</p>~",
"~<a[^>]*>\s?</a>~",
"~<font[^>]*>~",
"~<\/font>~",
"~style\=\"[^\"]*\"~",
"~<span[^>]*>\s?</span>~",
], '', $content) ?? $content;
}
private function remove_wp_head_noise(): void {
remove_action('wp_head', 'wlwmanifest_link');
remove_action('wp_head', 'rsd_link');
remove_action('wp_head', 'wp_generator');
remove_action('wp_head', 'start_post_rel_link');
remove_action('wp_head', 'index_rel_link');
remove_action('wp_head', 'feed_links_extra', 3);
remove_action('wp_head', 'feed_links', 2);
remove_action('wp_head', 'parent_post_rel_link', 10, 0);
remove_action('wp_head', 'start_post_rel_link', 10, 0);
remove_action('wp_head', 'adjacent_posts_rel_link_wp_head', 10, 0);
}
public function deregister_wp_embed(): void {
wp_deregister_script('wp-embed');
}
public function stop_empty_search(array $vars): array {
if (isset($_GET['s']) && empty($_GET['s'])) $vars['s'] = ' ';
return $vars;
}
public function unregister_default_widgets(): void {
foreach ([
'WP_Widget_Calendar', 'WP_Widget_Archives', 'WP_Widget_Meta',
'WP_Widget_Search', 'WP_Widget_Tag_Cloud',
] as $w) {
if (class_exists($w)) unregister_widget($w);
}
}
public function defer_js(string $tag): string {
$defer = [
'owl-carousel.min.js','mansonry.js','imgloaded.js',
'jquery.magnific-popup.min.js','bgswitcher.js','exit.js',
'lazyload.js','app.js',
'add-to-cart.min.js','cart-fragments.min.js','woocommerce.min.js',
'wp-embed.min.js',
];
foreach ($defer as $s) {
if (strpos($tag, $s) !== false) {
return str_replace(' src', ' defer="defer" src', $tag);
}
}
return $tag;
}
public function limit_heartbeat_pages(array $allowed): array {
return ['index.php','admin.php','edit.php','post.php','post-new.php'];
}
public function disable_dashboard_widgets(): void {
remove_meta_box('dashboard_primary', 'dashboard', 'core');
remove_meta_box('wpe_dify_news_feed', 'dashboard', 'normal');
global $wp_meta_boxes;
unset($wp_meta_boxes['dashboard']['normal']['core']['dashboard_right_now']);
unset($wp_meta_boxes['dashboard']['side']['core']['dashboard_secondary']);
unset($wp_meta_boxes['dashboard']['side']['core']['dashboard_quick_press']);
}
public function add_itq_dashboard_widget(): void {
wp_add_dashboard_widget(
'itk_info_widget',
'Developed & Maintained by',
[$this, 'itq_dashboard_widget_content']
);
}
public function itq_dashboard_widget_content(): void {
echo '<div style="text-align:center">'
. '<a href="https://informatiq.services" target="_blank">'
. '<img src="https://informatiq.services/images/logo_IQ_transparentAsset_1.png" width="200"></a>'
. '<br><strong>Strategic Solutions, Intelligent IT Services</strong>'
. '<br><br>Email: <a href="mailto:support@informatiq.services">support@informatiq.services</a>'
. '<br>Phone: (+34) 971 560 060 &nbsp;|&nbsp; Emergency: (+34) 643 732 407'
. '</div>';
}
public function remove_comment_url(array $fields): array {
unset($fields['url']);
return $fields;
}
public function disable_floc(array $headers): array {
$headers['Permissions-Policy'] = 'interest-cohort=()';
return $headers;
}
public function add_lightbox_rel(string $content): string {
global $post;
$title = isset($post->post_title) ? esc_attr($post->post_title) : '';
$pattern = '/<a(.*?)href=([\'"])(.*?)\.(bmp|gif|jpeg|jpg|png)([\'"])(.*?)>/i';
$replace = '<a$1href=$2$3.$4$5 rel="lightbox" title="' . $title . '"$6>';
return preg_replace($pattern, $replace, $content) ?? $content;
}
public function remove_admin_bar_links(): void {
global $wp_admin_bar;
foreach (['wp-logo','about','wporg','documentation','support-forums','feedback','comments'] as $node) {
$wp_admin_bar->remove_menu($node);
}
}
public function admin_footer_text(): string {
return '<a href="https://wordpress.org" target="_blank">WordPress</a> Core | '
. 'Customizations by <a href="https://informatiq.services/" target="_blank"><strong>InformatiQ Services</strong></a>';
}
public function toolbar_link(\WP_Admin_Bar $bar): void {
$bar->add_node([
'id' => 'itq-support',
'title' => 'InformatiQ Services',
'href' => 'https://informatiq.services',
'meta' => ['class' => 'itq-support', 'title' => 'Strategic Solutions, Intelligent IT Services'],
]);
}
public function remove_wp_logo(\WP_Admin_Bar $bar): void {
$bar->remove_node('wp-logo');
}
public function admin_notice(): void {
echo '<div class="updated notice"><p>'
. esc_html__('This website has been developed and is being hosted and maintained by', 'informatiq-toolkit')
. ' <a href="https://informatiq.services" target="_blank">InformatiQ</a></p></div>';
}
public function featured_to_rss(string $content): string {
global $post;
if (!empty($post->ID) && has_post_thumbnail($post->ID)) {
$content = get_the_post_thumbnail($post->ID, 'thumbnail', ['style' => 'float:left;margin:0 15px 15px 0']) . $content;
}
return $content;
}
public function dns_prefetch(): void {
echo "\n<!-- DNS Prefetch -->\n"
. '<meta http-equiv="x-dns-prefetch-control" content="on">' . "\n"
. "<!-- /DNS Prefetch -->\n";
}
public function use_google_jquery(): void {
if (is_admin()) return;
wp_deregister_script('jquery');
wp_register_script('jquery', 'https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js', [], null, true);
wp_enqueue_script('jquery');
}
/* ── Option update (called by admin AJAX) ─────────────────── */
public function update_options(array $opts): void {
$this->opts = $opts;
}
public function get_options(): array {
return $this->opts;
}
}

View File

@@ -0,0 +1,306 @@
<?php
if (!defined('ABSPATH')) exit;
/**
* ITK Protection
*
* Handles wp-login protection, security headers, sensitive file blocking,
* malicious query blocking, and custom login URL.
*
* Deactivation bug fix: every method checks its own option key before acting.
* Hooks are always registered; guards live inside the callbacks.
*/
class ITK_Protection {
private array $allowed_ips = [];
private string $allowed_ips_file;
public function __construct() {
$this->allowed_ips_file = ITK_PATH . 'config/allowed-ips.conf';
$this->load_allowed_ips();
add_action('init', [$this, 'protect_wp_login'], 0);
add_action('init', [$this, 'block_sensitive_files'], 0);
add_action('init', [$this, 'block_malicious_queries'], 0);
add_action('init', [$this, 'block_author_scans'], 0);
add_action('init', [$this, 'custom_login_url'], 0);
add_action('send_headers', [$this, 'add_security_headers']);
add_action('wp_loaded', [$this, 'wp_loaded_custom_login']);
add_filter('the_generator', '__return_empty_string');
add_filter('wp_redirect', [$this, 'redirect_filter'], 10, 2);
add_filter('network_site_url', [$this, 'network_url_filter'], 10, 3);
add_filter('site_url', [$this, 'site_url_filter'], 10, 4);
add_filter('wp_handle_upload_prefilter', [$this, 'filter_uploaded_files']);
}
/* ── wp-login protection ──────────────────────────────────── */
public function protect_wp_login(): void {
$options = get_option('itk_security', []);
if (empty($options['protect_wp_login'])) return;
$uri = $_SERVER['REQUEST_URI'] ?? '';
if (strpos($uri, 'wp-login.php') === false) return;
$ip = $this->get_client_ip();
$is_allowed = false;
foreach ($this->allowed_ips as $allowed) {
if ($ip === $allowed) { $is_allowed = true; break; }
if (strpos($allowed, '/') !== false && $this->ip_in_cidr($ip, $allowed)) {
$is_allowed = true; break;
}
}
if (!$is_allowed) {
$this->send_403($options, 'Access to login page not allowed from your IP address.');
}
$ua = $_SERVER['HTTP_USER_AGENT'] ?? '';
$protocol = $_SERVER['SERVER_PROTOCOL'] ?? '';
if (empty($ua) || $protocol === 'HTTP/1.0') {
$this->send_403($options, 'Invalid request detected.');
}
}
/* ── Security headers ─────────────────────────────────────── */
public function add_security_headers(): void {
$options = get_option('itk_security', []);
if (empty($options['add_security_headers'])) return;
if (headers_sent()) return;
header_remove('X-Powered-By');
header('X-Content-Type-Options: nosniff');
header('X-Frame-Options: SAMEORIGIN');
header('X-XSS-Protection: 1; mode=block');
header('Referrer-Policy: strict-origin-when-cross-origin');
}
/* ── Sensitive file blocking ──────────────────────────────── */
public function block_sensitive_files(): void {
$options = get_option('itk_security', []);
$uri = $_SERVER['REQUEST_URI'] ?? '';
if (!empty($options['protect_wp_includes'])) {
if (preg_match('#^/wp-includes/[^/]+\.php$#i', $uri)) {
$this->send_403($options, 'Access to this file is not allowed.');
}
if (preg_match('#^/wp-admin/includes/#i', $uri)) {
$this->send_403($options, 'Access to this file is not allowed.');
}
if (preg_match('#^/wp-includes/theme-compat/#i', $uri)) {
$this->send_403($options, 'Access to this file is not allowed.');
}
if (preg_match('#/wp-includes/js/tinymce/langs/.+\.php#i', $uri)) {
$this->send_403($options, 'Access to this file is not allowed.');
}
if (preg_match('#(license\.txt|wp-config-sample\.php|readme\.html)$#i', $uri)) {
$this->send_403($options, 'Access to this file is not allowed.');
}
if (preg_match('#(?:^|/)\.(?!well-known)#', $uri)) {
$this->send_403($options, 'Access to hidden files is not allowed.');
}
}
if (!empty($options['protect_uploads'])) {
if (preg_match('#^/wp-content/uploads/.*\.(?:php[1-6]?|pht|phtml?)$#i', $uri)) {
$this->send_403($options, 'PHP files are not allowed in the uploads directory.');
}
}
if (!empty($options['block_xmlrpc'])) {
if (strpos($uri, 'xmlrpc.php') !== false) {
$this->send_403($options, 'XML-RPC is disabled on this site.');
}
}
}
/* ── Malicious query blocking ─────────────────────────────── */
public function block_malicious_queries(): void {
$options = get_option('itk_security', []);
if (empty($options['block_malicious_queries'])) return;
$qs = $_SERVER['QUERY_STRING'] ?? '';
if (empty($qs)) return;
$patterns = [
'(eval\()',
'(127\.0\.0\.1)',
'([a-z0-9]{2000})',
'(javascript:)(.*)(;)',
'(base64_encode)(.*)(\()',
'(GLOBALS|REQUEST)(=|\[|%)',
'(<|%3C)(.*)script(.*)(>|%3)',
'(boot\.ini|etc/passwd|self/environ)',
'(thumbs?(_editor|open)?|tim(thumb)?)\.php',
'(\'|\\")(.*)(drop|insert|md5|select|union)',
];
foreach ($patterns as $pattern) {
if (preg_match('#' . $pattern . '#i', $qs)) {
$this->send_403($options, 'Malicious query detected.');
}
}
$method = strtolower($_SERVER['REQUEST_METHOD'] ?? '');
if (preg_match('#^(connect|debug|delete|move|put|trace|track)$#', $method)) {
$this->send_403($options, 'This request method is not allowed.');
}
}
/* ── Author scan blocking ─────────────────────────────────── */
public function block_author_scans(): void {
$options = get_option('itk_security', []);
if (empty($options['block_author_scans'])) return;
$uri = $_SERVER['REQUEST_URI'] ?? '';
$qs = $_SERVER['QUERY_STRING'] ?? '';
if (strpos($uri, '/wp-admin') === false && preg_match('/author=\d+/i', $qs)) {
wp_redirect(home_url(), 301);
exit;
}
}
/* ── Custom login URL ─────────────────────────────────────── */
public function custom_login_url(): void {
$options = get_option('itk_security', []);
if (empty($options['enable_custom_login'])) return;
$slug = $this->custom_slug($options);
$path = parse_url($_SERVER['REQUEST_URI'] ?? '', PHP_URL_PATH) ?? '';
$qs = parse_url($_SERVER['REQUEST_URI'] ?? '', PHP_URL_QUERY) ?? '';
if (strpos($path, '/' . $slug) !== false) {
if (!session_id()) session_start();
$_SESSION['itk_login_access'] = time();
require_once ABSPATH . 'wp-login.php';
exit;
}
$blocked = ['/wp-login.php', '/wp-admin/', '/login/', '/admin/'];
foreach ($blocked as $b) {
if (strpos($path, $b) !== false) {
if (defined('DOING_AJAX') && DOING_AJAX) return;
if (!session_id()) session_start();
if (isset($_SESSION['itk_login_access']) && (time() - $_SESSION['itk_login_access']) < 300) return;
if (is_user_logged_in()) return;
$this->send_403($options, 'Access denied. Please use the correct login URL.');
}
}
}
public function wp_loaded_custom_login(): void {
$options = get_option('itk_security', []);
if (empty($options['enable_custom_login'])) return;
global $pagenow;
if ($pagenow === 'wp-login.php') {
if (!session_id()) session_start();
if (!isset($_SESSION['itk_login_access'])) {
$this->send_403($options, 'Access denied. Please use the correct login URL.');
}
}
}
public function redirect_filter(string $location, int $status): string {
$options = get_option('itk_security', []);
if (empty($options['enable_custom_login'])) return $location;
return str_replace('wp-login.php', $this->custom_slug($options), $location);
}
public function network_url_filter(string $url, string $path): string {
$options = get_option('itk_security', []);
if (empty($options['enable_custom_login'])) return $url;
if (strpos($path, 'wp-login.php') !== false) {
return str_replace('wp-login.php', $this->custom_slug($options), $url);
}
return $url;
}
public function site_url_filter(string $url, string $path): string {
$options = get_option('itk_security', []);
if (empty($options['enable_custom_login'])) return $url;
if (strpos($path, 'wp-login.php') !== false) {
return str_replace('wp-login.php', $this->custom_slug($options), $url);
}
return $url;
}
/* ── File upload filter ───────────────────────────────────── */
public function filter_uploaded_files(array $file): array {
$options = get_option('itk_security', []);
if (empty($options['protect_uploads'])) return $file;
if (preg_match('/\.(php|phtml|php\d|pht|exe|dll|asp|aspx|jsp|cgi|pl)$/i', $file['name'] ?? '')) {
$file['error'] = 'PHP and executable files cannot be uploaded.';
}
return $file;
}
/* ── Helpers ──────────────────────────────────────────────── */
private function send_403(array $options, string $message): void {
$code = $options['response_code'] ?? '403';
$redir = $options['redirect_url'] ?? '';
if ($code === '301_custom' && !empty($redir)) {
header('Location: ' . esc_url_raw($redir), true, 301);
} else {
status_header(403);
echo esc_html($message);
}
exit;
}
private function custom_slug(array $options): string {
return !empty($options['custom_login_slug']) ? $options['custom_login_slug'] : 'thoushallpass';
}
private function load_allowed_ips(): void {
$defaults = ['127.0.0.1', '::1'];
if (file_exists($this->allowed_ips_file)) {
$lines = file($this->allowed_ips_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
foreach ($lines as $line) {
$line = trim($line);
if ($line !== '' && $line[0] !== '#') {
$this->allowed_ips[] = $line;
}
}
}
if (empty($this->allowed_ips)) {
$this->allowed_ips = $defaults;
}
}
private function get_client_ip(): string {
$keys = [
'HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED',
'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED',
'REMOTE_ADDR',
];
foreach ($keys as $key) {
if (empty($_SERVER[$key])) continue;
$ip = trim(explode(',', $_SERVER[$key])[0]);
if (filter_var($ip, FILTER_VALIDATE_IP)) return $ip;
}
return 'UNKNOWN';
}
private function ip_in_cidr(string $ip, string $cidr): bool {
[$subnet, $mask] = explode('/', $cidr, 2);
if (!filter_var($subnet, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) return false;
if (!is_numeric($mask) || $mask < 0 || $mask > 32) return false;
if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) return false;
$mask_dec = ~((1 << (32 - (int)$mask)) - 1);
return (ip2long($ip) & $mask_dec) === (ip2long($subnet) & $mask_dec);
}
public function get_allowed_ips(): array { return $this->allowed_ips; }
public function get_allowed_ips_file(): string { return $this->allowed_ips_file; }
}

1
index.php Normal file
View File

@@ -0,0 +1 @@
<?php // Silence is golden

147
informatiq-toolkit.php Normal file
View File

@@ -0,0 +1,147 @@
<?php
/**
* Plugin Name: InformatiQ Toolkit
* Plugin URI: https://informatiq.services
* Description: All-in-one security, optimization, and anti-spam toolkit. Bot blocking with dashboard, login protection, honeypot forms, and WordPress optimizations.
* Version: 1.0.0
* Author: Mălin Cenușă
* Author URI: https://mălin.ro
* License: GPL v2 or later
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
* Text Domain: informatiq-toolkit
*/
if (!defined('ABSPATH')) {
exit;
}
define('ITK_VERSION', '1.0.0');
define('ITK_PATH', plugin_dir_path(__FILE__));
define('ITK_URL', plugin_dir_url(__FILE__));
define('ITK_BASENAME', plugin_basename(__FILE__));
require_once ITK_PATH . 'includes/class-itk-database.php';
require_once ITK_PATH . 'includes/class-itk-bot-blocker.php';
require_once ITK_PATH . 'includes/class-itk-protection.php';
require_once ITK_PATH . 'includes/class-itk-optimization.php';
require_once ITK_PATH . 'includes/class-itk-honeypot.php';
require_once ITK_PATH . 'includes/class-itk-admin.php';
class InformatiQ_Toolkit {
private static $instance = null;
public static function instance() {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
private function __construct() {
new ITK_Bot_Blocker();
new ITK_Protection();
new ITK_Optimization();
new ITK_Honeypot();
if (is_admin()) {
new ITK_Admin();
}
add_filter('plugin_action_links_' . ITK_BASENAME, [$this, 'add_settings_link']);
}
public function add_settings_link($links) {
array_unshift($links, '<a href="' . admin_url('options-general.php?page=informatiq-toolkit') . '">Settings</a>');
return $links;
}
public static function activate() {
ITK_Database::install();
// Default security settings
if (!get_option('itk_security')) {
add_option('itk_security', [
'block_openai_bots' => 1,
'block_malicious_bots' => 1,
'block_bad_referrers' => 1,
'block_bad_networks' => 1,
'rate_limit_good_bots' => 1,
'protect_wp_login' => 1,
'protect_wp_includes' => 1,
'protect_uploads' => 1,
'block_author_scans' => 1,
'block_malicious_queries'=> 1,
'add_security_headers' => 1,
'block_xmlrpc' => 1,
'enable_custom_login' => 0,
'custom_login_slug' => 'thoushallpass',
'response_code' => '301_custom',
'redirect_url' => 'https://example.com/blocked',
'custom_message' => 'Access denied.',
'log_blocked_attempts' => 1,
]);
}
// Default optimization settings
if (!get_option('itk_optimization')) {
add_option('itk_optimization', [
'remove_wp_version' => 1,
'hide_login_errors' => 1,
'remove_author_class' => 1,
'remove_script_versions' => 1,
'change_author_base' => 1,
'limit_revisions' => 1,
'remove_emoji' => 1,
'remove_default_userfields'=> 1,
'clean_bad_content' => 1,
'remove_wp_head_noise' => 1,
'disable_xml_rpc' => 1,
'deregister_wp_embed' => 1,
'stop_empty_search_redirect'=> 1,
'unregister_default_widgets'=> 1,
'defer_js' => 1,
'limit_heartbeat' => 1,
'disable_dashboard_widgets'=> 1,
'disable_comments_url' => 1,
'disable_floc' => 1,
'lightbox_images' => 1,
'remove_admin_bar_links' => 1,
'admin_branding' => 1,
'use_google_jquery' => 0,
'featured_image_rss' => 1,
'dns_prefetch' => 1,
]);
}
// Default honeypot settings
if (!get_option('itk_honeypot')) {
add_option('itk_honeypot', [
'enabled' => 1,
'protect_comments' => 1,
'protect_login' => 1,
'protect_register' => 1,
'protect_lost_password'=> 1,
'protect_woocommerce' => 1,
'protect_cf7' => 1,
'protect_elementor' => 1,
'protect_gravity' => 1,
'protect_search' => 1,
'min_time' => 3,
'max_time' => 7200,
'retain_days' => 90,
]);
}
flush_rewrite_rules();
}
public static function deactivate() {
flush_rewrite_rules();
}
}
register_activation_hook(__FILE__, ['InformatiQ_Toolkit', 'activate']);
register_deactivation_hook(__FILE__, ['InformatiQ_Toolkit', 'deactivate']);
add_action('plugins_loaded', ['InformatiQ_Toolkit', 'instance']);

20
uninstall.php Normal file
View File

@@ -0,0 +1,20 @@
<?php
if (!defined('WP_UNINSTALL_PLUGIN')) exit;
// Remove all plugin options
delete_option('itk_security');
delete_option('itk_optimization');
delete_option('itk_honeypot');
delete_option('itk_hp_secret');
delete_option('itk_db_version');
// Remove transients
delete_transient('itk_bots_list');
delete_transient('itk_referrers_list');
delete_transient('itk_networks_list');
delete_transient('itk_goodbots_list');
// Drop database tables
global $wpdb;
$wpdb->query("DROP TABLE IF EXISTS {$wpdb->prefix}itk_bot_log");
$wpdb->query("DROP TABLE IF EXISTS {$wpdb->prefix}itk_honeypot_log");