Commit 3055c3da authored by Erick Hitter's avatar Erick Hitter
Browse files

PHPCS fixes

parent 2ec37a99
<?php
/**
* Offload cron event storage to a custom table
*
* @package a8c_Cron_Control
*/
namespace Automattic\WP\Cron_Control;
/**
* Events Store class
*/
class Events_Store extends Singleton {
/**
* PLUGIN SETUP
*/
/**
* Class properties
* Class constants
*/
const TABLE_SUFFIX = 'a8c_cron_control_jobs';
......@@ -27,40 +35,45 @@ class Events_Store extends Singleton {
const CACHE_KEY = 'a8c_cron_ctrl_option';
/**
* Whether or not event creation is temporarily blocked
*
* @var bool
*/
private $job_creation_suspended = false;
/**
* Register hooks
*/
protected function class_init() {
// Create tables during installation
// Create tables during installation.
add_action( 'wp_install', array( $this, 'create_table_during_install' ) );
add_action( 'wpmu_new_blog', array( $this, 'create_tables_during_multisite_install' ) );
// Remove table when a multisite subsite is deleted
// Remove table when a multisite subsite is deleted.
add_filter( 'wpmu_drop_tables', array( $this, 'remove_multisite_table' ) );
// Enable plugin when conditions support it, otherwise limit errors as much as possible
// Enable plugin when conditions support it, otherwise limit errors as much as possible.
if ( self::is_installed() ) {
// Option interception
// Option interception.
add_filter( 'pre_option_cron', array( $this, 'get_option' ) );
add_filter( 'pre_update_option_cron', array( $this, 'update_option' ), 10, 2 );
// Disallow duplicates
// Disallow duplicates.
add_filter( 'schedule_event', array( $this, 'block_creation_if_job_exists' ) );
} else {
// Can't create events since there's no table to hold them
// Can't create events since there's no table to hold them.
$this->suspend_event_creation();
// Prime plugin's options when the options table exists
// Prime plugin's options when the options table exists.
if ( ! defined( 'WP_INSTALLING' ) || ! WP_INSTALLING ) {
$this->prime_options();
}
// Don't schedule events that won't be run
// Don't schedule events that won't be run.
add_filter( 'schedule_event', '__return_false' );
// In limited circumstances, try creating the table
// In limited circumstances, try creating the table.
add_action( 'shutdown', array( $this, 'maybe_create_table_on_shutdown' ) );
}
}
......@@ -91,7 +104,7 @@ class Events_Store extends Singleton {
* Set initial options that control plugin's behaviour
*/
protected function prime_options() {
// Prime DB option
// Prime DB option.
add_option( self::DB_VERSION_OPTION, 0, null, false );
}
......@@ -108,6 +121,8 @@ class Events_Store extends Singleton {
/**
* Create table when new subsite is added to a multisite
*
* @param int $bid Blog ID.
*/
public function create_tables_during_multisite_install( $bid ) {
switch_to_blog( $bid );
......@@ -135,18 +150,18 @@ class Events_Store extends Singleton {
* Create table in non-setup contexts, with some protections
*/
public function prepare_table() {
// Table installed
// Table installed.
if ( self::is_installed() ) {
return;
}
// Nothing to do
// Nothing to do.
$current_version = (int) get_option( self::DB_VERSION_OPTION );
if ( version_compare( $current_version, self::DB_VERSION, '>=' ) ) {
return;
}
// Limit chance of race conditions when creating table
// Limit chance of race conditions when creating table.
$create_lock_set = wp_cache_add( self::TABLE_CREATE_LOCK, 1, null, 1 * \MINUTE_IN_SECONDS );
if ( false === $create_lock_set ) {
return;
......@@ -159,7 +174,7 @@ class Events_Store extends Singleton {
* Create the plugin's DB table when necessary
*/
protected function _prepare_table() {
// Use Core's method of creating/updating tables
// Use Core's method of creating/updating tables.
if ( ! function_exists( 'dbDelta' ) ) {
require_once ABSPATH . '/wp-admin/includes/upgrade.php';
}
......@@ -168,7 +183,7 @@ class Events_Store extends Singleton {
$table_name = $this->get_table_name();
// Define schema and create the table
// Define schema and create the table.
$schema = "CREATE TABLE `{$table_name}` (
`ID` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
......@@ -192,14 +207,14 @@ class Events_Store extends Singleton {
dbDelta( $schema, true );
// Confirm that the table was created, and set the option to prevent further updates
$table_count = count( $wpdb->get_col( "SHOW TABLES LIKE '{$table_name}'" ) );
// Confirm that the table was created, and set the option to prevent further updates.
$table_count = count( $wpdb->get_col( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table_name ) ) );
if ( 1 === $table_count ) {
update_option( self::DB_VERSION_OPTION, self::DB_VERSION );
}
// Clear caches now that table exists
// Clear caches now that table exists.
$this->flush_internal_caches();
}
......@@ -219,7 +234,7 @@ class Events_Store extends Singleton {
*
* Core only drops its tables
*
* @param array $tables_to_drop Array of prefixed table names to drop
* @param array $tables_to_drop Array of prefixed table names to drop.
* @return array
*/
public function remove_multisite_table( $tables_to_drop ) {
......@@ -236,19 +251,19 @@ class Events_Store extends Singleton {
* Override cron option requests with data from custom table
*/
public function get_option() {
// Use cached value when available
// Use cached value when available.
$cached_option = wp_cache_get( self::CACHE_KEY, null, true );
if ( false !== $cached_option ) {
return $cached_option;
}
// Start building a new cron option
// Start building a new cron option.
$cron_array = array(
'version' => 2, // Core versions the cron array; without this, events will continually requeue
'version' => 2, // Core versions the cron array; without this, events will continually requeue.
);
// Get events to re-render as the cron option
// Get events to re-render as the cron option.
$page = 1;
$quantity = 100;
......@@ -259,27 +274,27 @@ class Events_Store extends Singleton {
'page' => $page++,
) );
// Nothing more to add
// Nothing more to add.
if ( empty( $jobs ) ) {
break;
}
// Loop through results and built output Core expects
// Loop through results and built output Core expects.
foreach ( $jobs as $job ) {
// Alias event timestamp
// Alias event timestamp.
$timestamp = $job->timestamp;
// If timestamp is invalid, event is removed to let its source fix it
// If timestamp is invalid, event is removed to let its source fix it.
if ( $timestamp <= 0 ) {
$this->mark_job_record_completed( $job->ID );
continue;
}
// Basic arguments to add a job to the array format Core expects
// Basic arguments to add a job to the array format Core expects.
$action = $job->action;
$instance = $job->instance;
// Populate remaining job data
// Populate remaining job data.
$cron_array[ $timestamp ][ $action ][ $instance ] = array(
'schedule' => $job->schedule,
'args' => $job->args,
......@@ -290,13 +305,13 @@ class Events_Store extends Singleton {
$cron_array[ $timestamp ][ $action ][ $instance ]['interval'] = $job->interval;
}
}
} while( count( $jobs ) >= $quantity );
} while ( count( $jobs ) >= $quantity );
// Re-sort the array just as Core does when events are scheduled
// Ensures events are sorted chronologically
// Re-sort the array just as Core does when events are scheduled.
// Ensures events are sorted chronologically.
uksort( $cron_array, 'strnatcasecmp' );
// Cache the results
// Cache the results.
wp_cache_set( self::CACHE_KEY, $cron_array, null, 1 * \HOUR_IN_SECONDS );
return $cron_array;
......@@ -306,13 +321,17 @@ class Events_Store extends Singleton {
* Handle requests to update the cron option
*
* By returning $old_value, `cron` option won't be updated
*
* @param array $new_value New option value.
* @param array $old_value Old option value.
* @return array
*/
public function update_option( $new_value, $old_value ) {
// Find changes to record
// Find changes to record.
$new_events = $this->find_cron_array_differences( $new_value, $old_value );
$deleted_events = $this->find_cron_array_differences( $old_value, $new_value );
// Add/update new events
// Add/update new events.
foreach ( $new_events as $new_event ) {
$job_id = $this->get_job_id( $new_event['timestamp'], $new_event['action'], $new_event['instance'] );
......@@ -323,7 +342,7 @@ class Events_Store extends Singleton {
$this->create_or_update_job( $new_event['timestamp'], $new_event['action'], $new_event['args'], $job_id, false );
}
// Mark deleted entries for removal
// Mark deleted entries for removal.
foreach ( $deleted_events as $deleted_event ) {
$this->mark_job_completed( $deleted_event['timestamp'], $deleted_event['action'], $deleted_event['instance'], false );
}
......@@ -335,9 +354,12 @@ class Events_Store extends Singleton {
/**
* When an entry exists, don't try to create it again
*
* @param object $job Job object.
* @return bool|object
*/
public function block_creation_if_job_exists( $job ) {
// Job already disallowed, carry on
// Job already disallowed, carry on.
if ( ! is_object( $job ) ) {
return $job;
}
......@@ -357,7 +379,7 @@ class Events_Store extends Singleton {
/**
* Retrieve jobs given a set of parameters
*
* @param array $args
* @param array $args Job arguments to search by.
* @return array
*/
public function get_jobs( $args ) {
......@@ -374,15 +396,15 @@ class Events_Store extends Singleton {
$offset = 0;
}
// Avoid sorting whenever possible, otherwise filesort is used
// Generally only necessary in CLI commands for pagination, as full list of events is usually required
// Avoid sorting whenever possible, otherwise filesort is used.
// Generally only necessary in CLI commands for pagination, as full list of events is usually required.
if ( isset( $args['force_sort'] ) && true === $args['force_sort'] ) {
$query = $wpdb->prepare( "SELECT * FROM {$this->get_table_name()} WHERE status = %s ORDER BY timestamp ASC LIMIT %d,%d;", $args['status'], $offset, $args['quantity'] );
$query = $wpdb->prepare( "SELECT * FROM {$this->get_table_name()} WHERE status = %s ORDER BY timestamp ASC LIMIT %d,%d;", $args['status'], $offset, $args['quantity'] ); // Cannot prepare table name. @codingStandardsIgnoreLine
} else {
$query = $wpdb->prepare( "SELECT * FROM {$this->get_table_name()} WHERE status = %s LIMIT %d,%d;", $args['status'], $offset, $args['quantity'] );
$query = $wpdb->prepare( "SELECT * FROM {$this->get_table_name()} WHERE status = %s LIMIT %d,%d;", $args['status'], $offset, $args['quantity'] ); // Cannot prepare table name. @codingStandardsIgnoreLine
}
$jobs = $wpdb->get_results( $query, 'OBJECT' );
$jobs = $wpdb->get_results( $query, 'OBJECT' ); // @codingStandardsIgnoreLine
if ( is_array( $jobs ) ) {
$jobs = array_map( array( $this, 'format_job' ), $jobs );
......@@ -396,19 +418,19 @@ class Events_Store extends Singleton {
/**
* Retrieve a single event by its ID
*
* @param int $jid Job ID
* @param int $jid Job ID.
* @return object|false
*/
public function get_job_by_id( $jid ) {
global $wpdb;
// Validate ID
// Validate ID.
$jid = absint( $jid );
if ( ! $jid ) {
return false;
}
$job = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$this->get_table_name()} WHERE ID = %d AND status = %s LIMIT 1", $jid, self::STATUS_PENDING ) );
$job = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$this->get_table_name()} WHERE ID = %d AND status = %s LIMIT 1", $jid, self::STATUS_PENDING ) ); // Cannot prepare table name. @codingStandardsIgnoreLine
if ( is_object( $job ) && ! is_wp_error( $job ) ) {
$job = $this->format_job( $job );
......@@ -422,18 +444,18 @@ class Events_Store extends Singleton {
/**
* Retrieve a single event by a combination of its timestamp, instance identifier, and either action or the action's hashed representation
*
* @param array $attrs Array of event attributes to query by
* @param array $attrs Array of event attributes to query by.
* @return object|false
*/
public function get_job_by_attributes( $attrs ) {
global $wpdb;
// Validate basic inputs
// Validate basic inputs.
if ( ! is_array( $attrs ) || empty( $attrs ) ) {
return false;
}
// Validate requested status
// Validate requested status.
$allowed_status = self::ALLOWED_STATUSES;
$allowed_status[] = 'any';
......@@ -441,14 +463,14 @@ class Events_Store extends Singleton {
$attrs['status'] = self::STATUS_PENDING;
}
// Need a timestamp, an instance, and either an action or its hashed representation
// Need a timestamp, an instance, and either an action or its hashed representation.
if ( ! isset( $attrs['timestamp'] ) || ! isset( $attrs['instance'] ) ) {
return false;
} elseif ( ! isset( $attrs['action'] ) && ! isset( $attrs['action_hashed'] ) ) {
return false;
}
// Build query
// Build query.
if ( isset( $attrs['action'] ) ) {
$action_column = 'action';
$action_value = $attrs['action'];
......@@ -457,18 +479,18 @@ class Events_Store extends Singleton {
$action_value = $attrs['action_hashed'];
}
// Do not sort, otherwise index isn't used
$query = $wpdb->prepare( "SELECT * FROM {$this->get_table_name()} WHERE timestamp = %d AND {$action_column} = %s AND instance = %s", $attrs['timestamp'], $action_value, $attrs['instance'] );
// Do not sort, otherwise index isn't used.
$query = $wpdb->prepare( "SELECT * FROM {$this->get_table_name()} WHERE timestamp = %d AND {$action_column} = %s AND instance = %s", $attrs['timestamp'], $action_value, $attrs['instance'] ); // Cannot prepare table or column names. @codingStandardsIgnoreLine
// Final query preparations
// Final query preparations.
if ( 'any' !== $attrs['status'] ) {
$query .= $wpdb->prepare( ' AND status = %s', $attrs['status'] );
}
$query .= ' LIMIT 1';
// Query and format results
$job = $wpdb->get_row( $query );
// Query and format results.
$job = $wpdb->get_row( $query ); // @codingStandardsIgnoreLine
if ( is_object( $job ) && ! is_wp_error( $job ) ) {
$job = $this->format_job( $job );
......@@ -485,15 +507,15 @@ class Events_Store extends Singleton {
* Used in situations where performance matters, which is why it exists despite duplicating `get_job_by_attributes()`
* Queries outside of this class should use `get_job_by_attributes()`
*
* @param int $timestamp Unix timestamp event executes at
* @param string $action Name of action used when the event is registered (unhashed)
* @param string $instance md5 hash of the event's arguments array, which Core uses to index the `cron` option
* @param int $timestamp Unix timestamp event executes at.
* @param string $action Name of action used when the event is registered (unhashed).
* @param string $instance md5 hash of the event's arguments array, which Core uses to index the `cron` option.
* @return int
*/
private function get_job_id( $timestamp, $action, $instance ) {
global $wpdb;
$job = $wpdb->get_col( $wpdb->prepare( "SELECT ID FROM {$this->get_table_name()} WHERE timestamp = %d AND action = %s AND instance = %s AND status = %s LIMIT 1;", $timestamp, $action, $instance, self::STATUS_PENDING ) );
$job = $wpdb->get_col( $wpdb->prepare( "SELECT ID FROM {$this->get_table_name()} WHERE timestamp = %d AND action = %s AND instance = %s AND status = %s LIMIT 1;", $timestamp, $action, $instance, self::STATUS_PENDING ) ); // Cannot prepare table name. @codingStandardsIgnoreLine
return empty( $job ) ? 0 : (int) array_shift( $job );
}
......@@ -501,7 +523,7 @@ class Events_Store extends Singleton {
/**
* Standardize formatting and expand serialized data
*
* @param object $job Job row from DB, in object form
* @param object $job Job row from DB, in object form.
* @return object
*/
private function format_job( $job ) {
......@@ -524,14 +546,14 @@ class Events_Store extends Singleton {
/**
* Create or update entry for a given job
*
* @param int $timestamp Unix timestamp event executes at
* @param string $action Hook event fires
* @param array $args Array of event's schedule, arguments, and interval
* @param bool $update_id ID of existing entry to update, rather than creating a new entry
* @param bool $flush_cache Whether or not to flush internal caches after creating/updating the event
* @param int $timestamp Unix timestamp event executes at.
* @param string $action Hook event fires.
* @param array $args Array of event's schedule, arguments, and interval.
* @param bool $update_id ID of existing entry to update, rather than creating a new entry.
* @param bool $flush_cache Whether or not to flush internal caches after creating/updating the event.
*/
public function create_or_update_job( $timestamp, $action, $args, $update_id = null, $flush_cache = true ) {
// Don't create new jobs when manipulating jobs via the plugin's CLI commands
// Don't create new jobs when manipulating jobs via the plugin's CLI commands.
if ( $this->job_creation_suspended ) {
return;
}
......@@ -555,17 +577,17 @@ class Events_Store extends Singleton {
$job_post['interval'] = (int) $args['interval'];
}
// Create the post, or update an existing entry to run again in the future
// Create the post, or update an existing entry to run again in the future.
if ( is_int( $update_id ) && $update_id > 0 ) {
$wpdb->update( $this->get_table_name(), $job_post, array( 'ID' => $update_id, ) );
$wpdb->update( $this->get_table_name(), $job_post, array( 'ID' => $update_id ) );
} else {
$job_post['created'] = current_time( 'mysql', true );
$wpdb->insert( $this->get_table_name(), $job_post );
}
// Delete internal cache
// Should only be skipped during bulk operations
// Delete internal cache.
// Should only be skipped during bulk operations.
if ( $flush_cache ) {
$this->flush_internal_caches();
}
......@@ -576,10 +598,10 @@ class Events_Store extends Singleton {
*
* Completed entries will be cleaned up by an internal job
*
* @param int $timestamp Unix timestamp event executes at
* @param string $action Name of action used when the event is registered (unhashed)
* @param string $instance md5 hash of the event's arguments array, which Core uses to index the `cron` option
* @param bool $flush_cache Whether or not to flush internal caches after creating/updating the event
* @param int $timestamp Unix timestamp event executes at.
* @param string $action Name of action used when the event is registered (unhashed).
* @param string $instance md5 hash of the event's arguments array, which Core uses to index the `cron` option.
* @param bool $flush_cache Whether or not to flush internal caches after creating/updating the event.
* @return bool
*/
public function mark_job_completed( $timestamp, $action, $instance, $flush_cache = true ) {
......@@ -595,8 +617,8 @@ class Events_Store extends Singleton {
/**
* Set a job post to the "completed" status
*
* @param int $job_id ID of job's record
* @param bool $flush_cache Whether or not to flush internal caches after creating/updating the event
* @param int $job_id ID of job's record.
* @param bool $flush_cache Whether or not to flush internal caches after creating/updating the event.
* @return bool
*/
public function mark_job_record_completed( $job_id, $flush_cache = true ) {
......@@ -612,13 +634,13 @@ class Events_Store extends Singleton {
*/
$updates = array(
'status' => self::STATUS_COMPLETED,
'instance' => mt_rand( 1000000, 999999999 ), // Breaks unique constraint, and can be recreated from entry's remaining data
'instance' => mt_rand( 1000000, 999999999 ), // Breaks unique constraint, and can be recreated from entry's remaining data.
);
$success = $wpdb->update( $this->get_table_name(), $updates, array( 'ID' => $job_id, ) );
$success = $wpdb->update( $this->get_table_name(), $updates, array( 'ID' => $job_id ) );
// Delete internal cache
// Should only be skipped during bulk operations
// Delete internal cache.
// Should only be skipped during bulk operations.
if ( $flush_cache ) {
$this->flush_internal_caches();
}
......@@ -629,9 +651,8 @@ class Events_Store extends Singleton {
/**
* Compare two arrays and return collapsed representation of the items present in one but not the other
*
* @param array $changed Array to identify additional items from
* @param array $reference Array to compare against
*
* @param array $changed Array to identify additional items from.
* @param array $reference Array to compare against.
* @return array
*/
private function find_cron_array_differences( $changed, $reference ) {
......@@ -680,11 +701,13 @@ class Events_Store extends Singleton {
/**
* Remove entries for non-recurring events that have been run
*
* @param bool $count_first Should events be counted before they're deleted.
*/
public function purge_completed_events( $count_first = true ) {
global $wpdb;
// Skip count if already performed
// Skip count if already performed.
if ( $count_first ) {
if ( property_exists( $wpdb, 'srtm' ) ) {
$srtm = $wpdb->srtm;
......@@ -701,14 +724,14 @@ class Events_Store extends Singleton {
}
if ( $count > 0 ) {
$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
* @param string $status Event status to count.
* @return int|false
*/
public function count_events_by_status( $status ) {
......@@ -718,7 +741,7 @@ class Events_Store extends Singleton {
return false;
}
return (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(ID) FROM {$this->get_table_name()} WHERE status = %s", $status ) );
return (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(ID) FROM {$this->get_table_name()} WHERE status = %s", $status ) ); // Cannot prepare table name. @codingStandardsIgnoreLine
}
}
......
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