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();