notice, and fall through to render — matching // the existing Email Queue tab pattern. Actions are idempotent // enough (re-running a cron just fires it again; re-deleting is a // silent no-op) that a browser refresh re-submitting is safe. // ----------------------------------------------------------------------- if ( 'POST' === ( $_SERVER['REQUEST_METHOD'] ?? '' ) && isset( $_POST['wpforo_cron_action'] ) && wp_verify_nonce( wpfval( $_POST, '_wpnonce' ), $wpf_cron_nonce_action ) ) { $action = sanitize_text_field( wp_unslash( $_POST['wpforo_cron_action'] ) ); $timestamp = absint( wpfval( $_POST, 'timestamp' ) ); $hook = sanitize_text_field( wpfval( $_POST, 'hook' ) ); $md5 = sanitize_text_field( wpfval( $_POST, 'md5' ) ); $events = function_exists( '_get_cron_array' ) ? _get_cron_array() : []; if ( ! is_array( $events ) ) $events = []; switch ( $action ) { case 'run': if ( $timestamp && $hook && $md5 && isset( $events[ $timestamp ][ $hook ][ $md5 ] ) ) { $entry = $events[ $timestamp ][ $hook ][ $md5 ]; $args = isset( $entry['args'] ) ? (array) $entry['args'] : []; $schedule = $entry['schedule'] ?? false; // For single events: unschedule before firing so the queued one // doesn't run a second time. For recurring events: leave the // schedule alone — the regular cadence resumes after we fire. if ( false === $schedule ) { wp_unschedule_event( $timestamp, $hook, $args ); } try { do_action_ref_array( $hook, $args ); WPF()->notice->add( sprintf( /* translators: %s = cron hook name */ __( 'Cron event "%s" executed.', 'wpforo' ), $hook ), 'success' ); } catch ( \Throwable $e ) { WPF()->notice->add( sprintf( /* translators: 1: hook name, 2: error message */ __( 'Cron event "%1$s" raised an exception: %2$s', 'wpforo' ), $hook, $e->getMessage() ), 'error' ); } } else { WPF()->notice->add( __( 'Cron event no longer exists (likely already executed or rescheduled).', 'wpforo' ), 'warning' ); } break; case 'delete': if ( $timestamp && $hook && $md5 && isset( $events[ $timestamp ][ $hook ][ $md5 ] ) ) { $args = (array) ( $events[ $timestamp ][ $hook ][ $md5 ]['args'] ?? [] ); $schedule = $events[ $timestamp ][ $hook ][ $md5 ]['schedule'] ?? false; wp_unschedule_event( $timestamp, $hook, $args ); // Recurring event: also clear any future occurrences with the // same args so the row truly goes away from the listing. if ( false !== $schedule ) { wp_clear_scheduled_hook( $hook, $args ); } WPF()->notice->add( sprintf( /* translators: %s = cron hook name */ __( 'Cron event "%s" deleted.', 'wpforo' ), $hook ), 'success' ); } break; case 'run_all_wpforo': $ran = 0; foreach ( $events as $ts => $hooks ) { if ( ! is_array( $hooks ) ) continue; foreach ( $hooks as $h => $items ) { if ( strpos( $h, 'wpforo_' ) !== 0 ) continue; if ( ! is_array( $items ) ) continue; foreach ( $items as $item ) { $args = isset( $item['args'] ) ? (array) $item['args'] : []; $schedule = $item['schedule'] ?? false; if ( false === $schedule ) { wp_unschedule_event( $ts, $h, $args ); } try { do_action_ref_array( $h, $args ); $ran++; } catch ( \Throwable $e ) { WPF()->notice->add( sprintf( /* translators: 1: hook name, 2: error message */ __( 'Cron event "%1$s" raised an exception: %2$s', 'wpforo' ), $h, $e->getMessage() ), 'error' ); } } } } WPF()->notice->add( sprintf( /* translators: %d = number of cron events executed */ _n( '%d wpForo cron event executed.', '%d wpForo cron events executed.', $ran, 'wpforo' ), $ran ), 'success' ); break; } } // ----------------------------------------------------------------------- // Load + normalize + sort cron events // ----------------------------------------------------------------------- $cron_array = function_exists( '_get_cron_array' ) ? _get_cron_array() : []; if ( ! is_array( $cron_array ) ) $cron_array = []; $rows = []; $wpforo_count = 0; foreach ( $cron_array as $timestamp => $hooks ) { if ( ! is_array( $hooks ) ) continue; foreach ( $hooks as $hook => $items ) { if ( ! is_array( $items ) ) continue; foreach ( $items as $md5 => $entry ) { $is_wpforo = ( strpos( $hook, 'wpforo_' ) === 0 ); if ( $is_wpforo ) $wpforo_count++; $rows[] = [ 'timestamp' => (int) $timestamp, 'hook' => (string) $hook, 'md5' => (string) $md5, 'args' => isset( $entry['args'] ) ? (array) $entry['args'] : [], 'schedule' => $entry['schedule'] ?? false, 'interval' => isset( $entry['interval'] ) ? (int) $entry['interval'] : 0, 'is_wpforo' => $is_wpforo, ]; } } } // Sort: wpForo first (alphabetical by hook then by timestamp), then everything // else by next-run time. usort( $rows, function ( $a, $b ) { if ( $a['is_wpforo'] !== $b['is_wpforo'] ) return $a['is_wpforo'] ? -1 : 1; if ( $a['is_wpforo'] ) { $h = strcmp( $a['hook'], $b['hook'] ); if ( $h !== 0 ) return $h; return $a['timestamp'] <=> $b['timestamp']; } return $a['timestamp'] <=> $b['timestamp']; } ); $now = time(); $total_count = count( $rows ); $cron_disabled = defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON; $cron_alt = defined( 'ALTERNATE_WP_CRON' ) && ALTERNATE_WP_CRON; $schedules = function_exists( 'wp_get_schedules' ) ? wp_get_schedules() : []; // Pretty-print interval helper $wpf_fmt_interval = function ( $seconds ) { $seconds = (int) $seconds; if ( $seconds <= 0 ) return '—'; if ( $seconds < HOUR_IN_SECONDS ) { $n = max( 1, (int) round( $seconds / MINUTE_IN_SECONDS ) ); /* translators: %d is the number of minutes */ return sprintf( _n( 'every %d minute', 'every %d minutes', $n, 'wpforo' ), $n ); } if ( $seconds < DAY_IN_SECONDS ) { $n = max( 1, (int) round( $seconds / HOUR_IN_SECONDS ) ); /* translators: %d is the number of hours */ return sprintf( _n( 'every %d hour', 'every %d hours', $n, 'wpforo' ), $n ); } if ( $seconds < WEEK_IN_SECONDS ) { $n = max( 1, (int) round( $seconds / DAY_IN_SECONDS ) ); /* translators: %d is the number of days */ return sprintf( _n( 'every %d day', 'every %d days', $n, 'wpforo' ), $n ); } $n = max( 1, (int) round( $seconds / WEEK_IN_SECONDS ) ); /* translators: %d is the number of weeks */ return sprintf( _n( 'every %d week', 'every %d weeks', $n, 'wpforo' ), $n ); }; // Pretty-print relative time helper $wpf_fmt_when = function ( $ts ) use ( $now ) { $diff = $ts - $now; if ( $diff < 0 ) return '' . esc_html__( 'past due', 'wpforo' ) . ''; if ( $diff < MINUTE_IN_SECONDS ) return esc_html__( 'in <1 min', 'wpforo' ); if ( $diff < HOUR_IN_SECONDS ) { $n = (int) round( $diff / MINUTE_IN_SECONDS ); /* translators: %d is the number of minutes — matches wpforo_ai_format_next_run_time() in ai-features-helpers.php */ return esc_html( sprintf( _n( 'in %d min', 'in %d mins', $n, 'wpforo' ), $n ) ); } if ( $diff < DAY_IN_SECONDS ) { $n = (int) round( $diff / HOUR_IN_SECONDS ); /* translators: %d is the number of hours */ return esc_html( sprintf( _n( 'in %d hour', 'in %d hours', $n, 'wpforo' ), $n ) ); } $n = (int) round( $diff / DAY_IN_SECONDS ); /* translators: %d is the number of days */ return esc_html( sprintf( _n( 'in %d day', 'in %d days', $n, 'wpforo' ), $n ) ); }; ?>
notice->show() before this tab was // included, so anything we added since needs an explicit second flush // here to surface on the current request. WPF()->notice->show(); ?>

