5 ) ); if ( is_wp_error( $response ) ) { $result = array( 'content' => '', 'fetch_error' => $response->get_error_message(), ); } elseif ( 200 !== (int) wp_remote_retrieve_response_code( $response ) ) { $result = array( 'content' => '', /* translators: %d: HTTP response code. */ 'fetch_error' => sprintf( __( 'Unexpected HTTP %d response.', 'abds-seo' ), wp_remote_retrieve_response_code( $response ) ), ); } else { $result = array( 'content' => (string) wp_remote_retrieve_body( $response ), 'fetch_error' => '', ); } set_transient( self::TRANSIENT_KEY, $result, self::TRANSIENT_TTL ); return $result; } /** * Drops the cached fetch so the next check re-requests the live file. */ public static function invalidate_cache(): void { delete_transient( self::TRANSIENT_KEY ); } /** * Whether the given sitemap URL is declared via a `Sitemap:` line, * trailing-slash-insensitive. * * @param string $content robots.txt body. * @param string $sitemap_url The URL to look for. * @return bool */ public static function sitemap_declared( string $content, string $sitemap_url ): bool { $target = self::normalize_url( $sitemap_url ); foreach ( self::lines( $content ) as $line ) { if ( 0 === stripos( $line, 'sitemap:' ) ) { $declared = trim( substr( $line, strlen( 'sitemap:' ) ) ); if ( self::normalize_url( $declared ) === $target ) { return true; } } } return false; } /** * Whether /wp-admin/ is explicitly disallowed for at least one * user-agent block. A minimal, common-sense check — not a full robots * exclusion protocol interpreter (see class docblock: this is a * viewer/checker, not a robots.txt editor). * * @param string $content robots.txt body. * @return bool */ public static function wp_admin_blocked_appropriately( string $content ): bool { foreach ( self::lines( $content ) as $line ) { if ( preg_match( '#^disallow:\s*/wp-admin/?\s*$#i', $line ) ) { return true; } } return false; } /** * Whether a `Disallow: /` under a wildcard (`User-agent: *`) block * would block the entire site from crawling — the one critical, * always-worth-flagging misconfiguration this checker looks for. * * @param string $content robots.txt body. * @return bool */ public static function critical_disallow_all( string $content ): bool { $in_wildcard_block = false; foreach ( self::lines( $content ) as $line ) { if ( preg_match( '#^user-agent:\s*(.+)$#i', $line, $matches ) ) { $in_wildcard_block = '*' === trim( $matches[1] ); continue; } if ( $in_wildcard_block && preg_match( '#^disallow:\s*/\s*$#i', $line ) ) { return true; } } return false; } /** * Splits robots.txt content into trimmed, non-empty lines. * * @param string $content robots.txt body. * @return string[] */ protected static function lines( string $content ): array { $lines = preg_split( '/\r\n|\r|\n/', $content ); return array_values( array_filter( array_map( 'trim', false !== $lines ? $lines : array() ) ) ); } /** * Strips a trailing slash for tolerant URL comparison, without relying * on any WP helper (this method must stay WP-free — see class docblock). * * @param string $url URL to normalize. * @return string */ protected static function normalize_url( string $url ): string { return rtrim( trim( $url ), '/' ); } }