30%) (weight 20) * * Reads the meta description via the post's excerpt fallback, and by peeking * at Yoast / Rank Math / AIOSEO / SEOPress keys if present — matches the * detection used elsewhere in the plugin. * * @package AEOmatic */ if ( ! defined( 'ABSPATH' ) ) { exit; } class AEOMATIC_Analyzer_Trust implements AEOMATIC_Readiness_Analyzer_Interface { private const KNOWN_META_DESC_KEYS = array( '_yoast_wpseo_metadesc', 'rank_math_description', '_aioseo_description', '_seopress_titles_desc', ); private const STALE_DAYS = 365; public function analyze( WP_Post $post ): AEOMATIC_Readiness_Result { $score = 100; $issues = array(); $recommendations = array(); if ( ! $this->has_meta_description( $post ) ) { $score -= 20; $issues[] = __( 'No meta description set (checked Yoast / Rank Math / AIOSEO / SEOPress / excerpt).', 'aeomatic' ); $recommendations[] = __( 'Add a 120-155 character meta description that answers the primary question.', 'aeomatic' ); } if ( ! $this->has_author_byline( $post ) ) { $score -= 20; $issues[] = __( 'No author byline — post shows no attributable expert.', 'aeomatic' ); $recommendations[] = __( 'Assign a user with a filled-in bio as the author. AI engines cite content with a named expert more often.', 'aeomatic' ); } $age_days = $this->age_in_days( $post ); if ( $age_days > self::STALE_DAYS ) { $score -= 15; $issues[] = sprintf( __( 'Post was last modified %d days ago; AI engines de-prioritise stale content.', 'aeomatic' ), $age_days ); $recommendations[] = __( 'Update the post — refresh dates, stats, and examples — to signal ongoing accuracy.', 'aeomatic' ); } $outbound_count = $this->outbound_links_count( $post ); if ( $outbound_count < 2 ) { $score -= 25; $issues[] = sprintf( __( 'Only %d outbound source links; AI engines favour content that cites its sources.', 'aeomatic' ), $outbound_count ); $recommendations[] = __( 'Link to at least 2 authoritative external sources (studies, official docs, standards bodies).', 'aeomatic' ); } if ( $this->promotional_ratio( $post ) > 0.3 ) { $score -= 20; $issues[] = __( 'More than 30% of links are promotional (your own pricing, sign-up, or affiliate URLs).', 'aeomatic' ); $recommendations[] = __( 'Rebalance link mix: for every promo link, add at least two links to independent third-party sources.', 'aeomatic' ); } return new AEOMATIC_Readiness_Result( 'trust', $score, $issues, $recommendations ); } private function has_meta_description( WP_Post $post ): bool { foreach ( self::KNOWN_META_DESC_KEYS as $key ) { $val = get_post_meta( $post->ID, $key, true ); if ( is_string( $val ) && trim( $val ) !== '' ) { return true; } } return trim( (string) $post->post_excerpt ) !== ''; } private function has_author_byline( WP_Post $post ): bool { if ( (int) $post->post_author <= 0 ) { return false; } $user = get_userdata( (int) $post->post_author ); if ( ! $user ) { return false; } return trim( (string) $user->display_name ) !== ''; } private function age_in_days( WP_Post $post ): int { $modified = strtotime( (string) $post->post_modified_gmt ); if ( ! $modified ) { return 0; } return (int) floor( ( time() - $modified ) / DAY_IN_SECONDS ); } /** * Merged html from the shared extractor — post_content plus page-builder * and ACF content. Builder links are invisible in post_content alone. */ private function extracted_html( WP_Post $post ): string { return (string) ( AEOMATIC_Content_Extractor::extract( $post )['html'] ?? '' ); } private function outbound_links_count( WP_Post $post ): int { $home = wp_parse_url( home_url(), PHP_URL_HOST ); if ( ! preg_match_all( '/]*href=["\']([^"\']+)["\']/i', $this->extracted_html( $post ), $m ) ) { return 0; } $count = 0; foreach ( $m[1] as $url ) { $host = wp_parse_url( $url, PHP_URL_HOST ); if ( $host && $host !== $home && strpos( $url, 'http' ) === 0 ) { $count++; } } return $count; } private function promotional_ratio( WP_Post $post ): float { $home = wp_parse_url( home_url(), PHP_URL_HOST ); if ( ! preg_match_all( '/]*href=["\']([^"\']+)["\']/i', $this->extracted_html( $post ), $m ) ) { return 0.0; } $total = 0; $promo = 0; foreach ( $m[1] as $url ) { $host = wp_parse_url( $url, PHP_URL_HOST ); if ( ! $host || strpos( $url, 'http' ) !== 0 ) { continue; } $total++; if ( $host === $home ) { if ( preg_match( '#/(pricing|signup|sign-up|buy|checkout|cart|subscribe|trial)(/|$|\?)#i', (string) wp_parse_url( $url, PHP_URL_PATH ) ) ) { $promo++; } } if ( preg_match( '/(\?|&)(ref|aff|affiliate|utm_campaign=affiliate)/i', $url ) ) { $promo++; } } return $total > 0 ? $promo / $total : 0.0; } }