true, 'log_limit' => 500, 'ui_theme' => 'default', ); } function airai_free_get_options() { $opts = get_option( AIRAI_FREE_OPT_KEY ); if ( ! is_array( $opts ) ) { $opts = array(); } return array_merge( airai_free_default_options(), $opts ); } register_activation_hook( __FILE__, function(){ if ( ! get_option( AIRAI_FREE_OPT_KEY ) ) { add_option( AIRAI_FREE_OPT_KEY, airai_free_default_options() ); } } ); /** Explicit server accessors (unslash+sanitize on the *same* line, PCP-friendly) */ function airai_server_http_user_agent() { return sanitize_text_field( wp_unslash( (string) ( $_SERVER['HTTP_USER_AGENT'] ?? '' ) ) ); } function airai_server_remote_addr() { return sanitize_text_field( wp_unslash( (string) ( $_SERVER['REMOTE_ADDR'] ?? '' ) ) ); } function airai_server_request_uri() { return sanitize_text_field( wp_unslash( (string) ( $_SERVER['REQUEST_URI'] ?? '' ) ) ); } function airai_server_http_host() { return sanitize_text_field( wp_unslash( (string) ( $_SERVER['HTTP_HOST'] ?? '' ) ) ); } function airai_server_server_addr() { return sanitize_text_field( wp_unslash( (string) ( $_SERVER['SERVER_ADDR'] ?? '' ) ) ); } function airai_server_server_software() { return sanitize_text_field( wp_unslash( (string) ( $_SERVER['SERVER_SOFTWARE'] ?? '' ) ) ); } /** Path sanitizer (includes normalization + final sanitize) */ function airai_sanitize_path( $raw ) { $val = is_string( $raw ) ? $raw : '/'; $val = wp_unslash( $val ); $val = preg_replace( '~[^A-Za-z0-9\-\._~!\$&\'\(\)\*\+,;=:@%/]+~', '', $val ); if ( $val === '' ) { $val = '/'; } if ( $val[0] !== '/' ) { $val = '/' . $val; } if ( strlen( $val ) > 1024 ) { $val = substr( $val, 0, 1024 ); } return sanitize_text_field( $val ); } /** Known UAs */ function airai_known_uas() { return array( 'OAI-SearchBot' => 'OAI-SearchBot', 'ChatGPT-User' => 'ChatGPT-User', 'PerplexityBot' => 'PerplexityBot', 'GPTBot' => 'GPTBot', 'Google-Extended' => 'Google-Extended', 'Applebot-Extended' => 'Applebot-Extended', 'CCBot' => 'CCBot', ); } /** Robots.txt parser and evaluator */ function airai_robots_parse_groups( $robots ) { $robots = str_replace( array( "\r\n", "\r" ), "\n", (string) $robots ); $lines = preg_split( '/\n+/', $robots ); $groups = array(); $current = array( 'uas' => array(), 'rules' => array() ); $in = false; foreach ( $lines as $raw ) { $line = trim( $raw ); if ( $line === '' || strpos( $line, '#' ) === 0 ) { if ( $in ) { $groups[] = $current; $current = array( 'uas' => array(), 'rules' => array() ); $in = false; } continue; } $parts = explode( ':', $line, 2 ); if ( count( $parts ) < 2 ) { continue; } $field = strtolower( trim( $parts[0] ) ); $value = trim( $parts[1] ); if ( $field === 'user-agent' ) { if ( $in && ( ! empty( $current['uas'] ) || ! empty( $current['rules'] ) ) ) { $groups[] = $current; $current = array( 'uas' => array(), 'rules' => array() ); } $in = true; $current['uas'][] = $value; } elseif ( $field === 'disallow' || $field === 'allow' ) { if ( ! $in ) { $current = array( 'uas' => array( '*' ), 'rules' => array() ); $in = true; } $current['rules'][] = array( 'type' => $field, 'pattern' => $value ); } } if ( $in ) { $groups[] = $current; } return $groups; } function airai_robots_best_group_for_ua( $groups, $ua ) { $ua_lc = strtolower( (string) $ua ); $best = null; $best_len = -1; foreach ( $groups as $g ) { foreach ( $g['uas'] as $token ) { $tok = strtolower( $token ); if ( $tok === '*' ) { if ( $best === null && $best_len < 1 ) { $best = $g; $best_len = 1; } } elseif ( $tok !== '' && strpos( $ua_lc, $tok ) !== false ) { $len = strlen( $tok ); if ( $len > $best_len ) { $best = $g; $best_len = $len; } } } } return $best; } function airai_robots_rule_match_len( $pattern, $path ) { $pattern = (string) $pattern; $path = (string) $path; if ( $pattern === '' ) { return 0; } $escaped = preg_quote( $pattern, '/' ); $escaped = str_replace( '\*', '.*', $escaped ); $end = false; if ( substr( $escaped, -2 ) === '\$' ) { $escaped = substr( $escaped, 0, -2 ) . '$'; $end = true; } $regex = '/' . '^' . $escaped . '/' . 'i'; if ( preg_match( $regex, $path, $m ) ) { $len = strlen( $m[0] ); return $end ? ( $len + 1 ) : $len; } return 0; } function airai_robots_allowed( $robots, $ua, $path ) { $groups = airai_robots_parse_groups( $robots ); $group = airai_robots_best_group_for_ua( $groups, $ua ); if ( ! $group ) { return null; } $best_type = null; $best_len = -1; foreach ( $group['rules'] as $r ) { $pat = trim( $r['pattern'] ); $ml = ( $pat === '' ? 0 : airai_robots_rule_match_len( $pat, $path ) ); if ( $ml > $best_len ) { $best_len = $ml; $best_type = $r['type']; } elseif ( $ml === $best_len && 'allow' !== $best_type && 'allow' === $r['type'] ) { $best_type = 'allow'; } } if ( $best_len < 0 ) { return true; } return ( 'allow' === $best_type ); } /** Append a log entry (helper used by Quick Test) */ function airai_append_log_entry( $bot, $ua, $uri, $ip, $host ) { $log = get_option( AIRAI_FREE_LOG_KEY, array() ); if ( ! is_array( $log ) ) { $log = array(); } $log[] = array( 't' => current_time( 'mysql' ), 'ua' => sanitize_text_field( $ua ), 'bot' => sanitize_text_field( $bot ), 'ip' => sanitize_text_field( $ip ), 'uri' => sanitize_text_field( $uri ), 'host'=> sanitize_text_field( $host ), ); $opt = airai_free_get_options(); $limit = (int) ( isset( $opt['log_limit'] ) ? $opt['log_limit'] : 500 ); if ( $limit < 50 ) { $limit = 50; } if ( count( $log ) > $limit ) { $start = max( 0, count( $log ) - (int) $limit ); $log = array_slice( $log, $start ); } update_option( AIRAI_FREE_LOG_KEY, $log, false ); } /** Passive bot logger */ add_action( 'init', function(){ $opt = airai_free_get_options(); if ( empty( $opt['enable_bot_logging'] ) ) { return; } $ua_seen = airai_server_http_user_agent(); if ( $ua_seen === '' ) { return; } $match = null; foreach ( array_keys( airai_known_uas() ) as $needle ) { if ( stripos( $ua_seen, $needle ) !== false ) { $match = $needle; break; } } if ( $match === null ) { return; } airai_append_log_entry( $match, $ua_seen, airai_server_request_uri(), airai_server_remote_addr(), airai_server_http_host() ); } ); /** Admin pages */ add_action( 'admin_menu', function(){ add_menu_page( esc_html__( 'AI Readiness', 'ai-readiness-advisor' ), esc_html__( 'AI Readiness', 'ai-readiness-advisor' ), 'manage_options', 'airai-dashboard', 'airai_render_app', 'dashicons-shield', 58 ); add_submenu_page( 'airai-dashboard', esc_html__( 'Verification', 'ai-readiness-advisor' ), esc_html__( 'Verification', 'ai-readiness-advisor' ), 'manage_options', 'airai-verify', 'airai_render_app' ); add_submenu_page( 'airai-dashboard', esc_html__( 'Tools', 'ai-readiness-advisor' ), esc_html__( 'Tools', 'ai-readiness-advisor' ), 'manage_options', 'airai-tools', 'airai_render_app' ); add_submenu_page( 'airai-dashboard', esc_html__( 'Logs', 'ai-readiness-advisor' ), esc_html__( 'Logs', 'ai-readiness-advisor' ), 'manage_options', 'airai-logs', 'airai_render_app' ); add_submenu_page( 'airai-dashboard', esc_html__( 'Help', 'ai-readiness-advisor' ), esc_html__( 'Help', 'ai-readiness-advisor' ), 'manage_options', 'airai-help', 'airai_render_app' ); } ); add_action( 'admin_menu', function(){ remove_submenu_page( 'airai-dashboard', 'airai-dashboard' ); }, 100 ); /** Enqueue admin assets only on our pages */ add_action( 'admin_enqueue_scripts', function(){ // phpcs:ignore WordPress.Security.NonceVerification.Recommended $page = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : ''; if ( strpos( $page, 'airai-' ) !== 0 ) { return; } $ver = '1.5.5'; $base = plugin_dir_url( __FILE__ ) . 'assets/'; wp_enqueue_style( 'airai-free-admin', $base . 'admin.css', array(), $ver ); wp_enqueue_script( 'airai-free-admin', $base . 'admin.js', array(), $ver, true ); $opts = airai_free_get_options(); if ( ! is_array( $opts ) ) { $opts = array(); } $data = array( 'ajaxurl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'airai_free_ajax' ), 'currentPage' => $page === '' ? 'airai-dashboard' : $page, 'home' => home_url( '/' ), 'site' => site_url( '/' ), 'pluginVersion'=> $ver, 'theme' => ( isset( $opts['ui_theme'] ) ? sanitize_text_field( $opts['ui_theme'] ) : 'default' ), ); wp_localize_script( 'airai-free-admin', 'AIRAI_FREE', $data ); wp_localize_script( 'airai-free-admin', 'AIRAI', $data ); // compatibility alias } ); /** Render root container(s) */ function airai_render_app() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'Insufficient permissions.', 'ai-readiness-advisor' ) ); } echo '