settings = $settings; $this->llms = $llms; $this->forms = $forms; $this->tools = $tools; $this->accessibility = $accessibility; $this->layout = $layout; $this->woocommerce = $woocommerce; $this->integrations = $integrations; } /** * Run scan and return score plus issues. * * @return array */ public function scan() { $options = $this->settings->get_options(); $checks = array(); $sample_count = $this->count_agentic_public_items(); $detected_forms = $this->forms ? $this->forms->detect_forms( 40 ) : array(); $form_summary = $this->forms ? $this->forms->summarize_detected_forms( $detected_forms ) : array( 'total' => 0, 'required_without_name' => 0 ); $tools_summary = $this->tools ? $this->tools->summarize_tools() : array( 'enabled' => false, 'count' => 0, 'base_url' => '', 'registry_url' => '', 'tools' => array() ); $accessibility_scan = $this->accessibility ? $this->accessibility->scan( isset( $options['accessibility_scan_limit'] ) ? absint( $options['accessibility_scan_limit'] ) : 25 ) : array( 'enabled' => false, 'pages_scanned' => 0, 'metrics' => array(), 'issues' => array(), 'issue_count' => 0, 'critical_count' => 0, 'warning_count' => 0, 'score' => 0 ); $layout_scan = $this->layout ? $this->layout->scan( isset( $options['layout_scan_limit'] ) ? absint( $options['layout_scan_limit'] ) : 25 ) : array( 'enabled' => false, 'helper_enabled' => false, 'pages_scanned' => 0, 'metrics' => array(), 'issues' => array(), 'issue_count' => 0, 'critical_count' => 0, 'warning_count' => 0, 'score' => 0 ); $ecommerce_scan = $this->woocommerce ? $this->woocommerce->scan( isset( $options['ecommerce_scan_limit'] ) ? absint( $options['ecommerce_scan_limit'] ) : 25 ) : array( 'active' => false, 'enabled' => false, 'product_tools_enabled' => false, 'products_scanned' => 0, 'published_products' => 0, 'product_categories' => 0, 'products_missing_summary' => 0, 'products_missing_image_alt' => 0, 'out_of_stock_products' => 0, 'issues' => array(), 'issue_count' => 0, 'critical_count' => 0, 'warning_count' => 0, 'score' => 0, 'endpoints' => array() ); $integration_scan = $this->integrations ? $this->integrations->scan( isset( $options['integration_scan_limit'] ) ? absint( $options['integration_scan_limit'] ) : 25 ) : array( 'enabled' => false, 'detected_sources' => array(), 'enabled_sources' => array(), 'items_scanned' => 0, 'items_with_seo_title' => 0, 'items_with_description' => 0, 'items_with_canonical' => 0, 'items_with_focus_keyword' => 0, 'items_with_og_image' => 0, 'items_noindex' => 0, 'source_breakdown' => array(), 'issues' => array(), 'issue_count' => 0, 'critical_count' => 0, 'warning_count' => 0, 'score' => 0, 'preview' => array(), 'sources' => array() ); $ecommerce_relevant = ! empty( $ecommerce_scan['active'] ) || 'ecommerce' === $options['site_type'] || in_array( 'product', (array) $options['include_post_types'], true ); $this->add_check( $checks, 'llms_enabled', __( 'llms.txt endpoint enabled', 'agizoai-agentic-browsing-optimizer' ), (bool) $options['enabled'], 20, 'discoverability', __( 'Agents do not have a short public file explaining the site.', 'agizoai-agentic-browsing-optimizer' ), __( 'A root-level llms.txt gives LLM crawlers and browser assistants a fast site summary.', 'agizoai-agentic-browsing-optimizer' ), __( 'Enable /llms.txt in Settings.', 'agizoai-agentic-browsing-optimizer' ), 'settings' ); $this->add_check( $checks, 'llms_full_enabled', __( 'llms-full.txt endpoint enabled', 'agizoai-agentic-browsing-optimizer' ), (bool) $options['full_enabled'], 5, 'discoverability', __( 'Only the short agent file is active.', 'agizoai-agentic-browsing-optimizer' ), __( 'A fuller public index can help agents understand more of the site structure.', 'agizoai-agentic-browsing-optimizer' ), __( 'Enable /llms-full.txt when you want a deeper public content index.', 'agizoai-agentic-browsing-optimizer' ), 'settings' ); $this->add_check( $checks, 'post_types_selected', __( 'Public content types selected', 'agizoai-agentic-browsing-optimizer' ), ! empty( $options['include_post_types'] ), 10, 'discoverability', __( 'No public post types are selected for agent discovery.', 'agizoai-agentic-browsing-optimizer' ), __( 'Agents need canonical public pages, posts or products to navigate the site correctly.', 'agizoai-agentic-browsing-optimizer' ), __( 'Select at least one public post type.', 'agizoai-agentic-browsing-optimizer' ), 'settings' ); $this->add_check( $checks, 'taxonomies_selected', __( 'Public sections selected', 'agizoai-agentic-browsing-optimizer' ), ! empty( $options['include_taxonomies'] ), 5, 'discoverability', __( 'No categories or taxonomies are selected.', 'agizoai-agentic-browsing-optimizer' ), __( 'Public sections help agents understand topical clusters and site navigation.', 'agizoai-agentic-browsing-optimizer' ), __( 'Include categories, tags or relevant public taxonomies.', 'agizoai-agentic-browsing-optimizer' ), 'settings' ); $this->add_check( $checks, 'content_available', __( 'Public content available', 'agizoai-agentic-browsing-optimizer' ), $sample_count > 0, 10, 'discoverability', __( 'The selected content types do not currently return public items.', 'agizoai-agentic-browsing-optimizer' ), __( 'An agent file is only useful when it contains real public content links.', 'agizoai-agentic-browsing-optimizer' ), __( 'Publish public content or adjust the selected content types.', 'agizoai-agentic-browsing-optimizer' ), 'settings' ); $this->add_check( $checks, 'robots_reference', __( 'robots.txt reference enabled', 'agizoai-agentic-browsing-optimizer' ), (bool) $options['add_robots_reference'], 5, 'discoverability', __( 'robots.txt does not reference the agent files.', 'agizoai-agentic-browsing-optimizer' ), __( 'A robots.txt reference can make the files easier to discover.', 'agizoai-agentic-browsing-optimizer' ), __( 'Enable the robots.txt reference option.', 'agizoai-agentic-browsing-optimizer' ), 'settings' ); $this->add_check( $checks, 'site_purpose', __( 'Site purpose provided', 'agizoai-agentic-browsing-optimizer' ), '' !== trim( (string) $options['site_purpose'] ), 10, 'clarity', __( 'The agent file has no clear purpose statement.', 'agizoai-agentic-browsing-optimizer' ), __( 'Agents need a concise explanation of what the website is for and who it serves.', 'agizoai-agentic-browsing-optimizer' ), __( 'Add a short site purpose in the setup wizard or settings.', 'agizoai-agentic-browsing-optimizer' ), 'setup' ); $this->add_check( $checks, 'custom_guidance', __( 'Custom AI-agent guidance provided', 'agizoai-agentic-browsing-optimizer' ), '' !== trim( (string) $options['custom_intro'] ), 5, 'clarity', __( 'No custom guidance is provided for agents.', 'agizoai-agentic-browsing-optimizer' ), __( 'Guidance can clarify canonical URLs, public-only use, disclaimers and preferred source pages.', 'agizoai-agentic-browsing-optimizer' ), __( 'Add optional guidance for AI agents.', 'agizoai-agentic-browsing-optimizer' ), 'setup' ); $this->add_check( $checks, 'wizard_completed', __( 'First-run setup completed', 'agizoai-agentic-browsing-optimizer' ), (bool) $options['wizard_completed'], 5, 'clarity', __( 'The setup wizard has not been completed.', 'agizoai-agentic-browsing-optimizer' ), __( 'The wizard applies safe defaults and helps create a useful agent-facing summary quickly.', 'agizoai-agentic-browsing-optimizer' ), __( 'Complete the setup wizard.', 'agizoai-agentic-browsing-optimizer' ), 'setup' ); $this->add_check( $checks, 'respect_noindex', __( 'Noindex content respected', 'agizoai-agentic-browsing-optimizer' ), (bool) $options['respect_noindex'], 10, 'safety', __( 'Noindex content may appear in agent-facing files.', 'agizoai-agentic-browsing-optimizer' ), __( 'Content intentionally hidden from search engines should normally stay out of LLM-facing files.', 'agizoai-agentic-browsing-optimizer' ), __( 'Enable noindex detection.', 'agizoai-agentic-browsing-optimizer' ), 'settings' ); $this->add_check( $checks, 'private_excluded', __( 'Private content excluded', 'agizoai-agentic-browsing-optimizer' ), ! (bool) $options['include_private'], 10, 'safety', __( 'Private content inclusion is enabled.', 'agizoai-agentic-browsing-optimizer' ), __( 'Agent-facing discovery files should normally contain public content only.', 'agizoai-agentic-browsing-optimizer' ), __( 'Keep private content disabled unless you have a specific controlled use case.', 'agizoai-agentic-browsing-optimizer' ), 'settings' ); $this->add_check( $checks, 'safe_mode', __( 'Safe read-only mode enabled', 'agizoai-agentic-browsing-optimizer' ), (bool) $options['safe_mode'], 5, 'safety', __( 'Safe mode is disabled.', 'agizoai-agentic-browsing-optimizer' ), __( 'The plugin exposes only safe public read-only tools by default and should not expose action tools.', 'agizoai-agentic-browsing-optimizer' ), __( 'Enable safe read-only mode.', 'agizoai-agentic-browsing-optimizer' ), 'settings' ); $this->add_check( $checks, 'webmcp_enabled', __( 'WebMCP support enabled', 'agizoai-agentic-browsing-optimizer' ), (bool) $options['webmcp_enabled'], 10, 'webmcp', __( 'Declarative WebMCP support is disabled.', 'agizoai-agentic-browsing-optimizer' ), __( 'WebMCP-style metadata helps browser agents understand forms and public interaction surfaces.', 'agizoai-agentic-browsing-optimizer' ), __( 'Enable WebMCP support in the Forms tab.', 'agizoai-agentic-browsing-optimizer' ), 'forms' ); $this->add_check( $checks, 'webmcp_form_metadata_enabled', __( 'Declarative form metadata enabled', 'agizoai-agentic-browsing-optimizer' ), (bool) $options['webmcp_form_metadata_enabled'], 10, 'webmcp', __( 'Detected forms are not being enhanced with tool metadata.', 'agizoai-agentic-browsing-optimizer' ), __( 'Form-level tool names and descriptions help agents understand what each form is for.', 'agizoai-agentic-browsing-optimizer' ), __( 'Enable declarative form metadata in the Forms tab.', 'agizoai-agentic-browsing-optimizer' ), 'forms' ); $this->add_check( $checks, 'webmcp_search_form_enabled', __( 'Search form metadata enabled', 'agizoai-agentic-browsing-optimizer' ), (bool) $options['webmcp_search_form_enabled'], 5, 'webmcp', __( 'The native search form is not explicitly described for agents.', 'agizoai-agentic-browsing-optimizer' ), __( 'Search is usually the safest first agent-facing tool because it is public and read-only.', 'agizoai-agentic-browsing-optimizer' ), __( 'Enable search form enhancement in the Forms tab.', 'agizoai-agentic-browsing-optimizer' ), 'forms' ); $this->add_check( $checks, 'detected_forms_ready', __( 'Detected forms have metadata path', 'agizoai-agentic-browsing-optimizer' ), 0 === absint( $form_summary['total'] ) || ( (bool) $options['webmcp_enabled'] && (bool) $options['webmcp_form_metadata_enabled'] ), 5, 'webmcp', __( 'One or more public forms were detected but metadata enhancement is not fully active.', 'agizoai-agentic-browsing-optimizer' ), __( 'Agent-facing form metadata reduces ambiguity around contact, search and lead forms.', 'agizoai-agentic-browsing-optimizer' ), __( 'Review detected forms and keep form metadata enabled.', 'agizoai-agentic-browsing-optimizer' ), 'forms' ); $this->add_check( $checks, 'required_fields_named', __( 'Required form fields have names', 'agizoai-agentic-browsing-optimizer' ), 0 === absint( $form_summary['required_without_name'] ), 5, 'webmcp', __( 'A required form field appears to be missing a name attribute.', 'agizoai-agentic-browsing-optimizer' ), __( 'Agents and browsers need stable field names to submit forms correctly.', 'agizoai-agentic-browsing-optimizer' ), __( 'Fix the form markup or form plugin field configuration.', 'agizoai-agentic-browsing-optimizer' ), 'forms' ); $this->add_check( $checks, 'agent_tools_enabled', __( 'Public read-only agent tools enabled', 'agizoai-agentic-browsing-optimizer' ), ! empty( $options['agent_tools_enabled'] ) && ! empty( $options['agent_tools_rest_enabled'] ), 10, 'tools', __( 'Public agent tool endpoints are disabled.', 'agizoai-agentic-browsing-optimizer' ), __( 'Read-only tools let browser assistants understand public site capabilities without changing site state.', 'agizoai-agentic-browsing-optimizer' ), __( 'Enable public read-only tools in the Agent Tools tab.', 'agizoai-agentic-browsing-optimizer' ), 'tools' ); $this->add_check( $checks, 'agent_tools_registry', __( 'Tool registry available', 'agizoai-agentic-browsing-optimizer' ), absint( $tools_summary['count'] ) > 0, 10, 'tools', __( 'No active public tools are registered.', 'agizoai-agentic-browsing-optimizer' ), __( 'A tool registry gives agents a clear list of safe public capabilities and their endpoints.', 'agizoai-agentic-browsing-optimizer' ), __( 'Enable at least one read-only tool such as site info, search or recent content.', 'agizoai-agentic-browsing-optimizer' ), 'tools' ); $this->add_check( $checks, 'ai_profile_enabled', __( 'Machine-readable ai.json profile enabled', 'agizoai-agentic-browsing-optimizer' ), ! empty( $options['ai_profile_enabled'] ), 5, 'agent_tools', __( 'The site does not publish the compact ai.json discovery profile.', 'agizoai-agentic-browsing-optimizer' ), __( 'A machine-readable profile gives agents one compact description of discovery endpoints, capabilities and safety posture.', 'agizoai-agentic-browsing-optimizer' ), __( 'Enable the ai.json profile in Agent Tools.', 'agizoai-agentic-browsing-optimizer' ), 'tools' ); $this->add_check( $checks, 'abilities_enabled', __( 'Native WordPress abilities enabled', 'agizoai-agentic-browsing-optimizer' ), version_compare( get_bloginfo( 'version' ), '6.9', '<' ) || ! empty( $options['abilities_enabled'] ), 5, 'agent_tools', __( 'Native WordPress Abilities API integration is disabled.', 'agizoai-agentic-browsing-optimizer' ), __( 'WordPress 6.9+ abilities provide typed, discoverable functions that compatible REST and MCP clients can invoke.', 'agizoai-agentic-browsing-optimizer' ), __( 'Enable native abilities in Agent Tools when running WordPress 6.9 or newer.', 'agizoai-agentic-browsing-optimizer' ), 'tools' ); $this->add_check( $checks, 'agent_tools_safe_mode', __( 'Agent tools remain read-only', 'agizoai-agentic-browsing-optimizer' ), ! empty( $options['safe_mode'] ), 5, 'tools', __( 'Safe mode is disabled while public tool endpoints exist.', 'agizoai-agentic-browsing-optimizer' ), __( 'Agent-facing endpoints should stay read-only unless a future action tool has explicit confirmation and security controls.', 'agizoai-agentic-browsing-optimizer' ), __( 'Keep safe read-only mode enabled.', 'agizoai-agentic-browsing-optimizer' ), 'settings' ); $this->add_check( $checks, 'accessibility_scanner_enabled', __( 'Accessibility-for-agents scanner enabled', 'agizoai-agentic-browsing-optimizer' ), ! empty( $options['accessibility_scanner_enabled'] ), 5, 'accessibility', __( 'Accessibility scanning is disabled.', 'agizoai-agentic-browsing-optimizer' ), __( 'Agents use accessible names, labels and document structure to understand page interactions.', 'agizoai-agentic-browsing-optimizer' ), __( 'Enable the accessibility scanner in the Accessibility tab.', 'agizoai-agentic-browsing-optimizer' ), 'accessibility' ); $this->add_check( $checks, 'accessibility_pages_scanned', __( 'Public pages scanned for agent accessibility', 'agizoai-agentic-browsing-optimizer' ), absint( $accessibility_scan['pages_scanned'] ) > 0, 5, 'accessibility', __( 'No public pages were scanned for accessibility issues.', 'agizoai-agentic-browsing-optimizer' ), __( 'A real content sample is needed to find labels, headings, image alt text and vague links.', 'agizoai-agentic-browsing-optimizer' ), __( 'Publish public content or choose post types in Settings.', 'agizoai-agentic-browsing-optimizer' ), 'accessibility' ); $this->add_check( $checks, 'accessibility_no_critical', __( 'No critical accessibility-for-agents issues detected', 'agizoai-agentic-browsing-optimizer' ), 0 === absint( $accessibility_scan['critical_count'] ), 10, 'accessibility', __( 'Critical accessible-name or form-label issues were detected.', 'agizoai-agentic-browsing-optimizer' ), __( 'Agents need labelled controls and stable form fields to interact reliably.', 'agizoai-agentic-browsing-optimizer' ), __( 'Review the Accessibility tab and fix missing labels, field names or empty links.', 'agizoai-agentic-browsing-optimizer' ), 'accessibility' ); $this->add_check( $checks, 'accessibility_image_alt', __( 'Images have useful alt text', 'agizoai-agentic-browsing-optimizer' ), 0 === absint( isset( $accessibility_scan['metrics']['images_missing_alt'] ) ? $accessibility_scan['metrics']['images_missing_alt'] : 0 ), 5, 'accessibility', __( 'One or more images are missing useful alt text.', 'agizoai-agentic-browsing-optimizer' ), __( 'Alt text helps users and agents understand visual content without relying on pixels alone.', 'agizoai-agentic-browsing-optimizer' ), __( 'Add concise alt text to meaningful images.', 'agizoai-agentic-browsing-optimizer' ), 'accessibility' ); $this->add_check( $checks, 'accessibility_heading_structure', __( 'Heading structure is agent-friendly', 'agizoai-agentic-browsing-optimizer' ), 0 === absint( isset( $accessibility_scan['metrics']['heading_level_skips'] ) ? $accessibility_scan['metrics']['heading_level_skips'] : 0 ), 5, 'accessibility', __( 'One or more pages skip heading levels.', 'agizoai-agentic-browsing-optimizer' ), __( 'A clean heading outline helps agents extract page sections accurately.', 'agizoai-agentic-browsing-optimizer' ), __( 'Use sequential headings such as H2, then H3, then H4.', 'agizoai-agentic-browsing-optimizer' ), 'accessibility' ); $this->add_check( $checks, 'layout_scanner_enabled', __( 'Layout stability scanner enabled', 'agizoai-agentic-browsing-optimizer' ), ! empty( $options['layout_scanner_enabled'] ), 5, 'layout', __( 'Layout stability scanning is disabled.', 'agizoai-agentic-browsing-optimizer' ), __( 'Agents may rely on element positions, screenshots or visible click targets.', 'agizoai-agentic-browsing-optimizer' ), __( 'Enable the layout stability scanner in the Layout Stability tab.', 'agizoai-agentic-browsing-optimizer' ), 'layout' ); $this->add_check( $checks, 'layout_pages_scanned', __( 'Public pages scanned for layout risks', 'agizoai-agentic-browsing-optimizer' ), absint( $layout_scan['pages_scanned'] ) > 0, 5, 'layout', __( 'No public pages were scanned for layout stability risks.', 'agizoai-agentic-browsing-optimizer' ), __( 'A content sample is needed to find images, embeds and ads that can shift page layout.', 'agizoai-agentic-browsing-optimizer' ), __( 'Publish public content or choose post types in Settings.', 'agizoai-agentic-browsing-optimizer' ), 'layout' ); $this->add_check( $checks, 'layout_images_dimensions', __( 'Images and embeds reserve space', 'agizoai-agentic-browsing-optimizer' ), 0 === ( absint( isset( $layout_scan['metrics']['images_missing_dimensions'] ) ? $layout_scan['metrics']['images_missing_dimensions'] : 0 ) + absint( isset( $layout_scan['metrics']['iframes_missing_dimensions'] ) ? $layout_scan['metrics']['iframes_missing_dimensions'] : 0 ) ), 10, 'layout', __( 'One or more images or embeds do not reserve visual space.', 'agizoai-agentic-browsing-optimizer' ), __( 'Late-loading media can move links, buttons or forms after an agent has located them.', 'agizoai-agentic-browsing-optimizer' ), __( 'Add width/height attributes, aspect-ratio CSS or fixed containers.', 'agizoai-agentic-browsing-optimizer' ), 'layout' ); $this->add_check( $checks, 'layout_ad_slots_reserved', __( 'Ad slots reserve space', 'agizoai-agentic-browsing-optimizer' ), 0 === absint( isset( $layout_scan['metrics']['ad_slots_without_reserved_space'] ) ? $layout_scan['metrics']['ad_slots_without_reserved_space'] : 0 ), 10, 'layout', __( 'One or more likely ad slots have no reserved space.', 'agizoai-agentic-browsing-optimizer' ), __( 'Ad injection can cause large layout shifts that break coordinate-based interactions.', 'agizoai-agentic-browsing-optimizer' ), __( 'Enable the ad-slot helper or add fixed/min-height containers to your ad slots.', 'agizoai-agentic-browsing-optimizer' ), 'layout' ); $this->add_check( $checks, 'layout_helper_if_ads', __( 'Ad-slot helper available when needed', 'agizoai-agentic-browsing-optimizer' ), 0 === absint( isset( $layout_scan['metrics']['ad_slots_without_reserved_space'] ) ? $layout_scan['metrics']['ad_slots_without_reserved_space'] : 0 ) || ! empty( $layout_scan['helper_enabled'] ), 5, 'layout', __( 'Ad-slot layout risks were detected while the helper CSS is disabled.', 'agizoai-agentic-browsing-optimizer' ), __( 'The helper can reserve common ad slots to reduce content jumps on ad-heavy pages.', 'agizoai-agentic-browsing-optimizer' ), __( 'Enable the helper CSS in the Layout Stability tab or reserve slots in your theme.', 'agizoai-agentic-browsing-optimizer' ), 'layout' ); if ( $ecommerce_relevant ) { $this->add_check( $checks, 'ecommerce_woocommerce_active', __( 'WooCommerce is active for shop readiness', 'agizoai-agentic-browsing-optimizer' ), ! empty( $ecommerce_scan['active'] ), 10, 'ecommerce', __( 'The site is configured like a shop, but WooCommerce product APIs are not available.', 'agizoai-agentic-browsing-optimizer' ), __( 'Product-focused agent tools need WooCommerce active to expose safe public product summaries.', 'agizoai-agentic-browsing-optimizer' ), __( 'Activate WooCommerce or remove product/shop exposure from agent settings.', 'agizoai-agentic-browsing-optimizer' ), 'ecommerce' ); $this->add_check( $checks, 'ecommerce_scanner_enabled', __( 'WooCommerce readiness scanner enabled', 'agizoai-agentic-browsing-optimizer' ), ! empty( $options['ecommerce_scanner_enabled'] ), 5, 'ecommerce', __( 'WooCommerce readiness scanning is disabled.', 'agizoai-agentic-browsing-optimizer' ), __( 'Scanning products helps identify missing summaries, image alt text and category structure problems.', 'agizoai-agentic-browsing-optimizer' ), __( 'Enable WooCommerce readiness scanning in the WooCommerce tab.', 'agizoai-agentic-browsing-optimizer' ), 'ecommerce' ); $this->add_check( $checks, 'ecommerce_products_public', __( 'Published products available', 'agizoai-agentic-browsing-optimizer' ), absint( $ecommerce_scan['published_products'] ) > 0, 10, 'ecommerce', __( 'No published WooCommerce products were detected.', 'agizoai-agentic-browsing-optimizer' ), __( 'Agent product tools need published public products to return useful results.', 'agizoai-agentic-browsing-optimizer' ), __( 'Publish products or disable shop-specific agent exposure.', 'agizoai-agentic-browsing-optimizer' ), 'ecommerce' ); $this->add_check( $checks, 'ecommerce_product_tools_enabled', __( 'Read-only product tools enabled', 'agizoai-agentic-browsing-optimizer' ), ! empty( $options['ecommerce_product_tools_enabled'] ) && ! empty( $options['agent_tools_enabled'] ) && ! empty( $options['agent_tools_rest_enabled'] ), 10, 'ecommerce', __( 'Public read-only product tools are disabled.', 'agizoai-agentic-browsing-optimizer' ), __( 'Safe product tools let agents search products and read product details without touching cart, checkout, orders or customers.', 'agizoai-agentic-browsing-optimizer' ), __( 'Enable product search/detail/category tools in the WooCommerce tab.', 'agizoai-agentic-browsing-optimizer' ), 'ecommerce' ); $this->add_check( $checks, 'ecommerce_product_summaries', __( 'Products have public summaries', 'agizoai-agentic-browsing-optimizer' ), 0 === absint( $ecommerce_scan['products_missing_summary'] ), 5, 'ecommerce', __( 'One or more products are missing useful descriptions.', 'agizoai-agentic-browsing-optimizer' ), __( 'Agents need short, accurate product descriptions to answer shopping questions without guessing.', 'agizoai-agentic-browsing-optimizer' ), __( 'Add product short descriptions or descriptions for the flagged items.', 'agizoai-agentic-browsing-optimizer' ), 'ecommerce' ); $this->add_check( $checks, 'ecommerce_product_image_alt', __( 'Product images have alt text', 'agizoai-agentic-browsing-optimizer' ), 0 === absint( $ecommerce_scan['products_missing_image_alt'] ), 5, 'ecommerce', __( 'One or more product images are missing alt text.', 'agizoai-agentic-browsing-optimizer' ), __( 'Alt text helps agents and assistive technologies understand product visuals.', 'agizoai-agentic-browsing-optimizer' ), __( 'Add descriptive alt text to product featured images.', 'agizoai-agentic-browsing-optimizer' ), 'ecommerce' ); } $this->add_check( $checks, 'integrations_enabled', __( 'SEO metadata integrations enabled', 'agizoai-agentic-browsing-optimizer' ), ! empty( $options['integrations_enabled'] ), 10, 'integrations', __( 'SEO metadata extraction is disabled.', 'agizoai-agentic-browsing-optimizer' ), __( 'SEO titles, descriptions, canonicals and social images improve what agents see in public summaries.', 'agizoai-agentic-browsing-optimizer' ), __( 'Enable SEO integrations in the Integrations tab.', 'agizoai-agentic-browsing-optimizer' ), 'integrations' ); $this->add_check( $checks, 'integrations_enabled_sources', __( 'At least one metadata source enabled', 'agizoai-agentic-browsing-optimizer' ), ! empty( $integration_scan['enabled_sources'] ), 10, 'integrations', __( 'No metadata source is currently enabled and detected.', 'agizoai-agentic-browsing-optimizer' ), __( 'Agents need a reliable source of public titles, summaries and canonical URLs.', 'agizoai-agentic-browsing-optimizer' ), __( 'Enable WordPress Core fallback or an installed SEO plugin integration.', 'agizoai-agentic-browsing-optimizer' ), 'integrations' ); $this->add_check( $checks, 'integrations_content_scanned', __( 'Public content scanned for metadata', 'agizoai-agentic-browsing-optimizer' ), absint( $integration_scan['items_scanned'] ) > 0, 5, 'integrations', __( 'No public content was scanned for SEO metadata.', 'agizoai-agentic-browsing-optimizer' ), __( 'A sample is needed to confirm metadata coverage for agent-facing summaries.', 'agizoai-agentic-browsing-optimizer' ), __( 'Publish content or adjust selected public post types.', 'agizoai-agentic-browsing-optimizer' ), 'integrations' ); $this->add_check( $checks, 'integrations_meta_descriptions', __( 'Public items have agent-ready descriptions', 'agizoai-agentic-browsing-optimizer' ), absint( $integration_scan['items_scanned'] ) === 0 || absint( $integration_scan['items_with_description'] ) >= max( 1, (int) floor( absint( $integration_scan['items_scanned'] ) * 0.7 ) ), 10, 'integrations', __( 'Several public items are missing SEO descriptions or useful excerpts.', 'agizoai-agentic-browsing-optimizer' ), __( 'Descriptions help agents summarise pages without guessing from long theme HTML.', 'agizoai-agentic-browsing-optimizer' ), __( 'Add SEO meta descriptions or excerpts to the flagged items.', 'agizoai-agentic-browsing-optimizer' ), 'integrations' ); $this->add_check( $checks, 'integrations_tools_expose_metadata', __( 'Agent tools expose SEO metadata', 'agizoai-agentic-browsing-optimizer' ), ! empty( $options['integration_expose_seo_metadata_in_tools'] ), 5, 'integrations', __( 'Public content tools are not adding SEO metadata fields.', 'agizoai-agentic-browsing-optimizer' ), __( 'Metadata fields give agents canonical URLs, descriptions and robot hints through the REST tools.', 'agizoai-agentic-browsing-optimizer' ), __( 'Enable SEO metadata in public agent tools.', 'agizoai-agentic-browsing-optimizer' ), 'integrations' ); $this->add_check( $checks, 'integrations_llms_uses_descriptions', __( 'llms.txt can use SEO descriptions', 'agizoai-agentic-browsing-optimizer' ), ! empty( $options['integration_use_seo_descriptions_in_llms'] ), 5, 'integrations', __( 'The agent files are not prioritising SEO descriptions.', 'agizoai-agentic-browsing-optimizer' ), __( 'Good SEO descriptions are usually the clearest compact summaries for agents.', 'agizoai-agentic-browsing-optimizer' ), __( 'Enable SEO descriptions for llms.txt summaries.', 'agizoai-agentic-browsing-optimizer' ), 'integrations' ); $this->add_check( $checks, 'privacy_policy', __( 'Privacy policy available', 'agizoai-agentic-browsing-optimizer' ), ! empty( get_privacy_policy_url() ), 5, 'trust', __( 'No privacy policy page is detected.', 'agizoai-agentic-browsing-optimizer' ), __( 'A clear privacy page helps agents and users find data-use information.', 'agizoai-agentic-browsing-optimizer' ), __( 'Set a WordPress privacy policy page.', 'agizoai-agentic-browsing-optimizer' ), 'settings' ); $earned_points = 0; $total_points = 0; foreach ( $checks as $check ) { $total_points += absint( $check['weight'] ); if ( ! empty( $check['passed'] ) ) { $earned_points += absint( $check['weight'] ); } } $score = $total_points > 0 ? (int) round( ( $earned_points / $total_points ) * 100 ) : 0; $status = $this->status_from_score( $score ); $failed = array_values( array_filter( $checks, array( $this, 'is_failed_check' ) ) ); $critical = array_values( array_filter( $failed, array( $this, 'is_critical_check' ) ) ); $groups = $this->group_checks( $checks ); $timestamp = current_time( 'mysql' ); return array( 'score' => $score, 'status' => $status, 'status_label' => $this->status_label( $status ), 'checks' => $checks, 'groups' => $groups, 'failed' => $failed, 'critical' => $critical, 'failed_count' => count( $failed ), 'critical_count' => count( $critical ), 'public_items' => $sample_count, 'generated_at' => $timestamp, 'endpoints' => array( 'llms' => home_url( '/llms.txt' ), 'llms_full' => home_url( '/llms-full.txt' ), 'tools' => isset( $tools_summary['registry_url'] ) ? $tools_summary['registry_url'] : '', ), 'forms' => array( 'detected' => $detected_forms, 'total' => absint( $form_summary['total'] ), 'required_without_name' => absint( $form_summary['required_without_name'] ), ), 'tools' => $tools_summary, 'accessibility' => $accessibility_scan, 'layout' => $layout_scan, 'ecommerce' => $ecommerce_scan, 'integrations' => $integration_scan, ); } /** * Persist last scan metadata. * * @param array $scan Scan results. * @return void */ public function persist_last_scan( array $scan ) { $options = $this->settings->get_options(); $options['last_scan_score'] = isset( $scan['score'] ) ? absint( $scan['score'] ) : 0; $options['last_scan_status'] = isset( $scan['status'] ) ? sanitize_key( $scan['status'] ) : ''; $options['last_scan_time'] = isset( $scan['generated_at'] ) ? sanitize_text_field( $scan['generated_at'] ) : current_time( 'mysql' ); update_option( AGIZOAI_AGENTIC_OPTION, $this->settings->sanitize_options( $options ), false ); } /** * Add one scanner check. * * @param array> $checks Checks. * @param string $id ID. * @param string $label Label. * @param bool $passed Passed. * @param int $weight Weight. * @param string $group Group. * @param string $problem Problem text. * @param string $why Why text. * @param string $fix Fix text. * @param string $action_tab Admin tab. * @return void */ private function add_check( array &$checks, $id, $label, $passed, $weight, $group, $problem, $why, $fix, $action_tab ) { $checks[] = array( 'id' => sanitize_key( $id ), 'label' => $label, 'passed' => (bool) $passed, 'weight' => absint( $weight ), 'group' => sanitize_key( $group ), 'problem' => $problem, 'why' => $why, 'fix' => $fix, 'action_tab' => sanitize_key( $action_tab ), 'action_url' => admin_url( 'admin.php?page=agizoai-agentic&tab=' . sanitize_key( $action_tab ) ), 'severity' => $weight >= 10 ? 'high' : 'medium', ); } /** * Return failed check status. * * @param array $check Check. * @return bool */ private function is_failed_check( $check ) { return empty( $check['passed'] ); } /** * Return critical check status. * * @param array $check Check. * @return bool */ private function is_critical_check( $check ) { return empty( $check['passed'] ) && isset( $check['weight'] ) && absint( $check['weight'] ) >= 10; } /** * Group checks and calculate group scores. * * @param array> $checks Checks. * @return array> */ private function group_checks( array $checks ) { $groups = array( 'discoverability' => array( 'label' => __( 'LLM Discoverability', 'agizoai-agentic-browsing-optimizer' ), 'score' => 0, 'total' => 0, 'checks' => array(), ), 'clarity' => array( 'label' => __( 'Agent Clarity', 'agizoai-agentic-browsing-optimizer' ), 'score' => 0, 'total' => 0, 'checks' => array(), ), 'webmcp' => array( 'label' => __( 'WebMCP Forms', 'agizoai-agentic-browsing-optimizer' ), 'score' => 0, 'total' => 0, 'checks' => array(), ), 'tools' => array( 'label' => __( 'Read-only Agent Tools', 'agizoai-agentic-browsing-optimizer' ), 'score' => 0, 'total' => 0, 'checks' => array(), ), 'accessibility' => array( 'label' => __( 'Accessibility for Agents', 'agizoai-agentic-browsing-optimizer' ), 'score' => 0, 'total' => 0, 'checks' => array(), ), 'layout' => array( 'label' => __( 'Layout Stability', 'agizoai-agentic-browsing-optimizer' ), 'score' => 0, 'total' => 0, 'checks' => array(), ), 'ecommerce' => array( 'label' => __( 'WooCommerce Readiness', 'agizoai-agentic-browsing-optimizer' ), 'score' => 0, 'total' => 0, 'checks' => array(), ), 'integrations' => array( 'label' => __( 'SEO Metadata Integrations', 'agizoai-agentic-browsing-optimizer' ), 'score' => 0, 'total' => 0, 'checks' => array(), ), 'safety' => array( 'label' => __( 'Safety', 'agizoai-agentic-browsing-optimizer' ), 'score' => 0, 'total' => 0, 'checks' => array(), ), 'trust' => array( 'label' => __( 'Trust Signals', 'agizoai-agentic-browsing-optimizer' ), 'score' => 0, 'total' => 0, 'checks' => array(), ), ); foreach ( $checks as $check ) { $group = isset( $check['group'] ) ? sanitize_key( $check['group'] ) : 'discoverability'; if ( ! isset( $groups[ $group ] ) ) { continue; } $groups[ $group ]['total'] += absint( $check['weight'] ); if ( ! empty( $check['passed'] ) ) { $groups[ $group ]['score'] += absint( $check['weight'] ); } $groups[ $group ]['checks'][] = $check; } foreach ( $groups as $slug => $group ) { $total = absint( $group['total'] ); if ( 0 === $total ) { unset( $groups[ $slug ] ); continue; } $score = absint( $group['score'] ); $groups[ $slug ]['percent'] = (int) round( ( $score / $total ) * 100 ); } return $groups; } /** * Score status slug. * * @param int $score Score. * @return string */ private function status_from_score( $score ) { $score = absint( $score ); if ( $score < 50 ) { return 'critical'; } if ( $score < 75 ) { return 'needs-work'; } if ( $score < 90 ) { return 'good'; } return 'excellent'; } /** * Score status label. * * @param string $status Status. * @return string */ private function status_label( $status ) { switch ( $status ) { case 'critical': return __( 'Critical setup needed', 'agizoai-agentic-browsing-optimizer' ); case 'needs-work': return __( 'Needs work', 'agizoai-agentic-browsing-optimizer' ); case 'good': return __( 'Good foundation', 'agizoai-agentic-browsing-optimizer' ); case 'excellent': default: return __( 'Excellent foundation', 'agizoai-agentic-browsing-optimizer' ); } } /** * Count public items in selected post types. * * @return int */ private function count_agentic_public_items() { $post_types = (array) $this->settings->get( 'include_post_types', array() ); if ( empty( $post_types ) ) { return 0; } $query = new WP_Query( array( 'post_type' => $post_types, 'post_status' => 'publish', 'posts_per_page' => 20, 'fields' => 'ids', 'no_found_rows' => true, 'ignore_sticky_posts' => true, 'update_post_meta_cache' => true, 'update_post_term_cache' => false, ) ); $count = 0; foreach ( $query->posts as $post_id ) { $post = get_post( $post_id ); if ( $post instanceof WP_Post && ! $this->llms->should_skip_post( $post ) ) { $count++; } } return $count; } }