0 && ( time() - $ran_at ) <= self::CACHE_TTL ) { return $fallback; } } return array(); } public static function clear_cache(): void { delete_transient( self::TRANSIENT_KEY ); delete_option( self::OPTION_KEY ); } public static function run_and_cache( string $target_url, bool $force = false ): array { if ( $force ) { self::clear_cache(); } $results = self::run_checks( $target_url ); set_transient( self::TRANSIENT_KEY, $results, self::CACHE_TTL ); update_option( self::OPTION_KEY, $results, false ); return $results; } private static function run_checks( string $target_url ): array { $target_url = self::normalize_target_url( $target_url ); $items = array(); $items[] = self::check_search_visibility(); $items[] = self::check_permalink_structure(); $resp = self::fetch_url( $target_url ); $items[] = self::check_fetch_ok( $target_url, $resp ); $items[] = self::check_x_robots_tag_header( $resp ); $html = isset( $resp['body'] ) ? (string) $resp['body'] : ''; $items[] = self::check_meta_robots_noindex( $html ); $items[] = self::check_title_tag( $html ); $items[] = self::check_canonical_tag( $html ); $items[] = self::check_mixed_content( $html ); $items[] = self::check_recent_titles(); $items[] = self::check_robots_txt(); $items[] = self::check_sitemaps(); return array( 'ran_at' => time(), 'target_url' => $target_url, 'items' => $items, ); } private static function normalize_target_url( string $url ): string { $url = trim( $url ); if ( '' === $url ) { return home_url( '/' ); } // Allow relative paths. if ( 0 === strpos( $url, '/' ) ) { $url = home_url( $url ); } $url = esc_url_raw( $url ); if ( '' === $url ) { return home_url( '/' ); } $home_host = wp_parse_url( home_url( '/' ), PHP_URL_HOST ); $u_host = wp_parse_url( $url, PHP_URL_HOST ); if ( empty( $home_host ) || empty( $u_host ) || 0 !== strcasecmp( (string) $home_host, (string) $u_host ) ) { return home_url( '/' ); } return $url; } private static function fetch_url( string $url ): array { $args = array( 'timeout' => 8, 'redirection' => 3, 'reject_unsafe_urls' => true, 'limit_response_size' => 262144, // 256 KB safety. 'user-agent' => 'SSC Real SEO Baseline/' . SSC_REAL_SEO_VERSION . '; ' . home_url( '/' ), ); $response = wp_remote_get( $url, $args ); if ( is_wp_error( $response ) ) { return array( 'ok' => false, 'code' => 0, 'error' => $response->get_error_message(), 'headers' => array(), 'body' => '', ); } $code = (int) wp_remote_retrieve_response_code( $response ); $body = (string) wp_remote_retrieve_body( $response ); $headers = wp_remote_retrieve_headers( $response ); $headers_array = array(); if ( is_array( $headers ) ) { $headers_array = $headers; } elseif ( is_object( $headers ) ) { $headers_array = (array) $headers; } return array( 'ok' => ( $code >= 200 && $code < 400 ), 'code' => $code, 'error' => '', 'headers' => $headers_array, 'body' => $body, ); } private static function status_item( string $checked, string $status, string $found, string $why, string $next ): array { return array( 'checked' => $checked, 'status' => $status, // pass|warning|fail 'found' => $found, 'why' => $why, 'next' => $next, ); } private static function check_search_visibility(): array { $blog_public = (int) get_option( 'blog_public', 1 ); if ( 1 === $blog_public ) { return self::status_item( __( 'Search engine visibility setting', '7-sided-cube-real-seo' ), 'pass', __( 'Search engines are allowed to index this site.', '7-sided-cube-real-seo' ), __( 'If this is disabled, your site can be hidden from search engines.', '7-sided-cube-real-seo' ), __( 'Keep this enabled for most public websites.', '7-sided-cube-real-seo' ) ); } return self::status_item( __( 'Search engine visibility setting', '7-sided-cube-real-seo' ), 'fail', __( 'Search engines are discouraged from indexing this site.', '7-sided-cube-real-seo' ), __( 'This can prevent your pages from appearing in search results.', '7-sided-cube-real-seo' ), __( 'Go to Settings > Reading and uncheck the visibility option.', '7-sided-cube-real-seo' ) ); } private static function check_permalink_structure(): array { $structure = (string) get_option( 'permalink_structure', '' ); if ( '' === $structure ) { return self::status_item( __( 'Permalink structure', '7-sided-cube-real-seo' ), 'warning', __( 'Permalinks are set to Plain.', '7-sided-cube-real-seo' ), __( 'Pretty permalinks are easier to read and share, and often make analytics and internal linking clearer.', '7-sided-cube-real-seo' ), __( 'Go to Settings > Permalinks and choose a pretty structure like Post name.', '7-sided-cube-real-seo' ) ); } return self::status_item( __( 'Permalink structure', '7-sided-cube-real-seo' ), 'pass', __( 'Pretty permalinks are enabled.', '7-sided-cube-real-seo' ), __( 'Clean URLs support a stronger baseline and cleaner internal linking.', '7-sided-cube-real-seo' ), __( 'No action needed.', '7-sided-cube-real-seo' ) ); } private static function check_fetch_ok( string $url, array $resp ): array { if ( ! empty( $resp['ok'] ) ) { return self::status_item( __( 'Page fetch', '7-sided-cube-real-seo' ), 'pass', sprintf( /* translators: %d: HTTP status code */ __( 'Fetched successfully (HTTP %d).', '7-sided-cube-real-seo' ), (int) $resp['code'] ), __( 'If your pages cannot be fetched, search engines can struggle to crawl them.', '7-sided-cube-real-seo' ), __( 'No action needed.', '7-sided-cube-real-seo' ) ); } $msg = isset( $resp['error'] ) && '' !== $resp['error'] ? $resp['error'] : __( 'Request failed.', '7-sided-cube-real-seo' ); return self::status_item( __( 'Page fetch', '7-sided-cube-real-seo' ), 'fail', /* translators: Placeholder values such as HTTP status text and code. */ sprintf( __( 'Could not fetch page. %s', '7-sided-cube-real-seo' ), $msg ), __( 'If WordPress cannot fetch the page internally, it can be a sign of firewall rules, redirects, or server issues.', '7-sided-cube-real-seo' ), __( 'Try again. If it persists, check redirects, security plugins, or host firewall settings.', '7-sided-cube-real-seo' ) ); } private static function check_x_robots_tag_header( array $resp ): array { $headers = isset( $resp['headers'] ) && is_array( $resp['headers'] ) ? $resp['headers'] : array(); $value = ''; foreach ( $headers as $k => $v ) { if ( 0 === strcasecmp( (string) $k, 'x-robots-tag' ) ) { $value = is_array( $v ) ? implode( ', ', $v ) : (string) $v; break; } } if ( '' === $value ) { return self::status_item( __( 'X-Robots-Tag header', '7-sided-cube-real-seo' ), 'pass', __( 'No X-Robots-Tag header found.', '7-sided-cube-real-seo' ), __( 'Some systems use this header to apply noindex or nofollow sitewide.', '7-sided-cube-real-seo' ), __( 'No action needed.', '7-sided-cube-real-seo' ) ); } $lower = strtolower( $value ); if ( false !== strpos( $lower, 'noindex' ) ) { return self::status_item( __( 'X-Robots-Tag header', '7-sided-cube-real-seo' ), 'fail', /* translators: Placeholder values such as HTTP status text and code. */ sprintf( __( 'Header includes: %s', '7-sided-cube-real-seo' ), $value ), __( 'A noindex header can prevent pages from appearing in search results.', '7-sided-cube-real-seo' ), __( 'Check server headers, security plugins, or edge caching rules that may be adding this.', '7-sided-cube-real-seo' ) ); } return self::status_item( __( 'X-Robots-Tag header', '7-sided-cube-real-seo' ), 'warning', /* translators: Placeholder values such as HTTP status text and code. */ sprintf( __( 'Header includes: %s', '7-sided-cube-real-seo' ), $value ), __( 'Robots headers can influence indexing and crawling behavior.', '7-sided-cube-real-seo' ), __( 'Review the header value and confirm it is intentional.', '7-sided-cube-real-seo' ) ); } private static function check_meta_robots_noindex( string $html ): array { $robots = self::find_meta_robots( $html ); if ( '' === $robots ) { return self::status_item( __( 'Meta robots tag', '7-sided-cube-real-seo' ), 'pass', __( 'No meta robots tag found.', '7-sided-cube-real-seo' ), __( 'A meta robots tag can block indexing when it includes noindex.', '7-sided-cube-real-seo' ), __( 'No action needed.', '7-sided-cube-real-seo' ) ); } $lower = strtolower( $robots ); if ( false !== strpos( $lower, 'noindex' ) ) { return self::status_item( __( 'Meta robots tag', '7-sided-cube-real-seo' ), 'fail', /* translators: Placeholder values such as HTTP status text and code. */ sprintf( __( 'Found robots content: %s', '7-sided-cube-real-seo' ), $robots ), __( 'If a page has a noindex directive, it can be kept out of search results.', '7-sided-cube-real-seo' ), __( 'Remove noindex if this page should be indexed.', '7-sided-cube-real-seo' ) ); } return self::status_item( __( 'Meta robots tag', '7-sided-cube-real-seo' ), 'warning', /* translators: Placeholder values such as HTTP status text and code. */ sprintf( __( 'Found robots content: %s', '7-sided-cube-real-seo' ), $robots ), __( 'Robots directives can influence indexing and crawling behavior.', '7-sided-cube-real-seo' ), __( 'Review to confirm it matches your intent.', '7-sided-cube-real-seo' ) ); } private static function check_title_tag( string $html ): array { if ( preg_match( '/]*>(.*?)<\/title>/is', $html, $m ) ) { $title = trim( wp_strip_all_tags( $m[1] ) ); if ( '' !== $title ) { return self::status_item( __( 'Title tag', '7-sided-cube-real-seo' ), 'pass', /* translators: Placeholder values such as HTTP status text and code. */ sprintf( __( 'Title found: %s', '7-sided-cube-real-seo' ), $title ), __( 'Title tags help search engines understand the page and are often shown in search results.', '7-sided-cube-real-seo' ), __( 'Keep titles clear and specific.', '7-sided-cube-real-seo' ) ); } } return self::status_item( __( 'Title tag', '7-sided-cube-real-seo' ), 'warning', __( 'No title tag detected in fetched HTML.', '7-sided-cube-real-seo' ), __( 'A missing or empty title tag can weaken relevance signals.', '7-sided-cube-real-seo' ), __( 'Confirm your theme outputs a title tag and that the page has a meaningful title.', '7-sided-cube-real-seo' ) ); } private static function check_canonical_tag( string $html ): array { if ( preg_match( '/]*rel=[\'\"]canonical[\'\"][^>]*>/i', $html ) ) { return self::status_item( __( 'Canonical tag', '7-sided-cube-real-seo' ), 'pass', __( 'Canonical link tag detected.', '7-sided-cube-real-seo' ), __( 'Canonicals help search engines choose the preferred version of a URL.', '7-sided-cube-real-seo' ), __( 'No action needed.', '7-sided-cube-real-seo' ) ); } return self::status_item( __( 'Canonical tag', '7-sided-cube-real-seo' ), 'warning', __( 'No canonical link tag detected.', '7-sided-cube-real-seo' ), __( 'Without a canonical, duplicate or parameterized URLs can confuse indexing.', '7-sided-cube-real-seo' ), __( 'Many themes and SEO plugins add canonicals. Confirm your setup.', '7-sided-cube-real-seo' ) ); } private static function check_mixed_content( string $html ): array { if ( preg_match( '/\bhttp:\/\/[\w\.-]+/i', $html ) ) { return self::status_item( __( 'Mixed content', '7-sided-cube-real-seo' ), 'warning', __( 'Found http:// resources in HTML.', '7-sided-cube-real-seo' ), __( 'Mixed content can cause browser warnings and can interfere with secure crawling.', '7-sided-cube-real-seo' ), __( 'Update hardcoded http links to https and fix any insecure embeds.', '7-sided-cube-real-seo' ) ); } return self::status_item( __( 'Mixed content', '7-sided-cube-real-seo' ), 'pass', __( 'No obvious mixed content found in fetched HTML.', '7-sided-cube-real-seo' ), __( 'Keeping pages fully secure avoids crawling and user trust issues.', '7-sided-cube-real-seo' ), __( 'No action needed.', '7-sided-cube-real-seo' ) ); } private static function check_recent_titles(): array { $recent = get_posts( array( 'post_type' => array( 'post', 'page' ), 'post_status' => 'publish', 'posts_per_page' => 20, 'orderby' => 'date', 'order' => 'DESC', 'no_found_rows' => true, ) ); $missing = 0; foreach ( $recent as $p ) { $t = get_the_title( $p ); if ( '' === trim( (string) $t ) ) { $missing++; } } if ( 0 === $missing ) { return self::status_item( __( 'Recent titles', '7-sided-cube-real-seo' ), 'pass', __( 'Recent published pages and posts have titles.', '7-sided-cube-real-seo' ), __( 'Titles support clarity for users and search engines.', '7-sided-cube-real-seo' ), __( 'No action needed.', '7-sided-cube-real-seo' ) ); } return self::status_item( __( 'Recent titles', '7-sided-cube-real-seo' ), 'warning', sprintf( /* translators: %d: number of items */ __( '%d recent items appear to have an empty title.', '7-sided-cube-real-seo' ), $missing ), __( 'Pages without titles can be hard to understand and may weaken relevance signals.', '7-sided-cube-real-seo' ), __( 'Edit those items and add clear, descriptive titles.', '7-sided-cube-real-seo' ) ); } private static function check_robots_txt(): array { // WordPress can serve a virtual robots.txt, or it can be a physical file. $url = home_url( '/robots.txt' ); $resp = wp_remote_head( $url, array( 'timeout' => 6, 'redirection' => 3, 'reject_unsafe_urls' => true, 'limit_response_size' => 8192, 'user-agent' => 'SSC Real SEO Baseline/' . SSC_REAL_SEO_VERSION . '; ' . home_url( '/' ), ) ); if ( is_wp_error( $resp ) ) { return self::status_item( __( 'robots.txt', '7-sided-cube-real-seo' ), 'warning', __( 'Could not check robots.txt.', '7-sided-cube-real-seo' ), __( 'robots.txt helps guide crawlers, even when minimal.', '7-sided-cube-real-seo' ), __( 'Visit /robots.txt in your browser and confirm it loads.', '7-sided-cube-real-seo' ) ); } $code = (int) wp_remote_retrieve_response_code( $resp ); if ( $code >= 200 && $code < 400 ) { return self::status_item( __( 'robots.txt', '7-sided-cube-real-seo' ), 'pass', __( 'robots.txt endpoint responds.', '7-sided-cube-real-seo' ), __( 'Search engines often check this file early in crawling.', '7-sided-cube-real-seo' ), __( 'No action needed.', '7-sided-cube-real-seo' ) ); } return self::status_item( __( 'robots.txt', '7-sided-cube-real-seo' ), 'warning', sprintf( /* translators: %d: HTTP status code */ __( 'robots.txt returned HTTP %d.', '7-sided-cube-real-seo' ), $code ), __( 'If robots.txt is missing, crawling can still happen, but you lose a standard control point.', '7-sided-cube-real-seo' ), __( 'Confirm your site can serve /robots.txt (virtual or file).', '7-sided-cube-real-seo' ) ); } private static function check_sitemaps(): array { $endpoints = array( home_url( '/wp-sitemap.xml' ), home_url( '/sitemap.xml' ), home_url( '/sitemap_index.xml' ), ); foreach ( $endpoints as $ep ) { $resp = wp_remote_head( $ep, array( 'timeout' => 6, 'redirection' => 3, 'reject_unsafe_urls' => true, 'limit_response_size' => 8192, 'user-agent' => 'SSC Real SEO Baseline/' . SSC_REAL_SEO_VERSION . '; ' . home_url( '/' ), ) ); if ( is_wp_error( $resp ) ) { continue; } $code = (int) wp_remote_retrieve_response_code( $resp ); if ( $code >= 200 && $code < 400 ) { return self::status_item( __( 'Sitemap endpoint', '7-sided-cube-real-seo' ), 'pass', /* translators: Placeholder values such as HTTP status text and code. */ sprintf( __( 'Found sitemap at: %s', '7-sided-cube-real-seo' ), $ep ), __( 'Sitemaps help discovery and monitoring of indexed URLs.', '7-sided-cube-real-seo' ), __( 'No action needed.', '7-sided-cube-real-seo' ) ); } } return self::status_item( __( 'Sitemap endpoint', '7-sided-cube-real-seo' ), 'warning', __( 'No common sitemap endpoint responded.', '7-sided-cube-real-seo' ), __( 'Some sites use plugin generated sitemaps or custom endpoints.', '7-sided-cube-real-seo' ), __( 'If you use an SEO plugin, check its sitemap setting or URL.', '7-sided-cube-real-seo' ) ); } private static function find_meta_robots( string $html ): string { if ( preg_match( '/]*name=[\'\"]robots[\'\"][^>]*content=[\'\"]([^\'\"]+)[\'\"][^>]*>/i', $html, $m ) ) { return trim( (string) $m[1] ); } return ''; } }