detector = new AiMiles_Detector(); add_action('init', [$this, 'detect_ai_agent'], 1); add_action('admin_menu', [$this, 'add_admin_menu']); add_action('admin_init', [$this, 'register_settings']); add_action('admin_notices', [$this, 'admin_notices']); add_filter('plugin_action_links_' . plugin_basename(__FILE__), [$this, 'add_settings_link']); } public function admin_notices() { $api_key = get_option('aimiles_api_key', ''); if (empty($api_key)) { $settings_url = admin_url('options-general.php?page=aimiles'); echo '
'; echo '

aiMiles: Please configure your API key to enable AI agent detection and sponsored content.

'; echo '
'; } } public function detect_ai_agent() { if (!get_option('aimiles_enabled', true)) { return; } $api_key = get_option('aimiles_api_key', ''); if (empty($api_key)) { return; } $user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT'])) : ''; if (!$this->detector->is_ai_agent($user_agent)) { return; } // Build the original URL the bot was requesting $protocol = is_ssl() ? 'https' : 'http'; $host = isset($_SERVER['HTTP_HOST']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_HOST'])) : wp_parse_url(home_url(), PHP_URL_HOST); $request_uri = isset($_SERVER['REQUEST_URI']) ? esc_url_raw(wp_unslash($_SERVER['REQUEST_URI'])) : '/'; $original_url = $protocol . '://' . $host . $request_uri; // Proxy through the aiMiles serve API (handles fetching, injection, and logging) $serve_url = add_query_arg([ 'url' => $original_url, 'key' => $api_key, 'ua' => $user_agent, ], 'https://app.aimiles.ai/api/serve'); // Passive IP forwarding: pass the bot's real IP along so it can be // logged server-side. Recorded only — never checked or enforced. // Prefer the CDN-restored client IP over REMOTE_ADDR (which is the // CDN edge when the site sits behind Cloudflare or a proxy). if (isset($_SERVER['HTTP_CF_CONNECTING_IP'])) { $bot_ip = sanitize_text_field(wp_unslash($_SERVER['HTTP_CF_CONNECTING_IP'])); } elseif (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { $forwarded = sanitize_text_field(wp_unslash($_SERVER['HTTP_X_FORWARDED_FOR'])); $bot_ip = trim(explode(',', $forwarded)[0]); } else { $bot_ip = isset($_SERVER['REMOTE_ADDR']) ? sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'])) : ''; } $response = wp_remote_get($serve_url, [ 'timeout' => 10, 'headers' => [ 'X-Bot-IP' => $bot_ip, ], ]); if (is_wp_error($response)) { return; // Fail open — let WordPress serve the page normally } $status_code = wp_remote_retrieve_response_code($response); if ($status_code < 200 || $status_code >= 400) { return; // Fail open } $body = wp_remote_retrieve_body($response); if (empty($body)) { return; // Fail open } // Forward relevant headers from the serve API response $content_type = wp_remote_retrieve_header($response, 'content-type'); if (!empty($content_type)) { header('Content-Type: ' . $content_type); } // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- proxied HTML from aiMiles serve API echo $body; exit; } public function add_admin_menu() { add_options_page( 'aiMiles Settings', 'aiMiles', 'manage_options', 'aimiles', [$this, 'render_settings_page'] ); } public function register_settings() { register_setting('aimiles_settings', 'aimiles_enabled', [ 'type' => 'boolean', 'sanitize_callback' => 'rest_sanitize_boolean', 'default' => true, ]); register_setting('aimiles_settings', 'aimiles_api_key', [ 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', 'default' => '', ]); } public function add_settings_link($links) { $settings_link = 'Settings'; array_unshift($links, $settings_link); return $links; } public function render_settings_page() { include AIMILES_PLUGIN_DIR . 'admin/settings-page.php'; } } function aimiles_init() { return AiMiles::get_instance(); } add_action('plugins_loaded', 'aimiles_init'); register_activation_hook(__FILE__, 'aimiles_activate'); function aimiles_activate() { add_option('aimiles_enabled', true); add_option('aimiles_api_key', ''); } register_deactivation_hook(__FILE__, 'aimiles_deactivate'); function aimiles_deactivate() { // Clean up if needed }