15, // Direct Answer/Lead (0-15) 'headings_structure' => 15, // Headings & Structure (0-15) 'snippetability' => 10, // Snippetability (0-10) 'faq_presence' => 10, // FAQ Presence (0-10) 'readability' => 15, // Readability (0-15) 'paragraph_length' => 10, // Paragraph Length (0-10) 'sentence_variance' => 10, // Sentence Length variance (0-10) 'entity_coverage' => 10, // Entity/Keyword Coverage (0-10) 'meta_hints' => 5, // Meta Hints (0-5) 'structured_data' => 15 // Structured Data Bonus (0-15) - Can exceed 100 ]; /** * Analyze content and return AEO score with breakdown * * @param string $content Raw post content (HTML) * @param array $meta Meta data (title, description, etc.) * @return array Score analysis with breakdown and issues */ public function analyze($content, $meta = []) { // Clean and prepare content $clean_content = $this->clean_content($content); $text_content = $this->extract_text($clean_content); // Initialize results $breakdown = []; $issues = []; $total_score = 0; // Analyze each factor $direct_answer = $this->analyze_direct_answer($text_content); $breakdown[] = $direct_answer; $total_score += $direct_answer['points']; $headings = $this->analyze_headings_structure($clean_content); $breakdown[] = $headings; $total_score += $headings['points']; $snippetability = $this->analyze_snippetability($clean_content, $text_content); $breakdown[] = $snippetability; $total_score += $snippetability['points']; $faq = $this->analyze_faq_presence($clean_content); $breakdown[] = $faq; $total_score += $faq['points']; $readability = $this->analyze_readability($text_content); $breakdown[] = $readability; $total_score += $readability['points']; $paragraphs = $this->analyze_paragraph_length($clean_content); $breakdown[] = $paragraphs; $total_score += $paragraphs['points']; $sentences = $this->analyze_sentence_variance($text_content); $breakdown[] = $sentences; $total_score += $sentences['points']; $entities = $this->analyze_entity_coverage($text_content, $meta); $breakdown[] = $entities; $total_score += $entities['points']; $meta_hints = $this->analyze_meta_hints($meta); $breakdown[] = $meta_hints; $total_score += $meta_hints['points']; // Analyze structured data (bonus points - can exceed 100) $structured_data = $this->analyze_structured_data($content, $meta); $breakdown[] = $structured_data; $total_score += $structured_data['points']; // Collect issues $issues = $this->collect_issues($breakdown); // Allow exceeding 100 with structured data bonus $final_score = max(0, $total_score); return [ 'score' => $final_score, 'breakdown' => $breakdown, 'issues' => $issues ]; } /** * Clean content by removing Gutenberg blocks and normalizing */ private function clean_content($content) { // Remove Gutenberg block comments $content = preg_replace('//s', '', $content); $content = preg_replace('//s', '', $content); // Remove HTML comments $content = preg_replace('//s', '', $content); // Normalize whitespace $content = preg_replace('/\s+/', ' ', $content); return trim($content); } /** * Extract plain text from HTML content */ private function extract_text($content) { // Remove HTML tags but preserve structure $text = wp_strip_all_tags($content); // Normalize whitespace $text = preg_replace('/\s+/', ' ', $text); return trim($text); } /** * Extract plain text from HTML content while preserving paragraph structure */ private function extract_text_preserve_paragraphs($content) { // Replace paragraph tags with newlines $text = preg_replace('/<\/p>\s*
]*>/i', "\n\n", $content); $text = preg_replace('/
]*>/i', '', $text);
$text = preg_replace('/<\/p>/i', '', $text);
// Replace other block elements with newlines
$text = preg_replace('/<\/(div|section|article|header|footer|main|aside)[^>]*>/i', "\n\n", $text);
$text = preg_replace('/<(div|section|article|header|footer|main|aside)[^>]*>/i', '', $text);
// Replace
tags with newlines
$text = preg_replace('/
]*\/?>/i', "\n", $text);
// Remove remaining HTML tags
$text = wp_strip_all_tags($text);
// Normalize multiple newlines to double newlines
$text = preg_replace('/\n{3,}/', "\n\n", $text);
return trim($text);
}
/**
* Analyze direct answer in first ~120 words
*/
private function analyze_direct_answer($text) {
$words = explode(' ', $text);
$first_120 = array_slice($words, 0, 120);
$lead_text = implode(' ', $first_120);
$score = 0;
$notes = [];
// Check for question-answering patterns
if (preg_match('/\b(what|how|why|when|where|which|who)\b/i', $lead_text)) {
$score += 5;
$notes[] = 'Contains question words in lead';
}
// Check for direct statements
if (preg_match('/\b(is|are|means|refers to|defined as)\b/i', $lead_text)) {
$score += 5;
$notes[] = 'Contains definitional language';
}
// Check for value proposition
if (preg_match('/\b(benefits?|advantages?|features?|solutions?)\b/i', $lead_text)) {
$score += 5;
$notes[] = 'Promises value in lead';
}
$max = self::$AEO_WEIGHTS['direct_answer'];
$points = min($max, $score);
return [
'key' => 'direct_answer',
'points' => $points,
'max' => $max,
'notes' => $notes
];
}
/**
* Analyze headings and structure
*/
private function analyze_headings_structure($content) {
$score = 0;
$notes = [];
// Count headings
$h1_count = preg_match_all('/
]*>.*?a:\s*.*?<\/p>/i', 'type' => 'qa_pair'],
// Question patterns in headings
['pattern' => '/