array( $this, 'execute_jobs_directly' ), 'loopback_request' => array( $this, 'execute_loopback_request' ), 'spawn_cron' => array( $this, 'execute_spawn_cron' ), ); foreach ( $strategies as $strategy_name => $strategy_callback ) { accessibility_audio_tts_debug_log( "Cron Manual Trigger: Trying strategy: {$strategy_name}" ); $result = call_user_func( $strategy_callback ); if ( false !== $result && ( ! is_array( $result ) || false !== $result['success'] ) ) { accessibility_audio_tts_debug_log( "Cron Manual Trigger: Strategy {$strategy_name} successful" ); return array( 'success' => true, 'message' => __( 'CRON tasks were executed manually', 'accessibility-audio-tts-text-to-speech-for-articles' ) . " ({$strategy_name})", 'timestamp' => current_time( 'mysql' ), 'strategy' => $strategy_name, ); } accessibility_audio_tts_debug_log( "Cron Manual Trigger: Strategy {$strategy_name} failed" ); } accessibility_audio_tts_debug_log( 'Cron Manual Trigger: All strategies failed' ); return array( 'success' => false, 'message' => __( 'Failed to execute CRON tasks - all methods failed', 'accessibility-audio-tts-text-to-speech-for-articles' ), 'timestamp' => current_time( 'mysql' ), ); } /** * Force execute next scheduled cron job. * * @return array Execution result. */ public function execute_next_scheduled_job() { $cron_array = $this->get_cron_array(); if ( empty( $cron_array ) ) { return array( 'success' => false, 'message' => 'No scheduled jobs found', ); } // Get the earliest scheduled job. $next_timestamp = min( array_keys( $cron_array ) ); $next_jobs = $cron_array[ $next_timestamp ]; if ( empty( $next_jobs ) ) { return array( 'success' => false, 'message' => 'No jobs at next timestamp', ); } // Execute the first job. $hook = array_keys( $next_jobs )[0]; $jobs = $next_jobs[ $hook ]; $first_job = reset( $jobs ); accessibility_audio_tts_debug_log( "Executing cron job manually: {$hook}" ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound -- Executing scheduled WordPress cron jobs by design. do_action_ref_array( $hook, isset( $first_job['args'] ) ? $first_job['args'] : array() ); return array( 'success' => true, 'message' => "Executed job: {$hook}", 'timestamp' => gmdate( 'Y-m-d H:i:s', $next_timestamp ), 'hook' => $hook, ); } /** * Execute AAT CRON jobs directly. * * @return bool|array True on success, false on failure. */ private function execute_jobs_directly() { accessibility_audio_tts_debug_log( 'Cron Manual Trigger: Executing AAT jobs directly' ); try { // Get next scheduled AAT jobs. $aat_hooks = array( 'accessibility_audio_tts_process_queue', 'accessibility_audio_tts_process_background_posts' ); $executed_jobs = 0; foreach ( $aat_hooks as $hook ) { $next_run = wp_next_scheduled( $hook ); if ( $next_run ) { accessibility_audio_tts_debug_log( "Cron Manual Trigger: Executing hook: {$hook}" ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound -- Executing plugin-prefixed cron hooks by design. do_action( $hook ); ++$executed_jobs; } } if ( $executed_jobs > 0 ) { accessibility_audio_tts_debug_log( "Cron Manual Trigger: Executed {$executed_jobs} AAT jobs directly" ); return true; } accessibility_audio_tts_debug_log( 'Cron Manual Trigger: No AAT jobs to execute' ); return false; } catch ( \Exception $e ) { accessibility_audio_tts_debug_log( 'Cron Manual Trigger: Error executing AAT jobs: ' . $e->getMessage() ); return false; } } /** * Execute cron via loopback HTTP request to wp-cron.php. * * @return bool|array True on success, false on failure. */ private function execute_loopback_request() { accessibility_audio_tts_debug_log( 'Cron Manual Trigger: Executing via loopback request' ); try { $cron_url = site_url( 'wp-cron.php?doing_wp_cron=' . time() ); $response = wp_remote_get( $cron_url, array( 'timeout' => 30, 'blocking' => true, 'sslverify' => false, 'user-agent' => 'AAT-Manual-Cron/1.0', ) ); if ( is_wp_error( $response ) ) { accessibility_audio_tts_debug_log( 'Cron Manual Trigger: Loopback request failed: ' . $response->get_error_message() ); return false; } $response_code = wp_remote_retrieve_response_code( $response ); accessibility_audio_tts_debug_log( "Cron Manual Trigger: Loopback request response code: {$response_code}" ); if ( 200 === $response_code ) { accessibility_audio_tts_debug_log( 'Cron Manual Trigger: Loopback request successful' ); return true; } return false; } catch ( \Exception $e ) { accessibility_audio_tts_debug_log( 'Cron Manual Trigger: Error in loopback request: ' . $e->getMessage() ); return false; } } /** * Execute spawn_cron function safely. * * @return mixed Result of spawn_cron or false on failure. */ private function execute_spawn_cron() { if ( ! function_exists( 'spawn_cron' ) ) { accessibility_audio_tts_debug_log( 'Cron Manual Trigger: spawn_cron function not available' ); return false; } return spawn_cron(); } /** * Get cron array safely. * * @return array Cron array or empty array on failure. */ private function get_cron_array() { $cron_array = array(); if ( function_exists( '_get_cron_array' ) ) { $cron_array = _get_cron_array(); if ( ! is_array( $cron_array ) ) { $cron_array = array(); } } return $cron_array; } /** * Check if spawn_cron function is available. * * @return bool True if spawn_cron is available. */ public function is_spawn_cron_available() { return function_exists( 'spawn_cron' ); } /** * Get summary of next scheduled jobs. * * @param int $limit Number of jobs to return. * @return array Array of next scheduled jobs. */ public function get_next_jobs_summary( $limit = 5 ) { $cron_array = $this->get_cron_array(); $jobs_summary = array(); if ( empty( $cron_array ) ) { return $jobs_summary; } // Sort by timestamp and limit results. ksort( $cron_array ); $count = 0; foreach ( $cron_array as $timestamp => $jobs ) { if ( $count >= $limit ) { break; } foreach ( $jobs as $hook => $job_instances ) { if ( $count >= $limit ) { break; } $jobs_summary[] = array( 'hook' => $hook, 'timestamp' => $timestamp, 'formatted_time' => gmdate( 'Y-m-d H:i:s', $timestamp ), 'instances' => count( $job_instances ), ); ++$count; } } return $jobs_summary; } }