900, 'display' => __( 'Every 15 minutes', 'accessibility-audio-tts-text-to-speech-for-articles' ), ); // Dynamic interval based on user performance settings. $processing_interval = (int) get_option( 'accessibility_audio_tts_processing_interval', 15 ); // Enforce >= 15. if ( $processing_interval < 15 ) { $processing_interval = 15; // Yoda style ensured below where used. } $interval_seconds = $processing_interval * 60; // Convert minutes to seconds. $schedules['accessibility_audio_tts_custom_interval'] = array( 'interval' => $interval_seconds, /* translators: auto */ 'display' => sprintf( __( 'Every %d minutes (user settings)', 'accessibility-audio-tts-text-to-speech-for-articles' ), $processing_interval ), ); return $schedules; } /** * Register cron hooks. * * @return void */ public function register_cron_hooks() { add_action( 'accessibility_audio_tts_process_background_posts', array( $this, 'process_background_posts' ), 10, 1 ); add_action( 'accessibility_audio_tts_auto_trigger_cron', array( $this, 'execute_auto_trigger' ) ); add_action( 'publish_post', array( $this, 'auto_schedule_post_conversion' ), 10, 2 ); add_action( 'publish_to_publish', array( $this, 'auto_schedule_post_conversion' ), 10, 2 ); add_action( 'accessibility_audio_tts_process_queue', array( $this, 'process_auto_queue' ) ); add_action( 'accessibility_audio_tts_security_cleanup', array( $this, 'cleanup_security_data' ) ); add_filter( 'cron_schedules', array( $this, 'add_cron_intervals' ) ); } /** * Process background posts. * * @param array $args Arguments including optional 'post_ids'. * @return void */ public function process_background_posts( $args ) { accessibility_audio_tts_debug_log( 'Admin_Cron_Manager: Background posts processing triggered' ); try { // Extract arguments if provided. $post_ids = isset( $args['post_ids'] ) ? $args['post_ids'] : array(); if ( empty( $post_ids ) ) { accessibility_audio_tts_debug_log( 'Admin_Cron_Manager: No specific posts provided, using queue' ); // Use the queue processing method. $this->process_auto_queue(); return; } // Process specific posts. $conversion_processor = new Conversion_Processor(); $processed_count = 0; foreach ( $post_ids as $post_id ) { accessibility_audio_tts_debug_log( 'Admin_Cron_Manager: Processing background post ID: ' . $post_id ); // Check if post can be converted. $can_convert = $conversion_processor->can_convert_post( $post_id ); if ( ! $can_convert['can_convert'] ) { accessibility_audio_tts_debug_log( 'Admin_Cron_Manager: Post cannot be converted: ' . $can_convert['reason'] ); continue; } // Convert post. $result = $conversion_processor->convert_single_post( $post_id ); if ( $result['success'] ) { accessibility_audio_tts_debug_log( 'Admin_Cron_Manager: Successfully converted post ' . $post_id ); ++$processed_count; } else { accessibility_audio_tts_debug_log( 'Admin_Cron_Manager: Failed to convert post ' . $post_id . ': ' . $result['error'] ); } // Add delay to avoid API rate limits. sleep( 1 ); } accessibility_audio_tts_debug_log( 'Admin_Cron_Manager: Background processing completed. Processed ' . $processed_count . ' posts' ); } catch ( \Exception $e ) { accessibility_audio_tts_debug_log( 'Admin_Cron_Manager: Error in process_background_posts: ' . $e->getMessage() ); } } /** * Execute auto trigger. * * @return void */ public function execute_auto_trigger() { accessibility_audio_tts_debug_log( 'Admin_Cron_Manager: Auto trigger executed' ); // Implementation for auto trigger. } /** * Auto schedule post conversion when post is published. * * @param int $post_id Post ID. * @param \WP_Post $post Post object. * @return void */ public function auto_schedule_post_conversion( $post_id, $post ) { accessibility_audio_tts_debug_log( 'Admin_Cron_Manager: Auto schedule post conversion called for post ' . $post_id ); try { // Check if auto conversion is enabled. if ( ! get_option( 'accessibility_audio_tts_auto_conversion', false ) ) { accessibility_audio_tts_debug_log( 'Admin_Cron_Manager: Auto conversion is disabled' ); return; } // Check if it's a post object. if ( ! $post || ! is_object( $post ) ) { $post = get_post( $post_id ); } if ( ! $post ) { accessibility_audio_tts_debug_log( 'Admin_Cron_Manager: Invalid post' ); return; } // Check if post type is enabled. $enabled_post_types = get_option( 'accessibility_audio_tts_post_types', array( 'post' ) ); if ( ! in_array( $post->post_type, $enabled_post_types, true ) ) { accessibility_audio_tts_debug_log( 'Admin_Cron_Manager: Post type not enabled: ' . $post->post_type ); return; } // Check if post is published. if ( 'publish' !== $post->post_status ) { accessibility_audio_tts_debug_log( 'Admin_Cron_Manager: Post is not published: ' . $post->post_status ); return; } // Check if post already has audio. if ( get_post_meta( $post_id, 'accessibility_audio_tts_generated', true ) ) { accessibility_audio_tts_debug_log( 'Admin_Cron_Manager: Post already has audio' ); return; } // Add to queue. $queue_manager = new Admin_Queue_Manager(); $queue_manager->add_to_queue( $post_id ); accessibility_audio_tts_debug_log( 'Admin_Cron_Manager: Post ' . $post_id . ' added to conversion queue' ); // Trigger immediate processing if in instant mode. $processing_mode = get_option( 'accessibility_audio_tts_processing_mode', 'wp_cron' ); if ( 'instant' === $processing_mode ) { accessibility_audio_tts_debug_log( 'Admin_Cron_Manager: Triggering instant conversion' ); $conversion_processor = new Conversion_Processor(); $result = $conversion_processor->convert_single_post( $post_id ); if ( $result['success'] ) { accessibility_audio_tts_debug_log( 'Admin_Cron_Manager: Instant conversion successful' ); $queue_manager->remove_from_queue( $post_id ); } else { accessibility_audio_tts_debug_log( 'Admin_Cron_Manager: Instant conversion failed: ' . $result['error'] ); } } } catch ( \Exception $e ) { accessibility_audio_tts_debug_log( 'Admin_Cron_Manager: Error in auto_schedule_post_conversion: ' . $e->getMessage() ); } } /** * Process auto queue. * * @return void */ public function process_auto_queue() { accessibility_audio_tts_debug_log( 'Admin_Cron_Manager: Process auto queue called' ); try { // Check if auto conversion is enabled. if ( ! get_option( 'accessibility_audio_tts_auto_conversion', false ) ) { accessibility_audio_tts_debug_log( 'Admin_Cron_Manager: Auto conversion is disabled' ); return; } // Check processing mode. $processing_mode = get_option( 'accessibility_audio_tts_processing_mode', 'wp_cron' ); if ( 'wp_cron' !== $processing_mode ) { accessibility_audio_tts_debug_log( 'Admin_Cron_Manager: Processing mode is not wp_cron: ' . $processing_mode ); return; } // Initialize queue manager. $queue_manager = new Admin_Queue_Manager(); // Auto-discover pending posts if queue is empty. if ( 0 === $queue_manager->get_queue_size() ) { accessibility_audio_tts_debug_log( 'Admin_Cron_Manager: Queue is empty, auto-discovering posts' ); $queue_manager->auto_discover_pending_posts(); } // Get posts to process from performance settings. $posts_per_session = (int) get_option( 'accessibility_audio_tts_posts_per_session', 1 ); $posts_per_hour = (int) get_option( 'accessibility_audio_tts_posts_per_hour', 6 ); $processed_count = 0; // Check hourly rate limit. $hourly_processed = $this->get_posts_processed_in_last_hour(); if ( $hourly_processed >= $posts_per_hour ) { accessibility_audio_tts_debug_log( "Admin_Cron_Manager: Hourly limit reached ({$hourly_processed}/{$posts_per_hour}). Skipping processing." ); return; } // Adjust session limit based on remaining hourly capacity. $remaining_capacity = $posts_per_hour - $hourly_processed; $posts_per_session = min( $posts_per_session, $remaining_capacity ); accessibility_audio_tts_debug_log( "Admin_Cron_Manager: Processing up to {$posts_per_session} posts (hourly: {$hourly_processed}/{$posts_per_hour})" ); // Initialize conversion processor. $conversion_processor = new Conversion_Processor(); while ( $processed_count < $posts_per_session ) { $post_id = $queue_manager->process_next_and_remove(); if ( ! $post_id ) { accessibility_audio_tts_debug_log( 'Admin_Cron_Manager: No more posts in queue' ); break; } accessibility_audio_tts_debug_log( 'Admin_Cron_Manager: Processing post ID: ' . $post_id ); // Check if post can be converted. $can_convert = $conversion_processor->can_convert_post( $post_id ); if ( ! $can_convert['can_convert'] ) { accessibility_audio_tts_debug_log( 'Admin_Cron_Manager: Post cannot be converted: ' . $can_convert['reason'] ); continue; } // Convert post. $result = $conversion_processor->convert_single_post( $post_id ); if ( $result['success'] ) { accessibility_audio_tts_debug_log( 'Admin_Cron_Manager: Successfully converted post ' . $post_id ); ++$processed_count; $this->log_processed_post( $post_id ); } else { accessibility_audio_tts_debug_log( 'Admin_Cron_Manager: Failed to convert post ' . $post_id . ': ' . $result['error'] ); } // Add small delay to avoid API rate limits. if ( $processed_count < $posts_per_session ) { sleep( 1 ); } } accessibility_audio_tts_debug_log( 'Admin_Cron_Manager: Processed ' . $processed_count . ' posts' ); // Schedule next run if there are more posts. if ( $queue_manager->get_queue_size() > 0 ) { accessibility_audio_tts_debug_log( 'Admin_Cron_Manager: ' . $queue_manager->get_queue_size() . ' posts remaining in queue' ); } } catch ( \Exception $e ) { accessibility_audio_tts_debug_log( 'Admin_Cron_Manager: Error in process_auto_queue: ' . $e->getMessage() ); } } /** * Clear all scheduled cron jobs. * * @return void */ public function clear_all_cron_jobs() { wp_clear_scheduled_hook( 'accessibility_audio_tts_process_queue' ); wp_clear_scheduled_hook( 'accessibility_audio_tts_process_background_posts' ); wp_clear_scheduled_hook( 'accessibility_audio_tts_auto_trigger_cron' ); wp_clear_scheduled_hook( 'accessibility_audio_tts_security_cleanup' ); } /** * Get cron job status. * * @return array Status data. */ public function get_cron_status() { return array( 'process_queue_scheduled' => wp_next_scheduled( 'accessibility_audio_tts_process_queue' ), 'auto_conversion_enabled' => get_option( 'accessibility_audio_tts_auto_conversion', false ), ); } /** * Handle getting WP-Cron status - delegates to Cron_Ajax_Handler. * * @return void */ public function handle_get_cron_status() { $cron_ajax_handler = new \Accessibility_Audio_TTS\Handlers\Cron_Ajax_Handler(); $cron_ajax_handler->handle_get_cron_status(); } /** * Handle manual cron trigger - delegates to Cron_Ajax_Handler. * * @return void */ public function handle_trigger_cron_manually() { $cron_ajax_handler = new \Accessibility_Audio_TTS\Handlers\Cron_Ajax_Handler(); $cron_ajax_handler->handle_trigger_cron_manually(); } /** * Cleanup security data - called by daily cron. * * @return void */ public function cleanup_security_data() { accessibility_audio_tts_debug_log( 'Admin_Cron_Manager: Security cleanup started' ); accessibility_audio_tts_debug_log( 'Admin_Cron_Manager: Security cleanup completed' ); } /** * Log processed post for rate limiting. * * @param int $post_id Post ID that was processed. * @return void */ private function log_processed_post( $post_id ) { $processed_log = get_option( 'accessibility_audio_tts_processed_log', array() ); $current_time = time(); // Add current processing. $processed_log[] = array( 'post_id' => $post_id, 'timestamp' => $current_time, ); // Clean old entries (older than 1 hour). $one_hour_ago = $current_time - 3600; $processed_log = array_filter( $processed_log, function ( $entry ) use ( $one_hour_ago ) { return isset( $entry['timestamp'] ) && $entry['timestamp'] > $one_hour_ago; } ); // Save updated log. update_option( 'accessibility_audio_tts_processed_log', $processed_log ); accessibility_audio_tts_debug_log( "Admin_Cron_Manager: Logged processed post {$post_id}. Total in last hour: " . count( $processed_log ) ); } /** * Get number of posts processed in the last hour. * * @return int Number of posts processed. */ private function get_posts_processed_in_last_hour() { $processed_log = get_option( 'accessibility_audio_tts_processed_log', array() ); $one_hour_ago = time() - 3600; // Filter entries from last hour. $recent_entries = array_filter( $processed_log, function ( $entry ) use ( $one_hour_ago ) { return isset( $entry['timestamp'] ) && $entry['timestamp'] > $one_hour_ago; } ); return count( $recent_entries ); } }