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:
359
includes/class-itk-optimization.php
Normal file
359
includes/class-itk-optimization.php
Normal 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 | 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user