plugin = ai_seo_article_generator();
$this->init_hooks();
}
private function init_hooks() {
add_action('wp_head', array($this, 'inject_aeo_schema'), 5);
add_filter('ai_seo_article_generator_schema_types', array($this, 'get_available_schema_types'));
}
/**
* Main method to inject AEO-optimized schema markup
*/
public function inject_aeo_schema() {
global $post;
if (!is_single() || !$post || !get_post_meta($post->ID, 'ai_seo_article_generator_generated', true)) {
return;
}
$content = get_post_field('post_content', $post->ID);
$content_type = $this->detect_content_type($content);
$schemas = array();
switch ($content_type) {
case 'howto':
$schemas[] = $this->generate_howto_schema($post->ID, $content);
break;
case 'qa':
$schemas[] = $this->generate_qapage_schema($post->ID, $content);
break;
case 'review':
$schemas[] = $this->generate_review_schema($post->ID, $content);
break;
case 'video':
$schemas[] = $this->generate_video_schema($post->ID, $content);
break;
default:
$schemas[] = $this->generate_enhanced_article_schema($post->ID, $content);
break;
}
// Always add FAQ schema if Q&A patterns detected
$faq_schema = $this->generate_enhanced_faq_schema($post->ID, $content);
if ($faq_schema) {
$schemas[] = $faq_schema;
}
// Output all schemas
foreach ($schemas as $schema) {
if ($schema) {
echo '' . "\n";
}
}
}
/**
* Auto-detect content type based on content analysis
*/
public function detect_content_type($content) {
$text = wp_strip_all_tags($content);
// Hebrew and English patterns for different content types
$patterns = array(
'howto' => array(
'hebrew' => '/(?:שלבים?|צעדים?|אופן|איך|כיצד|מדריך|הוראות|ביצוע)/ui',
'english' => '/(?:steps?|how\s+to|guide|instructions|tutorial|process)/ui'
),
'qa' => array(
'hebrew' => '/(?:שאלות?\s+ותשובות?|ש[:\-]|ת[:\-]|שאלה|תשובה)/ui',
'english' => '/(?:q[\s\:&]*a|question|answer|faq)/ui'
),
'review' => array(
'hebrew' => '/(?:ביקורת|סקירה|דירוג|הערכה|המלצה|כוכבים)/ui',
'english' => '/(?:review|rating|stars?|recommendation|evaluation)/ui'
),
'video' => array(
'hebrew' => '/(?:וידאו|סרטון|צפייה|צפו|רצח)/ui',
'english' => '/(?:video|watch|play|youtube|vimeo)/ui'
)
);
$scores = array();
foreach ($patterns as $type => $pattern_set) {
$scores[$type] = 0;
foreach ($pattern_set as $lang => $pattern) {
$matches = preg_match_all($pattern, $text);
if ($matches) {
$scores[$type] += $matches;
}
}
}
// Return the type with highest score, or 'article' as default
$max_score = max($scores);
if ($max_score >= 2) {
return array_search($max_score, $scores);
}
return 'article';
}
/**
* Generate HowTo schema for step-by-step content
*/
public function generate_howto_schema($post_id, $content) {
$post = get_post($post_id);
// Extract steps from content
$steps = $this->extract_steps($content);
if (empty($steps)) {
return false;
}
$schema = array(
'@context' => 'https://schema.org',
'@type' => 'HowTo',
'name' => get_the_title($post_id),
'description' => $this->get_post_excerpt($post_id),
'image' => $this->get_post_image($post_id),
'step' => array()
);
foreach ($steps as $index => $step) {
$schema['step'][] = array(
'@type' => 'HowToStep',
'position' => $index + 1,
'name' => $step['title'],
'text' => $step['description']
);
}
// Add estimated time if detectable
$estimated_time = $this->extract_estimated_time($content);
if ($estimated_time) {
$schema['totalTime'] = $estimated_time;
}
// Add tools/supplies if mentioned
$tools = $this->extract_tools_supplies($content);
if (!empty($tools)) {
$schema['tool'] = array_map(function($tool) {
return array(
'@type' => 'HowToTool',
'name' => $tool
);
}, $tools);
}
return $schema;
}
/**
* Generate QAPage schema for Q&A content
*/
public function generate_qapage_schema($post_id, $content) {
$qa_pairs = $this->extract_qa_pairs($content);
if (empty($qa_pairs)) {
return false;
}
$schema = array(
'@context' => 'https://schema.org',
'@type' => 'QAPage',
'mainEntity' => array()
);
foreach ($qa_pairs as $qa) {
$schema['mainEntity'][] = array(
'@type' => 'Question',
'name' => $qa['question'],
'acceptedAnswer' => array(
'@type' => 'Answer',
'text' => $qa['answer'],
'author' => array(
'@type' => 'Person',
'name' => get_the_author_meta('display_name', get_post($post_id)->post_author)
)
)
);
}
return $schema;
}
/**
* Generate Review schema for review content
*/
public function generate_review_schema($post_id, $content) {
$review_data = $this->extract_review_data($content);
if (!$review_data) {
return false;
}
$schema = array(
'@context' => 'https://schema.org',
'@type' => 'Review',
'itemReviewed' => array(
'@type' => 'Thing',
'name' => $review_data['item_name']
),
'author' => array(
'@type' => 'Person',
'name' => get_the_author_meta('display_name', get_post($post_id)->post_author)
),
'reviewBody' => $review_data['review_text'],
'datePublished' => get_the_date('c', $post_id)
);
if (isset($review_data['rating'])) {
$schema['reviewRating'] = array(
'@type' => 'Rating',
'ratingValue' => $review_data['rating'],
'bestRating' => $review_data['max_rating'] ?? 5
);
}
return $schema;
}
/**
* Generate VideoObject schema for video content
*/
public function generate_video_schema($post_id, $content) {
$video_data = $this->extract_video_data($content);
if (!$video_data) {
return false;
}
$schema = array(
'@context' => 'https://schema.org',
'@type' => 'VideoObject',
'name' => get_the_title($post_id),
'description' => $this->get_post_excerpt($post_id),
'thumbnailUrl' => $this->get_post_image($post_id),
'uploadDate' => get_the_date('c', $post_id)
);
if (isset($video_data['url'])) {
$schema['contentUrl'] = $video_data['url'];
}
if (isset($video_data['duration'])) {
$schema['duration'] = $video_data['duration'];
}
return $schema;
}
/**
* Generate enhanced article schema with AEO optimizations
*/
public function generate_enhanced_article_schema($post_id, $content) {
$post = get_post($post_id);
$main_keyword = get_post_meta($post_id, 'ai_seo_article_generator_main_keyword', true);
$sub_keywords = get_post_meta($post_id, 'ai_seo_article_generator_sub_keywords', true);
$is_hebrew = get_post_meta($post_id, 'ai_seo_article_generator_is_hebrew', true);
$schema = array(
'@context' => 'https://schema.org',
'@type' => 'Article',
'headline' => get_the_title($post_id),
'description' => $this->get_post_excerpt($post_id),
'author' => array(
'@type' => 'Person',
'name' => get_the_author_meta('display_name', $post->post_author),
'url' => get_author_posts_url($post->post_author)
),
'publisher' => array(
'@type' => 'Organization',
'name' => get_bloginfo('name'),
'url' => home_url(),
'logo' => array(
'@type' => 'ImageObject',
'url' => get_site_icon_url(60)
)
),
'datePublished' => get_the_date('c', $post_id),
'dateModified' => get_the_modified_date('c', $post_id),
'mainEntityOfPage' => array(
'@type' => 'WebPage',
'@id' => get_permalink($post_id)
),
'image' => $this->get_post_image($post_id)
);
// Add keywords
if ($main_keyword && $sub_keywords) {
$schema['keywords'] = $main_keyword . ', ' . $sub_keywords;
}
// Add language
if ($is_hebrew) {
$schema['inLanguage'] = 'he-IL';
}
// Add article sections for better structure
$headings = $this->extract_headings($content);
if (!empty($headings)) {
$schema['articleSection'] = array_map(function($h) {
return $h['text'];
}, $headings);
}
// Add word count for AEO
$word_count = str_word_count(wp_strip_all_tags($content));
$schema['wordCount'] = $word_count;
// Add citation-ready abstract
$abstract = $this->generate_citation_abstract($content, $main_keyword);
if ($abstract) {
$schema['abstract'] = $abstract;
}
return $schema;
}
/**
* Enhanced FAQ schema with better Hebrew pattern recognition
*/
public function generate_enhanced_faq_schema($post_id, $content) {
$text = wp_strip_all_tags($content);
// Enhanced Hebrew Q&A patterns
$hebrew_patterns = array(
'/(?:^|\n)(?:שאלה\s*[\d\)\.]*\s*[\:\-]?\s*)(.+?)\n(?:תשובה\s*[\d\)\.]*\s*[\:\-]?\s*)(.+?)(?=\n(?:שאלה|$))/uis',
'/(?:^|\n)(?:ש\s*[\d\)\.]*\s*[\:\-]\s*)(.+?)\n(?:ת\s*[\d\)\.]*\s*[\:\-]\s*)(.+?)(?=\n(?:ש\s*[\d\)\.]*\s*[\:\-]|$))/uis',
'/(?:מהו|מה זה|איך|כיצד|למה|מתי|איפה|מדוע)\s+(.+?)\?\s*\n(.+?)(?=\n(?:מהו|מה זה|איך|כיצד|למה|מתי|איפה|מדוע)|$)/uis'
);
// English Q&A patterns
$english_patterns = array(
'/(?:^|\n)(?:Q\s*[\d\)\.]*\s*[\:\-]?\s*)(.+?)\n(?:A\s*[\d\)\.]*\s*[\:\-]?\s*)(.+?)(?=\n(?:Q\s*[\d\)\.]*\s*[\:\-]|$))/uis',
'/(?:What|How|Why|When|Where|Which)\s+(.+?)\?\s*\n(.+?)(?=\n(?:What|How|Why|When|Where|Which)|$)/uis'
);
$faqs = array();
// Process Hebrew patterns
foreach ($hebrew_patterns as $pattern) {
if (preg_match_all($pattern, $text, $matches, PREG_SET_ORDER)) {
foreach ($matches as $match) {
$question = trim($match[1]);
$answer = trim($match[2]);
if (strlen($question) > 10 && strlen($answer) > 20) {
$faqs[] = array(
'@type' => 'Question',
'name' => $this->clean_hebrew_text($question),
'acceptedAnswer' => array(
'@type' => 'Answer',
'text' => $this->clean_hebrew_text($answer),
'author' => array(
'@type' => 'Person',
'name' => get_the_author_meta('display_name', get_post($post_id)->post_author)
)
)
);
}
}
}
}
// Process English patterns
foreach ($english_patterns as $pattern) {
if (preg_match_all($pattern, $text, $matches, PREG_SET_ORDER)) {
foreach ($matches as $match) {
$question = trim($match[1]);
$answer = trim($match[2]);
if (strlen($question) > 10 && strlen($answer) > 20) {
$faqs[] = array(
'@type' => 'Question',
'name' => $question,
'acceptedAnswer' => array(
'@type' => 'Answer',
'text' => $answer,
'author' => array(
'@type' => 'Person',
'name' => get_the_author_meta('display_name', get_post($post_id)->post_author)
)
)
);
}
}
}
}
if (count($faqs) >= 2) {
return array(
'@context' => 'https://schema.org',
'@type' => 'FAQPage',
'mainEntity' => $faqs
);
}
return false;
}
/**
* Get available schema types for admin interface
*/
public function get_available_schema_types() {
return array(
'article' => __('Article', 'ai-seo-article-generator'),
'howto' => __('How-To Guide', 'ai-seo-article-generator'),
'qa' => __('Q&A Page', 'ai-seo-article-generator'),
'review' => __('Review', 'ai-seo-article-generator'),
'video' => __('Video Content', 'ai-seo-article-generator'),
'faq' => __('FAQ Page', 'ai-seo-article-generator')
);
}
// Helper methods
private function extract_steps($content) {
$text = wp_strip_all_tags($content);
$steps = array();
// Hebrew step patterns
$hebrew_patterns = array(
'/(?:שלב\s*[\d\)\.]+\s*[\:\-]?\s*)(.+?)(?:\n(.+?))?(?=\n(?:שלב\s*[\d\)\.]+|$))/uis',
'/(?:צעד\s*[\d\)\.]+\s*[\:\-]?\s*)(.+?)(?:\n(.+?))?(?=\n(?:צעד\s*[\d\)\.]+|$))/uis'
);
// English step patterns
$english_patterns = array(
'/(?:step\s*[\d\)\.]+\s*[\:\-]?\s*)(.+?)(?:\n(.+?))?(?=\n(?:step\s*[\d\)\.]+|$))/uis',
'/(?:^\d+\.\s*)(.+?)(?:\n(.+?))?(?=\n(?:\d+\.|$))/uis'
);
$all_patterns = array_merge($hebrew_patterns, $english_patterns);
foreach ($all_patterns as $pattern) {
if (preg_match_all($pattern, $text, $matches, PREG_SET_ORDER)) {
foreach ($matches as $match) {
$title = trim($match[1]);
$description = isset($match[2]) ? trim($match[2]) : '';
if (strlen($title) > 5) {
$steps[] = array(
'title' => $title,
'description' => $description ?: $title
);
}
}
break; // Use first matching pattern
}
}
return $steps;
}
private function extract_qa_pairs($content) {
$text = wp_strip_all_tags($content);
$qa_pairs = array();
// Use enhanced FAQ extraction logic
$temp_schema = $this->generate_enhanced_faq_schema(0, $content);
if ($temp_schema && isset($temp_schema['mainEntity'])) {
foreach ($temp_schema['mainEntity'] as $item) {
$qa_pairs[] = array(
'question' => $item['name'],
'answer' => $item['acceptedAnswer']['text']
);
}
}
return $qa_pairs;
}
private function extract_review_data($content) {
$text = wp_strip_all_tags($content);
// Look for rating patterns
$rating_patterns = array(
'/(?:דירוג|ציון|כוכבים?)\s*[\:\-]?\s*(\d+(?:\.\d+)?)\s*(?:מתוך|\/)\s*(\d+)/ui',
'/(?:rating|score|stars?)\s*[\:\-]?\s*(\d+(?:\.\d+)?)\s*(?:out\s+of|\/)\s*(\d+)/ui',
'/(\d+(?:\.\d+)?)\s*(?:כוכבים?|stars?)/ui'
);
$rating = null;
$max_rating = 5;
foreach ($rating_patterns as $pattern) {
if (preg_match($pattern, $text, $matches)) {
$rating = floatval($matches[1]);
if (isset($matches[2])) {
$max_rating = intval($matches[2]);
}
break;
}
}
if ($rating === null) {
return false;
}
// Extract item name (usually from title or first heading)
$item_name = get_the_title();
return array(
'rating' => $rating,
'max_rating' => $max_rating,
'item_name' => $item_name,
'review_text' => substr($text, 0, 500) // First 500 chars as review excerpt
);
}
private function extract_video_data($content) {
// Look for video URLs or embeds
if (preg_match('/(?:youtube\.com\/watch\?v=|youtu\.be\/|vimeo\.com\/)([^\s&]+)/i', $content, $matches)) {
return array(
'url' => $matches[0],
'duration' => null // Could be enhanced to extract duration
);
}
return false;
}
private function extract_estimated_time($content) {
$text = wp_strip_all_tags($content);
// Time patterns in Hebrew and English
$time_patterns = array(
'/(?:זמן\s+ביצוע|משך\s+זמן|לוקח)\s*[\:\-]?\s*(\d+)\s*(?:דקות?|שעות?)/ui',
'/(?:takes?|duration|time)\s*[\:\-]?\s*(\d+)\s*(?:minutes?|hours?|mins?)/ui'
);
foreach ($time_patterns as $pattern) {
if (preg_match($pattern, $text, $matches)) {
$time = intval($matches[1]);
$unit = 'M'; // Default to minutes
if (strpos(strtolower($matches[0]), 'hour') !== false ||
strpos($matches[0], 'שעה') !== false) {
$unit = 'H';
}
return 'PT' . $time . $unit;
}
}
return null;
}
private function extract_tools_supplies($content) {
$text = wp_strip_all_tags($content);
$tools = array();
// Look for tool/supply lists
$tool_patterns = array(
'/(?:כלים|ציוד|חומרים)\s*[\:\-]?\s*(.+?)(?=\n\n|\n[א-ת]|\n[A-Z]|$)/uis',
'/(?:tools?|supplies|materials?|equipment)\s*[\:\-]?\s*(.+?)(?=\n\n|\n[A-Z]|$)/uis'
);
foreach ($tool_patterns as $pattern) {
if (preg_match($pattern, $text, $matches)) {
$tool_text = $matches[1];
// Split by common separators
$items = preg_split('/[\n\-\*\d\.\)]+/', $tool_text);
foreach ($items as $item) {
$item = trim($item);
if (strlen($item) > 3 && strlen($item) < 100) {
$tools[] = $item;
}
}
break;
}
}
return array_slice($tools, 0, 10); // Limit to 10 tools
}
private function extract_headings($content) {
$headings = array();
if (preg_match_all('/]*>(.+?)<\/h[1-6]>/i', $content, $matches, PREG_SET_ORDER)) {
foreach ($matches as $match) {
$headings[] = array(
'level' => intval($match[1]),
'text' => wp_strip_all_tags($match[2])
);
}
}
return $headings;
}
private function generate_citation_abstract($content, $main_keyword) {
$text = wp_strip_all_tags($content);
$sentences = preg_split('/[.!?]+/', $text);
// Find sentences containing the main keyword
$relevant_sentences = array();
foreach ($sentences as $sentence) {
$sentence = trim($sentence);
if (strlen($sentence) > 20 && strlen($sentence) < 200) {
if (stripos($sentence, $main_keyword) !== false) {
$relevant_sentences[] = $sentence;
}
}
}
if (!empty($relevant_sentences)) {
// Return the first relevant sentence as abstract (40-60 words ideal for AI citation)
$abstract = $relevant_sentences[0];
$word_count = str_word_count($abstract);
if ($word_count > 60) {
$words = explode(' ', $abstract);
$abstract = implode(' ', array_slice($words, 0, 55)) . '...';
}
return $abstract;
}
return null;
}
private function get_post_excerpt($post_id) {
$excerpt = get_the_excerpt($post_id);
if (!$excerpt) {
$content = get_post_field('post_content', $post_id);
$excerpt = wp_trim_words(wp_strip_all_tags($content), 30);
}
return $excerpt;
}
private function get_post_image($post_id) {
$image_url = get_the_post_thumbnail_url($post_id, 'large');
if ($image_url) {
return array(
'@type' => 'ImageObject',
'url' => $image_url,
'width' => 1200,
'height' => 630
);
}
// Fallback to site icon
$site_icon = get_site_icon_url(512);
if ($site_icon) {
return array(
'@type' => 'ImageObject',
'url' => $site_icon,
'width' => 512,
'height' => 512
);
}
return null;
}
private function clean_hebrew_text($text) {
if (!$text) return '';
// Remove multiple backslashes and fix encoding issues
$cleaned = $text;
$cleaned = preg_replace('/```html\s*/', '', $cleaned);
$cleaned = preg_replace('/```\s*/', '', $cleaned);
$cleaned = preg_replace('/\\\\+\'/', "'", $cleaned);
$cleaned = preg_replace('/\\\\+"/', '"', $cleaned);
$cleaned = html_entity_decode($cleaned, ENT_QUOTES | ENT_HTML5, 'UTF-8');
$cleaned = stripslashes($cleaned);
$cleaned = preg_replace('/\s+/', ' ', $cleaned);
return trim($cleaned);
}
}