Commit 767ee6d4 authored by Erick Hitter's avatar Erick Hitter
Browse files

Fix `events` CLI commands

Considerable cleanup and simplification along the way
parent b635f8fb
...@@ -79,7 +79,7 @@ class Events_Store extends Singleton { ...@@ -79,7 +79,7 @@ class Events_Store extends Singleton {
`created` datetime NOT NULL, `created` datetime NOT NULL,
`last_modified` datetime NOT NULL, `last_modified` datetime NOT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`ID`),
UNIQUE KEY `ts_action_instance` (`timestamp`, `action`, `instance`) UNIQUE KEY `ts_action_instance` (`timestamp`, `action`, `instance`)
) ENGINE=InnoDB;\n"; ) ENGINE=InnoDB;\n";
...@@ -236,7 +236,10 @@ class Events_Store extends Singleton { ...@@ -236,7 +236,10 @@ class Events_Store extends Singleton {
*/ */
/** /**
* Retrieve list of jobs * Retrieve jobs given a set of parameters
*
* @param array $args
* @return array|false
*/ */
public function get_jobs( $args ) { public function get_jobs( $args ) {
global $wpdb; global $wpdb;
...@@ -439,10 +442,10 @@ class Events_Store extends Singleton { ...@@ -439,10 +442,10 @@ class Events_Store extends Singleton {
/** /**
* Set a job post to the "completed" status * Set a job post to the "completed" status
*/ */
private function mark_job_record_completed( $job_id, $flush_cache = true ) { public function mark_job_record_completed( $job_id, $flush_cache = true ) {
global $wpdb; global $wpdb;
$wpdb->update( $this->get_table_name(), array( 'status' => self::STATUS_COMPLETED, ), array( 'ID' => $job_id, ) ); $success = $wpdb->update( $this->get_table_name(), array( 'status' => self::STATUS_COMPLETED, ), array( 'ID' => $job_id, ) );
// Delete internal cache // Delete internal cache
// Should only be skipped when deleting duplicates, as they are excluded from the cache // Should only be skipped when deleting duplicates, as they are excluded from the cache
...@@ -450,7 +453,7 @@ class Events_Store extends Singleton { ...@@ -450,7 +453,7 @@ class Events_Store extends Singleton {
wp_cache_delete( self::CACHE_KEY ); wp_cache_delete( self::CACHE_KEY );
} }
return true; return (bool) $success;
} }
/** /**
...@@ -496,8 +499,12 @@ class Events_Store extends Singleton { ...@@ -496,8 +499,12 @@ class Events_Store extends Singleton {
/** /**
* Stop discarding events, once again storing them in the table * Stop discarding events, once again storing them in the table
*
* First clears any completed events to free unique timestamp-action-instance key
*/ */
public function resume_event_creation() { public function resume_event_creation() {
$this->purge_completed_events();
$this->job_creation_suspended = false; $this->job_creation_suspended = false;
} }
...@@ -509,6 +516,22 @@ class Events_Store extends Singleton { ...@@ -509,6 +516,22 @@ class Events_Store extends Singleton {
$wpdb->delete( $this->get_table_name(), array( 'status' => self::STATUS_COMPLETED, ) ); $wpdb->delete( $this->get_table_name(), array( 'status' => self::STATUS_COMPLETED, ) );
} }
/**
* Count number of events with a given status
*
* @param string $status
* @return int|false
*/
public function count_events_by_status( $status ) {
global $wpdb;
if ( ! in_array( $status, array( self::STATUS_PENDING, self::STATUS_RUNNING, self::STATUS_COMPLETED ), true ) ) {
return false;
}
return (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(action) FROM {$this->get_table_name()} WHERE status = %s", $status ) );
}
} }
Events_Store::instance(); Events_Store::instance();
...@@ -64,7 +64,7 @@ class REST_API extends Singleton { ...@@ -64,7 +64,7 @@ class REST_API extends Singleton {
$action = isset( $event['action'] ) ? trim( sanitize_text_field( $event['action'] ) ) : null; $action = isset( $event['action'] ) ? trim( sanitize_text_field( $event['action'] ) ) : null;
$instance = isset( $event['instance'] ) ? trim( sanitize_text_field( $event['instance'] ) ) : null; $instance = isset( $event['instance'] ) ? trim( sanitize_text_field( $event['instance'] ) ) : null;
return rest_ensure_response( Events::instance()->run_event( $timestamp, $action, $instance ) ); return rest_ensure_response( run_event( $timestamp, $action, $instance ) );
} }
/** /**
......
...@@ -40,15 +40,6 @@ function is_rest_endpoint_request( $type = 'list' ) { ...@@ -40,15 +40,6 @@ function is_rest_endpoint_request( $type = 'list' ) {
return in_array( $run_endpoint, parse_request() ); return in_array( $run_endpoint, parse_request() );
} }
/**
* Flush plugin's internal caches
*
* FOR INTERNAL USE ONLY - see WP-CLI; all other cache clearance should happen through the `Events_Store` class
*/
function _flush_internal_caches() {
return wp_cache_delete( Events_Store::CACHE_KEY );
}
/** /**
* Schedule an event directly, bypassing the plugin's filtering to capture Core's scheduling functions * Schedule an event directly, bypassing the plugin's filtering to capture Core's scheduling functions
* *
...@@ -61,6 +52,20 @@ function schedule_event( $timestamp, $action, $args, $job_id = null ) { ...@@ -61,6 +52,20 @@ function schedule_event( $timestamp, $action, $args, $job_id = null ) {
Events_Store::instance()->create_or_update_job( $timestamp, $action, $args, $job_id ); Events_Store::instance()->create_or_update_job( $timestamp, $action, $args, $job_id );
} }
/**
* Execute a specific event
*
* @param int $timestamp Unix timestamp
* @param string $action_hashed md5 hash of the action used when the event is registered
* @param string $instance md5 hash of the event's arguments array, which Core uses to index the `cron` option
* @param bool $force Run event regardless of timestamp or lock status? eg, when executing jobs via wp-cli
*
* @return array|\WP_Error
*/
function run_event( $timestamp, $action_hashed, $instance, $force = false ) {
return Events::instance()->run_event( $timestamp, $action_hashed, $instance, $force );
}
/** /**
* Delete an event entry directly, bypassing the plugin's filtering to capture same * Delete an event entry directly, bypassing the plugin's filtering to capture same
* *
...@@ -72,6 +77,17 @@ function delete_event( $timestamp, $action, $instance ) { ...@@ -72,6 +77,17 @@ function delete_event( $timestamp, $action, $instance ) {
Events_Store::instance()->mark_job_completed( $timestamp, $action, $instance ); Events_Store::instance()->mark_job_completed( $timestamp, $action, $instance );
} }
/**
* Delete an event by its ID
*
* @param int $id Event ID
* $param bool $flush_cache Flush internal cacehs
* @return bool
*/
function delete_event_by_id( $id, $flush_cache = false ) {
return Events_Store::instance()->mark_job_record_completed( $id, $flush_cache );
}
/** /**
* Check if an entry exists for a particular job, and return its ID if requested * Check if an entry exists for a particular job, and return its ID if requested
* *
...@@ -86,6 +102,16 @@ function event_exists( $timestamp, $action, $instance, $return_id = false ) { ...@@ -86,6 +102,16 @@ function event_exists( $timestamp, $action, $instance, $return_id = false ) {
return Events_Store::instance()->job_exists( $timestamp, $action, $instance, $return_id ); return Events_Store::instance()->job_exists( $timestamp, $action, $instance, $return_id );
} }
/**
* Retrieve jobs given a set of parameters
*
* @param array $args
* @return array|false
*/
function get_events( $args ) {
return Events_Store::instance()->get_jobs( $args );
}
/** /**
* Retrieve a single event by ID, or by a combination of its timestamp, instance identifier, and either action or the action's hashed representation * Retrieve a single event by ID, or by a combination of its timestamp, instance identifier, and either action or the action's hashed representation
* *
...@@ -96,18 +122,37 @@ function get_event_by_attributes( $attributes ) { ...@@ -96,18 +122,37 @@ function get_event_by_attributes( $attributes ) {
return Events_Store::instance()->get_job( $attributes ); return Events_Store::instance()->get_job( $attributes );
} }
/**
* Count events with a given status
*
* @param string $status Status to count
* @return int|false
*/
function count_events_by_status( $status ) {
return Events_Store::instance()->count_events_by_status( $status );
}
/**
* Flush plugin's internal caches
*
* FOR INTERNAL USE ONLY - see WP-CLI; all other cache clearance should happen through the `Events_Store` class
*/
function _flush_internal_caches() {
return wp_cache_delete( Events_Store::CACHE_KEY );
}
/** /**
* Prevent event store from creating new entries * Prevent event store from creating new entries
* *
* Should be used sparingly, and followed by a call to resume_event_creation(), during bulk operations * Should be used sparingly, and followed by a call to resume_event_creation(), during bulk operations
*/ */
function suspend_event_creation() { function _suspend_event_creation() {
Events_Store::instance()->suspend_event_creation(); Events_Store::instance()->suspend_event_creation();
} }
/** /**
* Stop discarding events, once again storing them in the table * Stop discarding events, once again storing them in the table
*/ */
function resume_event_creation() { function _resume_event_creation() {
Events_Store::instance()->resume_event_creation(); Events_Store::instance()->resume_event_creation();
} }
...@@ -104,24 +104,19 @@ class Events extends \WP_CLI_Command { ...@@ -104,24 +104,19 @@ class Events extends \WP_CLI_Command {
\WP_CLI::error( __( 'Specify the ID of an event to run', 'automattic-cron-control' ) ); \WP_CLI::error( __( 'Specify the ID of an event to run', 'automattic-cron-control' ) );
} }
// Validate event ID and get the information needed to execute it // Retrieve information needed to execute event
global $wpdb; $event = \Automattic\WP\Cron_Control\get_event_by_attributes( array( 'ID' => $args[0], ) );
$event = $wpdb->get_var( $wpdb->prepare( "SELECT post_title FROM {$wpdb->posts} WHERE ID = %d AND post_type = %s AND post_status = %s LIMIT 1", $args[0], \Automattic\WP\Cron_Control\Cron_Options_CPT::POST_TYPE, \Automattic\WP\Cron_Control\Cron_Options_CPT::POST_STATUS_PENDING ) );
if ( empty( $event ) ) { if ( ! is_object( $event ) ) {
\WP_CLI::error( sprintf( __( 'Failed to locate event %d. Please confirm that the entry exists and that the ID is that of an event.', 'automattic-cron-control' ), $args[0] ) ); \WP_CLI::error( sprintf( __( 'Failed to locate event %d. Please confirm that the entry exists and that the ID is that of an event.', 'automattic-cron-control' ), $args[0] ) );
} }
// Event data \WP_CLI::line( sprintf( __( 'Found event %1$d with action `%2$s` and instance identifier `%3$s`', 'automattic-cron-control' ), $args[0], $event->action, $event->instance ) );
$event_data = $this->get_event_details_from_post_title( $event );
\WP_CLI::line( sprintf( __( 'Found event %1$d with action `%2$s` and instance identifier `%3$s`', 'automattic-cron-control' ), $args[0], $event_data['action'], $event_data['instance'] ) );
// Proceed? // Proceed?
$now = time(); $now = time();
if ( $event_data['timestamp'] > $now ) { if ( $event->timestamp > $now ) {
\WP_CLI::warning( sprintf( __( 'This event is not scheduled to run until %1$s GMT (%2$s)', 'automattic-cron-control' ), date( TIME_FORMAT, $event_data['timestamp'] ), $this->calculate_interval( $event_data['timestamp'] - $now ) ) ); \WP_CLI::warning( sprintf( __( 'This event is not scheduled to run until %1$s GMT (%2$s)', 'automattic-cron-control' ), date( TIME_FORMAT, $event->timestamp ), $this->calculate_interval( $event->timestamp - $now ) ) );
} }
\WP_CLI::confirm( sprintf( __( 'Run this event?', 'automattic-cron-control' ) ) ); \WP_CLI::confirm( sprintf( __( 'Run this event?', 'automattic-cron-control' ) ) );
...@@ -132,7 +127,7 @@ class Events extends \WP_CLI_Command { ...@@ -132,7 +127,7 @@ class Events extends \WP_CLI_Command {
} }
// Run the event // Run the event
$run = \Automattic\WP\Cron_Control\Events::instance()->run_event( $event_data['timestamp'], md5( $event_data['action'] ), $event_data['instance'], true ); $run = \Automattic\WP\Cron_Control\run_event( $event->timestamp, $event->action_hashed, $event->instance, true );
// Output based on run attempt // Output based on run attempt
if ( is_array( $run ) ) { if ( is_array( $run ) ) {
...@@ -187,7 +182,7 @@ class Events extends \WP_CLI_Command { ...@@ -187,7 +182,7 @@ class Events extends \WP_CLI_Command {
$offset = absint( ( $page - 1 ) * $limit ); $offset = absint( ( $page - 1 ) * $limit );
// Query // Query
$items = \Automattic\WP\Cron_Control\Events_Store::instance()->get_jobs( array( $items = \Automattic\WP\Cron_Control\get_events( array(
'status' => $event_status, 'status' => $event_status,
'quantity' => $limit, 'quantity' => $limit,
'page' => $page, 'page' => $page,
...@@ -199,7 +194,7 @@ class Events extends \WP_CLI_Command { ...@@ -199,7 +194,7 @@ class Events extends \WP_CLI_Command {
} }
// Include totals for pagination etc // Include totals for pagination etc
$total_items = (int) $wpdb->get_var( 'SELECT FOUND_ROWS()' ); $total_items = \Automattic\WP\Cron_Control\count_events_by_status( \Automattic\WP\Cron_Control\Events_Store::STATUS_PENDING );
$total_pages = ceil( $total_items / $limit ); $total_pages = ceil( $total_items / $limit );
return compact( 'status', 'limit', 'page', 'offset', 'items', 'total_items', 'total_pages' ); return compact( 'status', 'limit', 'page', 'offset', 'items', 'total_items', 'total_pages' );
...@@ -326,31 +321,26 @@ class Events extends \WP_CLI_Command { ...@@ -326,31 +321,26 @@ class Events extends \WP_CLI_Command {
\WP_CLI::line( __( 'Locating event...', 'automattic-cron-control' ) . "\n" ); \WP_CLI::line( __( 'Locating event...', 'automattic-cron-control' ) . "\n" );
// Look up full object and confirm that the entry belongs to this plugin's CPT // Look up full object and confirm that the entry belongs to this plugin's CPT
global $wpdb; $event = \Automattic\WP\Cron_Control\get_event_by_attributes( array( 'ID' => $jid, ) );
$event_post = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->posts} WHERE post_type = %s AND ID = %d LIMIT 1", \Automattic\WP\Cron_Control\Cron_Options_CPT::POST_TYPE, $jid ) );
if ( is_object( $event_post ) && ! is_wp_error( $event_post ) ) {
// Parse basic event data and output, lest someone delete the wrong thing
$event_details = $this->get_event_details_from_post_title( $event_post->post_title );
if ( is_object( $event ) ) {
// Warning about Internal Events // Warning about Internal Events
if ( \Automattic\WP\Cron_Control\is_internal_event( $event_details['action'] ) ) { if ( \Automattic\WP\Cron_Control\is_internal_event( $event->action ) ) {
\WP_CLI::warning( __( 'This is an event created by the Cron Control plugin. It will recreated automatically.', 'automattic-cron-control' ) ); \WP_CLI::warning( __( 'This is an event created by the Cron Control plugin. It will recreated automatically.', 'automattic-cron-control' ) );
} }
\WP_CLI::line( sprintf( __( 'Execution time: %s GMT', 'automattic-cron-control' ), date( TIME_FORMAT, $event_details['timestamp'] ) ) ); \WP_CLI::line( sprintf( __( 'Execution time: %s GMT', 'automattic-cron-control' ), date( TIME_FORMAT, $event->timestamp ) ) );
\WP_CLI::line( sprintf( __( 'Action: %s', 'automattic-cron-control' ), $event_details['action'] ) ); \WP_CLI::line( sprintf( __( 'Action: %s', 'automattic-cron-control' ), $event->action ) );
\WP_CLI::line( sprintf( __( 'Instance identifier: %s', 'automattic-cron-control' ), $event_details['instance'] ) ); \WP_CLI::line( sprintf( __( 'Instance identifier: %s', 'automattic-cron-control' ), $event->instance ) );
\WP_CLI::line( '' ); \WP_CLI::line( '' );
\WP_CLI::confirm( sprintf( __( 'Are you sure you want to delete this event?', 'automattic-cron-control' ) ) ); \WP_CLI::confirm( sprintf( __( 'Are you sure you want to delete this event?', 'automattic-cron-control' ) ) );
// Try to delete the item and provide some relevant output // Try to delete the item and provide some relevant output
\Automattic\WP\Cron_Control\suspend_event_creation(); \Automattic\WP\Cron_Control\_suspend_event_creation();
$trashed = wp_delete_post( $event_post->ID, true ); $deleted = \Automattic\WP\Cron_Control\delete_event_by_id( $event->ID, true );
\Automattic\WP\Cron_Control\resume_event_creation(); \Automattic\WP\Cron_Control\_resume_event_creation();
if ( false === $trashed ) { if ( false === $deleted ) {
\WP_CLI::error( sprintf( __( 'Failed to delete event %d', 'automattic-cron-control' ), $jid ) ); \WP_CLI::error( sprintf( __( 'Failed to delete event %d', 'automattic-cron-control' ), $jid ) );
} else { } else {
\Automattic\WP\Cron_Control\_flush_internal_caches(); \Automattic\WP\Cron_Control\_flush_internal_caches();
...@@ -401,14 +391,8 @@ class Events extends \WP_CLI_Command { ...@@ -401,14 +391,8 @@ class Events extends \WP_CLI_Command {
// Check events for those that should be deleted // Check events for those that should be deleted
foreach ( $events['items'] as $single_event ) { foreach ( $events['items'] as $single_event ) {
$event_details = $this->get_event_details_from_post_title( $single_event->post_title ); if ( $single_event->action === $action ) {
$events_to_delete[] = (array) $single_event;
if ( $event_details['action'] === $action ) {
$events_to_delete[] = array_merge( $event_details, array(
'ID' => (int) $single_event->ID,
'post_date_gmt' => $single_event->post_date_gmt,
'post_modified_gmt' => $single_event->post_modified_gmt,
) );
} }
$search_progress->tick(); $search_progress->tick();
...@@ -441,8 +425,8 @@ class Events extends \WP_CLI_Command { ...@@ -441,8 +425,8 @@ class Events extends \WP_CLI_Command {
if ( $total_to_delete <= $assoc_args['limit'] ) { if ( $total_to_delete <= $assoc_args['limit'] ) {
\WP_CLI\Utils\format_items( 'table', $events_to_delete, array( \WP_CLI\Utils\format_items( 'table', $events_to_delete, array(
'ID', 'ID',
'post_date_gmt', 'created',
'post_modified_gmt', 'last_modified',
'timestamp', 'timestamp',
'instance', 'instance',
) ); ) );
...@@ -460,10 +444,10 @@ class Events extends \WP_CLI_Command { ...@@ -460,10 +444,10 @@ class Events extends \WP_CLI_Command {
$events_deleted_count = $events_failed_delete = 0; $events_deleted_count = $events_failed_delete = 0;
// Don't create new events while deleting events // Don't create new events while deleting events
\Automattic\WP\Cron_Control\suspend_event_creation(); \Automattic\WP\Cron_Control\_suspend_event_creation();
foreach ( $events_to_delete as $event_to_delete ) { foreach ( $events_to_delete as $event_to_delete ) {
$deleted = wp_delete_post( $event_to_delete['ID'], true ); $deleted = \Automattic\WP\Cron_Control\delete_event_by_id( $event_to_delete['ID'], false );
$events_deleted[] = array( $events_deleted[] = array(
'ID' => $event_to_delete['ID'], 'ID' => $event_to_delete['ID'],
...@@ -487,7 +471,7 @@ class Events extends \WP_CLI_Command { ...@@ -487,7 +471,7 @@ class Events extends \WP_CLI_Command {
} }
// New events can be created now that removal is complete // New events can be created now that removal is complete
\Automattic\WP\Cron_Control\resume_event_creation(); \Automattic\WP\Cron_Control\_resume_event_creation();
// List the removed items // List the removed items
\WP_CLI::line( "\n" . __( 'RESULTS:', 'automattic-cron-control' ) ); \WP_CLI::line( "\n" . __( 'RESULTS:', 'automattic-cron-control' ) );
...@@ -503,8 +487,6 @@ class Events extends \WP_CLI_Command { ...@@ -503,8 +487,6 @@ class Events extends \WP_CLI_Command {
// Limit just to failed deletes when many events are removed // Limit just to failed deletes when many events are removed
if ( count( $events_deleted ) > $assoc_args['limit'] ) { if ( count( $events_deleted ) > $assoc_args['limit'] ) {
$events_deleted_unfiltered = $events_deleted;
$events_deleted = array_filter( $events_deleted, function( $event ) { $events_deleted = array_filter( $events_deleted, function( $event ) {
if ( 'no' === $event['deleted'] ) { if ( 'no' === $event['deleted'] ) {
return $event; return $event;
...@@ -531,20 +513,6 @@ class Events extends \WP_CLI_Command { ...@@ -531,20 +513,6 @@ class Events extends \WP_CLI_Command {
return; return;
} }
/**
* Parse event details stored in an item's post_title
*/
private function get_event_details_from_post_title( $title ) {
$event_details = explode( '|', $title );
$event_details = array_map( 'trim', $event_details );
return array(
'timestamp' => (int) $event_details[0],
'action' => $event_details[1],
'instance' => $event_details[2],
);
}
} }
\WP_CLI::add_command( 'cron-control events', 'Automattic\WP\Cron_Control\CLI\Events' ); \WP_CLI::add_command( 'cron-control events', 'Automattic\WP\Cron_Control\CLI\Events' );
...@@ -26,7 +26,7 @@ class One_Time_Fixers extends \WP_CLI_Command { ...@@ -26,7 +26,7 @@ class One_Time_Fixers extends \WP_CLI_Command {
\WP_CLI::line( __( 'CRON CONTROL', 'automattic-cron-control' ) . "\n" ); \WP_CLI::line( __( 'CRON CONTROL', 'automattic-cron-control' ) . "\n" );
// Don't create new events while deleting events // Don't create new events while deleting events
\Automattic\WP\Cron_Control\suspend_event_creation(); \Automattic\WP\Cron_Control\_suspend_event_creation();
$count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(ID) FROM {$wpdb->posts} WHERE post_type = %s;", 'a8c_cron_ctrl_event' ) ); $count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(ID) FROM {$wpdb->posts} WHERE post_type = %s;", 'a8c_cron_ctrl_event' ) );
...@@ -106,7 +106,7 @@ class One_Time_Fixers extends \WP_CLI_Command { ...@@ -106,7 +106,7 @@ class One_Time_Fixers extends \WP_CLI_Command {
} }
// Let event creation resume // Let event creation resume
\Automattic\WP\Cron_Control\resume_event_creation(); \Automattic\WP\Cron_Control\_resume_event_creation();
// Fin // Fin
\WP_CLI::success( __( 'All done.', 'automattic-cron-control' ) ); \WP_CLI::success( __( 'All done.', 'automattic-cron-control' ) );
......
Markdown is supported
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