post_type, (array) ( $post_types ?? [] ), true ) ) { return; } if ( ! empty( $analyze_only_with_keyword ) ) { $kws = AIT_AIPSEO_Keyword_Density::get_target_keywords( $post_id ); if ( empty( $kws ) ) { return; } } $this->analyze_and_store( $post_id ); } public function analyze_and_store( int $post_id ): array { $result = $this->analyze( $post_id ); update_post_meta( $post_id, '_ait_aipseo_score', $result ); $this->update_recent_activity( $post_id, $result['score'] ?? 0 ); AIT_AIPSEO_Dashboard::update_seo_score(); return $result; } public function analyze( int $post_id ): array { $post = get_post( $post_id ); if ( ! $post ) { return [ 'score' => 0, 'signals' => [] ]; } $title = (string) get_the_title( $post ); $meta_title = get_post_meta( $post_id, '_yoast_wpseo_title', true ); $meta_desc = get_post_meta( $post_id, '_yoast_wpseo_metadesc', true ); if ( '' === $meta_title ) { $meta_title = get_post_meta( $post_id, '_seopress_titles_title', true ); } if ( '' === $meta_desc ) { $meta_desc = get_post_meta( $post_id, '_seopress_titles_desc', true ); } if ( '' === $meta_title ) { $meta_title = get_post_meta( $post_id, '_aioseo_title', true ); } if ( '' === $meta_desc ) { $meta_desc = get_post_meta( $post_id, '_aioseo_description', true ); } if ( '' === $meta_title ) { $meta_title = $title; } if ( '' === $meta_desc ) { $meta_desc = wp_trim_words( $post->post_excerpt ?: wp_strip_all_tags( $post->post_content ), 30, '' ); } $content = wp_strip_all_tags( (string) $post->post_content ); $keywords = class_exists( 'AIT_AIPSEO_Keyword_Density' ) ? ( new AIT_AIPSEO_Keyword_Density() )->get_target_keywords( $post_id ) : []; $signals = [ 'title' => $this->signal_title( $title, $keywords ), 'meta' => $this->signal_meta( $meta_title, $meta_desc, $keywords ), 'headings' => $this->signal_headings( $post_id, $keywords ), 'readability' => $this->signal_readability( $content ), 'links' => $this->signal_links( $post_id ), 'images' => $this->signal_images( $post_id ), ]; $score = class_exists( 'AIT_AIPSEO_Score' ) ? ( new AIT_AIPSEO_Score() )->calculate( $signals ) : [ 'score' => 0, 'breakdown' => [] ]; return array_merge( $score, [ 'signals' => $signals ] ); } private function signal_title( string $title, array $keywords ): float { $len_ok = ( strlen( $title ) >= 30 && strlen( $title ) <= 60 ); $kw_ok = empty( $keywords ) ? true : ( false !== stripos( $title, $keywords[0] ) ); return ( $len_ok ? 0.6 : 0.3 ) + ( $kw_ok ? 0.4 : 0.0 ); } private function signal_meta( string $title, string $desc, array $keywords ): float { $len = strlen( $desc ); $ok = ( $len >= 120 && $len <= 160 ); $kwok = empty( $keywords ) ? true : ( false !== stripos( $desc, $keywords[0] ) ); return ( $ok ? 0.6 : 0.3 ) + ( $kwok ? 0.4 : 0.0 ); } private function signal_headings( int $post_id, array $keywords ): float { $content = get_post_field( 'post_content', $post_id ); preg_match_all( '/]*>(.*?)<\/h[1-3]>/', $content, $m ); $has_h1 = (bool) preg_match( '/]/i', $content ); $k = empty( $keywords ) ? '' : preg_quote( $keywords[0], '/' ); $kw_in_h = ( $k && preg_match( '/]*>[^<]*' . $k . '[^<]*<\/h[1-3]>/i', $content ) ) ? 1 : 0; $base = $has_h1 ? 0.6 : 0.3; return $base + ( $kw_in_h ? 0.4 : 0 ); } private function signal_readability( string $content ): float { $words = str_word_count( $content ); $sent = max( 1, substr_count( $content, '.' ) + substr_count( $content, '!' ) + substr_count( $content, '?' ) ); $avg = $words / $sent; if ( $avg >= 12 && $avg <= 25 ) { return 1.0; } if ( $avg >= 8 && $avg <= 30 ) { return 0.7; } return 0.4; } private function signal_links( int $post_id ): float { $content = get_post_field( 'post_content', $post_id ); $internal = preg_match_all( '/]+href=\"(?!https?:\\/\\/)([^\"#]+)\"/i', $content ); $external = preg_match_all( '/]+href=\"https?:\\/\\//i', $content ); return ( $internal > 0 ? 0.5 : 0.2 ) + ( $external > 0 ? 0.5 : 0.2 ); } private function signal_images( int $post_id ): float { $content = get_post_field( 'post_content', $post_id ); $imgs = preg_match_all( '/]+alt=\"[^\"]+\"/i', $content ); if ( $imgs == 0 ) { return 0.6; } // not image-heavy content: neutral $ratio = $alts > 0 ? min( 1, $alts / max( 1, $imgs ) ) : 0; return 0.3 + 0.7 * $ratio; } private function update_recent_activity( int $post_id, $score ) { $recent = get_option( 'ait_aipseo_recent', [] ); if ( ! is_array( $recent ) ) { $recent = []; } array_unshift( $recent, [ 'post_id' => $post_id, 'score' => (float) $score, 'time' => current_time( 'timestamp' ) ] ); $recent = array_slice( $recent, 0, 10 ); update_option( 'ait_aipseo_recent', $recent ); } }