is_refresh_requested() ) { delete_transient( self::TRANSIENT ); } $result = get_transient( self::TRANSIENT ); if ( ! is_array( $result ) || ! isset( $result['score'], $result['checks'] ) || ! is_array( $result['checks'] ) ) { $result = self::run_quickcheck(); set_transient( self::TRANSIENT, $result, 12 * HOUR_IN_SECONDS ); } $this->render_result( $result ); } /** * Whether a nonce-valid refresh was requested on the dashboard URL. * * @return bool */ private function is_refresh_requested() { if ( ! isset( $_GET['aeo_connect_refresh'], $_GET['_wpnonce'] ) ) { return false; } $nonce = sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ); return false !== wp_verify_nonce( $nonce, self::NONCE_ACTION ); } /* --------------------------------------------------------------------- * Quick-check (self-requests only, never calls everlange.com) * ------------------------------------------------------------------ */ /** * Run the local quick-check and return the result. * * Total = 100 points across 10 signals: * homepage HTML (JSON-LD 15, meta description 10, Open Graph 10, title 5, * html lang 5, RSS link 10), robots.txt (reachable 10, AI bots 10), * llms.txt (15), sitemap (10). * * @return array { score: int, checks: array, checked_at: int } */ public static function run_quickcheck() { $checks = array(); // a. Homepage HTML signals. $home_html = self::fetch_body( home_url( '/' ), 8 ); $checks['jsonld'] = array( 'points' => 15, 'pass' => (bool) preg_match( '#]+application/ld\+json#i', $home_html ), ); $checks['meta_description'] = array( 'points' => 10, 'pass' => (bool) preg_match( '#]+name=["\']description["\']#i', $home_html ), ); $checks['open_graph'] = array( 'points' => 10, 'pass' => (bool) preg_match( '#]+property=["\']og:title["\']#i', $home_html ) && (bool) preg_match( '#]+property=["\']og:description["\']#i', $home_html ), ); $has_title = false; if ( preg_match( '#]*>(.*?)#is', $home_html, $m ) ) { $has_title = '' !== trim( wp_strip_all_tags( $m[1] ) ); } $checks['title'] = array( 'points' => 5, 'pass' => $has_title, ); $checks['html_lang'] = array( 'points' => 5, 'pass' => (bool) preg_match( '#]+lang=["\'][^"\']+#i', $home_html ), ); // b. robots.txt: reachable + AI-bot directives. $robots_response = self::fetch( home_url( '/robots.txt' ), 6 ); $robots_ok = ! is_wp_error( $robots_response ) && 200 === (int) wp_remote_retrieve_response_code( $robots_response ); $robots_body = $robots_ok ? (string) wp_remote_retrieve_body( $robots_response ) : ''; $checks['robots'] = array( 'points' => 10, 'pass' => $robots_ok, ); $checks['robots_ai'] = array( 'points' => 10, 'pass' => (bool) preg_match( '/gptbot|anthropic-ai|perplexitybot|google-extended/i', $robots_body ), ); // c. llms.txt: HTTP 200 with a non-HTML body. $llms_response = self::fetch( home_url( '/llms.txt' ), 6 ); $llms_ok = false; if ( ! is_wp_error( $llms_response ) && 200 === (int) wp_remote_retrieve_response_code( $llms_response ) ) { $llms_body = ltrim( (string) wp_remote_retrieve_body( $llms_response ) ); $llms_ok = '' !== $llms_body && 0 !== stripos( $llms_body, ' 15, 'pass' => $llms_ok, ); // d. XML sitemap: /sitemap.xml, with /wp-sitemap.xml as fallback. $checks['sitemap'] = array( 'points' => 10, 'pass' => self::url_returns_200( home_url( '/sitemap.xml' ), 6 ) || self::url_returns_200( home_url( '/wp-sitemap.xml' ), 6 ), ); // e. RSS feed declared in the homepage HTML. $checks['rss'] = array( 'points' => 10, 'pass' => false !== stripos( $home_html, 'application/rss+xml' ), ); $score = 0; foreach ( $checks as $check ) { if ( $check['pass'] ) { $score += (int) $check['points']; } } return array( 'score' => min( 100, $score ), 'checks' => $checks, 'checked_at' => time(), ); } /** * Self-request helper (short timeout, never calls everlange.com). * * @param string $url URL on this site. * @param int $timeout Timeout in seconds. * @return array|WP_Error wp_remote_get() response. */ private static function fetch( $url, $timeout ) { return wp_remote_get( $url, array( 'timeout' => (int) $timeout, 'redirection' => 3, 'user-agent' => 'AEO-Tool-Connect/' . AEO_CONNECT_VERSION . '; ' . home_url( '/' ), ) ); } /** * Fetch a URL and return its body, or '' on any error / non-200. * * @param string $url URL on this site. * @param int $timeout Timeout in seconds. * @return string */ private static function fetch_body( $url, $timeout ) { $response = self::fetch( $url, $timeout ); if ( is_wp_error( $response ) || 200 !== (int) wp_remote_retrieve_response_code( $response ) ) { return ''; } return (string) wp_remote_retrieve_body( $response ); } /** * Whether a URL on this site answers HTTP 200. * * @param string $url URL on this site. * @param int $timeout Timeout in seconds. * @return bool */ private static function url_returns_200( $url, $timeout ) { $response = self::fetch( $url, $timeout ); return ! is_wp_error( $response ) && 200 === (int) wp_remote_retrieve_response_code( $response ); } /* --------------------------------------------------------------------- * Rendering * ------------------------------------------------------------------ */ /** * Human-readable label for each check key. Resolved at render time so the * cached result never stores translated strings. * * @return array */ private static function check_labels() { return array( 'jsonld' => __( 'JSON-LD structured data on the homepage', 'aeo-tool-connect' ), 'meta_description' => __( 'Meta description on the homepage', 'aeo-tool-connect' ), 'open_graph' => __( 'Open Graph tags (og:title + og:description)', 'aeo-tool-connect' ), 'title' => __( 'Homepage title tag', 'aeo-tool-connect' ), 'html_lang' => __( 'HTML lang attribute', 'aeo-tool-connect' ), 'robots' => __( 'robots.txt reachable', 'aeo-tool-connect' ), 'robots_ai' => __( 'robots.txt mentions AI crawlers (GPTBot, PerplexityBot...)', 'aeo-tool-connect' ), 'llms' => __( '/llms.txt file served', 'aeo-tool-connect' ), 'sitemap' => __( 'XML sitemap reachable', 'aeo-tool-connect' ), 'rss' => __( 'RSS feed declared on the homepage', 'aeo-tool-connect' ), ); } /** * Render the quick-check result inside the widget. * * @param array $result Quick-check result (score, checks, checked_at). * @return void */ private function render_result( $result ) { $score = (int) $result['score']; $checks = $result['checks']; $labels = self::check_labels(); // WP admin palette: green / amber / red. if ( $score >= 70 ) { $color = '#00a32a'; } elseif ( $score >= 40 ) { $color = '#dba617'; } else { $color = '#d63638'; } // Paired = a valid key is stored AND it has not been revoked from the // everlange.com dashboard (a revoked key must fall back to the audit CTA). $paired = '' !== AEO_Connect_API_Client::get_site_key() && ! get_option( 'aeo_connect_revoked' ); if ( $paired ) { $cta_url = 'https://everlange.com/?utm_source=wp-plugin&utm_medium=dashboard-widget'; $cta_label = __( 'View my full report on everlange.com', 'aeo-tool-connect' ); } else { $cta_url = 'https://everlange.com/audit?url=' . rawurlencode( home_url( '/' ) ) . '&utm_source=wp-plugin&utm_medium=dashboard-widget&utm_campaign=quickcheck'; $cta_label = __( 'Run the full audit on everlange.com', 'aeo-tool-connect' ); } $refresh_url = wp_nonce_url( add_query_arg( 'aeo_connect_refresh', '1', admin_url( 'index.php' ) ), self::NONCE_ACTION ); $checked_at = isset( $result['checked_at'] ) ? (int) $result['checked_at'] : 0; ?>

/100

    $aeo_check ) : ?>

0 ) : ?>