Verified Commit 5a2dceca authored by Erick Hitter's avatar Erick Hitter
Browse files

Introduce method to store cron events in a CPT instead of an option, to avoid race conditions

Still need to:
* deal with unscheduling events
* ensure all events are captured (Jetpack/VaultPress seem to be missing)
parent e291e10c
......@@ -23,13 +23,19 @@ class Cron_Options_CPT {
/**
* Class properties
*/
private $post_type = 'wpccr_events';
private $post_type = 'wpccr_events';
private $post_status = 'inherit';
/**
* Register hooks
*/
private function __construct() {
// Data storage
add_action( 'init', array( $this, 'register_post_type' ) );
// Option interception
add_filter( 'pre_option_cron', array( $this, 'get_option' ) );
add_filter( 'pre_update_option_cron', array( $this, 'update_option' ), 10, 2 );
}
/**
......@@ -48,6 +54,138 @@ class Cron_Options_CPT {
/**
* PLUGIN FUNCTIONALITY
*/
/**
* Override cron option requests with data from CPT
*/
public function get_option( $value ) {
$cron_array = array(
'version' => 2, // Core versions the cron array; without this, events will continually requeue
);
// Get events to re-render as the cron option
$jobs_posts = get_posts( array(
'post_type' => $this->post_type,
'post_status' => $this->post_status,
'suppress_filters' => false,
'posts_per_page' => 1000,
) );
// Loop through results and built output Core expects
if ( ! empty( $jobs_posts ) ) {
foreach ( $jobs_posts as $jobs_post ) {
$timestamp = strtotime( $jobs_post->post_date_gmt );
$job_args = maybe_unserialize( $jobs_post->post_content_filtered );
if ( ! is_array( $job_args ) ) {
continue;
}
$action = $job_args['action'];
$instance = $job_args['instance'];
$args = $job_args['args'];
$cron_array[ $timestamp ][ $action ][ $instance ] = array(
'schedule' => $args['schedule'],
'args' => $args['args'],
);
if ( isset( $args['interval'] ) ) {
$cron_array[ $timestamp ][ $action ][ $instance ]['interval'] = $args['interval'];
}
}
}
uksort( $cron_array, "strnatcasecmp" );
return $cron_array;
}
/**
* Save cron events in CPT
*
* By returning $old_value, `cron` option won't be updated
*/
public function update_option( $new_value, $old_value ) {
if ( is_array( $new_value ) && ! empty( $new_value ) ) {
foreach ( $new_value as $timestamp => $timestamp_events ) {
// Skip non-event data that Core includes in the option
if ( ! is_numeric( $timestamp ) ) {
continue;
}
foreach ( $timestamp_events as $action => $action_instances ) {
foreach ( $action_instances as $instance => $instance_args ) {
// There are some jobs we never care to run
if ( is_blocked_event( $action ) ) {
continue;
}
// Check if post exists and bail
$job_exists = get_posts( array(
'name' => sprintf( '%s-%s-%s', $timestamp, md5( $action ), $instance ),
'post_type' => $this->post_type,
'post_status' => $this->post_status,
'suppress_filters' => false,
'posts_per_page' => 1,
) );
// Create a post, if needed
if ( empty( $job_exists ) ) {
$job_args = array(
'action' => $action,
'instance' => $instance,
'args' => $instance_args,
);
wp_insert_post( array(
'post_title' => sprintf( '%s | %s | %s', $timestamp, $action, $instance ),
'post_name' => sprintf( '%s-%s-%s', $timestamp, md5( $action ), $instance ),
'post_content_filtered' => maybe_serialize( $job_args ),
'post_date' => date( 'Y-m-d H:i:s', $timestamp ),
'post_date_gmt' => date( 'Y-m-d H:i:s', $timestamp ),
'post_type' => $this->post_type,
'post_status' => $this->post_status,
) );
}
}
}
}
}
return $old_value;
}
/**
* PLUGIN UTILITY METHODS
*/
/**
* Remove an event's CPT entry
*
* @param $timestamp int Unix timestamp
* @param $action string name of action used when the event is registered (unhashed)
* @param $instance string md5 hash of the event's arguments array, which Core uses to index the `cron` option
*
* @return bool
*/
public function delete_event( $timestamp, $action, $instance ) {
$job_exists = get_posts( array(
'name' => sprintf( '%s-%s-%s', $timestamp, md5( $action ), $instance ),
'post_type' => $this->post_type,
'post_status' => $this->post_status,
'suppress_filters' => false,
'posts_per_page' => 1,
) );
if ( empty( $job_exists ) ) {
return false;
}
wp_delete_post( $job_exists[0]->ID, true );
return true;
}
}
Cron_Options_CPT::instance();
......@@ -8,3 +8,16 @@ namespace WP_Cron_Control_Revisited;
function get_plugin_var( $variable ) {
return property_exists( Main::instance(), $variable ) ? Main::instance()->$variable : null;
}
/**
* Delete an event
*
* @param $timestamp int Unix timestamp
* @param $action string name of action used when the event is registered (unhashed)
* @param $instance string md5 hash of the event's arguments array, which Core uses to index the `cron` option
*
* @return bool
*/
function delete_cron_event( $timestamp, $action, $instance ) {
return Cron_Options_CPT::instance()->delete_event( $timestamp, $action, $instance );
}
......@@ -248,6 +248,7 @@ class Main {
}
wp_unschedule_event( $event['timestamp'], $event['action'], $event['args'] );
delete_cron_event( $event['timestamp'], $event['action'], $event['instance'] );
// Run the event
do_action_ref_array( $event['action'], $event['args'] );
......@@ -280,6 +281,7 @@ class Main {
$event = $action_events[ $instance ];
$event['timestamp'] = $timestamp;
$event['action'] = $action;
$event['instance'] = $instance;
break;
}
}
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment