check_title_issues($title, $clean_content); $issues = array_merge($issues, $title_issues); // Intro issues (early answer) $intro_issues = $this->check_intro_issues($content, $clean_content); $issues = array_merge($issues, $intro_issues); // Header issues $header_issues = $this->check_header_issues($content); $issues = array_merge($issues, $header_issues); // Body content issues $body_issues = $this->check_body_issues($content, $clean_content); $issues = array_merge($issues, $body_issues); // Link issues $link_issues = $this->check_link_issues($content); $issues = array_merge($issues, $link_issues); // Schema opportunities $schema_issues = $this->check_schema_opportunities($content, $clean_content, $title); $issues = array_merge($issues, $schema_issues); // Meta description issues if (!empty($meta_description)) { $meta_issues = $this->check_meta_issues($meta_description, $title); $issues = array_merge($issues, $meta_issues); } // Calculate signals $signals = $this->calculate_signals($content, $clean_content, $breakdown); return array( 'issues' => $issues, 'signals' => $signals, ); } /** * Check title issues * * @param string $title Post title * @param string $content Clean content * @return array Issues */ private function check_title_issues($title, $content) { $issues = array(); $title_length = strlen($title); $title_words = str_word_count($title); // Too short or too long if ($title_length < 35) { $issues[] = array( 'id' => 'title_too_short', 'severity' => 'medium', 'why' => 'Short titles miss opportunities for entities and context', 'hint' => 'Expand to 40-65 characters with specific entities or qualifiers', ); } elseif ($title_length > 70) { $issues[] = array( 'id' => 'title_too_long', 'severity' => 'medium', 'why' => 'Long titles get truncated in search results', 'hint' => 'Trim to 60-65 characters, removing filler words', ); } // No question mark when content suggests it should be if (substr_count($content, '?') > 5 && strpos($title, '?') === false) { $issues[] = array( 'id' => 'title_lacks_question', 'severity' => 'low', 'why' => 'FAQ-style content benefits from question titles', 'hint' => 'Consider reformatting as "How to..." or "What is..." question', ); } // Lacks specific entities $has_entity = preg_match('/\b([A-Z][a-z]+(?:\s+[A-Z][a-z]+)?)\b/', $title); if (!$has_entity && $title_words > 3) { $issues[] = array( 'id' => 'title_lacks_entity', 'severity' => 'high', 'why' => 'Titles without specific entities are less discoverable', 'hint' => 'Add a specific tool, product, concept, or brand name', ); } // Late answer pattern (answer buried in title) if (preg_match('/\b(the answer|here\'s how|learn|discover)\b/i', $title)) { $issues[] = array( 'id' => 'title_late_answer', 'severity' => 'medium', 'why' => 'Title promises answer but doesn\'t deliver it immediately', 'hint' => 'Front-load the key insight or answer in the title', ); } return $issues; } /** * Check intro/early answer issues * * @param string $content HTML content * @param string $clean_content Clean text * @return array Issues */ private function check_intro_issues($content, $clean_content) { $issues = array(); // Extract first 2-3 paragraphs preg_match_all('/
]*>(.*?)<\/p>/is', $content, $paragraphs);
$first_paras = array_slice($paragraphs[1], 0, 3);
$intro_text = implode(' ', array_map('wp_strip_all_tags', $first_paras));
$intro_words = str_word_count($intro_text);
// No early answer summary
if ($intro_words < 30) {
$issues[] = array(
'id' => 'no_early_answer',
'severity' => 'high',
'why' => 'AEO requires a concise answer in the opening 2-3 sentences',
'hint' => 'Add a 2-sentence summary answering the core query at the very top',
);
}
// First sentence is too long (reduces scannability)
$sentences = preg_split('/[.!?]+/', $intro_text, -1, PREG_SPLIT_NO_EMPTY);
if (!empty($sentences)) {
$first_sentence_words = str_word_count($sentences[0]);
if ($first_sentence_words > 25) {
$issues[] = array(
'id' => 'first_sentence_too_long',
'severity' => 'medium',
'why' => 'Long opening sentences reduce scannability for LLMs',
'hint' => 'Break first sentence into 2 shorter sentences (15-20 words each)',
);
}
}
// Lacks definition in intro
$has_definition = preg_match('/\b(?:is|means|refers to|defined as)\b/i', $intro_text);
if (!$has_definition && strlen($intro_text) > 100) {
$issues[] = array(
'id' => 'intro_lacks_definition',
'severity' => 'medium',
'why' => 'Early definitions help LLMs categorize and cite content',
'hint' => 'Add a simple "X is..." definition in first 2 sentences',
);
}
return $issues;
}
/**
* Check header structure issues
*
* @param string $content HTML content
* @return array Issues
*/
private function check_header_issues($content) {
$issues = array();
// Extract all headers
preg_match_all('/ ]*>(.*?)<\/p>/is', $content, $paragraphs);
$dense_para_count = 0;
foreach ($paragraphs[1] as $para) {
$para_words = str_word_count(wp_strip_all_tags($para));
if ($para_words > 100) {
$dense_para_count++;
}
}
if ($dense_para_count > 2) {
$issues[] = array(
'id' => 'dense_paragraphs',
'severity' => 'medium',
'why' => 'Paragraphs over 100 words reduce scannability',
'hint' => 'Break dense paragraphs into 2-3 shorter ones (40-80 words)',
);
}
// Missing citations/sources
$has_citations = preg_match('/\b(according to|research shows|study found|source:|via)\b/i', $clean_content);
if (!$has_citations && str_word_count($clean_content) > 800) {
$issues[] = array(
'id' => 'no_citations',
'severity' => 'low',
'why' => 'Citations improve trustworthiness for LLMs',
'hint' => 'Add 1-2 references to authoritative sources or studies',
);
}
return $issues;
}
/**
* Check link issues
*
* @param string $content HTML content
* @return array Issues
*/
private function check_link_issues($content) {
$issues = array();
// Extract all links
preg_match_all('/]+href=["\']([^"\']+)["\'][^>]*>(.*?)<\/a>/is', $content, $links);
$link_count = count($links[0]);
if ($link_count === 0) {
$issues[] = array(
'id' => 'no_links',
'severity' => 'high',
'why' => 'Links signal topical context and authority to LLMs',
'hint' => 'Add 2-3 contextual links (1 internal, 1-2 external authority)',
);
return $issues;
}
// Categorize links
$internal_links = 0;
$external_links = 0;
$current_domain = wp_parse_url(home_url(), PHP_URL_HOST);
foreach ($links[1] as $url) {
$link_domain = wp_parse_url($url, PHP_URL_HOST);
if ($link_domain === $current_domain || strpos($url, '/') === 0) {
$internal_links++;
} else {
$external_links++;
}
}
// No internal deep links
if ($internal_links === 0) {
$issues[] = array(
'id' => 'no_internal_links',
'severity' => 'medium',
'why' => 'Internal links to cornerstone content strengthen topical authority',
'hint' => 'Link to 1-2 related internal posts or pillar pages',
);
}
// No external authority links
if ($external_links === 0) {
$issues[] = array(
'id' => 'no_external_links',
'severity' => 'medium',
'why' => 'External links to authority sites signal research depth',
'hint' => 'Add 1-2 links to authoritative sources (.edu, .gov, industry leaders)',
);
}
return $issues;
}
/**
* Check schema opportunities
*
* @param string $content HTML content
* @param string $clean_content Clean text
* @param string $title Post title
* @return array Issues
*/
private function check_schema_opportunities($content, $clean_content, $title) {
$issues = array();
// FAQ opportunity
$question_count = substr_count($clean_content, '?');
if ($question_count > 2) {
preg_match_all('/ ]*>(.*?)<\/p>/is', $content, $paragraphs);
$dense_paras = false;
foreach ($paragraphs[1] as $para) {
$para_words = str_word_count(wp_strip_all_tags($para));
if ($para_words > 100) {
$dense_paras = true;
break;
}
}
// Extract header count
preg_match_all('/