[ 'keywords' => ['новини', 'news', 'breaking', 'urgent', 'update', 'latest', 'breaking news', 'urgent news'], 'prompt' => 'Create a professional news image with modern design, clean typography, and visual elements that represent current events and journalism. Use a color scheme suitable for news media.', 'style' => 'professional, modern, clean, journalistic' ], 'technology' => [ 'keywords' => ['tech', 'technology', 'ai', 'artificial intelligence', 'machine learning', 'software', 'hardware', 'innovation'], 'prompt' => 'Design a futuristic technology image with digital elements, circuit patterns, and modern aesthetics. Include visual representations of AI, data, and technological advancement.', 'style' => 'futuristic, digital, technological, innovative' ], 'business' => [ 'keywords' => ['business', 'finance', 'economy', 'market', 'investment', 'corporate', 'startup', 'entrepreneur'], 'prompt' => 'Create a professional business image with corporate elements, financial symbols, and modern business aesthetics. Use professional colors and clean design.', 'style' => 'professional, corporate, modern, business-like' ], 'health' => [ 'keywords' => ['health', 'medical', 'wellness', 'fitness', 'nutrition', 'medicine', 'healthcare', 'wellbeing'], 'prompt' => 'Design a health and wellness image with medical symbols, natural elements, and positive health imagery. Use calming colors and professional medical aesthetics.', 'style' => 'medical, professional, calming, health-focused' ], 'lifestyle' => [ 'keywords' => ['lifestyle', 'fashion', 'beauty', 'travel', 'food', 'home', 'design', 'culture'], 'prompt' => 'Create a lifestyle image with modern aesthetics, cultural elements, and contemporary design. Use vibrant colors and engaging visual elements.', 'style' => 'modern, vibrant, cultural, contemporary' ] ]; } /** * Get prompt for specific category * * @param string $category Category name * @return array|null Prompt data or null if not found */ public static function get_prompt_by_category($category) { $prompts = self::get_prompts(); return isset($prompts[$category]) ? $prompts[$category] : null; } } /** * Get appropriate image prompt based on content analysis * * @param string $content The content to analyze * @param string $title Optional title for context * @return array Array containing prompt, style, and category */ function airsrefe_get_image_prompt($content, $title = '') { $prompts = AIRSREFE_Image_Prompts_Manager::get_prompts(); $text = strtolower($title . ' ' . $content); foreach ($prompts as $category => $data) { foreach ($data['keywords'] as $keyword) { if (strpos($text, $keyword) !== false) { return [ 'prompt' => $data['prompt'], 'style' => $data['style'], 'category' => $category ]; } } } // Default prompt return [ 'prompt' => 'Create a professional, modern image that represents the content with clean design and engaging visual elements.', 'style' => 'professional, modern, clean', 'category' => 'general' ]; } /** * Check if image generation is available and enabled */ function airsrefe_can_generate_image() { // Check if OpenAI API key is configured $api_key = airsrefe_get_api_key(); if (empty($api_key)) { airsrefe_append_log("❌ Cannot generate image: OpenAI API key not configured"); return false; } // Check if image generation is enabled in settings $image_source = get_option('airsrefe_image_source', 'rss'); if ($image_source !== 'ai') { airsrefe_append_log("❌ Cannot generate image: Image source is set to '{$image_source}', not 'ai'"); return false; } // Check if processing mode allows AI features $processing_mode = get_option('airsrefe_processing_mode', 'ai'); if ($processing_mode === 'simple') { airsrefe_append_log("❌ Cannot generate image: Processing mode is 'simple', AI features disabled"); return false; } // Check budget limits $daily_budget = get_option('airsrefe_daily_budget_limit', 0); if ($daily_budget > 0) { $used_budget = get_option('airsrefe_daily_budget_used', 0); if ($used_budget >= $daily_budget) { airsrefe_append_log("❌ Cannot generate image: Daily budget limit reached ({$used_budget}/{$daily_budget})"); return false; } } airsrefe_append_log("✅ Image generation is available and enabled"); return true; } /** * Generate image using OpenAI DALL-E */ function airsrefe_generate_image($prompt, $size = '1792x1024', $quality = 'standard', $style = 'natural') { $api_key = airsrefe_get_api_key(); if (empty($api_key)) { return new WP_Error('no_api_key', 'OpenAI API key not configured'); } // Clean prompt from HTML tags and limit length $clean_prompt = wp_strip_all_tags($prompt); $clean_prompt = wp_trim_words($clean_prompt, 100); // Limit to 100 words for DALL-E // Enhance prompt with style $enhanced_prompt = $clean_prompt . ' Style: ' . $style . '. High quality, professional design.'; airsrefe_append_log("🎨 Cleaned prompt for DALL-E: " . substr($enhanced_prompt, 0, 150) . "..."); $response = wp_remote_post('https://api.openai.com/v1/images/generations', [ 'headers' => [ 'Content-Type' => 'application/json', 'Authorization' => 'Bearer ' . $api_key, ], 'body' => wp_json_encode([ 'model' => 'dall-e-3', 'prompt' => $enhanced_prompt, 'size' => $size, 'quality' => $quality, 'n' => 1, ]), 'timeout' => 60, ]); if (is_wp_error($response)) { return $response; } $body = json_decode(wp_remote_retrieve_body($response), true); if (isset($body['error'])) { return new WP_Error('api_error', $body['error']['message']); } if (isset($body['data'][0]['url'])) { return $body['data'][0]['url']; } return new WP_Error('no_image_url', 'No image URL in response'); } /** * Asynchronous image generation with queue */ function airsrefe_generate_image_async($prompt, $post_id, $size = '1792x1024', $quality = 'standard') { $queue_item = [ 'prompt' => $prompt, 'post_id' => $post_id, 'size' => $size, 'quality' => $quality, 'status' => 'pending', 'created_at' => current_time('mysql'), 'attempts' => 0 ]; $queue = get_option('airsrefe_image_queue', []); $queue[] = $queue_item; update_option('airsrefe_image_queue', $queue); // Schedule background processing if (!wp_next_scheduled('airsrefe_process_image_queue')) { wp_schedule_single_event(time() + 30, 'airsrefe_process_image_queue'); } return true; } /** * Process image generation queue */ function airsrefe_process_image_queue() { $queue = get_option('airsrefe_image_queue', []); if (empty($queue)) return; $max_attempts = 3; $processed = []; $failed = []; foreach ($queue as $item) { if ($item['attempts'] >= $max_attempts) { $failed[] = $item; continue; } $item['attempts']++; try { $image_url = airsrefe_generate_image($item['prompt'], $item['size'], $item['quality']); if (is_wp_error($image_url)) { if ($item['attempts'] >= $max_attempts) { $failed[] = $item; } else { $processed[] = $item; } } else { // Download and attach image to post $attachment_id = airsrefe_download_and_attach_image($image_url, $item['post_id']); if ($attachment_id) { set_post_thumbnail($item['post_id'], $attachment_id); airsrefe_append_log("Image generated and attached to post {$item['post_id']}"); } } } catch (Exception $e) { $item['attempts']++; if ($item['attempts'] >= $max_attempts) { $failed[] = $item; } else { $processed[] = $item; } } } // Update queue update_option('airsrefe_image_queue', $processed); // Log failed items if (!empty($failed)) { foreach ($failed as $item) { airsrefe_append_log("Failed to generate image for post {$item['post_id']} after {$item['attempts']} attempts"); } } } /** * Parallel image processing for multiple posts */ function airsrefe_process_images_parallel($posts_data, $max_concurrent = 3) { $results = []; $chunks = array_chunk($posts_data, $max_concurrent); foreach ($chunks as $chunk) { $processes = []; foreach ($chunk as $post_data) { $processes[] = [ 'post_id' => $post_data['post_id'], 'prompt' => $post_data['prompt'], 'size' => $post_data['size'] ?? '1792x1024', 'quality' => $post_data['quality'] ?? 'standard' ]; } // Process chunk foreach ($processes as $process) { $result = airsrefe_generate_image_async( $process['prompt'], $process['post_id'], $process['size'], $process['quality'] ); $results[] = [ 'post_id' => $process['post_id'], 'success' => $result, 'timestamp' => current_time('mysql') ]; } // Small delay between chunks to avoid rate limiting if (count($chunks) > 1) { sleep(2); } } return $results; } /** * Download and attach image to post */ function airsrefe_download_and_attach_image($image_url, $post_id) { require_once(ABSPATH . 'wp-admin/includes/media.php'); require_once(ABSPATH . 'wp-admin/includes/file.php'); // SECURITY FIX: Enhanced URL validation if (!filter_var($image_url, FILTER_VALIDATE_URL)) { airsrefe_append_log("❌ Invalid image URL: " . $image_url); return false; } // SECURITY FIX: Validate URL scheme $parsed_url = wp_parse_url($image_url); if (!$parsed_url || !in_array($parsed_url['scheme'], ['http', 'https'])) { airsrefe_append_log("❌ Invalid URL scheme: " . ($parsed_url['scheme'] ?? 'unknown')); return false; } // Check if URL is from allowed domains (optional security measure) $allowed_domains = apply_filters('airsrefe_allowed_image_domains', []); if (!empty($allowed_domains)) { $domain = $parsed_url['host'] ?? ''; if (!in_array($domain, $allowed_domains)) { airsrefe_append_log("❌ Image domain not allowed: " . $domain); return false; } } // SECURITY FIX: Set timeout and user agent for download $download_args = [ 'timeout' => 30, 'user-agent' => 'AI RSS Rewriter/' . AIRSREFE_VERSION . '; ' . home_url(), 'sslverify' => true, 'redirection' => 5 ]; $tmp = download_url($image_url, 300, $download_args); if (is_wp_error($tmp)) { airsrefe_append_log("❌ Failed to download image: " . $tmp->get_error_message()); return false; } // SECURITY FIX: Enhanced file validation if (!file_exists($tmp)) { airsrefe_append_log("❌ Downloaded file does not exist"); return false; } // Validate file type with MIME check $file_info = wp_check_filetype($tmp); if (!$file_info['type'] || !in_array($file_info['type'], ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp'])) { airsrefe_append_log("❌ Invalid file type: " . ($file_info['type'] ?? 'unknown')); wp_delete_file($tmp); return false; } // SECURITY FIX: Additional MIME type validation $finfo = finfo_open(FILEINFO_MIME_TYPE); $mime_type = finfo_file($finfo, $tmp); finfo_close($finfo); if (!in_array($mime_type, ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp'])) { airsrefe_append_log("❌ MIME type validation failed: " . $mime_type); wp_delete_file($tmp); return false; } // Validate file size (max 10MB) $file_size = filesize($tmp); if ($file_size > 10 * 1024 * 1024) { airsrefe_append_log("❌ File too large: " . size_format($file_size)); wp_delete_file($tmp); return false; } // SECURITY FIX: Validate minimum file size (prevent empty files) if ($file_size < 100) { airsrefe_append_log("❌ File too small: " . size_format($file_size)); wp_delete_file($tmp); return false; } // Generate secure filename $file_extension = $file_info['ext'] ?? 'jpg'; $secure_filename = 'airsrefe_' . wp_generate_uuid4() . '.' . $file_extension; $file_array = [ 'name' => $secure_filename, 'tmp_name' => $tmp ]; $attachment_id = media_handle_sideload($file_array, $post_id); if (is_wp_error($attachment_id)) { airsrefe_append_log("❌ Failed to attach image: " . $attachment_id->get_error_message()); wp_delete_file($tmp); return false; } airsrefe_append_log("✅ Image attached successfully (ID: {$attachment_id})"); return $attachment_id; } /** * Optimize image after generation * * SECURITY ENHANCED: Added comprehensive validation and secure file operations */ function airsrefe_optimize_generated_image($attachment_id) { // Validate input if (!is_numeric($attachment_id) || $attachment_id <= 0) { airsrefe_append_log("❌ Invalid attachment ID for optimization: " . $attachment_id); return false; } $file_path = get_attached_file($attachment_id); if (!$file_path || !file_exists($file_path)) { airsrefe_append_log("❌ File not found for attachment ID: " . $attachment_id); return false; } // SECURITY: Validate file path is within WordPress uploads directory $upload_dir = wp_upload_dir(); $real_file_path = realpath($file_path); $real_upload_path = realpath($upload_dir['basedir']); if (!$real_file_path || strpos($real_file_path, $real_upload_path) !== 0) { airsrefe_append_log("❌ Security violation: File path outside uploads directory"); return false; } // Check if GD library is available if (!extension_loaded('gd')) { airsrefe_append_log("❌ GD library not available for image optimization"); return false; } // SECURITY: Validate file size before processing $file_size = filesize($file_path); $max_file_size = wp_convert_hr_to_bytes('10MB'); // Limit to 10MB if ($file_size > $max_file_size) { airsrefe_append_log("❌ File too large for optimization: " . size_format($file_size)); return false; } $image_info = getimagesize($file_path); if (!$image_info) { airsrefe_append_log("❌ Invalid image file: " . $file_path); return false; } // SECURITY: Validate image dimensions if ($image_info[0] <= 0 || $image_info[1] <= 0 || $image_info[0] > 8000 || $image_info[1] > 8000) { airsrefe_append_log("❌ Invalid image dimensions: " . $image_info[0] . "x" . $image_info[1]); return false; } $max_width = get_option('airsrefe_max_image_width', 1792); $max_height = get_option('airsrefe_max_image_height', 1024); $quality = get_option('airsrefe_image_compression_quality', 85); $format = get_option('airsrefe_image_format', 'webp'); // SECURITY: Validate settings $max_width = max(100, min(4000, (int)$max_width)); $max_height = max(100, min(4000, (int)$max_height)); $quality = max(10, min(100, (int)$quality)); $format = in_array($format, ['webp', 'jpeg', 'jpg', 'png']) ? $format : 'webp'; // Resize if needed if ($image_info[0] > $max_width || $image_info[1] > $max_height) { $ratio = min($max_width / $image_info[0], $max_height / $image_info[1]); $new_width = round($image_info[0] * $ratio); $new_height = round($image_info[1] * $ratio); // SECURITY: Additional validation for new dimensions if ($new_width <= 0 || $new_height <= 0 || $new_width > 4000 || $new_height > 4000) { airsrefe_append_log("❌ Invalid calculated dimensions: " . $new_width . "x" . $new_height); return false; } // SECURITY: Use WordPress file reading instead of direct file_get_contents $file_content = wp_get_file_contents($file_path); if ($file_content === false) { airsrefe_append_log("❌ Failed to read file content securely"); return false; } $source_image = imagecreatefromstring($file_content); if (!$source_image) { airsrefe_append_log("❌ Failed to create image resource from file content"); return false; } $new_image = imagecreatetruecolor($new_width, $new_height); if (!$new_image) { airsrefe_append_log("❌ Failed to create new image resource"); imagedestroy($source_image); return false; } // Enable alpha blending for transparency support imagealphablending($new_image, false); imagesavealpha($new_image, true); $success = imagecopyresampled($new_image, $source_image, 0, 0, 0, 0, $new_width, $new_height, $image_info[0], $image_info[1]); if (!$success) { airsrefe_append_log("❌ Failed to resample image"); imagedestroy($source_image); imagedestroy($new_image); return false; } // SECURITY: Generate safe output filename $file_info = pathinfo($file_path); $new_file_path = $file_info['dirname'] . '/' . $file_info['filename'] . '_optimized.' . $format; // SECURITY: Validate output path is within uploads directory $real_new_path = realpath(dirname($new_file_path)); if (!$real_new_path || strpos($real_new_path, $real_upload_path) !== 0) { airsrefe_append_log("❌ Security violation: Output path outside uploads directory"); imagedestroy($source_image); imagedestroy($new_image); return false; } // Save optimized image with error handling $save_success = false; if ($format === 'webp' && function_exists('imagewebp')) { $save_success = imagewebp($new_image, $new_file_path, $quality); } elseif ($format === 'png') { $save_success = imagepng($new_image, $new_file_path, 9); } else { $save_success = imagejpeg($new_image, $new_file_path, $quality); } imagedestroy($source_image); imagedestroy($new_image); if (!$save_success) { airsrefe_append_log("❌ Failed to save optimized image"); return false; } // SECURITY: Verify the file was created and is valid if (!file_exists($new_file_path) || filesize($new_file_path) === 0) { airsrefe_append_log("❌ Optimized file was not created or is empty"); return false; } // Update attachment metadata update_attached_file($attachment_id, $new_file_path); wp_update_attachment_metadata($attachment_id, wp_generate_attachment_metadata($attachment_id, $new_file_path)); // Remove original file if different if ($new_file_path !== $file_path && file_exists($file_path)) { wp_delete_file($file_path); } airsrefe_append_log("✅ Image optimized successfully: " . $new_file_path); return true; } return false; } // Hook for background processing add_action('airsrefe_process_image_queue', 'airsrefe_process_image_queue'); // Initialize image processing on plugin load add_action('init', function() { if (get_option('airsrefe_optimize_images', 1)) { add_filter('wp_handle_upload', function($file) { if (strpos($file['type'], 'image/') === 0) { // Schedule optimization for later wp_schedule_single_event(time() + 60, 'airsrefe_optimize_uploaded_image', [$file['file']]); } return $file; }); } }); // Hook for uploaded image optimization add_action('airsrefe_optimize_uploaded_image', function($file_path) { // Find attachment by file path using a more efficient approach $cache_key = 'airsrefe_attachment_' . md5(basename($file_path)); $attachment_id = wp_cache_get($cache_key); if ($attachment_id === false) { // Get recent attachments and filter in PHP $attachments = get_posts([ 'post_type' => 'attachment', 'posts_per_page' => 50, // Get more to filter 'fields' => 'ids', 'no_found_rows' => true, 'update_post_meta_cache' => false, 'update_post_term_cache' => false, 'cache_results' => false ]); // Filter for the specific file $target_filename = basename($file_path); foreach ($attachments as $att_id) { $attached_file = get_post_meta($att_id, '_wp_attached_file', true); if (!empty($attached_file) && strpos($attached_file, $target_filename) !== false) { $attachment_id = $att_id; break; } } wp_cache_set($cache_key, $attachment_id, '', 1800); // Cache for 30 minutes } if ($attachment_id) { airsrefe_optimize_generated_image($attachment_id); } }); /** * Optimize existing image by attachment ID * This function is used for bulk optimization */ function airsrefe_optimize_image($attachment_id) { if (!$attachment_id || !is_numeric($attachment_id)) { return false; } // Check if image exists $attachment = get_post($attachment_id); if (!$attachment || $attachment->post_type !== 'attachment') { return false; } // Check if it's an image if (!wp_attachment_is_image($attachment_id)) { return false; } // Use the existing optimization function return airsrefe_optimize_generated_image($attachment_id); } /** * Save generated image and set as featured image */ function airsrefe_save_generated_image($image_url, $post_id, $title = '') { if (empty($image_url) || !$post_id) { airsrefe_append_log("❌ Invalid parameters for saving generated image"); return false; } // Use the existing function to set featured image if (function_exists('airsrefe_set_featured_image')) { $result = airsrefe_set_featured_image($image_url, $post_id, $title); if ($result) { // Mark the image as AI-generated $attachment_id = get_post_thumbnail_id($post_id); if ($attachment_id) { update_post_meta($attachment_id, '_airsrefe_image_source', 'ai'); airsrefe_append_log("✅ AI-generated image saved and set as featured image (ID: {$attachment_id})"); return $attachment_id; } } } airsrefe_append_log("❌ Failed to save generated image"); return false; }