'llms_txt', 'name' => __('llms.txt File', 'ai-ready-core'), 'status' => 'fail', 'message' => '', 'recommendation' => '', 'priority' => 'high' ]; $llms_url = home_url('/llms.txt'); // Test the virtual endpoint $response = wp_remote_get($llms_url, [ 'timeout' => 10, 'user-agent' => 'AI-Ready-Diagnostics/1.0' ]); if (is_wp_error($response)) { $check['status'] = 'fail'; /* translators: %s: error message */ $check['message'] = sprintf(__('llms.txt endpoint is not accessible: %s', 'ai-ready-core'), $response->get_error_message()); $check['recommendation'] = __('Ensure the AI-Ready plugin is active and rewrite rules are flushed.', 'ai-ready-core'); return $check; } $status_code = wp_remote_retrieve_response_code($response); $content = wp_remote_retrieve_body($response); $content_type = wp_remote_retrieve_header($response, 'content-type'); if ($status_code !== 200) { $check['status'] = 'fail'; /* translators: %d: HTTP status code */ $check['message'] = sprintf(__('llms.txt returns HTTP %d instead of 200', 'ai-ready-core'), $status_code); $check['recommendation'] = __('Check your WordPress rewrite rules and plugin configuration.', 'ai-ready-core'); return $check; } if (strpos($content_type, 'text/plain') === false) { $check['status'] = 'warning'; /* translators: %s: content type header value */ $check['message'] = sprintf(__('llms.txt should return Content-Type: text/plain (currently: %s)', 'ai-ready-core'), $content_type); $check['recommendation'] = __('Ensure proper content-type headers are set in the virtual endpoint.', 'ai-ready-core'); } if (empty($content) || strlen($content) < 50) { $check['status'] = 'warning'; $check['message'] = __('llms.txt content appears to be empty or too short', 'ai-ready-core'); $check['recommendation'] = __('Add meaningful content to your llms.txt file through the AI Ready settings page.', 'ai-ready-core'); return $check; } // Validate content format if (function_exists('ai_ready_validate_llms_txt')) { $validation = ai_ready_validate_llms_txt($content); if (!$validation['valid']) { $check['status'] = 'warning'; /* translators: %s: comma-separated list of validation errors */ $check['message'] = sprintf(__('llms.txt format issues: %s', 'ai-ready-core'), implode(', ', $validation['errors'])); $check['recommendation'] = __('Fix the format issues or use the "Reset to Defaults" option in AI Ready settings.', 'ai-ready-core'); return $check; } } $check['status'] = 'pass'; /* translators: %d: length of llms.txt content in characters */ $check['message'] = sprintf(__('llms.txt is accessible and properly formatted (%d characters)', 'ai-ready-core'), strlen($content)); $check['recommendation'] = __('Great! Your llms.txt file is working correctly.', 'ai-ready-core'); return $check; } /** * Check robots.txt for AI bot permissions and Agent Gate configuration * * @return array Diagnostic result */ function ai_ready_check_robots_txt() { $check = [ 'id' => 'robots_txt', 'name' => __('AI Bot Access (robots.txt)', 'ai-ready-core'), 'status' => 'unknown', 'message' => '', 'recommendation' => '', 'priority' => 'high' ]; // Use WordPress's robots.txt filter to get the actual content that would be served // This ensures we're checking the filtered content that includes Agent Gate rules $is_public = get_option('blog_public'); $robots_content = apply_filters('robots_txt', '', $is_public); // If no content from filter, fall back to HTTP request if (empty($robots_content)) { $robots_url = home_url('/robots.txt'); $response = wp_remote_get($robots_url, ['timeout' => 10]); if (is_wp_error($response) || wp_remote_retrieve_response_code($response) !== 200) { $check['status'] = 'warning'; $check['message'] = __('robots.txt not found or not accessible', 'ai-ready-core'); $check['recommendation'] = __('Consider creating a robots.txt file to provide clear guidance to AI crawlers. You can configure AI bot access using the Agent Gate tab.', 'ai-ready-core'); return $check; } $robots_content = wp_remote_retrieve_body($response); } // Check for global block first (highest priority) if (preg_match('/User-agent:\s*\*\s*\n\s*Disallow:\s*\//i', $robots_content)) { $check['status'] = 'info'; $check['message'] = __('🔒 GLOBAL BLOCK: robots.txt blocks ALL bots (User-agent: * Disallow: /)', 'ai-ready-core'); $check['recommendation'] = __('Your site blocks all crawlers including AI bots. This is a valid choice for privacy, but will prevent AI discovery. Use the Agent Gate tab to adjust AI bot access.', 'ai-ready-core'); return $check; } // Use Agent Gate module for diagnostics if available global $ai_ready_agent_gate; if (isset($ai_ready_agent_gate) && method_exists($ai_ready_agent_gate, 'get_diagnostics')) { $agent_gate_check = $ai_ready_agent_gate->get_diagnostics(); // Merge Agent Gate diagnostics with our basic check structure $check['status'] = $agent_gate_check['status']; $check['message'] = $agent_gate_check['message']; $check['recommendation'] = $agent_gate_check['recommendation']; // Add link to Agent Gate tab in recommendation if not already present if (strpos($check['recommendation'], 'agent-gate') === false) { /* translators: %s: URL to Agent Gate settings tab */ $check['recommendation'] .= sprintf(__(' Configure AI bot access in the Agent Gate tab →', 'ai-ready-core'), '?page=ai-ready-settings&tab=agent-gate'); } } else { // Fallback: basic Agent Gate check using direct option access $gate_mode = get_option('ai_ready_agent_gate_mode', 'allow_all'); $custom_bots = get_option('ai_ready_agent_gate_custom', []); switch ($gate_mode) { case 'allow_all': $check['status'] = 'pass'; $check['message'] = __('🟢 Agent Gate: All AI bots are allowed', 'ai-ready-core'); $check['recommendation'] = __('Excellent! All AI agents can access your content for maximum discoverability.', 'ai-ready-core'); break; case 'block_all': $check['status'] = 'info'; $check['message'] = __('🔒 Agent Gate: All AI bots are blocked', 'ai-ready-core'); $check['recommendation'] = __('You are blocking all AI bots as configured. This protects privacy but prevents AI discovery.', 'ai-ready-core'); break; case 'custom': $allowed_count = count($custom_bots); $check['status'] = 'pass'; /* translators: %d: number of allowed bots */ $check['message'] = sprintf(__('⚙️ Agent Gate: Custom configuration - %d bots allowed', 'ai-ready-core'), $allowed_count); $check['recommendation'] = __('You have selectively configured AI bot access.', 'ai-ready-core'); break; default: $check['status'] = 'warning'; $check['message'] = __('⚠️ Agent Gate: Unknown configuration', 'ai-ready-core'); $check['recommendation'] = __('Agent Gate configuration appears to be invalid. Please check your settings.', 'ai-ready-core'); break; } // Add link to Agent Gate tab /* translators: %s: URL to Agent Gate settings tab */ $check['recommendation'] .= sprintf(__(' Configure AI bot access in the Agent Gate tab →', 'ai-ready-core'), '?page=ai-ready-settings&tab=agent-gate'); } return $check; } /** * Check for noai meta tags that block AI crawling * * @return array Diagnostic result */ function ai_ready_check_noai_meta() { $check = [ 'id' => 'noai_meta', 'name' => __('No-AI Meta Tags', 'ai-ready-core'), 'status' => 'unknown', 'message' => '', 'recommendation' => '', 'priority' => 'medium' ]; // Check homepage for noai meta tags $homepage_response = wp_remote_get(home_url(), ['timeout' => 10]); if (is_wp_error($homepage_response)) { $check['status'] = 'unknown'; $check['message'] = __('Could not fetch homepage to check for noai meta tags', 'ai-ready-core'); $check['recommendation'] = __('Manually check your site for meta name="robots" content="noai" tags.', 'ai-ready-core'); return $check; } $homepage_content = wp_remote_retrieve_body($homepage_response); // Check for various noai patterns $noai_patterns = [ '/meta\s+name=["\']robots["\']\s+content=["\'][^"\']*noai/i', '/meta\s+name=["\']googlebot["\']\s+content=["\'][^"\']*noai/i', '/meta\s+name=["\']ai["\']\s+content=["\'][^"\']*noindex/i' ]; $found_noai = false; foreach ($noai_patterns as $pattern) { if (preg_match($pattern, $homepage_content)) { $found_noai = true; break; } } if ($found_noai) { $check['status'] = 'warning'; $check['message'] = __('Homepage contains noai meta tags that may block AI crawlers', 'ai-ready-core'); $check['recommendation'] = __('Remove noai meta tags if you want your content to be discoverable by AI systems.', 'ai-ready-core'); return $check; } $check['status'] = 'pass'; $check['message'] = __('No blocking noai meta tags found on homepage', 'ai-ready-core'); $check['recommendation'] = __('Good! Your pages are not explicitly blocking AI crawlers.', 'ai-ready-core'); return $check; } /** * Check for canonical URLs * * @return array Diagnostic result */ function ai_ready_check_canonical_urls() { $check = [ 'id' => 'canonical_urls', 'name' => __('Canonical URLs', 'ai-ready-core'), 'status' => 'unknown', 'message' => '', 'recommendation' => '', 'priority' => 'low' ]; // Check homepage for canonical link $homepage_response = wp_remote_get(home_url(), ['timeout' => 10]); if (is_wp_error($homepage_response)) { $check['status'] = 'unknown'; $check['message'] = __('Could not fetch homepage to check for canonical URLs', 'ai-ready-core'); $check['recommendation'] = __('Manually check your site for tags.', 'ai-ready-core'); return $check; } $homepage_content = wp_remote_retrieve_body($homepage_response); // Check for canonical link tag if (preg_match('/ 'structured_data', 'name' => __('Structured Data (JSON-LD)', 'ai-ready-core'), 'status' => 'unknown', 'message' => '', 'recommendation' => '', 'priority' => 'medium' ]; // Check homepage for JSON-LD structured data $homepage_response = wp_remote_get(home_url(), ['timeout' => 10]); if (is_wp_error($homepage_response)) { $check['status'] = 'unknown'; $check['message'] = __('Could not fetch homepage to check for structured data', 'ai-ready-core'); $check['recommendation'] = __('Manually check your site for JSON-LD structured data.', 'ai-ready-core'); return $check; } $homepage_content = wp_remote_retrieve_body($homepage_response); // Count JSON-LD blocks $jsonld_count = preg_match_all('/]*type=["\']application\/ld\+json["\'][^>]*>(.*?)<\/script>/is', $homepage_content, $matches); if ($jsonld_count > 0) { $check['status'] = 'pass'; /* translators: %d: number of JSON-LD blocks found */ $check['message'] = sprintf(__('Found %d JSON-LD structured data block(s)', 'ai-ready-core'), $jsonld_count); $check['recommendation'] = __('Excellent! Structured data helps AI systems understand your content better.', 'ai-ready-core'); // Try to identify schema types $schema_types = []; foreach ($matches[1] as $json_content) { if (preg_match('/"@type"\s*:\s*"([^"]+)"/', $json_content, $type_match)) { $schema_types[] = $type_match[1]; } } if (!empty($schema_types)) { $check['message'] .= ' (Schema types: ' . implode(', ', array_unique($schema_types)) . ')'; } } else { $check['status'] = 'warning'; $check['message'] = __('No JSON-LD structured data found on homepage', 'ai-ready-core'); $check['recommendation'] = __('Consider adding structured data to help AI systems understand your content.', 'ai-ready-core'); } return $check; } /** * Check sitemap accessibility * * @return array Diagnostic result */ function ai_ready_check_sitemap() { $check = [ 'id' => 'sitemap', 'name' => __('XML Sitemap', 'ai-ready-core'), 'status' => 'unknown', 'message' => '', 'recommendation' => '', 'priority' => 'low' ]; $sitemap_urls = [ home_url('/sitemap.xml'), home_url('/wp-sitemap.xml'), home_url('/sitemap_index.xml') ]; foreach ($sitemap_urls as $sitemap_url) { $response = wp_remote_get($sitemap_url, ['timeout' => 10]); if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) === 200) { $content = wp_remote_retrieve_body($response); if (strpos($content, ' 'content_accessibility', 'name' => __('Content Accessibility', 'ai-ready-core'), 'status' => 'unknown', 'message' => '', 'recommendation' => '', 'priority' => 'low' ]; // Check if site requires login $homepage_response = wp_remote_get(home_url(), ['timeout' => 10]); if (is_wp_error($homepage_response)) { $check['status'] = 'unknown'; $check['message'] = __('Could not check content accessibility', 'ai-ready-core'); return $check; } $content = wp_remote_retrieve_body($homepage_response); // Look for login forms or password protection if (preg_match('/wp-login|login-form|password.*protected/i', $content)) { $check['status'] = 'warning'; $check['message'] = __('Site may require authentication to access content', 'ai-ready-core'); $check['recommendation'] = __('Ensure important content is publicly accessible for AI discovery.', 'ai-ready-core'); return $check; } // Check for actual content vs just navigation $text_content = wp_strip_all_tags($content); $word_count = str_word_count($text_content); if ($word_count < 50) { $check['status'] = 'warning'; /* translators: %d: word count */ $check['message'] = sprintf(__('Homepage has very little text content (%d words)', 'ai-ready-core'), $word_count); $check['recommendation'] = __('Add more descriptive content to help AI systems understand your site.', 'ai-ready-core'); return $check; } $check['status'] = 'pass'; /* translators: %d: word count */ $check['message'] = sprintf(__('Content appears to be publicly accessible (%d words on homepage)', 'ai-ready-core'), $word_count); $check['recommendation'] = __('Good! Your content is accessible to AI systems.', 'ai-ready-core'); return $check; } /** * Check basic SEO elements that help AI understanding * * @return array Diagnostic result */ function ai_ready_check_seo_basics() { $check = [ 'id' => 'seo_basics', 'name' => __('SEO Basics', 'ai-ready-core'), 'status' => 'unknown', 'message' => '', 'recommendation' => '', 'priority' => 'low' ]; $homepage_response = wp_remote_get(home_url(), ['timeout' => 10]); if (is_wp_error($homepage_response)) { $check['status'] = 'unknown'; $check['message'] = __('Could not check SEO basics', 'ai-ready-core'); return $check; } $content = wp_remote_retrieve_body($homepage_response); $issues = []; $good_elements = []; // Check for title tag if (!preg_match('/]*>(.+?)<\/title>/i', $content, $title_match)) { $issues[] = __('Missing title tag', 'ai-ready-core'); } else { $title = trim($title_match[1]); if (strlen($title) < 10) { $issues[] = __('Title tag too short', 'ai-ready-core'); } elseif (strlen($title) > 60) { $issues[] = __('Title tag may be too long', 'ai-ready-core'); } else { $good_elements[] = __('Title tag', 'ai-ready-core'); } } // Check for meta description if (!preg_match('/ 160) { $issues[] = __('Meta description too long', 'ai-ready-core'); } else { $good_elements[] = __('Meta description', 'ai-ready-core'); } } // Check for heading tags if (!preg_match('/]*>/i', $content)) { $issues[] = __('Missing H1 tag', 'ai-ready-core'); } else { $good_elements[] = __('H1 tag', 'ai-ready-core'); } if (!empty($issues)) { $check['status'] = 'warning'; /* translators: %s: comma-separated list of SEO issues */ $check['message'] = sprintf(__('SEO issues found: %s', 'ai-ready-core'), implode(', ', $issues)); if (!empty($good_elements)) { $check['message'] .= ' (Good: ' . implode(', ', $good_elements) . ')'; } $check['recommendation'] = __('Fix SEO basics to help AI systems better understand your content.', 'ai-ready-core'); } else { $check['status'] = 'pass'; /* translators: %s: comma-separated list of good SEO elements */ $check['message'] = sprintf(__('Basic SEO elements are present: %s', 'ai-ready-core'), implode(', ', $good_elements)); $check['recommendation'] = __('Great! Your basic SEO setup helps AI systems understand your content.', 'ai-ready-core'); } return $check; } /** * Get human-readable time since last diagnostics scan * * @return string|false Human-readable time or false if never run */ function ai_ready_get_last_scan_time() { $last_scan = get_option('ai_ready_last_diagnostics_scan'); if (!$last_scan) { return false; } $current_time = current_time('timestamp'); $time_diff = $current_time - $last_scan; // Less than a minute if ($time_diff < 60) { return __('Just now', 'ai-ready-core'); } // Less than an hour if ($time_diff < HOUR_IN_SECONDS) { $minutes = floor($time_diff / 60); return sprintf( /* translators: %d: number of minutes */ _n('%d minute ago', '%d minutes ago', $minutes, 'ai-ready-core'), $minutes ); } // Less than a day if ($time_diff < DAY_IN_SECONDS) { $hours = floor($time_diff / HOUR_IN_SECONDS); return sprintf( /* translators: %d: number of hours */ _n('%d hour ago', '%d hours ago', $hours, 'ai-ready-core'), $hours ); } // Less than a week if ($time_diff < WEEK_IN_SECONDS) { $days = floor($time_diff / DAY_IN_SECONDS); if ($days == 1) { return __('Yesterday', 'ai-ready-core'); } return sprintf( /* translators: %d: number of days */ _n('%d day ago', '%d days ago', $days, 'ai-ready-core'), $days ); } // Less than a month if ($time_diff < MONTH_IN_SECONDS) { $weeks = floor($time_diff / WEEK_IN_SECONDS); return sprintf( /* translators: %d: number of weeks */ _n('%d week ago', '%d weeks ago', $weeks, 'ai-ready-core'), $weeks ); } // More than a month $months = floor($time_diff / MONTH_IN_SECONDS); if ($months < 12) { return sprintf( /* translators: %d: number of months */ _n('%d month ago', '%d months ago', $months, 'ai-ready-core'), $months ); } // More than a year return __('More than a year ago', 'ai-ready-core'); } /** * Check if diagnostics have been run before * * @return bool True if diagnostics have been run at least once */ function ai_ready_has_diagnostics_been_run() { return get_option('ai_ready_last_diagnostics_scan') !== false; } /** * Get diagnostic status summary * * @param array $diagnostics Array of diagnostic results * @return array Summary with counts and overall status */ function ai_ready_get_diagnostic_summary($diagnostics) { $summary = [ 'total' => count($diagnostics), 'pass' => 0, 'warning' => 0, 'fail' => 0, 'info' => 0, 'unknown' => 0, 'overall_status' => 'unknown', 'score' => 0 ]; foreach ($diagnostics as $diagnostic) { switch ($diagnostic['status']) { case 'pass': $summary['pass']++; break; case 'warning': $summary['warning']++; break; case 'fail': $summary['fail']++; break; case 'info': $summary['info']++; break; default: $summary['unknown']++; break; } } // Calculate score (pass = 100%, warning = 50%, info = 75%, fail/unknown = 0%) $summary['score'] = round(($summary['pass'] * 100 + $summary['warning'] * 50 + $summary['info'] * 75) / ($summary['total'] * 100) * 100); // Determine overall status if ($summary['fail'] > 0) { $summary['overall_status'] = 'fail'; } elseif ($summary['warning'] > $summary['pass']) { $summary['overall_status'] = 'warning'; } elseif ($summary['pass'] > 0) { $summary['overall_status'] = 'pass'; } return $summary; }