claude_api_key = get_option('ai_seo_article_generator_api_key', ''); $this->openai_api_key = get_option('ai_seo_article_generator_openai_api_key', ''); $this->ai_provider = get_option('ai_seo_article_generator_ai_provider', 'claude'); } /** * Determine content language based on user selection */ public function determine_content_language($article_language) { switch ($article_language) { case 'hebrew': return true; case 'english': return false; case 'auto': default: // Use WordPress locale detection $locale = get_locale(); $hebrew_locales = array('he_IL', 'he', 'hebrew', 'Hebrew', 'HE_IL', 'HE'); return in_array($locale, $hebrew_locales) || strpos($locale, 'he') === 0; } } /** * Get English length category for article type */ private function get_length_category_english($target_words) { if ($target_words >= 3000) return 'very comprehensive'; if ($target_words >= 2500) return 'comprehensive'; if ($target_words >= 2000) return 'detailed'; if ($target_words >= 1500) return 'long'; if ($target_words >= 1000) return 'medium'; return 'short'; } /** * 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 API: ' . $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 test_connection() { if ($this->ai_provider === 'openai') { $response = $this->make_openai_request('Test connection', 'Please respond with "Connection successful"', 50, 30); } else { $response = $this->make_claude_request('Test connection', 'Please respond with "Connection successful"', 50, 30); } if (is_wp_error($response)) { return array('success' => false, 'message' => $response->get_error_message()); } if (strpos(strtolower($response), 'connection successful') !== false) { update_option('ai_seo_article_generator_api_connected', true); $provider_name = ($this->ai_provider === 'openai') ? 'OpenAI' : 'Claude'; return array('success' => true, 'message' => "Connection successful with {$provider_name}!"); } return array('success' => false, 'message' => 'Unexpected response from API'); } public function generate_article_structure($main_keyword, $sub_keywords, $target_words, $article_language = 'auto') { $this->debug_log("šÆ AI SEO API: Starting structure generation for keyword: $main_keyword (language: $article_language)"); $prompt = $this->build_structure_prompt($main_keyword, $sub_keywords, $target_words, $article_language); $this->debug_log("š AI SEO API: Built prompt, length: " . strlen($prompt)); // Check the appropriate API key based on provider $api_key = ($this->ai_provider === 'openai') ? $this->openai_api_key : $this->claude_api_key; if (empty($api_key)) { $this->debug_log("ā AI SEO API: API key is empty for provider: " . $this->ai_provider); return array('success' => false, 'message' => 'API key ×× ××××ר'); } $this->debug_log("š AI SEO API: API key is set, making request..."); // Calculate dynamic timeout matching frontend calculation // Frontend: 30s + 30s per 1000 words (60s-180s range) $structure_timeout = max(60, min(180, 30 + ($target_words / 1000) * 30)); $this->debug_log("ā° AI SEO API: Structure timeout calculated: {$structure_timeout}s for {$target_words} words (matching frontend)"); $response = $this->make_request('Generate Article Structure', $prompt, 2500, $structure_timeout); if (is_wp_error($response)) { $this->debug_log("ā AI SEO API: Request failed with error: " . $response->get_error_message()); return array('success' => false, 'message' => $response->get_error_message()); } $this->debug_log("ā AI SEO API: Got response, length: " . strlen($response)); $this->debug_log("š AI SEO API: Response preview: " . substr($response, 0, 200) . "..."); // Check if response appears truncated if ($this->is_response_truncated($response)) { $this->debug_log("ā ļø AI SEO API: Response appears truncated, retrying with higher max_tokens..."); // Use extended timeout for retry (add 50% more time) $retry_timeout = min(270, ceil($structure_timeout * 1.5)); // Cap at 4.5 minutes $this->debug_log("ā° AI SEO API: Structure retry timeout: {$retry_timeout}s"); $retry_response = $this->make_request('Generate Article Structure - Retry', $prompt, 4000, $retry_timeout); if (!is_wp_error($retry_response) && !$this->is_response_truncated($retry_response)) { $this->debug_log("ā AI SEO API: Retry successful, using retry response"); $response = $retry_response; } else { $this->debug_log("ā ļø AI SEO API: Retry also truncated or failed, proceeding with original response"); } } $structure = $this->parse_structure_response($response); $this->debug_log("šļø AI SEO API: Parsed structure", $structure); return array('success' => true, 'structure' => $structure); } public function generate_article_content($structure, $main_keyword, $sub_keywords, $target_words, $article_language = 'auto') { $this->debug_log("š AI SEO API: Starting content generation for {$target_words} words (language: $article_language)"); $prompt = $this->build_content_prompt($structure, $main_keyword, $sub_keywords, $target_words, $article_language); $this->debug_log("š AI SEO API: Content prompt built, length: " . strlen($prompt)); // Calculate max_tokens based on target words (approximately 7-8 tokens per Hebrew word) // Claude-3.5-Sonnet has a max of 8192 tokens output, but we'll use dynamic scaling $tokens_per_word = 7; // More accurate for Hebrew content $max_tokens = min(8100, max(4000, $target_words * $tokens_per_word)); // For very long articles, we'll use section-by-section generation if ($target_words > 2500) { $this->debug_log("š AI SEO API: Long article detected ({$target_words} words), implementing enhanced generation strategy"); return $this->generate_long_article_content($structure, $main_keyword, $sub_keywords, $target_words); } // Calculate timeout based on target words (more words = longer timeout) // Use admin-configurable maximum timeout for Hebrew content generation $max_content_timeout = get_option('ai_seo_article_generator_max_content_timeout', 600); $base_content_timeout = min(180, $max_content_timeout * 0.3); // 30% of max as base $content_timeout = max($base_content_timeout, min($max_content_timeout, $target_words / 4)); // Dynamic based on word count $this->debug_log("āļø AI SEO API: Using max_tokens: {$max_tokens}, timeout: {$content_timeout}s (max: {$max_content_timeout}s)"); $response = $this->make_request('Generate Article Content', $prompt, $max_tokens, $content_timeout); if (is_wp_error($response)) { $this->debug_log("ā AI SEO API: Content generation failed: " . $response->get_error_message()); return array('success' => false, 'message' => $response->get_error_message()); } $this->debug_log("ā AI SEO API: Content generation completed, response length: " . strlen($response)); // Validate content structure and quality $structure_validation = $this->validate_content_structure($response, $main_keyword, $target_words); // If structure validation fails, try to fix the content if (!$structure_validation['valid']) { $this->debug_log("ā ļø AI SEO API: Content structure validation failed: " . implode(', ', $structure_validation['issues'])); // Try to enhance the content with missing elements $enhanced_content = $this->enhance_content_structure($response, $main_keyword, $structure_validation['issues']); if ($enhanced_content) { $response = $enhanced_content; $this->debug_log("š§ AI SEO API: Enhanced content structure automatically"); // Re-validate after enhancement $structure_validation = $this->validate_content_structure($response, $main_keyword, $target_words); } } // Validate content length with improved logic $validation_result = $this->validate_content_length($response, $target_words); // Apply keyword density optimization if needed if ($structure_validation['valid'] && isset($structure_validation['stats']['keyword_density'])) { $keyword_density = $structure_validation['stats']['keyword_density']; if ($keyword_density < 1.0) { $this->debug_log("ā ļø AI SEO API: Low keyword density ({$keyword_density}%), attempting optimization"); $optimized_content = $this->optimize_keyword_density($response, $main_keyword, $target_words); if ($optimized_content) { $response = $optimized_content; $this->debug_log("š§ AI SEO API: Applied keyword density optimization"); } } } // Much stricter acceptance criteria - require at least 90% of target words $min_acceptable = $target_words * 0.9; // Require 90% of target $should_retry = false; if ($validation_result['word_count'] >= $min_acceptable) { $this->debug_log("ā AI SEO API: Content accepted - {$validation_result['word_count']} words (target: {$target_words})"); // Even more aggressive retry logic - always retry if not 95% of target $should_retry = $validation_result['word_count'] < ($target_words * 0.95); if ($should_retry && $target_words <= 3500) { $this->debug_log("ā ļø AI SEO API: Content short ({$validation_result['word_count']} words), attempting retry..."); $percentage_achieved = round(($validation_result['word_count'] / $target_words) * 100); $words_needed = $target_words - $validation_result['word_count']; $enhanced_prompt = $this->build_content_prompt($structure, $main_keyword, $sub_keywords, $target_words) . "\n\nšØ × ××”××× ×©× × - ××××ר ××§××× ×§×¦×Ø ×××!\n" . "š ×××©× × ××××: {$validation_result['word_count']} ××××× ({$percentage_achieved}% ××××¢×)\n" . "šÆ × ×רש ××××”××£: {$words_needed} ××××× × ×הפ××Ŗ\n" . "š ××¢× ×”×פ×: {$target_words} ××××× ×××××§××Ŗ\n\n" . "š„ ××ר×××Ŗ ×××××××Ŗ ×××××× ××הר:\n" . "- ×××”×£ פהק×××Ŗ × ×הפ××Ŗ ××× ×¤×Ø×§ (×פ×××Ŗ 6-8 פהק×××Ŗ ×פרק)\n" . "- ×××× ×××××××Ŗ ×עש×××Ŗ ×פ×ר×××Ŗ ×××§×Ø× ××××\n" . "- ×××”×£ רש××××Ŗ ×©× 8-12 × ×§××××Ŗ ×¢× ××”×ר××\n" . "- ×××× ×©××××Ŗ ×תש××××Ŗ × ×¤×צ××Ŗ ××× ×¤×Ø×§\n" . "- ×ר×× ×¢× ×¤×Ø××× ××× ×××, × ×Ŗ×× ×× ××”××××”×××§××Ŗ\n" . "- ×××”×£ ××פ×× ××Ŗ×§×××× ×××× ××§××Ŗ ×××××\n" . "- ××Ŗ×× ××××× ××ר××× - ××× ×××× ×¢××××!\n" . "- ×× ×Ŗ×¢×¦×ר ×¢× ×©×Ŗ×××¢ ×××××§ ×-{$target_words} ×××××!"; $retry_max_tokens = min(8000, $target_words * 5); $retry_response = $this->make_request('Generate Article Content - Retry', $enhanced_prompt, $retry_max_tokens, $content_timeout + 60); if (!is_wp_error($retry_response)) { $retry_validation = $this->validate_content_length($retry_response, $target_words); // Use retry if it's significantly better, otherwise stick with original if ($retry_validation['word_count'] > $validation_result['word_count'] * 1.3) { $this->debug_log("ā AI SEO API: Retry successful - using {$retry_validation['word_count']} words instead of {$validation_result['word_count']}"); return array('success' => true, 'content' => $retry_response, 'word_count' => $retry_validation['word_count']); } else { $this->debug_log("ā ļø AI SEO API: Retry didn't improve significantly, using original content"); } } else { $this->debug_log("ā ļø AI SEO API: Retry failed, using original content"); } } // Return the original content (it's good enough) return array('success' => true, 'content' => $response, 'word_count' => $validation_result['word_count']); } // For articles that are still too short, try content extension (now more aggressive) if ($validation_result['word_count'] < $target_words * 0.95) { $this->debug_log("ā ļø AI SEO API: Article significantly short, attempting content extension"); $extended_content = $this->extend_article_content($response, $main_keyword, $target_words, $validation_result['word_count']); if ($extended_content) { $extended_validation = $this->validate_content_length($extended_content, $target_words); if ($extended_validation['word_count'] > $validation_result['word_count']) { $this->debug_log("ā AI SEO API: Content extended successfully to {$extended_validation['word_count']} words"); return array('success' => true, 'content' => $extended_content, 'word_count' => $extended_validation['word_count']); } } } return array('success' => true, 'content' => $response, 'word_count' => $validation_result['word_count']); } public function enhance_section($section_content, $main_keyword, $enhancement_type = 'improve') { $prompt = $this->build_enhancement_prompt($section_content, $main_keyword, $enhancement_type); $response = $this->make_request('Enhance Section', $prompt, 1000, 45); if (is_wp_error($response)) { return array('success' => false, 'message' => $response->get_error_message()); } return array('success' => true, 'content' => $response); } private function make_claude_request($purpose, $prompt, $max_tokens = 1000, $timeout = 60) { $this->debug_log("š AI SEO API: Making request for: $purpose"); $this->debug_log("š AI SEO API: Max tokens: $max_tokens"); $this->debug_log("ā° AI SEO API: Timeout: {$timeout}s"); // Set appropriate execution time limit for long-running operations if ($timeout > 300) { // For very long operations (5+ minutes), remove execution time limit if (function_exists('set_time_limit') && !ini_get('safe_mode')) { // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged -- Required for long API operations @set_time_limit(0); } $this->debug_log("ā° AI SEO API: Removed execution time limit for long operation ({$timeout}s timeout)"); } else { // Set execution time limit to 50% more than request timeout to allow for processing $execution_limit = ceil($timeout * 1.5); if (function_exists('set_time_limit') && !ini_get('safe_mode')) { // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged -- Required for API timeout control @set_time_limit($execution_limit); } $this->debug_log("ā° AI SEO API: Set execution time limit to {$execution_limit}s (request timeout: {$timeout}s)"); } // Log start time for debugging timeout issues $start_time = microtime(true); $this->debug_log("š AI SEO API: Starting request at " . gmdate('Y-m-d H:i:s') . " UTC with {$timeout}s timeout"); if (empty($this->claude_api_key)) { $this->debug_log("ā AI SEO API: Claude API key is empty in make_claude_request"); return new WP_Error('no_api_key', 'API key not configured'); } $this->debug_log("š AI SEO API: Claude API key length: " . strlen($this->claude_api_key)); $headers = array( 'Content-Type' => 'application/json', 'x-api-key' => $this->claude_api_key, 'anthropic-version' => '2023-06-01' ); $this->debug_log("š AI SEO API: Headers prepared"); // Determine system prompt language based on content $is_hebrew_content = strpos($prompt, '××¢×ר××Ŗ') !== false || strpos($prompt, '×××ר') !== false; $system_prompt = $is_hebrew_content ? '××Ŗ× ×××Ŗ× ×Ŗ××× ×קצ××¢× ××¢×ר××Ŗ ×××Ŗ××× ×-SEO, AEO ×-GEO. ××Ŗ× ×××צר ×Ŗ××× ×××××Ŗ×, ×פ××Ø× ×××§××£ ××¢×ר××Ŗ ×××¢××Ŗ.' : 'You are a professional content writer specializing in SEO, AEO, and GEO. You produce high-quality, detailed, and comprehensive content in natural English.'; $body = array( 'model' => 'claude-sonnet-4-20250514', 'max_tokens' => $max_tokens, 'temperature' => 0.7, 'system' => $system_prompt, 'messages' => array( array( 'role' => 'user', 'content' => $prompt ) ) ); $this->debug_log("š¦ AI SEO API: Request body prepared, JSON size: " . strlen(json_encode($body))); $this->debug_log("šÆ AI SEO API: Request URL: " . $this->claude_api_url); $this->debug_log("ā° AI SEO API: Request timeout: {$timeout}s"); $response = wp_remote_post($this->claude_api_url, array( 'headers' => $headers, 'body' => json_encode($body), 'timeout' => $timeout, )); // Log completion time for debugging $end_time = microtime(true); $actual_duration = round($end_time - $start_time, 2); $this->debug_log("š AI SEO API: Request completed in {$actual_duration}s (timeout was {$timeout}s)"); $this->debug_log("š” AI SEO API: Request sent, checking response..."); if (is_wp_error($response)) { $error_message = $response->get_error_message(); $this->debug_log("ā AI SEO API: WP_Error in response: " . $error_message); // Handle timeout specifically with improved messaging if (strpos($error_message, 'timed out') !== false || strpos($error_message, 'timeout') !== false) { $this->debug_log("ā° AI SEO API: Request timed out after {$timeout}s"); // Provide specific timeout messages based on operation type if ($purpose === 'Generate Article Content' || $purpose === 'Generate Article Content - Retry') { $timeout_minutes = ceil($timeout / 60); return new WP_Error('content_timeout', "×צ×רת ××××ר ×××§××Ŗ ××× ×Ø× ××צפ×× ({$timeout_minutes} ××§××Ŗ). ×× ×××× ×קר××Ŗ ×¢× ×××ר×× ×ר×××× ×× ××ר××××. ×× × × ×”× ×©×× ×× ××§×× ××Ŗ ×××Ø× ××××ר."); } elseif (strpos($purpose, 'Generate Article Structure') !== false) { $timeout_minutes = ceil($timeout / 60); return new WP_Error('structure_timeout', "×צ×רת ××× × ××××ר ×××§××Ŗ ××× ×Ø× ××צפ×× ({$timeout_minutes} ××§××Ŗ). ×× ×××× ×קר××Ŗ ×¢× ×××ר×× ××ר××××. ×× × × ×”× ×©×× ×× ××¤×©× ××Ŗ ××ר×ש××Ŗ."); } else { return new WP_Error('operation_timeout', "×פע××× ×××§××Ŗ ××× ×Ø× ××צפ×× ({$timeout} ×©× ×××Ŗ). ×× × × ×”× ×©×× ××××ר ××תר."); } } return $response; } $response_body = wp_remote_retrieve_body($response); $response_code = wp_remote_retrieve_response_code($response); $this->debug_log("š AI SEO API: Response code: $response_code"); $this->debug_log("š AI SEO API: Response body length: " . strlen($response_body)); if ($response_code !== 200) { $this->debug_log("ā AI SEO API: Non-200 response code: $response_code"); $this->debug_log("š AI SEO API: Error response body: " . $response_body); $error_data = json_decode($response_body, true); $error_message = $error_data['error']['message'] ?? 'Unknown error'; $error_type = $error_data['error']['type'] ?? 'unknown_error'; $this->debug_log("š„ AI SEO API: Parsed error - Type: $error_type, Message: $error_message"); // Handle specific error types if ($error_type === 'invalid_request_error' && strpos($error_message, 'max_tokens') !== false) { return new WP_Error('token_limit_exceeded', '×××§×©× ×ר××× ×××. × ×”× ×××§××× ××Ŗ ×××Ø× ××××ר ××××קש.'); } elseif ($response_code === 429) { return new WP_Error('rate_limit_exceeded', '×××¢×Ŗ ××××××Ŗ ××קש××Ŗ. × ×”× ×©×× ××¢×× ××× ××§××Ŗ.'); } elseif ($response_code >= 500) { return new WP_Error('server_error', 'ש××××Ŗ שרת ×-Claude API. × ×”× ×©×× ××¢×× ××× ××§××Ŗ.'); } return new WP_Error('api_error', 'API Error: ' . $error_message); } $data = json_decode($response_body, true); $this->debug_log("š AI SEO API: JSON decode result: " . (is_array($data) ? 'Success' : 'Failed')); if (!isset($data['content'][0]['text'])) { $this->debug_log("ā AI SEO API: Invalid response structure"); $this->debug_log("š AI SEO API: Response structure", $data); return new WP_Error('invalid_response', 'Invalid API response format'); } $response_text = $data['content'][0]['text']; $final_duration = round(microtime(true) - $start_time, 2); $this->debug_log("ā AI SEO API: Successfully extracted response text, length: " . strlen($response_text) . " (total time: {$final_duration}s)"); return $response_text; } private function make_openai_request($purpose, $prompt, $max_tokens = 1000, $timeout = 60) { $this->debug_log("š AI SEO API (OpenAI): Making request for: $purpose"); $this->debug_log("š AI SEO API (OpenAI): Max tokens: $max_tokens"); $this->debug_log("ā° AI SEO API (OpenAI): Timeout: {$timeout}s"); // Set appropriate execution time limit for long-running operations if ($timeout > 300) { if (function_exists('set_time_limit') && !ini_get('safe_mode')) { // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged -- Required for long API operations @set_time_limit(0); } $this->debug_log("ā° AI SEO API (OpenAI): Removed execution time limit for long operation ({$timeout}s timeout)"); } else { $execution_limit = ceil($timeout * 1.5); if (function_exists('set_time_limit') && !ini_get('safe_mode')) { // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged -- Required for API timeout control @set_time_limit($execution_limit); } $this->debug_log("ā° AI SEO API (OpenAI): Set execution time limit to {$execution_limit}s (request timeout: {$timeout}s)"); } $start_time = microtime(true); $this->debug_log("š AI SEO API (OpenAI): Starting request at " . gmdate('Y-m-d H:i:s') . " UTC with {$timeout}s timeout"); if (empty($this->openai_api_key)) { $this->debug_log("ā AI SEO API (OpenAI): OpenAI API key is empty in make_openai_request"); return new WP_Error('no_api_key', 'OpenAI API key not configured'); } $this->debug_log("š AI SEO API (OpenAI): OpenAI API key length: " . strlen($this->openai_api_key)); $headers = array( 'Content-Type' => 'application/json', 'Authorization' => 'Bearer ' . $this->openai_api_key, ); $this->debug_log("š AI SEO API (OpenAI): Headers prepared"); // Get selected OpenAI model $openai_model = get_option('ai_seo_article_generator_openai_model', 'gpt-4o'); $body = array( 'model' => $openai_model, 'max_tokens' => $max_tokens, 'temperature' => 0.7, 'messages' => array( array( 'role' => 'system', 'content' => '××Ŗ× ×××Ŗ× ×Ŗ××× ×קצ××¢× ××¢×ר××Ŗ ×××Ŗ××× ×-SEO, AEO ×-GEO. ××Ŗ× ×××צר ×Ŗ××× ×××××Ŗ×, ×פ××Ø× ×××§××£ ××¢×ר××Ŗ ×××¢××Ŗ.' ), array( 'role' => 'user', 'content' => $prompt ) ) ); $this->debug_log("š¦ AI SEO API (OpenAI): Request body prepared, JSON size: " . strlen(json_encode($body))); $this->debug_log("šÆ AI SEO API (OpenAI): Request URL: " . $this->openai_api_url); $this->debug_log("š¤ AI SEO API (OpenAI): Using model: " . $openai_model); $this->debug_log("ā° AI SEO API (OpenAI): Request timeout: {$timeout}s"); $response = wp_remote_post($this->openai_api_url, array( 'headers' => $headers, 'body' => json_encode($body), 'timeout' => $timeout, )); $end_time = microtime(true); $actual_duration = round($end_time - $start_time, 2); $this->debug_log("š AI SEO API (OpenAI): Request completed in {$actual_duration}s (timeout was {$timeout}s)"); $this->debug_log("š” AI SEO API (OpenAI): Request sent, checking response..."); if (is_wp_error($response)) { $error_message = $response->get_error_message(); $this->debug_log("ā AI SEO API (OpenAI): WP_Error in response: " . $error_message); if (strpos($error_message, 'timed out') !== false || strpos($error_message, 'timeout') !== false) { $this->debug_log("ā° AI SEO API (OpenAI): Request timed out after {$timeout}s"); if ($purpose === 'Generate Article Content' || $purpose === 'Generate Article Content - Retry') { $timeout_minutes = ceil($timeout / 60); return new WP_Error('content_timeout', "×צ×רת ××××ר ×××צע××Ŗ OpenAI ×××§××Ŗ ××× ×Ø× ××צפ×× ({$timeout_minutes} ××§××Ŗ). ×× ×××× ×קר××Ŗ ×¢× ×××ר×× ×ר×××× ×× ××ר××××. ×× × × ×”× ×©×× ×× ××§×× ××Ŗ ×××Ø× ××××ר."); } elseif (strpos($purpose, 'Generate Article Structure') !== false) { $timeout_minutes = ceil($timeout / 60); return new WP_Error('structure_timeout', "×צ×רת ××× × ××××ר ×××צע××Ŗ OpenAI ×××§××Ŗ ××× ×Ø× ××צפ×× ({$timeout_minutes} ××§××Ŗ). ×× ×××× ×קר××Ŗ ×¢× ×××ר×× ××ר××××. ×× × × ×”× ×©×× ×× ××¤×©× ××Ŗ ××ר×ש××Ŗ."); } else { return new WP_Error('operation_timeout', "×פע××× ×××צע××Ŗ OpenAI ×××§××Ŗ ××× ×Ø× ××צפ×× ({$timeout} ×©× ×××Ŗ). ×× × × ×”× ×©×× ××××ר ××תר."); } } return $response; } $response_body = wp_remote_retrieve_body($response); $response_code = wp_remote_retrieve_response_code($response); $this->debug_log("š AI SEO API (OpenAI): Response code: $response_code"); $this->debug_log("š AI SEO API (OpenAI): Response body length: " . strlen($response_body)); if ($response_code !== 200) { $this->debug_log("ā AI SEO API (OpenAI): Non-200 response code: $response_code"); $this->debug_log("š AI SEO API (OpenAI): Error response body: " . $response_body); $error_data = json_decode($response_body, true); $error_message = $error_data['error']['message'] ?? 'Unknown error'; $error_type = $error_data['error']['type'] ?? 'unknown_error'; $this->debug_log("š„ AI SEO API (OpenAI): Parsed error - Type: $error_type, Message: $error_message"); if ($response_code === 429) { return new WP_Error('rate_limit_exceeded', '×××¢×Ŗ ××××××Ŗ ××קש××Ŗ ×©× OpenAI. × ×”× ×©×× ××¢×× ××× ××§××Ŗ.'); } elseif ($response_code >= 500) { return new WP_Error('server_error', 'ש××××Ŗ שרת ×-OpenAI API. × ×”× ×©×× ××¢×× ××× ××§××Ŗ.'); } elseif ($response_code === 401) { return new WP_Error('invalid_api_key', '××¤×Ŗ× ×-API ×©× OpenAI ×× ×Ŗ×§××. ×× × ××××§ ××Ŗ ××××ר××Ŗ.'); } return new WP_Error('api_error', 'OpenAI API Error: ' . $error_message); } $data = json_decode($response_body, true); $this->debug_log("š AI SEO API (OpenAI): JSON decode result: " . (is_array($data) ? 'Success' : 'Failed')); if (!isset($data['choices'][0]['message']['content'])) { $this->debug_log("ā AI SEO API (OpenAI): Invalid response structure"); $this->debug_log("š AI SEO API (OpenAI): Response structure", $data); return new WP_Error('invalid_response', 'Invalid OpenAI API response format'); } $response_text = $data['choices'][0]['message']['content']; $final_duration = round(microtime(true) - $start_time, 2); $this->debug_log("ā AI SEO API (OpenAI): Successfully extracted response text, length: " . strlen($response_text) . " (total time: {$final_duration}s)"); return $response_text; } /** * Helper method to route requests to the appropriate AI provider */ private function make_request($purpose, $prompt, $max_tokens = 1000, $timeout = 60) { if ($this->ai_provider === 'openai') { return $this->make_openai_request($purpose, $prompt, $max_tokens, $timeout); } else { return $this->make_claude_request($purpose, $prompt, $max_tokens, $timeout); } } private function build_structure_prompt($main_keyword, $sub_keywords, $target_words, $article_language = 'auto') { $sub_keywords_list = is_array($sub_keywords) ? implode(', ', $sub_keywords) : $sub_keywords; // Determine if content should be Hebrew $is_hebrew = $this->determine_content_language($article_language); $data = array('main_keyword' => $main_keyword, 'is_hebrew' => $is_hebrew); // Apply AEO enhancement filter if ($is_hebrew) { $base_prompt = "××Ŗ× ×××Ŗ× ×Ŗ××× ×קצ××¢× ××¢×ר××Ŗ ×××Ŗ××× ×-SEO, AEO ×-GEO. צ×ר ××× × ×פ××Ø× ××××ר ××פ××××× ××¢×ר××Ŗ. ××××Ŗ ×××¤×Ŗ× ×ר×ש××Ŗ: {$main_keyword} ×××××Ŗ ××¤×Ŗ× ××©× ×××Ŗ: {$sub_keywords_list} ×××Ø× ××¢×: {$target_words} ××××× ×¦×ר ××× × ×××ר ×××××: 1. ××תרת ר×ש××Ŗ ××ש××Ŗ ×××××××Ŗ××Ŗ ××¢×××ר 2. ×Ŗ×Ŗ-××תר××Ŗ (H2, H3) ×¢× ×©×××× ×××¢× ×©× ×××××Ŗ ×××¤×Ŗ× 3. פהק×××Ŗ ×××× ××”×××× 4. רש×××Ŗ × ×ש××× ×¢×קר××× ××× ×¤×Ø×§ 5. ×צע××Ŗ ××Ŗ××× ××××××××× (×Ŗ××× ××Ŗ, ×××××) šÆ ×ר×ש××Ŗ AEO (××פ×××××צ×× ××× ××¢× ×Ŗ×©××××Ŗ) 2025: - ×× ××תרת פרק ×××××Ŗ ×××××Ŗ ×צ×רת ש××× (×× ×× {$main_keyword}? ××× ×¢××× {$main_keyword}? ××× ×ש×× {$main_keyword}?) - ×× ×¤×Ø×§ ××Ŗ××× ×תש××× ×ש××Ø× ×©× 40-60 ××××× - ×××× ×§××¢× ××××Ø× ×ר×ר×× ××× ××× × ×ר××× - ×Ŗ×× × ××§×× ××××××××Ŗ ××¢×××××Ŗ ×הפר×××Ŗ - צ×ר ××× × ×©×§× ×××ׄ ××× × ××××¢ (××××§× ××××¢ קצר××) - ×××”×£ ×§××¢× ×©××××Ŗ × ×¤×צ××Ŗ ××× ×¤×Ø×§ ×ר××× ×××× × ×¦×Ø×× ×××××Ŗ: - ××××××Ŗ× ××× ××¢× ××פ×ש (SEO) - ×××Ŗ×× ××× ××¢× ×Ŗ×©××××Ŗ (AEO) - תש××××Ŗ ×ש×ר××Ŗ ××ר×ר××Ŗ - ××פ××××× ××× ××¢× ×צ××Ø× (GEO) - ××©×¤× ×¢×ר××Ŗ ×××¢××Ŗ ××קצ××¢××Ŗ - ×××ר×× ×צ××Ø× ××××× ××Ŗ ×קר××× ā ļø ×ש×× ××××: ×××ר רק JSON ×Ŗ×§×× ××× ××§×”× × ××”×£! ×× ×Ŗ×©×Ŗ×ש ×××××§ ×§×× markdown (```json) ×××ר רק ××Ŗ ×-JSON ××× ×ש×× ××ר ××ר: { \"title\": \"××תרת ר×ש××Ŗ ×קצ××¢××Ŗ ×¢× ××××Ŗ ××פת×\", \"introduction\": \"×Ŗ×××ר ×פ××Ø× ×©× ××××× ×©××××× ×××××Ŗ ×פת×\", \"sections\": [ { \"heading\": \"פרק 1: ××תרת H2 ×¢× ××××Ŗ ×פת×\", \"subheadings\": [\"×Ŗ×Ŗ-× ××©× 1\", \"×Ŗ×Ŗ-× ××©× 2\", \"×Ŗ×Ŗ-× ××©× 3\"], \"topics\": [\"× ××©× ×פ××Ø× 1\", \"× ××©× ×פ××Ø× 2\", \"× ××©× ×פ××Ø× 3\"], \"word_count\": 400 }, { \"heading\": \"פרק 2: ××תרת H2 × ×הפת\", \"subheadings\": [\"×Ŗ×Ŗ-× ××©× 1\", \"×Ŗ×Ŗ-× ××©× 2\"], \"topics\": [\"× ××©× ×פ××Ø× 1\", \"× ××©× ×פ××Ø× 2\"], \"word_count\": 400 } ], \"conclusion\": \"×Ŗ×××ר ×פ××Ø× ×©× ××”××××\", \"multimedia_suggestions\": [\"×Ŗ××× × ××××××× ××Ŗ ×× ×ש×\", \"××× ×¤××רפ××§× ×¢× × ×Ŗ×× ××\", \"××××× ××”×ר\"] } ש×× ××: - ××××ר ×©× 1000 ×××××: צ×ר 4-5 פרק×× ×¢×קר××× - ××××ר ×©× 1500 ×××××: צ×ר 5-6 פרק×× ×¢×קר××× - ××××ר ×©× 2000 ×××××: צ×ר 6-7 פרק×× ×¢×קר××× - ××××ר ×©× 2500+ ×××××: צ×ר 7-9 פרק×× ×¢×קר××× ×פ×ר××× ×¢××ר ×××ר ×©× {$target_words} ××××× - ××§×¤× ××צ×ר ×הפר פרק×× ××Ŗ××× ××פ×ר×××!"; } else { // English prompt $base_prompt = "You are a professional content writer specializing in SEO, AEO & GEO. Create a detailed structure for an optimal English article. Main keyword: {$main_keyword} Secondary keywords: {$sub_keywords_list} Target length: {$target_words} words Create an article structure including: 1. Engaging main title with keyword integration 2. Subheadings (H2, H3) with natural keyword integration 3. Introduction and conclusion paragraphs 4. List of main topics for each chapter 5. Multimedia content suggestions (images, video) šÆ AEO (Answer Engine Optimization) Requirements 2025: - Every chapter heading must be in question format (What is {$main_keyword}? How does {$main_keyword} work? Why is {$main_keyword} important?) - Each chapter starts with a direct answer of 40-60 words - Include clear definition sections for all key terms - Plan space for examples and numerical facts - Create structure that's easy to extract information from (short info blocks) - Add FAQ sections in each main chapter The structure should be: - Search engine friendly (SEO) - Optimized for answer engines (AEO) - direct and clear answers - Optimal for generative engines (GEO) - In natural, professional English - Logically organized and readable ā ļø Very important: Return only valid JSON without additional text! Do not use markdown code blocks (```json) Return only this JSON and nothing else: { \"title\": \"Professional main title with keyword\", \"introduction\": \"Detailed description of introduction including keywords\", \"sections\": [ { \"heading\": \"Chapter 1: H2 heading with keyword\", \"subheadings\": [\"Sub-topic 1\", \"Sub-topic 2\", \"Sub-topic 3\"], \"topics\": [\"Detailed topic 1\", \"Detailed topic 2\", \"Detailed topic 3\"], \"word_count\": 400 }, { \"heading\": \"Chapter 2: Another H2 heading\", \"subheadings\": [\"Sub-topic 1\", \"Sub-topic 2\"], \"topics\": [\"Detailed topic 1\", \"Detailed topic 2\"], \"word_count\": 400 } ], \"conclusion\": \"Detailed description of conclusion section\", \"faq\": [ { \"question\": \"Common question about {$main_keyword}\", \"answer_preview\": \"Brief answer preview\" } ], \"total_word_count\": {$target_words} } š Article Length Guidelines: - For 500-1000 word articles: Create 3-4 main chapters - For 1000-1500 word articles: Create 4-5 main chapters - For 1500-2000 word articles: Create 5-6 detailed chapters - For 2000-2500 word articles: Create 6-7 comprehensive chapters - For 2500+ word articles: Create 7-9 detailed main chapters For an article of {$target_words} words - ensure appropriate number of detailed chapters!"; } // Apply AEO enhancement filter return apply_filters('ai_seo_article_generator_structure_prompt', $base_prompt, $data); } private function build_content_prompt($structure, $main_keyword, $sub_keywords, $target_words, $article_language = 'auto') { $structure_json = json_encode($structure, JSON_UNESCAPED_UNICODE); $sub_keywords_list = is_array($sub_keywords) ? implode(', ', $sub_keywords) : $sub_keywords; // Determine content language $is_hebrew = $this->determine_content_language($article_language); // Enhanced word distribution for longer articles $sections_count = isset($structure['sections']) ? count($structure['sections']) : 3; $intro_words = min(300, $target_words * 0.15); // 10-15% for intro $conclusion_words = min(250, $target_words * 0.12); // 10-12% for conclusion $remaining_words = $target_words - $intro_words - $conclusion_words; $words_per_section = floor($remaining_words / $sections_count); // For longer articles, increase minimum words per section if ($target_words >= 2500) { $words_per_section = max($words_per_section, 400); } // Calculate optimal keyword density (1.5-2.5%) $target_keyword_density = 2.0; $target_keyword_occurrences = ceil(($target_words * $target_keyword_density) / 100); // Build detailed section requirements with expansion instructions $section_requirements = ""; if (isset($structure['sections'])) { foreach ($structure['sections'] as $index => $section) { $section_words = isset($section['word_count']) ? max($section['word_count'], $words_per_section) : $words_per_section; $section_keywords_needed = ceil($section_words * 0.02); // 2% density per section if ($is_hebrew) { $section_requirements .= "פרק " . ($index + 1) . " ('{$section['heading']}'): ×פ×××Ŗ {$section_words} ××××× ×פ×ר×××Ŗ\n"; $section_requirements .= " ⢠×××× ×פ×××Ŗ 4-5 פהק×××Ŗ\n"; $section_requirements .= " ⢠×××”×£ 2-3 ×××××××Ŗ ×עש×××Ŗ\n"; $section_requirements .= " ⢠×××× ×Ø×©××× ×©× 5-7 × ×§××××Ŗ\n"; $section_requirements .= " ⢠××”×ר ×פ××Ø× ×××× ×\n"; $section_requirements .= " ⢠ש×× ××Ŗ ××××Ŗ ×××¤×Ŗ× '{$main_keyword}' {$section_keywords_needed} פע××× ××××¤× ×××¢×\n\n"; } else { $section_requirements .= "Chapter " . ($index + 1) . " ('{$section['heading']}'): at least {$section_words} detailed words\n"; $section_requirements .= " ⢠Include at least 4-5 paragraphs\n"; $section_requirements .= " ⢠Add 2-3 practical examples\n"; $section_requirements .= " ⢠Include a list of 5-7 points\n"; $section_requirements .= " ⢠Provide detailed technical explanation\n"; $section_requirements .= " ⢠Integrate the keyword '{$main_keyword}' {$section_keywords_needed} times naturally\n\n"; } } } // Enhanced instructions for longer articles if ($is_hebrew) { $length_category = $target_words >= 2500 ? '×ר××' : ($target_words >= 1500 ? '××× ×× ×-×ר××' : '××× ×× ×'); $expansion_level = $target_words >= 2500 ? '××§×”×××××Ŗ' : '×××××'; $base_prompt = "šØ ××Ŗ×× ×××ר ××× ×××§××£ ××¢×ר××Ŗ - {$target_words} ××××× ×××× ××××××Ŗ! š ×”×× ×××ר: {$length_category} (×ר×שת ×ר×××: {$expansion_level}) ā ļø ×ש×× ×××תר: ××××ר ×××× ××××× ×פ×××Ŗ {$target_words} ×××××! ×× ×Ŗ×¢×¦×ר ××¤× × ×©×××¢×Ŗ ×××¢×! šÆ פר×× ××××ר: ××××Ŗ ×××¤×Ŗ× ×ר×ש××Ŗ: {$main_keyword} ×××××Ŗ ××¤×Ŗ× ××©× ×××Ŗ: {$sub_keywords_list} ×××Ø× ××¢× ×××××: {$target_words} ××××× ×××××§ (×× ×¤×××Ŗ!) ××× × ××××ר: {$structure_json} š ×ר×ש××Ŗ ×××Ø× ×××××§××Ŗ (×”×\"× {$target_words} ×××××): - ×××× ×פ×ר×: ×פ×××Ŗ {$intro_words} ××××× - ×”×××× ××§××£: ×פ×××Ŗ {$conclusion_words} ××××× {$section_requirements} šÆ ×ר×ש××Ŗ SEO קר×××××Ŗ: - ש×××× ××××Ŗ ×××¤×Ŗ× '{$main_keyword}' ×××××§ {$target_keyword_occurrences} פע××× (צפ×פ××Ŗ {$target_keyword_density}%) - ×××× ×××××Ŗ H1 ××× ×××× ×¢× ××××Ŗ ×××¤×Ŗ× ×××תרת ×ר×ש××Ŗ - ×פ×××Ŗ {$sections_count} ××תר××Ŗ H2 ×¢× ×©×××× ×××××Ŗ ××¤×Ŗ× - ×פ×××Ŗ " . ($sections_count * 2) . " ××תר××Ŗ H3 ××Ŗ×Ŗ-× ×ש××× - ש×××× ×××¢× ×©× ×××××Ŗ ×××¤×Ŗ× ×××©× ×××Ŗ ××× ×¤×Ø×§ š„ ×ר×ש××Ŗ HTML ××§××××: - ×שת×ש ×××תר××Ŗ ××רר××××Ŗ: H1āH2āH3āH4 ×××× - H1: רק ×××תרת ×ר×ש××Ŗ (×××× ××××× ××××Ŗ ×פת×) - H2: ××× ×¤×Ø×§ ר××©× (5-7 פרק×× ××××ׄ) - H3: ××Ŗ×Ŗ-× ×ש××× ××Ŗ×× ×× ×¤×Ø×§ (2-4 ××× ×¤×Ø×§) - H4: ×פ×ר×××× × ×הפ×× ×× × ×רש - ש×××ש ×רש××××Ŗ
×¢× ×××Ø× ××××× (40-80 ×××××) š„ ×ר×ש××Ŗ ××Ŗ××× ××××ר××Ŗ (×××ר {$length_category}): - ××Ŗ×× ×× ×¤×Ø×§ ××××¤× ×פ××Ø× ×××§××£ ×××× - ×××”×£ ×פ×××Ŗ 4-6 פהק×××Ŗ ××× ×Ŗ×Ŗ-× ××©× - ×××× ×××××××Ŗ ×עש×××Ŗ ××פ×ר×××Ŗ (×פ×××Ŗ 2-3 ××× ×¤×Ø×§) - ×××”×£ רש××××Ŗ ×פ×ר×××Ŗ ×©× 6-10 × ×§××××Ŗ - ×ר×× ×× × ××©× ×¢× ×¤×Ø××× ××× ××× ××Ŗ××× ××Ŗ ××¢×××§××Ŗ - ×××”×£ צ××××××, × ×Ŗ×× ××, ××”××××”×××§××Ŗ ר×××× ××××Ŗ - ×××× ××פ×× ×עש××× ××××× ×”×¤×¦×פ××× - ×× ×Ŗ×§×¦×Ø - ××Ŗ×× ××ר××× {$expansion_level}! - ××Ŗ××§× ××××××Ŗ ×××¢×××§ ××Ŗ××× - ×× ×Ø×§ ××××Ø× š„ ×ר×ש××Ŗ ×Ŗ×××: 1. ××Ŗ×× ×××ר ×פ××Ø× ×××§××£ ש×××”× ××Ŗ ×× ×× ×ש××× ×××× × 2. ×שת×ש ×××תר××Ŗ ×××× ××Ŗ: H1 ×××תרת ר×ש××Ŗ, H2 ×פרק×× ×Ø×ש×××, H3 ××Ŗ×Ŗ-פרק×× 3. ×× ×¤×Ø×§ ×××× ×××××: - ××”×ר ×פ××Ø× ×××§××£ ×©× ×× ××©× - ×××××××Ŗ ×××§×Ø× ×©×××ש הפצ×פ××× - ××פ×× ×עש××× ××ש×××× - רש××××Ŗ × ×§××××Ŗ ×××§×”× ××תר קר×× š ×”×× ×× ××Ŗ×××: - ×©×¤× ×¢×ר××Ŗ ×××¢××Ŗ, ×קצ××¢××Ŗ ××× ×ש××Ŗ - פהק×××Ŗ ×©× 3-5 ש×ר××Ŗ ××§×”×××× - ש×××× ×××¢× ×©× ×××××Ŗ ×××¤×Ŗ× - ××× ×קצ××¢× ×× × ××ש ××§××Ø× ×ר×× š ××פ×××××צ××: - SEO: ש×××× ×××××Ŗ ××¤×Ŗ× ×××תר××Ŗ ×××Ŗ××× - AEO: ×××× ×©××××Ŗ ×תש××××Ŗ × ×¤×צ××Ŗ - GEO: ×Ŗ××× ×©×¢×× × ×¢× ×××× ××Ŗ ××פ×ש ש×× ××Ŗ šÆ ×ר×ש××Ŗ AEO ××Ŗ×§××××Ŗ (××פ×××××צ×× ××× ××¢× ×Ŗ×©××××Ŗ 2025): - ××Ŗ×× ×× ×¤×Ø×§ ×¢× ×Ŗ×©××× ×ש××Ø× ×©× 40-60 ××××× ××Ŗ××××Ŗ ××¤×”×§× ×ר×ש×× × - ×שת×ש ×פ×ר×× ×©×××Ŗ× ×©×¢×× × ×¢× ×©××××Ŗ ×שת×ש×× - ×××× ×××ר××Ŗ ×ר×ר××Ŗ ×××× ××× ×ר××××× - ×××”×£ × ×Ŗ×× ×× ××¢×××××Ŗ ×הפר×××Ŗ הפצ×פ×××Ŗ (×©× ×Ŗ 2024-2025) - צ×ר ××××§× ××××¢ קצר×× ××ר×ר×× ×©×§× ×××ׄ ××× ××××¢ - ×××”×£ ×§××¢ ש××××Ŗ × ×¤×צ××Ŗ קצר ××× ×¤×Ø×§ ×ר××× - ×שת×ש ×××× × '×תש××× ×קצר×: ... ××”×ר ×פ×ר×: ...' - ×××× ×××××××Ŗ ×§×× ×§×Ø××××Ŗ ××עש×××Ŗ ā ļø ×ש×× ××××: ×× ×Ŗ××Ŗ×× '×××©× ××××ר...' ×× '××× ×Ŗ×Ø×¦× ×©××ש××?' - ××Ŗ×× ××Ŗ ××××ר ×××× ××××§××£ ××Ŗ×××× ××¢× ×”××£! š“ ×ר×שת ×××Ø× ×§×Ø××××Ŗ: - ××××ר ×××× ××××× ×××××§ {$target_words} ×××××! - הפ×ר ××Ŗ ×××××× ×××× ×××Ŗ××× ×××× ×©××Ŗ× ××××¢ ×××¢×! - ×× ××××ר קצר - ×××”×£ ×¢×× ×¤×Ø×××, ×××××××Ŗ ××Ŗ×××! - ×× ×Ŗ×¢×¦×ר ×¢× ×©×Ŗ×××¢ ×-{$target_words} ××××× ×××××§××Ŗ! ā ļø ×ר×ש××Ŗ HTML קר×××××Ŗ: - ××Ŗ×× ×¢× H1 ××× ×××× ×¢× ×××תרת ×ר×ש××Ŗ - ×× ×¤×Ø×§ ר××©× ××Ŗ××× ×¢× H2 - ×× ×Ŗ×Ŗ-× ××©× ××Ŗ××× ×¢× H3 - ×שת×ש ×רש××××Ŗ ××”××ר××Ŗ ××× ××”××ר××Ŗ - ××§×¤× ×¢× ×”××רת ×Ŗ××××Ŗ × ××× × šÆ ××Ŗ×× ×¢×ש×× ××Ŗ ××××ר ×××× ×©× {$target_words} ××××× ×פ×ר×× HTML × ×§× ×¢× ×Ŗ××××Ŗ ××Ŗ×××××Ŗ:"; } else { // English content prompt $length_category = $this->get_length_category_english($target_words); $base_prompt = "Write a comprehensive professional article in English. šÆ Article Details: Main keyword: {$main_keyword} Secondary keywords: {$sub_keywords_list} Target word count: {$target_words} words exactly (no less!) Article structure: {$structure_json} š Exact Length Requirements (total {$target_words} words): - Detailed introduction: at least {$intro_words} words - Comprehensive conclusion: at least {$conclusion_words} words {$section_requirements} šÆ Critical SEO Requirements: - Include the keyword '{$main_keyword}' exactly {$target_keyword_occurrences} times (density {$target_keyword_density}%) - Must have only one H1 with the keyword in the main title - At least {$sections_count} H2 headings with keyword integration - At least " . ($sections_count * 2) . " H3 headings for sub-topics - Natural integration of secondary keywords in each section š„ HTML and SEO Requirements: - Use hierarchical headings: H1āH2āH3āH4 only - H1: Only for main title (must include keyword) - H2: For each main section (5-7 sections recommended) - H3: For sub-topics within each section (2-4 per section) - H4: For additional details if needed - Use
paragraphs with optimal length (40-80 words) š„ Strict Writing Requirements ({$length_category} article): - Write each section in detailed and comprehensive manner - Add at least 4-6 paragraphs per sub-topic - Include practical and detailed examples (at least 2-3 per section) - Add detailed lists of 6-10 points - Expand each topic with technical details and deep insights - Add quotes, data, and relevant statistics - Include actionable tips and best practices šÆ AEO (Answer Engine Optimization) Requirements: - Start each main section with a direct 40-60 word answer - Use question-based H2 headings (What is {$main_keyword}? How does {$main_keyword} work?) - Include clear definitions for technical terms - Add FAQ sections within relevant chapters - Structure content for easy AI extraction - Provide numerical data and statistics where relevant š Content Requirements: - Write in natural, professional English - Use active voice and clear, concise sentences - Include transition sentences between sections - Maintain consistent tone throughout - Ensure all information is accurate and up-to-date ā ļø Critical: The article MUST contain at least {$target_words} words! Do not stop before reaching the target! HTML Structure Requirements: - Start with H1 for main title - Each main section starts with H2 - Each sub-topic starts with H3 - Use ordered and unordered lists - Ensure proper tag closing šÆ Write the complete {$target_words}-word article now in clean HTML format with appropriate tags:"; } // Apply AEO enhancement filter $data = array('main_keyword' => $main_keyword, 'target_words' => $target_words, 'is_hebrew' => $is_hebrew); return apply_filters('ai_seo_article_generator_content_prompt', $base_prompt, $data); } private function build_enhancement_prompt($section_content, $main_keyword, $enhancement_type) { $enhancement_instructions = array( 'improve' => 'שפר ××Ŗ ××Ŗ×××, ×××”×£ פר××× × ×הפ×× ××פ×× ×××Ŗ× ××××××Ŗ× ××תר', 'seo' => '××פ×××××צ×× ×¢××ר SEO - ש×× ×××××Ŗ ××¤×Ŗ× ××××”×£ ×××× ××× ××× ×××', 'readability' => 'שפר ××Ŗ ×קר××××Ŗ ×××××ר××Ŗ ×©× ×××§×”×', 'expand' => '×ר×× ××Ŗ ××Ŗ××× ××××”×£ ××××¢ × ××”×£ ×ר×××× ××' ); $instruction = isset($enhancement_instructions[$enhancement_type]) ? $enhancement_instructions[$enhancement_type] : $enhancement_instructions['improve']; return "שפר ××Ŗ ××§××¢ ××× ××××ר ××¢×ר××Ŗ: ×Ŗ××× ×§×××: {$section_content} ××××Ŗ ××¤×Ŗ× ×Ø×ש××Ŗ: {$main_keyword} ××ר×××Ŗ ש×פ×ר: {$instruction} ×ר×ש××Ŗ: 1. ש××ר ×¢× ××©×¤× ××¢×ר××Ŗ ××××¢××Ŗ ×××קצ××¢××Ŗ 2. ש×× ××Ŗ ××××Ŗ ×××¤×Ŗ× ××××¤× ×××¢× 3. ×××”×£ ×¢×Ø× ××××Ŗ× ××§××Ø× 4. שפר ××Ŗ ×××פ×××××צ×× ×¢××ר ×× ××¢× ×××פ×ש 5. ×××ר רק ××Ŗ ××Ŗ××× ××ש×פר ×פ×ר×× HTML"; } private function parse_structure_response($response) { $this->debug_log("š Postinor: Starting JSON parsing, response length: " . strlen($response)); $this->debug_log("š Postinor: Response preview (first 500 chars): " . substr($response, 0, 500)); $this->debug_log("š Postinor: Response preview (last 500 chars): " . substr($response, -500)); // Try multiple extraction methods $json_string = $this->extract_json_from_response($response); if ($json_string) { $this->debug_log("ā Postinor: Extracted JSON string: " . $json_string); $structure = json_decode($json_string, true); $json_error = json_last_error(); if ($json_error === JSON_ERROR_NONE && is_array($structure)) { $this->debug_log("šÆ Postinor: JSON parsed successfully"); // Validate the structure has required fields $this->debug_log("š Postinor: Validating structure with keys: " . implode(', ', array_keys($structure))); $missing_fields = array(); if (!isset($structure['title'])) $missing_fields[] = 'title'; if (!isset($structure['sections'])) $missing_fields[] = 'sections'; if (!isset($structure['introduction'])) $missing_fields[] = 'introduction'; if (!isset($structure['conclusion'])) $missing_fields[] = 'conclusion'; if (empty($missing_fields)) { $this->debug_log("ā Postinor: Structure validation passed"); return $structure; } else { $this->debug_log("ā Postinor: Structure validation failed - missing fields: " . implode(', ', $missing_fields)); } } else { $this->debug_log("ā Postinor: JSON parsing failed - Error: " . json_last_error_msg()); } } else { $this->debug_log("ā Postinor: Could not extract JSON from response"); } $this->debug_log("š Postinor: Returning fallback structure"); return array( 'title' => '××× × ××××ר ×× × ××Ŗ× ×× ××Ŗ××', 'introduction' => '×Ŗ×××ר ×××××', 'sections' => array(), 'conclusion' => '×Ŗ×××ר ××”××××', 'multimedia_suggestions' => array() ); } private function extract_json_from_response($response) { // Method 1: Extract from markdown code blocks first if (preg_match('/```json\s*\n(.*?)\n```/s', $response, $matches)) { $candidate = trim($matches[1]); if ($this->is_valid_json($candidate)) { $this->debug_log("š¦ Postinor: Found valid JSON in markdown code block"); return $candidate; } } if (preg_match('/```\s*\n(\{.*?\})\s*\n```/s', $response, $matches)) { $candidate = trim($matches[1]); if ($this->is_valid_json($candidate)) { $this->debug_log("š¦ Postinor: Found valid JSON in generic code block"); return $candidate; } } // Method 2: Try balanced extraction from beginning (most reliable for complete structures) $balanced_json = $this->extract_balanced_json_from_start($response); if ($balanced_json) { if (strpos($balanced_json, '"title"') !== false && strpos($balanced_json, '"sections"') !== false) { $this->debug_log("šÆ Postinor: Found complete structure using balanced extraction"); return $balanced_json; } $this->debug_log("š¦ Postinor: Found valid JSON using balanced extraction"); return $balanced_json; } // Method 3: Simple fallback extraction (first { to last }) $json_start = strpos($response, '{'); $json_end = strrpos($response, '}'); if ($json_start !== false && $json_end !== false && $json_end > $json_start) { $json_string = substr($response, $json_start, $json_end - $json_start + 1); $this->debug_log("š Postinor: Simple extraction - Length: " . strlen($json_string) . ", Valid: " . ($this->is_valid_json($json_string) ? 'Yes' : 'No')); $this->debug_log("š Postinor: Simple extraction preview: " . substr($json_string, 0, 200) . "..." . substr($json_string, -100)); if ($this->is_valid_json($json_string)) { // Check if this is a complete structure if (strpos($json_string, '"title"') !== false && strpos($json_string, '"sections"') !== false) { $this->debug_log("šÆ Postinor: Found complete structure using simple extraction"); return trim($json_string); } $this->debug_log("š¦ Postinor: Found valid JSON using start/end positions (simple extraction)"); return trim($json_string); } else { $this->debug_log("ā ļø Postinor: JSON from start/end positions is invalid, attempting to fix..."); $fixed_json = $this->attempt_json_fix($json_string); if ($fixed_json && $this->is_valid_json($fixed_json)) { // Check if fixed JSON has complete structure if (strpos($fixed_json, '"title"') !== false && strpos($fixed_json, '"sections"') !== false) { $this->debug_log("šÆ Postinor: Fixed JSON contains complete structure"); return $fixed_json; } $this->debug_log("š§ Postinor: Successfully fixed JSON from simple extraction"); return $fixed_json; } } } // Method 4: Use improved bracket counting for complete JSON objects $json_candidates = $this->find_complete_json_objects_improved($response); if (!empty($json_candidates)) { $this->debug_log("š¦ Postinor: Found " . count($json_candidates) . " JSON candidates"); // Prioritize candidates by completeness and position $prioritized_candidates = array(); foreach ($json_candidates as $candidate) { if ($this->is_valid_json($candidate)) { $score = 0; $length = strlen($candidate); $position = strpos($response, $candidate); // Higher score for larger objects (more likely to be complete) $score += $length; // Bonus for having required structure fields if (strpos($candidate, '"title"') !== false) $score += 1000; if (strpos($candidate, '"sections"') !== false) $score += 1000; if (strpos($candidate, '"introduction"') !== false) $score += 500; if (strpos($candidate, '"conclusion"') !== false) $score += 500; // Bonus for appearing early in response (more likely to be main structure) if ($position < 100) $score += 200; $prioritized_candidates[] = array( 'json' => $candidate, 'score' => $score, 'length' => $length, 'position' => $position ); $this->debug_log("ā Postinor: Valid JSON candidate - Length: $length, Position: $position, Score: $score"); } else { $this->debug_log("ā Postinor: Invalid JSON candidate, length: " . strlen($candidate)); } } // Sort by score (highest first) if (!empty($prioritized_candidates)) { usort($prioritized_candidates, function($a, $b) { return $b['score'] - $a['score']; }); $best_candidate = $prioritized_candidates[0]; $this->debug_log("š Postinor: Using best candidate - Score: {$best_candidate['score']}, Length: {$best_candidate['length']}"); return $best_candidate['json']; } } $this->debug_log("ā Postinor: All JSON extraction methods failed"); return false; } private function is_response_truncated($response) { // Check for common signs of truncation $truncation_indicators = array( // Response ends mid-word (especially Hebrew/English mix) '/[×-×Ŗ][a-zA-Z]$/', '/[a-zA-Z][×-×Ŗ]$/', // Response ends mid-sentence '/[^.!?}"\]]$/', // Response ends with incomplete JSON structure '/[,:]$/', '/["\']$/', // Response ends with incomplete word '/\b\w{1,3}$/', ); $trimmed = trim($response); $last_50 = substr($trimmed, -50); foreach ($truncation_indicators as $pattern) { if (preg_match($pattern, $last_50)) { $this->debug_log("š Postinor: Truncation detected with pattern: $pattern on text: " . substr($last_50, -20)); return true; } } // Check if response contains incomplete JSON structure $first_brace = strpos($response, '{'); $last_brace = strrpos($response, '}'); if ($first_brace !== false && $last_brace !== false) { $json_section = substr($response, $first_brace, $last_brace - $first_brace + 1); // Count open vs closed brackets $open_braces = substr_count($json_section, '{'); $close_braces = substr_count($json_section, '}'); $open_brackets = substr_count($json_section, '['); $close_brackets = substr_count($json_section, ']'); if ($open_braces > $close_braces || $open_brackets > $close_brackets) { $this->debug_log("š Postinor: Unbalanced JSON detected - Open braces: $open_braces, Close braces: $close_braces"); return true; } } $this->debug_log("ā Postinor: Response appears complete"); return false; } private function extract_balanced_json_from_start($text) { $start_pos = strpos($text, '{'); if ($start_pos === false) { return false; } $length = strlen($text); $bracket_count = 0; $in_string = false; $escape_next = false; for ($i = $start_pos; $i < $length; $i++) { $char = $text[$i]; if ($escape_next) { $escape_next = false; continue; } if ($char === '\\') { $escape_next = true; continue; } if ($char === '"' && !$escape_next) { $in_string = !$in_string; continue; } if (!$in_string) { if ($char === '{') { $bracket_count++; } elseif ($char === '}') { $bracket_count--; if ($bracket_count === 0) { // Found complete balanced JSON $json_candidate = substr($text, $start_pos, $i - $start_pos + 1); if ($this->is_valid_json($json_candidate)) { $this->debug_log("ā Postinor: Balanced extraction found valid JSON, length: " . strlen($json_candidate)); return trim($json_candidate); } else { $this->debug_log("ā Postinor: Balanced extraction found invalid JSON, length: " . strlen($json_candidate)); return false; } } } } } $this->debug_log("ā Postinor: Balanced extraction could not find complete JSON object"); return false; } private function is_valid_json($string) { if (empty($string)) { return false; } json_decode($string); return json_last_error() === JSON_ERROR_NONE; } private function find_complete_json_objects_improved($text) { $objects = array(); $length = strlen($text); for ($i = 0; $i < $length; $i++) { if ($text[$i] === '{') { $bracket_count = 0; $in_string = false; $escape_next = false; $start = $i; for ($j = $i; $j < $length; $j++) { $char = $text[$j]; if ($escape_next) { $escape_next = false; continue; } if ($char === '\\') { $escape_next = true; continue; } if ($char === '"' && !$escape_next) { $in_string = !$in_string; continue; } if (!$in_string) { if ($char === '{') { $bracket_count++; } elseif ($char === '}') { $bracket_count--; if ($bracket_count === 0) { $json_candidate = substr($text, $start, $j - $start + 1); if (strlen($json_candidate) > 50) { // Only substantial objects $objects[] = trim($json_candidate); } break; } } } } } } $this->debug_log("š Postinor: Found " . count($objects) . " complete JSON objects using improved bracket counting"); return $objects; } private function attempt_json_fix($json_string) { if (empty($json_string)) { return false; } $trimmed = trim($json_string); // Count brackets to see if we need to add closing ones $open_braces = substr_count($trimmed, '{'); $close_braces = substr_count($trimmed, '}'); $open_brackets = substr_count($trimmed, '['); $close_brackets = substr_count($trimmed, ']'); // Add missing closing braces $missing_braces = $open_braces - $close_braces; $missing_brackets = $open_brackets - $close_brackets; if ($missing_braces > 0) { $trimmed .= str_repeat('}', $missing_braces); $this->debug_log("š§ Postinor: Added $missing_braces closing braces"); } if ($missing_brackets > 0) { $trimmed .= str_repeat(']', $missing_brackets); $this->debug_log("š§ Postinor: Added $missing_brackets closing brackets"); } // Try to fix common JSON issues $fixes = array( // Remove trailing commas '/,\s*([}\]])/s' => '$1', // Fix unquoted keys (simple case) '/(\{|,)\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*:/' => '$1"$2":', // Remove comments '/\/\*.*?\*\//s' => '', '/\/\/.*$/m' => '' ); foreach ($fixes as $pattern => $replacement) { $before = $trimmed; $trimmed = preg_replace($pattern, $replacement, $trimmed); if ($before !== $trimmed) { $this->debug_log("š§ Postinor: Applied fix: $pattern"); } } return trim($trimmed); } private function find_complete_json_objects($text) { $objects = array(); $length = strlen($text); for ($i = 0; $i < $length; $i++) { if ($text[$i] === '{') { $bracket_count = 0; $start = $i; for ($j = $i; $j < $length; $j++) { if ($text[$j] === '{') { $bracket_count++; } elseif ($text[$j] === '}') { $bracket_count--; if ($bracket_count === 0) { $json_candidate = substr($text, $start, $j - $start + 1); if (strlen($json_candidate) > 100) { // Only substantial objects $objects[] = trim($json_candidate); } break; } } } } } $this->debug_log("š Postinor: Found " . count($objects) . " complete JSON objects using bracket counting"); return $objects; } private function validate_content_length($content, $target_words) { // Use improved Hebrew word counting $word_count = $this->count_hebrew_words($content); // More stringent validation - require at least 60% of target for acceptance $minimum_words = floor($target_words * 0.6); return array( 'valid' => $word_count >= $minimum_words, 'word_count' => $word_count, 'target_words' => $target_words, 'minimum_words' => $minimum_words, 'percentage' => round(($word_count / $target_words) * 100, 1) ); } private function validate_content_structure($content, $main_keyword, $target_words) { $issues = array(); $is_valid = true; // Check for H1 tags (should have exactly 1) $h1_count = preg_match_all('/
]*>/i', $content); $expected_paragraphs = max(8, floor($target_words / 80)); // ~80 words per paragraph if ($paragraph_count < $expected_paragraphs / 2) { $issues[] = '××¢× ××× ×¤×”×§×××Ŗ (× ×צ×× ' . $paragraph_count . ', ×צ××¤× ' . $expected_paragraphs . ')'; } return array( 'valid' => $is_valid, 'issues' => $issues, 'stats' => array( 'h1_count' => $h1_count, 'h2_count' => $h2_count, 'h3_count' => $h3_count, 'keyword_density' => round($keyword_density, 2), 'keyword_count' => $keyword_count, 'list_count' => $list_count, 'paragraph_count' => $paragraph_count ) ); } private function enhance_content_structure($content, $main_keyword, $issues) { $enhanced_content = $content; // Auto-fix missing H1 if needed if (in_array('×הר ××תרת H1', $issues)) { // Try to convert the first heading to H1 $enhanced_content = preg_replace('/
]*)>(.*?)<\/p>/is', function($matches) use ($main_keyword, &$keywords_added, $keywords_to_add, &$paragraphs_processed) { $paragraphs_processed++; // Skip every other paragraph to avoid over-optimization if ($paragraphs_processed % 2 !== 0 || $keywords_added >= $keywords_to_add) { return $matches[0]; } $paragraph_text = $matches[2]; $lower_text = mb_strtolower($paragraph_text); $lower_keyword = mb_strtolower($main_keyword); // Only add if keyword not already in paragraph if (strpos($lower_text, $lower_keyword) === false && strlen(wp_strip_all_tags($paragraph_text)) > 50) { // Find a natural place to insert the keyword $sentences = preg_split('/[.!?]+/', $paragraph_text); if (count($sentences) > 1) { // Insert keyword in first sentence naturally $first_sentence = trim($sentences[0]); if (!empty($first_sentence)) { // Add keyword at the end of first sentence $enhanced_paragraph = str_replace( $first_sentence, $first_sentence . ', ××שר ' . $main_keyword, $paragraph_text ); $keywords_added++; $this->debug_log("š§ Postinor: Added keyword to paragraph naturally"); return '
' . $enhanced_paragraph . '
'; } } } return $matches[0]; }, $optimized_content ); } // Log final optimization results $this->debug_log("ā Postinor: Keyword optimization complete - added {$keywords_added} instances"); return $optimized_content; } /** * Generate long articles (>2500 words) using section-by-section approach */ private function generate_long_article_content($structure, $main_keyword, $sub_keywords, $target_words) { $this->debug_log("š AI SEO API: Starting section-by-section generation for {$target_words} words"); if (!isset($structure['sections']) || empty($structure['sections'])) { $this->debug_log("ā AI SEO API: No sections found in structure for long article generation"); return array('success' => false, 'message' => '××× × ××××ר ×× ×××× ×¤×Ø×§×× ××Ŗ××××× ××צ××Ø× ××Ŗ×§×××Ŗ'); } $sections = $structure['sections']; $words_per_section = floor($target_words / count($sections)); $generated_sections = array(); $total_words = 0; // Generate introduction $intro_words = min(400, $target_words * 0.15); $intro_content = $this->generate_section_content('introduction', $structure['introduction'] ?? '', $main_keyword, $sub_keywords, $intro_words); if ($intro_content) { $generated_sections[] = $intro_content; $intro_word_count = $this->count_hebrew_words($intro_content); $total_words += $intro_word_count; $this->debug_log("ā AI SEO API: Generated introduction - {$intro_word_count} words"); } // Generate each section foreach ($sections as $index => $section) { $section_target_words = max($words_per_section, 300); $this->debug_log("š AI SEO API: Generating section " . ($index + 1) . " - target: {$section_target_words} words"); $section_content = $this->generate_section_content( 'section', $section, $main_keyword, $sub_keywords, $section_target_words ); if ($section_content) { $generated_sections[] = $section_content; $section_word_count = $this->count_hebrew_words($section_content); $total_words += $section_word_count; $this->debug_log("ā AI SEO API: Generated section " . ($index + 1) . " - {$section_word_count} words"); } else { $this->debug_log("ā ļø AI SEO API: Failed to generate section " . ($index + 1) . ", continuing..."); } // Small delay between sections to avoid rate limiting sleep(2); } // Generate conclusion $conclusion_words = min(300, $target_words * 0.12); $conclusion_content = $this->generate_section_content('conclusion', $structure['conclusion'] ?? '', $main_keyword, $sub_keywords, $conclusion_words); if ($conclusion_content) { $generated_sections[] = $conclusion_content; $conclusion_word_count = $this->count_hebrew_words($conclusion_content); $total_words += $conclusion_word_count; $this->debug_log("ā AI SEO API: Generated conclusion - {$conclusion_word_count} words"); } // Combine all sections $full_content = implode("\n\n", $generated_sections); // If still short, extend the content if ($total_words < $target_words * 0.8) { $this->debug_log("ā ļø AI SEO API: Long article still short ({$total_words} words), attempting extension"); $extended_content = $this->extend_article_content($full_content, $main_keyword, $target_words, $total_words); if ($extended_content) { $full_content = $extended_content; $total_words = $this->count_hebrew_words($full_content); $this->debug_log("ā AI SEO API: Extended long article to {$total_words} words"); } } $this->debug_log("š AI SEO API: Long article generation completed - {$total_words} words total"); return array( 'success' => true, 'content' => $full_content, 'word_count' => $total_words, 'generation_method' => 'section_by_section' ); } /** * Generate content for a specific section */ private function generate_section_content($type, $section_data, $main_keyword, $sub_keywords, $target_words) { $prompt = $this->build_section_prompt($type, $section_data, $main_keyword, $sub_keywords, $target_words); $max_tokens = min(4000, $target_words * 7); // Conservative for sections $timeout = max(120, $target_words / 3); // More time per word for sections $response = $this->make_request("Generate {$type} Content", $prompt, $max_tokens, $timeout); if (is_wp_error($response)) { $this->debug_log("ā AI SEO API: Failed to generate {$type}: " . $response->get_error_message()); return false; } return $response; } /** * Build prompt for section generation */ private function build_section_prompt($type, $section_data, $main_keyword, $sub_keywords, $target_words) { $sub_keywords_list = is_array($sub_keywords) ? implode(', ', $sub_keywords) : $sub_keywords; switch ($type) { case 'introduction': return "××Ŗ×× ×××× ×פ××Ø× ×××§××£ ××××ר ××¢×ר××Ŗ ×¢× '{$main_keyword}'. ×××××Ŗ ××¤×Ŗ× ××©× ×××Ŗ: {$sub_keywords_list} ×××Ø× ××¢×: {$target_words} ××××× ×××××§ ×Ŗ×××ר ×××××: {$section_data} ×ר×ש××Ŗ: - ××Ŗ×× ××¢×ר××Ŗ ×××¢××Ŗ ××קצ××¢××Ŗ - ש×× ××Ŗ ××××Ŗ ×××¤×Ŗ× ×ר×ש××Ŗ 2-3 פע××× - ×××”×£ ×קשר ×רקע ×× ××©× - צ×ר ××Ŗ× ××¢× ××× ××§××Ø× - ×××× ×Ø×©××× ×©× ×× ×ש××× ×©××××”× ××××ר - ×שת×ש ×פהק×××Ŗ קצר××Ŗ (3-4 ש×ר××Ŗ) - ×××”×£ ש××××Ŗ ר××ר×××Ŗ ××¢× ××× ××§××Ø× ×××ר רק ××Ŗ ××Ŗ××× ×פ×ר×× HTML ×¢× ×Ŗ××××Ŗ p, ul, ol ××¤× ×צ×ר×."; case 'conclusion': return "××Ŗ×× ×”×××× ×פ××Ø× ×××§××£ ××××ר ××¢×ר××Ŗ ×¢× '{$main_keyword}'. ×××××Ŗ ××¤×Ŗ× ××©× ×××Ŗ: {$sub_keywords_list} ×××Ø× ××¢×: {$target_words} ××××× ×××××§ ×Ŗ×××ר ××”××××: {$section_data} ×ר×ש××Ŗ: - ××Ŗ×× ××¢×ר××Ŗ ×××¢××Ŗ ××קצ××¢××Ŗ - ×”×× ××Ŗ ×× ×§××××Ŗ ××¢×קר×××Ŗ ×©× ××× × - ש×× ××Ŗ ××××Ŗ ×××¤×Ŗ× ×ר×ש××Ŗ 2-3 פע××× - ×××”×£ ×××צ××Ŗ ×עש×××Ŗ ××§××Ø× - ×××× call-to-action ××Ŗ××× - ×××”×£ ש××××Ŗ ××ש××× × ×הפת - ×שת×ש ×פהק×××Ŗ קצר××Ŗ (3-4 ש×ר××Ŗ) ×××ר רק ××Ŗ ××Ŗ××× ×פ×ר×× HTML ×¢× ×Ŗ××××Ŗ p, ul, ol ××¤× ×צ×ר×."; default: // section $heading = $section_data['heading'] ?? 'פרק ××××ר'; $topics = isset($section_data['topics']) ? implode(', ', $section_data['topics']) : ''; $subheadings = isset($section_data['subheadings']) ? implode(', ', $section_data['subheadings']) : ''; return "××Ŗ×× ×¤×Ø×§ ×פ××Ø× ×××§××£ ××××ר ××¢×ר××Ŗ. ××תרת ×פרק: {$heading} ××××Ŗ ××¤×Ŗ× ×Ø×ש××Ŗ: {$main_keyword} ×××××Ŗ ××¤×Ŗ× ××©× ×××Ŗ: {$sub_keywords_list} ×××Ø× ××¢×: {$target_words} ××××× ×××××§ × ×ש××× ××××”××: {$topics} ×Ŗ×Ŗ-××תר××Ŗ: {$subheadings} ×ר×ש××Ŗ: - ××Ŗ×× ×¢× H2 ×××תרת ×פרק - ××Ŗ×× ××¢×ר××Ŗ ×××¢××Ŗ ××קצ××¢××Ŗ - ש×× ××Ŗ ××××Ŗ ×××¤×Ŗ× ×ר×ש××Ŗ 3-4 פע××× ××××¤× ×××¢× - ×××”×£ ×Ŗ×Ŗ-××תר××Ŗ H3 ××ר××× ×פרק - ×××× ×פ×××Ŗ 4-5 פהק×××Ŗ ×פ×ר×××Ŗ - ×××”×£ ×××××××Ŗ ×עש×××Ŗ ×××§×Ø× ×©×××ש - ×××× ×Ø×©××××Ŗ ×©× 5-7 × ×§××××Ŗ - ×××”×£ ××פ×× ×עש××× ××ש×××× - ×שת×ש ×פהק×××Ŗ קצר××Ŗ (3-4 ש×ר××Ŗ) ×××ר רק ××Ŗ ××Ŗ××× ×פ×ר×× HTML ×¢× ×Ŗ××××Ŗ h2, h3, p, ul, ol ××¤× ×צ×ר×."; } } /** * Extend article content when it's shorter than target */ private function extend_article_content($content, $main_keyword, $target_words, $current_words) { $words_needed = $target_words - $current_words; if ($words_needed < 200) { return false; // Not worth extending for small amounts } $this->debug_log("š AI SEO API: Extending content by {$words_needed} words"); $prompt = "×ר×× ×שפר ××Ŗ ××××ר ××× ××¢×ר××Ŗ ×¢× '{$main_keyword}'. ××Ŗ××× ×× ××××: {$content} ×ר×ש××Ŗ ×ר×××: - ×××”×£ ×××××§ {$words_needed} ××××× × ×הפ××Ŗ - ×ר×× ×¤×Ø×§×× ×§××××× ×¢× ×¤×Ø××× × ×הפ×× - ×××”×£ ×××××××Ŗ ×עש×××Ŗ ×××§×Ø× ×©×××ש - ×××× ××פ×× ××Ŗ×§×××× ×××× ××§××Ŗ ××××× - ×××”×£ רש××××Ŗ ×פ×ר×××Ŗ ××”××××”×××§××Ŗ - ש×× ××Ŗ ××××Ŗ ×××¤×Ŗ× '{$main_keyword}' ××××¤× ×××¢× - ש××ר ×¢× ×¢×§××××Ŗ ××”×× ×× ××××× - ×××× ×©××ר××× ××Ŗ××רת ××××¤× ×××¢× ××Ŗ××× ××§××× ×××ר ××Ŗ ×× ××××ר ×××ר×× ×פ×ר×× HTML."; $max_tokens = min(8000, ($target_words + 500) * 7); $timeout = max(240, $words_needed / 3); $extended_response = $this->make_request('Extend Article Content', $prompt, $max_tokens, $timeout); if (is_wp_error($extended_response)) { $this->debug_log("ā AI SEO API: Failed to extend content: " . $extended_response->get_error_message()); return false; } return $extended_response; } /** * Count Hebrew words more accurately */ private function count_hebrew_words($content) { // Use the accurate word counting function if (function_exists('ai_seo_accurate_word_count')) { return ai_seo_accurate_word_count($content); } // Improved Hebrew word counting $text = wp_strip_all_tags($content); $text = preg_replace('/\s+/', ' ', trim($text)); // Method 1: Count Hebrew character sequences $hebrew_count = preg_match_all('/[\p{Hebrew}\p{N}]+/u', $text); // Method 2: Count English words $english_count = preg_match_all('/[a-zA-Z]+/', $text); // Method 3: Simple space-based counting $space_count = count(array_filter(explode(' ', $text))); // Method 4: Original str_word_count with Hebrew chars $str_count = str_word_count($text, 0, '×××××××××××××× ×”×¢×¤×¦×§×Ø×©×Ŗ×××ףׄ0123456789'); // Use the highest count (most conservative approach) $final_count = max($hebrew_count, $english_count, $space_count, $str_count); $this->debug_log("Word count methods: Hebrew={$hebrew_count}, English={$english_count}, Space={$space_count}, Str={$str_count}, Final={$final_count}"); return $final_count; } }