DISABLE_WP_CRON is set. Events stay scheduled but will not fire automatically — you need to trigger wp-cron.php via a system cron or call it manually.', 'wpforo' ); ?>
ALTERNATE_WP_CRON is set; events fire via redirect rather than WordPress\'s normal background spawn.', 'wpforo' ); ?>
%d total scheduled events', 'wpforo' ), $total_count ); ?>
%d from wpForo', 'wpforo' ), $wpforo_count ); ?>
0 ) : ?>
0 ? $wpf_fmt_interval( $row['interval'] ) : '—'; $next_abs = wp_date( 'Y-m-d H:i:s', $row['timestamp'] ); $row_class = $row['is_wpforo'] ? 'wpforo-row' : ''; ?>
wpForo
$row['hook'], 'source' => $row['is_wpforo'] ? 'wpForo' : __( 'Other / WordPress core', 'wpforo' ), 'timestamp' => (int) $row['timestamp'], 'next_local' => wp_date( 'Y-m-d H:i:s T', $row['timestamp'] ), 'next_gmt' => gmdate( 'Y-m-d H:i:s', $row['timestamp'] ) . ' GMT', 'next_rel' => wp_strip_all_tags( $wpf_fmt_when( $row['timestamp'] ) ), // Schedule: raw key + human-readable display (when registered) 'schedule' => $row['schedule'] ? $row['schedule'] : 'single_event', 'schedule_lbl' => $has_display ? $schedules[ $row['schedule'] ]['display'] : '', 'interval' => $row['interval'], 'interval_lbl' => $interval_lbl, 'args' => $details_args_pretty, 'md5' => $row['md5'], ]; ?>
');">