scan_posts(); $rows = $data['rows']; $site_status_level = $data['site_status_level']; $checklist = $data['checklist']; // 2. Render Site-Level Indicator $status_config = [ 'healthy' => ['icon' => 'yes', 'color' => '#46b450', 'text' => 'Healthy', 'desc' => 'No known issues.'], 'attention' => ['icon' => 'warning', 'color' => '#ffb900', 'text' => 'Attention Needed', 'desc' => 'Some pages rely on global defaults.'], 'action' => ['icon' => 'no', 'color' => '#dc3232', 'text' => 'Action Required', 'desc' => 'Blocking issues detected on some pages.'] ]; $current_status = $status_config[$site_status_level]; ?>

|

30) ? '...' : ''; ?>

= 300 && $code < 400) ? '#ffb900' : '#dc3232'); if ($code === 0) { $code = '-'; $c_color = '#999'; } echo '' . esc_html($code) . ''; ?> post_title); ?>
|
activetwist.com' ); echo wp_kses_post($about_link); ?>
true], 'names'); $recent_posts = get_posts([ 'post_type' => $post_types, 'posts_per_page' => 50, 'post_status' => 'publish' ]); $rows = []; $site_status_level = 'healthy'; // Checklist State (default healthy) $checklist = [ 'meta_desc' => ['label' => 'Meta descriptions present', 'status' => 'healthy'], 'og_image' => ['label' => 'Social images available', 'status' => 'healthy'], 'canonical' => ['label' => 'Canonical URLs set', 'status' => 'healthy'], 'canonical_conflict' => ['label' => 'Canonical Status (Observed)', 'status' => 'healthy'], // New Phase 4.1 'indexability_status' => ['label' => 'Indexability Config', 'status' => 'healthy'], 'http_status' => ['label' => 'HTTP Status (Loopback)', 'status' => 'healthy'], 'robots' => ['label' => 'Robots directives valid', 'status' => 'healthy'], 'jsonld' => ['label' => 'Structured data valid', 'status' => 'healthy'], 'local_business' => ['label' => 'Local Business Schema', 'status' => 'healthy'], 'author_schema' => ['label' => 'Author Schema details', 'status' => 'healthy'], 'breadcrumbs' => ['label' => 'Breadcrumbs enabled', 'status' => 'healthy'], 'xml_sitemap' => ['label' => 'XML Sitemap enabled', 'status' => 'healthy'], 'robots_txt' => ['label' => 'Robots.txt managed', 'status' => 'healthy'], 'llms_txt' => ['label' => 'LLMs.txt enabled', 'status' => 'healthy'], ]; // Global Checks // 1. Local Business if (\ActiveTwist\SEO\Settings\Options::get('local_business_enabled')) { $lb_name = \ActiveTwist\SEO\Settings\Options::get('local_business_name') ?: \ActiveTwist\SEO\Settings\Options::get('org_name'); if (empty($lb_name)) { $checklist['local_business']['status'] = 'blocking'; $site_status_level = 'action'; } } else { $checklist['local_business']['label'] .= ' (Disabled)'; } // 2. XML Sitemap if (!get_option('permalink_structure')) { $checklist['xml_sitemap']['status'] = 'blocking'; $checklist['xml_sitemap']['label'] .= ' (Requires Permalinks)'; $site_status_level = 'action'; } // 3. Robots.txt $robots_mode = \ActiveTwist\SEO\Settings\Options::get('robots_txt_mode', 'auto'); if ($robots_mode === 'custom') { $checklist['robots_txt']['label'] .= ' (Custom)'; } // 4. LLMs.txt // Default considered enabled (auto). foreach ($recent_posts as $post) { $row = [ 'post' => $post, 'status_level' => 'healthy', 'issues' => [] ]; // --- Observability Inspection (Phase 4.1) --- // Perform loopback request to specific post permalink $permalink = get_permalink($post->ID); $inspection = \ActiveTwist\SEO\Utils\Inspector::inspect_url($permalink); // Check Inspection Errors (e.g. timeout, connection refused) // Check Inspection Errors (e.g. timeout, connection refused) if (!empty($inspection['error'])) { $row['issues'][] = ['type' => 'warning', 'message' => 'Inspection Failed: ' . $inspection['error']]; if ($row['status_level'] !== 'action') { $row['status_level'] = 'attention'; } if ($checklist['http_status']['status'] !== 'blocking') $checklist['http_status']['status'] = 'warning'; $row['http_code'] = 0; $row['indexability'] = ['status' => 'unknown', 'reason' => 'Scan Failed']; } else { // Canonical Conflict Detection $expected_canonical = Helpers::get_canonical_url($post); $observed_canonicals = $inspection['canonicals']; if (count($observed_canonicals) > 1) { $row['issues'][] = ['type' => 'blocking', 'message' => 'Multiple Configured Canonicals']; $row['status_level'] = 'action'; $checklist['canonical_conflict']['status'] = 'blocking'; } elseif (count($observed_canonicals) === 1) { $observed = $observed_canonicals[0]; if ($observed !== $expected_canonical) { $row['issues'][] = ['type' => 'warning', 'message' => 'Canonical Override Detected']; // Non-blocking but noteworthy if ($row['status_level'] !== 'action') { $row['status_level'] = 'attention'; } if ($checklist['canonical_conflict']['status'] !== 'blocking') { $checklist['canonical_conflict']['status'] = 'warning'; } } } else { // Count 0 $row['issues'][] = ['type' => 'blocking', 'message' => 'Missing Canonical Tag (Rendered)']; $row['status_level'] = 'action'; $checklist['canonical_conflict']['status'] = 'blocking'; } // Indexability Evaluation (Phase 4.1) $indexability = \ActiveTwist\SEO\Utils\IndexabilityEvaluator::evaluate($inspection, $permalink); $row['indexability'] = $indexability; $row['http_code'] = $inspection['code']; // Explicit capture // HTTP Status Checklist Logic if ($inspection['code'] !== 200) { if ($inspection['code'] >= 300 && $inspection['code'] < 400) { if ($checklist['http_status']['status'] !== 'blocking') $checklist['http_status']['status'] = 'warning'; } else { // 404, 500 $checklist['http_status']['status'] = 'blocking'; $row['status_level'] = 'action'; } } if ($indexability['status'] === \ActiveTwist\SEO\Utils\IndexabilityEvaluator::STATUS_INDETERMINATE) { $row['issues'][] = ['type' => 'blocking', 'message' => 'Indexability Conflict: ' . $indexability['reason']]; $row['status_level'] = 'action'; $checklist['indexability_status']['status'] = 'blocking'; } elseif ($indexability['status'] === \ActiveTwist\SEO\Utils\IndexabilityEvaluator::STATUS_BLOCKED) { // Blocked by robots.txt $row['issues'][] = ['type' => 'warning', 'message' => 'Blocked from Indexing: ' . $indexability['reason']]; } elseif ($indexability['status'] === \ActiveTwist\SEO\Utils\IndexabilityEvaluator::STATUS_NOINDEX) { // Check if this is an error or intentional if ($inspection['code'] !== 200) { $row['issues'][] = ['type' => 'blocking', 'message' => 'HTTP ' . $inspection['code'] . ': ' . $indexability['reason']]; $row['status_level'] = 'action'; } else { $row['issues'][] = ['type' => 'warning', 'message' => 'Not Indexable: ' . $indexability['reason']]; } } } // --- Check Meta Description --- $desc_data = Helpers::get_seo_description_with_source($post); if ($desc_data['source'] === Helpers::SOURCE_MISSING) { $row['issues'][] = ['type' => 'blocking', 'message' => 'Missing Meta Description']; $row['status_level'] = 'action'; $checklist['meta_desc']['status'] = 'blocking'; } elseif ($desc_data['source'] === Helpers::SOURCE_INHERITED) { $row['issues'][] = ['type' => 'warning', 'message' => 'Uses global defaults']; if ($row['status_level'] !== 'action') { $row['status_level'] = 'attention'; } if ($checklist['meta_desc']['status'] !== 'blocking') { $checklist['meta_desc']['status'] = 'warning'; } } // --- Check Social Image --- $img_data = Helpers::get_og_image_with_source($post); if ($img_data['source'] === Helpers::SOURCE_MISSING) { $row['issues'][] = ['type' => 'blocking', 'message' => 'Missing Social Image']; $row['status_level'] = 'action'; $checklist['og_image']['status'] = 'blocking'; } elseif ($img_data['source'] === Helpers::SOURCE_INHERITED) { $row['issues'][] = ['type' => 'warning', 'message' => 'Uses global defaults']; if ($row['status_level'] !== 'action') { $row['status_level'] = 'attention'; } if ($checklist['og_image']['status'] !== 'blocking') { $checklist['og_image']['status'] = 'warning'; } } // --- Check JSON-LD --- $meta = Helpers::get_post_meta($post->ID); $mode = !empty($meta['jsonld_mode']) ? $meta['jsonld_mode'] : 'auto'; if ($mode === 'custom') { if (empty($meta['jsonld_custom'])) { $row['issues'][] = ['type' => 'blocking', 'message' => 'JSON-LD Custom Mode Empty']; $row['status_level'] = 'action'; $checklist['jsonld']['status'] = 'blocking'; } elseif (json_decode($meta['jsonld_custom']) === null) { $row['issues'][] = ['type' => 'blocking', 'message' => 'Invalid JSON-LD Syntax']; $row['status_level'] = 'action'; $checklist['jsonld']['status'] = 'blocking'; } } // Breadcrumbs check if ($mode === 'none') { $checklist['breadcrumbs']['status'] = 'blocking'; $checklist['breadcrumbs']['label'] = 'Breadcrumbs (Disabled)'; } elseif ($mode === 'custom') { $checklist['breadcrumbs']['label'] = 'Breadcrumbs (Custom)'; } // --- Check Author Schema --- $author_job = get_the_author_meta('at_seo_job_title', $post->post_author); if (empty($author_job)) { $row['issues'][] = ['type' => 'warning', 'message' => 'Author missing Job Title']; if ($row['status_level'] !== 'action') { $row['status_level'] = 'attention'; } if ($checklist['author_schema']['status'] !== 'blocking') { $checklist['author_schema']['status'] = 'warning'; } } // Update Site Status based on Row Status if ($row['status_level'] === 'action') { $site_status_level = 'action'; } elseif ($row['status_level'] === 'attention' && $site_status_level !== 'action') { $site_status_level = 'attention'; } if (!empty($row['issues'])) { $rows[] = $row; } } return [ 'rows' => $rows, 'site_status_level' => $site_status_level, 'checklist' => $checklist ]; } public function handle_manual_refresh() { check_admin_referer('at_seo_manual_refresh'); // Logic to trigger re-scan // For now, we update the timestamp and state. // In a real async worker system, this would queue a job. // Since our scan is deterministic and mostly on-the-fly or triggered here, // we can just update the timestamp to indicate "freshness" of the manual action. // The actual "scan" happens when rendering the page (scan_posts) currently, // unless we move to a background process model. // Phase 5 requirements say "Clears derived data caches, Re-runs discovery". $options = Options::get_all(); $options['last_scan_timestamp'] = current_time('timestamp'); $options['scan_state'] = 'idle'; // It runs essentially instantly in this MVP architecture update_option(Options::OPTION_NAME, $options); // Redirect back wp_safe_redirect(admin_url('admin.php?page=active-twist-seo-status&refreshed=1')); exit; } }