Last active
December 5, 2025 19:06
-
-
Save mwender/690d9a6ccc44b696b5dac0203b750b89 to your computer and use it in GitHub Desktop.
[Publish Missed Scheduled Posts - WP-CLI Command] WP-CLI command for publishing missed scheduled posts #wordpress #wpcli
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| /** | |
| * Enhanced "missed schedule" WP-CLI command. Add this to /mu-plugins/ and run it via a real cron job. | |
| * | |
| * Usage: | |
| * wp missed-schedule fix | |
| * wp missed-schedule fix --quiet | |
| * wp missed-schedule fix --log | |
| */ | |
| if ( defined( 'WP_CLI' ) && WP_CLI ) { | |
| class Missed_Schedule_Command { | |
| /** | |
| * Publishes missed scheduled posts. | |
| * | |
| * ## OPTIONS | |
| * | |
| * [--quiet] | |
| * : Suppress all normal output. Only errors print. | |
| * | |
| * [--log] | |
| * : Log activity to missed-posts.log (stored in same directory as debug.log). | |
| * | |
| * ## EXAMPLES | |
| * | |
| * wp missed-schedule fix | |
| * wp missed-schedule fix --quiet | |
| * wp missed-schedule fix --log | |
| * | |
| * @when after_wp_load | |
| */ | |
| public function fix( $args, $assoc_args ) { | |
| global $wpdb; | |
| $quiet = isset( $assoc_args['quiet'] ); | |
| $log = isset( $assoc_args['log'] ); | |
| // If logging, determine log file location dynamically. | |
| $log_file = null; | |
| if ( $log ) { | |
| $log_file = $this->resolve_log_path(); | |
| $this->maybe_rotate_log( $log_file ); | |
| } | |
| // Query for missed posts | |
| $post_ids = $wpdb->get_col(" | |
| SELECT ID FROM {$wpdb->posts} | |
| WHERE post_status = 'future' | |
| AND post_date <= NOW() | |
| "); | |
| if ( empty( $post_ids ) ) { | |
| if ( ! $quiet ) { | |
| WP_CLI::success( "No missed-schedule posts found." ); | |
| } | |
| if ( $log ) { | |
| $this->write_log( $log_file, "No missed-schedule posts found." ); | |
| } | |
| return; | |
| } | |
| foreach ( $post_ids as $post_id ) { | |
| $post = get_post( $post_id ); | |
| if ( ! $post ) { | |
| if ( ! $quiet ) { | |
| WP_CLI::warning( "Post $post_id not found." ); | |
| } | |
| if ( $log ) { | |
| $this->write_log( $log_file, "Post $post_id not found." ); | |
| } | |
| continue; | |
| } | |
| wp_update_post([ | |
| 'ID' => $post_id, | |
| 'post_status' => 'publish', | |
| ]); | |
| $message = "Published {$post->ID} — {$post->post_title}"; | |
| if ( ! $quiet ) { | |
| WP_CLI::log( $message ); | |
| } | |
| if ( $log ) { | |
| $this->write_log( $log_file, $message ); | |
| } | |
| } | |
| if ( ! $quiet ) { | |
| WP_CLI::success( "Finished publishing missed posts." ); | |
| } | |
| if ( $log ) { | |
| $this->write_log( $log_file, "Finished publishing missed posts." ); | |
| } | |
| } | |
| /** | |
| * Dynamically finds the directory where debug.log is stored, then | |
| * returns a path to missed-posts.log in the same directory. | |
| */ | |
| private function resolve_log_path() { | |
| $debug_log = ini_get( 'error_log' ); | |
| if ( ! $debug_log || ! is_string( $debug_log ) ) { | |
| WP_CLI::warning( "Could not determine error_log path—defaulting to WP_CONTENT_DIR." ); | |
| return WP_CONTENT_DIR . '/missed-posts.log'; | |
| } | |
| $dir = dirname( $debug_log ); | |
| return $dir . '/missed-posts.log'; | |
| } | |
| /** | |
| * Writes a timestamped entry into the log file. | |
| */ | |
| private function write_log( $file, $message ) { | |
| $entry = "[" . date( "Y-m-d H:i:s" ) . "] " . $message . PHP_EOL; | |
| file_put_contents( $file, $entry, FILE_APPEND ); | |
| } | |
| /** | |
| * Rotates the log file so it only retains entries from the past 7 days. | |
| */ | |
| private function maybe_rotate_log( $file ) { | |
| if ( ! file_exists( $file ) ) { | |
| return; // Nothing to rotate. | |
| } | |
| $lines = file( $file, FILE_IGNORE_NEW_LINES ); | |
| $cutoff = strtotime( "-7 days" ); | |
| $filtered = []; | |
| foreach ( $lines as $line ) { | |
| if ( preg_match( '/^\[(.*?)\]/', $line, $match ) ) { | |
| $timestamp = strtotime( $match[1] ); | |
| if ( $timestamp !== false && $timestamp >= $cutoff ) { | |
| $filtered[] = $line; | |
| } | |
| } | |
| } | |
| file_put_contents( $file, implode( PHP_EOL, $filtered ) . PHP_EOL ); | |
| } | |
| } | |
| WP_CLI::add_command( 'missed-schedule', 'Missed_Schedule_Command' ); | |
| } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
👆I use this on a high-traffic WordPress site that receives 75,000+ page views per day. It's a known bug with WordPress that it sometimes misses Scheduled Posts. So this gives us
wp missed-schedule fixto fix that problem. Trigger it via a real cron job every couple of hours to ensure all your scheduled posts get published.