$feed_url, 'categories' => $categories, 'limit' => $limit, 'user_id' => $user_id, 'model' => 'openai', 'enabled' => true, 'created' => current_time('mysql') ]; $feeds[] = $new_feed; update_option('airsrefe_feeds', $feeds); // translators: %s is the RSS feed URL airsrefe_append_log(sprintf(__('➕ Added new RSS feed: %s', 'ai-rss-rewriter'), $new_feed['url'])); wp_safe_redirect(admin_url('admin.php?page=ai-rss-rewriter&message=feed_added')); exit; } function airsrefe_edit_feed() { if (!current_user_can('manage_options')) { wp_die(esc_html__('Insufficient permissions.', 'ai-rss-rewriter')); } $nonce = isset($_POST['airsrefe_edit_feed_nonce']) ? sanitize_text_field(wp_unslash($_POST['airsrefe_edit_feed_nonce'])) : ''; if (!wp_verify_nonce($nonce, 'airsrefe_edit_feed')) { wp_die(esc_html__('Invalid nonce.', 'ai-rss-rewriter')); } $index = intval($_POST['feed_index'] ?? -1); $feeds = get_option('airsrefe_feeds', []); if (!isset($feeds[$index])) { wp_die(esc_html__('Feed not found.', 'ai-rss-rewriter')); } $categories = isset($_POST['categories']) ? array_map('intval', (array) wp_unslash($_POST['categories'])) : [0]; $feed_url = isset($_POST['feed_url']) ? esc_url_raw(wp_unslash($_POST['feed_url'])) : ''; if (empty($feed_url)) { wp_die(esc_html__('Feed URL is required.', 'ai-rss-rewriter')); } // Enhanced URL validation if (!airsrefe_validate_feed_url($feed_url)) { wp_die(esc_html__('Invalid feed URL format.', 'ai-rss-rewriter')); } $limit = isset($_POST['limit']) ? max(1, min(20, intval($_POST['limit']))) : 10; $user_id = isset($_POST['user_id']) ? intval($_POST['user_id']) : get_current_user_id(); $feeds[$index]['url'] = $feed_url; $feeds[$index]['categories'] = $categories; $feeds[$index]['limit'] = $limit; $feeds[$index]['user_id'] = $user_id; $feeds[$index]['updated'] = current_time('mysql'); update_option('airsrefe_feeds', $feeds); // translators: %s is the RSS feed URL airsrefe_append_log(sprintf(__('✏️ Edited RSS feed: %s', 'ai-rss-rewriter'), $feeds[$index]['url'])); wp_safe_redirect(admin_url('admin.php?page=ai-rss-rewriter&message=feed_updated')); exit; } function airsrefe_delete_feed() { if (!current_user_can('manage_options')) { wp_die(esc_html__('Insufficient permissions.', 'ai-rss-rewriter')); } $nonce = isset($_POST['airsrefe_delete_feed_nonce']) ? sanitize_text_field(wp_unslash($_POST['airsrefe_delete_feed_nonce'])) : ''; if (!wp_verify_nonce($nonce, 'airsrefe_delete_feed')) { wp_die(esc_html__('Invalid nonce.', 'ai-rss-rewriter')); } $index = intval($_POST['feed_index'] ?? -1); $feeds = get_option('airsrefe_feeds', []); if (!isset($feeds[$index])) { wp_die(esc_html__('Feed not found.', 'ai-rss-rewriter')); } $deleted_url = $feeds[$index]['url']; unset($feeds[$index]); update_option('airsrefe_feeds', array_values($feeds)); // translators: %s is the RSS feed URL airsrefe_append_log(sprintf(__('🗑️ Deleted RSS feed: %s', 'ai-rss-rewriter'), $deleted_url)); wp_safe_redirect(admin_url('admin.php?page=ai-rss-rewriter&tab=feeds&message=feed_deleted')); exit; } function airsrefe_run_now() { if (!current_user_can('manage_options')) { wp_die(esc_html__('Insufficient permissions.', 'ai-rss-rewriter')); } $index = intval($_POST['feed_index'] ?? -1); $nonce = isset($_POST['airsrefe_run_now_nonce']) ? sanitize_text_field(wp_unslash($_POST['airsrefe_run_now_nonce'])) : ''; if (!wp_verify_nonce($nonce, 'airsrefe_run_now_action_' . $index)) { wp_die(esc_html__('Invalid nonce.', 'ai-rss-rewriter')); } $feeds = get_option('airsrefe_feeds', []); if (!isset($feeds[$index])) { wp_die(esc_html__('Feed not found.', 'ai-rss-rewriter')); } // translators: %s is the RSS feed URL airsrefe_append_log(sprintf(__('🚀 Manual run started for: %s', 'ai-rss-rewriter'), $feeds[$index]['url'])); $was_blocked = get_option('airsrefe_block_parsing'); if ($was_blocked) { update_option('airsrefe_block_parsing', 0); } try { if (function_exists('airsrefe_process_feed')) { airsrefe_process_feed($feeds[$index]); $feeds[$index]['last_run'] = current_time('mysql'); update_option('airsrefe_feeds', $feeds); airsrefe_append_log(__('✅ Manual processing completed successfully', 'ai-rss-rewriter')); wp_safe_redirect(admin_url('admin.php?page=ai-rss-rewriter&message=feed_processed')); } else { airsrefe_append_log(__('❌ Feed processing function not available', 'ai-rss-rewriter')); wp_safe_redirect(admin_url('admin.php?page=ai-rss-rewriter&message=feed_error')); } } catch (Exception $e) { // translators: %s is the error message airsrefe_append_log(sprintf(__('❌ Manual processing error: %s', 'ai-rss-rewriter'), $e->getMessage())); wp_safe_redirect(admin_url('admin.php?page=ai-rss-rewriter&message=feed_error')); } finally { if ($was_blocked) { update_option('airsrefe_block_parsing', $was_blocked); } } exit; } function airsrefe_bulk_action() { airsrefe_append_log("🚀 Bulk action function started"); airsrefe_append_log("👤 Current user: " . wp_get_current_user()->user_login); airsrefe_append_log("🔐 User capabilities: " . implode(', ', array_keys(wp_get_current_user()->allcaps))); if (!current_user_can('manage_options')) { airsrefe_append_log("❌ User lacks 'manage_options' capability"); wp_die(esc_html__('Insufficient permissions.', 'ai-rss-rewriter')); } // Check if nonce exists if (!isset($_POST['airsrefe_bulk_action_nonce'])) { wp_die(esc_html__('Security check failed: Nonce field missing.', 'ai-rss-rewriter')); } $nonce = sanitize_text_field(wp_unslash($_POST['airsrefe_bulk_action_nonce'])); // Log nonce details after sanitization airsrefe_append_log("🔍 Nonce value received: " . substr($nonce, 0, 10) . "... (length: " . strlen($nonce) . ")"); if (empty($nonce)) { airsrefe_append_log("❌ Nonce value is empty after sanitization"); wp_die(esc_html__('Security check failed: Nonce value is empty.', 'ai-rss-rewriter')); } airsrefe_append_log("✅ Nonce value is not empty"); if (!wp_verify_nonce($nonce, 'airsrefe_bulk_action')) { airsrefe_append_log("❌ Nonce verification failed for bulk action"); airsrefe_append_log("📊 Nonce details - Received: " . substr($nonce, 0, 10) . "... (length: " . strlen($nonce) . ")"); wp_die(esc_html__('Security check failed: Invalid nonce. Please refresh the page and try again.', 'ai-rss-rewriter')); } airsrefe_append_log("✅ Nonce verification successful for bulk action"); // Now safe to log POST data details after nonce verification $post_keys = array_keys($_POST); $sanitized_keys = array_map('sanitize_key', $post_keys); airsrefe_append_log("🔍 Available POST fields: " . implode(', ', $sanitized_keys)); airsrefe_append_log("🔍 Extracting action and feed IDs from POST data"); // Note: $_POST data is now safe to access after nonce verification above // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $action = isset($_POST['bulk_action']) ? sanitize_text_field(wp_unslash($_POST['bulk_action'])) : ''; $feed_ids = isset($_POST['feed_ids']) ? array_map('intval', $_POST['feed_ids']) : []; airsrefe_append_log("📊 Action extracted: '{$action}' (length: " . strlen($action) . ")"); airsrefe_append_log("📊 Feed IDs extracted: " . implode(', ', $feed_ids) . " (count: " . count($feed_ids) . ")"); // Validate action if (empty($action)) { airsrefe_append_log("❌ No action selected for bulk operation"); wp_die(esc_html__('No action selected. Please select an action and try again.', 'ai-rss-rewriter')); } airsrefe_append_log("✅ Action validated: {$action}"); // Validate feed selection if (empty($feed_ids)) { airsrefe_append_log("❌ No feeds selected for bulk operation"); wp_die(esc_html__('No feeds selected. Please select at least one feed and try again.', 'ai-rss-rewriter')); } airsrefe_append_log("✅ Feed selection validated: " . count($feed_ids) . " feeds selected"); // Get feeds data early for logging $feeds = get_option('airsrefe_feeds', []); // Ensure feeds is an array if (!is_array($feeds)) { airsrefe_append_log("❌ Feeds data is not an array: " . gettype($feeds)); $feeds = []; } // Ensure feed_ids is an array if (!is_array($feed_ids)) { airsrefe_append_log("❌ Feed IDs is not an array: " . gettype($feed_ids)); $feed_ids = []; } // Ensure action is a string if (!is_string($action)) { airsrefe_append_log("❌ Action is not a string: " . gettype($action)); $action = ''; } // Ensure nonce is a string if (!is_string($nonce)) { airsrefe_append_log("❌ Nonce is not a string: " . gettype($nonce)); $nonce = ''; } // Log the received data airsrefe_append_log("📝 Bulk action received - Action: {$action}, Feed IDs: " . implode(', ', $feed_ids)); // Log POST data (without sensitive information) // Note: Using already verified $nonce variable instead of $_POST // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $post_data_log = [ 'action' => $action, 'feed_ids_count' => count($feed_ids), 'feed_ids' => $feed_ids, 'nonce_exists' => !empty($nonce), 'nonce_length' => strlen($nonce), 'total_feeds_available' => count($feeds) ]; airsrefe_append_log("📊 POST data analysis: " . wp_json_encode($post_data_log)); // Validate action value airsrefe_append_log("🔍 Validating action value against allowed actions"); $allowed_actions = ['run', 'enable', 'disable', 'delete']; airsrefe_append_log("📊 Allowed actions: " . implode(', ', $allowed_actions)); airsrefe_append_log("📊 Selected action: '{$action}'"); if (!in_array($action, $allowed_actions, true)) { airsrefe_append_log("❌ Invalid action selected: {$action}"); airsrefe_append_log("📊 Allowed actions: " . implode(', ', $allowed_actions)); wp_die(esc_html__('Invalid action selected. Please select a valid action and try again.', 'ai-rss-rewriter')); } airsrefe_append_log("✅ Action value validated: {$action} is allowed"); // Log available feeds airsrefe_append_log("📊 Available feeds in database: " . count($feeds) . " total"); if (!empty($feeds)) { $available_ids = array_keys($feeds); airsrefe_append_log("📋 Available feed IDs: " . implode(', ', $available_ids)); } // Validate that all selected feed IDs exist and are valid integers $valid_feed_ids = []; $invalid_feed_ids = []; foreach ($feed_ids as $id) { // Ensure ID is a valid integer $feed_id = intval($id); if ($feed_id >= 0 && isset($feeds[$feed_id])) { $valid_feed_ids[] = $feed_id; } else { $invalid_feed_ids[] = $id; airsrefe_append_log("⚠️ Invalid feed ID selected: " . $id . " (converted to: " . $feed_id . ")"); } } // Log validation results if (!empty($invalid_feed_ids)) { airsrefe_append_log("⚠️ Invalid feed IDs found: " . implode(', ', $invalid_feed_ids)); } airsrefe_append_log("✅ Valid feed IDs: " . implode(', ', $valid_feed_ids)); if (empty($valid_feed_ids)) { airsrefe_append_log("❌ No valid feeds found in selection"); wp_die(esc_html__('No valid feeds found in selection. Please refresh the page and try again.', 'ai-rss-rewriter')); } // Use only valid feed IDs $feed_ids = $valid_feed_ids; airsrefe_append_log("✅ Feed IDs validated: " . count($feed_ids) . " valid feeds found"); airsrefe_append_log("🚀 Bulk action '{$action}' initiated for " . count($feed_ids) . " feeds"); // Final validation before switch if (empty($action) || empty($feed_ids) || empty($feeds)) { airsrefe_append_log("❌ Final validation failed - Action: '{$action}', Feed IDs count: " . count($feed_ids) . ", Feeds count: " . count($feeds)); wp_die(esc_html__('Validation failed. Please refresh the page and try again.', 'ai-rss-rewriter')); } switch ($action) { case 'run': airsrefe_append_log("🚀 Starting bulk run for " . count($feed_ids) . " feeds"); $processed = 0; $was_blocked = get_option('airsrefe_block_parsing'); // Temporarily unblock parsing for manual run if ($was_blocked) { update_option('airsrefe_block_parsing', 0); airsrefe_append_log("🔓 Temporarily unblocked parsing for bulk run"); } foreach ($feed_ids as $id) { airsrefe_append_log("🔄 Starting processing for feed #{$id} (" . ($processed + 1) . " of " . count($feed_ids) . ")"); if (isset($feeds[$id])) { airsrefe_append_log("🔄 Processing feed #{$id} (" . ($processed + 1) . " of " . count($feed_ids) . ")"); try { // translators: %1$d is the feed ID number, %2$s is the RSS feed URL airsrefe_append_log(sprintf(__('🚀 Bulk processing feed #%1$d: %2$s', 'ai-rss-rewriter'), $id, $feeds[$id]['url'])); if (function_exists('airsrefe_process_feed')) { airsrefe_append_log("🔄 Calling airsrefe_process_feed for feed #{$id}"); airsrefe_process_feed($feeds[$id]); $feeds[$id]['last_run'] = current_time('mysql'); $processed++; // translators: %d is the feed ID number airsrefe_append_log(sprintf(__('✅ Feed #%d processed successfully', 'ai-rss-rewriter'), $id)); } else { airsrefe_append_log(__('❌ airsrefe_process_feed function not found', 'ai-rss-rewriter')); airsrefe_append_log("🔍 Available functions: " . implode(', ', get_defined_functions()['user'])); break; } // Add delay between feeds to prevent overwhelming the server if ($processed < count($feed_ids)) { airsrefe_append_log("⏳ Adding 2 second delay before next feed..."); sleep(2); } } catch (Exception $e) { // translators: %1$d is the feed ID number, %2$s is the error message airsrefe_append_log(sprintf(__('❌ Bulk processing error for feed %1$d: %2$s', 'ai-rss-rewriter'), $id, $e->getMessage())); airsrefe_append_log("🔍 Error details - File: " . $e->getFile() . ", Line: " . $e->getLine()); airsrefe_append_log("📚 Stack trace: " . $e->getTraceAsString()); continue; } } } // Restore blocking status if ($was_blocked) { update_option('airsrefe_block_parsing', $was_blocked); airsrefe_append_log("🔒 Restored parsing block status"); } update_option('airsrefe_feeds', $feeds); // translators: %d is the number of feeds processed airsrefe_append_log(sprintf(__('📦 Bulk processing completed: %d feeds processed', 'ai-rss-rewriter'), $processed)); airsrefe_append_log("📊 Processing summary - Total: " . count($feed_ids) . ", Successful: {$processed}, Failed: " . (count($feed_ids) - $processed)); $message = 'feeds_processed'; break; case 'enable': airsrefe_append_log("🔓 Enabling " . count($feed_ids) . " feeds"); foreach ($feed_ids as $id) { if (isset($feeds[$id])) { $feeds[$id]['enabled'] = true; airsrefe_append_log("✅ Enabled feed #{$id}: " . $feeds[$id]['url']); } } $message = 'feeds_enabled'; airsrefe_append_log("✅ Bulk enable completed for " . count($feed_ids) . " feeds"); break; case 'disable': airsrefe_append_log("🔒 Disabling " . count($feed_ids) . " feeds"); foreach ($feed_ids as $id) { if (isset($feeds[$id])) { $feeds[$id]['enabled'] = false; airsrefe_append_log("✅ Disabled feed #{$id}: " . $feeds[$id]['url']); } } $message = 'feeds_disabled'; airsrefe_append_log("✅ Bulk disable completed for " . count($feed_ids) . " feeds"); break; case 'delete': airsrefe_append_log("🗑️ Deleting " . count($feed_ids) . " feeds"); foreach (array_reverse($feed_ids) as $id) { if (isset($feeds[$id])) { $feed_url = $feeds[$id]['url']; unset($feeds[$id]); airsrefe_append_log("✅ Deleted feed #{$id}: " . $feed_url); } } $feeds = array_values($feeds); $message = 'feeds_deleted'; airsrefe_append_log("✅ Bulk delete completed for " . count($feed_ids) . " feeds"); break; default: airsrefe_append_log("❌ Unexpected action value: '{$action}'"); airsrefe_append_log("📊 This should not happen as we validated the action earlier"); wp_safe_redirect(admin_url('admin.php?page=ai-rss-rewriter&message=no_selection')); exit; } update_option('airsrefe_feeds', $feeds); // translators: %1$s is the bulk action name, %2$d is the number of feeds affected airsrefe_append_log(sprintf(__('📦 Bulk operation \'%1$s\' performed on %2$d feeds', 'ai-rss-rewriter'), $action, count($feed_ids))); // Log final status airsrefe_append_log("🎯 Bulk action '{$action}' completed successfully"); airsrefe_append_log("📊 Final feed count in database: " . count($feeds)); airsrefe_append_log("🔄 Redirecting to admin page with message: {$message}"); airsrefe_append_log("🏁 Bulk action function completed successfully"); wp_safe_redirect(admin_url('admin.php?page=ai-rss-rewriter&tab=feeds&message=' . $message)); exit; } function airsrefe_toggle_feed_form() { if (!current_user_can('manage_options')) { wp_die(esc_html__('Insufficient permissions.', 'ai-rss-rewriter')); } $feed_index = isset($_POST['feed_index']) ? intval($_POST['feed_index']) : -1; $nonce = isset($_POST['toggle_nonce']) ? sanitize_text_field(wp_unslash($_POST['toggle_nonce'])) : ''; if (!wp_verify_nonce($nonce, 'airsrefe_toggle_' . $feed_index)) { wp_die(esc_html__('Access denied', 'ai-rss-rewriter')); } $feeds = get_option('airsrefe_feeds', []); if (isset($feeds[$feed_index])) { $feeds[$feed_index]['enabled'] = !($feeds[$feed_index]['enabled'] ?? true); update_option('airsrefe_feeds', $feeds); } wp_safe_redirect(admin_url('admin.php?page=ai-rss-rewriter')); exit; } function airsrefe_unblock_parsing() { if (!current_user_can('manage_options')) { wp_die(esc_html__('Insufficient permissions.', 'ai-rss-rewriter')); } $nonce = isset($_POST['airsrefe_unblock_nonce']) ? sanitize_text_field(wp_unslash($_POST['airsrefe_unblock_nonce'])) : ''; if (!wp_verify_nonce($nonce, 'airsrefe_unblock')) { wp_die(esc_html__('Invalid nonce.', 'ai-rss-rewriter')); } update_option('airsrefe_block_parsing', 0); airsrefe_append_log(__('🔓 Parsing unblocked by administrator', 'ai-rss-rewriter')); wp_safe_redirect(admin_url('admin.php?page=ai-rss-rewriter&message=parsing_unblocked')); exit; } function airsrefe_clear_log() { if (!current_user_can('manage_options')) { wp_die(esc_html__('Insufficient permissions.', 'ai-rss-rewriter')); } $nonce = isset($_POST['airsrefe_clear_log_nonce']) ? sanitize_text_field(wp_unslash($_POST['airsrefe_clear_log_nonce'])) : ''; if (!wp_verify_nonce($nonce, 'airsrefe_clear_log')) { wp_die(esc_html__('Invalid nonce.', 'ai-rss-rewriter')); } update_option('airsrefe_log', ''); update_option('airsrefe_logs', []); airsrefe_append_log(__('🗑️ Logs cleared by administrator', 'ai-rss-rewriter')); wp_safe_redirect(admin_url('admin.php?page=ai-rss-rewriter&tab=logs&message=logs_cleared')); exit; } // AJAX handlers /** * AJAX handler for toggling feed status * * @since 1.0.0 * @return void */ function airsrefe_ajax_toggle_feed() { if (!current_user_can('manage_options')) { wp_send_json_error('Insufficient permissions'); return; } if (!check_ajax_referer('airsrefe_ajax', 'nonce', false)) { wp_send_json_error('Invalid nonce'); return; } $feed_index = intval($_POST['feed_index'] ?? -1); if ($feed_index < 0) { wp_send_json_error('Invalid feed index'); return; } $feeds = get_option('airsrefe_feeds', []); if (!isset($feeds[$feed_index])) { wp_send_json_error('Feed not found'); return; } $current_status = $feeds[$feed_index]['enabled'] ?? true; $feeds[$feed_index]['enabled'] = !$current_status; $new_status = $feeds[$feed_index]['enabled']; if (update_option('airsrefe_feeds', $feeds)) { $status_text = $new_status ? 'enabled' : 'disabled'; airsrefe_append_log(sprintf('🔄 Feed %s: %s', $status_text, $feeds[$feed_index]['url'])); wp_send_json_success([ 'status' => $status_text, 'message' => 'Feed ' . $status_text . ' successfully' ]); } else { wp_send_json_error('Failed to update feed status'); } } /** * AJAX handler for exporting plugin settings * * @since 1.0.0 * @return void */ function airsrefe_ajax_export_settings() { if (!current_user_can('manage_options')) { wp_die(esc_html__('Insufficient permissions.', 'ai-rss-rewriter')); } check_ajax_referer('airsrefe_ajax', 'nonce'); $data = [ 'version' => ARR_VERSION, 'exported' => current_time('mysql'), 'site_url' => site_url(), 'data' => [ 'feeds' => get_option('airsrefe_feeds', []), 'settings' => airsrefe_get_exportable_settings(), 'prompts' => get_option('airsrefe_prompts', []) ] ]; wp_send_json_success($data); } /** * AJAX handler for importing plugin settings * * @since 1.0.0 * @return void */ function airsrefe_ajax_import_settings() { if (!current_user_can('manage_options')) { wp_die(esc_html__('Insufficient permissions.', 'ai-rss-rewriter')); } check_ajax_referer('airsrefe_ajax', 'nonce'); // SECURITY FIX: Enhanced input validation and sanitization if (!isset($_POST['settings']) || empty($_POST['settings'])) { wp_send_json_error('No settings data provided'); } $raw_settings = isset($_POST['settings']) ? sanitize_textarea_field(wp_unslash($_POST['settings'])) : ''; // Validate JSON structure first $import_data = json_decode($raw_settings, true); if (json_last_error() !== JSON_ERROR_NONE) { wp_send_json_error('Invalid JSON data: ' . json_last_error_msg()); } // Validate that we have valid structure if (!is_array($import_data)) { wp_send_json_error('Invalid data format'); } // Additional validation for required structure if (!isset($import_data['data']) || !is_array($import_data['data'])) { wp_send_json_error('Invalid settings structure'); } try { $import_data = $raw_settings['data']; // Import feeds with validation if (isset($import_data['feeds']) && is_array($import_data['feeds'])) { // Validate each feed has required fields $valid_feeds = []; foreach ($import_data['feeds'] as $feed) { if (is_array($feed) && isset($feed['url']) && filter_var($feed['url'], FILTER_VALIDATE_URL)) { $valid_feeds[] = $feed; } } update_option('airsrefe_feeds', $valid_feeds); } // Import settings with whitelist validation if (isset($import_data['settings']) && is_array($import_data['settings'])) { $allowed_settings = [ 'airsrefe_cron_interval', 'airsrefe_generate_tags', 'airsrefe_image_source', 'airsrefe_cache_time', 'airsrefe_processing_mode', 'airsrefe_use_original_date', 'airsrefe_optimize_images', 'airsrefe_image_format', 'airsrefe_model_content', 'airsrefe_model_title', 'airsrefe_smart_model_selection', 'airsrefe_daily_budget_limit' ]; foreach ($import_data['settings'] as $key => $value) { if (in_array($key, $allowed_settings, true)) { update_option($key, sanitize_text_field($value)); } } } // Import prompts with validation if (isset($import_data['prompts']) && is_array($import_data['prompts'])) { $sanitized_prompts = map_deep($import_data['prompts'], 'sanitize_textarea_field'); update_option('airsrefe_prompts', $sanitized_prompts); } // Reschedule cron wp_clear_scheduled_hook('airsrefe_auto_parse_event'); if (function_exists('airsrefe_schedule_cron')) { airsrefe_schedule_cron(); } airsrefe_append_log('✅ Settings imported successfully'); wp_send_json_success(); } catch (Exception $e) { airsrefe_append_log('❌ Import failed: ' . $e->getMessage()); wp_send_json_error('Import failed: ' . $e->getMessage()); } } function airsrefe_ajax_check_api_status() { if (!current_user_can('manage_options')) { wp_die(esc_html__('Invalid request.', 'ai-rss-rewriter')); } check_ajax_referer('airsrefe_ajax', 'nonce'); $api_key = get_option('airsrefe_openai_key', ''); if (empty($api_key)) { airsrefe_append_log('❌ API key not configured for status check'); wp_send_json_error(esc_html__('API key not configured', 'ai-rss-rewriter')); return; } airsrefe_append_log('🔍 Checking OpenAI API status...'); $response = wp_remote_get('https://api.openai.com/v1/models', [ 'headers' => [ 'Authorization' => 'Bearer ' . $api_key, ], 'timeout' => 15, 'user-agent' => 'AI-RSS-Rewriter/1.0' ]); if (is_wp_error($response)) { $error_message = $response->get_error_message(); airsrefe_append_log('❌ OpenAI API connection error: ' . $error_message); wp_send_json_error(esc_html__('Connection error: ', 'ai-rss-rewriter') . $error_message); return; } $status_code = wp_remote_retrieve_response_code($response); $body = wp_remote_retrieve_body($response); airsrefe_append_log('📡 OpenAI API response: HTTP ' . $status_code); if ($status_code === 200) { update_option('airsrefe_block_parsing', 0); airsrefe_append_log('✅ OpenAI API is working normally'); wp_send_json_success(esc_html__('OpenAI API is working normally', 'ai-rss-rewriter')); } else { $body_data = json_decode($body, true); $error_message = ''; if ($body_data && isset($body_data['error']['message'])) { $error_message = $body_data['error']['message']; } else { $error_message = esc_html__('Unknown error', 'ai-rss-rewriter'); } airsrefe_append_log('❌ OpenAI API error (' . $status_code . '): ' . $error_message); // translators: %1$d is the HTTP status code, %2$s is the error message wp_send_json_error(sprintf(esc_html__('API error (%1$d): %2$s', 'ai-rss-rewriter'), $status_code, $error_message)); } } function airsrefe_ajax_reschedule_cron() { if (!current_user_can('manage_options')) { wp_die(esc_html__('Invalid request.', 'ai-rss-rewriter')); } check_ajax_referer('airsrefe_ajax', 'nonce'); wp_clear_scheduled_hook('airsrefe_auto_parse_event'); $result = airsrefe_schedule_cron(); if ($result !== false) { wp_send_json_success(['message' => 'Cron rescheduled successfully']); } else { wp_send_json_error('Failed to reschedule cron'); } } function airsrefe_ajax_manual_run_all() { if (!current_user_can('manage_options')) { wp_die(esc_html__('Invalid request.', 'ai-rss-rewriter')); } check_ajax_referer('airsrefe_ajax', 'nonce'); airsrefe_append_log('🚀 Manual run started by admin user'); if (function_exists('airsrefe_auto_parse_handler_improved')) { airsrefe_auto_parse_handler_improved(); wp_send_json_success(['message' => esc_html__('Manual processing completed successfully', 'ai-rss-rewriter')]); } else { wp_send_json_error(esc_html__('Auto parse handler function not found', 'ai-rss-rewriter')); } } function airsrefe_reorder_feeds() { if (!current_user_can('manage_options')) { wp_die(esc_html__('Invalid request.', 'ai-rss-rewriter')); } check_ajax_referer('airsrefe_ajax', 'nonce'); $order_data = isset($_POST['order']) ? sanitize_text_field(wp_unslash($_POST['order'])) : ''; parse_str($order_data, $order); $feed_order = $order['feed'] ?? []; $feeds = get_option('airsrefe_feeds', []); $reordered_feeds = []; foreach ($feed_order as $index) { if (isset($feeds[$index])) { $reordered_feeds[] = $feeds[$index]; } } update_option('airsrefe_feeds', $reordered_feeds); airsrefe_append_log(__('📋 Feed order updated via drag & drop', 'ai-rss-rewriter')); wp_send_json_success(); } function airsrefe_ajax_refresh_translations() { if (!current_user_can('manage_options')) { wp_die(esc_html__('Invalid request.', 'ai-rss-rewriter')); } check_ajax_referer('airsrefe_ajax', 'nonce'); if (function_exists('wp_cache_flush')) { wp_cache_flush(); } wp_cache_flush_group('translations'); $plugin_language = get_option('airsrefe_plugin_language', 'en'); if ($plugin_language === 'uk') { $uk_file = dirname(__FILE__) . '/../languages/uk.mo'; if (file_exists($uk_file)) { load_textdomain('ai-rss-rewriter', $uk_file); } } // translators: %s is the plugin language airsrefe_append_log(sprintf(__('🔄 Translations refreshed for language: %s', 'ai-rss-rewriter'), $plugin_language)); wp_send_json_success(['message' => __('Translations refreshed successfully', 'ai-rss-rewriter')]); } // Helper function to get exportable settings function airsrefe_get_exportable_settings() { $settings = [ 'airsrefe_cron_interval', 'airsrefe_generate_tags', 'airsrefe_image_source', 'airsrefe_cache_time', 'airsrefe_processing_mode', 'airsrefe_use_original_date', 'airsrefe_optimize_images', 'airsrefe_image_format', 'airsrefe_model_content', 'airsrefe_model_title', 'airsrefe_smart_model_selection', 'airsrefe_daily_budget_limit' ]; $data = []; foreach ($settings as $setting) { $data[$setting] = get_option($setting); } return $data; } // Cron management functions // Custom cron schedules add_filter('cron_schedules', 'airsrefe_custom_cron_schedule'); function airsrefe_custom_cron_schedule($schedules) { $interval_minutes = max(1, (int) get_option('airsrefe_cron_interval', 60)); $custom_key = 'airsrefe_custom_' . $interval_minutes . 'min'; // Only use translation if it's available (after init action) if (did_action('init') > 0) { // translators: %d is the number of minutes for the cron interval $display_text = sprintf(__('Every %d minutes (AI RSS Rewriter Custom)', 'ai-rss-rewriter'), $interval_minutes); } else { $display_text = sprintf('Every %d minutes (AI RSS Rewriter Custom)', $interval_minutes); } $schedules[$custom_key] = [ 'interval' => $interval_minutes * 60, 'display' => $display_text ]; // Additional standard intervals for fallback $schedules['airsrefe_1min'] = [ 'interval' => 1 * 60, 'display' => 'Every 1 minute (AI RSS Rewriter)' ]; $schedules['airsrefe_2min'] = [ 'interval' => 2 * 60, 'display' => 'Every 2 minutes (AI RSS Rewriter)' ]; $schedules['airsrefe_5min'] = [ 'interval' => 5 * 60, 'display' => 'Every 5 minutes (AI RSS Rewriter)' ]; $schedules['airsrefe_10min'] = [ 'interval' => 10 * 60, 'display' => 'Every 10 minutes (AI RSS Rewriter)' ]; $schedules['airsrefe_15min'] = [ 'interval' => 15 * 60, 'display' => 'Every 15 minutes (AI RSS Rewriter)' ]; $schedules['airsrefe_30min'] = [ 'interval' => 30 * 60, 'display' => 'Every 30 minutes (AI RSS Rewriter)' ]; $schedules['airsrefe_hourly'] = [ 'interval' => 60 * 60, 'display' => 'Every hour (AI RSS Rewriter)' ]; return $schedules; } // Cron scheduling function function airsrefe_schedule_cron() { wp_clear_scheduled_hook('airsrefe_auto_parse_event'); $interval_minutes = max(1, (int) get_option('airsrefe_cron_interval', 60)); $custom_key = 'airsrefe_custom_' . $interval_minutes . 'min'; $start_delay = get_option('airsrefe_cron_start_delay', 2); $next_time = time() + ($start_delay * 60); $result = wp_schedule_event($next_time, $custom_key, 'airsrefe_auto_parse_event'); if ($result === false) { $fallback_key = airsrefe_get_fallback_interval($interval_minutes); $result = wp_schedule_event($next_time, $fallback_key, 'airsrefe_auto_parse_event'); if ($result === false) { $result = wp_schedule_event($next_time, 'hourly', 'airsrefe_auto_parse_event'); } } if ($result !== false) { update_option('airsrefe_cron_last_scheduled', current_time('mysql')); update_option('airsrefe_cron_interval_used', $interval_minutes); } return $result; } function airsrefe_get_fallback_interval($target_minutes) { $standard_intervals = [ 1 => 'airsrefe_1min', 2 => 'airsrefe_2min', 5 => 'airsrefe_5min', 10 => 'airsrefe_10min', 15 => 'airsrefe_15min', 30 => 'airsrefe_30min', 60 => 'airsrefe_hourly' ]; $closest = 60; $min_diff = abs($target_minutes - 60); foreach ($standard_intervals as $minutes => $key) { $diff = abs($target_minutes - $minutes); if ($diff < $min_diff) { $min_diff = $diff; $closest = $minutes; } } return $standard_intervals[$closest]; } // Auto parse handler (simplified version) add_action('airsrefe_auto_parse_event', 'airsrefe_auto_parse_handler_improved'); function airsrefe_auto_parse_handler_improved() { airsrefe_append_log(__('🚀 Auto parse started by WP Cron', 'ai-rss-rewriter')); if (get_option('airsrefe_block_parsing') == 1) { airsrefe_append_log(__('⛔ Auto parsing blocked due to API issues', 'ai-rss-rewriter')); return; } $lock_option = 'airsrefe_auto_parse_lock'; $lock_time = get_option($lock_option, 0); if ($lock_time && (time() - $lock_time) < 1800) { // 30 min timeout airsrefe_append_log(__('⏳ Auto parsing already in progress', 'ai-rss-rewriter')); return; } update_option($lock_option, time()); try { $feeds = get_option('airsrefe_feeds', []); if (empty($feeds)) { airsrefe_append_log(__('⚠️ No feeds configured for auto parsing', 'ai-rss-rewriter')); return; } $processed_count = 0; $enabled_feeds = array_filter($feeds, function($feed) { return !isset($feed['enabled']) || $feed['enabled'] === true; }); // translators: %d is the number of enabled feeds found airsrefe_append_log(sprintf(__('📊 Found %d enabled feeds to process', 'ai-rss-rewriter'), count($enabled_feeds))); foreach ($enabled_feeds as $index => $feed) { try { // translators: %1$d is the feed index, %2$s is the feed URL airsrefe_append_log(sprintf(__('🔄 Processing feed #%1$d: %2$s', 'ai-rss-rewriter'), $index, $feed['url'])); if (function_exists('airsrefe_process_feed')) { airsrefe_process_feed($feed); $processed_count++; $feeds[$index]['last_run'] = current_time('mysql'); // translators: %d is the feed index airsrefe_append_log(sprintf(__('✅ Feed #%d processed successfully', 'ai-rss-rewriter'), $index)); } else { airsrefe_append_log(__('❌ airsrefe_process_feed function not found', 'ai-rss-rewriter')); break; } if ($processed_count < count($enabled_feeds)) { sleep(3); } if (memory_get_usage() > wp_convert_hr_to_bytes(ini_get('memory_limit')) * 0.8) { airsrefe_append_log(__('⚠️ Memory usage high, stopping processing', 'ai-rss-rewriter')); break; } } catch (Exception $e) { // translators: %1$d is the feed index, %2$s is the error message airsrefe_append_log(sprintf(__('❌ Error processing feed #%1$d: %2$s', 'ai-rss-rewriter'), $index, $e->getMessage())); continue; } } update_option('airsrefe_feeds', $feeds); // translators: %1$d is the number of processed feeds, %2$d is the total number of enabled feeds airsrefe_append_log(sprintf(__('🎯 Auto parsing completed. Processed: %1$d/%2$d feeds', 'ai-rss-rewriter'), $processed_count, count($enabled_feeds))); } catch (Exception $e) { // translators: %s is the error message airsrefe_append_log(sprintf(__('❌ Critical error in auto parse: %s', 'ai-rss-rewriter'), $e->getMessage())); } finally { delete_option($lock_option); } $next_scheduled = wp_next_scheduled('airsrefe_auto_parse_event'); if (!$next_scheduled) { airsrefe_schedule_cron(); airsrefe_append_log(__('🔄 Rescheduled next auto parse', 'ai-rss-rewriter')); } } // Check and fix cron on admin init add_action('admin_init', 'airsrefe_check_and_fix_cron'); function airsrefe_check_and_fix_cron() { if (defined('DISABLE_WP_CRON') && DISABLE_WP_CRON) { update_option('airsrefe_cron_status', 'disabled'); airsrefe_append_log(__('⚠️ WP Cron disabled via DISABLE_WP_CRON constant', 'ai-rss-rewriter')); return; } $next_scheduled = wp_next_scheduled('airsrefe_auto_parse_event'); $interval_minutes = max(1, (int) get_option('airsrefe_cron_interval', 60)); if (!$next_scheduled) { airsrefe_schedule_cron(); update_option('airsrefe_cron_status', 'scheduled'); airsrefe_append_log(__('🔧 Cron was not scheduled - rescheduled', 'ai-rss-rewriter')); } else { $time_until_next = $next_scheduled - time(); $max_expected_time = $interval_minutes * 60 * 2; if ($time_until_next > $max_expected_time || $time_until_next < -3600) { wp_clear_scheduled_hook('airsrefe_auto_parse_event'); airsrefe_schedule_cron(); update_option('airsrefe_cron_status', 'rescheduled'); airsrefe_append_log(__('🔧 Cron timing was off - rescheduled', 'ai-rss-rewriter')); } else { update_option('airsrefe_cron_status', 'active'); } } } // Deactivation cleanup register_deactivation_hook(__FILE__, function() { wp_clear_scheduled_hook('airsrefe_auto_parse_event'); if (function_exists('airsrefe_append_log')) { airsrefe_append_log(__('⛔ Plugin deactivated. All scheduled tasks cleared', 'ai-rss-rewriter')); } }); // Uninstall function is defined in main plugin file to avoid duplication /** * AJAX handler for auto-fixing image source compatibility */ function airsrefe_ajax_auto_fix_image_source() { // Check nonce for security if (!check_ajax_referer('airsrefe_ajax', 'nonce', false)) { wp_send_json_error(['message' => __('Security check failed', 'ai-rss-rewriter')]); return; } // Check user capabilities if (!current_user_can('manage_options')) { wp_send_json_error(['message' => __('Insufficient permissions', 'ai-rss-rewriter')]); return; } $processing_mode = sanitize_text_field(wp_unslash($_POST['processing_mode'] ?? '')); if ($processing_mode === 'simple') { // Force image source to RSS Only in Simple mode update_option('airsrefe_image_source', 'rss'); // Log the automatic change if (function_exists('airsrefe_append_log')) { airsrefe_append_log('Image Source automatically changed to RSS Only via AJAX for Simple Processing Mode'); } wp_send_json_success([ 'message' => __('Image Source has been automatically changed to "RSS Only" for Simple Processing Mode.', 'ai-rss-rewriter') ]); } else { wp_send_json_error(['message' => 'Invalid processing mode']); } } /** * AJAX handler for bulk image optimization */ function airsrefe_ajax_bulk_optimize() { if (!check_ajax_referer('airsrefe_ajax', 'nonce', false)) { wp_send_json_error(['message' => __('Security check failed', 'ai-rss-rewriter')]); return; } if (!current_user_can('manage_options')) { wp_send_json_error(['message' => __('Insufficient permissions', 'ai-rss-rewriter')]); return; } $limit = absint($_POST['limit'] ?? 10); $limit = max(1, min(50, $limit)); // Limit between 1-50 try { // Get posts with AI RSS Rewriter images that haven't been optimized // Use a more efficient approach with separate queries $cache_key = 'airsrefe_unoptimized_posts_' . $limit; $posts = wp_cache_get($cache_key); if ($posts === false) { // Use a more efficient approach without meta_key $recent_posts = get_posts([ 'post_type' => 'post', 'posts_per_page' => $limit * 3, // 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 processed posts that haven't been optimized $unoptimized_posts = []; foreach ($recent_posts as $post_id) { $processed_date = get_post_meta($post_id, '_airsrefe_processed_date', true); if (!empty($processed_date)) { $optimized = get_post_meta($post_id, '_airsrefe_image_optimized', true); if (!$optimized) { $unoptimized_posts[] = get_post($post_id); if (count($unoptimized_posts) >= $limit) { break; } } } } $posts = $unoptimized_posts; wp_cache_set($cache_key, $posts, '', 300); // Cache for 5 minutes } if (empty($posts)) { wp_send_json_success([ 'message' => __('No images found that need optimization.', 'ai-rss-rewriter') ]); return; } $optimized_count = 0; $error_count = 0; foreach ($posts as $post) { $featured_image_id = get_post_thumbnail_id($post->ID); if ($featured_image_id) { // Check if image was created by AI RSS Rewriter $image_source = get_post_meta($featured_image_id, '_airsrefe_image_source', true); if ($image_source === 'ai' || $image_source === 'rss') { // Optimize the image if (function_exists('airsrefe_optimize_image')) { $optimization_result = airsrefe_optimize_image($featured_image_id); if ($optimization_result) { $optimized_count++; // Mark as optimized update_post_meta($post->ID, '_airsrefe_image_optimized', current_time('mysql')); airsrefe_append_log("✅ Optimized image for post {$post->ID}"); } else { $error_count++; airsrefe_append_log("❌ Failed to optimize image for post {$post->ID}"); } } else { $error_count++; airsrefe_append_log("❌ airsrefe_optimize_image function not found"); } } } } // translators: %1$d is the number of images optimized, %2$d is the number of errors $message = sprintf( // translators: %1$d is the number of images optimized, %2$d is the number of errors __('Optimization completed: %1$d images optimized, %2$d errors.', 'ai-rss-rewriter'), $optimized_count, $error_count ); wp_send_json_success(['message' => $message]); } catch (Exception $e) { airsrefe_append_log("❌ Error in bulk optimization: " . $e->getMessage()); wp_send_json_error(['message' => 'Optimization failed: ' . $e->getMessage()]); } } /** * AJAX handler for resetting API key * * @since 1.0.0 * @return void */ function airsrefe_ajax_reset_api_key() { if (!current_user_can('manage_options')) { wp_send_json_error('Insufficient permissions'); return; } if (!check_ajax_referer('airsrefe_ajax', 'nonce', false)) { wp_send_json_error('Invalid nonce'); return; } $result = airsrefe_reset_api_key(); if ($result) { wp_send_json_success([ 'message' => 'API key has been reset. Please enter a new API key in the settings.' ]); } else { wp_send_json_error('Failed to reset API key'); } }