prefix . 'aicrg_crawlers' ); } if ( ! defined( 'AICRG_TABLE_LOGS' ) ) { define( 'AICRG_TABLE_LOGS', $wpdb->prefix . 'aicrg_logs' ); } require_once AICRG_PLUGIN_DIR . 'includes/functions-helpers.php'; require_once AICRG_PLUGIN_DIR . 'includes/class-rules-provider.php'; require_once AICRG_PLUGIN_DIR . 'includes/class-detector.php'; require_once AICRG_PLUGIN_DIR . 'includes/class-crawler-registry.php'; require_once AICRG_PLUGIN_DIR . 'includes/class-policy.php'; require_once AICRG_PLUGIN_DIR . 'includes/class-logger.php'; require_once AICRG_PLUGIN_DIR . 'includes/class-admin.php'; register_activation_hook( __FILE__, 'aicrg_activate' ); register_deactivation_hook( __FILE__, 'aicrg_deactivate' ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Function is prefixed with 'aicrg_' function aicrg_activate(): void { global $wpdb; require_once ABSPATH . 'wp-admin/includes/upgrade.php'; $charset_collate = $wpdb->get_charset_collate(); $crawl_table = $wpdb->prefix . 'aicrg_crawlers'; $logs_table = $wpdb->prefix . 'aicrg_logs'; $sql_crawlers = "CREATE TABLE {$crawl_table} ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, bot_slug VARCHAR(100) NOT NULL, display_name VARCHAR(255) NOT NULL, status VARCHAR(20) NOT NULL DEFAULT 'unknown', first_seen DATETIME NOT NULL, last_seen DATETIME NOT NULL, total_hits INT NOT NULL DEFAULT 0, allowed_hits INT NOT NULL DEFAULT 0, blocked_hits INT NOT NULL DEFAULT 0, PRIMARY KEY (id), UNIQUE KEY bot_slug (bot_slug) ) {$charset_collate};"; $sql_logs = "CREATE TABLE {$logs_table} ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, time DATETIME NOT NULL, bot_slug VARCHAR(100) NOT NULL, bot_name VARCHAR(255) NOT NULL, user_agent TEXT, url TEXT, action VARCHAR(20) NOT NULL, PRIMARY KEY (id), KEY time (time) ) {$charset_collate};"; dbDelta( $sql_crawlers ); dbDelta( $sql_logs ); if ( false === get_option( 'aicrg_settings', false ) ) { add_option( 'aicrg_settings', aicrg_get_default_settings() ); } } // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Function is prefixed with 'aicrg_' function aicrg_deactivate(): void { // Intentionally left blank for future cleanup. } // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Function is prefixed with 'aicrg_' function aicrg_services(): array { static $services = null; if ( null === $services ) { global $wpdb; $rules_provider = new AICRG_Rules_Provider(); $detector = new AICRG_Detector( $rules_provider ); $registry = new AICRG_Crawler_Registry( $wpdb, AICRG_TABLE_CRAWLERS ); $logger = new AICRG_Logger( $wpdb, AICRG_TABLE_LOGS ); $policy = new AICRG_Policy( $registry, $rules_provider ); if ( is_admin() ) { new AICRG_Admin( $registry, $logger, AICRG_TABLE_LOGS, $rules_provider ); } $services = compact( 'rules_provider', 'detector', 'registry', 'logger', 'policy' ); } return $services; } add_action( 'plugins_loaded', 'aicrg_services' ); add_action( 'template_redirect', 'aicrg_maybe_block_crawler', 0 ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Function is prefixed with 'aicrg_' function aicrg_maybe_block_crawler(): void { static $processed = false; if ( $processed ) { return; } if ( is_admin() || wp_doing_cron() || ( defined( 'WP_CLI' ) && WP_CLI ) || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ) { return; } $processed = true; $services = aicrg_services(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- User-Agent is sanitized below $user_agent = sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ?? '' ) ); $detection = $services['detector']->detect( $user_agent ); // Only process if a bot was detected - regular users are not tracked if ( empty( $detection['is_bot'] ) ) { return; } $settings = aicrg_get_settings(); $history_enabled = ! empty( $settings['history_enabled'] ); $bot_slug = $detection['bot_slug']; $bot_name = $detection['bot_name']; $rule_info = $services['rules_provider']->get_category_info( $bot_slug ); $default_status = $rule_info['default_status'] ?? 'unknown'; if ( $history_enabled ) { $services['registry']->get_or_create( $bot_slug, $bot_name, $default_status ); } $decision = $services['policy']->decide( $bot_slug ); $action = 'block' === $decision ? 'blocked' : 'allowed'; $protocol = ( isset( $_SERVER['HTTPS'] ) && 'on' === $_SERVER['HTTPS'] ) ? 'https://' : 'http://'; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- HTTP_HOST is sanitized below $host = sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ?? '' ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- REQUEST_URI is sanitized below $uri = sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ?? '' ) ); $url = $protocol . $host . $uri; $services['logger']->log( $bot_slug, $bot_name, $user_agent, $url, $action ); if ( $history_enabled ) { $services['registry']->update_stats( $bot_slug, $action ); } if ( 'block' === $decision ) { status_header( 403 ); nocache_headers(); echo esc_html__( 'Access denied for automated crawlers.', 'ai-crawler-guard' ); exit; } }