plugin = ai_seo_article_generator(); add_action('admin_menu', array($this, 'add_admin_menu')); // Add debug admin notice for language detection add_action('admin_notices', array($this, 'debug_language_notice')); add_action('wp_ajax_ai_seo_article_generator_test_connection', array($this, 'ajax_test_connection')); add_action('wp_ajax_ai_seo_article_generator_generate_structure', array($this, 'ajax_generate_structure')); add_action('wp_ajax_ai_seo_article_generator_generate_content', array($this, 'ajax_generate_content')); add_action('wp_ajax_ai_seo_article_generator_queue_background_generation', array($this, 'ajax_queue_background_generation')); add_action('wp_ajax_ai_seo_article_generator_check_job_status', array($this, 'ajax_check_job_status')); add_action('wp_ajax_ai_seo_article_generator_retry_failed_job', array($this, 'ajax_retry_failed_job')); add_action('wp_ajax_ai_seo_article_generator_enhance_section', array($this, 'ajax_enhance_section')); add_action('wp_ajax_ai_seo_article_generator_save_draft', array($this, 'ajax_save_draft')); add_action('wp_ajax_ai_seo_article_generator_load_draft', array($this, 'ajax_load_draft')); add_action('wp_ajax_ai_seo_article_generator_delete_draft', array($this, 'ajax_delete_draft')); add_action('wp_ajax_ai_seo_article_generator_create_tables', array($this, 'ajax_create_tables')); add_action('wp_ajax_ai_seo_article_generator_save_structure', array($this, 'ajax_save_structure')); add_action('wp_ajax_ai_seo_article_generator_load_structure', array($this, 'ajax_load_structure')); add_action('wp_ajax_ai_seo_article_generator_delete_structure', array($this, 'ajax_delete_structure')); add_action('wp_ajax_ai_seo_article_generator_debug_structures', array($this, 'ajax_debug_structures')); add_action('wp_ajax_ai_seo_article_generator_publish_article', array($this, 'ajax_publish_article')); add_action('wp_ajax_ai_seo_article_generator_manual_trigger_background', array($this, 'ajax_manual_trigger_background')); add_action('admin_init', array($this, 'handle_settings')); } /** * Get language direction helper */ private function get_text_direction() { return $this->plugin->get_text_direction(); } /** * Get text alignment helper */ private function get_text_alignment() { return $this->plugin->get_text_alignment(); } /** * Check if Hebrew locale */ private function is_hebrew_locale() { return $this->plugin->is_hebrew_locale(); } /** * Debug admin notice for language detection */ public function debug_language_notice() { // Only show on AI SEO plugin pages // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only operation if (!isset($_GET['page']) || strpos(sanitize_text_field(wp_unslash($_GET['page'])), 'ai-seo-article-generator') === false) { return; } if (!current_user_can('manage_options')) { return; } $locale = get_locale(); $is_hebrew = $this->is_hebrew_locale(); $direction = $this->get_text_direction(); $alignment = $this->get_text_alignment(); echo '
'; echo '

AI SEO Article Generator - Language Debug:

'; echo '

WordPress Locale: ' . esc_html($locale) . '

'; echo '

Hebrew Detected: ' . ($is_hebrew ? 'YES' : 'NO') . '

'; echo '

Text Direction: ' . esc_html($direction) . '

'; echo '

Text Alignment: ' . esc_html($alignment) . '

'; echo '

Translation Test: ' . esc_html(__('AI SEO Article Generator', 'ai-seo-article-generator')) . '

'; echo '
'; } /** * Debug logging helper function */ private function debug_log($message, $data = null) { if (get_option('ai_seo_article_generator_debug_logging', 0)) { $log_message = 'AI SEO Article Generator Admin: ' . $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); } } } public function add_admin_menu() { add_menu_page( __('AI SEO Article Generator', 'ai-seo-article-generator'), __('AI SEO Generator', 'ai-seo-article-generator'), 'manage_options', 'ai-seo-article-generator', array($this, 'admin_page'), 'dashicons-edit-large', 30 ); add_submenu_page( 'ai-seo-article-generator', __('AI SEO Generator Settings', 'ai-seo-article-generator'), __('Settings', 'ai-seo-article-generator'), 'manage_options', 'ai-seo-article-generator-settings', array($this, 'settings_page') ); add_submenu_page( 'ai-seo-article-generator', __('Create New Article', 'ai-seo-article-generator'), __('New Article', 'ai-seo-article-generator'), 'manage_options', 'ai-seo-article-generator-new', array($this, 'new_article_page') ); add_submenu_page( 'ai-seo-article-generator', __('Article Drafts', 'ai-seo-article-generator'), __('Drafts', 'ai-seo-article-generator'), 'manage_options', 'ai-seo-article-generator-drafts', array($this, 'drafts_page') ); add_submenu_page( 'ai-seo-article-generator', __('Structure Library', 'ai-seo-article-generator'), __('Saved Structures', 'ai-seo-article-generator'), 'manage_options', 'ai-seo-article-generator-structures', array($this, 'structures_page') ); add_submenu_page( 'ai-seo-article-generator', __('Background Jobs', 'ai-seo-article-generator'), __('Background Jobs', 'ai-seo-article-generator'), 'manage_options', 'ai-seo-article-generator-jobs', array($this, 'background_jobs_page') ); } public function admin_page() { $api_connected = get_option('ai_seo_article_generator_api_connected', false); ?>

🤖

💡

📝

⚙️

