is_hebrew_locale()) {
add_filter('gettext', array($this, 'force_hebrew_translations'), 10, 3);
add_filter('gettext_with_context', array($this, 'force_hebrew_translations_with_context'), 10, 4);
}
// Add custom cron schedule
add_filter('cron_schedules', array($this, 'add_custom_cron_schedules'));
// Ensure tables exist on every admin load
if (is_admin()) {
$this->ensure_tables_exist();
$this->load_admin();
}
// Load background processor
$this->load_background_processor();
// Load AEO Schema Manager
$this->load_aeo_schema();
add_action('wp_enqueue_scripts', array($this, 'enqueue_scripts'));
add_action('admin_enqueue_scripts', array($this, 'admin_enqueue_scripts'));
// Add frontend hooks for meta tags and RTL styling
add_action('wp_head', array($this, 'inject_ai_seo_article_generator_meta_tags'));
add_action('wp_head', array($this, 'inject_ai_seo_article_generator_rtl_styles'));
}
public function activate() {
$this->create_tables();
if (!wp_next_scheduled('ai_seo_article_generator_cleanup_drafts')) {
wp_schedule_event(time(), 'daily', 'ai_seo_article_generator_cleanup_drafts');
}
// Schedule background processing
if (!wp_next_scheduled('ai_seo_article_generator_process_queue')) {
wp_schedule_event(time(), 'ai_seo_article_generator_every_minute', 'ai_seo_article_generator_process_queue');
}
// Migrate old options
$this->migrate_options();
add_option('ai_seo_article_generator_version', AI_SEO_ARTICLE_GENERATOR_VERSION);
add_option('ai_seo_article_generator_api_key', '');
add_option('ai_seo_article_generator_openai_api_key', '');
add_option('ai_seo_article_generator_ai_provider', 'claude');
add_option('ai_seo_article_generator_openai_model', 'gpt-4o');
add_option('ai_seo_article_generator_api_connected', false);
add_option('ai_seo_article_generator_debug_logging', 1); // Enable debug logging by default
}
public function deactivate() {
wp_clear_scheduled_hook('ai_seo_article_generator_cleanup_drafts');
wp_clear_scheduled_hook('ai_seo_article_generator_process_queue');
}
// Removed load_textdomain function - WordPress.org handles translations automatically
/**
* Detect if WordPress is using Hebrew language
*/
public function is_hebrew_locale() {
$locale = get_locale();
// Debug: Log current locale for troubleshooting
// error_log('AI SEO Article Generator - Current WordPress locale: ' . $locale);
// Check for various Hebrew locale formats
$hebrew_locales = array('he_IL', 'he', 'hebrew', 'Hebrew', 'HE_IL', 'HE');
$is_hebrew = in_array($locale, $hebrew_locales) || strpos($locale, 'he') === 0;
// Also check for RTL direction as fallback
if (!$is_hebrew && function_exists('is_rtl')) {
$is_hebrew = is_rtl();
}
// error_log('AI SEO Article Generator - Is Hebrew detected: ' . ($is_hebrew ? 'YES' : 'NO'));
return $is_hebrew;
}
/**
* Get text direction based on WordPress locale
*/
public function get_text_direction() {
return $this->is_hebrew_locale() ? 'rtl' : 'ltr';
}
/**
* Get text alignment based on WordPress locale
*/
public function get_text_alignment() {
return $this->is_hebrew_locale() ? 'right' : 'left';
}
/**
* Force Hebrew translations when .mo file is not available
*/
public function force_hebrew_translations($translation, $text, $domain) {
if ($domain !== 'ai-seo-article-generator') {
return $translation;
}
static $hebrew_translations = null;
static $translation_loaded = false;
// Load translations from PHP file if available
if ($hebrew_translations === null) {
$php_translation_file = plugin_dir_path(__FILE__) . 'languages/ai-seo-article-generator-he_IL.php';
if (file_exists($php_translation_file)) {
$hebrew_translations = include $php_translation_file;
// error_log('AI SEO Article Generator - Loaded PHP translations: ' . count($hebrew_translations) . ' entries');
$translation_loaded = true;
} else {
$hebrew_translations = array();
// error_log('AI SEO Article Generator - PHP translation file not found: ' . $php_translation_file);
}
}
// Fallback hardcoded translations if PHP file not available
if (empty($hebrew_translations)) {
$hebrew_translations = array(
'AI SEO Article Generator' => 'מחולל מאמרי SEO בינה מלאכותית',
'AI SEO Generator' => 'מחולל SEO AI',
'Settings' => 'הגדרות',
'Create New Article' => 'יצירת מאמר חדש',
'New Article' => 'מאמר חדש',
'Article Drafts' => 'טיוטות מאמרים',
'Drafts' => 'טיוטות',
'Structure Library' => 'ספריית מבנים',
'Saved Structures' => 'מבנים שמורים',
'Notice:' => 'שימו לב:',
'You need to configure your Claude Sonnet 4 API key to use this plugin.' => 'נדרש להגדיר את מפתח ה-API של Claude Sonnet 4 כדי להשתמש בתוסף.',
'Go to Settings' => 'עבור להגדרות',
'Start Now' => 'התחל עכשיו',
'Saved Drafts' => 'טיוטות שמורות',
'Manage and edit article drafts saved in the system' => 'נהל ועזר טיוטות מאמרים שנשמרו במערכת',
'View Drafts' => 'צפה בטיוטות',
'System Settings' => 'הגדרות מערכת',
'Configure your API key and additional options' => 'הגדר את מפתח ה-API ואפשרויות נוספות',
'Plugin Features' => 'תכונות התוסף',
'Advanced SEO:' => 'SEO מתקדם:',
'Complete optimization for search engines' => 'אופטימיזציה מלאה למנועי החיפוש',
'AEO:' => 'AEO:',
'Optimization for answer engines' => 'אופטימיזציה למנועי תשובות',
'GEO:' => 'GEO:',
'Optimization for generative engines' => 'אופטימיזציה למנועי יצירה',
'Natural Hebrew:' => 'עברית טבעית:',
'Professional content in Hebrew language' => 'תוכן מקצועי בשפה עברית',
'Smart Editing:' => 'עריכה חכמה:',
'Enhance specific sections with AI' => 'שיפור חלקים ספציפיים בעזרת AI',
'Claude Sonnet 4 API Key' => 'מפתח API של Claude Sonnet 4',
'Test Connection' => 'בדוק חיבור',
'Testing connection...' => 'בודק חיבור...',
'Connection successful!' => 'החיבור הצליח!',
'Connection failed. Please check your API key.' => 'החיבור נכשל. אנא בדוק את מפתח ה-API שלך.',
'Database Status' => 'מצב מסד הנתונים',
'Tables Exist' => 'טבלאות קיימות',
'Tables Missing' => 'טבלאות חסרות',
'Recreate Tables' => 'צור טבלאות מחדש',
'Save Settings' => 'שמור הגדרות'
);
}
// Debug translation requests for our domain
if (isset($hebrew_translations[$text])) {
// error_log('AI SEO Article Generator - Translating: "' . $text . '" -> "' . $hebrew_translations[$text] . '"');
return $hebrew_translations[$text];
} else {
// error_log('AI SEO Article Generator - No translation found for: "' . $text . '"');
return $translation;
}
}
/**
* Force Hebrew translations with context when .mo file is not available
*/
public function force_hebrew_translations_with_context($translation, $text, $context, $domain) {
if ($domain !== 'ai-seo-article-generator') {
return $translation;
}
return $this->force_hebrew_translations($translation, $text, $domain);
}
/**
* Debug function to test language detection
*/
public function debug_language_detection() {
if (current_user_can('manage_options')) {
$locale = get_locale();
$is_hebrew = $this->is_hebrew_locale();
$direction = $this->get_text_direction();
$alignment = $this->get_text_alignment();
$this->debug_log("Language Detection Debug:");
$this->debug_log("Locale: " . $locale);
$this->debug_log("Is Hebrew: " . ($is_hebrew ? 'Yes' : 'No'));
$this->debug_log("Text Direction: " . $direction);
$this->debug_log("Text Alignment: " . $alignment);
}
}
public function create_tables() {
global $wpdb;
$charset_collate = $wpdb->get_charset_collate();
$article_drafts_table = $wpdb->prefix . 'ai_seo_article_generator_drafts';
$article_structures_table = $wpdb->prefix . 'ai_seo_article_generator_structures';
$saved_structures_table = $wpdb->prefix . 'ai_seo_article_generator_saved_structures';
// Create drafts table first
$sql_drafts = "CREATE TABLE $article_drafts_table (
id mediumint(9) NOT NULL AUTO_INCREMENT,
title varchar(255) DEFAULT '',
main_keyword varchar(255) NOT NULL,
sub_keywords text,
target_words int(6) DEFAULT 1000,
structure_data longtext,
content longtext,
status varchar(20) DEFAULT 'draft',
background_status varchar(20) DEFAULT 'none',
wp_post_id mediumint(9) DEFAULT NULL,
word_count int(6) DEFAULT 0,
outbound_links longtext,
created_at datetime DEFAULT CURRENT_TIMESTAMP,
updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY main_keyword (main_keyword),
KEY status (status),
KEY background_status (background_status),
KEY wp_post_id (wp_post_id),
KEY word_count (word_count),
KEY created_at (created_at)
) $charset_collate;";
// Create structures table without foreign key constraint (WordPress doesn't support it well with dbDelta)
$sql_structures = "CREATE TABLE $article_structures_table (
id mediumint(9) NOT NULL AUTO_INCREMENT,
draft_id mediumint(9) NOT NULL,
structure_json longtext NOT NULL,
is_approved tinyint(1) DEFAULT 0,
created_at datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY draft_id (draft_id),
KEY is_approved (is_approved)
) $charset_collate;";
// Create saved structures table
$sql_saved_structures = "CREATE TABLE $saved_structures_table (
id mediumint(9) NOT NULL AUTO_INCREMENT,
name varchar(255) NOT NULL,
description text,
main_keyword varchar(255) NOT NULL,
sub_keywords text,
target_words int(6) DEFAULT 1000,
structure_data longtext NOT NULL,
usage_count int(6) DEFAULT 0,
created_at datetime DEFAULT CURRENT_TIMESTAMP,
updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY name (name),
KEY main_keyword (main_keyword),
KEY created_at (created_at)
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
// phpcs:disable WordPress.DB.DirectDatabaseQuery.SchemaChange -- Table creation required for plugin functionality
$result_drafts = dbDelta($sql_drafts);
$result_structures = dbDelta($sql_structures);
$result_saved_structures = dbDelta($sql_saved_structures);
// phpcs:enable WordPress.DB.DirectDatabaseQuery.SchemaChange
// Log the results for debugging
$this->debug_log('Table creation results - Drafts', $result_drafts);
$this->debug_log('Table creation results - Structures', $result_structures);
$this->debug_log('Table creation results - Saved Structures', $result_saved_structures);
// Verify tables were created
$tables_created = true;
if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $article_drafts_table)) != $article_drafts_table) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery
$this->debug_log('Failed to create drafts table');
$tables_created = false;
}
if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $article_structures_table)) != $article_structures_table) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery
$this->debug_log('Failed to create structures table');
$tables_created = false;
}
if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $saved_structures_table)) != $saved_structures_table) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery
$this->debug_log('Failed to create saved structures table');
$tables_created = false;
}
if ($tables_created) {
$this->debug_log('All tables created successfully');
update_option('ai_seo_article_generator_tables_created', true);
} else {
$this->debug_log('Table creation failed');
update_option('ai_seo_article_generator_tables_created', false);
}
}
public function inject_ai_seo_article_generator_meta_tags() {
global $post;
// Only inject on single posts that are AI SEO Article Generator generated
if (!is_single() || !$post || !get_post_meta($post->ID, 'ai_seo_article_generator_generated', true)) {
return;
}
$this->inject_comprehensive_meta_tags($post->ID);
}
public function inject_ai_seo_article_generator_rtl_styles() {
global $post;
// Only inject on single posts that are AI SEO Article Generator generated and Hebrew
if (!is_single() || !$post || !get_post_meta($post->ID, 'ai_seo_article_generator_generated', true)) {
return;
}
$is_hebrew = get_post_meta($post->ID, 'ai_seo_article_generator_is_hebrew', true);
if ($is_hebrew) {
// Add body class for better CSS targeting
add_filter('body_class', array($this, 'add_hebrew_body_class'));
// Enqueue RTL styles instead of inline
wp_enqueue_style('ai-seo-article-generator-rtl', AI_SEO_ARTICLE_GENERATOR_PLUGIN_URL . 'assets/css/rtl-styles.css', array(), AI_SEO_ARTICLE_GENERATOR_VERSION);
}
}
public function add_hebrew_body_class($classes) {
$classes[] = 'ai-seo-article-generator-hebrew-content';
return $classes;
}
private function inject_comprehensive_meta_tags($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);
$meta_description = get_post_meta($post_id, 'description', true);
$post_title = get_the_title($post_id);
$post_url = get_permalink($post_id);
$is_hebrew = get_post_meta($post_id, 'ai_seo_article_generator_is_hebrew', true);
// Clean Hebrew text from escaping issues
$main_keyword = $this->clean_hebrew_text($main_keyword);
$sub_keywords = $this->clean_hebrew_text($sub_keywords);
$meta_description = $this->clean_hebrew_text($meta_description);
$post_title = $this->clean_hebrew_text($post_title);
echo "\n\n";
// Basic SEO Meta Tags
if ($meta_description) {
echo '' . "\n";
}
if ($main_keyword && $sub_keywords) {
echo '' . "\n";
}
echo '' . "\n";
echo '' . "\n";
// Language and direction
if ($is_hebrew) {
echo '' . "\n";
echo '' . "\n";
}
// Open Graph (Facebook, LinkedIn)
echo '' . "\n";
echo '' . "\n";
echo '' . "\n";
echo '' . "\n";
echo '' . "\n";
if ($is_hebrew) {
echo '' . "\n";
}
// Twitter Cards
echo '' . "\n";
echo '' . "\n";
echo '' . "\n";
// Schema.org JSON-LD for AEO
$schema = array(
'@context' => 'https://schema.org',
'@type' => 'Article',
'headline' => $post_title,
'description' => $meta_description,
'url' => $post_url,
'datePublished' => get_the_date('c', $post_id),
'dateModified' => get_the_modified_date('c', $post_id),
'author' => array(
'@type' => 'Organization',
'name' => get_bloginfo('name'),
'url' => home_url()
),
'publisher' => array(
'@type' => 'Organization',
'name' => get_bloginfo('name'),
'url' => home_url()
),
'mainEntityOfPage' => array(
'@type' => 'WebPage',
'@id' => $post_url
)
);
if ($main_keyword && $sub_keywords) {
$schema['keywords'] = $main_keyword . ', ' . $sub_keywords;
}
if ($is_hebrew) {
$schema['inLanguage'] = 'he-IL';
}
echo '' . "\n";
// Add FAQ Schema if Q&A content is detected
$faq_schema = $this->extract_faq_schema($post_id);
if ($faq_schema) {
echo '' . "\n";
}
// Add Breadcrumb Schema
$breadcrumb_schema = $this->generate_breadcrumb_schema($post_id);
if ($breadcrumb_schema) {
echo '' . "\n";
}
echo "\n\n";
}
// Removed inject_rtl_styles function - styles are now in rtl-styles.css
/**
* Detect theme-specific content selectors for better RTL support
*/
private function detect_theme_selectors() {
$current_theme = get_template();
$selectors = array();
// Common theme-specific selectors
$theme_patterns = array(
'twentytwentyone' => array('.entry-content', '.wp-block-post-content'),
'twentytwenty' => array('.entry-content', '.post-inner'),
'astra' => array('.entry-content', '.ast-article-single'),
'generatepress' => array('.entry-content', '.inside-article'),
'oceanwp' => array('.entry-content', '.single-post-content'),
'neve' => array('.entry-content', '.nv-content-wrap'),
'kadence' => array('.entry-content', '.entry-content-wrap'),
'blocksy' => array('.entry-content', '.post-content'),
'hello-elementor' => array('.entry-content', '.elementor-post'),
'storefront' => array('.entry-content', '.single-product-summary')
);
// Check if current theme has specific selectors
if (isset($theme_patterns[$current_theme])) {
$selectors = array_merge($selectors, $theme_patterns[$current_theme]);
}
// Add generic selectors that might work with unknown themes
$generic_selectors = array(
'.main-content',
'.site-main',
'.primary-content',
'.post-body',
'.article-content',
'.single-post-content'
);
$selectors = array_merge($selectors, $generic_selectors);
// Remove duplicates and return
return array_unique($selectors);
}
private function clean_hebrew_text($text) {
if (!$text) return '';
// Remove multiple backslashes before Hebrew apostrophes
$cleaned = $text;
// Remove markdown code blocks
$cleaned = preg_replace('/```html\s*/', '', $cleaned);
$cleaned = preg_replace('/```\s*/', '', $cleaned);
// Fix escaped apostrophes and quotes
$cleaned = preg_replace('/\\\\+\'/', "'", $cleaned);
$cleaned = preg_replace('/\\\\+"/', '"', $cleaned);
$cleaned = preg_replace('/\\\\\\\\/', "\\", $cleaned);
// Fix HTML entities
$cleaned = html_entity_decode($cleaned, ENT_QUOTES | ENT_HTML5, 'UTF-8');
// Remove any remaining weird escaping
$cleaned = stripslashes($cleaned);
// Normalize whitespace
$cleaned = preg_replace('/\s+/', ' ', $cleaned);
return trim($cleaned);
}
private function extract_faq_schema($post_id) {
$content = get_post_field('post_content', $post_id);
// Look for Q&A patterns in Hebrew
$qa_patterns = [
'/(?:שאלה|ש:)\s*[:\-]?\s*(.+?)\n(?:תשובה|ת:)\s*[:\-]?\s*(.+?)(?=\n(?:שאלה|ש:)|$)/uis',
'/(?:מהו|מה זה|איך|למה|מתי|איפה)\s+(.+?)\?\s*\n(.+?)(?=\n(?:מהו|מה זה|איך|למה|מתי|איפה)|$)/uis'
];
$faqs = [];
foreach ($qa_patterns as $pattern) {
if (preg_match_all($pattern, $content, $matches, PREG_SET_ORDER)) {
foreach ($matches as $match) {
$question = trim(wp_strip_all_tags($match[1]));
$answer = trim(wp_strip_all_tags($match[2]));
if (strlen($question) > 10 && strlen($answer) > 20) {
$faqs[] = [
'@type' => 'Question',
'name' => $this->clean_hebrew_text($question),
'acceptedAnswer' => [
'@type' => 'Answer',
'text' => $this->clean_hebrew_text($answer)
]
];
}
}
}
}
if (count($faqs) >= 2) {
return [
'@context' => 'https://schema.org',
'@type' => 'FAQPage',
'mainEntity' => $faqs
];
}
return false;
}
private function generate_breadcrumb_schema($post_id) {
$post = get_post($post_id);
$categories = get_the_category($post_id);
$breadcrumbs = [
[
'@type' => 'ListItem',
'position' => 1,
'name' => get_bloginfo('name'),
'item' => home_url()
]
];
$position = 2;
// Add category breadcrumbs
if (!empty($categories)) {
$main_category = $categories[0];
$breadcrumbs[] = [
'@type' => 'ListItem',
'position' => $position++,
'name' => $main_category->name,
'item' => get_category_link($main_category->term_id)
];
}
// Add current post
$breadcrumbs[] = [
'@type' => 'ListItem',
'position' => $position,
'name' => get_the_title($post_id),
'item' => get_permalink($post_id)
];
return [
'@context' => 'https://schema.org',
'@type' => 'BreadcrumbList',
'itemListElement' => $breadcrumbs
];
}
private function ensure_tables_exist() {
global $wpdb;
$article_drafts_table = $wpdb->prefix . 'ai_seo_article_generator_drafts';
$article_structures_table = $wpdb->prefix . 'ai_seo_article_generator_structures';
$saved_structures_table = $wpdb->prefix . 'ai_seo_article_generator_saved_structures';
// Check if tables exist
// phpcs:disable WordPress.DB.DirectDatabaseQuery -- Table existence check required
$drafts_exist = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $article_drafts_table)) == $article_drafts_table;
$structures_exist = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $article_structures_table)) == $article_structures_table;
$saved_structures_exist = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $saved_structures_table)) == $saved_structures_table;
// phpcs:enable WordPress.DB.DirectDatabaseQuery
if (!$drafts_exist || !$structures_exist || !$saved_structures_exist) {
$this->debug_log('Tables missing, creating them now...');
$this->create_tables();
} else {
// Check if wp_post_id column exists in drafts table
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery -- Table structure check required
$column_exists = $wpdb->get_results($wpdb->prepare("SHOW COLUMNS FROM `$article_drafts_table` LIKE %s", 'wp_post_id'));
if (empty($column_exists)) {
$this->debug_log('wp_post_id column missing, adding it now...');
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange -- Schema update required
$wpdb->query("ALTER TABLE `$article_drafts_table` ADD COLUMN wp_post_id mediumint(9) DEFAULT NULL AFTER status");
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange -- Index creation required
$wpdb->query("ALTER TABLE `$article_drafts_table` ADD INDEX wp_post_id (wp_post_id)");
$this->debug_log('wp_post_id column added successfully');
}
// Check if word_count column exists in drafts table
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery -- Table structure check required
$word_count_exists = $wpdb->get_results($wpdb->prepare("SHOW COLUMNS FROM `$article_drafts_table` LIKE %s", 'word_count'));
if (empty($word_count_exists)) {
$this->debug_log('word_count column missing, adding it now...');
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange -- Schema update required
$wpdb->query("ALTER TABLE `$article_drafts_table` ADD COLUMN word_count int(6) DEFAULT 0 AFTER wp_post_id");
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange -- Index creation required
$wpdb->query("ALTER TABLE `$article_drafts_table` ADD INDEX word_count (word_count)");
$this->debug_log('word_count column added successfully');
// Update existing drafts with word count
$this->update_existing_drafts_word_count();
}
// Check if outbound_links column exists in drafts table
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery -- Table structure check required
$outbound_links_exists = $wpdb->get_results($wpdb->prepare("SHOW COLUMNS FROM `$article_drafts_table` LIKE %s", 'outbound_links'));
if (empty($outbound_links_exists)) {
$this->debug_log('outbound_links column missing, adding it now...');
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange -- Schema update required
$wpdb->query("ALTER TABLE `$article_drafts_table` ADD COLUMN outbound_links longtext AFTER word_count");
$this->debug_log('outbound_links column added successfully');
}
// Check if background_status column exists in drafts table
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery -- Table structure check required
$background_status_exists = $wpdb->get_results($wpdb->prepare("SHOW COLUMNS FROM `$article_drafts_table` LIKE %s", 'background_status'));
if (empty($background_status_exists)) {
$this->debug_log('background_status column missing, adding it now...');
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange -- Schema update required
$wpdb->query("ALTER TABLE `$article_drafts_table` ADD COLUMN background_status varchar(20) DEFAULT 'none' AFTER status");
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange -- Index creation required
$wpdb->query("ALTER TABLE `$article_drafts_table` ADD INDEX background_status (background_status)");
$this->debug_log('background_status column added successfully');
}
}
}
/**
* Update existing drafts with word count
*/
private function update_existing_drafts_word_count() {
global $wpdb;
$table_name = $wpdb->prefix . 'ai_seo_article_generator_drafts';
// Get all drafts without word count
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery -- Direct query required for bulk update
$drafts = $wpdb->get_results("SELECT id, content FROM `$table_name` WHERE word_count = 0 AND content != ''");
if (empty($drafts)) {
$this->debug_log('No drafts need word count update');
return;
}
$this->debug_log('Updating word count for ' . count($drafts) . ' existing drafts');
// Load admin class to access word count function
require_once AI_SEO_ARTICLE_GENERATOR_PLUGIN_DIR . 'includes/class-ai-seo-article-generator-admin.php';
$admin = new AI_SEO_Article_Generator_Admin();
$updated_count = 0;
foreach ($drafts as $draft) {
$word_count = $admin->calculate_word_count($draft->content);
// phpcs:ignore WordPress.DB.DirectDatabaseQuery -- Direct update required for custom table
$result = $wpdb->update(
$table_name,
array('word_count' => $word_count),
array('id' => $draft->id),
array('%d'),
array('%d')
);
if ($result !== false) {
$updated_count++;
}
}
$this->debug_log("Successfully updated word count for $updated_count drafts");
}
private function load_admin() {
require_once AI_SEO_ARTICLE_GENERATOR_PLUGIN_DIR . 'includes/class-ai-seo-article-generator-admin.php';
require_once AI_SEO_ARTICLE_GENERATOR_PLUGIN_DIR . 'includes/class-ai-seo-article-generator-api.php';
require_once AI_SEO_ARTICLE_GENERATOR_PLUGIN_DIR . 'includes/class-ai-seo-article-generator-generator.php';
require_once AI_SEO_ARTICLE_GENERATOR_PLUGIN_DIR . 'includes/class-ai-seo-article-generator-feedback.php';
new AI_SEO_Article_Generator_Admin();
}
private function load_background_processor() {
require_once AI_SEO_ARTICLE_GENERATOR_PLUGIN_DIR . 'includes/class-ai-seo-article-generator-api.php';
require_once AI_SEO_ARTICLE_GENERATOR_PLUGIN_DIR . 'includes/class-ai-seo-article-generator-background-processor.php';
new AI_SEO_Article_Generator_Background_Processor();
}
private function load_aeo_schema() {
require_once AI_SEO_ARTICLE_GENERATOR_PLUGIN_DIR . 'includes/class-ai-seo-article-generator-aeo-schema.php';
require_once AI_SEO_ARTICLE_GENERATOR_PLUGIN_DIR . 'includes/class-ai-seo-article-generator-aeo-content.php';
require_once AI_SEO_ARTICLE_GENERATOR_PLUGIN_DIR . 'includes/class-ai-seo-article-generator-citations.php';
require_once AI_SEO_ARTICLE_GENERATOR_PLUGIN_DIR . 'includes/class-ai-seo-article-generator-content-fixer.php';
require_once AI_SEO_ARTICLE_GENERATOR_PLUGIN_DIR . 'includes/class-ai-seo-article-generator-completion-fixer.php';
require_once AI_SEO_ARTICLE_GENERATOR_PLUGIN_DIR . 'includes/class-ai-seo-article-generator-hebrew-fixer.php';
new AI_SEO_Article_Generator_AEO_Schema();
new AI_SEO_Article_Generator_AEO_Content();
new AI_SEO_Article_Generator_Citations();
new AI_SEO_Article_Generator_Content_Fixer();
new AI_SEO_Article_Generator_Completion_Fixer();
new AI_SEO_Article_Generator_Hebrew_Fixer();
}
public function add_custom_cron_schedules($schedules) {
$schedules['ai_seo_article_generator_every_minute'] = array(
'interval' => 60,
'display' => __('Every Minute (AI SEO Article Generator)', 'ai-seo-article-generator')
);
return $schedules;
}
public function enqueue_scripts() {
wp_enqueue_style('ai-seo-article-generator-style', AI_SEO_ARTICLE_GENERATOR_PLUGIN_URL . 'assets/css/ai-seo-article-generator.css', array(), AI_SEO_ARTICLE_GENERATOR_VERSION);
wp_enqueue_script('ai-seo-article-generator-script', AI_SEO_ARTICLE_GENERATOR_PLUGIN_URL . 'assets/js/ai-seo-article-generator.js', array('jquery'), AI_SEO_ARTICLE_GENERATOR_VERSION, true);
}
public function admin_enqueue_scripts($hook) {
if (strpos($hook, 'ai-seo-article-generator') === false) {
return;
}
wp_enqueue_style('ai-seo-article-generator-admin-style', AI_SEO_ARTICLE_GENERATOR_PLUGIN_URL . 'assets/css/admin.css', array(), AI_SEO_ARTICLE_GENERATOR_VERSION);
wp_enqueue_script('ai-seo-article-generator-admin-script', AI_SEO_ARTICLE_GENERATOR_PLUGIN_URL . 'assets/js/admin.js', array('jquery', 'wp-util'), AI_SEO_ARTICLE_GENERATOR_VERSION, true);
// Add dynamic CSS for language direction
$this->add_dynamic_language_css();
wp_localize_script('ai-seo-article-generator-admin-script', 'ai_seo_article_generator_ajax', array(
'ajax_url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('ai_seo_article_generator_nonce'),
'language' => array(
'locale' => get_locale(),
'is_hebrew' => $this->is_hebrew_locale(),
'text_direction' => $this->get_text_direction(),
'text_alignment' => $this->get_text_alignment()
),
'timeouts' => array(
'max_structure_timeout' => intval(get_option('ai_seo_article_generator_max_structure_timeout', 300)),
'max_content_timeout' => intval(get_option('ai_seo_article_generator_max_content_timeout', 600)),
'enable_progress_checking' => intval(get_option('ai_seo_article_generator_enable_progress_checking', 1))
),
'i18n' => array(
'testing_connection' => __('Testing connection...', 'ai-seo-article-generator'),
'connection_success' => __('Connection successful!', 'ai-seo-article-generator'),
'connection_failed' => __('Connection failed. Please check your API key.', 'ai-seo-article-generator'),
'generating_structure' => __('Generating article structure...', 'ai-seo-article-generator'),
'generating_content' => __('Generating article content...', 'ai-seo-article-generator'),
'enhancing_section' => __('Enhancing section...', 'ai-seo-article-generator'),
)
));
}
/**
* Add dynamic CSS based on WordPress language setting
*/
private function add_dynamic_language_css() {
$direction = $this->get_text_direction();
$alignment = $this->get_text_alignment();
$css = "
.ai-seo-article-generator-wrap {
direction: {$direction} !important;
text-align: {$alignment} !important;
}
.ai-seo-article-generator-wrap .form-table th {
text-align: {$alignment} !important;
}
.ai-seo-article-generator-wrap .form-table td {
text-align: {$alignment} !important;
}
.ai-seo-article-generator-wrap input[type='text'],
.ai-seo-article-generator-wrap input[type='password'],
.ai-seo-article-generator-wrap textarea,
.ai-seo-article-generator-wrap select {
direction: {$direction} !important;
text-align: {$alignment} !important;
}
.ai-seo-article-generator-dashboard {
direction: {$direction} !important;
}
.ai-seo-article-generator-features {
direction: {$direction} !important;
text-align: {$alignment} !important;
}
.ai-seo-article-generator-step {
direction: {$direction} !important;
text-align: {$alignment} !important;
}
.ai-seo-article-generator-modal-content {
direction: {$direction} !important;
text-align: {$alignment} !important;
}";
wp_add_inline_style('ai-seo-article-generator-admin-style', $css);
}
/**
* Migration function to move old options to new names
*/
private function migrate_options() {
$old_options = array(
'postinor_version' => 'ai_seo_article_generator_version',
'postinor_api_key' => 'ai_seo_article_generator_api_key',
'postinor_api_connected' => 'ai_seo_article_generator_api_connected',
'postinor_debug_logging' => 'ai_seo_article_generator_debug_logging',
'postinor_max_structure_timeout' => 'ai_seo_article_generator_max_structure_timeout',
'postinor_max_content_timeout' => 'ai_seo_article_generator_max_content_timeout',
'postinor_enable_progress_checking' => 'ai_seo_article_generator_enable_progress_checking',
'postinor_tables_created' => 'ai_seo_article_generator_tables_created'
);
foreach ($old_options as $old_key => $new_key) {
$value = get_option($old_key, null);
if ($value !== null) {
update_option($new_key, $value);
delete_option($old_key);
}
}
// Migrate old database tables
$this->migrate_database_tables();
}
/**
* Migration function to rename old database tables to new names
*/
private function migrate_database_tables() {
global $wpdb;
$old_tables = array(
'postinor_article_drafts' => 'ai_seo_article_generator_drafts',
'postinor_article_structures' => 'ai_seo_article_generator_structures',
'postinor_saved_structures' => 'ai_seo_article_generator_saved_structures'
);
foreach ($old_tables as $old_name => $new_name) {
$old_table = $wpdb->prefix . $old_name;
$new_table = $wpdb->prefix . $new_name;
// Check if old table exists and new table doesn't
// phpcs:ignore WordPress.DB.DirectDatabaseQuery -- Table existence check required for migration
$old_exists = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $old_table));
// phpcs:ignore WordPress.DB.DirectDatabaseQuery -- Table existence check required for migration
$new_exists = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $new_table));
if ($old_exists && !$new_exists) {
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange -- Table rename required for migration
$wpdb->query("RENAME TABLE `$old_table` TO `$new_table`");
$this->debug_log("Migrated database table from {$old_table} to {$new_table}");
}
}
}
/**
* Debug logging function that only logs when debug mode is enabled
*/
private function debug_log($message, $data = null) {
if (get_option('ai_seo_article_generator_debug_logging', 0)) {
$log_message = 'AI SEO Article Generator: ' . $message;
if ($data !== null && is_array($data)) {
$log_message .= ' - ' . wp_json_encode($data);
} elseif ($data !== null) {
$log_message .= ' - ' . $data;
}
if (function_exists('error_log')) {
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Conditional debug logging
error_log($log_message);
}
}
}
}
function ai_seo_article_generator() {
return AI_SEO_Article_Generator::get_instance();
}
ai_seo_article_generator();