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 {
`created` datetime NOT NULL,
`last_modified` datetime NOT NULL,
PRIMARY KEY (`id`),
PRIMARY KEY (`ID`),
UNIQUE KEY `ts_action_instance` (`timestamp`, `action`, `instance`)
) ENGINE=InnoDB;\n";
......@@ -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 ) {
global $wpdb;
......@@ -439,10 +442,10 @@ class Events_Store extends Singleton {
/**
* 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;
$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
// Should only be skipped when deleting duplicates, as they are excluded from the cache
......@@ -450,7 +453,7 @@ class Events_Store extends Singleton {
wp_cache_delete( self::CACHE_KEY );
}
return true;
return (bool) $success;
}
/**
......@@ -496,8 +499,12 @@ class Events_Store extends Singleton {
/**
* 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() {
$this->purge_completed_events();
$this->job_creation_suspended = false;
}
......@@ -509,6 +516,22 @@ class Events_Store extends Singleton {
$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();
......@@ -64,7 +64,7 @@ class REST_API extends Singleton {
$action = isset( $event['action'] ) ? trim( sanitize_text_field( $event['action'] ) ) : 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' ) {
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
*
......@@ -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 );
}
/**
* 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
*
......@@ -72,6 +77,17 @@ function delete_event( $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
*
......@@ -86,6 +102,16 @@ function event_exists( $timestamp, $action, $instance, $return_id = false ) {
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
*
......@@ -96,18 +122,37 @@ function get_event_by_attributes( $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
*
* 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();
}
/**
* Stop discarding events, once again storing them in the table
*/
function resume_event_creation() {
function _resume_event_creation() {
Events_Store::instance()->resume_event_creation();
}
......@@ -104,24 +104,19 @@ class Events extends \WP_CLI_Command {
\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
global $wpdb;
$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 ) );
// Retrieve information needed to execute event
$event = \Automattic\WP\Cron_Control\get_event_by_attributes( array( 'ID' => $args[0], ) );
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] ) );
}
// Event data
$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'] ) );
\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 ) );
// Proceed?
$now = time();
if ( $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_data['timestamp'] ), $this->calculate_interval( $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->timestamp ), $this->calculate_interval( $event->timestamp - $now ) ) );
}
\WP_CLI::confirm( sprintf( __( 'Run this event?', 'automattic-cron-control' ) ) );
......@@ -132,7 +127,7 @@ class Events extends \WP_CLI_Command {
}
// 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
if ( is_array( $run ) ) {
......@@ -187,7 +182,7 @@ class Events extends \WP_CLI_Command {
$offset = absint( ( $page - 1 ) * $limit );
// Query
$items = \Automattic\WP\Cron_Control\Events_Store::instance()->get_jobs( array(
$items = \Automattic\WP\Cron_Control\get_events( array(
'status' => $event_status,
'quantity' => $limit,
'page' => $page,
......@@ -199,7 +194,7 @@ class Events extends \WP_CLI_Command {
}
// 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 );
return compact( 'status', 'limit', 'page', 'offset', 'items', 'total_items', 'total_pages' );
......@@ -326,31 +321,26 @@ class Events extends \WP_CLI_Command {
\WP_CLI::line( __( 'Locating event...', 'automattic-cron-control' ) . "\n" );
// Look up full object and confirm that the entry belongs to this plugin's CPT
global $wpdb;
$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 );
$event = \Automattic\WP\Cron_Control\get_event_by_attributes( array( 'ID' => $jid, ) );
if ( is_object( $event ) ) {
// 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::line( sprintf( __( 'Execution time: %s GMT', 'automattic-cron-control' ), date( TIME_FORMAT, $event_details['timestamp'] ) ) );
\WP_CLI::line( sprintf( __( 'Action: %s', 'automattic-cron-control' ), $event_details['action'] ) );
\WP_CLI::line( sprintf( __( 'Instance identifier: %s', 'automattic-cron-control' ), $event_details['instance'] ) );
\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->action ) );
\WP_CLI::line( sprintf( __( 'Instance identifier: %s', 'automattic-cron-control' ), $event->instance ) );
\WP_CLI::line( '' );
\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
\Automattic\WP\Cron_Control\suspend_event_creation();
$trashed = wp_delete_post( $event_post->ID, true );
\Automattic\WP\Cron_Control\resume_event_creation();
\Automattic\WP\Cron_Control\_suspend_event_creation();
$deleted = \Automattic\WP\Cron_Control\delete_event_by_id( $event->ID, true );
\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 ) );
} else {
\Automattic\WP\Cron_Control\_flush_internal_caches();
......@@ -401,14 +391,8 @@ class Events extends \WP_CLI_Command {
// Check events for those that should be deleted
foreach ( $events['items'] as $single_event ) {
$event_details = $this->get_event_details_from_post_title( $single_event->post_title );
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,
) );
if ( $single_event->action === $action ) {
$events_to_delete[] = (array) $single_event;
}
$search_progress->tick();
......@@ -441,8 +425,8 @@ class Events extends \WP_CLI_Command {
if ( $total_to_delete <= $assoc_args['limit'] ) {
\WP_CLI\Utils\format_items( 'table', $events_to_delete, array(
'ID',
'post_date_gmt',
'post_modified_gmt',
'created',
'last_modified',
'timestamp',
'instance',
) );
......@@ -460,10 +444,10 @@ class Events extends \WP_CLI_Command {
$events_deleted_count = $events_failed_delete = 0;
// 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 ) {
$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(
'ID' => $event_to_delete['ID'],
......@@ -487,7 +471,7 @@ class Events extends \WP_CLI_Command {
}
// 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
\WP_CLI::line( "\n" . __( 'RESULTS:', 'automattic-cron-control' ) );
......@@ -503,8 +487,6 @@ class Events extends \WP_CLI_Command {
// Limit just to failed deletes when many events are removed
if ( count( $events_deleted ) > $assoc_args['limit'] ) {
$events_deleted_unfiltered = $events_deleted;
$events_deleted = array_filter( $events_deleted, function( $event ) {
if ( 'no' === $event['deleted'] ) {
return $event;
......@@ -531,20 +513,6 @@ class Events extends \WP_CLI_Command {
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' );
......@@ -26,7 +26,7 @@ class One_Time_Fixers extends \WP_CLI_Command {
\WP_CLI::line( __( 'CRON CONTROL', 'automattic-cron-control' ) . "\n" );
// 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' ) );
......@@ -106,7 +106,7 @@ class One_Time_Fixers extends \WP_CLI_Command {
}
// Let event creation resume
\Automattic\WP\Cron_Control\resume_event_creation();
\Automattic\WP\Cron_Control\_resume_event_creation();
// Fin
\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