prefix . 'ai_seo_article_generator_drafts'; // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table query for status display $queued_count = $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM `$table_name` WHERE background_status = %s", 'queued')); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table query for status display $processing_count = $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM `$table_name` WHERE background_status = %s", 'processing')); ?>

⚙️

✨

save_settings(); } $api_key = get_option('ai_seo_article_generator_api_key', ''); $api_connected = get_option('ai_seo_article_generator_api_connected', false); ?>

⚙️

✅ ❌

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'; $drafts_exist = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $article_drafts_table)) == $article_drafts_table; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Table existence check $structures_exist = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $article_structures_table)) == $article_structures_table; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Table existence check $saved_structures_exist = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $saved_structures_table)) == $saved_structures_table; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Table existence check ?>
✅ ❌

⏰

📋

💡

🎯


📋

✍️

👁️

📊

prefix . 'ai_seo_article_generator_drafts'; // Get all background jobs (not 'none' status) $jobs = $wpdb->get_results($wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table query "SELECT * FROM `$table_name` WHERE background_status != %s ORDER BY created_at DESC LIMIT 50", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name cannot be prepared 'none' )); ?>

⚙️

id); ?> main_keyword); ?> target_words); ?> get_background_status_label($job->background_status)); ?> word_count > 0 ? esc_html(number_format($job->word_count)) : '-'; ?> created_at))); ?> updated_at))); ?> background_status === 'completed' && !empty($job->content)): ?> background_status === 'processing'): ?> 🔄 background_status === 'queued'): ?> ⏳ background_status === 'failed'): ?>
__('Queued', 'ai-seo-article-generator'), 'processing' => __('Processing', 'ai-seo-article-generator'), 'completed' => __('Completed', 'ai-seo-article-generator'), 'failed' => __('Failed', 'ai-seo-article-generator'), 'none' => __('None', 'ai-seo-article-generator') ); return isset($labels[$status]) ? $labels[$status] : $status; } public function drafts_page() { global $wpdb; $table_name = $wpdb->prefix . 'ai_seo_article_generator_drafts'; $drafts = $wpdb->get_results("SELECT * FROM `{$wpdb->prefix}ai_seo_article_generator_drafts` ORDER BY updated_at DESC"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table query ?>

📝

title ?: __('No title', 'ai-seo-article-generator')); ?> main_keyword); ?> word_count) ? intval($draft->word_count) : 0; if ($word_count == 0 && !empty($draft->content)) { // Calculate word count for existing drafts without it $word_count = $this->calculate_word_count($draft->content); } echo esc_html(number_format($word_count) . ' ' . __('words', 'ai-seo-article-generator')); ?> get_status_label($draft->status)); ?> updated_at))); ?>

' . esc_html(__('Settings saved successfully!', 'ai-seo-article-generator')) . '

'; } public function ajax_test_connection() { check_ajax_referer('ai_seo_article_generator_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_die('Insufficient permissions'); } $api = new AI_SEO_Article_Generator_API(); $result = $api->test_connection(); wp_send_json($result); } public function ajax_generate_structure() { $this->debug_log('🚀 AI SEO Article Generator: Starting structure generation AJAX handler'); try { check_ajax_referer('ai_seo_article_generator_nonce', 'nonce'); $this->debug_log('✅ AI SEO Article Generator: Nonce verification passed'); if (!current_user_can('manage_options')) { $this->debug_log('❌ AI SEO Article Generator: Insufficient permissions'); wp_send_json(array('success' => false, 'message' => __('Insufficient permissions', 'ai-seo-article-generator'))); return; } $main_keyword = isset($_POST['main_keyword']) ? sanitize_text_field(wp_unslash($_POST['main_keyword'])) : ''; $sub_keywords = isset($_POST['sub_keywords']) ? sanitize_textarea_field(wp_unslash($_POST['sub_keywords'])) : ''; $target_words = isset($_POST['target_words']) ? intval($_POST['target_words']) : 0; $article_language = isset($_POST['article_language']) ? sanitize_text_field(wp_unslash($_POST['article_language'])) : 'auto'; $this->debug_log("📝 AI SEO Article Generator: Structure generation params - Keyword: $main_keyword, Sub-keywords: $sub_keywords, Target words: $target_words"); if (empty($main_keyword)) { $this->debug_log('❌ AI SEO Article Generator: Main keyword is empty'); wp_send_json(array('success' => false, 'message' => __('Main keyword is required', 'ai-seo-article-generator'))); return; } $api = new AI_SEO_Article_Generator_API(); $this->debug_log('🔧 AI SEO Article Generator: API instance created, calling generate_article_structure'); $result = $api->generate_article_structure($main_keyword, $sub_keywords, $target_words, $article_language); $this->debug_log('📊 AI SEO Article Generator: API result', $result); wp_send_json($result); } catch (Exception $e) { $this->debug_log('💥 AI SEO Article Generator: Exception in ajax_generate_structure: ' . $e->getMessage()); wp_send_json(array('success' => false, 'message' => __('Internal error:', 'ai-seo-article-generator') . ' ' . $e->getMessage())); } } public function ajax_generate_content() { check_ajax_referer('ai_seo_article_generator_nonce', 'nonce'); $structure = isset($_POST['structure']) ? json_decode(sanitize_textarea_field(wp_unslash($_POST['structure'])), true) : array(); $main_keyword = isset($_POST['main_keyword']) ? sanitize_text_field(wp_unslash($_POST['main_keyword'])) : ''; $sub_keywords = isset($_POST['sub_keywords']) ? sanitize_textarea_field(wp_unslash($_POST['sub_keywords'])) : ''; $target_words = isset($_POST['target_words']) ? intval($_POST['target_words']) : 0; $article_language = isset($_POST['article_language']) ? sanitize_text_field(wp_unslash($_POST['article_language'])) : 'auto'; $api = new AI_SEO_Article_Generator_API(); $result = $api->generate_article_content($structure, $main_keyword, $sub_keywords, $target_words, $article_language); wp_send_json($result); } public function ajax_enhance_section() { check_ajax_referer('ai_seo_article_generator_nonce', 'nonce'); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized by sanitize_seo_content() $section_content = isset($_POST['section_content']) ? $this->sanitize_seo_content(wp_unslash($_POST['section_content'])) : ''; $main_keyword = isset($_POST['main_keyword']) ? sanitize_text_field(wp_unslash($_POST['main_keyword'])) : ''; $enhancement_type = isset($_POST['enhancement_type']) ? sanitize_text_field(wp_unslash($_POST['enhancement_type'])) : ''; $api = new AI_SEO_Article_Generator_API(); $result = $api->enhance_section($section_content, $main_keyword, $enhancement_type); wp_send_json($result); } public function ajax_save_draft() { check_ajax_referer('ai_seo_article_generator_nonce', 'nonce'); global $wpdb; $table_name = $wpdb->prefix . 'ai_seo_article_generator_drafts'; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized by sanitize_seo_content() $content = isset($_POST['content']) ? $this->sanitize_seo_content(wp_unslash($_POST['content'])) : ''; $data = array( 'title' => isset($_POST['title']) ? sanitize_text_field(wp_unslash($_POST['title'])) : '', 'main_keyword' => isset($_POST['main_keyword']) ? sanitize_text_field(wp_unslash($_POST['main_keyword'])) : '', 'sub_keywords' => isset($_POST['sub_keywords']) ? sanitize_textarea_field(wp_unslash($_POST['sub_keywords'])) : '', 'target_words' => isset($_POST['target_words']) ? intval($_POST['target_words']) : 0, // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized by sanitize_seo_content() 'structure_data' => isset($_POST['structure_data']) ? $this->sanitize_seo_content(wp_unslash($_POST['structure_data'])) : '', 'content' => $content, 'status' => isset($_POST['status']) ? sanitize_text_field(wp_unslash($_POST['status'])) : 'draft', 'word_count' => $this->calculate_word_count($content), // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized by sanitize_seo_content() 'outbound_links' => isset($_POST['outbound_links']) ? $this->sanitize_seo_content(wp_unslash($_POST['outbound_links'])) : '' ); if (isset($_POST['draft_id']) && !empty($_POST['draft_id'])) { $wpdb->update($table_name, $data, array('id' => intval($_POST['draft_id']))); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table update $draft_id = isset($_POST['draft_id']) ? intval($_POST['draft_id']) : 0; } else { $wpdb->insert($table_name, $data); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Custom table insert $draft_id = $wpdb->insert_id; } wp_send_json(array('success' => true, 'draft_id' => $draft_id)); } private function get_status_label($status) { $labels = array( 'draft' => __('Draft', 'ai-seo-article-generator'), 'structure' => __('Structure', 'ai-seo-article-generator'), 'content' => __('Content', 'ai-seo-article-generator'), 'ready' => __('Ready to Publish', 'ai-seo-article-generator') ); return isset($labels[$status]) ? $labels[$status] : $status; } public function ajax_load_draft() { check_ajax_referer('ai_seo_article_generator_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_die('Insufficient permissions'); } $draft_id = isset($_POST['draft_id']) ? intval($_POST['draft_id']) : 0; global $wpdb; $table_name = $wpdb->prefix . 'ai_seo_article_generator_drafts'; // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table query $draft = $wpdb->get_row($wpdb->prepare("SELECT * FROM `$table_name` WHERE id = %d", $draft_id)); if (!$draft) { wp_send_json(array('success' => false, 'message' => __('Draft not found', 'ai-seo-article-generator'))); return; } // Parse structure data if it exists $structure_data = null; if (!empty($draft->structure_data)) { $structure_data = json_decode($draft->structure_data, true); } // Parse outbound links data if it exists $outbound_links = array(); if (!empty($draft->outbound_links)) { $parsed_links = json_decode($draft->outbound_links, true); if ($parsed_links) { $outbound_links = $parsed_links; } } wp_send_json(array( 'success' => true, 'draft' => array( 'id' => $draft->id, 'title' => $draft->title, 'main_keyword' => $draft->main_keyword, 'sub_keywords' => $draft->sub_keywords, 'target_words' => $draft->target_words, 'structure_data' => $structure_data, 'content' => $draft->content, 'status' => $draft->status, 'outbound_links' => $outbound_links, 'created_at' => $draft->created_at, 'updated_at' => $draft->updated_at ) )); } public function ajax_delete_draft() { check_ajax_referer('ai_seo_article_generator_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_die('Insufficient permissions'); } $draft_id = isset($_POST['draft_id']) ? intval($_POST['draft_id']) : 0; global $wpdb; $table_name = $wpdb->prefix . 'ai_seo_article_generator_drafts'; $result = $wpdb->delete($table_name, array('id' => $draft_id), array('%d')); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table delete if ($result === false) { wp_send_json(array('success' => false, 'message' => __('Error deleting draft', 'ai-seo-article-generator'))); return; } wp_send_json(array('success' => true, 'message' => __('Draft deleted successfully', 'ai-seo-article-generator'))); } public function ajax_create_tables() { check_ajax_referer('ai_seo_article_generator_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_die('Insufficient permissions'); } // Call the main plugin's create_tables method $plugin = ai_seo_article_generator(); $plugin->create_tables(); // Check if tables were created successfully 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'; $drafts_exist = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $article_drafts_table)) == $article_drafts_table; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Table existence check $structures_exist = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $article_structures_table)) == $article_structures_table; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Table existence check $saved_structures_exist = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $saved_structures_table)) == $saved_structures_table; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Table existence check if ($drafts_exist && $structures_exist && $saved_structures_exist) { update_option('ai_seo_article_generator_tables_created', true); wp_send_json(array('success' => true, 'message' => __('Tables created successfully!', 'ai-seo-article-generator'))); } else { $missing_tables = array(); if (!$drafts_exist) $missing_tables[] = 'drafts'; if (!$structures_exist) $missing_tables[] = 'structures'; if (!$saved_structures_exist) $missing_tables[] = 'saved_structures'; $error_message = __('Error creating tables:', 'ai-seo-article-generator') . ' ' . implode(', ', $missing_tables); wp_send_json(array('success' => false, 'message' => $error_message)); } } public function handle_settings() { if (isset($_GET['page']) && sanitize_text_field(wp_unslash($_GET['page'])) === 'ai-seo-article-generator-settings' && isset($_POST['submit']) && isset($_POST['ai_seo_article_generator_nonce']) && wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['ai_seo_article_generator_nonce'])), 'ai_seo_article_generator_settings')) { $this->save_settings(); } } public function structures_page() { global $wpdb; $table_name = $wpdb->prefix . 'ai_seo_article_generator_saved_structures'; $structures = $wpdb->get_results("SELECT * FROM `{$wpdb->prefix}ai_seo_article_generator_saved_structures` ORDER BY created_at DESC"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table query ?>

📚

name); ?> description): ?>
description); ?>
main_keyword); ?> target_words . ' ' . __('words', 'ai-seo-article-generator')); ?> usage_count); ?> created_at))); ?>
debug_log('🔍 AI SEO Article Generator JSON Validation: Starting validation for: ' . substr($raw_json, 0, 100) . '...'); $this->debug_log('🔍 AI SEO Article Generator JSON Validation: Input encoding: ' . mb_detect_encoding($raw_json)); $this->debug_log('🔍 AI SEO Article Generator JSON Validation: Input length: ' . strlen($raw_json)); // Check if input is empty if (empty($raw_json)) { $this->debug_log('❌ AI SEO Article Generator JSON Validation: Empty JSON input'); return false; } // Clean and prepare JSON for decoding $cleaned_json = trim($raw_json); // Check for double-encoding (escaped quotes in JSON) if (strpos($cleaned_json, '\\"') !== false) { $this->debug_log('⚠️ AI SEO Article Generator JSON Validation: Detected double-encoded JSON, attempting to fix...'); // Try to decode as a JSON string first (if it was JSON-encoded twice) $test_decode = json_decode($cleaned_json, true); if (json_last_error() === JSON_ERROR_NONE && is_string($test_decode)) { $this->debug_log('✅ AI SEO Article Generator JSON Validation: Successfully unwrapped double-encoded JSON'); $cleaned_json = $test_decode; } else { // Fallback: manually remove escaping $this->debug_log('⚠️ AI SEO Article Generator JSON Validation: Manual escape removal...'); $cleaned_json = str_replace('\\"', '"', $cleaned_json); $cleaned_json = str_replace('\\\\', '\\', $cleaned_json); } $this->debug_log('🔍 AI SEO Article Generator JSON Validation: After double-encoding fix: ' . substr($cleaned_json, 0, 200) . '...'); } // Log a sample of the actual JSON being processed $this->debug_log('🔍 AI SEO Article Generator JSON Validation: First 500 chars: ' . substr($cleaned_json, 0, 500)); $this->debug_log('🔍 AI SEO Article Generator JSON Validation: Last 100 chars: ' . substr($cleaned_json, -100)); // Decode JSON to validate structure $decoded = json_decode($cleaned_json, true); if (json_last_error() !== JSON_ERROR_NONE) { $this->debug_log('❌ AI SEO Article Generator JSON Validation: JSON decode error: ' . json_last_error_msg()); $this->debug_log('❌ AI SEO Article Generator JSON Validation: JSON error code: ' . json_last_error()); $this->debug_log('❌ AI SEO Article Generator JSON Validation: Problematic JSON sample: ' . substr($cleaned_json, 0, 1000)); return false; } // Validate required structure properties if (!is_array($decoded)) { $this->debug_log('❌ AI SEO Article Generator JSON Validation: Decoded data is not an array'); return false; } // Check for essential structure elements $required_fields = array('title', 'sections'); foreach ($required_fields as $field) { if (!isset($decoded[$field])) { $this->debug_log('❌ AI SEO Article Generator JSON Validation: Missing required field: ' . $field); return false; } } // Validate sections array if (!is_array($decoded['sections'])) { $this->debug_log('❌ AI SEO Article Generator JSON Validation: Sections is not an array'); return false; } // Re-encode to ensure clean JSON (this also sanitizes) $clean_json = json_encode($decoded, JSON_UNESCAPED_UNICODE); if ($clean_json === false) { $this->debug_log('❌ AI SEO Article Generator JSON Validation: Failed to re-encode JSON'); return false; } $this->debug_log('✅ AI SEO Article Generator JSON Validation: Successfully validated and sanitized JSON'); $this->debug_log('🔍 AI SEO Article Generator JSON Validation: Clean JSON preview: ' . substr($clean_json, 0, 200) . '...'); return $clean_json; } public function ajax_save_structure() { check_ajax_referer('ai_seo_article_generator_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_die('Insufficient permissions'); } $name = isset($_POST['name']) ? sanitize_text_field(wp_unslash($_POST['name'])) : ''; $description = isset($_POST['description']) ? sanitize_textarea_field(wp_unslash($_POST['description'])) : ''; $main_keyword = isset($_POST['main_keyword']) ? sanitize_text_field(wp_unslash($_POST['main_keyword'])) : ''; $sub_keywords = isset($_POST['sub_keywords']) ? sanitize_textarea_field(wp_unslash($_POST['sub_keywords'])) : ''; $target_words = isset($_POST['target_words']) ? intval($_POST['target_words']) : 0; // Properly handle JSON structure data with WordPress escaping fixes $raw_structure_data = isset($_POST['structure_data']) ? sanitize_textarea_field(wp_unslash($_POST['structure_data'])) : ''; $this->debug_log('🔍 AI SEO Article Generator Save Structure: Raw POST data received: ' . substr($raw_structure_data, 0, 200) . '...'); $this->debug_log('🔍 AI SEO Article Generator Save Structure: Raw data length: ' . strlen($raw_structure_data)); // Fix WordPress auto-escaping (magic quotes behavior) if (function_exists('wp_unslash')) { $unslashed_data = wp_unslash($raw_structure_data); $this->debug_log('🔍 AI SEO Article Generator Save Structure: After wp_unslash length: ' . strlen($unslashed_data)); } else { // Fallback for older WordPress versions $unslashed_data = stripslashes($raw_structure_data); $this->debug_log('🔍 AI SEO Article Generator Save Structure: After stripslashes length: ' . strlen($unslashed_data)); } // Fix potential encoding issues with Hebrew text if (!mb_check_encoding($unslashed_data, 'UTF-8')) { $this->debug_log('⚠️ AI SEO Article Generator Save Structure: Data encoding issue detected, attempting to fix...'); $unslashed_data = mb_convert_encoding($unslashed_data, 'UTF-8', 'auto'); } $this->debug_log('🔍 AI SEO Article Generator Save Structure: Final processed data: ' . substr($unslashed_data, 0, 200) . '...'); $this->debug_log('🔍 AI SEO Article Generator Save Structure: Data encoding: ' . mb_detect_encoding($unslashed_data)); $this->debug_log('🔍 AI SEO Article Generator Save Structure: Final data length: ' . strlen($unslashed_data)); // Validate and sanitize JSON using the unslashed data $structure_data = $this->validate_and_sanitize_structure_json($unslashed_data); if (empty($name)) { wp_send_json(array('success' => false, 'message' => __('Structure name is required', 'ai-seo-article-generator'))); return; } if ($structure_data === false) { $this->debug_log('❌ AI SEO Article Generator Save Structure: Invalid JSON structure data'); wp_send_json(array('success' => false, 'message' => __('Structure data is corrupted or missing', 'ai-seo-article-generator'))); return; } global $wpdb; $table_name = $wpdb->prefix . 'ai_seo_article_generator_saved_structures'; $data = array( 'name' => $name, 'description' => $description, 'main_keyword' => $main_keyword, 'sub_keywords' => $sub_keywords, 'target_words' => $target_words, 'structure_data' => $structure_data ); $this->debug_log('🔍 AI SEO Article Generator Save Structure: Data to be inserted', $data); $result = $wpdb->insert($table_name, $data); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Custom table insert if ($result === false) { $this->debug_log('❌ AI SEO Article Generator Save Structure: Database insert failed. Error: ' . $wpdb->last_error); wp_send_json(array('success' => false, 'message' => __('Error saving structure to database', 'ai-seo-article-generator'))); return; } $structure_id = $wpdb->insert_id; $this->debug_log('✅ AI SEO Article Generator Save Structure: Successfully saved with ID: ' . $structure_id); wp_send_json(array('success' => true, 'message' => __('Structure saved successfully!', 'ai-seo-article-generator'), 'structure_id' => $structure_id)); } public function ajax_load_structure() { check_ajax_referer('ai_seo_article_generator_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_die('Insufficient permissions'); } $structure_id = isset($_POST['structure_id']) ? intval($_POST['structure_id']) : 0; $this->debug_log('🔍 AI SEO Article Generator Load Structure: Loading structure ID: ' . $structure_id); global $wpdb; $table_name = $wpdb->prefix . 'ai_seo_article_generator_saved_structures'; // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table query $structure = $wpdb->get_row($wpdb->prepare("SELECT * FROM `$table_name` WHERE id = %d", $structure_id)); if (!$structure) { $this->debug_log('❌ AI SEO Article Generator Load Structure: Structure not found for ID: ' . $structure_id); wp_send_json(array('success' => false, 'message' => __('Structure not found', 'ai-seo-article-generator'))); return; } $this->debug_log('🔍 AI SEO Article Generator Load Structure: Found structure. Raw data length: ' . strlen($structure->structure_data)); $this->debug_log('🔍 AI SEO Article Generator Load Structure: Raw structure_data preview: ' . substr($structure->structure_data, 0, 200) . '...'); // Increment usage count $wpdb->update($table_name, array('usage_count' => $structure->usage_count + 1), array('id' => $structure_id)); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table update // Parse structure data $structure_data = null; if (!empty($structure->structure_data)) { $structure_data = json_decode($structure->structure_data, true); $json_error = json_last_error(); if ($json_error !== JSON_ERROR_NONE) { $this->debug_log('❌ AI SEO Article Generator Load Structure: JSON decode error: ' . json_last_error_msg()); $this->debug_log('❌ AI SEO Article Generator Load Structure: Problematic JSON: ' . $structure->structure_data); } else { $this->debug_log('✅ AI SEO Article Generator Load Structure: JSON decoded successfully'); } } else { $this->debug_log('❌ AI SEO Article Generator Load Structure: structure_data is empty or null'); } wp_send_json(array( 'success' => true, 'structure' => array( 'id' => $structure->id, 'name' => $structure->name, 'description' => $structure->description, 'main_keyword' => $structure->main_keyword, 'sub_keywords' => $structure->sub_keywords, 'target_words' => $structure->target_words, 'structure_data' => $structure_data, 'usage_count' => $structure->usage_count + 1, 'created_at' => $structure->created_at ) )); } public function ajax_delete_structure() { check_ajax_referer('ai_seo_article_generator_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_die('Insufficient permissions'); } $structure_id = isset($_POST['structure_id']) ? intval($_POST['structure_id']) : 0; global $wpdb; $table_name = $wpdb->prefix . 'ai_seo_article_generator_saved_structures'; $result = $wpdb->delete($table_name, array('id' => $structure_id), array('%d')); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table delete if ($result === false) { wp_send_json(array('success' => false, 'message' => __('Error deleting structure', 'ai-seo-article-generator'))); return; } wp_send_json(array('success' => true, 'message' => __('Structure deleted successfully', 'ai-seo-article-generator'))); } public function ajax_debug_structures() { check_ajax_referer('ai_seo_article_generator_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_die('Insufficient permissions'); } global $wpdb; $table_name = $wpdb->prefix . 'ai_seo_article_generator_saved_structures'; $structures = $wpdb->get_results("SELECT id, name, structure_data FROM `{$wpdb->prefix}ai_seo_article_generator_saved_structures` ORDER BY id ASC"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table query for debug $this->debug_log('🔍 AI SEO Article Generator Debug: Found ' . count($structures) . ' structures in database'); $debug_info = array(); foreach ($structures as $structure) { $structure_data_length = strlen($structure->structure_data); $is_json_valid = false; $json_error = ''; if (!empty($structure->structure_data)) { $decoded = json_decode($structure->structure_data, true); if (json_last_error() === JSON_ERROR_NONE) { $is_json_valid = true; } else { $json_error = json_last_error_msg(); } } $debug_info[] = array( 'id' => $structure->id, 'name' => $structure->name, 'data_length' => $structure_data_length, 'is_json_valid' => $is_json_valid, 'json_error' => $json_error, 'data_preview' => substr($structure->structure_data, 0, 100) . '...' ); $this->debug_log("🔍 Structure ID {$structure->id} ({$structure->name}): Length={$structure_data_length}, Valid JSON={$is_json_valid}, Error={$json_error}"); } wp_send_json(array( 'success' => true, 'structures_count' => count($structures), 'debug_info' => $debug_info )); } public function ajax_queue_background_generation() { check_ajax_referer('ai_seo_article_generator_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_die('Insufficient permissions'); } $main_keyword = isset($_POST['main_keyword']) ? sanitize_text_field(wp_unslash($_POST['main_keyword'])) : ''; $sub_keywords = isset($_POST['sub_keywords']) ? sanitize_textarea_field(wp_unslash($_POST['sub_keywords'])) : ''; $target_words = isset($_POST['target_words']) ? intval($_POST['target_words']) : 1000; $article_language = isset($_POST['article_language']) ? sanitize_text_field(wp_unslash($_POST['article_language'])) : 'auto'; $structure_data = isset($_POST['structure_data']) ? sanitize_textarea_field(wp_unslash($_POST['structure_data'])) : ''; if (empty($main_keyword)) { wp_send_json(array('success' => false, 'message' => __('Main keyword is required', 'ai-seo-article-generator'))); return; } // Load background processor require_once AI_SEO_ARTICLE_GENERATOR_PLUGIN_DIR . 'includes/class-ai-seo-article-generator-background-processor.php'; $result = AI_SEO_Article_Generator_Background_Processor::queue_article_generation( $main_keyword, $sub_keywords, $target_words, $structure_data ? json_decode($structure_data, true) : null, $article_language ); wp_send_json($result); } public function ajax_check_job_status() { check_ajax_referer('ai_seo_article_generator_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_die('Insufficient permissions'); } $job_id = isset($_POST['job_id']) ? intval($_POST['job_id']) : 0; if (!$job_id) { wp_send_json(array('success' => false, 'message' => __('Job ID is required', 'ai-seo-article-generator'))); return; } // Load background processor require_once AI_SEO_ARTICLE_GENERATOR_PLUGIN_DIR . 'includes/class-ai-seo-article-generator-background-processor.php'; $result = AI_SEO_Article_Generator_Background_Processor::get_job_status($job_id); wp_send_json($result); } public function ajax_retry_failed_job() { check_ajax_referer('ai_seo_article_generator_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_die('Insufficient permissions'); } $job_id = isset($_POST['job_id']) ? intval($_POST['job_id']) : 0; if (!$job_id) { wp_send_json(array('success' => false, 'message' => __('Job ID is required', 'ai-seo-article-generator'))); return; } global $wpdb; $table_name = $wpdb->prefix . 'ai_seo_article_generator_drafts'; // Check if job exists and is failed $job = $wpdb->get_row($wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table query "SELECT * FROM `$table_name` WHERE id = %d AND background_status = %s", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name cannot be prepared $job_id, 'failed' )); if (!$job) { wp_send_json(array('success' => false, 'message' => __('Job not found or not in failed status', 'ai-seo-article-generator'))); return; } // Reset job to queued status $result = $wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table update $table_name, array( 'background_status' => 'queued', 'updated_at' => current_time('mysql') ), array('id' => $job_id), array('%s', '%s'), array('%d') ); if ($result === false) { wp_send_json(array('success' => false, 'message' => __('Failed to retry job', 'ai-seo-article-generator'))); return; } // Clear retry count to start fresh delete_option('ai_seo_retry_count_job_' . $job_id); wp_send_json(array( 'success' => true, 'message' => __('Job has been queued for retry', 'ai-seo-article-generator') )); } public function ajax_manual_trigger_background() { check_ajax_referer('ai_seo_article_generator_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_die('Insufficient permissions'); } // Load background processor require_once AI_SEO_ARTICLE_GENERATOR_PLUGIN_DIR . 'includes/class-ai-seo-article-generator-background-processor.php'; $processor = new AI_SEO_Article_Generator_Background_Processor(); // Enable debug logging temporarily for this test update_option('ai_seo_article_generator_debug_logging', 1); // Log the manual trigger $this->debug_log('Manual background processing trigger initiated by user'); // Process queued jobs $processor->process_queued_jobs(); wp_send_json(array( 'success' => true, 'message' => __('Manual background processing triggered. Check debug logs and job status.', 'ai-seo-article-generator') )); } public function ajax_publish_article() { check_ajax_referer('ai_seo_article_generator_nonce', 'nonce'); if (!current_user_can('publish_posts')) { wp_die('Insufficient permissions to publish posts'); } // Fix WordPress auto-escaping before processing $title = isset($_POST['title']) ? sanitize_text_field(wp_unslash($_POST['title'])) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized by sanitize_seo_content() $content = isset($_POST['content']) ? $this->sanitize_seo_content(wp_unslash($_POST['content'])) : ''; $main_keyword = isset($_POST['main_keyword']) ? sanitize_text_field(wp_unslash($_POST['main_keyword'])) : ''; $sub_keywords = isset($_POST['sub_keywords']) ? sanitize_textarea_field(wp_unslash($_POST['sub_keywords'])) : ''; $structure_data = isset($_POST['structure_data']) ? sanitize_textarea_field(wp_unslash($_POST['structure_data'])) : ''; $draft_id = isset($_POST['draft_id']) ? intval($_POST['draft_id']) : null; $article_language = isset($_POST['article_language']) ? sanitize_text_field(wp_unslash($_POST['article_language'])) : 'auto'; // Clean markdown artifacts from content $content = $this->clean_content_artifacts($content); $title = $this->clean_content_artifacts($title); $main_keyword = $this->clean_content_artifacts($main_keyword); $sub_keywords = $this->clean_content_artifacts($sub_keywords); // Determine if content is Hebrew based on selected language $api = new AI_SEO_Article_Generator_API(); $is_hebrew = $api->determine_content_language($article_language); // Add table of contents to content require_once AI_SEO_ARTICLE_GENERATOR_PLUGIN_DIR . 'includes/class-ai-seo-article-generator-generator.php'; $generator = new AI_SEO_Article_Generator_Generator(); $content = $generator->insert_table_of_contents($content, 'auto', $is_hebrew); if (empty($title) || empty($content)) { wp_send_json(array('success' => false, 'message' => __('Title and content are required', 'ai-seo-article-generator'))); return; } // Parse structure data for meta description $structure = null; $meta_description = ''; if (!empty($structure_data)) { $structure = json_decode($structure_data, true); if ($structure && isset($structure['introduction'])) { $meta_description = wp_trim_words($structure['introduction'], 25, '...'); } } // If no meta description from structure, create one from content if (empty($meta_description)) { $meta_description = wp_trim_words(wp_strip_all_tags($content), 25, '...'); } // Create WordPress post $post_data = array( 'post_title' => $title, 'post_content' => $content, 'post_status' => 'draft', 'post_type' => 'post', 'meta_input' => array( // AI SEO Article Generator specific meta 'ai_seo_article_generator_main_keyword' => $main_keyword, 'ai_seo_article_generator_sub_keywords' => $sub_keywords, 'ai_seo_article_generator_structure_data' => $structure_data, 'ai_seo_article_generator_generated' => true, 'ai_seo_article_generator_draft_id' => $draft_id, 'ai_seo_article_generator_is_hebrew' => $is_hebrew, // SEO Meta Tags '_yoast_wpseo_metadesc' => $meta_description, // Yoast SEO '_aioseop_description' => $meta_description, // All in One SEO 'description' => $meta_description, // Generic '_yoast_wpseo_focuskw' => $main_keyword, // Yoast focus keyword '_aioseop_keywords' => $main_keyword . ', ' . $sub_keywords, // AIOSEO keywords // Open Graph meta '_yoast_wpseo_opengraph-title' => $title, '_yoast_wpseo_opengraph-description' => $meta_description, '_yoast_wpseo_opengraph-image' => '', // Can be populated later // Twitter Card meta '_yoast_wpseo_twitter-title' => $title, '_yoast_wpseo_twitter-description' => $meta_description, // Language and direction 'content_language' => $is_hebrew ? 'he' : 'en', 'text_direction' => $is_hebrew ? 'rtl' : 'ltr' ) ); $post_id = wp_insert_post($post_data); if (is_wp_error($post_id)) { wp_send_json(array('success' => false, 'message' => __('Error creating post:', 'ai-seo-article-generator') . ' ' . $post_id->get_error_message())); return; } if (!$post_id) { wp_send_json(array('success' => false, 'message' => __('Error creating post', 'ai-seo-article-generator'))); return; } // Generate edit URL for the new post $edit_url = admin_url('post.php?post=' . $post_id . '&action=edit'); // Update draft status if we have a draft_id if ($draft_id) { global $wpdb; $table_name = $wpdb->prefix . 'ai_seo_article_generator_drafts'; $wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table update $table_name, array('status' => 'published', 'wp_post_id' => $post_id), array('id' => $draft_id), array('%s', '%d'), array('%d') ); } wp_send_json(array( 'success' => true, 'message' => __('Article created successfully as WordPress draft', 'ai-seo-article-generator'), 'post_id' => $post_id, 'edit_url' => $edit_url )); } private function detect_hebrew_content($text) { // Check if text contains Hebrew characters return preg_match('/[\x{05D0}-\x{05EA}]/u', $text) > 0; } private function clean_content_artifacts($text) { if (!$text) return ''; // Remove markdown code blocks but preserve HTML structure $cleaned = preg_replace('/```html\s*/', '', $text); $cleaned = preg_replace('/```\s*/', '', $cleaned); // Fix escaped apostrophes and quotes but preserve HTML attributes $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 but preserve HTML structure $cleaned = stripslashes($cleaned); // Normalize whitespace but preserve HTML structure // Don't collapse whitespace inside HTML tags $cleaned = preg_replace('/\s+(?![^<]*>)/', ' ', $cleaned); // Clean up any malformed HTML tags $cleaned = $this->fix_malformed_html($cleaned); return trim($cleaned); } private function fix_malformed_html($content) { // Fix unclosed tags and common HTML issues $fixed = $content; // Ensure proper heading tag closure $fixed = preg_replace('/<(h[1-6])([^>]*)>(.*?)(?=$3', $fixed); // Ensure proper paragraph tag closure $fixed = preg_replace('/]*)>(.*?)(?=$2

