$this->slug(), 'risk' => 'none', 'summary' => 'Lists WP-Cron scheduled events with next-run times and an overdue count. ' . 'Call when scheduled posts, emails, digests, or backups seem stuck, or before and ' . 'after changing cron-heavy plugins. A large overdue count usually means WP-Cron is ' . 'not firing (low traffic or a disabled trigger). Read-only.', 'annotations' => array( 'readonly' => true, ), // Prefill for the admin Run panel (v1.4); agents may copy it too. 'example_input' => array( 'overdue_only' => true ), 'input_schema' => array( // 'null' tolerated: a parameterless REST GET run sends input=null (M6). 'type' => array( 'object', 'null' ), 'properties' => array( 'hook' => array( 'type' => 'string', 'description' => 'Only list events for this exact hook name.', ), 'overdue_only' => array( 'type' => 'boolean', 'default' => false, 'description' => 'Only list events whose scheduled time has already passed.', ), 'limit' => array( 'type' => 'integer', 'minimum' => 1, 'maximum' => 100, 'default' => self::DEFAULT_LIMIT, ), ), ), ); } public function dry_run( $atb_input ) { return $this->output( true, true, $this->gather( $atb_input ) ); } public function run( $atb_input ) { return $this->output( true, false, $this->gather( $atb_input ) ); } /** * @param array|null $atb_input * @return array details shape per v1.2 spec §3.4. */ protected function gather( $atb_input ) { $atb_hook = ( is_array( $atb_input ) && ! empty( $atb_input['hook'] ) ) ? (string) $atb_input['hook'] : ''; $atb_overdue_only = is_array( $atb_input ) && ! empty( $atb_input['overdue_only'] ); $atb_limit = ( is_array( $atb_input ) && isset( $atb_input['limit'] ) ) ? (int) $atb_input['limit'] : self::DEFAULT_LIMIT; $atb_limit = max( 1, min( 100, $atb_limit ) ); $atb_now = time(); $atb_crons = function_exists( '_get_cron_array' ) ? _get_cron_array() : array(); $atb_total = 0; $atb_overdue = 0; $atb_matched = array(); // The cron option is kept sorted by timestamp ascending (core relies // on that in wp_get_ready_cron_jobs()), so events come out soonest-first. foreach ( $atb_crons as $atb_timestamp => $atb_hooks ) { if ( ! is_array( $atb_hooks ) ) { continue; } foreach ( $atb_hooks as $atb_hook_name => $atb_events ) { if ( ! is_array( $atb_events ) ) { continue; } foreach ( $atb_events as $atb_event ) { $atb_total++; $atb_is_overdue = $atb_timestamp < $atb_now; if ( $atb_is_overdue ) { $atb_overdue++; } if ( '' !== $atb_hook && $atb_hook !== (string) $atb_hook_name ) { continue; } if ( $atb_overdue_only && ! $atb_is_overdue ) { continue; } $atb_args = ( is_array( $atb_event ) && isset( $atb_event['args'] ) && is_array( $atb_event['args'] ) ) ? $atb_event['args'] : array(); $atb_schedule = ( is_array( $atb_event ) && ! empty( $atb_event['schedule'] ) ) ? (string) $atb_event['schedule'] : null; $atb_matched[] = array( 'hook' => (string) $atb_hook_name, 'next_run_gmt' => gmdate( 'Y-m-d H:i:s', (int) $atb_timestamp ), 'in_s' => (int) $atb_timestamp - $atb_now, 'schedule' => $atb_schedule, 'interval_s' => ( is_array( $atb_event ) && isset( $atb_event['interval'] ) ) ? (int) $atb_event['interval'] : null, 'args_count' => count( $atb_args ), 'args_digest' => empty( $atb_args ) ? '' : substr( md5( wp_json_encode( $atb_args ) ), 0, 12 ), ); } } } $atb_truncated = count( $atb_matched ) > $atb_limit; return array( 'now_gmt' => gmdate( 'Y-m-d H:i:s', $atb_now ), 'cron_disabled' => defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON, 'alternate_cron' => defined( 'ALTERNATE_WP_CRON' ) && ALTERNATE_WP_CRON, 'events' => array_slice( $atb_matched, 0, $atb_limit ), 'total_events' => $atb_total, 'overdue_count' => $atb_overdue, 'truncated' => $atb_truncated, ); } } }