', $fixed); // Fix list items $fixed = preg_replace('/]*)>(.*?)(?=|<\/ol>|$)/s', '$2', $fixed); // Remove empty paragraphs $fixed = preg_replace('/]*>\s*<\/p>/', '', $fixed); // Remove duplicate consecutive tags $fixed = preg_replace('/(<\/p>\s*]*>)+/', '

', $fixed); return $fixed; } private function sanitize_seo_content($content) { // Define allowed HTML tags for SEO content $allowed_html = wp_kses_allowed_html('post'); // Add essential SEO tags that might be missing from default allowed tags $seo_tags = array( 'h1' => array( 'id' => array(), 'class' => array(), 'style' => array(), ), 'h2' => array( 'id' => array(), 'class' => array(), 'style' => array(), ), 'h3' => array( 'id' => array(), 'class' => array(), 'style' => array(), ), 'h4' => array( 'id' => array(), 'class' => array(), 'style' => array(), ), 'h5' => array( 'id' => array(), 'class' => array(), 'style' => array(), ), 'h6' => array( 'id' => array(), 'class' => array(), 'style' => array(), ), 'div' => array( 'id' => array(), 'class' => array(), 'style' => array(), ), 'span' => array( 'id' => array(), 'class' => array(), 'style' => array(), ), 'table' => array( 'id' => array(), 'class' => array(), 'style' => array(), 'border' => array(), 'cellpadding' => array(), 'cellspacing' => array(), ), 'thead' => array(), 'tbody' => array(), 'tr' => array( 'id' => array(), 'class' => array(), ), 'th' => array( 'id' => array(), 'class' => array(), 'scope' => array(), ), 'td' => array( 'id' => array(), 'class' => array(), 'colspan' => array(), 'rowspan' => array(), ), ); // Merge with existing allowed tags $allowed_html = array_merge($allowed_html, $seo_tags); // Apply sanitization with our enhanced allowed tags return wp_kses($content, $allowed_html); } /** * Calculate word count for content * Handles both Hebrew and English text properly */ public function calculate_word_count($content) { if (empty($content)) { return 0; } // Remove HTML tags and decode entities $text = wp_strip_all_tags($content); $text = html_entity_decode($text, ENT_QUOTES, 'UTF-8'); // Remove extra whitespace and normalize $text = preg_replace('/\s+/', ' ', trim($text)); if (empty($text)) { return 0; } // For Hebrew text, count words by spaces and punctuation // Hebrew doesn't use spaces between all words consistently if (preg_match('/[\x{0590}-\x{05FF}]/u', $text)) { // Hebrew text detected - use character-based estimation // Average Hebrew word is about 4-5 characters $char_count = mb_strlen($text, 'UTF-8'); $estimated_words = max(1, round($char_count / 4.5)); // Also count actual spaces for mixed content $space_count = substr_count($text, ' '); // Use the higher count for better accuracy return max($estimated_words, $space_count + 1); } // For English/Latin text, use standard word counting $words = str_word_count($text, 0, 'אבגדהוזחטיכלמנסעפצקרשתךםןףץ'); return max(1, $words); } }