init
This commit is contained in:
+30
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
if ( ! function_exists( 'action_scheduler_register_3_dot_8_dot_0' ) && function_exists( 'add_action' ) ) { // WRCS: DEFINED_VERSION.
|
||||
if ( ! class_exists( 'ActionScheduler_Versions', false ) ) {
|
||||
require_once __DIR__ . '/classes/ActionScheduler_Versions.php';
|
||||
add_action( 'plugins_loaded', array( 'ActionScheduler_Versions', 'initialize_latest_version' ), 1, 0 );
|
||||
}
|
||||
add_action( 'plugins_loaded', 'action_scheduler_register_3_dot_8_dot_0', 0, 0 ); // WRCS: DEFINED_VERSION.
|
||||
// phpcs:disable Generic.Functions.OpeningFunctionBraceKernighanRitchie.ContentAfterBrace
|
||||
function action_scheduler_register_3_dot_8_dot_0() { // WRCS: DEFINED_VERSION.
|
||||
$versions = ActionScheduler_Versions::instance();
|
||||
$versions->register( '3.8.0', 'action_scheduler_initialize_3_dot_8_dot_0' ); // WRCS: DEFINED_VERSION.
|
||||
}
|
||||
// phpcs:disable Generic.Functions.OpeningFunctionBraceKernighanRitchie.ContentAfterBrace
|
||||
function action_scheduler_initialize_3_dot_8_dot_0() { // WRCS: DEFINED_VERSION.
|
||||
// A final safety check is required even here, because historic versions of Action Scheduler
|
||||
// followed a different pattern (in some unusual cases, we could reach this point and the
|
||||
// ActionScheduler class is already defined—so we need to guard against that).
|
||||
if ( ! class_exists( 'ActionScheduler', false ) ) {
|
||||
require_once __DIR__ . '/classes/abstracts/ActionScheduler.php';
|
||||
ActionScheduler::init( __FILE__ );
|
||||
}
|
||||
}
|
||||
// Support usage in themes - load this version if no plugin has loaded a version yet.
|
||||
if ( did_action( 'plugins_loaded' ) && ! doing_action( 'plugins_loaded' ) && ! class_exists( 'ActionScheduler', false ) ) {
|
||||
action_scheduler_initialize_3_dot_8_dot_0(); // WRCS: DEFINED_VERSION.
|
||||
do_action( 'action_scheduler_pre_theme_init' );
|
||||
ActionScheduler_Versions::initialize_latest_version();
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class ActionScheduler_ActionClaim {
|
||||
private $id = '';
|
||||
private $action_ids = array();
|
||||
public function __construct( $id, array $action_ids ) {
|
||||
$this->id = $id;
|
||||
$this->action_ids = $action_ids;
|
||||
}
|
||||
public function get_id() {
|
||||
return $this->id;
|
||||
}
|
||||
public function get_actions() {
|
||||
return $this->action_ids;
|
||||
}
|
||||
}
|
||||
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class ActionScheduler_ActionFactory {
|
||||
public function get_stored_action( $status, $hook, array $args = array(), ActionScheduler_Schedule $schedule = null, $group = '' ) {
|
||||
// The 6th parameter ($priority) is not formally declared in the method signature to maintain compatibility with
|
||||
// third-party subclasses created before this param was added.
|
||||
$priority = func_num_args() >= 6 ? (int) func_get_arg( 5 ) : 10;
|
||||
switch ( $status ) {
|
||||
case ActionScheduler_Store::STATUS_PENDING:
|
||||
$action_class = 'ActionScheduler_Action';
|
||||
break;
|
||||
case ActionScheduler_Store::STATUS_CANCELED:
|
||||
$action_class = 'ActionScheduler_CanceledAction';
|
||||
if ( ! is_null( $schedule ) && ! is_a( $schedule, 'ActionScheduler_CanceledSchedule' ) && ! is_a( $schedule, 'ActionScheduler_NullSchedule' ) ) {
|
||||
$schedule = new ActionScheduler_CanceledSchedule( $schedule->get_date() );
|
||||
}
|
||||
break;
|
||||
default:
|
||||
$action_class = 'ActionScheduler_FinishedAction';
|
||||
break;
|
||||
}
|
||||
$action_class = apply_filters( 'action_scheduler_stored_action_class', $action_class, $status, $hook, $args, $schedule, $group );
|
||||
$action = new $action_class( $hook, $args, $schedule, $group );
|
||||
$action->set_priority( $priority );
|
||||
return apply_filters( 'action_scheduler_stored_action_instance', $action, $hook, $args, $schedule, $group, $priority );
|
||||
}
|
||||
public function async( $hook, $args = array(), $group = '' ) {
|
||||
return $this->async_unique( $hook, $args, $group, false );
|
||||
}
|
||||
public function async_unique( $hook, $args = array(), $group = '', $unique = true ) {
|
||||
$schedule = new ActionScheduler_NullSchedule();
|
||||
$action = new ActionScheduler_Action( $hook, $args, $schedule, $group );
|
||||
return $unique ? $this->store_unique_action( $action, $unique ) : $this->store( $action );
|
||||
}
|
||||
public function single( $hook, $args = array(), $when = null, $group = '' ) {
|
||||
return $this->single_unique( $hook, $args, $when, $group, false );
|
||||
}
|
||||
public function single_unique( $hook, $args = array(), $when = null, $group = '', $unique = true ) {
|
||||
$date = as_get_datetime_object( $when );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $date );
|
||||
$action = new ActionScheduler_Action( $hook, $args, $schedule, $group );
|
||||
return $unique ? $this->store_unique_action( $action ) : $this->store( $action );
|
||||
}
|
||||
public function recurring( $hook, $args = array(), $first = null, $interval = null, $group = '' ) {
|
||||
return $this->recurring_unique( $hook, $args, $first, $interval, $group, false );
|
||||
}
|
||||
public function recurring_unique( $hook, $args = array(), $first = null, $interval = null, $group = '', $unique = true ) {
|
||||
if ( empty( $interval ) ) {
|
||||
return $this->single_unique( $hook, $args, $first, $group, $unique );
|
||||
}
|
||||
$date = as_get_datetime_object( $first );
|
||||
$schedule = new ActionScheduler_IntervalSchedule( $date, $interval );
|
||||
$action = new ActionScheduler_Action( $hook, $args, $schedule, $group );
|
||||
return $unique ? $this->store_unique_action( $action ) : $this->store( $action );
|
||||
}
|
||||
public function cron( $hook, $args = array(), $base_timestamp = null, $schedule = null, $group = '' ) {
|
||||
return $this->cron_unique( $hook, $args, $base_timestamp, $schedule, $group, false );
|
||||
}
|
||||
public function cron_unique( $hook, $args = array(), $base_timestamp = null, $schedule = null, $group = '', $unique = true ) {
|
||||
if ( empty( $schedule ) ) {
|
||||
return $this->single_unique( $hook, $args, $base_timestamp, $group, $unique );
|
||||
}
|
||||
$date = as_get_datetime_object( $base_timestamp );
|
||||
$cron = CronExpression::factory( $schedule );
|
||||
$schedule = new ActionScheduler_CronSchedule( $date, $cron );
|
||||
$action = new ActionScheduler_Action( $hook, $args, $schedule, $group );
|
||||
return $unique ? $this->store_unique_action( $action ) : $this->store( $action );
|
||||
}
|
||||
public function repeat( $action ) {
|
||||
$schedule = $action->get_schedule();
|
||||
$next = $schedule->get_next( as_get_datetime_object() );
|
||||
if ( is_null( $next ) || ! $schedule->is_recurring() ) {
|
||||
throw new InvalidArgumentException( __( 'Invalid action - must be a recurring action.', 'action-scheduler' ) );
|
||||
}
|
||||
$schedule_class = get_class( $schedule );
|
||||
$new_schedule = new $schedule( $next, $schedule->get_recurrence(), $schedule->get_first_date() );
|
||||
$new_action = new ActionScheduler_Action( $action->get_hook(), $action->get_args(), $new_schedule, $action->get_group() );
|
||||
$new_action->set_priority( $action->get_priority() );
|
||||
return $this->store( $new_action );
|
||||
}
|
||||
public function create( array $options = array() ) {
|
||||
$defaults = array(
|
||||
'type' => 'single',
|
||||
'hook' => '',
|
||||
'arguments' => array(),
|
||||
'group' => '',
|
||||
'unique' => false,
|
||||
'when' => time(),
|
||||
'pattern' => null,
|
||||
'priority' => 10,
|
||||
);
|
||||
$options = array_merge( $defaults, $options );
|
||||
// Cron/recurring actions without a pattern are treated as single actions (this gives calling code the ability
|
||||
// to use functions like as_schedule_recurring_action() to schedule recurring as well as single actions).
|
||||
if ( ( 'cron' === $options['type'] || 'recurring' === $options['type'] ) && empty( $options['pattern'] ) ) {
|
||||
$options['type'] = 'single';
|
||||
}
|
||||
switch ( $options['type'] ) {
|
||||
case 'async':
|
||||
$schedule = new ActionScheduler_NullSchedule();
|
||||
break;
|
||||
case 'cron':
|
||||
$date = as_get_datetime_object( $options['when'] );
|
||||
$cron = CronExpression::factory( $options['pattern'] );
|
||||
$schedule = new ActionScheduler_CronSchedule( $date, $cron );
|
||||
break;
|
||||
case 'recurring':
|
||||
$date = as_get_datetime_object( $options['when'] );
|
||||
$schedule = new ActionScheduler_IntervalSchedule( $date, $options['pattern'] );
|
||||
break;
|
||||
case 'single':
|
||||
$date = as_get_datetime_object( $options['when'] );
|
||||
$schedule = new ActionScheduler_SimpleSchedule( $date );
|
||||
break;
|
||||
default:
|
||||
error_log( "Unknown action type '{$options['type']}' specified when trying to create an action for '{$options['hook']}'." );
|
||||
return 0;
|
||||
}
|
||||
$action = new ActionScheduler_Action( $options['hook'], $options['arguments'], $schedule, $options['group'] );
|
||||
$action->set_priority( $options['priority'] );
|
||||
$action_id = 0;
|
||||
try {
|
||||
$action_id = $options['unique'] ? $this->store_unique_action( $action ) : $this->store( $action );
|
||||
} catch ( Exception $e ) {
|
||||
error_log(
|
||||
sprintf(
|
||||
__( 'Caught exception while enqueuing action "%1$s": %2$s', 'action-scheduler' ),
|
||||
$options['hook'],
|
||||
$e->getMessage()
|
||||
)
|
||||
);
|
||||
}
|
||||
return $action_id;
|
||||
}
|
||||
protected function store( ActionScheduler_Action $action ) {
|
||||
$store = ActionScheduler_Store::instance();
|
||||
return $store->save_action( $action );
|
||||
}
|
||||
protected function store_unique_action( ActionScheduler_Action $action ) {
|
||||
$store = ActionScheduler_Store::instance();
|
||||
if ( method_exists( $store, 'save_unique_action' ) ) {
|
||||
return $store->save_unique_action( $action );
|
||||
} else {
|
||||
$existing_action_id = (int) $store->find_action(
|
||||
$action->get_hook(),
|
||||
array(
|
||||
'args' => $action->get_args(),
|
||||
'status' => ActionScheduler_Store::STATUS_PENDING,
|
||||
'group' => $action->get_group(),
|
||||
)
|
||||
);
|
||||
if ( $existing_action_id > 0 ) {
|
||||
return 0;
|
||||
}
|
||||
return $store->save_action( $action );
|
||||
}
|
||||
}
|
||||
}
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class ActionScheduler_AdminView extends ActionScheduler_AdminView_Deprecated {
|
||||
private static $admin_view = NULL;
|
||||
private static $screen_id = 'tools_page_action-scheduler';
|
||||
protected $list_table;
|
||||
public static function instance() {
|
||||
if ( empty( self::$admin_view ) ) {
|
||||
$class = apply_filters('action_scheduler_admin_view_class', 'ActionScheduler_AdminView');
|
||||
self::$admin_view = new $class();
|
||||
}
|
||||
return self::$admin_view;
|
||||
}
|
||||
public function init() {
|
||||
if ( is_admin() && ( ! defined( 'DOING_AJAX' ) || false == DOING_AJAX ) ) {
|
||||
if ( class_exists( 'WooCommerce' ) ) {
|
||||
add_action( 'woocommerce_admin_status_content_action-scheduler', array( $this, 'render_admin_ui' ) );
|
||||
add_action( 'woocommerce_system_status_report', array( $this, 'system_status_report' ) );
|
||||
add_filter( 'woocommerce_admin_status_tabs', array( $this, 'register_system_status_tab' ) );
|
||||
}
|
||||
add_action( 'admin_menu', array( $this, 'register_menu' ) );
|
||||
add_action( 'admin_notices', array( $this, 'maybe_check_pastdue_actions' ) );
|
||||
add_action( 'current_screen', array( $this, 'add_help_tabs' ) );
|
||||
}
|
||||
}
|
||||
public function system_status_report() {
|
||||
$table = new ActionScheduler_wcSystemStatus( ActionScheduler::store() );
|
||||
$table->render();
|
||||
}
|
||||
public function register_system_status_tab( array $tabs ) {
|
||||
$tabs['action-scheduler'] = __( 'Scheduled Actions', 'action-scheduler' );
|
||||
return $tabs;
|
||||
}
|
||||
public function register_menu() {
|
||||
$hook_suffix = add_submenu_page(
|
||||
'tools.php',
|
||||
__( 'Scheduled Actions', 'action-scheduler' ),
|
||||
__( 'Scheduled Actions', 'action-scheduler' ),
|
||||
'manage_options',
|
||||
'action-scheduler',
|
||||
array( $this, 'render_admin_ui' )
|
||||
);
|
||||
add_action( 'load-' . $hook_suffix , array( $this, 'process_admin_ui' ) );
|
||||
}
|
||||
public function process_admin_ui() {
|
||||
$this->get_list_table();
|
||||
}
|
||||
public function render_admin_ui() {
|
||||
$table = $this->get_list_table();
|
||||
$table->display_page();
|
||||
}
|
||||
protected function get_list_table() {
|
||||
if ( null === $this->list_table ) {
|
||||
$this->list_table = new ActionScheduler_ListTable( ActionScheduler::store(), ActionScheduler::logger(), ActionScheduler::runner() );
|
||||
$this->list_table->process_actions();
|
||||
}
|
||||
return $this->list_table;
|
||||
}
|
||||
public function maybe_check_pastdue_actions() {
|
||||
# Filter to prevent checking actions (ex: inappropriate user).
|
||||
if ( ! apply_filters( 'action_scheduler_check_pastdue_actions', current_user_can( 'manage_options' ) ) ) {
|
||||
return;
|
||||
}
|
||||
# Get last check transient.
|
||||
$last_check = get_transient( 'action_scheduler_last_pastdue_actions_check' );
|
||||
# If transient exists, we're within interval, so bail.
|
||||
if ( ! empty( $last_check ) ) {
|
||||
return;
|
||||
}
|
||||
# Perform the check.
|
||||
$this->check_pastdue_actions();
|
||||
}
|
||||
protected function check_pastdue_actions() {
|
||||
# Set thresholds.
|
||||
$threshold_seconds = ( int ) apply_filters( 'action_scheduler_pastdue_actions_seconds', DAY_IN_SECONDS );
|
||||
$threshhold_min = ( int ) apply_filters( 'action_scheduler_pastdue_actions_min', 1 );
|
||||
// Set fallback value for past-due actions count.
|
||||
$num_pastdue_actions = 0;
|
||||
// Allow third-parties to preempt the default check logic.
|
||||
$check = apply_filters( 'action_scheduler_pastdue_actions_check_pre', null );
|
||||
// If no third-party preempted and there are no past-due actions, return early.
|
||||
if ( ! is_null( $check ) ) {
|
||||
return;
|
||||
}
|
||||
# Scheduled actions query arguments.
|
||||
$query_args = array(
|
||||
'date' => as_get_datetime_object( time() - $threshold_seconds ),
|
||||
'status' => ActionScheduler_Store::STATUS_PENDING,
|
||||
'per_page' => $threshhold_min,
|
||||
);
|
||||
# If no third-party preempted, run default check.
|
||||
if ( is_null( $check ) ) {
|
||||
$store = ActionScheduler_Store::instance();
|
||||
$num_pastdue_actions = ( int ) $store->query_actions( $query_args, 'count' );
|
||||
# Check if past-due actions count is greater than or equal to threshold.
|
||||
$check = ( $num_pastdue_actions >= $threshhold_min );
|
||||
$check = ( bool ) apply_filters( 'action_scheduler_pastdue_actions_check', $check, $num_pastdue_actions, $threshold_seconds, $threshhold_min );
|
||||
}
|
||||
# If check failed, set transient and abort.
|
||||
if ( ! boolval( $check ) ) {
|
||||
$interval = apply_filters( 'action_scheduler_pastdue_actions_check_interval', round( $threshold_seconds / 4 ), $threshold_seconds );
|
||||
set_transient( 'action_scheduler_last_pastdue_actions_check', time(), $interval );
|
||||
return;
|
||||
}
|
||||
$actions_url = add_query_arg( array(
|
||||
'page' => 'action-scheduler',
|
||||
'status' => 'past-due',
|
||||
'order' => 'asc',
|
||||
), admin_url( 'tools.php' ) );
|
||||
# Print notice.
|
||||
echo '<div class="notice notice-warning"><p>';
|
||||
printf(
|
||||
// translators: 1) is the number of affected actions, 2) is a link to an admin screen.
|
||||
_n(
|
||||
'<strong>Action Scheduler:</strong> %1$d <a href="%2$s">past-due action</a> found; something may be wrong. <a href="https://actionscheduler.org/faq/#my-site-has-past-due-actions-what-can-i-do" target="_blank">Read documentation »</a>',
|
||||
'<strong>Action Scheduler:</strong> %1$d <a href="%2$s">past-due actions</a> found; something may be wrong. <a href="https://actionscheduler.org/faq/#my-site-has-past-due-actions-what-can-i-do" target="_blank">Read documentation »</a>',
|
||||
$num_pastdue_actions,
|
||||
'action-scheduler'
|
||||
),
|
||||
$num_pastdue_actions,
|
||||
esc_attr( esc_url( $actions_url ) )
|
||||
);
|
||||
echo '</p></div>';
|
||||
# Facilitate third-parties to evaluate and print notices.
|
||||
do_action( 'action_scheduler_pastdue_actions_extra_notices', $query_args );
|
||||
}
|
||||
public function add_help_tabs() {
|
||||
$screen = get_current_screen();
|
||||
if ( ! $screen || self::$screen_id != $screen->id ) {
|
||||
return;
|
||||
}
|
||||
$as_version = ActionScheduler_Versions::instance()->latest_version();
|
||||
$screen->add_help_tab(
|
||||
array(
|
||||
'id' => 'action_scheduler_about',
|
||||
'title' => __( 'About', 'action-scheduler' ),
|
||||
'content' =>
|
||||
// translators: %s is the Action Scheduler version.
|
||||
'<h2>' . sprintf( __( 'About Action Scheduler %s', 'action-scheduler' ), $as_version ) . '</h2>' .
|
||||
'<p>' .
|
||||
__( 'Action Scheduler is a scalable, traceable job queue for background processing large sets of actions. Action Scheduler works by triggering an action hook to run at some time in the future. Scheduled actions can also be scheduled to run on a recurring schedule.', 'action-scheduler' ) .
|
||||
'</p>',
|
||||
)
|
||||
);
|
||||
$screen->add_help_tab(
|
||||
array(
|
||||
'id' => 'action_scheduler_columns',
|
||||
'title' => __( 'Columns', 'action-scheduler' ),
|
||||
'content' =>
|
||||
'<h2>' . __( 'Scheduled Action Columns', 'action-scheduler' ) . '</h2>' .
|
||||
'<ul>' .
|
||||
sprintf( '<li><strong>%1$s</strong>: %2$s</li>', __( 'Hook', 'action-scheduler' ), __( 'Name of the action hook that will be triggered.', 'action-scheduler' ) ) .
|
||||
sprintf( '<li><strong>%1$s</strong>: %2$s</li>', __( 'Status', 'action-scheduler' ), __( 'Action statuses are Pending, Complete, Canceled, Failed', 'action-scheduler' ) ) .
|
||||
sprintf( '<li><strong>%1$s</strong>: %2$s</li>', __( 'Arguments', 'action-scheduler' ), __( 'Optional data array passed to the action hook.', 'action-scheduler' ) ) .
|
||||
sprintf( '<li><strong>%1$s</strong>: %2$s</li>', __( 'Group', 'action-scheduler' ), __( 'Optional action group.', 'action-scheduler' ) ) .
|
||||
sprintf( '<li><strong>%1$s</strong>: %2$s</li>', __( 'Recurrence', 'action-scheduler' ), __( 'The action\'s schedule frequency.', 'action-scheduler' ) ) .
|
||||
sprintf( '<li><strong>%1$s</strong>: %2$s</li>', __( 'Scheduled', 'action-scheduler' ), __( 'The date/time the action is/was scheduled to run.', 'action-scheduler' ) ) .
|
||||
sprintf( '<li><strong>%1$s</strong>: %2$s</li>', __( 'Log', 'action-scheduler' ), __( 'Activity log for the action.', 'action-scheduler' ) ) .
|
||||
'</ul>',
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
class ActionScheduler_AsyncRequest_QueueRunner extends WP_Async_Request {
|
||||
protected $store;
|
||||
protected $prefix = 'as';
|
||||
protected $action = 'async_request_queue_runner';
|
||||
public function __construct( ActionScheduler_Store $store ) {
|
||||
parent::__construct();
|
||||
$this->store = $store;
|
||||
}
|
||||
protected function handle() {
|
||||
do_action( 'action_scheduler_run_queue', 'Async Request' ); // run a queue in the same way as WP Cron, but declare the Async Request context
|
||||
$sleep_seconds = $this->get_sleep_seconds();
|
||||
if ( $sleep_seconds ) {
|
||||
sleep( $sleep_seconds );
|
||||
}
|
||||
$this->maybe_dispatch();
|
||||
}
|
||||
public function maybe_dispatch() {
|
||||
if ( ! $this->allow() ) {
|
||||
return;
|
||||
}
|
||||
$this->dispatch();
|
||||
ActionScheduler_QueueRunner::instance()->unhook_dispatch_async_request();
|
||||
}
|
||||
protected function allow() {
|
||||
if ( ! has_action( 'action_scheduler_run_queue' ) || ActionScheduler::runner()->has_maximum_concurrent_batches() || ! $this->store->has_pending_actions_due() ) {
|
||||
$allow = false;
|
||||
} else {
|
||||
$allow = true;
|
||||
}
|
||||
return apply_filters( 'action_scheduler_allow_async_request_runner', $allow );
|
||||
}
|
||||
protected function get_sleep_seconds() {
|
||||
return apply_filters( 'action_scheduler_async_request_sleep_seconds', 5, $this );
|
||||
}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class ActionScheduler_Compatibility {
|
||||
public static function convert_hr_to_bytes( $value ) {
|
||||
if ( function_exists( 'wp_convert_hr_to_bytes' ) ) {
|
||||
return wp_convert_hr_to_bytes( $value );
|
||||
}
|
||||
$value = strtolower( trim( $value ) );
|
||||
$bytes = (int) $value;
|
||||
if ( false !== strpos( $value, 'g' ) ) {
|
||||
$bytes *= GB_IN_BYTES;
|
||||
} elseif ( false !== strpos( $value, 'm' ) ) {
|
||||
$bytes *= MB_IN_BYTES;
|
||||
} elseif ( false !== strpos( $value, 'k' ) ) {
|
||||
$bytes *= KB_IN_BYTES;
|
||||
}
|
||||
// Deal with large (float) values which run into the maximum integer size.
|
||||
return min( $bytes, PHP_INT_MAX );
|
||||
}
|
||||
public static function raise_memory_limit() {
|
||||
if ( function_exists( 'wp_raise_memory_limit' ) ) {
|
||||
return wp_raise_memory_limit( 'admin' );
|
||||
}
|
||||
$current_limit = @ini_get( 'memory_limit' );
|
||||
$current_limit_int = self::convert_hr_to_bytes( $current_limit );
|
||||
if ( -1 === $current_limit_int ) {
|
||||
return false;
|
||||
}
|
||||
$wp_max_limit = WP_MAX_MEMORY_LIMIT;
|
||||
$wp_max_limit_int = self::convert_hr_to_bytes( $wp_max_limit );
|
||||
$filtered_limit = apply_filters( 'admin_memory_limit', $wp_max_limit );
|
||||
$filtered_limit_int = self::convert_hr_to_bytes( $filtered_limit );
|
||||
if ( -1 === $filtered_limit_int || ( $filtered_limit_int > $wp_max_limit_int && $filtered_limit_int > $current_limit_int ) ) {
|
||||
if ( false !== @ini_set( 'memory_limit', $filtered_limit ) ) {
|
||||
return $filtered_limit;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} elseif ( -1 === $wp_max_limit_int || $wp_max_limit_int > $current_limit_int ) {
|
||||
if ( false !== @ini_set( 'memory_limit', $wp_max_limit ) ) {
|
||||
return $wp_max_limit;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public static function raise_time_limit( $limit = 0 ) {
|
||||
$limit = (int) $limit;
|
||||
$max_execution_time = (int) ini_get( 'max_execution_time' );
|
||||
// If the max execution time is already set to zero (unlimited), there is no reason to make a further change.
|
||||
if ( 0 === $max_execution_time ) {
|
||||
return;
|
||||
}
|
||||
// Whichever of $max_execution_time or $limit is higher is the amount by which we raise the time limit.
|
||||
$raise_by = 0 === $limit || $limit > $max_execution_time ? $limit : $max_execution_time;
|
||||
if ( function_exists( 'wc_set_time_limit' ) ) {
|
||||
wc_set_time_limit( $raise_by );
|
||||
} elseif ( function_exists( 'set_time_limit' ) && false === strpos( ini_get( 'disable_functions' ), 'set_time_limit' ) && ! ini_get( 'safe_mode' ) ) { // phpcs:ignore PHPCompatibility.IniDirectives.RemovedIniDirectives.safe_modeDeprecatedRemoved
|
||||
@set_time_limit( $raise_by ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
|
||||
}
|
||||
}
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use Action_Scheduler\Migration\Controller;
|
||||
class ActionScheduler_DataController {
|
||||
const DATASTORE_CLASS = 'ActionScheduler_DBStore';
|
||||
const LOGGER_CLASS = 'ActionScheduler_DBLogger';
|
||||
const STATUS_FLAG = 'action_scheduler_migration_status';
|
||||
const STATUS_COMPLETE = 'complete';
|
||||
const MIN_PHP_VERSION = '5.5';
|
||||
private static $instance;
|
||||
private static $sleep_time = 0;
|
||||
private static $free_ticks = 50;
|
||||
public static function dependencies_met() {
|
||||
$php_support = version_compare( PHP_VERSION, self::MIN_PHP_VERSION, '>=' );
|
||||
return $php_support && apply_filters( 'action_scheduler_migration_dependencies_met', true );
|
||||
}
|
||||
public static function is_migration_complete() {
|
||||
return get_option( self::STATUS_FLAG ) === self::STATUS_COMPLETE;
|
||||
}
|
||||
public static function mark_migration_complete() {
|
||||
update_option( self::STATUS_FLAG, self::STATUS_COMPLETE );
|
||||
}
|
||||
public static function mark_migration_incomplete() {
|
||||
delete_option( self::STATUS_FLAG );
|
||||
}
|
||||
public static function set_store_class( $class ) {
|
||||
return self::DATASTORE_CLASS;
|
||||
}
|
||||
public static function set_logger_class( $class ) {
|
||||
return self::LOGGER_CLASS;
|
||||
}
|
||||
public static function set_sleep_time( $sleep_time ) {
|
||||
self::$sleep_time = (int) $sleep_time;
|
||||
}
|
||||
public static function set_free_ticks( $free_ticks ) {
|
||||
self::$free_ticks = (int) $free_ticks;
|
||||
}
|
||||
public static function maybe_free_memory( $ticks ) {
|
||||
if ( self::$free_ticks && 0 === $ticks % self::$free_ticks ) {
|
||||
self::free_memory();
|
||||
}
|
||||
}
|
||||
public static function free_memory() {
|
||||
if ( 0 < self::$sleep_time ) {
|
||||
\WP_CLI::warning( sprintf( _n( 'Stopped the insanity for %d second', 'Stopped the insanity for %d seconds', self::$sleep_time, 'action-scheduler' ), self::$sleep_time ) );
|
||||
sleep( self::$sleep_time );
|
||||
}
|
||||
\WP_CLI::warning( __( 'Attempting to reduce used memory...', 'action-scheduler' ) );
|
||||
global $wpdb, $wp_object_cache;
|
||||
$wpdb->queries = array();
|
||||
if ( ! is_a( $wp_object_cache, 'WP_Object_Cache' ) ) {
|
||||
return;
|
||||
}
|
||||
$wp_object_cache->group_ops = array();
|
||||
$wp_object_cache->stats = array();
|
||||
$wp_object_cache->memcache_debug = array();
|
||||
$wp_object_cache->cache = array();
|
||||
if ( is_callable( array( $wp_object_cache, '__remoteset' ) ) ) {
|
||||
call_user_func( array( $wp_object_cache, '__remoteset' ) ); // important
|
||||
}
|
||||
}
|
||||
public static function init() {
|
||||
if ( self::is_migration_complete() ) {
|
||||
add_filter( 'action_scheduler_store_class', array( 'ActionScheduler_DataController', 'set_store_class' ), 100 );
|
||||
add_filter( 'action_scheduler_logger_class', array( 'ActionScheduler_DataController', 'set_logger_class' ), 100 );
|
||||
add_action( 'deactivate_plugin', array( 'ActionScheduler_DataController', 'mark_migration_incomplete' ) );
|
||||
} elseif ( self::dependencies_met() ) {
|
||||
Controller::init();
|
||||
}
|
||||
add_action( 'action_scheduler/progress_tick', array( 'ActionScheduler_DataController', 'maybe_free_memory' ) );
|
||||
}
|
||||
public static function instance() {
|
||||
if ( ! isset( self::$instance ) ) {
|
||||
self::$instance = new static();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
}
|
||||
wp-content/plugins/mailpoet/vendor/woocommerce/action-scheduler/classes/ActionScheduler_DateTime.php
Vendored
+25
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class ActionScheduler_DateTime extends DateTime {
|
||||
protected $utcOffset = 0;
|
||||
#[\ReturnTypeWillChange]
|
||||
public function getTimestamp() {
|
||||
return method_exists( 'DateTime', 'getTimestamp' ) ? parent::getTimestamp() : $this->format( 'U' );
|
||||
}
|
||||
public function setUtcOffset( $offset ) {
|
||||
$this->utcOffset = intval( $offset );
|
||||
}
|
||||
#[\ReturnTypeWillChange]
|
||||
public function getOffset() {
|
||||
return $this->utcOffset ? $this->utcOffset : parent::getOffset();
|
||||
}
|
||||
#[\ReturnTypeWillChange]
|
||||
public function setTimezone( $timezone ) {
|
||||
$this->utcOffset = 0;
|
||||
parent::setTimezone( $timezone );
|
||||
return $this;
|
||||
}
|
||||
public function getOffsetTimestamp() {
|
||||
return $this->getTimestamp() + $this->getOffset();
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
interface ActionScheduler_Exception {}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class ActionScheduler_FatalErrorMonitor {
|
||||
private $claim = NULL;
|
||||
private $store = NULL;
|
||||
private $action_id = 0;
|
||||
public function __construct( ActionScheduler_Store $store ) {
|
||||
$this->store = $store;
|
||||
}
|
||||
public function attach( ActionScheduler_ActionClaim $claim ) {
|
||||
$this->claim = $claim;
|
||||
add_action( 'shutdown', array( $this, 'handle_unexpected_shutdown' ) );
|
||||
add_action( 'action_scheduler_before_execute', array( $this, 'track_current_action' ), 0, 1 );
|
||||
add_action( 'action_scheduler_after_execute', array( $this, 'untrack_action' ), 0, 0 );
|
||||
add_action( 'action_scheduler_execution_ignored', array( $this, 'untrack_action' ), 0, 0 );
|
||||
add_action( 'action_scheduler_failed_execution', array( $this, 'untrack_action' ), 0, 0 );
|
||||
}
|
||||
public function detach() {
|
||||
$this->claim = NULL;
|
||||
$this->untrack_action();
|
||||
remove_action( 'shutdown', array( $this, 'handle_unexpected_shutdown' ) );
|
||||
remove_action( 'action_scheduler_before_execute', array( $this, 'track_current_action' ), 0 );
|
||||
remove_action( 'action_scheduler_after_execute', array( $this, 'untrack_action' ), 0 );
|
||||
remove_action( 'action_scheduler_execution_ignored', array( $this, 'untrack_action' ), 0 );
|
||||
remove_action( 'action_scheduler_failed_execution', array( $this, 'untrack_action' ), 0 );
|
||||
}
|
||||
public function track_current_action( $action_id ) {
|
||||
$this->action_id = $action_id;
|
||||
}
|
||||
public function untrack_action() {
|
||||
$this->action_id = 0;
|
||||
}
|
||||
public function handle_unexpected_shutdown() {
|
||||
if ( $error = error_get_last() ) {
|
||||
if ( in_array( $error['type'], array( E_ERROR, E_PARSE, E_COMPILE_ERROR, E_USER_ERROR, E_RECOVERABLE_ERROR ) ) ) {
|
||||
if ( !empty($this->action_id) ) {
|
||||
$this->store->mark_failure( $this->action_id );
|
||||
do_action( 'action_scheduler_unexpected_shutdown', $this->action_id, $error );
|
||||
}
|
||||
}
|
||||
$this->store->release_claim( $this->claim );
|
||||
}
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class ActionScheduler_InvalidActionException extends \InvalidArgumentException implements ActionScheduler_Exception {
|
||||
public static function from_schedule( $action_id, $schedule ) {
|
||||
$message = sprintf(
|
||||
__( 'Action [%1$s] has an invalid schedule: %2$s', 'action-scheduler' ),
|
||||
$action_id,
|
||||
var_export( $schedule, true )
|
||||
);
|
||||
return new static( $message );
|
||||
}
|
||||
public static function from_decoding_args( $action_id, $args = array() ) {
|
||||
$message = sprintf(
|
||||
__( 'Action [%1$s] has invalid arguments. It cannot be JSON decoded to an array. $args = %2$s', 'action-scheduler' ),
|
||||
$action_id,
|
||||
var_export( $args, true )
|
||||
);
|
||||
return new static( $message );
|
||||
}
|
||||
}
|
||||
+381
@@ -0,0 +1,381 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class ActionScheduler_ListTable extends ActionScheduler_Abstract_ListTable {
|
||||
protected $package = 'action-scheduler';
|
||||
protected $columns = array();
|
||||
protected $row_actions = array();
|
||||
protected $store;
|
||||
protected $logger;
|
||||
protected $runner;
|
||||
protected $bulk_actions = array();
|
||||
protected static $did_notification = false;
|
||||
private static $time_periods;
|
||||
public function __construct( ActionScheduler_Store $store, ActionScheduler_Logger $logger, ActionScheduler_QueueRunner $runner ) {
|
||||
$this->store = $store;
|
||||
$this->logger = $logger;
|
||||
$this->runner = $runner;
|
||||
$this->table_header = __( 'Scheduled Actions', 'action-scheduler' );
|
||||
$this->bulk_actions = array(
|
||||
'delete' => __( 'Delete', 'action-scheduler' ),
|
||||
);
|
||||
$this->columns = array(
|
||||
'hook' => __( 'Hook', 'action-scheduler' ),
|
||||
'status' => __( 'Status', 'action-scheduler' ),
|
||||
'args' => __( 'Arguments', 'action-scheduler' ),
|
||||
'group' => __( 'Group', 'action-scheduler' ),
|
||||
'recurrence' => __( 'Recurrence', 'action-scheduler' ),
|
||||
'schedule' => __( 'Scheduled Date', 'action-scheduler' ),
|
||||
'log_entries' => __( 'Log', 'action-scheduler' ),
|
||||
);
|
||||
$this->sort_by = array(
|
||||
'schedule',
|
||||
'hook',
|
||||
'group',
|
||||
);
|
||||
$this->search_by = array(
|
||||
'hook',
|
||||
'args',
|
||||
'claim_id',
|
||||
);
|
||||
$request_status = $this->get_request_status();
|
||||
if ( empty( $request_status ) ) {
|
||||
$this->sort_by[] = 'status';
|
||||
} elseif ( in_array( $request_status, array( 'in-progress', 'failed' ) ) ) {
|
||||
$this->columns += array( 'claim_id' => __( 'Claim ID', 'action-scheduler' ) );
|
||||
$this->sort_by[] = 'claim_id';
|
||||
}
|
||||
$this->row_actions = array(
|
||||
'hook' => array(
|
||||
'run' => array(
|
||||
'name' => __( 'Run', 'action-scheduler' ),
|
||||
'desc' => __( 'Process the action now as if it were run as part of a queue', 'action-scheduler' ),
|
||||
),
|
||||
'cancel' => array(
|
||||
'name' => __( 'Cancel', 'action-scheduler' ),
|
||||
'desc' => __( 'Cancel the action now to avoid it being run in future', 'action-scheduler' ),
|
||||
'class' => 'cancel trash',
|
||||
),
|
||||
),
|
||||
);
|
||||
self::$time_periods = array(
|
||||
array(
|
||||
'seconds' => YEAR_IN_SECONDS,
|
||||
'names' => _n_noop( '%s year', '%s years', 'action-scheduler' ),
|
||||
),
|
||||
array(
|
||||
'seconds' => MONTH_IN_SECONDS,
|
||||
'names' => _n_noop( '%s month', '%s months', 'action-scheduler' ),
|
||||
),
|
||||
array(
|
||||
'seconds' => WEEK_IN_SECONDS,
|
||||
'names' => _n_noop( '%s week', '%s weeks', 'action-scheduler' ),
|
||||
),
|
||||
array(
|
||||
'seconds' => DAY_IN_SECONDS,
|
||||
'names' => _n_noop( '%s day', '%s days', 'action-scheduler' ),
|
||||
),
|
||||
array(
|
||||
'seconds' => HOUR_IN_SECONDS,
|
||||
'names' => _n_noop( '%s hour', '%s hours', 'action-scheduler' ),
|
||||
),
|
||||
array(
|
||||
'seconds' => MINUTE_IN_SECONDS,
|
||||
'names' => _n_noop( '%s minute', '%s minutes', 'action-scheduler' ),
|
||||
),
|
||||
array(
|
||||
'seconds' => 1,
|
||||
'names' => _n_noop( '%s second', '%s seconds', 'action-scheduler' ),
|
||||
),
|
||||
);
|
||||
parent::__construct(
|
||||
array(
|
||||
'singular' => 'action-scheduler',
|
||||
'plural' => 'action-scheduler',
|
||||
'ajax' => false,
|
||||
)
|
||||
);
|
||||
add_screen_option(
|
||||
'per_page',
|
||||
array(
|
||||
'default' => $this->items_per_page,
|
||||
)
|
||||
);
|
||||
add_filter( 'set_screen_option_' . $this->get_per_page_option_name(), array( $this, 'set_items_per_page_option' ), 10, 3 );
|
||||
set_screen_options();
|
||||
}
|
||||
public function set_items_per_page_option( $status, $option, $value ) {
|
||||
return $value;
|
||||
}
|
||||
private static function human_interval( $interval, $periods_to_include = 2 ) {
|
||||
if ( $interval <= 0 ) {
|
||||
return __( 'Now!', 'action-scheduler' );
|
||||
}
|
||||
$output = '';
|
||||
for ( $time_period_index = 0, $periods_included = 0, $seconds_remaining = $interval; $time_period_index < count( self::$time_periods ) && $seconds_remaining > 0 && $periods_included < $periods_to_include; $time_period_index++ ) {
|
||||
$periods_in_interval = floor( $seconds_remaining / self::$time_periods[ $time_period_index ]['seconds'] );
|
||||
if ( $periods_in_interval > 0 ) {
|
||||
if ( ! empty( $output ) ) {
|
||||
$output .= ' ';
|
||||
}
|
||||
$output .= sprintf( translate_nooped_plural( self::$time_periods[ $time_period_index ]['names'], $periods_in_interval, 'action-scheduler' ), $periods_in_interval );
|
||||
$seconds_remaining -= $periods_in_interval * self::$time_periods[ $time_period_index ]['seconds'];
|
||||
$periods_included++;
|
||||
}
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
protected function get_recurrence( $action ) {
|
||||
$schedule = $action->get_schedule();
|
||||
if ( $schedule->is_recurring() && method_exists( $schedule, 'get_recurrence' ) ) {
|
||||
$recurrence = $schedule->get_recurrence();
|
||||
if ( is_numeric( $recurrence ) ) {
|
||||
return sprintf( __( 'Every %s', 'action-scheduler' ), self::human_interval( $recurrence ) );
|
||||
} else {
|
||||
return $recurrence;
|
||||
}
|
||||
}
|
||||
return __( 'Non-repeating', 'action-scheduler' );
|
||||
}
|
||||
public function column_args( array $row ) {
|
||||
if ( empty( $row['args'] ) ) {
|
||||
return apply_filters( 'action_scheduler_list_table_column_args', '', $row );
|
||||
}
|
||||
$row_html = '<ul>';
|
||||
foreach ( $row['args'] as $key => $value ) {
|
||||
$row_html .= sprintf( '<li><code>%s => %s</code></li>', esc_html( var_export( $key, true ) ), esc_html( var_export( $value, true ) ) );
|
||||
}
|
||||
$row_html .= '</ul>';
|
||||
return apply_filters( 'action_scheduler_list_table_column_args', $row_html, $row );
|
||||
}
|
||||
public function column_log_entries( array $row ) {
|
||||
$log_entries_html = '<ol>';
|
||||
$timezone = new DateTimezone( 'UTC' );
|
||||
foreach ( $row['log_entries'] as $log_entry ) {
|
||||
$log_entries_html .= $this->get_log_entry_html( $log_entry, $timezone );
|
||||
}
|
||||
$log_entries_html .= '</ol>';
|
||||
return $log_entries_html;
|
||||
}
|
||||
protected function get_log_entry_html( ActionScheduler_LogEntry $log_entry, DateTimezone $timezone ) {
|
||||
$date = $log_entry->get_date();
|
||||
$date->setTimezone( $timezone );
|
||||
return sprintf( '<li><strong>%s</strong><br/>%s</li>', esc_html( $date->format( 'Y-m-d H:i:s O' ) ), esc_html( $log_entry->get_message() ) );
|
||||
}
|
||||
protected function maybe_render_actions( $row, $column_name ) {
|
||||
if ( 'pending' === strtolower( $row[ 'status_name' ] ) ) {
|
||||
return parent::maybe_render_actions( $row, $column_name );
|
||||
}
|
||||
return '';
|
||||
}
|
||||
public function display_admin_notices() {
|
||||
global $wpdb;
|
||||
if ( ( is_a( $this->store, 'ActionScheduler_HybridStore' ) || is_a( $this->store, 'ActionScheduler_DBStore' ) ) && apply_filters( 'action_scheduler_enable_recreate_data_store', true ) ) {
|
||||
$table_list = array(
|
||||
'actionscheduler_actions',
|
||||
'actionscheduler_logs',
|
||||
'actionscheduler_groups',
|
||||
'actionscheduler_claims',
|
||||
);
|
||||
$found_tables = $wpdb->get_col( "SHOW TABLES LIKE '{$wpdb->prefix}actionscheduler%'" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
foreach ( $table_list as $table_name ) {
|
||||
if ( ! in_array( $wpdb->prefix . $table_name, $found_tables ) ) {
|
||||
$this->admin_notices[] = array(
|
||||
'class' => 'error',
|
||||
'message' => __( 'It appears one or more database tables were missing. Attempting to re-create the missing table(s).' , 'action-scheduler' ),
|
||||
);
|
||||
$this->recreate_tables();
|
||||
parent::display_admin_notices();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( $this->runner->has_maximum_concurrent_batches() ) {
|
||||
$claim_count = $this->store->get_claim_count();
|
||||
$this->admin_notices[] = array(
|
||||
'class' => 'updated',
|
||||
'message' => sprintf(
|
||||
_n(
|
||||
'Maximum simultaneous queues already in progress (%s queue). No additional queues will begin processing until the current queues are complete.',
|
||||
'Maximum simultaneous queues already in progress (%s queues). No additional queues will begin processing until the current queues are complete.',
|
||||
$claim_count,
|
||||
'action-scheduler'
|
||||
),
|
||||
$claim_count
|
||||
),
|
||||
);
|
||||
} elseif ( $this->store->has_pending_actions_due() ) {
|
||||
$async_request_lock_expiration = ActionScheduler::lock()->get_expiration( 'async-request-runner' );
|
||||
// No lock set or lock expired
|
||||
if ( false === $async_request_lock_expiration || $async_request_lock_expiration < time() ) {
|
||||
$in_progress_url = add_query_arg( 'status', 'in-progress', remove_query_arg( 'status' ) );
|
||||
$async_request_message = sprintf( __( 'A new queue has begun processing. <a href="%s">View actions in-progress »</a>', 'action-scheduler' ), esc_url( $in_progress_url ) );
|
||||
} else {
|
||||
$async_request_message = sprintf( __( 'The next queue will begin processing in approximately %d seconds.', 'action-scheduler' ), $async_request_lock_expiration - time() );
|
||||
}
|
||||
$this->admin_notices[] = array(
|
||||
'class' => 'notice notice-info',
|
||||
'message' => $async_request_message,
|
||||
);
|
||||
}
|
||||
$notification = get_transient( 'action_scheduler_admin_notice' );
|
||||
if ( is_array( $notification ) ) {
|
||||
delete_transient( 'action_scheduler_admin_notice' );
|
||||
$action = $this->store->fetch_action( $notification['action_id'] );
|
||||
$action_hook_html = '<strong><code>' . $action->get_hook() . '</code></strong>';
|
||||
if ( 1 == $notification['success'] ) {
|
||||
$class = 'updated';
|
||||
switch ( $notification['row_action_type'] ) {
|
||||
case 'run' :
|
||||
$action_message_html = sprintf( __( 'Successfully executed action: %s', 'action-scheduler' ), $action_hook_html );
|
||||
break;
|
||||
case 'cancel' :
|
||||
$action_message_html = sprintf( __( 'Successfully canceled action: %s', 'action-scheduler' ), $action_hook_html );
|
||||
break;
|
||||
default :
|
||||
$action_message_html = sprintf( __( 'Successfully processed change for action: %s', 'action-scheduler' ), $action_hook_html );
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
$class = 'error';
|
||||
$action_message_html = sprintf( __( 'Could not process change for action: "%1$s" (ID: %2$d). Error: %3$s', 'action-scheduler' ), $action_hook_html, esc_html( $notification['action_id'] ), esc_html( $notification['error_message'] ) );
|
||||
}
|
||||
$action_message_html = apply_filters( 'action_scheduler_admin_notice_html', $action_message_html, $action, $notification );
|
||||
$this->admin_notices[] = array(
|
||||
'class' => $class,
|
||||
'message' => $action_message_html,
|
||||
);
|
||||
}
|
||||
parent::display_admin_notices();
|
||||
}
|
||||
public function column_schedule( $row ) {
|
||||
return $this->get_schedule_display_string( $row['schedule'] );
|
||||
}
|
||||
protected function get_schedule_display_string( ActionScheduler_Schedule $schedule ) {
|
||||
$schedule_display_string = '';
|
||||
if ( is_a( $schedule, 'ActionScheduler_NullSchedule' ) ) {
|
||||
return __( 'async', 'action-scheduler' );
|
||||
}
|
||||
if ( ! method_exists( $schedule, 'get_date' ) || ! $schedule->get_date() ) {
|
||||
return '0000-00-00 00:00:00';
|
||||
}
|
||||
$next_timestamp = $schedule->get_date()->getTimestamp();
|
||||
$schedule_display_string .= $schedule->get_date()->format( 'Y-m-d H:i:s O' );
|
||||
$schedule_display_string .= '<br/>';
|
||||
if ( gmdate( 'U' ) > $next_timestamp ) {
|
||||
$schedule_display_string .= sprintf( __( ' (%s ago)', 'action-scheduler' ), self::human_interval( gmdate( 'U' ) - $next_timestamp ) );
|
||||
} else {
|
||||
$schedule_display_string .= sprintf( __( ' (%s)', 'action-scheduler' ), self::human_interval( $next_timestamp - gmdate( 'U' ) ) );
|
||||
}
|
||||
return $schedule_display_string;
|
||||
}
|
||||
protected function bulk_delete( array $ids, $ids_sql ) {
|
||||
foreach ( $ids as $id ) {
|
||||
try {
|
||||
$this->store->delete_action( $id );
|
||||
} catch ( Exception $e ) {
|
||||
// A possible reason for an exception would include a scenario where the same action is deleted by a
|
||||
// concurrent request.
|
||||
error_log(
|
||||
sprintf(
|
||||
__( 'Action Scheduler was unable to delete action %1$d. Reason: %2$s', 'action-scheduler' ),
|
||||
$id,
|
||||
$e->getMessage()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
protected function row_action_cancel( $action_id ) {
|
||||
$this->process_row_action( $action_id, 'cancel' );
|
||||
}
|
||||
protected function row_action_run( $action_id ) {
|
||||
$this->process_row_action( $action_id, 'run' );
|
||||
}
|
||||
protected function recreate_tables() {
|
||||
if ( is_a( $this->store, 'ActionScheduler_HybridStore' ) ) {
|
||||
$store = $this->store;
|
||||
} else {
|
||||
$store = new ActionScheduler_HybridStore();
|
||||
}
|
||||
add_action( 'action_scheduler/created_table', array( $store, 'set_autoincrement' ), 10, 2 );
|
||||
$store_schema = new ActionScheduler_StoreSchema();
|
||||
$logger_schema = new ActionScheduler_LoggerSchema();
|
||||
$store_schema->register_tables( true );
|
||||
$logger_schema->register_tables( true );
|
||||
remove_action( 'action_scheduler/created_table', array( $store, 'set_autoincrement' ), 10 );
|
||||
}
|
||||
protected function process_row_action( $action_id, $row_action_type ) {
|
||||
try {
|
||||
switch ( $row_action_type ) {
|
||||
case 'run' :
|
||||
$this->runner->process_action( $action_id, 'Admin List Table' );
|
||||
break;
|
||||
case 'cancel' :
|
||||
$this->store->cancel_action( $action_id );
|
||||
break;
|
||||
}
|
||||
$success = 1;
|
||||
$error_message = '';
|
||||
} catch ( Exception $e ) {
|
||||
$success = 0;
|
||||
$error_message = $e->getMessage();
|
||||
}
|
||||
set_transient( 'action_scheduler_admin_notice', compact( 'action_id', 'success', 'error_message', 'row_action_type' ), 30 );
|
||||
}
|
||||
public function prepare_items() {
|
||||
$this->prepare_column_headers();
|
||||
$per_page = $this->get_items_per_page( $this->get_per_page_option_name(), $this->items_per_page );
|
||||
$query = array(
|
||||
'per_page' => $per_page,
|
||||
'offset' => $this->get_items_offset(),
|
||||
'status' => $this->get_request_status(),
|
||||
'orderby' => $this->get_request_orderby(),
|
||||
'order' => $this->get_request_order(),
|
||||
'search' => $this->get_request_search_query(),
|
||||
);
|
||||
if ( 'past-due' === $this->get_request_status() ) {
|
||||
$query['status'] = ActionScheduler_Store::STATUS_PENDING;
|
||||
$query['date'] = as_get_datetime_object();
|
||||
}
|
||||
$this->items = array();
|
||||
$total_items = $this->store->query_actions( $query, 'count' );
|
||||
$status_labels = $this->store->get_status_labels();
|
||||
foreach ( $this->store->query_actions( $query ) as $action_id ) {
|
||||
try {
|
||||
$action = $this->store->fetch_action( $action_id );
|
||||
} catch ( Exception $e ) {
|
||||
continue;
|
||||
}
|
||||
if ( is_a( $action, 'ActionScheduler_NullAction' ) ) {
|
||||
continue;
|
||||
}
|
||||
$this->items[ $action_id ] = array(
|
||||
'ID' => $action_id,
|
||||
'hook' => $action->get_hook(),
|
||||
'status_name' => $this->store->get_status( $action_id ),
|
||||
'status' => $status_labels[ $this->store->get_status( $action_id ) ],
|
||||
'args' => $action->get_args(),
|
||||
'group' => $action->get_group(),
|
||||
'log_entries' => $this->logger->get_logs( $action_id ),
|
||||
'claim_id' => $this->store->get_claim_id( $action_id ),
|
||||
'recurrence' => $this->get_recurrence( $action ),
|
||||
'schedule' => $action->get_schedule(),
|
||||
);
|
||||
}
|
||||
$this->set_pagination_args( array(
|
||||
'total_items' => $total_items,
|
||||
'per_page' => $per_page,
|
||||
'total_pages' => ceil( $total_items / $per_page ),
|
||||
) );
|
||||
}
|
||||
protected function display_filter_by_status() {
|
||||
$this->status_counts = $this->store->action_counts() + $this->store->extra_action_counts();
|
||||
parent::display_filter_by_status();
|
||||
}
|
||||
protected function get_search_box_button_text() {
|
||||
return __( 'Search hook, args and claim ID', 'action-scheduler' );
|
||||
}
|
||||
protected function get_per_page_option_name() {
|
||||
return str_replace( '-', '_', $this->screen->id ) . '_per_page';
|
||||
}
|
||||
}
|
||||
wp-content/plugins/mailpoet/vendor/woocommerce/action-scheduler/classes/ActionScheduler_LogEntry.php
Vendored
+25
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class ActionScheduler_LogEntry {
|
||||
protected $action_id = '';
|
||||
protected $message = '';
|
||||
protected $date;
|
||||
public function __construct( $action_id, $message, $date = null ) {
|
||||
if ( null !== $date && ! is_a( $date, 'DateTime' ) ) {
|
||||
_doing_it_wrong( __METHOD__, 'The third parameter must be a valid DateTime instance, or null.', '2.0.0' );
|
||||
$date = null;
|
||||
}
|
||||
$this->action_id = $action_id;
|
||||
$this->message = $message;
|
||||
$this->date = $date ? $date : new Datetime;
|
||||
}
|
||||
public function get_date() {
|
||||
return $this->date;
|
||||
}
|
||||
public function get_action_id() {
|
||||
return $this->action_id;
|
||||
}
|
||||
public function get_message() {
|
||||
return $this->message;
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class ActionScheduler_NullLogEntry extends ActionScheduler_LogEntry {
|
||||
public function __construct( $action_id = '', $message = '' ) {
|
||||
// nothing to see here
|
||||
}
|
||||
}
|
||||
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class ActionScheduler_OptionLock extends ActionScheduler_Lock {
|
||||
public function set( $lock_type ) {
|
||||
global $wpdb;
|
||||
$lock_key = $this->get_key( $lock_type );
|
||||
$existing_lock_value = $this->get_existing_lock( $lock_type );
|
||||
$new_lock_value = $this->new_lock_value( $lock_type );
|
||||
// The lock may not exist yet, or may have been deleted.
|
||||
if ( empty( $existing_lock_value ) ) {
|
||||
return (bool) $wpdb->insert(
|
||||
$wpdb->options,
|
||||
array(
|
||||
'option_name' => $lock_key,
|
||||
'option_value' => $new_lock_value,
|
||||
'autoload' => 'no',
|
||||
)
|
||||
);
|
||||
}
|
||||
if ( $this->get_expiration_from( $existing_lock_value ) >= time() ) {
|
||||
return false;
|
||||
}
|
||||
// Otherwise, try to obtain the lock.
|
||||
return (bool) $wpdb->update(
|
||||
$wpdb->options,
|
||||
array( 'option_value' => $new_lock_value ),
|
||||
array(
|
||||
'option_name' => $lock_key,
|
||||
'option_value' => $existing_lock_value,
|
||||
)
|
||||
);
|
||||
}
|
||||
public function get_expiration( $lock_type ) {
|
||||
return $this->get_expiration_from( $this->get_existing_lock( $lock_type ) );
|
||||
}
|
||||
private function get_expiration_from( $lock_value ) {
|
||||
$lock_string = explode( '|', $lock_value );
|
||||
// Old style lock?
|
||||
if ( count( $lock_string ) === 1 && is_numeric( $lock_string[0] ) ) {
|
||||
return (int) $lock_string[0];
|
||||
}
|
||||
// New style lock?
|
||||
if ( count( $lock_string ) === 2 && is_numeric( $lock_string[1] ) ) {
|
||||
return (int) $lock_string[1];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
protected function get_key( $lock_type ) {
|
||||
return sprintf( 'action_scheduler_lock_%s', $lock_type );
|
||||
}
|
||||
private function get_existing_lock( $lock_type ) {
|
||||
global $wpdb;
|
||||
// Now grab the existing lock value, if there is one.
|
||||
return (string) $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT option_value FROM $wpdb->options WHERE option_name = %s",
|
||||
$this->get_key( $lock_type )
|
||||
)
|
||||
);
|
||||
}
|
||||
private function new_lock_value( $lock_type ) {
|
||||
return uniqid( '', true ) . '|' . ( time() + $this->get_duration( $lock_type ) );
|
||||
}
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class ActionScheduler_QueueCleaner {
|
||||
protected $batch_size;
|
||||
private $store = null;
|
||||
private $month_in_seconds = 2678400;
|
||||
private $default_statuses_to_purge = [
|
||||
ActionScheduler_Store::STATUS_COMPLETE,
|
||||
ActionScheduler_Store::STATUS_CANCELED,
|
||||
];
|
||||
public function __construct( ActionScheduler_Store $store = null, $batch_size = 20 ) {
|
||||
$this->store = $store ? $store : ActionScheduler_Store::instance();
|
||||
$this->batch_size = $batch_size;
|
||||
}
|
||||
public function delete_old_actions() {
|
||||
$lifespan = apply_filters( 'action_scheduler_retention_period', $this->month_in_seconds );
|
||||
try {
|
||||
$cutoff = as_get_datetime_object( $lifespan . ' seconds ago' );
|
||||
} catch ( Exception $e ) {
|
||||
_doing_it_wrong(
|
||||
__METHOD__,
|
||||
sprintf(
|
||||
esc_html__( 'It was not possible to determine a valid cut-off time: %s.', 'action-scheduler' ),
|
||||
esc_html( $e->getMessage() )
|
||||
),
|
||||
'3.5.5'
|
||||
);
|
||||
return array();
|
||||
}
|
||||
$statuses_to_purge = (array) apply_filters( 'action_scheduler_default_cleaner_statuses', $this->default_statuses_to_purge );
|
||||
return $this->clean_actions( $statuses_to_purge, $cutoff, $this->get_batch_size() );
|
||||
}
|
||||
public function clean_actions( array $statuses_to_purge, DateTime $cutoff_date, $batch_size = null, $context = 'old' ) {
|
||||
$batch_size = $batch_size !== null ? $batch_size : $this->batch_size;
|
||||
$cutoff = $cutoff_date !== null ? $cutoff_date : as_get_datetime_object( $this->month_in_seconds . ' seconds ago' );
|
||||
$lifespan = time() - $cutoff->getTimestamp();
|
||||
if ( empty( $statuses_to_purge ) ) {
|
||||
$statuses_to_purge = $this->default_statuses_to_purge;
|
||||
}
|
||||
$deleted_actions = [];
|
||||
foreach ( $statuses_to_purge as $status ) {
|
||||
$actions_to_delete = $this->store->query_actions( array(
|
||||
'status' => $status,
|
||||
'modified' => $cutoff,
|
||||
'modified_compare' => '<=',
|
||||
'per_page' => $batch_size,
|
||||
'orderby' => 'none',
|
||||
) );
|
||||
$deleted_actions = array_merge( $deleted_actions, $this->delete_actions( $actions_to_delete, $lifespan, $context ) );
|
||||
}
|
||||
return $deleted_actions;
|
||||
}
|
||||
private function delete_actions( array $actions_to_delete, $lifespan = null, $context = 'old' ) {
|
||||
$deleted_actions = [];
|
||||
if ( $lifespan === null ) {
|
||||
$lifespan = $this->month_in_seconds;
|
||||
}
|
||||
foreach ( $actions_to_delete as $action_id ) {
|
||||
try {
|
||||
$this->store->delete_action( $action_id );
|
||||
$deleted_actions[] = $action_id;
|
||||
} catch ( Exception $e ) {
|
||||
do_action( "action_scheduler_failed_{$context}_action_deletion", $action_id, $e, $lifespan, count( $actions_to_delete ) );
|
||||
}
|
||||
}
|
||||
return $deleted_actions;
|
||||
}
|
||||
public function reset_timeouts( $time_limit = 300 ) {
|
||||
$timeout = apply_filters( 'action_scheduler_timeout_period', $time_limit );
|
||||
if ( $timeout < 0 ) {
|
||||
return;
|
||||
}
|
||||
$cutoff = as_get_datetime_object($timeout.' seconds ago');
|
||||
$actions_to_reset = $this->store->query_actions( array(
|
||||
'status' => ActionScheduler_Store::STATUS_PENDING,
|
||||
'modified' => $cutoff,
|
||||
'modified_compare' => '<=',
|
||||
'claimed' => true,
|
||||
'per_page' => $this->get_batch_size(),
|
||||
'orderby' => 'none',
|
||||
) );
|
||||
foreach ( $actions_to_reset as $action_id ) {
|
||||
$this->store->unclaim_action( $action_id );
|
||||
do_action( 'action_scheduler_reset_action', $action_id );
|
||||
}
|
||||
}
|
||||
public function mark_failures( $time_limit = 300 ) {
|
||||
$timeout = apply_filters( 'action_scheduler_failure_period', $time_limit );
|
||||
if ( $timeout < 0 ) {
|
||||
return;
|
||||
}
|
||||
$cutoff = as_get_datetime_object($timeout.' seconds ago');
|
||||
$actions_to_reset = $this->store->query_actions( array(
|
||||
'status' => ActionScheduler_Store::STATUS_RUNNING,
|
||||
'modified' => $cutoff,
|
||||
'modified_compare' => '<=',
|
||||
'per_page' => $this->get_batch_size(),
|
||||
'orderby' => 'none',
|
||||
) );
|
||||
foreach ( $actions_to_reset as $action_id ) {
|
||||
$this->store->mark_failure( $action_id );
|
||||
do_action( 'action_scheduler_failed_action', $action_id, $timeout );
|
||||
}
|
||||
}
|
||||
public function clean( $time_limit = 300 ) {
|
||||
$this->delete_old_actions();
|
||||
$this->reset_timeouts( $time_limit );
|
||||
$this->mark_failures( $time_limit );
|
||||
}
|
||||
protected function get_batch_size() {
|
||||
return absint( apply_filters( 'action_scheduler_cleanup_batch_size', $this->batch_size ) );
|
||||
}
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class ActionScheduler_QueueRunner extends ActionScheduler_Abstract_QueueRunner {
|
||||
const WP_CRON_HOOK = 'action_scheduler_run_queue';
|
||||
const WP_CRON_SCHEDULE = 'every_minute';
|
||||
protected $async_request;
|
||||
private static $runner = null;
|
||||
private $processed_actions_count = 0;
|
||||
public static function instance() {
|
||||
if ( empty(self::$runner) ) {
|
||||
$class = apply_filters('action_scheduler_queue_runner_class', 'ActionScheduler_QueueRunner');
|
||||
self::$runner = new $class();
|
||||
}
|
||||
return self::$runner;
|
||||
}
|
||||
public function __construct( ActionScheduler_Store $store = null, ActionScheduler_FatalErrorMonitor $monitor = null, ActionScheduler_QueueCleaner $cleaner = null, ActionScheduler_AsyncRequest_QueueRunner $async_request = null ) {
|
||||
parent::__construct( $store, $monitor, $cleaner );
|
||||
if ( is_null( $async_request ) ) {
|
||||
$async_request = new ActionScheduler_AsyncRequest_QueueRunner( $this->store );
|
||||
}
|
||||
$this->async_request = $async_request;
|
||||
}
|
||||
public function init() {
|
||||
add_filter( 'cron_schedules', array( self::instance(), 'add_wp_cron_schedule' ) );
|
||||
// Check for and remove any WP Cron hook scheduled by Action Scheduler < 3.0.0, which didn't include the $context param
|
||||
$next_timestamp = wp_next_scheduled( self::WP_CRON_HOOK );
|
||||
if ( $next_timestamp ) {
|
||||
wp_unschedule_event( $next_timestamp, self::WP_CRON_HOOK );
|
||||
}
|
||||
$cron_context = array( 'WP Cron' );
|
||||
if ( ! wp_next_scheduled( self::WP_CRON_HOOK, $cron_context ) ) {
|
||||
$schedule = apply_filters( 'action_scheduler_run_schedule', self::WP_CRON_SCHEDULE );
|
||||
wp_schedule_event( time(), $schedule, self::WP_CRON_HOOK, $cron_context );
|
||||
}
|
||||
add_action( self::WP_CRON_HOOK, array( self::instance(), 'run' ) );
|
||||
$this->hook_dispatch_async_request();
|
||||
}
|
||||
public function hook_dispatch_async_request() {
|
||||
add_action( 'shutdown', array( $this, 'maybe_dispatch_async_request' ) );
|
||||
}
|
||||
public function unhook_dispatch_async_request() {
|
||||
remove_action( 'shutdown', array( $this, 'maybe_dispatch_async_request' ) );
|
||||
}
|
||||
public function maybe_dispatch_async_request() {
|
||||
// Only start an async queue at most once every 60 seconds.
|
||||
if (
|
||||
is_admin()
|
||||
&& ! ActionScheduler::lock()->is_locked( 'async-request-runner' )
|
||||
&& ActionScheduler::lock()->set( 'async-request-runner' )
|
||||
) {
|
||||
$this->async_request->maybe_dispatch();
|
||||
}
|
||||
}
|
||||
public function run( $context = 'WP Cron' ) {
|
||||
ActionScheduler_Compatibility::raise_memory_limit();
|
||||
ActionScheduler_Compatibility::raise_time_limit( $this->get_time_limit() );
|
||||
do_action( 'action_scheduler_before_process_queue' );
|
||||
$this->run_cleanup();
|
||||
$this->processed_actions_count = 0;
|
||||
if ( false === $this->has_maximum_concurrent_batches() ) {
|
||||
$batch_size = apply_filters( 'action_scheduler_queue_runner_batch_size', 25 );
|
||||
do {
|
||||
$processed_actions_in_batch = $this->do_batch( $batch_size, $context );
|
||||
$this->processed_actions_count += $processed_actions_in_batch;
|
||||
} while ( $processed_actions_in_batch > 0 && ! $this->batch_limits_exceeded( $this->processed_actions_count ) ); // keep going until we run out of actions, time, or memory
|
||||
}
|
||||
do_action( 'action_scheduler_after_process_queue' );
|
||||
return $this->processed_actions_count;
|
||||
}
|
||||
protected function do_batch( $size = 100, $context = '' ) {
|
||||
$claim = $this->store->stake_claim($size);
|
||||
$this->monitor->attach($claim);
|
||||
$processed_actions = 0;
|
||||
foreach ( $claim->get_actions() as $action_id ) {
|
||||
// bail if we lost the claim
|
||||
if ( ! in_array( $action_id, $this->store->find_actions_by_claim_id( $claim->get_id() ) ) ) {
|
||||
break;
|
||||
}
|
||||
$this->process_action( $action_id, $context );
|
||||
$processed_actions++;
|
||||
if ( $this->batch_limits_exceeded( $processed_actions + $this->processed_actions_count ) ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->store->release_claim($claim);
|
||||
$this->monitor->detach();
|
||||
$this->clear_caches();
|
||||
return $processed_actions;
|
||||
}
|
||||
protected function clear_caches() {
|
||||
$flushing_runtime_cache_explicitly_supported = function_exists( 'wp_cache_supports' ) && wp_cache_supports( 'flush_runtime' );
|
||||
$flushing_runtime_cache_implicitly_supported = ! function_exists( 'wp_cache_supports' ) && function_exists( 'wp_cache_flush_runtime' );
|
||||
if ( $flushing_runtime_cache_explicitly_supported || $flushing_runtime_cache_implicitly_supported ) {
|
||||
wp_cache_flush_runtime();
|
||||
} elseif (
|
||||
! wp_using_ext_object_cache()
|
||||
|| apply_filters( 'action_scheduler_queue_runner_flush_cache', false )
|
||||
) {
|
||||
wp_cache_flush();
|
||||
}
|
||||
}
|
||||
public function add_wp_cron_schedule( $schedules ) {
|
||||
$schedules['every_minute'] = array(
|
||||
'interval' => 60, // in seconds
|
||||
'display' => __( 'Every minute', 'action-scheduler' ),
|
||||
);
|
||||
return $schedules;
|
||||
}
|
||||
}
|
||||
wp-content/plugins/mailpoet/vendor/woocommerce/action-scheduler/classes/ActionScheduler_Versions.php
Vendored
+42
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class ActionScheduler_Versions {
|
||||
private static $instance = NULL;
|
||||
private $versions = array();
|
||||
public function register( $version_string, $initialization_callback ) {
|
||||
if ( isset($this->versions[$version_string]) ) {
|
||||
return FALSE;
|
||||
}
|
||||
$this->versions[$version_string] = $initialization_callback;
|
||||
return TRUE;
|
||||
}
|
||||
public function get_versions() {
|
||||
return $this->versions;
|
||||
}
|
||||
public function latest_version() {
|
||||
$keys = array_keys($this->versions);
|
||||
if ( empty($keys) ) {
|
||||
return false;
|
||||
}
|
||||
uasort( $keys, 'version_compare' );
|
||||
return end($keys);
|
||||
}
|
||||
public function latest_version_callback() {
|
||||
$latest = $this->latest_version();
|
||||
if ( empty($latest) || !isset($this->versions[$latest]) ) {
|
||||
return '__return_null';
|
||||
}
|
||||
return $this->versions[$latest];
|
||||
}
|
||||
public static function instance() {
|
||||
if ( empty(self::$instance) ) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
public static function initialize_latest_version() {
|
||||
$self = self::instance();
|
||||
call_user_func($self->latest_version_callback());
|
||||
}
|
||||
}
|
||||
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class ActionScheduler_WPCommentCleaner {
|
||||
protected static $cleanup_hook = 'action_scheduler/cleanup_wp_comment_logs';
|
||||
protected static $wp_comment_logger = null;
|
||||
protected static $has_logs_option_key = 'as_has_wp_comment_logs';
|
||||
public static function init() {
|
||||
if ( empty( self::$wp_comment_logger ) ) {
|
||||
self::$wp_comment_logger = new ActionScheduler_wpCommentLogger();
|
||||
}
|
||||
add_action( self::$cleanup_hook, array( __CLASS__, 'delete_all_action_comments' ) );
|
||||
// While there are orphaned logs left in the comments table, we need to attach the callbacks which filter comment counts.
|
||||
add_action( 'pre_get_comments', array( self::$wp_comment_logger, 'filter_comment_queries' ), 10, 1 );
|
||||
add_action( 'wp_count_comments', array( self::$wp_comment_logger, 'filter_comment_count' ), 20, 2 ); // run after WC_Comments::wp_count_comments() to make sure we exclude order notes and action logs
|
||||
add_action( 'comment_feed_where', array( self::$wp_comment_logger, 'filter_comment_feed' ), 10, 2 );
|
||||
// Action Scheduler may be displayed as a Tools screen or WooCommerce > Status administration screen
|
||||
add_action( 'load-tools_page_action-scheduler', array( __CLASS__, 'register_admin_notice' ) );
|
||||
add_action( 'load-woocommerce_page_wc-status', array( __CLASS__, 'register_admin_notice' ) );
|
||||
}
|
||||
public static function has_logs() {
|
||||
return 'yes' === get_option( self::$has_logs_option_key );
|
||||
}
|
||||
public static function maybe_schedule_cleanup() {
|
||||
if ( (bool) get_comments( array( 'type' => ActionScheduler_wpCommentLogger::TYPE, 'number' => 1, 'fields' => 'ids' ) ) ) {
|
||||
update_option( self::$has_logs_option_key, 'yes' );
|
||||
if ( ! as_next_scheduled_action( self::$cleanup_hook ) ) {
|
||||
as_schedule_single_action( gmdate( 'U' ) + ( 6 * MONTH_IN_SECONDS ), self::$cleanup_hook );
|
||||
}
|
||||
}
|
||||
}
|
||||
public static function delete_all_action_comments() {
|
||||
global $wpdb;
|
||||
$wpdb->delete( $wpdb->comments, array( 'comment_type' => ActionScheduler_wpCommentLogger::TYPE, 'comment_agent' => ActionScheduler_wpCommentLogger::AGENT ) );
|
||||
delete_option( self::$has_logs_option_key );
|
||||
}
|
||||
public static function register_admin_notice() {
|
||||
add_action( 'admin_notices', array( __CLASS__, 'print_admin_notice' ) );
|
||||
}
|
||||
public static function print_admin_notice() {
|
||||
$next_cleanup_message = '';
|
||||
$next_scheduled_cleanup_hook = as_next_scheduled_action( self::$cleanup_hook );
|
||||
if ( $next_scheduled_cleanup_hook ) {
|
||||
$next_cleanup_message = sprintf( __( 'This data will be deleted in %s.', 'action-scheduler' ), human_time_diff( gmdate( 'U' ), $next_scheduled_cleanup_hook ) );
|
||||
}
|
||||
$notice = sprintf(
|
||||
__( 'Action Scheduler has migrated data to custom tables; however, orphaned log entries exist in the WordPress Comments table. %1$s <a href="%2$s">Learn more »</a>', 'action-scheduler' ),
|
||||
$next_cleanup_message,
|
||||
'https://github.com/woocommerce/action-scheduler/issues/368'
|
||||
);
|
||||
echo '<div class="notice notice-warning"><p>' . wp_kses_post( $notice ) . '</p></div>';
|
||||
}
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class ActionScheduler_wcSystemStatus {
|
||||
protected $store;
|
||||
public function __construct( $store ) {
|
||||
$this->store = $store;
|
||||
}
|
||||
public function render() {
|
||||
$action_counts = $this->store->action_counts();
|
||||
$status_labels = $this->store->get_status_labels();
|
||||
$oldest_and_newest = $this->get_oldest_and_newest( array_keys( $status_labels ) );
|
||||
$this->get_template( $status_labels, $action_counts, $oldest_and_newest );
|
||||
}
|
||||
protected function get_oldest_and_newest( $status_keys ) {
|
||||
$oldest_and_newest = array();
|
||||
foreach ( $status_keys as $status ) {
|
||||
$oldest_and_newest[ $status ] = array(
|
||||
'oldest' => '–',
|
||||
'newest' => '–',
|
||||
);
|
||||
if ( 'in-progress' === $status ) {
|
||||
continue;
|
||||
}
|
||||
$oldest_and_newest[ $status ]['oldest'] = $this->get_action_status_date( $status, 'oldest' );
|
||||
$oldest_and_newest[ $status ]['newest'] = $this->get_action_status_date( $status, 'newest' );
|
||||
}
|
||||
return $oldest_and_newest;
|
||||
}
|
||||
protected function get_action_status_date( $status, $date_type = 'oldest' ) {
|
||||
$order = 'oldest' === $date_type ? 'ASC' : 'DESC';
|
||||
$action = $this->store->query_actions(
|
||||
array(
|
||||
'claimed' => false,
|
||||
'status' => $status,
|
||||
'per_page' => 1,
|
||||
'order' => $order,
|
||||
)
|
||||
);
|
||||
if ( ! empty( $action ) ) {
|
||||
$date_object = $this->store->get_date( $action[0] );
|
||||
$action_date = $date_object->format( 'Y-m-d H:i:s O' );
|
||||
} else {
|
||||
$action_date = '–';
|
||||
}
|
||||
return $action_date;
|
||||
}
|
||||
protected function get_template( $status_labels, $action_counts, $oldest_and_newest ) {
|
||||
$as_version = ActionScheduler_Versions::instance()->latest_version();
|
||||
$as_datastore = get_class( ActionScheduler_Store::instance() );
|
||||
?>
|
||||
<table class="wc_status_table widefat" cellspacing="0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="5" data-export-label="Action Scheduler"><h2><?php esc_html_e( 'Action Scheduler', 'action-scheduler' ); ?><?php echo wc_help_tip( esc_html__( 'This section shows details of Action Scheduler.', 'action-scheduler' ) ); ?></h2></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" data-export-label="Version"><?php esc_html_e( 'Version:', 'action-scheduler' ); ?></td>
|
||||
<td colspan="3"><?php echo esc_html( $as_version ); ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" data-export-label="Data store"><?php esc_html_e( 'Data store:', 'action-scheduler' ); ?></td>
|
||||
<td colspan="3"><?php echo esc_html( $as_datastore ); ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong><?php esc_html_e( 'Action Status', 'action-scheduler' ); ?></strong></td>
|
||||
<td class="help"> </td>
|
||||
<td><strong><?php esc_html_e( 'Count', 'action-scheduler' ); ?></strong></td>
|
||||
<td><strong><?php esc_html_e( 'Oldest Scheduled Date', 'action-scheduler' ); ?></strong></td>
|
||||
<td><strong><?php esc_html_e( 'Newest Scheduled Date', 'action-scheduler' ); ?></strong></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php
|
||||
foreach ( $action_counts as $status => $count ) {
|
||||
// WC uses the 3rd column for export, so we need to display more data in that (hidden when viewed as part of the table) and add an empty 2nd column.
|
||||
printf(
|
||||
'<tr><td>%1$s</td><td> </td><td>%2$s<span style="display: none;">, Oldest: %3$s, Newest: %4$s</span></td><td>%3$s</td><td>%4$s</td></tr>',
|
||||
esc_html( $status_labels[ $status ] ),
|
||||
esc_html( number_format_i18n( $count ) ),
|
||||
esc_html( $oldest_and_newest[ $status ]['oldest'] ),
|
||||
esc_html( $oldest_and_newest[ $status ]['newest'] )
|
||||
);
|
||||
}
|
||||
?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php
|
||||
}
|
||||
public function __call( $name, $arguments ) {
|
||||
switch ( $name ) {
|
||||
case 'print':
|
||||
_deprecated_function( __CLASS__ . '::print()', '2.2.4', __CLASS__ . '::render()' );
|
||||
return call_user_func_array( array( $this, 'render' ), $arguments );
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class ActionScheduler_WPCLI_Clean_Command extends WP_CLI_Command {
|
||||
public function clean( $args, $assoc_args ) {
|
||||
// Handle passed arguments.
|
||||
$batch = absint( \WP_CLI\Utils\get_flag_value( $assoc_args, 'batch-size', 20 ) );
|
||||
$batches = absint( \WP_CLI\Utils\get_flag_value( $assoc_args, 'batches', 0 ) );
|
||||
$status = explode( ',', WP_CLI\Utils\get_flag_value( $assoc_args, 'status', '' ) );
|
||||
$status = array_filter( array_map( 'trim', $status ) );
|
||||
$before = \WP_CLI\Utils\get_flag_value( $assoc_args, 'before', '' );
|
||||
$sleep = \WP_CLI\Utils\get_flag_value( $assoc_args, 'pause', 0 );
|
||||
$batches_completed = 0;
|
||||
$actions_deleted = 0;
|
||||
$unlimited = $batches === 0;
|
||||
try {
|
||||
$lifespan = as_get_datetime_object( $before );
|
||||
} catch ( Exception $e ) {
|
||||
$lifespan = null;
|
||||
}
|
||||
try {
|
||||
// Custom queue cleaner instance.
|
||||
$cleaner = new ActionScheduler_QueueCleaner( null, $batch );
|
||||
// Clean actions for as long as possible.
|
||||
while ( $unlimited || $batches_completed < $batches ) {
|
||||
if ( $sleep && $batches_completed > 0 ) {
|
||||
sleep( $sleep );
|
||||
}
|
||||
$deleted = count( $cleaner->clean_actions( $status, $lifespan, null,'CLI' ) );
|
||||
if ( $deleted <= 0 ) {
|
||||
break;
|
||||
}
|
||||
$actions_deleted += $deleted;
|
||||
$batches_completed++;
|
||||
$this->print_success( $deleted );
|
||||
}
|
||||
} catch ( Exception $e ) {
|
||||
$this->print_error( $e );
|
||||
}
|
||||
$this->print_total_batches( $batches_completed );
|
||||
if ( $batches_completed > 1 ) {
|
||||
$this->print_success( $actions_deleted );
|
||||
}
|
||||
}
|
||||
protected function print_total_batches( int $batches_processed ) {
|
||||
WP_CLI::log(
|
||||
sprintf(
|
||||
_n( '%d batch processed.', '%d batches processed.', $batches_processed, 'action-scheduler' ),
|
||||
$batches_processed
|
||||
)
|
||||
);
|
||||
}
|
||||
protected function print_error( Exception $e ) {
|
||||
WP_CLI::error(
|
||||
sprintf(
|
||||
__( 'There was an error deleting an action: %s', 'action-scheduler' ),
|
||||
$e->getMessage()
|
||||
)
|
||||
);
|
||||
}
|
||||
protected function print_success( int $actions_deleted ) {
|
||||
WP_CLI::success(
|
||||
sprintf(
|
||||
_n( '%d action deleted.', '%d actions deleted.', $actions_deleted, 'action-scheduler' ),
|
||||
$actions_deleted
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use Action_Scheduler\WP_CLI\ProgressBar;
|
||||
class ActionScheduler_WPCLI_QueueRunner extends ActionScheduler_Abstract_QueueRunner {
|
||||
protected $actions;
|
||||
protected $claim;
|
||||
protected $progress_bar;
|
||||
public function __construct( ActionScheduler_Store $store = null, ActionScheduler_FatalErrorMonitor $monitor = null, ActionScheduler_QueueCleaner $cleaner = null ) {
|
||||
if ( ! ( defined( 'WP_CLI' ) && WP_CLI ) ) {
|
||||
throw new Exception( sprintf( __( 'The %s class can only be run within WP CLI.', 'action-scheduler' ), __CLASS__ ) );
|
||||
}
|
||||
parent::__construct( $store, $monitor, $cleaner );
|
||||
}
|
||||
public function setup( $batch_size, $hooks = array(), $group = '', $force = false ) {
|
||||
$this->run_cleanup();
|
||||
$this->add_hooks();
|
||||
// Check to make sure there aren't too many concurrent processes running.
|
||||
if ( $this->has_maximum_concurrent_batches() ) {
|
||||
if ( $force ) {
|
||||
WP_CLI::warning( __( 'There are too many concurrent batches, but the run is forced to continue.', 'action-scheduler' ) );
|
||||
} else {
|
||||
WP_CLI::error( __( 'There are too many concurrent batches.', 'action-scheduler' ) );
|
||||
}
|
||||
}
|
||||
// Stake a claim and store it.
|
||||
$this->claim = $this->store->stake_claim( $batch_size, null, $hooks, $group );
|
||||
$this->monitor->attach( $this->claim );
|
||||
$this->actions = $this->claim->get_actions();
|
||||
return count( $this->actions );
|
||||
}
|
||||
protected function add_hooks() {
|
||||
add_action( 'action_scheduler_before_execute', array( $this, 'before_execute' ) );
|
||||
add_action( 'action_scheduler_after_execute', array( $this, 'after_execute' ), 10, 2 );
|
||||
add_action( 'action_scheduler_failed_execution', array( $this, 'action_failed' ), 10, 2 );
|
||||
}
|
||||
protected function setup_progress_bar() {
|
||||
$count = count( $this->actions );
|
||||
$this->progress_bar = new ProgressBar(
|
||||
sprintf( _n( 'Running %d action', 'Running %d actions', $count, 'action-scheduler' ), $count ),
|
||||
$count
|
||||
);
|
||||
}
|
||||
public function run( $context = 'WP CLI' ) {
|
||||
do_action( 'action_scheduler_before_process_queue' );
|
||||
$this->setup_progress_bar();
|
||||
foreach ( $this->actions as $action_id ) {
|
||||
// Error if we lost the claim.
|
||||
if ( ! in_array( $action_id, $this->store->find_actions_by_claim_id( $this->claim->get_id() ) ) ) {
|
||||
WP_CLI::warning( __( 'The claim has been lost. Aborting current batch.', 'action-scheduler' ) );
|
||||
break;
|
||||
}
|
||||
$this->process_action( $action_id, $context );
|
||||
$this->progress_bar->tick();
|
||||
}
|
||||
$completed = $this->progress_bar->current();
|
||||
$this->progress_bar->finish();
|
||||
$this->store->release_claim( $this->claim );
|
||||
do_action( 'action_scheduler_after_process_queue' );
|
||||
return $completed;
|
||||
}
|
||||
public function before_execute( $action_id ) {
|
||||
WP_CLI::log( sprintf( __( 'Started processing action %s', 'action-scheduler' ), $action_id ) );
|
||||
}
|
||||
public function after_execute( $action_id, $action = null ) {
|
||||
// backward compatibility
|
||||
if ( null === $action ) {
|
||||
$action = $this->store->fetch_action( $action_id );
|
||||
}
|
||||
WP_CLI::log( sprintf( __( 'Completed processing action %1$s with hook: %2$s', 'action-scheduler' ), $action_id, $action->get_hook() ) );
|
||||
}
|
||||
public function action_failed( $action_id, $exception ) {
|
||||
WP_CLI::error(
|
||||
sprintf( __( 'Error processing action %1$s: %2$s', 'action-scheduler' ), $action_id, $exception->getMessage() ),
|
||||
false
|
||||
);
|
||||
}
|
||||
protected function stop_the_insanity( $sleep_time = 0 ) {
|
||||
_deprecated_function( 'ActionScheduler_WPCLI_QueueRunner::stop_the_insanity', '3.0.0', 'ActionScheduler_DataController::free_memory' );
|
||||
ActionScheduler_DataController::free_memory();
|
||||
}
|
||||
protected function maybe_stop_the_insanity() {
|
||||
// The value returned by progress_bar->current() might be padded. Remove padding, and convert to int.
|
||||
$current_iteration = intval( trim( $this->progress_bar->current() ) );
|
||||
if ( 0 === $current_iteration % 50 ) {
|
||||
$this->stop_the_insanity();
|
||||
}
|
||||
}
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class ActionScheduler_WPCLI_Scheduler_command extends WP_CLI_Command {
|
||||
public function fix_schema( $args, $assoc_args ) {
|
||||
$schema_classes = array( ActionScheduler_LoggerSchema::class, ActionScheduler_StoreSchema::class );
|
||||
foreach ( $schema_classes as $classname ) {
|
||||
if ( is_subclass_of( $classname, ActionScheduler_Abstract_Schema::class ) ) {
|
||||
$obj = new $classname();
|
||||
$obj->init();
|
||||
$obj->register_tables( true );
|
||||
WP_CLI::success(
|
||||
sprintf(
|
||||
__( 'Registered schema for %s', 'action-scheduler' ),
|
||||
$classname
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
public function run( $args, $assoc_args ) {
|
||||
// Handle passed arguments.
|
||||
$batch = absint( \WP_CLI\Utils\get_flag_value( $assoc_args, 'batch-size', 100 ) );
|
||||
$batches = absint( \WP_CLI\Utils\get_flag_value( $assoc_args, 'batches', 0 ) );
|
||||
$clean = absint( \WP_CLI\Utils\get_flag_value( $assoc_args, 'cleanup-batch-size', $batch ) );
|
||||
$hooks = explode( ',', WP_CLI\Utils\get_flag_value( $assoc_args, 'hooks', '' ) );
|
||||
$hooks = array_filter( array_map( 'trim', $hooks ) );
|
||||
$group = \WP_CLI\Utils\get_flag_value( $assoc_args, 'group', '' );
|
||||
$exclude_groups = \WP_CLI\Utils\get_flag_value( $assoc_args, 'exclude-groups', '' );
|
||||
$free_on = \WP_CLI\Utils\get_flag_value( $assoc_args, 'free-memory-on', 50 );
|
||||
$sleep = \WP_CLI\Utils\get_flag_value( $assoc_args, 'pause', 0 );
|
||||
$force = \WP_CLI\Utils\get_flag_value( $assoc_args, 'force', false );
|
||||
ActionScheduler_DataController::set_free_ticks( $free_on );
|
||||
ActionScheduler_DataController::set_sleep_time( $sleep );
|
||||
$batches_completed = 0;
|
||||
$actions_completed = 0;
|
||||
$unlimited = $batches === 0;
|
||||
if ( is_callable( [ ActionScheduler::store(), 'set_claim_filter' ] ) ) {
|
||||
$exclude_groups = $this->parse_comma_separated_string( $exclude_groups );
|
||||
if ( ! empty( $exclude_groups ) ) {
|
||||
ActionScheduler::store()->set_claim_filter('exclude-groups', $exclude_groups );
|
||||
}
|
||||
}
|
||||
try {
|
||||
// Custom queue cleaner instance.
|
||||
$cleaner = new ActionScheduler_QueueCleaner( null, $clean );
|
||||
// Get the queue runner instance
|
||||
$runner = new ActionScheduler_WPCLI_QueueRunner( null, null, $cleaner );
|
||||
// Determine how many tasks will be run in the first batch.
|
||||
$total = $runner->setup( $batch, $hooks, $group, $force );
|
||||
// Run actions for as long as possible.
|
||||
while ( $total > 0 ) {
|
||||
$this->print_total_actions( $total );
|
||||
$actions_completed += $runner->run();
|
||||
$batches_completed++;
|
||||
// Maybe set up tasks for the next batch.
|
||||
$total = ( $unlimited || $batches_completed < $batches ) ? $runner->setup( $batch, $hooks, $group, $force ) : 0;
|
||||
}
|
||||
} catch ( Exception $e ) {
|
||||
$this->print_error( $e );
|
||||
}
|
||||
$this->print_total_batches( $batches_completed );
|
||||
$this->print_success( $actions_completed );
|
||||
}
|
||||
private function parse_comma_separated_string( $string ): array {
|
||||
return array_filter( str_getcsv( $string ) );
|
||||
}
|
||||
protected function print_total_actions( $total ) {
|
||||
WP_CLI::log(
|
||||
sprintf(
|
||||
_n( 'Found %d scheduled task', 'Found %d scheduled tasks', $total, 'action-scheduler' ),
|
||||
$total
|
||||
)
|
||||
);
|
||||
}
|
||||
protected function print_total_batches( $batches_completed ) {
|
||||
WP_CLI::log(
|
||||
sprintf(
|
||||
_n( '%d batch executed.', '%d batches executed.', $batches_completed, 'action-scheduler' ),
|
||||
$batches_completed
|
||||
)
|
||||
);
|
||||
}
|
||||
protected function print_error( Exception $e ) {
|
||||
WP_CLI::error(
|
||||
sprintf(
|
||||
__( 'There was an error running the action scheduler: %s', 'action-scheduler' ),
|
||||
$e->getMessage()
|
||||
)
|
||||
);
|
||||
}
|
||||
protected function print_success( $actions_completed ) {
|
||||
WP_CLI::success(
|
||||
sprintf(
|
||||
_n( '%d scheduled task completed.', '%d scheduled tasks completed.', $actions_completed, 'action-scheduler' ),
|
||||
$actions_completed
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
wp-content/plugins/mailpoet/vendor/woocommerce/action-scheduler/classes/WP_CLI/Migration_Command.php
Vendored
+101
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
namespace Action_Scheduler\WP_CLI;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use Action_Scheduler\Migration\Config;
|
||||
use Action_Scheduler\Migration\Runner;
|
||||
use Action_Scheduler\Migration\Scheduler;
|
||||
use Action_Scheduler\Migration\Controller;
|
||||
use WP_CLI;
|
||||
use WP_CLI_Command;
|
||||
class Migration_Command extends WP_CLI_Command {
|
||||
private $total_processed = 0;
|
||||
public function register() {
|
||||
if ( ! defined( 'WP_CLI' ) || ! WP_CLI ) {
|
||||
return;
|
||||
}
|
||||
WP_CLI::add_command( 'action-scheduler migrate', [ $this, 'migrate' ], [
|
||||
'shortdesc' => 'Migrates actions to the DB tables store',
|
||||
'synopsis' => [
|
||||
[
|
||||
'type' => 'assoc',
|
||||
'name' => 'batch-size',
|
||||
'optional' => true,
|
||||
'default' => 100,
|
||||
'description' => 'The number of actions to process in each batch',
|
||||
],
|
||||
[
|
||||
'type' => 'assoc',
|
||||
'name' => 'free-memory-on',
|
||||
'optional' => true,
|
||||
'default' => 50,
|
||||
'description' => 'The number of actions to process between freeing memory. 0 disables freeing memory',
|
||||
],
|
||||
[
|
||||
'type' => 'assoc',
|
||||
'name' => 'pause',
|
||||
'optional' => true,
|
||||
'default' => 0,
|
||||
'description' => 'The number of seconds to pause when freeing memory',
|
||||
],
|
||||
[
|
||||
'type' => 'flag',
|
||||
'name' => 'dry-run',
|
||||
'optional' => true,
|
||||
'description' => 'Reports on the actions that would have been migrated, but does not change any data',
|
||||
],
|
||||
],
|
||||
] );
|
||||
}
|
||||
public function migrate( $positional_args, $assoc_args ) {
|
||||
$this->init_logging();
|
||||
$config = $this->get_migration_config( $assoc_args );
|
||||
$runner = new Runner( $config );
|
||||
$runner->init_destination();
|
||||
$batch_size = isset( $assoc_args[ 'batch-size' ] ) ? (int) $assoc_args[ 'batch-size' ] : 100;
|
||||
$free_on = isset( $assoc_args[ 'free-memory-on' ] ) ? (int) $assoc_args[ 'free-memory-on' ] : 50;
|
||||
$sleep = isset( $assoc_args[ 'pause' ] ) ? (int) $assoc_args[ 'pause' ] : 0;
|
||||
\ActionScheduler_DataController::set_free_ticks( $free_on );
|
||||
\ActionScheduler_DataController::set_sleep_time( $sleep );
|
||||
do {
|
||||
$actions_processed = $runner->run( $batch_size );
|
||||
$this->total_processed += $actions_processed;
|
||||
} while ( $actions_processed > 0 );
|
||||
if ( ! $config->get_dry_run() ) {
|
||||
// let the scheduler know that there's nothing left to do
|
||||
$scheduler = new Scheduler();
|
||||
$scheduler->mark_complete();
|
||||
}
|
||||
WP_CLI::success( sprintf( '%s complete. %d actions processed.', $config->get_dry_run() ? 'Dry run' : 'Migration', $this->total_processed ) );
|
||||
}
|
||||
private function get_migration_config( $args ) {
|
||||
$args = wp_parse_args( $args, [
|
||||
'dry-run' => false,
|
||||
] );
|
||||
$config = Controller::instance()->get_migration_config_object();
|
||||
$config->set_dry_run( ! empty( $args[ 'dry-run' ] ) );
|
||||
return $config;
|
||||
}
|
||||
private function init_logging() {
|
||||
add_action( 'action_scheduler/migrate_action_dry_run', function ( $action_id ) {
|
||||
WP_CLI::debug( sprintf( 'Dry-run: migrated action %d', $action_id ) );
|
||||
}, 10, 1 );
|
||||
add_action( 'action_scheduler/no_action_to_migrate', function ( $action_id ) {
|
||||
WP_CLI::debug( sprintf( 'No action found to migrate for ID %d', $action_id ) );
|
||||
}, 10, 1 );
|
||||
add_action( 'action_scheduler/migrate_action_failed', function ( $action_id ) {
|
||||
WP_CLI::warning( sprintf( 'Failed migrating action with ID %d', $action_id ) );
|
||||
}, 10, 1 );
|
||||
add_action( 'action_scheduler/migrate_action_incomplete', function ( $source_id, $destination_id ) {
|
||||
WP_CLI::warning( sprintf( 'Unable to remove source action with ID %d after migrating to new ID %d', $source_id, $destination_id ) );
|
||||
}, 10, 2 );
|
||||
add_action( 'action_scheduler/migrated_action', function ( $source_id, $destination_id ) {
|
||||
WP_CLI::debug( sprintf( 'Migrated source action with ID %d to new store with ID %d', $source_id, $destination_id ) );
|
||||
}, 10, 2 );
|
||||
add_action( 'action_scheduler/migration_batch_starting', function ( $batch ) {
|
||||
WP_CLI::debug( 'Beginning migration of batch: ' . print_r( $batch, true ) );
|
||||
}, 10, 1 );
|
||||
add_action( 'action_scheduler/migration_batch_complete', function ( $batch ) {
|
||||
WP_CLI::log( sprintf( 'Completed migration of %d actions', count( $batch ) ) );
|
||||
}, 10, 1 );
|
||||
}
|
||||
}
|
||||
Vendored
+50
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
namespace Action_Scheduler\WP_CLI;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class ProgressBar {
|
||||
protected $total_ticks;
|
||||
protected $count;
|
||||
protected $interval;
|
||||
protected $message;
|
||||
protected $progress_bar;
|
||||
public function __construct( $message, $count, $interval = 100 ) {
|
||||
if ( ! ( defined( 'WP_CLI' ) && WP_CLI ) ) {
|
||||
throw new \Exception( sprintf( __( 'The %s class can only be run within WP CLI.', 'action-scheduler' ), __CLASS__ ) );
|
||||
}
|
||||
$this->total_ticks = 0;
|
||||
$this->message = $message;
|
||||
$this->count = $count;
|
||||
$this->interval = $interval;
|
||||
}
|
||||
public function tick() {
|
||||
if ( null === $this->progress_bar ) {
|
||||
$this->setup_progress_bar();
|
||||
}
|
||||
$this->progress_bar->tick();
|
||||
$this->total_ticks++;
|
||||
do_action( 'action_scheduler/progress_tick', $this->total_ticks );
|
||||
}
|
||||
public function current() {
|
||||
return $this->progress_bar ? $this->progress_bar->current() : 0;
|
||||
}
|
||||
public function finish() {
|
||||
if ( null !== $this->progress_bar ) {
|
||||
$this->progress_bar->finish();
|
||||
}
|
||||
$this->progress_bar = null;
|
||||
}
|
||||
public function set_message( $message ) {
|
||||
$this->message = $message;
|
||||
}
|
||||
public function set_count( $count ) {
|
||||
$this->count = $count;
|
||||
$this->finish();
|
||||
}
|
||||
protected function setup_progress_bar() {
|
||||
$this->progress_bar = \WP_CLI\Utils\make_progress_bar(
|
||||
$this->message,
|
||||
$this->count,
|
||||
$this->interval
|
||||
);
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
<?php
|
||||
+208
@@ -0,0 +1,208 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use Action_Scheduler\WP_CLI\Migration_Command;
|
||||
use Action_Scheduler\Migration\Controller;
|
||||
abstract class ActionScheduler {
|
||||
private static $plugin_file = '';
|
||||
private static $factory = NULL;
|
||||
private static $data_store_initialized = false;
|
||||
public static function factory() {
|
||||
if ( !isset(self::$factory) ) {
|
||||
self::$factory = new ActionScheduler_ActionFactory();
|
||||
}
|
||||
return self::$factory;
|
||||
}
|
||||
public static function store() {
|
||||
return ActionScheduler_Store::instance();
|
||||
}
|
||||
public static function lock() {
|
||||
return ActionScheduler_Lock::instance();
|
||||
}
|
||||
public static function logger() {
|
||||
return ActionScheduler_Logger::instance();
|
||||
}
|
||||
public static function runner() {
|
||||
return ActionScheduler_QueueRunner::instance();
|
||||
}
|
||||
public static function admin_view() {
|
||||
return ActionScheduler_AdminView::instance();
|
||||
}
|
||||
public static function plugin_path( $path ) {
|
||||
$base = dirname(self::$plugin_file);
|
||||
if ( $path ) {
|
||||
return trailingslashit($base).$path;
|
||||
} else {
|
||||
return untrailingslashit($base);
|
||||
}
|
||||
}
|
||||
public static function plugin_url( $path ) {
|
||||
return plugins_url($path, self::$plugin_file);
|
||||
}
|
||||
public static function autoload( $class ) {
|
||||
$d = DIRECTORY_SEPARATOR;
|
||||
$classes_dir = self::plugin_path( 'classes' . $d );
|
||||
$separator = strrpos( $class, '\\' );
|
||||
if ( false !== $separator ) {
|
||||
if ( 0 !== strpos( $class, 'Action_Scheduler' ) ) {
|
||||
return;
|
||||
}
|
||||
$class = substr( $class, $separator + 1 );
|
||||
}
|
||||
if ( 'Deprecated' === substr( $class, -10 ) ) {
|
||||
$dir = self::plugin_path( 'deprecated' . $d );
|
||||
} elseif ( self::is_class_abstract( $class ) ) {
|
||||
$dir = $classes_dir . 'abstracts' . $d;
|
||||
} elseif ( self::is_class_migration( $class ) ) {
|
||||
$dir = $classes_dir . 'migration' . $d;
|
||||
} elseif ( 'Schedule' === substr( $class, -8 ) ) {
|
||||
$dir = $classes_dir . 'schedules' . $d;
|
||||
} elseif ( 'Action' === substr( $class, -6 ) ) {
|
||||
$dir = $classes_dir . 'actions' . $d;
|
||||
} elseif ( 'Schema' === substr( $class, -6 ) ) {
|
||||
$dir = $classes_dir . 'schema' . $d;
|
||||
} elseif ( strpos( $class, 'ActionScheduler' ) === 0 ) {
|
||||
$segments = explode( '_', $class );
|
||||
$type = isset( $segments[ 1 ] ) ? $segments[ 1 ] : '';
|
||||
switch ( $type ) {
|
||||
case 'WPCLI':
|
||||
$dir = $classes_dir . 'WP_CLI' . $d;
|
||||
break;
|
||||
case 'DBLogger':
|
||||
case 'DBStore':
|
||||
case 'HybridStore':
|
||||
case 'wpPostStore':
|
||||
case 'wpCommentLogger':
|
||||
$dir = $classes_dir . 'data-stores' . $d;
|
||||
break;
|
||||
default:
|
||||
$dir = $classes_dir;
|
||||
break;
|
||||
}
|
||||
} elseif ( self::is_class_cli( $class ) ) {
|
||||
$dir = $classes_dir . 'WP_CLI' . $d;
|
||||
} elseif ( strpos( $class, 'CronExpression' ) === 0 ) {
|
||||
$dir = self::plugin_path( 'lib' . $d . 'cron-expression' . $d );
|
||||
} elseif ( strpos( $class, 'WP_Async_Request' ) === 0 ) {
|
||||
$dir = self::plugin_path( 'lib' . $d );
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
if ( file_exists( $dir . "{$class}.php" ) ) {
|
||||
include( $dir . "{$class}.php" );
|
||||
return;
|
||||
}
|
||||
}
|
||||
public static function init( $plugin_file ) {
|
||||
self::$plugin_file = $plugin_file;
|
||||
spl_autoload_register( array( __CLASS__, 'autoload' ) );
|
||||
do_action( 'action_scheduler_pre_init' );
|
||||
require_once( self::plugin_path( 'functions.php' ) );
|
||||
ActionScheduler_DataController::init();
|
||||
$store = self::store();
|
||||
$logger = self::logger();
|
||||
$runner = self::runner();
|
||||
$admin_view = self::admin_view();
|
||||
// Ensure initialization on plugin activation.
|
||||
if ( ! did_action( 'init' ) ) {
|
||||
add_action( 'init', array( $admin_view, 'init' ), 0, 0 ); // run before $store::init()
|
||||
add_action( 'init', array( $store, 'init' ), 1, 0 );
|
||||
add_action( 'init', array( $logger, 'init' ), 1, 0 );
|
||||
add_action( 'init', array( $runner, 'init' ), 1, 0 );
|
||||
add_action(
|
||||
'init',
|
||||
function () {
|
||||
self::$data_store_initialized = true;
|
||||
do_action( 'action_scheduler_init' );
|
||||
},
|
||||
1
|
||||
);
|
||||
} else {
|
||||
$admin_view->init();
|
||||
$store->init();
|
||||
$logger->init();
|
||||
$runner->init();
|
||||
self::$data_store_initialized = true;
|
||||
do_action( 'action_scheduler_init' );
|
||||
}
|
||||
if ( apply_filters( 'action_scheduler_load_deprecated_functions', true ) ) {
|
||||
require_once( self::plugin_path( 'deprecated/functions.php' ) );
|
||||
}
|
||||
if ( defined( 'WP_CLI' ) && WP_CLI ) {
|
||||
WP_CLI::add_command( 'action-scheduler', 'ActionScheduler_WPCLI_Scheduler_command' );
|
||||
WP_CLI::add_command( 'action-scheduler', 'ActionScheduler_WPCLI_Clean_Command' );
|
||||
if ( ! ActionScheduler_DataController::is_migration_complete() && Controller::instance()->allow_migration() ) {
|
||||
$command = new Migration_Command();
|
||||
$command->register();
|
||||
}
|
||||
}
|
||||
if ( is_a( $logger, 'ActionScheduler_DBLogger' ) && ActionScheduler_DataController::is_migration_complete() && ActionScheduler_WPCommentCleaner::has_logs() ) {
|
||||
ActionScheduler_WPCommentCleaner::init();
|
||||
}
|
||||
add_action( 'action_scheduler/migration_complete', 'ActionScheduler_WPCommentCleaner::maybe_schedule_cleanup' );
|
||||
}
|
||||
public static function is_initialized( $function_name = null ) {
|
||||
if ( ! self::$data_store_initialized && ! empty( $function_name ) ) {
|
||||
$message = sprintf(
|
||||
__( '%s() was called before the Action Scheduler data store was initialized', 'action-scheduler' ),
|
||||
esc_attr( $function_name )
|
||||
);
|
||||
_doing_it_wrong( $function_name, $message, '3.1.6' );
|
||||
}
|
||||
return self::$data_store_initialized;
|
||||
}
|
||||
protected static function is_class_abstract( $class ) {
|
||||
static $abstracts = array(
|
||||
'ActionScheduler' => true,
|
||||
'ActionScheduler_Abstract_ListTable' => true,
|
||||
'ActionScheduler_Abstract_QueueRunner' => true,
|
||||
'ActionScheduler_Abstract_Schedule' => true,
|
||||
'ActionScheduler_Abstract_RecurringSchedule' => true,
|
||||
'ActionScheduler_Lock' => true,
|
||||
'ActionScheduler_Logger' => true,
|
||||
'ActionScheduler_Abstract_Schema' => true,
|
||||
'ActionScheduler_Store' => true,
|
||||
'ActionScheduler_TimezoneHelper' => true,
|
||||
);
|
||||
return isset( $abstracts[ $class ] ) && $abstracts[ $class ];
|
||||
}
|
||||
protected static function is_class_migration( $class ) {
|
||||
static $migration_segments = array(
|
||||
'ActionMigrator' => true,
|
||||
'BatchFetcher' => true,
|
||||
'DBStoreMigrator' => true,
|
||||
'DryRun' => true,
|
||||
'LogMigrator' => true,
|
||||
'Config' => true,
|
||||
'Controller' => true,
|
||||
'Runner' => true,
|
||||
'Scheduler' => true,
|
||||
);
|
||||
$segments = explode( '_', $class );
|
||||
$segment = isset( $segments[ 1 ] ) ? $segments[ 1 ] : $class;
|
||||
return isset( $migration_segments[ $segment ] ) && $migration_segments[ $segment ];
|
||||
}
|
||||
protected static function is_class_cli( $class ) {
|
||||
static $cli_segments = array(
|
||||
'QueueRunner' => true,
|
||||
'Command' => true,
|
||||
'ProgressBar' => true,
|
||||
);
|
||||
$segments = explode( '_', $class );
|
||||
$segment = isset( $segments[ 1 ] ) ? $segments[ 1 ] : $class;
|
||||
return isset( $cli_segments[ $segment ] ) && $cli_segments[ $segment ];
|
||||
}
|
||||
final public function __clone() {
|
||||
trigger_error("Singleton. No cloning allowed!", E_USER_ERROR);
|
||||
}
|
||||
final public function __wakeup() {
|
||||
trigger_error("Singleton. No serialization allowed!", E_USER_ERROR);
|
||||
}
|
||||
final private function __construct() {}
|
||||
public static function get_datetime_object( $when = null, $timezone = 'UTC' ) {
|
||||
_deprecated_function( __METHOD__, '2.0', 'wcs_add_months()' );
|
||||
return as_get_datetime_object( $when, $timezone );
|
||||
}
|
||||
public static function check_shutdown_hook( $function_name ) {
|
||||
_deprecated_function( __FUNCTION__, '3.1.6' );
|
||||
}
|
||||
}
|
||||
+394
@@ -0,0 +1,394 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
if ( ! class_exists( 'WP_List_Table' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
|
||||
}
|
||||
abstract class ActionScheduler_Abstract_ListTable extends WP_List_Table {
|
||||
protected $table_name;
|
||||
protected $package;
|
||||
protected $items_per_page = 10;
|
||||
protected $search_by = array();
|
||||
protected $columns = array();
|
||||
protected $row_actions = array();
|
||||
protected $ID = 'ID';
|
||||
protected $sort_by = array();
|
||||
protected $filter_by = array();
|
||||
protected $status_counts = array();
|
||||
protected $admin_notices = array();
|
||||
protected $table_header;
|
||||
protected $bulk_actions = array();
|
||||
protected function translate( $text, $context = '' ) {
|
||||
return $text;
|
||||
}
|
||||
protected function get_bulk_actions() {
|
||||
$actions = array();
|
||||
foreach ( $this->bulk_actions as $action => $label ) {
|
||||
if ( ! is_callable( array( $this, 'bulk_' . $action ) ) ) {
|
||||
throw new RuntimeException( "The bulk action $action does not have a callback method" );
|
||||
}
|
||||
$actions[ $action ] = $label;
|
||||
}
|
||||
return $actions;
|
||||
}
|
||||
protected function process_bulk_action() {
|
||||
global $wpdb;
|
||||
// Detect when a bulk action is being triggered.
|
||||
$action = $this->current_action();
|
||||
if ( ! $action ) {
|
||||
return;
|
||||
}
|
||||
check_admin_referer( 'bulk-' . $this->_args['plural'] );
|
||||
$method = 'bulk_' . $action;
|
||||
if ( array_key_exists( $action, $this->bulk_actions ) && is_callable( array( $this, $method ) ) && ! empty( $_GET['ID'] ) && is_array( $_GET['ID'] ) ) {
|
||||
$ids_sql = '(' . implode( ',', array_fill( 0, count( $_GET['ID'] ), '%s' ) ) . ')';
|
||||
$id = array_map( 'absint', $_GET['ID'] );
|
||||
$this->$method( $id, $wpdb->prepare( $ids_sql, $id ) ); //phpcs:ignore WordPress.DB.PreparedSQL
|
||||
}
|
||||
if ( isset( $_SERVER['REQUEST_URI'] ) ) {
|
||||
wp_safe_redirect(
|
||||
remove_query_arg(
|
||||
array( '_wp_http_referer', '_wpnonce', 'ID', 'action', 'action2' ),
|
||||
esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) )
|
||||
)
|
||||
);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
protected function bulk_delete( array $ids, $ids_sql ) {
|
||||
$store = ActionScheduler::store();
|
||||
foreach ( $ids as $action_id ) {
|
||||
$store->delete( $action_id );
|
||||
}
|
||||
}
|
||||
protected function prepare_column_headers() {
|
||||
$this->_column_headers = array(
|
||||
$this->get_columns(),
|
||||
get_hidden_columns( $this->screen ),
|
||||
$this->get_sortable_columns(),
|
||||
);
|
||||
}
|
||||
public function get_sortable_columns() {
|
||||
$sort_by = array();
|
||||
foreach ( $this->sort_by as $column ) {
|
||||
$sort_by[ $column ] = array( $column, true );
|
||||
}
|
||||
return $sort_by;
|
||||
}
|
||||
public function get_columns() {
|
||||
$columns = array_merge(
|
||||
array( 'cb' => '<input type="checkbox" />' ),
|
||||
$this->columns
|
||||
);
|
||||
return $columns;
|
||||
}
|
||||
protected function get_items_query_limit() {
|
||||
global $wpdb;
|
||||
$per_page = $this->get_items_per_page( $this->get_per_page_option_name(), $this->items_per_page );
|
||||
return $wpdb->prepare( 'LIMIT %d', $per_page );
|
||||
}
|
||||
protected function get_items_offset() {
|
||||
$per_page = $this->get_items_per_page( $this->get_per_page_option_name(), $this->items_per_page );
|
||||
$current_page = $this->get_pagenum();
|
||||
if ( 1 < $current_page ) {
|
||||
$offset = $per_page * ( $current_page - 1 );
|
||||
} else {
|
||||
$offset = 0;
|
||||
}
|
||||
return $offset;
|
||||
}
|
||||
protected function get_items_query_offset() {
|
||||
global $wpdb;
|
||||
return $wpdb->prepare( 'OFFSET %d', $this->get_items_offset() );
|
||||
}
|
||||
protected function get_items_query_order() {
|
||||
if ( empty( $this->sort_by ) ) {
|
||||
return '';
|
||||
}
|
||||
$orderby = esc_sql( $this->get_request_orderby() );
|
||||
$order = esc_sql( $this->get_request_order() );
|
||||
return "ORDER BY {$orderby} {$order}";
|
||||
}
|
||||
protected function get_request_query_args_to_persist() {
|
||||
return array_merge(
|
||||
$this->sort_by,
|
||||
array(
|
||||
'page',
|
||||
'status',
|
||||
'tab',
|
||||
)
|
||||
);
|
||||
}
|
||||
protected function get_request_orderby() {
|
||||
$valid_sortable_columns = array_values( $this->sort_by );
|
||||
if ( ! empty( $_GET['orderby'] ) && in_array( $_GET['orderby'], $valid_sortable_columns, true ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$orderby = sanitize_text_field( wp_unslash( $_GET['orderby'] ) ); //phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
} else {
|
||||
$orderby = $valid_sortable_columns[0];
|
||||
}
|
||||
return $orderby;
|
||||
}
|
||||
protected function get_request_order() {
|
||||
if ( ! empty( $_GET['order'] ) && 'desc' === strtolower( sanitize_text_field( wp_unslash( $_GET['order'] ) ) ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$order = 'DESC';
|
||||
} else {
|
||||
$order = 'ASC';
|
||||
}
|
||||
return $order;
|
||||
}
|
||||
protected function get_request_status() {
|
||||
$status = ( ! empty( $_GET['status'] ) ) ? sanitize_text_field( wp_unslash( $_GET['status'] ) ) : ''; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
return $status;
|
||||
}
|
||||
protected function get_request_search_query() {
|
||||
$search_query = ( ! empty( $_GET['s'] ) ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : ''; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
return $search_query;
|
||||
}
|
||||
protected function get_table_columns() {
|
||||
$columns = array_keys( $this->columns );
|
||||
if ( ! in_array( $this->ID, $columns, true ) ) {
|
||||
$columns[] = $this->ID;
|
||||
}
|
||||
return $columns;
|
||||
}
|
||||
protected function get_items_query_search() {
|
||||
global $wpdb;
|
||||
if ( empty( $_GET['s'] ) || empty( $this->search_by ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
return '';
|
||||
}
|
||||
$search_string = sanitize_text_field( wp_unslash( $_GET['s'] ) ); //phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$filter = array();
|
||||
foreach ( $this->search_by as $column ) {
|
||||
$wild = '%';
|
||||
$sql_like = $wild . $wpdb->esc_like( $search_string ) . $wild;
|
||||
$filter[] = $wpdb->prepare( '`' . $column . '` LIKE %s', $sql_like ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.DB.PreparedSQL.NotPrepared
|
||||
}
|
||||
return implode( ' OR ', $filter );
|
||||
}
|
||||
protected function get_items_query_filters() {
|
||||
global $wpdb;
|
||||
if ( ! $this->filter_by || empty( $_GET['filter_by'] ) || ! is_array( $_GET['filter_by'] ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
return '';
|
||||
}
|
||||
$filter = array();
|
||||
foreach ( $this->filter_by as $column => $options ) {
|
||||
if ( empty( $_GET['filter_by'][ $column ] ) || empty( $options[ $_GET['filter_by'][ $column ] ] ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
continue;
|
||||
}
|
||||
$filter[] = $wpdb->prepare( "`$column` = %s", sanitize_text_field( wp_unslash( $_GET['filter_by'][ $column ] ) ) ); //phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
}
|
||||
return implode( ' AND ', $filter );
|
||||
}
|
||||
public function prepare_items() {
|
||||
global $wpdb;
|
||||
$this->process_bulk_action();
|
||||
$this->process_row_actions();
|
||||
if ( ! empty( $_REQUEST['_wp_http_referer'] && ! empty( $_SERVER['REQUEST_URI'] ) ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
// _wp_http_referer is used only on bulk actions, we remove it to keep the $_GET shorter
|
||||
wp_safe_redirect( remove_query_arg( array( '_wp_http_referer', '_wpnonce' ), esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) ) );
|
||||
exit;
|
||||
}
|
||||
$this->prepare_column_headers();
|
||||
$limit = $this->get_items_query_limit();
|
||||
$offset = $this->get_items_query_offset();
|
||||
$order = $this->get_items_query_order();
|
||||
$where = array_filter(
|
||||
array(
|
||||
$this->get_items_query_search(),
|
||||
$this->get_items_query_filters(),
|
||||
)
|
||||
);
|
||||
$columns = '`' . implode( '`, `', $this->get_table_columns() ) . '`';
|
||||
if ( ! empty( $where ) ) {
|
||||
$where = 'WHERE (' . implode( ') AND (', $where ) . ')';
|
||||
} else {
|
||||
$where = '';
|
||||
}
|
||||
$sql = "SELECT $columns FROM {$this->table_name} {$where} {$order} {$limit} {$offset}";
|
||||
$this->set_items( $wpdb->get_results( $sql, ARRAY_A ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
$query_count = "SELECT COUNT({$this->ID}) FROM {$this->table_name} {$where}";
|
||||
$total_items = $wpdb->get_var( $query_count ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
$per_page = $this->get_items_per_page( $this->get_per_page_option_name(), $this->items_per_page );
|
||||
$this->set_pagination_args(
|
||||
array(
|
||||
'total_items' => $total_items,
|
||||
'per_page' => $per_page,
|
||||
'total_pages' => ceil( $total_items / $per_page ),
|
||||
)
|
||||
);
|
||||
}
|
||||
public function extra_tablenav( $which ) {
|
||||
if ( ! $this->filter_by || 'top' !== $which ) {
|
||||
return;
|
||||
}
|
||||
echo '<div class="alignleft actions">';
|
||||
foreach ( $this->filter_by as $id => $options ) {
|
||||
$default = ! empty( $_GET['filter_by'][ $id ] ) ? sanitize_text_field( wp_unslash( $_GET['filter_by'][ $id ] ) ) : ''; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( empty( $options[ $default ] ) ) {
|
||||
$default = '';
|
||||
}
|
||||
echo '<select name="filter_by[' . esc_attr( $id ) . ']" class="first" id="filter-by-' . esc_attr( $id ) . '">';
|
||||
foreach ( $options as $value => $label ) {
|
||||
echo '<option value="' . esc_attr( $value ) . '" ' . esc_html( $value === $default ? 'selected' : '' ) . '>'
|
||||
. esc_html( $label )
|
||||
. '</option>';
|
||||
}
|
||||
echo '</select>';
|
||||
}
|
||||
submit_button( esc_html__( 'Filter', 'action-scheduler' ), '', 'filter_action', false, array( 'id' => 'post-query-submit' ) );
|
||||
echo '</div>';
|
||||
}
|
||||
protected function set_items( array $items ) {
|
||||
$this->items = array();
|
||||
foreach ( $items as $item ) {
|
||||
$this->items[ $item[ $this->ID ] ] = array_map( 'maybe_unserialize', $item );
|
||||
}
|
||||
}
|
||||
public function column_cb( $row ) {
|
||||
return '<input name="ID[]" type="checkbox" value="' . esc_attr( $row[ $this->ID ] ) . '" />';
|
||||
}
|
||||
protected function maybe_render_actions( $row, $column_name ) {
|
||||
if ( empty( $this->row_actions[ $column_name ] ) ) {
|
||||
return;
|
||||
}
|
||||
$row_id = $row[ $this->ID ];
|
||||
$actions = '<div class="row-actions">';
|
||||
$action_count = 0;
|
||||
foreach ( $this->row_actions[ $column_name ] as $action_key => $action ) {
|
||||
$action_count++;
|
||||
if ( ! method_exists( $this, 'row_action_' . $action_key ) ) {
|
||||
continue;
|
||||
}
|
||||
$action_link = ! empty( $action['link'] ) ? $action['link'] : add_query_arg(
|
||||
array(
|
||||
'row_action' => $action_key,
|
||||
'row_id' => $row_id,
|
||||
'nonce' => wp_create_nonce( $action_key . '::' . $row_id ),
|
||||
)
|
||||
);
|
||||
$span_class = ! empty( $action['class'] ) ? $action['class'] : $action_key;
|
||||
$separator = ( $action_count < count( $this->row_actions[ $column_name ] ) ) ? ' | ' : '';
|
||||
$actions .= sprintf( '<span class="%s">', esc_attr( $span_class ) );
|
||||
$actions .= sprintf( '<a href="%1$s" title="%2$s">%3$s</a>', esc_url( $action_link ), esc_attr( $action['desc'] ), esc_html( $action['name'] ) );
|
||||
$actions .= sprintf( '%s</span>', $separator );
|
||||
}
|
||||
$actions .= '</div>';
|
||||
return $actions;
|
||||
}
|
||||
protected function process_row_actions() {
|
||||
$parameters = array( 'row_action', 'row_id', 'nonce' );
|
||||
foreach ( $parameters as $parameter ) {
|
||||
if ( empty( $_REQUEST[ $parameter ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
return;
|
||||
}
|
||||
}
|
||||
$action = sanitize_text_field( wp_unslash( $_REQUEST['row_action'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotValidated
|
||||
$row_id = sanitize_text_field( wp_unslash( $_REQUEST['row_id'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotValidated
|
||||
$nonce = sanitize_text_field( wp_unslash( $_REQUEST['nonce'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotValidated
|
||||
$method = 'row_action_' . $action; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( wp_verify_nonce( $nonce, $action . '::' . $row_id ) && method_exists( $this, $method ) ) {
|
||||
$this->$method( sanitize_text_field( wp_unslash( $row_id ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
}
|
||||
if ( isset( $_SERVER['REQUEST_URI'] ) ) {
|
||||
wp_safe_redirect(
|
||||
remove_query_arg(
|
||||
array( 'row_id', 'row_action', 'nonce' ),
|
||||
esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) )
|
||||
)
|
||||
);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
public function column_default( $item, $column_name ) {
|
||||
$column_html = esc_html( $item[ $column_name ] );
|
||||
$column_html .= $this->maybe_render_actions( $item, $column_name );
|
||||
return $column_html;
|
||||
}
|
||||
protected function display_header() {
|
||||
echo '<h1 class="wp-heading-inline">' . esc_attr( $this->table_header ) . '</h1>';
|
||||
if ( $this->get_request_search_query() ) {
|
||||
echo '<span class="subtitle">' . esc_attr( sprintf( __( 'Search results for "%s"', 'action-scheduler' ), $this->get_request_search_query() ) ) . '</span>';
|
||||
}
|
||||
echo '<hr class="wp-header-end">';
|
||||
}
|
||||
protected function display_admin_notices() {
|
||||
foreach ( $this->admin_notices as $notice ) {
|
||||
echo '<div id="message" class="' . esc_attr( $notice['class'] ) . '">';
|
||||
echo ' <p>' . wp_kses_post( $notice['message'] ) . '</p>';
|
||||
echo '</div>';
|
||||
}
|
||||
}
|
||||
protected function display_filter_by_status() {
|
||||
$status_list_items = array();
|
||||
$request_status = $this->get_request_status();
|
||||
// Helper to set 'all' filter when not set on status counts passed in.
|
||||
if ( ! isset( $this->status_counts['all'] ) ) {
|
||||
$all_count = array_sum( $this->status_counts );
|
||||
if ( isset( $this->status_counts['past-due'] ) ) {
|
||||
$all_count -= $this->status_counts['past-due'];
|
||||
}
|
||||
$this->status_counts = array( 'all' => $all_count ) + $this->status_counts;
|
||||
}
|
||||
// Translated status labels.
|
||||
$status_labels = ActionScheduler_Store::instance()->get_status_labels();
|
||||
$status_labels['all'] = esc_html_x( 'All', 'status labels', 'action-scheduler' );
|
||||
$status_labels['past-due'] = esc_html_x( 'Past-due', 'status labels', 'action-scheduler' );
|
||||
foreach ( $this->status_counts as $status_slug => $count ) {
|
||||
if ( 0 === $count ) {
|
||||
continue;
|
||||
}
|
||||
if ( $status_slug === $request_status || ( empty( $request_status ) && 'all' === $status_slug ) ) {
|
||||
$status_list_item = '<li class="%1$s"><a href="%2$s" class="current">%3$s</a> (%4$d)</li>';
|
||||
} else {
|
||||
$status_list_item = '<li class="%1$s"><a href="%2$s">%3$s</a> (%4$d)</li>';
|
||||
}
|
||||
$status_name = isset( $status_labels[ $status_slug ] ) ? $status_labels[ $status_slug ] : ucfirst( $status_slug );
|
||||
$status_filter_url = ( 'all' === $status_slug ) ? remove_query_arg( 'status' ) : add_query_arg( 'status', $status_slug );
|
||||
$status_filter_url = remove_query_arg( array( 'paged', 's' ), $status_filter_url );
|
||||
$status_list_items[] = sprintf( $status_list_item, esc_attr( $status_slug ), esc_url( $status_filter_url ), esc_html( $status_name ), absint( $count ) );
|
||||
}
|
||||
if ( $status_list_items ) {
|
||||
echo '<ul class="subsubsub">';
|
||||
echo implode( " | \n", $status_list_items ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo '</ul>';
|
||||
}
|
||||
}
|
||||
protected function display_table() {
|
||||
echo '<form id="' . esc_attr( $this->_args['plural'] ) . '-filter" method="get">';
|
||||
foreach ( $this->get_request_query_args_to_persist() as $arg ) {
|
||||
$arg_value = isset( $_GET[ $arg ] ) ? sanitize_text_field( wp_unslash( $_GET[ $arg ] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( ! $arg_value ) {
|
||||
continue;
|
||||
}
|
||||
echo '<input type="hidden" name="' . esc_attr( $arg ) . '" value="' . esc_attr( $arg_value ) . '" />';
|
||||
}
|
||||
if ( ! empty( $this->search_by ) ) {
|
||||
echo $this->search_box( $this->get_search_box_button_text(), 'plugin' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
parent::display();
|
||||
echo '</form>';
|
||||
}
|
||||
public function process_actions() {
|
||||
$this->process_bulk_action();
|
||||
$this->process_row_actions();
|
||||
if ( ! empty( $_REQUEST['_wp_http_referer'] ) && ! empty( $_SERVER['REQUEST_URI'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
// _wp_http_referer is used only on bulk actions, we remove it to keep the $_GET shorter
|
||||
wp_safe_redirect( remove_query_arg( array( '_wp_http_referer', '_wpnonce' ), esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) ) );
|
||||
exit;
|
||||
}
|
||||
}
|
||||
public function display_page() {
|
||||
$this->prepare_items();
|
||||
echo '<div class="wrap">';
|
||||
$this->display_header();
|
||||
$this->display_admin_notices();
|
||||
$this->display_filter_by_status();
|
||||
$this->display_table();
|
||||
echo '</div>';
|
||||
}
|
||||
protected function get_search_box_placeholder() {
|
||||
return esc_html__( 'Search', 'action-scheduler' );
|
||||
}
|
||||
protected function get_per_page_option_name() {
|
||||
return $this->package . '_items_per_page';
|
||||
}
|
||||
}
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
abstract class ActionScheduler_Abstract_QueueRunner extends ActionScheduler_Abstract_QueueRunner_Deprecated {
|
||||
protected $cleaner;
|
||||
protected $monitor;
|
||||
protected $store;
|
||||
private $created_time;
|
||||
public function __construct( ActionScheduler_Store $store = null, ActionScheduler_FatalErrorMonitor $monitor = null, ActionScheduler_QueueCleaner $cleaner = null ) {
|
||||
$this->created_time = microtime( true );
|
||||
$this->store = $store ? $store : ActionScheduler_Store::instance();
|
||||
$this->monitor = $monitor ? $monitor : new ActionScheduler_FatalErrorMonitor( $this->store );
|
||||
$this->cleaner = $cleaner ? $cleaner : new ActionScheduler_QueueCleaner( $this->store );
|
||||
}
|
||||
public function process_action( $action_id, $context = '' ) {
|
||||
// Temporarily override the error handler while we process the current action.
|
||||
set_error_handler(
|
||||
function ( $type, $message ) {
|
||||
throw new Exception( $message );
|
||||
},
|
||||
E_USER_ERROR | E_RECOVERABLE_ERROR
|
||||
);
|
||||
try {
|
||||
try {
|
||||
$valid_action = false;
|
||||
do_action( 'action_scheduler_before_execute', $action_id, $context );
|
||||
if ( ActionScheduler_Store::STATUS_PENDING !== $this->store->get_status( $action_id ) ) {
|
||||
do_action( 'action_scheduler_execution_ignored', $action_id, $context );
|
||||
return;
|
||||
}
|
||||
$valid_action = true;
|
||||
do_action( 'action_scheduler_begin_execute', $action_id, $context );
|
||||
$action = $this->store->fetch_action( $action_id );
|
||||
$this->store->log_execution( $action_id );
|
||||
$action->execute();
|
||||
do_action( 'action_scheduler_after_execute', $action_id, $action, $context );
|
||||
$this->store->mark_complete( $action_id );
|
||||
} catch ( Throwable $e ) {
|
||||
// Throwable is defined when executing under PHP 7.0 and up. We convert it to an exception, for
|
||||
// compatibility with ActionScheduler_Logger.
|
||||
throw new Exception( $e->getMessage(), $e->getCode(), $e );
|
||||
}
|
||||
} catch ( Exception $e ) {
|
||||
// This catch block exists for compatibility with PHP 5.6.
|
||||
$this->handle_action_error( $action_id, $e, $context, $valid_action );
|
||||
} finally {
|
||||
restore_error_handler();
|
||||
}
|
||||
if ( isset( $action ) && is_a( $action, 'ActionScheduler_Action' ) && $action->get_schedule()->is_recurring() ) {
|
||||
$this->schedule_next_instance( $action, $action_id );
|
||||
}
|
||||
}
|
||||
private function handle_action_error( $action_id, $e, $context, $valid_action ) {
|
||||
if ( $valid_action ) {
|
||||
$this->store->mark_failure( $action_id );
|
||||
do_action( 'action_scheduler_failed_execution', $action_id, $e, $context );
|
||||
} else {
|
||||
do_action( 'action_scheduler_failed_validation', $action_id, $e, $context );
|
||||
}
|
||||
}
|
||||
protected function schedule_next_instance( ActionScheduler_Action $action, $action_id ) {
|
||||
// If a recurring action has been consistently failing, we may wish to stop rescheduling it.
|
||||
if (
|
||||
ActionScheduler_Store::STATUS_FAILED === $this->store->get_status( $action_id )
|
||||
&& $this->recurring_action_is_consistently_failing( $action, $action_id )
|
||||
) {
|
||||
ActionScheduler_Logger::instance()->log(
|
||||
$action_id,
|
||||
__( 'This action appears to be consistently failing. A new instance will not be scheduled.', 'action-scheduler' )
|
||||
);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
ActionScheduler::factory()->repeat( $action );
|
||||
} catch ( Exception $e ) {
|
||||
do_action( 'action_scheduler_failed_to_schedule_next_instance', $action_id, $e, $action );
|
||||
}
|
||||
}
|
||||
private function recurring_action_is_consistently_failing( ActionScheduler_Action $action, $action_id ) {
|
||||
$consistent_failure_threshold = (int) apply_filters( 'action_scheduler_recurring_action_failure_threshold', 5 );
|
||||
// This query should find the earliest *failing* action (for the hook we are interested in) within our threshold.
|
||||
$query_args = array(
|
||||
'hook' => $action->get_hook(),
|
||||
'status' => ActionScheduler_Store::STATUS_FAILED,
|
||||
'date' => date_create( 'now', timezone_open( 'UTC' ) )->format( 'Y-m-d H:i:s' ),
|
||||
'date_compare' => '<',
|
||||
'per_page' => 1,
|
||||
'offset' => $consistent_failure_threshold - 1
|
||||
);
|
||||
$first_failing_action_id = $this->store->query_actions( $query_args );
|
||||
// If we didn't retrieve an action ID, then there haven't been enough failures for us to worry about.
|
||||
if ( empty( $first_failing_action_id ) ) {
|
||||
return false;
|
||||
}
|
||||
// Now let's fetch the first action (having the same hook) of *any status* within the same window.
|
||||
unset( $query_args['status'] );
|
||||
$first_action_id_with_the_same_hook = $this->store->query_actions( $query_args );
|
||||
return (bool) apply_filters(
|
||||
'action_scheduler_recurring_action_is_consistently_failing',
|
||||
$first_action_id_with_the_same_hook === $first_failing_action_id,
|
||||
$action
|
||||
);
|
||||
}
|
||||
protected function run_cleanup() {
|
||||
$this->cleaner->clean( 10 * $this->get_time_limit() );
|
||||
}
|
||||
public function get_allowed_concurrent_batches() {
|
||||
return apply_filters( 'action_scheduler_queue_runner_concurrent_batches', 1 );
|
||||
}
|
||||
public function has_maximum_concurrent_batches() {
|
||||
return $this->store->get_claim_count() >= $this->get_allowed_concurrent_batches();
|
||||
}
|
||||
protected function get_time_limit() {
|
||||
$time_limit = 30;
|
||||
// Apply deprecated filter from deprecated get_maximum_execution_time() method
|
||||
if ( has_filter( 'action_scheduler_maximum_execution_time' ) ) {
|
||||
_deprecated_function( 'action_scheduler_maximum_execution_time', '2.1.1', 'action_scheduler_queue_runner_time_limit' );
|
||||
$time_limit = apply_filters( 'action_scheduler_maximum_execution_time', $time_limit );
|
||||
}
|
||||
return absint( apply_filters( 'action_scheduler_queue_runner_time_limit', $time_limit ) );
|
||||
}
|
||||
protected function get_execution_time() {
|
||||
$execution_time = microtime( true ) - $this->created_time;
|
||||
// Get the CPU time if the hosting environment uses it rather than wall-clock time to calculate a process's execution time.
|
||||
if ( function_exists( 'getrusage' ) && apply_filters( 'action_scheduler_use_cpu_execution_time', defined( 'PANTHEON_ENVIRONMENT' ) ) ) {
|
||||
$resource_usages = getrusage();
|
||||
if ( isset( $resource_usages['ru_stime.tv_usec'], $resource_usages['ru_stime.tv_usec'] ) ) {
|
||||
$execution_time = $resource_usages['ru_stime.tv_sec'] + ( $resource_usages['ru_stime.tv_usec'] / 1000000 );
|
||||
}
|
||||
}
|
||||
return $execution_time;
|
||||
}
|
||||
protected function time_likely_to_be_exceeded( $processed_actions ) {
|
||||
$execution_time = $this->get_execution_time();
|
||||
$max_execution_time = $this->get_time_limit();
|
||||
// Safety against division by zero errors.
|
||||
if ( 0 === $processed_actions ) {
|
||||
return $execution_time >= $max_execution_time;
|
||||
}
|
||||
$time_per_action = $execution_time / $processed_actions;
|
||||
$estimated_time = $execution_time + ( $time_per_action * 3 );
|
||||
$likely_to_be_exceeded = $estimated_time > $max_execution_time;
|
||||
return apply_filters( 'action_scheduler_maximum_execution_time_likely_to_be_exceeded', $likely_to_be_exceeded, $this, $processed_actions, $execution_time, $max_execution_time );
|
||||
}
|
||||
protected function get_memory_limit() {
|
||||
if ( function_exists( 'ini_get' ) ) {
|
||||
$memory_limit = ini_get( 'memory_limit' );
|
||||
} else {
|
||||
$memory_limit = '128M'; // Sensible default, and minimum required by WooCommerce
|
||||
}
|
||||
if ( ! $memory_limit || -1 === $memory_limit || '-1' === $memory_limit ) {
|
||||
// Unlimited, set to 32GB.
|
||||
$memory_limit = '32G';
|
||||
}
|
||||
return ActionScheduler_Compatibility::convert_hr_to_bytes( $memory_limit );
|
||||
}
|
||||
protected function memory_exceeded() {
|
||||
$memory_limit = $this->get_memory_limit() * 0.90;
|
||||
$current_memory = memory_get_usage( true );
|
||||
$memory_exceeded = $current_memory >= $memory_limit;
|
||||
return apply_filters( 'action_scheduler_memory_exceeded', $memory_exceeded, $this );
|
||||
}
|
||||
protected function batch_limits_exceeded( $processed_actions ) {
|
||||
return $this->memory_exceeded() || $this->time_likely_to_be_exceeded( $processed_actions );
|
||||
}
|
||||
abstract public function run( $context = '' );
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
abstract class ActionScheduler_Abstract_RecurringSchedule extends ActionScheduler_Abstract_Schedule {
|
||||
private $first_date = NULL;
|
||||
protected $first_timestamp = NULL;
|
||||
protected $recurrence;
|
||||
public function __construct( DateTime $date, $recurrence, DateTime $first = null ) {
|
||||
parent::__construct( $date );
|
||||
$this->first_date = empty( $first ) ? $date : $first;
|
||||
$this->recurrence = $recurrence;
|
||||
}
|
||||
public function is_recurring() {
|
||||
return true;
|
||||
}
|
||||
public function get_first_date() {
|
||||
return clone $this->first_date;
|
||||
}
|
||||
public function get_recurrence() {
|
||||
return $this->recurrence;
|
||||
}
|
||||
public function __sleep() {
|
||||
$sleep_params = parent::__sleep();
|
||||
$this->first_timestamp = $this->first_date->getTimestamp();
|
||||
return array_merge( $sleep_params, array(
|
||||
'first_timestamp',
|
||||
'recurrence'
|
||||
) );
|
||||
}
|
||||
public function __wakeup() {
|
||||
parent::__wakeup();
|
||||
if ( $this->first_timestamp > 0 ) {
|
||||
$this->first_date = as_get_datetime_object( $this->first_timestamp );
|
||||
} else {
|
||||
$this->first_date = $this->get_date();
|
||||
}
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
abstract class ActionScheduler_Abstract_Schedule extends ActionScheduler_Schedule_Deprecated {
|
||||
private $scheduled_date = NULL;
|
||||
protected $scheduled_timestamp = NULL;
|
||||
public function __construct( DateTime $date ) {
|
||||
$this->scheduled_date = $date;
|
||||
}
|
||||
abstract public function is_recurring();
|
||||
abstract protected function calculate_next( DateTime $after );
|
||||
public function get_next( DateTime $after ) {
|
||||
$after = clone $after;
|
||||
if ( $after > $this->scheduled_date ) {
|
||||
$after = $this->calculate_next( $after );
|
||||
return $after;
|
||||
}
|
||||
return clone $this->scheduled_date;
|
||||
}
|
||||
public function get_date() {
|
||||
return $this->scheduled_date;
|
||||
}
|
||||
public function __sleep() {
|
||||
$this->scheduled_timestamp = $this->scheduled_date->getTimestamp();
|
||||
return array(
|
||||
'scheduled_timestamp',
|
||||
);
|
||||
}
|
||||
public function __wakeup() {
|
||||
$this->scheduled_date = as_get_datetime_object( $this->scheduled_timestamp );
|
||||
unset( $this->scheduled_timestamp );
|
||||
}
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
abstract class ActionScheduler_Abstract_Schema {
|
||||
protected $schema_version = 1;
|
||||
protected $db_version;
|
||||
protected $tables = array();
|
||||
public function init() {}
|
||||
public function register_tables( $force_update = false ) {
|
||||
global $wpdb;
|
||||
// make WP aware of our tables
|
||||
foreach ( $this->tables as $table ) {
|
||||
$wpdb->tables[] = $table;
|
||||
$name = $this->get_full_table_name( $table );
|
||||
$wpdb->$table = $name;
|
||||
}
|
||||
// create the tables
|
||||
if ( $this->schema_update_required() || $force_update ) {
|
||||
foreach ( $this->tables as $table ) {
|
||||
do_action( 'action_scheduler_before_schema_update', $table, $this->db_version );
|
||||
$this->update_table( $table );
|
||||
}
|
||||
$this->mark_schema_update_complete();
|
||||
}
|
||||
}
|
||||
abstract protected function get_table_definition( $table );
|
||||
private function schema_update_required() {
|
||||
$option_name = 'schema-' . static::class;
|
||||
$this->db_version = get_option( $option_name, 0 );
|
||||
// Check for schema option stored by the Action Scheduler Custom Tables plugin in case site has migrated from that plugin with an older schema
|
||||
if ( 0 === $this->db_version ) {
|
||||
$plugin_option_name = 'schema-';
|
||||
switch ( static::class ) {
|
||||
case 'ActionScheduler_StoreSchema':
|
||||
$plugin_option_name .= 'Action_Scheduler\Custom_Tables\DB_Store_Table_Maker';
|
||||
break;
|
||||
case 'ActionScheduler_LoggerSchema':
|
||||
$plugin_option_name .= 'Action_Scheduler\Custom_Tables\DB_Logger_Table_Maker';
|
||||
break;
|
||||
}
|
||||
$this->db_version = get_option( $plugin_option_name, 0 );
|
||||
delete_option( $plugin_option_name );
|
||||
}
|
||||
return version_compare( $this->db_version, $this->schema_version, '<' );
|
||||
}
|
||||
private function mark_schema_update_complete() {
|
||||
$option_name = 'schema-' . static::class;
|
||||
// work around race conditions and ensure that our option updates
|
||||
$value_to_save = (string) $this->schema_version . '.0.' . time();
|
||||
update_option( $option_name, $value_to_save );
|
||||
}
|
||||
private function update_table( $table ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
|
||||
$definition = $this->get_table_definition( $table );
|
||||
if ( $definition ) {
|
||||
$updated = dbDelta( $definition );
|
||||
foreach ( $updated as $updated_table => $update_description ) {
|
||||
if ( strpos( $update_description, 'Created table' ) === 0 ) {
|
||||
do_action( 'action_scheduler/created_table', $updated_table, $table );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
protected function get_full_table_name( $table ) {
|
||||
return $GLOBALS['wpdb']->prefix . $table;
|
||||
}
|
||||
public function tables_exist() {
|
||||
global $wpdb;
|
||||
$tables_exist = true;
|
||||
foreach ( $this->tables as $table_name ) {
|
||||
$table_name = $wpdb->prefix . $table_name;
|
||||
$pattern = str_replace( '_', '\\_', $table_name );
|
||||
$existing_table = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $pattern ) );
|
||||
if ( $existing_table !== $table_name ) {
|
||||
$tables_exist = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $tables_exist;
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
abstract class ActionScheduler_Lock {
|
||||
private static $locker = NULL;
|
||||
protected static $lock_duration = MINUTE_IN_SECONDS;
|
||||
public function is_locked( $lock_type ) {
|
||||
return ( $this->get_expiration( $lock_type ) >= time() );
|
||||
}
|
||||
abstract public function set( $lock_type );
|
||||
abstract public function get_expiration( $lock_type );
|
||||
protected function get_duration( $lock_type ) {
|
||||
return apply_filters( 'action_scheduler_lock_duration', self::$lock_duration, $lock_type );
|
||||
}
|
||||
public static function instance() {
|
||||
if ( empty( self::$locker ) ) {
|
||||
$class = apply_filters( 'action_scheduler_lock_class', 'ActionScheduler_OptionLock' );
|
||||
self::$locker = new $class();
|
||||
}
|
||||
return self::$locker;
|
||||
}
|
||||
}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
abstract class ActionScheduler_Logger {
|
||||
private static $logger = NULL;
|
||||
public static function instance() {
|
||||
if ( empty(self::$logger) ) {
|
||||
$class = apply_filters('action_scheduler_logger_class', 'ActionScheduler_wpCommentLogger');
|
||||
self::$logger = new $class();
|
||||
}
|
||||
return self::$logger;
|
||||
}
|
||||
abstract public function log( $action_id, $message, DateTime $date = NULL );
|
||||
abstract public function get_entry( $entry_id );
|
||||
abstract public function get_logs( $action_id );
|
||||
public function init() {
|
||||
$this->hook_stored_action();
|
||||
add_action( 'action_scheduler_canceled_action', array( $this, 'log_canceled_action' ), 10, 1 );
|
||||
add_action( 'action_scheduler_begin_execute', array( $this, 'log_started_action' ), 10, 2 );
|
||||
add_action( 'action_scheduler_after_execute', array( $this, 'log_completed_action' ), 10, 3 );
|
||||
add_action( 'action_scheduler_failed_execution', array( $this, 'log_failed_action' ), 10, 3 );
|
||||
add_action( 'action_scheduler_failed_action', array( $this, 'log_timed_out_action' ), 10, 2 );
|
||||
add_action( 'action_scheduler_unexpected_shutdown', array( $this, 'log_unexpected_shutdown' ), 10, 2 );
|
||||
add_action( 'action_scheduler_reset_action', array( $this, 'log_reset_action' ), 10, 1 );
|
||||
add_action( 'action_scheduler_execution_ignored', array( $this, 'log_ignored_action' ), 10, 2 );
|
||||
add_action( 'action_scheduler_failed_fetch_action', array( $this, 'log_failed_fetch_action' ), 10, 2 );
|
||||
add_action( 'action_scheduler_failed_to_schedule_next_instance', array( $this, 'log_failed_schedule_next_instance' ), 10, 2 );
|
||||
add_action( 'action_scheduler_bulk_cancel_actions', array( $this, 'bulk_log_cancel_actions' ), 10, 1 );
|
||||
}
|
||||
public function hook_stored_action() {
|
||||
add_action( 'action_scheduler_stored_action', array( $this, 'log_stored_action' ) );
|
||||
}
|
||||
public function unhook_stored_action() {
|
||||
remove_action( 'action_scheduler_stored_action', array( $this, 'log_stored_action' ) );
|
||||
}
|
||||
public function log_stored_action( $action_id ) {
|
||||
$this->log( $action_id, __( 'action created', 'action-scheduler' ) );
|
||||
}
|
||||
public function log_canceled_action( $action_id ) {
|
||||
$this->log( $action_id, __( 'action canceled', 'action-scheduler' ) );
|
||||
}
|
||||
public function log_started_action( $action_id, $context = '' ) {
|
||||
if ( ! empty( $context ) ) {
|
||||
$message = sprintf( __( 'action started via %s', 'action-scheduler' ), $context );
|
||||
} else {
|
||||
$message = __( 'action started', 'action-scheduler' );
|
||||
}
|
||||
$this->log( $action_id, $message );
|
||||
}
|
||||
public function log_completed_action( $action_id, $action = NULL, $context = '' ) {
|
||||
if ( ! empty( $context ) ) {
|
||||
$message = sprintf( __( 'action complete via %s', 'action-scheduler' ), $context );
|
||||
} else {
|
||||
$message = __( 'action complete', 'action-scheduler' );
|
||||
}
|
||||
$this->log( $action_id, $message );
|
||||
}
|
||||
public function log_failed_action( $action_id, Exception $exception, $context = '' ) {
|
||||
if ( ! empty( $context ) ) {
|
||||
$message = sprintf( __( 'action failed via %1$s: %2$s', 'action-scheduler' ), $context, $exception->getMessage() );
|
||||
} else {
|
||||
$message = sprintf( __( 'action failed: %s', 'action-scheduler' ), $exception->getMessage() );
|
||||
}
|
||||
$this->log( $action_id, $message );
|
||||
}
|
||||
public function log_timed_out_action( $action_id, $timeout ) {
|
||||
$this->log( $action_id, sprintf( __( 'action marked as failed after %s seconds. Unknown error occurred. Check server, PHP and database error logs to diagnose cause.', 'action-scheduler' ), $timeout ) );
|
||||
}
|
||||
public function log_unexpected_shutdown( $action_id, $error ) {
|
||||
if ( ! empty( $error ) ) {
|
||||
$this->log( $action_id, sprintf( __( 'unexpected shutdown: PHP Fatal error %1$s in %2$s on line %3$s', 'action-scheduler' ), $error['message'], $error['file'], $error['line'] ) );
|
||||
}
|
||||
}
|
||||
public function log_reset_action( $action_id ) {
|
||||
$this->log( $action_id, __( 'action reset', 'action-scheduler' ) );
|
||||
}
|
||||
public function log_ignored_action( $action_id, $context = '' ) {
|
||||
if ( ! empty( $context ) ) {
|
||||
$message = sprintf( __( 'action ignored via %s', 'action-scheduler' ), $context );
|
||||
} else {
|
||||
$message = __( 'action ignored', 'action-scheduler' );
|
||||
}
|
||||
$this->log( $action_id, $message );
|
||||
}
|
||||
public function log_failed_fetch_action( $action_id, Exception $exception = NULL ) {
|
||||
if ( ! is_null( $exception ) ) {
|
||||
$log_message = sprintf( __( 'There was a failure fetching this action: %s', 'action-scheduler' ), $exception->getMessage() );
|
||||
} else {
|
||||
$log_message = __( 'There was a failure fetching this action', 'action-scheduler' );
|
||||
}
|
||||
$this->log( $action_id, $log_message );
|
||||
}
|
||||
public function log_failed_schedule_next_instance( $action_id, Exception $exception ) {
|
||||
$this->log( $action_id, sprintf( __( 'There was a failure scheduling the next instance of this action: %s', 'action-scheduler' ), $exception->getMessage() ) );
|
||||
}
|
||||
public function bulk_log_cancel_actions( $action_ids ) {
|
||||
if ( empty( $action_ids ) ) {
|
||||
return;
|
||||
}
|
||||
foreach ( $action_ids as $action_id ) {
|
||||
$this->log_canceled_action( $action_id );
|
||||
}
|
||||
}
|
||||
}
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
abstract class ActionScheduler_Store extends ActionScheduler_Store_Deprecated {
|
||||
const STATUS_COMPLETE = 'complete';
|
||||
const STATUS_PENDING = 'pending';
|
||||
const STATUS_RUNNING = 'in-progress';
|
||||
const STATUS_FAILED = 'failed';
|
||||
const STATUS_CANCELED = 'canceled';
|
||||
const DEFAULT_CLASS = 'ActionScheduler_wpPostStore';
|
||||
private static $store = NULL;
|
||||
protected static $max_args_length = 191;
|
||||
abstract public function save_action( ActionScheduler_Action $action, DateTime $scheduled_date = NULL );
|
||||
abstract public function fetch_action( $action_id );
|
||||
public function find_action( $hook, $params = array() ) {
|
||||
$params = wp_parse_args(
|
||||
$params,
|
||||
array(
|
||||
'args' => null,
|
||||
'status' => self::STATUS_PENDING,
|
||||
'group' => '',
|
||||
)
|
||||
);
|
||||
// These params are fixed for this method.
|
||||
$params['hook'] = $hook;
|
||||
$params['orderby'] = 'date';
|
||||
$params['per_page'] = 1;
|
||||
if ( ! empty( $params['status'] ) ) {
|
||||
if ( self::STATUS_PENDING === $params['status'] ) {
|
||||
$params['order'] = 'ASC'; // Find the next action that matches.
|
||||
} else {
|
||||
$params['order'] = 'DESC'; // Find the most recent action that matches.
|
||||
}
|
||||
}
|
||||
$results = $this->query_actions( $params );
|
||||
return empty( $results ) ? null : $results[0];
|
||||
}
|
||||
abstract public function query_actions( $query = array(), $query_type = 'select' );
|
||||
public function query_action( $query ) {
|
||||
$query['per_page'] = 1;
|
||||
$query['offset'] = 0;
|
||||
$results = $this->query_actions( $query );
|
||||
if ( empty( $results ) ) {
|
||||
return null;
|
||||
} else {
|
||||
return (int) $results[0];
|
||||
}
|
||||
}
|
||||
abstract public function action_counts();
|
||||
public function extra_action_counts() {
|
||||
$extra_actions = array();
|
||||
$pastdue_action_counts = ( int ) $this->query_actions( array(
|
||||
'status' => self::STATUS_PENDING,
|
||||
'date' => as_get_datetime_object(),
|
||||
), 'count' );
|
||||
if ( $pastdue_action_counts ) {
|
||||
$extra_actions['past-due'] = $pastdue_action_counts;
|
||||
}
|
||||
return apply_filters( 'action_scheduler_extra_action_counts', $extra_actions );
|
||||
}
|
||||
abstract public function cancel_action( $action_id );
|
||||
abstract public function delete_action( $action_id );
|
||||
abstract public function get_date( $action_id );
|
||||
abstract public function stake_claim( $max_actions = 10, DateTime $before_date = null, $hooks = array(), $group = '' );
|
||||
abstract public function get_claim_count();
|
||||
abstract public function release_claim( ActionScheduler_ActionClaim $claim );
|
||||
abstract public function unclaim_action( $action_id );
|
||||
abstract public function mark_failure( $action_id );
|
||||
abstract public function log_execution( $action_id );
|
||||
abstract public function mark_complete( $action_id );
|
||||
abstract public function get_status( $action_id );
|
||||
abstract public function get_claim_id( $action_id );
|
||||
abstract public function find_actions_by_claim_id( $claim_id );
|
||||
protected function validate_sql_comparator( $comparison_operator ) {
|
||||
if ( in_array( $comparison_operator, array('!=', '>', '>=', '<', '<=', '=') ) ) {
|
||||
return $comparison_operator;
|
||||
}
|
||||
return '=';
|
||||
}
|
||||
protected function get_scheduled_date_string( ActionScheduler_Action $action, DateTime $scheduled_date = NULL ) {
|
||||
$next = null === $scheduled_date ? $action->get_schedule()->get_date() : $scheduled_date;
|
||||
if ( ! $next ) {
|
||||
$next = date_create();
|
||||
}
|
||||
$next->setTimezone( new DateTimeZone( 'UTC' ) );
|
||||
return $next->format( 'Y-m-d H:i:s' );
|
||||
}
|
||||
protected function get_scheduled_date_string_local( ActionScheduler_Action $action, DateTime $scheduled_date = NULL ) {
|
||||
$next = null === $scheduled_date ? $action->get_schedule()->get_date() : $scheduled_date;
|
||||
if ( ! $next ) {
|
||||
$next = date_create();
|
||||
}
|
||||
ActionScheduler_TimezoneHelper::set_local_timezone( $next );
|
||||
return $next->format( 'Y-m-d H:i:s' );
|
||||
}
|
||||
protected function validate_args( $args, $action_id ) {
|
||||
// Ensure we have an array of args.
|
||||
if ( ! is_array( $args ) ) {
|
||||
throw ActionScheduler_InvalidActionException::from_decoding_args( $action_id );
|
||||
}
|
||||
// Validate JSON decoding if possible.
|
||||
if ( function_exists( 'json_last_error' ) && JSON_ERROR_NONE !== json_last_error() ) {
|
||||
throw ActionScheduler_InvalidActionException::from_decoding_args( $action_id, $args );
|
||||
}
|
||||
}
|
||||
protected function validate_schedule( $schedule, $action_id ) {
|
||||
if ( empty( $schedule ) || ! is_a( $schedule, 'ActionScheduler_Schedule' ) ) {
|
||||
throw ActionScheduler_InvalidActionException::from_schedule( $action_id, $schedule );
|
||||
}
|
||||
}
|
||||
protected function validate_action( ActionScheduler_Action $action ) {
|
||||
if ( strlen( wp_json_encode( $action->get_args() ) ) > static::$max_args_length ) {
|
||||
// translators: %d is a number (maximum length of action arguments).
|
||||
throw new InvalidArgumentException( sprintf( __( 'ActionScheduler_Action::$args too long. To ensure the args column can be indexed, action args should not be more than %d characters when encoded as JSON.', 'action-scheduler' ), static::$max_args_length ) );
|
||||
}
|
||||
}
|
||||
public function cancel_actions_by_hook( $hook ) {
|
||||
$action_ids = true;
|
||||
while ( ! empty( $action_ids ) ) {
|
||||
$action_ids = $this->query_actions(
|
||||
array(
|
||||
'hook' => $hook,
|
||||
'status' => self::STATUS_PENDING,
|
||||
'per_page' => 1000,
|
||||
'orderby' => 'none',
|
||||
)
|
||||
);
|
||||
$this->bulk_cancel_actions( $action_ids );
|
||||
}
|
||||
}
|
||||
public function cancel_actions_by_group( $group ) {
|
||||
$action_ids = true;
|
||||
while ( ! empty( $action_ids ) ) {
|
||||
$action_ids = $this->query_actions(
|
||||
array(
|
||||
'group' => $group,
|
||||
'status' => self::STATUS_PENDING,
|
||||
'per_page' => 1000,
|
||||
'orderby' => 'none',
|
||||
)
|
||||
);
|
||||
$this->bulk_cancel_actions( $action_ids );
|
||||
}
|
||||
}
|
||||
private function bulk_cancel_actions( $action_ids ) {
|
||||
foreach ( $action_ids as $action_id ) {
|
||||
$this->cancel_action( $action_id );
|
||||
}
|
||||
do_action( 'action_scheduler_bulk_cancel_actions', $action_ids );
|
||||
}
|
||||
public function get_status_labels() {
|
||||
return array(
|
||||
self::STATUS_COMPLETE => __( 'Complete', 'action-scheduler' ),
|
||||
self::STATUS_PENDING => __( 'Pending', 'action-scheduler' ),
|
||||
self::STATUS_RUNNING => __( 'In-progress', 'action-scheduler' ),
|
||||
self::STATUS_FAILED => __( 'Failed', 'action-scheduler' ),
|
||||
self::STATUS_CANCELED => __( 'Canceled', 'action-scheduler' ),
|
||||
);
|
||||
}
|
||||
public function has_pending_actions_due() {
|
||||
$pending_actions = $this->query_actions( array(
|
||||
'date' => as_get_datetime_object(),
|
||||
'status' => ActionScheduler_Store::STATUS_PENDING,
|
||||
'orderby' => 'none',
|
||||
) );
|
||||
return ! empty( $pending_actions );
|
||||
}
|
||||
public function init() {}
|
||||
public function mark_migrated( $action_id ) {}
|
||||
public static function instance() {
|
||||
if ( empty( self::$store ) ) {
|
||||
$class = apply_filters( 'action_scheduler_store_class', self::DEFAULT_CLASS );
|
||||
self::$store = new $class();
|
||||
}
|
||||
return self::$store;
|
||||
}
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
abstract class ActionScheduler_TimezoneHelper {
|
||||
private static $local_timezone = NULL;
|
||||
public static function set_local_timezone( DateTime $date ) {
|
||||
// Accept a DateTime for easier backward compatibility, even though we require methods on ActionScheduler_DateTime
|
||||
if ( ! is_a( $date, 'ActionScheduler_DateTime' ) ) {
|
||||
$date = as_get_datetime_object( $date->format( 'U' ) );
|
||||
}
|
||||
if ( get_option( 'timezone_string' ) ) {
|
||||
$date->setTimezone( new DateTimeZone( self::get_local_timezone_string() ) );
|
||||
} else {
|
||||
$date->setUtcOffset( self::get_local_timezone_offset() );
|
||||
}
|
||||
return $date;
|
||||
}
|
||||
protected static function get_local_timezone_string( $reset = false ) {
|
||||
// If site timezone string exists, return it.
|
||||
$timezone = get_option( 'timezone_string' );
|
||||
if ( $timezone ) {
|
||||
return $timezone;
|
||||
}
|
||||
// Get UTC offset, if it isn't set then return UTC.
|
||||
$utc_offset = intval( get_option( 'gmt_offset', 0 ) );
|
||||
if ( 0 === $utc_offset ) {
|
||||
return 'UTC';
|
||||
}
|
||||
// Adjust UTC offset from hours to seconds.
|
||||
$utc_offset *= 3600;
|
||||
// Attempt to guess the timezone string from the UTC offset.
|
||||
$timezone = timezone_name_from_abbr( '', $utc_offset );
|
||||
if ( $timezone ) {
|
||||
return $timezone;
|
||||
}
|
||||
// Last try, guess timezone string manually.
|
||||
foreach ( timezone_abbreviations_list() as $abbr ) {
|
||||
foreach ( $abbr as $city ) {
|
||||
if ( (bool) date( 'I' ) === (bool) $city['dst'] && $city['timezone_id'] && intval( $city['offset'] ) === $utc_offset ) { // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date -- we are actually interested in the runtime timezone.
|
||||
return $city['timezone_id'];
|
||||
}
|
||||
}
|
||||
}
|
||||
// No timezone string
|
||||
return '';
|
||||
}
|
||||
protected static function get_local_timezone_offset() {
|
||||
$timezone = get_option( 'timezone_string' );
|
||||
if ( $timezone ) {
|
||||
$timezone_object = new DateTimeZone( $timezone );
|
||||
return $timezone_object->getOffset( new DateTime( 'now' ) );
|
||||
} else {
|
||||
return floatval( get_option( 'gmt_offset', 0 ) ) * HOUR_IN_SECONDS;
|
||||
}
|
||||
}
|
||||
public static function get_local_timezone( $reset = FALSE ) {
|
||||
_deprecated_function( __FUNCTION__, '2.1.0', 'ActionScheduler_TimezoneHelper::set_local_timezone()' );
|
||||
if ( $reset ) {
|
||||
self::$local_timezone = NULL;
|
||||
}
|
||||
if ( !isset(self::$local_timezone) ) {
|
||||
$tzstring = get_option('timezone_string');
|
||||
if ( empty($tzstring) ) {
|
||||
$gmt_offset = get_option('gmt_offset');
|
||||
if ( $gmt_offset == 0 ) {
|
||||
$tzstring = 'UTC';
|
||||
} else {
|
||||
$gmt_offset *= HOUR_IN_SECONDS;
|
||||
$tzstring = timezone_name_from_abbr( '', $gmt_offset, 1 );
|
||||
// If there's no timezone string, try again with no DST.
|
||||
if ( false === $tzstring ) {
|
||||
$tzstring = timezone_name_from_abbr( '', $gmt_offset, 0 );
|
||||
}
|
||||
// Try mapping to the first abbreviation we can find.
|
||||
if ( false === $tzstring ) {
|
||||
$is_dst = date( 'I' ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date -- we are actually interested in the runtime timezone.
|
||||
foreach ( timezone_abbreviations_list() as $abbr ) {
|
||||
foreach ( $abbr as $city ) {
|
||||
if ( $city['dst'] == $is_dst && $city['offset'] == $gmt_offset ) {
|
||||
// If there's no valid timezone ID, keep looking.
|
||||
if ( null === $city['timezone_id'] ) {
|
||||
continue;
|
||||
}
|
||||
$tzstring = $city['timezone_id'];
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// If we still have no valid string, then fall back to UTC.
|
||||
if ( false === $tzstring ) {
|
||||
$tzstring = 'UTC';
|
||||
}
|
||||
}
|
||||
}
|
||||
self::$local_timezone = new DateTimeZone($tzstring);
|
||||
}
|
||||
return self::$local_timezone;
|
||||
}
|
||||
}
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
<?php
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class ActionScheduler_Action {
|
||||
protected $hook = '';
|
||||
protected $args = array();
|
||||
protected $schedule = NULL;
|
||||
protected $group = '';
|
||||
protected $priority = 10;
|
||||
public function __construct( $hook, array $args = array(), ActionScheduler_Schedule $schedule = NULL, $group = '' ) {
|
||||
$schedule = empty( $schedule ) ? new ActionScheduler_NullSchedule() : $schedule;
|
||||
$this->set_hook($hook);
|
||||
$this->set_schedule($schedule);
|
||||
$this->set_args($args);
|
||||
$this->set_group($group);
|
||||
}
|
||||
public function execute() {
|
||||
$hook = $this->get_hook();
|
||||
if ( ! has_action( $hook ) ) {
|
||||
throw new Exception(
|
||||
sprintf(
|
||||
__( 'Scheduled action for %1$s will not be executed as no callbacks are registered.', 'action-scheduler' ),
|
||||
$hook
|
||||
)
|
||||
);
|
||||
}
|
||||
do_action_ref_array( $hook, array_values( $this->get_args() ) );
|
||||
}
|
||||
protected function set_hook( $hook ) {
|
||||
$this->hook = $hook;
|
||||
}
|
||||
public function get_hook() {
|
||||
return $this->hook;
|
||||
}
|
||||
protected function set_schedule( ActionScheduler_Schedule $schedule ) {
|
||||
$this->schedule = $schedule;
|
||||
}
|
||||
public function get_schedule() {
|
||||
return $this->schedule;
|
||||
}
|
||||
protected function set_args( array $args ) {
|
||||
$this->args = $args;
|
||||
}
|
||||
public function get_args() {
|
||||
return $this->args;
|
||||
}
|
||||
protected function set_group( $group ) {
|
||||
$this->group = $group;
|
||||
}
|
||||
public function get_group() {
|
||||
return $this->group;
|
||||
}
|
||||
public function is_finished() {
|
||||
return FALSE;
|
||||
}
|
||||
public function set_priority( $priority ) {
|
||||
if ( $priority < 0 ) {
|
||||
$priority = 0;
|
||||
} elseif ( $priority > 255 ) {
|
||||
$priority = 255;
|
||||
}
|
||||
$this->priority = (int) $priority;
|
||||
}
|
||||
public function get_priority() {
|
||||
return $this->priority;
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class ActionScheduler_CanceledAction extends ActionScheduler_FinishedAction {
|
||||
public function __construct( $hook, array $args = array(), ActionScheduler_Schedule $schedule = null, $group = '' ) {
|
||||
parent::__construct( $hook, $args, $schedule, $group );
|
||||
if ( is_null( $schedule ) ) {
|
||||
$this->set_schedule( new ActionScheduler_NullSchedule() );
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class ActionScheduler_FinishedAction extends ActionScheduler_Action {
|
||||
public function execute() {
|
||||
// don't execute
|
||||
}
|
||||
public function is_finished() {
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class ActionScheduler_NullAction extends ActionScheduler_Action {
|
||||
public function __construct( $hook = '', array $args = array(), ActionScheduler_Schedule $schedule = NULL ) {
|
||||
$this->set_schedule( new ActionScheduler_NullSchedule() );
|
||||
}
|
||||
public function execute() {
|
||||
// don't execute
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
<?php
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class ActionScheduler_DBLogger extends ActionScheduler_Logger {
|
||||
public function log( $action_id, $message, DateTime $date = null ) {
|
||||
if ( empty( $date ) ) {
|
||||
$date = as_get_datetime_object();
|
||||
} else {
|
||||
$date = clone $date;
|
||||
}
|
||||
$date_gmt = $date->format( 'Y-m-d H:i:s' );
|
||||
ActionScheduler_TimezoneHelper::set_local_timezone( $date );
|
||||
$date_local = $date->format( 'Y-m-d H:i:s' );
|
||||
//phpcs:ignore Generic.Commenting.DocComment.MissingShort
|
||||
global $wpdb;
|
||||
$wpdb->insert(
|
||||
$wpdb->actionscheduler_logs,
|
||||
array(
|
||||
'action_id' => $action_id,
|
||||
'message' => $message,
|
||||
'log_date_gmt' => $date_gmt,
|
||||
'log_date_local' => $date_local,
|
||||
),
|
||||
array( '%d', '%s', '%s', '%s' )
|
||||
);
|
||||
return $wpdb->insert_id;
|
||||
}
|
||||
public function get_entry( $entry_id ) {
|
||||
//phpcs:ignore Generic.Commenting.DocComment.MissingShort
|
||||
global $wpdb;
|
||||
$entry = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->actionscheduler_logs} WHERE log_id=%d", $entry_id ) );
|
||||
return $this->create_entry_from_db_record( $entry );
|
||||
}
|
||||
private function create_entry_from_db_record( $record ) {
|
||||
if ( empty( $record ) ) {
|
||||
return new ActionScheduler_NullLogEntry();
|
||||
}
|
||||
if ( is_null( $record->log_date_gmt ) ) {
|
||||
$date = as_get_datetime_object( ActionScheduler_StoreSchema::DEFAULT_DATE );
|
||||
} else {
|
||||
$date = as_get_datetime_object( $record->log_date_gmt );
|
||||
}
|
||||
return new ActionScheduler_LogEntry( $record->action_id, $record->message, $date );
|
||||
}
|
||||
public function get_logs( $action_id ) {
|
||||
//phpcs:ignore Generic.Commenting.DocComment.MissingShort
|
||||
global $wpdb;
|
||||
$records = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$wpdb->actionscheduler_logs} WHERE action_id=%d", $action_id ) );
|
||||
return array_map( array( $this, 'create_entry_from_db_record' ), $records );
|
||||
}
|
||||
public function init() {
|
||||
$table_maker = new ActionScheduler_LoggerSchema();
|
||||
$table_maker->init();
|
||||
$table_maker->register_tables();
|
||||
parent::init();
|
||||
add_action( 'action_scheduler_deleted_action', array( $this, 'clear_deleted_action_logs' ), 10, 1 );
|
||||
}
|
||||
public function clear_deleted_action_logs( $action_id ) {
|
||||
//phpcs:ignore Generic.Commenting.DocComment.MissingShort
|
||||
global $wpdb;
|
||||
$wpdb->delete( $wpdb->actionscheduler_logs, array( 'action_id' => $action_id ), array( '%d' ) );
|
||||
}
|
||||
public function bulk_log_cancel_actions( $action_ids ) {
|
||||
if ( empty( $action_ids ) ) {
|
||||
return;
|
||||
}
|
||||
//phpcs:ignore Generic.Commenting.DocComment.MissingShort
|
||||
global $wpdb;
|
||||
$date = as_get_datetime_object();
|
||||
$date_gmt = $date->format( 'Y-m-d H:i:s' );
|
||||
ActionScheduler_TimezoneHelper::set_local_timezone( $date );
|
||||
$date_local = $date->format( 'Y-m-d H:i:s' );
|
||||
$message = __( 'action canceled', 'action-scheduler' );
|
||||
$format = '(%d, ' . $wpdb->prepare( '%s, %s, %s', $message, $date_gmt, $date_local ) . ')';
|
||||
$sql_query = "INSERT {$wpdb->actionscheduler_logs} (action_id, message, log_date_gmt, log_date_local) VALUES ";
|
||||
$value_rows = array();
|
||||
foreach ( $action_ids as $action_id ) {
|
||||
$value_rows[] = $wpdb->prepare( $format, $action_id ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
}
|
||||
$sql_query .= implode( ',', $value_rows );
|
||||
$wpdb->query( $sql_query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
}
|
||||
}
|
||||
+692
@@ -0,0 +1,692 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class ActionScheduler_DBStore extends ActionScheduler_Store {
|
||||
private $claim_before_date = null;
|
||||
protected static $max_args_length = 8000;
|
||||
protected static $max_index_length = 191;
|
||||
protected $claim_filters = [
|
||||
'group' => '',
|
||||
'hooks' => '',
|
||||
'exclude-groups' => '',
|
||||
];
|
||||
public function init() {
|
||||
$table_maker = new ActionScheduler_StoreSchema();
|
||||
$table_maker->init();
|
||||
$table_maker->register_tables();
|
||||
}
|
||||
public function save_unique_action( ActionScheduler_Action $action, \DateTime $scheduled_date = null ) {
|
||||
return $this->save_action_to_db( $action, $scheduled_date, true );
|
||||
}
|
||||
public function save_action( ActionScheduler_Action $action, \DateTime $scheduled_date = null ) {
|
||||
return $this->save_action_to_db( $action, $scheduled_date, false );
|
||||
}
|
||||
private function save_action_to_db( ActionScheduler_Action $action, DateTime $date = null, $unique = false ) {
|
||||
global $wpdb;
|
||||
try {
|
||||
$this->validate_action( $action );
|
||||
$data = array(
|
||||
'hook' => $action->get_hook(),
|
||||
'status' => ( $action->is_finished() ? self::STATUS_COMPLETE : self::STATUS_PENDING ),
|
||||
'scheduled_date_gmt' => $this->get_scheduled_date_string( $action, $date ),
|
||||
'scheduled_date_local' => $this->get_scheduled_date_string_local( $action, $date ),
|
||||
'schedule' => serialize( $action->get_schedule() ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
|
||||
'group_id' => current( $this->get_group_ids( $action->get_group() ) ),
|
||||
'priority' => $action->get_priority(),
|
||||
);
|
||||
$args = wp_json_encode( $action->get_args() );
|
||||
if ( strlen( $args ) <= static::$max_index_length ) {
|
||||
$data['args'] = $args;
|
||||
} else {
|
||||
$data['args'] = $this->hash_args( $args );
|
||||
$data['extended_args'] = $args;
|
||||
}
|
||||
$insert_sql = $this->build_insert_sql( $data, $unique );
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $insert_sql should be already prepared.
|
||||
$wpdb->query( $insert_sql );
|
||||
$action_id = $wpdb->insert_id;
|
||||
if ( is_wp_error( $action_id ) ) {
|
||||
throw new \RuntimeException( $action_id->get_error_message() );
|
||||
} elseif ( empty( $action_id ) ) {
|
||||
if ( $unique ) {
|
||||
return 0;
|
||||
}
|
||||
throw new \RuntimeException( $wpdb->last_error ? $wpdb->last_error : __( 'Database error.', 'action-scheduler' ) );
|
||||
}
|
||||
do_action( 'action_scheduler_stored_action', $action_id );
|
||||
return $action_id;
|
||||
} catch ( \Exception $e ) {
|
||||
throw new \RuntimeException( sprintf( __( 'Error saving action: %s', 'action-scheduler' ), $e->getMessage() ), 0 );
|
||||
}
|
||||
}
|
||||
private function build_insert_sql( array $data, $unique ) {
|
||||
global $wpdb;
|
||||
$columns = array_keys( $data );
|
||||
$values = array_values( $data );
|
||||
$placeholders = array_map( array( $this, 'get_placeholder_for_column' ), $columns );
|
||||
$table_name = ! empty( $wpdb->actionscheduler_actions ) ? $wpdb->actionscheduler_actions : $wpdb->prefix . 'actionscheduler_actions';
|
||||
$column_sql = '`' . implode( '`, `', $columns ) . '`';
|
||||
$placeholder_sql = implode( ', ', $placeholders );
|
||||
$where_clause = $this->build_where_clause_for_insert( $data, $table_name, $unique );
|
||||
// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- $column_sql and $where_clause are already prepared. $placeholder_sql is hardcoded.
|
||||
$insert_query = $wpdb->prepare(
|
||||
"
|
||||
INSERT INTO $table_name ( $column_sql )
|
||||
SELECT $placeholder_sql FROM DUAL
|
||||
WHERE ( $where_clause ) IS NULL",
|
||||
$values
|
||||
);
|
||||
// phpcs:enable
|
||||
return $insert_query;
|
||||
}
|
||||
private function build_where_clause_for_insert( $data, $table_name, $unique ) {
|
||||
global $wpdb;
|
||||
if ( ! $unique ) {
|
||||
return 'SELECT NULL FROM DUAL';
|
||||
}
|
||||
$pending_statuses = array(
|
||||
ActionScheduler_Store::STATUS_PENDING,
|
||||
ActionScheduler_Store::STATUS_RUNNING,
|
||||
);
|
||||
$pending_status_placeholders = implode( ', ', array_fill( 0, count( $pending_statuses ), '%s' ) );
|
||||
// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- $pending_status_placeholders is hardcoded.
|
||||
$where_clause = $wpdb->prepare(
|
||||
"
|
||||
SELECT action_id FROM $table_name
|
||||
WHERE status IN ( $pending_status_placeholders )
|
||||
AND hook = %s
|
||||
AND `group_id` = %d
|
||||
",
|
||||
array_merge(
|
||||
$pending_statuses,
|
||||
array(
|
||||
$data['hook'],
|
||||
$data['group_id'],
|
||||
)
|
||||
)
|
||||
);
|
||||
// phpcs:enable
|
||||
return "$where_clause" . ' LIMIT 1';
|
||||
}
|
||||
private function get_placeholder_for_column( $column_name ) {
|
||||
$string_columns = array(
|
||||
'hook',
|
||||
'status',
|
||||
'scheduled_date_gmt',
|
||||
'scheduled_date_local',
|
||||
'args',
|
||||
'schedule',
|
||||
'last_attempt_gmt',
|
||||
'last_attempt_local',
|
||||
'extended_args',
|
||||
);
|
||||
return in_array( $column_name, $string_columns ) ? '%s' : '%d';
|
||||
}
|
||||
protected function hash_args( $args ) {
|
||||
return md5( $args );
|
||||
}
|
||||
protected function get_args_for_query( $args ) {
|
||||
$encoded = wp_json_encode( $args );
|
||||
if ( strlen( $encoded ) <= static::$max_index_length ) {
|
||||
return $encoded;
|
||||
}
|
||||
return $this->hash_args( $encoded );
|
||||
}
|
||||
protected function get_group_ids( $slugs, $create_if_not_exists = true ) {
|
||||
$slugs = (array) $slugs;
|
||||
$group_ids = array();
|
||||
if ( empty( $slugs ) ) {
|
||||
return array();
|
||||
}
|
||||
global $wpdb;
|
||||
foreach ( $slugs as $slug ) {
|
||||
$group_id = (int) $wpdb->get_var( $wpdb->prepare( "SELECT group_id FROM {$wpdb->actionscheduler_groups} WHERE slug=%s", $slug ) );
|
||||
if ( empty( $group_id ) && $create_if_not_exists ) {
|
||||
$group_id = $this->create_group( $slug );
|
||||
}
|
||||
if ( $group_id ) {
|
||||
$group_ids[] = $group_id;
|
||||
}
|
||||
}
|
||||
return $group_ids;
|
||||
}
|
||||
protected function create_group( $slug ) {
|
||||
global $wpdb;
|
||||
$wpdb->insert( $wpdb->actionscheduler_groups, array( 'slug' => $slug ) );
|
||||
return (int) $wpdb->insert_id;
|
||||
}
|
||||
public function fetch_action( $action_id ) {
|
||||
global $wpdb;
|
||||
$data = $wpdb->get_row(
|
||||
$wpdb->prepare(
|
||||
"SELECT a.*, g.slug AS `group` FROM {$wpdb->actionscheduler_actions} a LEFT JOIN {$wpdb->actionscheduler_groups} g ON a.group_id=g.group_id WHERE a.action_id=%d",
|
||||
$action_id
|
||||
)
|
||||
);
|
||||
if ( empty( $data ) ) {
|
||||
return $this->get_null_action();
|
||||
}
|
||||
if ( ! empty( $data->extended_args ) ) {
|
||||
$data->args = $data->extended_args;
|
||||
unset( $data->extended_args );
|
||||
}
|
||||
// Convert NULL dates to zero dates.
|
||||
$date_fields = array(
|
||||
'scheduled_date_gmt',
|
||||
'scheduled_date_local',
|
||||
'last_attempt_gmt',
|
||||
'last_attempt_gmt',
|
||||
);
|
||||
foreach ( $date_fields as $date_field ) {
|
||||
if ( is_null( $data->$date_field ) ) {
|
||||
$data->$date_field = ActionScheduler_StoreSchema::DEFAULT_DATE;
|
||||
}
|
||||
}
|
||||
try {
|
||||
$action = $this->make_action_from_db_record( $data );
|
||||
} catch ( ActionScheduler_InvalidActionException $exception ) {
|
||||
do_action( 'action_scheduler_failed_fetch_action', $action_id, $exception );
|
||||
return $this->get_null_action();
|
||||
}
|
||||
return $action;
|
||||
}
|
||||
protected function get_null_action() {
|
||||
return new ActionScheduler_NullAction();
|
||||
}
|
||||
protected function make_action_from_db_record( $data ) {
|
||||
$hook = $data->hook;
|
||||
$args = json_decode( $data->args, true );
|
||||
$schedule = unserialize( $data->schedule ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_unserialize
|
||||
$this->validate_args( $args, $data->action_id );
|
||||
$this->validate_schedule( $schedule, $data->action_id );
|
||||
if ( empty( $schedule ) ) {
|
||||
$schedule = new ActionScheduler_NullSchedule();
|
||||
}
|
||||
$group = $data->group ? $data->group : '';
|
||||
return ActionScheduler::factory()->get_stored_action( $data->status, $data->hook, $args, $schedule, $group, $data->priority );
|
||||
}
|
||||
protected function get_query_actions_sql( array $query, $select_or_count = 'select' ) {
|
||||
if ( ! in_array( $select_or_count, array( 'select', 'count' ), true ) ) {
|
||||
throw new InvalidArgumentException( __( 'Invalid value for select or count parameter. Cannot query actions.', 'action-scheduler' ) );
|
||||
}
|
||||
$query = wp_parse_args( $query, array(
|
||||
'hook' => '',
|
||||
'args' => null,
|
||||
'partial_args_matching' => 'off', // can be 'like' or 'json'
|
||||
'date' => null,
|
||||
'date_compare' => '<=',
|
||||
'modified' => null,
|
||||
'modified_compare' => '<=',
|
||||
'group' => '',
|
||||
'status' => '',
|
||||
'claimed' => null,
|
||||
'per_page' => 5,
|
||||
'offset' => 0,
|
||||
'orderby' => 'date',
|
||||
'order' => 'ASC',
|
||||
) );
|
||||
global $wpdb;
|
||||
$db_server_info = is_callable( array( $wpdb, 'db_server_info' ) ) ? $wpdb->db_server_info() : $wpdb->db_version();
|
||||
if ( false !== strpos( $db_server_info, 'MariaDB' ) ) {
|
||||
$supports_json = version_compare(
|
||||
PHP_VERSION_ID >= 80016 ? $wpdb->db_version() : preg_replace( '/[^0-9.].*/', '', str_replace( '5.5.5-', '', $db_server_info ) ),
|
||||
'10.2',
|
||||
'>='
|
||||
);
|
||||
} else {
|
||||
$supports_json = version_compare( $wpdb->db_version(), '5.7', '>=' );
|
||||
}
|
||||
$sql = ( 'count' === $select_or_count ) ? 'SELECT count(a.action_id)' : 'SELECT a.action_id';
|
||||
$sql .= " FROM {$wpdb->actionscheduler_actions} a";
|
||||
$sql_params = array();
|
||||
if ( ! empty( $query['group'] ) || 'group' === $query['orderby'] ) {
|
||||
$sql .= " LEFT JOIN {$wpdb->actionscheduler_groups} g ON g.group_id=a.group_id";
|
||||
}
|
||||
$sql .= " WHERE 1=1";
|
||||
if ( ! empty( $query['group'] ) ) {
|
||||
$sql .= " AND g.slug=%s";
|
||||
$sql_params[] = $query['group'];
|
||||
}
|
||||
if ( ! empty( $query['hook'] ) ) {
|
||||
$sql .= " AND a.hook=%s";
|
||||
$sql_params[] = $query['hook'];
|
||||
}
|
||||
if ( ! is_null( $query['args'] ) ) {
|
||||
switch ( $query['partial_args_matching'] ) {
|
||||
case 'json':
|
||||
if ( ! $supports_json ) {
|
||||
throw new \RuntimeException( __( 'JSON partial matching not supported in your environment. Please check your MySQL/MariaDB version.', 'action-scheduler' ) );
|
||||
}
|
||||
$supported_types = array(
|
||||
'integer' => '%d',
|
||||
'boolean' => '%s',
|
||||
'double' => '%f',
|
||||
'string' => '%s',
|
||||
);
|
||||
foreach ( $query['args'] as $key => $value ) {
|
||||
$value_type = gettype( $value );
|
||||
if ( 'boolean' === $value_type ) {
|
||||
$value = $value ? 'true' : 'false';
|
||||
}
|
||||
$placeholder = isset( $supported_types[ $value_type ] ) ? $supported_types[ $value_type ] : false;
|
||||
if ( ! $placeholder ) {
|
||||
throw new \RuntimeException( sprintf(
|
||||
__( 'The value type for the JSON partial matching is not supported. Must be either integer, boolean, double or string. %s type provided.', 'action-scheduler' ),
|
||||
$value_type
|
||||
) );
|
||||
}
|
||||
$sql .= ' AND JSON_EXTRACT(a.args, %s)='.$placeholder;
|
||||
$sql_params[] = '$.'.$key;
|
||||
$sql_params[] = $value;
|
||||
}
|
||||
break;
|
||||
case 'like':
|
||||
foreach ( $query['args'] as $key => $value ) {
|
||||
$sql .= ' AND a.args LIKE %s';
|
||||
$json_partial = $wpdb->esc_like( trim( wp_json_encode( array( $key => $value ) ), '{}' ) );
|
||||
$sql_params[] = "%{$json_partial}%";
|
||||
}
|
||||
break;
|
||||
case 'off':
|
||||
$sql .= " AND a.args=%s";
|
||||
$sql_params[] = $this->get_args_for_query( $query['args'] );
|
||||
break;
|
||||
default:
|
||||
throw new \RuntimeException( __( 'Unknown partial args matching value.', 'action-scheduler' ) );
|
||||
}
|
||||
}
|
||||
if ( $query['status'] ) {
|
||||
$statuses = (array) $query['status'];
|
||||
$placeholders = array_fill( 0, count( $statuses ), '%s' );
|
||||
$sql .= ' AND a.status IN (' . join( ', ', $placeholders ) . ')';
|
||||
$sql_params = array_merge( $sql_params, array_values( $statuses ) );
|
||||
}
|
||||
if ( $query['date'] instanceof \DateTime ) {
|
||||
$date = clone $query['date'];
|
||||
$date->setTimezone( new \DateTimeZone( 'UTC' ) );
|
||||
$date_string = $date->format( 'Y-m-d H:i:s' );
|
||||
$comparator = $this->validate_sql_comparator( $query['date_compare'] );
|
||||
$sql .= " AND a.scheduled_date_gmt $comparator %s";
|
||||
$sql_params[] = $date_string;
|
||||
}
|
||||
if ( $query['modified'] instanceof \DateTime ) {
|
||||
$modified = clone $query['modified'];
|
||||
$modified->setTimezone( new \DateTimeZone( 'UTC' ) );
|
||||
$date_string = $modified->format( 'Y-m-d H:i:s' );
|
||||
$comparator = $this->validate_sql_comparator( $query['modified_compare'] );
|
||||
$sql .= " AND a.last_attempt_gmt $comparator %s";
|
||||
$sql_params[] = $date_string;
|
||||
}
|
||||
if ( true === $query['claimed'] ) {
|
||||
$sql .= ' AND a.claim_id != 0';
|
||||
} elseif ( false === $query['claimed'] ) {
|
||||
$sql .= ' AND a.claim_id = 0';
|
||||
} elseif ( ! is_null( $query['claimed'] ) ) {
|
||||
$sql .= ' AND a.claim_id = %d';
|
||||
$sql_params[] = $query['claimed'];
|
||||
}
|
||||
if ( ! empty( $query['search'] ) ) {
|
||||
$sql .= ' AND (a.hook LIKE %s OR (a.extended_args IS NULL AND a.args LIKE %s) OR a.extended_args LIKE %s';
|
||||
for ( $i = 0; $i < 3; $i++ ) {
|
||||
$sql_params[] = sprintf( '%%%s%%', $query['search'] );
|
||||
}
|
||||
$search_claim_id = (int) $query['search'];
|
||||
if ( $search_claim_id ) {
|
||||
$sql .= ' OR a.claim_id = %d';
|
||||
$sql_params[] = $search_claim_id;
|
||||
}
|
||||
$sql .= ')';
|
||||
}
|
||||
if ( 'select' === $select_or_count ) {
|
||||
if ( 'ASC' === strtoupper( $query['order'] ) ) {
|
||||
$order = 'ASC';
|
||||
} else {
|
||||
$order = 'DESC';
|
||||
}
|
||||
switch ( $query['orderby'] ) {
|
||||
case 'hook':
|
||||
$sql .= " ORDER BY a.hook $order";
|
||||
break;
|
||||
case 'group':
|
||||
$sql .= " ORDER BY g.slug $order";
|
||||
break;
|
||||
case 'modified':
|
||||
$sql .= " ORDER BY a.last_attempt_gmt $order";
|
||||
break;
|
||||
case 'none':
|
||||
break;
|
||||
case 'action_id':
|
||||
$sql .= " ORDER BY a.action_id $order";
|
||||
break;
|
||||
case 'date':
|
||||
default:
|
||||
$sql .= " ORDER BY a.scheduled_date_gmt $order";
|
||||
break;
|
||||
}
|
||||
if ( $query['per_page'] > 0 ) {
|
||||
$sql .= ' LIMIT %d, %d';
|
||||
$sql_params[] = $query['offset'];
|
||||
$sql_params[] = $query['per_page'];
|
||||
}
|
||||
}
|
||||
if ( ! empty( $sql_params ) ) {
|
||||
$sql = $wpdb->prepare( $sql, $sql_params ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
}
|
||||
return $sql;
|
||||
}
|
||||
public function query_actions( $query = array(), $query_type = 'select' ) {
|
||||
global $wpdb;
|
||||
$sql = $this->get_query_actions_sql( $query, $query_type );
|
||||
return ( 'count' === $query_type ) ? $wpdb->get_var( $sql ) : $wpdb->get_col( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.NoSql, WordPress.DB.DirectDatabaseQuery.NoCaching
|
||||
}
|
||||
public function action_counts() {
|
||||
global $wpdb;
|
||||
$sql = "SELECT a.status, count(a.status) as 'count'";
|
||||
$sql .= " FROM {$wpdb->actionscheduler_actions} a";
|
||||
$sql .= ' GROUP BY a.status';
|
||||
$actions_count_by_status = array();
|
||||
$action_stati_and_labels = $this->get_status_labels();
|
||||
foreach ( $wpdb->get_results( $sql ) as $action_data ) { // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
// Ignore any actions with invalid status.
|
||||
if ( array_key_exists( $action_data->status, $action_stati_and_labels ) ) {
|
||||
$actions_count_by_status[ $action_data->status ] = $action_data->count;
|
||||
}
|
||||
}
|
||||
return $actions_count_by_status;
|
||||
}
|
||||
public function cancel_action( $action_id ) {
|
||||
global $wpdb;
|
||||
$updated = $wpdb->update(
|
||||
$wpdb->actionscheduler_actions,
|
||||
array( 'status' => self::STATUS_CANCELED ),
|
||||
array( 'action_id' => $action_id ),
|
||||
array( '%s' ),
|
||||
array( '%d' )
|
||||
);
|
||||
if ( false === $updated ) {
|
||||
throw new \InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) );
|
||||
}
|
||||
do_action( 'action_scheduler_canceled_action', $action_id );
|
||||
}
|
||||
public function cancel_actions_by_hook( $hook ) {
|
||||
$this->bulk_cancel_actions( array( 'hook' => $hook ) );
|
||||
}
|
||||
public function cancel_actions_by_group( $group ) {
|
||||
$this->bulk_cancel_actions( array( 'group' => $group ) );
|
||||
}
|
||||
protected function bulk_cancel_actions( $query_args ) {
|
||||
global $wpdb;
|
||||
if ( ! is_array( $query_args ) ) {
|
||||
return;
|
||||
}
|
||||
// Don't cancel actions that are already canceled.
|
||||
if ( isset( $query_args['status'] ) && self::STATUS_CANCELED === $query_args['status'] ) {
|
||||
return;
|
||||
}
|
||||
$action_ids = true;
|
||||
$query_args = wp_parse_args(
|
||||
$query_args,
|
||||
array(
|
||||
'per_page' => 1000,
|
||||
'status' => self::STATUS_PENDING,
|
||||
'orderby' => 'none',
|
||||
)
|
||||
);
|
||||
while ( $action_ids ) {
|
||||
$action_ids = $this->query_actions( $query_args );
|
||||
if ( empty( $action_ids ) ) {
|
||||
break;
|
||||
}
|
||||
$format = array_fill( 0, count( $action_ids ), '%d' );
|
||||
$query_in = '(' . implode( ',', $format ) . ')';
|
||||
$parameters = $action_ids;
|
||||
array_unshift( $parameters, self::STATUS_CANCELED );
|
||||
$wpdb->query(
|
||||
$wpdb->prepare(
|
||||
"UPDATE {$wpdb->actionscheduler_actions} SET status = %s WHERE action_id IN {$query_in}", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$parameters
|
||||
)
|
||||
);
|
||||
do_action( 'action_scheduler_bulk_cancel_actions', $action_ids );
|
||||
}
|
||||
}
|
||||
public function delete_action( $action_id ) {
|
||||
global $wpdb;
|
||||
$deleted = $wpdb->delete( $wpdb->actionscheduler_actions, array( 'action_id' => $action_id ), array( '%d' ) );
|
||||
if ( empty( $deleted ) ) {
|
||||
throw new \InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) ); //phpcs:ignore WordPress.WP.I18n.MissingTranslatorsComment
|
||||
}
|
||||
do_action( 'action_scheduler_deleted_action', $action_id );
|
||||
}
|
||||
public function get_date( $action_id ) {
|
||||
$date = $this->get_date_gmt( $action_id );
|
||||
ActionScheduler_TimezoneHelper::set_local_timezone( $date );
|
||||
return $date;
|
||||
}
|
||||
protected function get_date_gmt( $action_id ) {
|
||||
global $wpdb;
|
||||
$record = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->actionscheduler_actions} WHERE action_id=%d", $action_id ) );
|
||||
if ( empty( $record ) ) {
|
||||
throw new \InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) ); //phpcs:ignore WordPress.WP.I18n.MissingTranslatorsComment
|
||||
}
|
||||
if ( self::STATUS_PENDING === $record->status ) {
|
||||
return as_get_datetime_object( $record->scheduled_date_gmt );
|
||||
} else {
|
||||
return as_get_datetime_object( $record->last_attempt_gmt );
|
||||
}
|
||||
}
|
||||
public function stake_claim( $max_actions = 10, \DateTime $before_date = null, $hooks = array(), $group = '' ) {
|
||||
$claim_id = $this->generate_claim_id();
|
||||
$this->claim_before_date = $before_date;
|
||||
$this->claim_actions( $claim_id, $max_actions, $before_date, $hooks, $group );
|
||||
$action_ids = $this->find_actions_by_claim_id( $claim_id );
|
||||
$this->claim_before_date = null;
|
||||
return new ActionScheduler_ActionClaim( $claim_id, $action_ids );
|
||||
}
|
||||
protected function generate_claim_id() {
|
||||
global $wpdb;
|
||||
$now = as_get_datetime_object();
|
||||
$wpdb->insert( $wpdb->actionscheduler_claims, array( 'date_created_gmt' => $now->format( 'Y-m-d H:i:s' ) ) );
|
||||
return $wpdb->insert_id;
|
||||
}
|
||||
public function set_claim_filter( $filter_name, $filter_values ) {
|
||||
if ( isset( $this->claim_filters[ $filter_name ] ) ) {
|
||||
$this->claim_filters[ $filter_name ] = $filter_values;
|
||||
}
|
||||
}
|
||||
public function get_claim_filter( $filter_name ) {
|
||||
if ( isset( $this->claim_filters[ $filter_name ] ) ) {
|
||||
return $this->claim_filters[ $filter_name ];
|
||||
}
|
||||
return '';
|
||||
}
|
||||
protected function claim_actions( $claim_id, $limit, \DateTime $before_date = null, $hooks = array(), $group = '' ) {
|
||||
global $wpdb;
|
||||
$now = as_get_datetime_object();
|
||||
$date = is_null( $before_date ) ? $now : clone $before_date;
|
||||
// can't use $wpdb->update() because of the <= condition.
|
||||
$update = "UPDATE {$wpdb->actionscheduler_actions} SET claim_id=%d, last_attempt_gmt=%s, last_attempt_local=%s";
|
||||
$params = array(
|
||||
$claim_id,
|
||||
$now->format( 'Y-m-d H:i:s' ),
|
||||
current_time( 'mysql' ),
|
||||
);
|
||||
// Set claim filters.
|
||||
if ( ! empty( $hooks ) ) {
|
||||
$this->set_claim_filter( 'hooks', $hooks );
|
||||
} else {
|
||||
$hooks = $this->get_claim_filter( 'hooks' );
|
||||
}
|
||||
if ( ! empty( $group ) ) {
|
||||
$this->set_claim_filter( 'group', $group );
|
||||
} else {
|
||||
$group = $this->get_claim_filter( 'group' );
|
||||
}
|
||||
$where = 'WHERE claim_id = 0 AND scheduled_date_gmt <= %s AND status=%s';
|
||||
$params[] = $date->format( 'Y-m-d H:i:s' );
|
||||
$params[] = self::STATUS_PENDING;
|
||||
if ( ! empty( $hooks ) ) {
|
||||
$placeholders = array_fill( 0, count( $hooks ), '%s' );
|
||||
$where .= ' AND hook IN (' . join( ', ', $placeholders ) . ')';
|
||||
$params = array_merge( $params, array_values( $hooks ) );
|
||||
}
|
||||
$group_operator = 'IN';
|
||||
if ( empty( $group ) ) {
|
||||
$group = $this->get_claim_filter( 'exclude-groups' );
|
||||
$group_operator = 'NOT IN';
|
||||
}
|
||||
if ( ! empty( $group ) ) {
|
||||
$group_ids = $this->get_group_ids( $group, false );
|
||||
// throw exception if no matching group(s) found, this matches ActionScheduler_wpPostStore's behaviour.
|
||||
if ( empty( $group_ids ) ) {
|
||||
throw new InvalidArgumentException(
|
||||
sprintf(
|
||||
_n(
|
||||
'The group "%s" does not exist.',
|
||||
'The groups "%s" do not exist.',
|
||||
is_array( $group ) ? count( $group ) : 1,
|
||||
'action-scheduler'
|
||||
),
|
||||
$group
|
||||
)
|
||||
);
|
||||
}
|
||||
$id_list = implode( ',', array_map( 'intval', $group_ids ) );
|
||||
$where .= " AND group_id {$group_operator} ( $id_list )";
|
||||
}
|
||||
$order = apply_filters( 'action_scheduler_claim_actions_order_by', 'ORDER BY priority ASC, attempts ASC, scheduled_date_gmt ASC, action_id ASC' );
|
||||
$params[] = $limit;
|
||||
$sql = $wpdb->prepare( "{$update} {$where} {$order} LIMIT %d", $params ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders
|
||||
$rows_affected = $wpdb->query( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
|
||||
if ( false === $rows_affected ) {
|
||||
$error = empty( $wpdb->last_error )
|
||||
? _x( 'unknown', 'database error', 'action-scheduler' )
|
||||
: $wpdb->last_error;
|
||||
throw new \RuntimeException(
|
||||
sprintf(
|
||||
__( 'Unable to claim actions. Database error: %s.', 'action-scheduler' ),
|
||||
$error
|
||||
)
|
||||
);
|
||||
}
|
||||
return (int) $rows_affected;
|
||||
}
|
||||
public function get_claim_count() {
|
||||
global $wpdb;
|
||||
$sql = "SELECT COUNT(DISTINCT claim_id) FROM {$wpdb->actionscheduler_actions} WHERE claim_id != 0 AND status IN ( %s, %s)";
|
||||
$sql = $wpdb->prepare( $sql, array( self::STATUS_PENDING, self::STATUS_RUNNING ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
return (int) $wpdb->get_var( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
}
|
||||
public function get_claim_id( $action_id ) {
|
||||
global $wpdb;
|
||||
$sql = "SELECT claim_id FROM {$wpdb->actionscheduler_actions} WHERE action_id=%d";
|
||||
$sql = $wpdb->prepare( $sql, $action_id ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
return (int) $wpdb->get_var( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
}
|
||||
public function find_actions_by_claim_id( $claim_id ) {
|
||||
global $wpdb;
|
||||
$action_ids = array();
|
||||
$before_date = isset( $this->claim_before_date ) ? $this->claim_before_date : as_get_datetime_object();
|
||||
$cut_off = $before_date->format( 'Y-m-d H:i:s' );
|
||||
$sql = $wpdb->prepare(
|
||||
"SELECT action_id, scheduled_date_gmt FROM {$wpdb->actionscheduler_actions} WHERE claim_id = %d ORDER BY priority ASC, attempts ASC, scheduled_date_gmt ASC, action_id ASC",
|
||||
$claim_id
|
||||
);
|
||||
// Verify that the scheduled date for each action is within the expected bounds (in some unusual
|
||||
// cases, we cannot depend on MySQL to honor all of the WHERE conditions we specify).
|
||||
foreach ( $wpdb->get_results( $sql ) as $claimed_action ) { // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
if ( $claimed_action->scheduled_date_gmt <= $cut_off ) {
|
||||
$action_ids[] = absint( $claimed_action->action_id );
|
||||
}
|
||||
}
|
||||
return $action_ids;
|
||||
}
|
||||
public function release_claim( ActionScheduler_ActionClaim $claim ) {
|
||||
global $wpdb;
|
||||
$action_ids = $wpdb->get_col( $wpdb->prepare( "SELECT action_id FROM {$wpdb->actionscheduler_actions} WHERE claim_id = %d", $claim->get_id() ) );
|
||||
$row_updates = 0;
|
||||
if ( count( $action_ids ) > 0 ) {
|
||||
$action_id_string = implode( ',', array_map( 'absint', $action_ids ) );
|
||||
$row_updates = $wpdb->query( "UPDATE {$wpdb->actionscheduler_actions} SET claim_id = 0 WHERE action_id IN ({$action_id_string})" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
}
|
||||
$wpdb->delete( $wpdb->actionscheduler_claims, array( 'claim_id' => $claim->get_id() ), array( '%d' ) );
|
||||
if ( $row_updates < count( $action_ids ) ) {
|
||||
throw new RuntimeException(
|
||||
sprintf(
|
||||
// translators: %d is an id.
|
||||
__( 'Unable to release actions from claim id %d.', 'action-scheduler' ),
|
||||
$claim->get_id()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
public function unclaim_action( $action_id ) {
|
||||
global $wpdb;
|
||||
$wpdb->update(
|
||||
$wpdb->actionscheduler_actions,
|
||||
array( 'claim_id' => 0 ),
|
||||
array( 'action_id' => $action_id ),
|
||||
array( '%s' ),
|
||||
array( '%d' )
|
||||
);
|
||||
}
|
||||
public function mark_failure( $action_id ) {
|
||||
global $wpdb;
|
||||
$updated = $wpdb->update(
|
||||
$wpdb->actionscheduler_actions,
|
||||
array( 'status' => self::STATUS_FAILED ),
|
||||
array( 'action_id' => $action_id ),
|
||||
array( '%s' ),
|
||||
array( '%d' )
|
||||
);
|
||||
if ( empty( $updated ) ) {
|
||||
throw new \InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) ); //phpcs:ignore WordPress.WP.I18n.MissingTranslatorsComment
|
||||
}
|
||||
}
|
||||
public function log_execution( $action_id ) {
|
||||
global $wpdb;
|
||||
$sql = "UPDATE {$wpdb->actionscheduler_actions} SET attempts = attempts+1, status=%s, last_attempt_gmt = %s, last_attempt_local = %s WHERE action_id = %d";
|
||||
$sql = $wpdb->prepare( $sql, self::STATUS_RUNNING, current_time( 'mysql', true ), current_time( 'mysql' ), $action_id ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
$status_updated = $wpdb->query( $sql );
|
||||
if ( ! $status_updated ) {
|
||||
throw new Exception(
|
||||
sprintf(
|
||||
__( 'Unable to update the status of action %1$d to %2$s.', 'action-scheduler' ),
|
||||
$action_id,
|
||||
self::STATUS_RUNNING
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
public function mark_complete( $action_id ) {
|
||||
global $wpdb;
|
||||
$updated = $wpdb->update(
|
||||
$wpdb->actionscheduler_actions,
|
||||
array(
|
||||
'status' => self::STATUS_COMPLETE,
|
||||
'last_attempt_gmt' => current_time( 'mysql', true ),
|
||||
'last_attempt_local' => current_time( 'mysql' ),
|
||||
),
|
||||
array( 'action_id' => $action_id ),
|
||||
array( '%s' ),
|
||||
array( '%d' )
|
||||
);
|
||||
if ( empty( $updated ) ) {
|
||||
throw new \InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) ); //phpcs:ignore WordPress.WP.I18n.MissingTranslatorsComment
|
||||
}
|
||||
do_action( 'action_scheduler_completed_action', $action_id );
|
||||
}
|
||||
public function get_status( $action_id ) {
|
||||
global $wpdb;
|
||||
$sql = "SELECT status FROM {$wpdb->actionscheduler_actions} WHERE action_id=%d";
|
||||
$sql = $wpdb->prepare( $sql, $action_id ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
$status = $wpdb->get_var( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
if ( null === $status ) {
|
||||
throw new \InvalidArgumentException( __( 'Invalid action ID. No status found.', 'action-scheduler' ) );
|
||||
} elseif ( empty( $status ) ) {
|
||||
throw new \RuntimeException( __( 'Unknown status found for action.', 'action-scheduler' ) );
|
||||
} else {
|
||||
return $status;
|
||||
}
|
||||
}
|
||||
}
|
||||
+205
@@ -0,0 +1,205 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use ActionScheduler_Store as Store;
|
||||
use Action_Scheduler\Migration\Runner;
|
||||
use Action_Scheduler\Migration\Config;
|
||||
use Action_Scheduler\Migration\Controller;
|
||||
class ActionScheduler_HybridStore extends Store {
|
||||
const DEMARKATION_OPTION = 'action_scheduler_hybrid_store_demarkation';
|
||||
private $primary_store;
|
||||
private $secondary_store;
|
||||
private $migration_runner;
|
||||
private $demarkation_id = 0;
|
||||
public function __construct( Config $config = null ) {
|
||||
$this->demarkation_id = (int) get_option( self::DEMARKATION_OPTION, 0 );
|
||||
if ( empty( $config ) ) {
|
||||
$config = Controller::instance()->get_migration_config_object();
|
||||
}
|
||||
$this->primary_store = $config->get_destination_store();
|
||||
$this->secondary_store = $config->get_source_store();
|
||||
$this->migration_runner = new Runner( $config );
|
||||
}
|
||||
public function init() {
|
||||
add_action( 'action_scheduler/created_table', [ $this, 'set_autoincrement' ], 10, 2 );
|
||||
$this->primary_store->init();
|
||||
$this->secondary_store->init();
|
||||
remove_action( 'action_scheduler/created_table', [ $this, 'set_autoincrement' ], 10 );
|
||||
}
|
||||
public function set_autoincrement( $table_name, $table_suffix ) {
|
||||
if ( ActionScheduler_StoreSchema::ACTIONS_TABLE === $table_suffix ) {
|
||||
if ( empty( $this->demarkation_id ) ) {
|
||||
$this->demarkation_id = $this->set_demarkation_id();
|
||||
}
|
||||
global $wpdb;
|
||||
$default_date = new DateTime( 'tomorrow' );
|
||||
$null_action = new ActionScheduler_NullAction();
|
||||
$date_gmt = $this->get_scheduled_date_string( $null_action, $default_date );
|
||||
$date_local = $this->get_scheduled_date_string_local( $null_action, $default_date );
|
||||
$row_count = $wpdb->insert(
|
||||
$wpdb->{ActionScheduler_StoreSchema::ACTIONS_TABLE},
|
||||
[
|
||||
'action_id' => $this->demarkation_id,
|
||||
'hook' => '',
|
||||
'status' => '',
|
||||
'scheduled_date_gmt' => $date_gmt,
|
||||
'scheduled_date_local' => $date_local,
|
||||
'last_attempt_gmt' => $date_gmt,
|
||||
'last_attempt_local' => $date_local,
|
||||
]
|
||||
);
|
||||
if ( $row_count > 0 ) {
|
||||
$wpdb->delete(
|
||||
$wpdb->{ActionScheduler_StoreSchema::ACTIONS_TABLE},
|
||||
[ 'action_id' => $this->demarkation_id ]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
private function set_demarkation_id( $id = null ) {
|
||||
if ( empty( $id ) ) {
|
||||
global $wpdb;
|
||||
$id = (int) $wpdb->get_var( "SELECT MAX(ID) FROM $wpdb->posts" );
|
||||
$id ++;
|
||||
}
|
||||
update_option( self::DEMARKATION_OPTION, $id );
|
||||
return $id;
|
||||
}
|
||||
public function find_action( $hook, $params = [] ) {
|
||||
$found_unmigrated_action = $this->secondary_store->find_action( $hook, $params );
|
||||
if ( ! empty( $found_unmigrated_action ) ) {
|
||||
$this->migrate( [ $found_unmigrated_action ] );
|
||||
}
|
||||
return $this->primary_store->find_action( $hook, $params );
|
||||
}
|
||||
public function query_actions( $query = [], $query_type = 'select' ) {
|
||||
$found_unmigrated_actions = $this->secondary_store->query_actions( $query, 'select' );
|
||||
if ( ! empty( $found_unmigrated_actions ) ) {
|
||||
$this->migrate( $found_unmigrated_actions );
|
||||
}
|
||||
return $this->primary_store->query_actions( $query, $query_type );
|
||||
}
|
||||
public function action_counts() {
|
||||
$unmigrated_actions_count = $this->secondary_store->action_counts();
|
||||
$migrated_actions_count = $this->primary_store->action_counts();
|
||||
$actions_count_by_status = array();
|
||||
foreach ( $this->get_status_labels() as $status_key => $status_label ) {
|
||||
$count = 0;
|
||||
if ( isset( $unmigrated_actions_count[ $status_key ] ) ) {
|
||||
$count += $unmigrated_actions_count[ $status_key ];
|
||||
}
|
||||
if ( isset( $migrated_actions_count[ $status_key ] ) ) {
|
||||
$count += $migrated_actions_count[ $status_key ];
|
||||
}
|
||||
$actions_count_by_status[ $status_key ] = $count;
|
||||
}
|
||||
$actions_count_by_status = array_filter( $actions_count_by_status );
|
||||
return $actions_count_by_status;
|
||||
}
|
||||
public function stake_claim( $max_actions = 10, DateTime $before_date = null, $hooks = array(), $group = '' ) {
|
||||
$claim = $this->secondary_store->stake_claim( $max_actions, $before_date, $hooks, $group );
|
||||
$claimed_actions = $claim->get_actions();
|
||||
if ( ! empty( $claimed_actions ) ) {
|
||||
$this->migrate( $claimed_actions );
|
||||
}
|
||||
$this->secondary_store->release_claim( $claim );
|
||||
return $this->primary_store->stake_claim( $max_actions, $before_date, $hooks, $group );
|
||||
}
|
||||
private function migrate( $action_ids ) {
|
||||
$this->migration_runner->migrate_actions( $action_ids );
|
||||
}
|
||||
public function save_action( ActionScheduler_Action $action, DateTime $date = null ) {
|
||||
return $this->primary_store->save_action( $action, $date );
|
||||
}
|
||||
public function fetch_action( $action_id ) {
|
||||
$store = $this->get_store_from_action_id( $action_id, true );
|
||||
if ( $store ) {
|
||||
return $store->fetch_action( $action_id );
|
||||
} else {
|
||||
return new ActionScheduler_NullAction();
|
||||
}
|
||||
}
|
||||
public function cancel_action( $action_id ) {
|
||||
$store = $this->get_store_from_action_id( $action_id );
|
||||
if ( $store ) {
|
||||
$store->cancel_action( $action_id );
|
||||
}
|
||||
}
|
||||
public function delete_action( $action_id ) {
|
||||
$store = $this->get_store_from_action_id( $action_id );
|
||||
if ( $store ) {
|
||||
$store->delete_action( $action_id );
|
||||
}
|
||||
}
|
||||
public function get_date( $action_id ) {
|
||||
$store = $this->get_store_from_action_id( $action_id );
|
||||
if ( $store ) {
|
||||
return $store->get_date( $action_id );
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
public function mark_failure( $action_id ) {
|
||||
$store = $this->get_store_from_action_id( $action_id );
|
||||
if ( $store ) {
|
||||
$store->mark_failure( $action_id );
|
||||
}
|
||||
}
|
||||
public function log_execution( $action_id ) {
|
||||
$store = $this->get_store_from_action_id( $action_id );
|
||||
if ( $store ) {
|
||||
$store->log_execution( $action_id );
|
||||
}
|
||||
}
|
||||
public function mark_complete( $action_id ) {
|
||||
$store = $this->get_store_from_action_id( $action_id );
|
||||
if ( $store ) {
|
||||
$store->mark_complete( $action_id );
|
||||
}
|
||||
}
|
||||
public function get_status( $action_id ) {
|
||||
$store = $this->get_store_from_action_id( $action_id );
|
||||
if ( $store ) {
|
||||
return $store->get_status( $action_id );
|
||||
}
|
||||
return null;
|
||||
}
|
||||
protected function get_store_from_action_id( $action_id, $primary_first = false ) {
|
||||
if ( $primary_first ) {
|
||||
$stores = [
|
||||
$this->primary_store,
|
||||
$this->secondary_store,
|
||||
];
|
||||
} elseif ( $action_id < $this->demarkation_id ) {
|
||||
$stores = [
|
||||
$this->secondary_store,
|
||||
$this->primary_store,
|
||||
];
|
||||
} else {
|
||||
$stores = [
|
||||
$this->primary_store,
|
||||
];
|
||||
}
|
||||
foreach ( $stores as $store ) {
|
||||
$action = $store->fetch_action( $action_id );
|
||||
if ( ! is_a( $action, 'ActionScheduler_NullAction' ) ) {
|
||||
return $store;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public function get_claim_count() {
|
||||
return $this->primary_store->get_claim_count();
|
||||
}
|
||||
public function get_claim_id( $action_id ) {
|
||||
return $this->primary_store->get_claim_id( $action_id );
|
||||
}
|
||||
public function release_claim( ActionScheduler_ActionClaim $claim ) {
|
||||
$this->primary_store->release_claim( $claim );
|
||||
}
|
||||
public function unclaim_action( $action_id ) {
|
||||
$this->primary_store->unclaim_action( $action_id );
|
||||
}
|
||||
public function find_actions_by_claim_id( $claim_id ) {
|
||||
return $this->primary_store->find_actions_by_claim_id( $claim_id );
|
||||
}
|
||||
}
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class ActionScheduler_wpCommentLogger extends ActionScheduler_Logger {
|
||||
const AGENT = 'ActionScheduler';
|
||||
const TYPE = 'action_log';
|
||||
public function log( $action_id, $message, DateTime $date = NULL ) {
|
||||
if ( empty($date) ) {
|
||||
$date = as_get_datetime_object();
|
||||
} else {
|
||||
$date = as_get_datetime_object( clone $date );
|
||||
}
|
||||
$comment_id = $this->create_wp_comment( $action_id, $message, $date );
|
||||
return $comment_id;
|
||||
}
|
||||
protected function create_wp_comment( $action_id, $message, DateTime $date ) {
|
||||
$comment_date_gmt = $date->format('Y-m-d H:i:s');
|
||||
ActionScheduler_TimezoneHelper::set_local_timezone( $date );
|
||||
$comment_data = array(
|
||||
'comment_post_ID' => $action_id,
|
||||
'comment_date' => $date->format('Y-m-d H:i:s'),
|
||||
'comment_date_gmt' => $comment_date_gmt,
|
||||
'comment_author' => self::AGENT,
|
||||
'comment_content' => $message,
|
||||
'comment_agent' => self::AGENT,
|
||||
'comment_type' => self::TYPE,
|
||||
);
|
||||
return wp_insert_comment($comment_data);
|
||||
}
|
||||
public function get_entry( $entry_id ) {
|
||||
$comment = $this->get_comment( $entry_id );
|
||||
if ( empty($comment) || $comment->comment_type != self::TYPE ) {
|
||||
return new ActionScheduler_NullLogEntry();
|
||||
}
|
||||
$date = as_get_datetime_object( $comment->comment_date_gmt );
|
||||
ActionScheduler_TimezoneHelper::set_local_timezone( $date );
|
||||
return new ActionScheduler_LogEntry( $comment->comment_post_ID, $comment->comment_content, $date );
|
||||
}
|
||||
public function get_logs( $action_id ) {
|
||||
$status = 'all';
|
||||
if ( get_post_status($action_id) == 'trash' ) {
|
||||
$status = 'post-trashed';
|
||||
}
|
||||
$comments = get_comments(array(
|
||||
'post_id' => $action_id,
|
||||
'orderby' => 'comment_date_gmt',
|
||||
'order' => 'ASC',
|
||||
'type' => self::TYPE,
|
||||
'status' => $status,
|
||||
));
|
||||
$logs = array();
|
||||
foreach ( $comments as $c ) {
|
||||
$entry = $this->get_entry( $c );
|
||||
if ( !empty($entry) ) {
|
||||
$logs[] = $entry;
|
||||
}
|
||||
}
|
||||
return $logs;
|
||||
}
|
||||
protected function get_comment( $comment_id ) {
|
||||
return get_comment( $comment_id );
|
||||
}
|
||||
public function filter_comment_queries( $query ) {
|
||||
foreach ( array('ID', 'parent', 'post_author', 'post_name', 'post_parent', 'type', 'post_type', 'post_id', 'post_ID') as $key ) {
|
||||
if ( !empty($query->query_vars[$key]) ) {
|
||||
return; // don't slow down queries that wouldn't include action_log comments anyway
|
||||
}
|
||||
}
|
||||
$query->query_vars['action_log_filter'] = TRUE;
|
||||
add_filter( 'comments_clauses', array( $this, 'filter_comment_query_clauses' ), 10, 2 );
|
||||
}
|
||||
public function filter_comment_query_clauses( $clauses, $query ) {
|
||||
if ( !empty($query->query_vars['action_log_filter']) ) {
|
||||
$clauses['where'] .= $this->get_where_clause();
|
||||
}
|
||||
return $clauses;
|
||||
}
|
||||
public function filter_comment_feed( $where, $query ) {
|
||||
if ( is_comment_feed() ) {
|
||||
$where .= $this->get_where_clause();
|
||||
}
|
||||
return $where;
|
||||
}
|
||||
protected function get_where_clause() {
|
||||
global $wpdb;
|
||||
return sprintf( " AND {$wpdb->comments}.comment_type != '%s'", self::TYPE );
|
||||
}
|
||||
public function filter_comment_count( $stats, $post_id ) {
|
||||
global $wpdb;
|
||||
if ( 0 === $post_id ) {
|
||||
$stats = $this->get_comment_count();
|
||||
}
|
||||
return $stats;
|
||||
}
|
||||
protected function get_comment_count() {
|
||||
global $wpdb;
|
||||
$stats = get_transient( 'as_comment_count' );
|
||||
if ( ! $stats ) {
|
||||
$stats = array();
|
||||
$count = $wpdb->get_results( "SELECT comment_approved, COUNT( * ) AS num_comments FROM {$wpdb->comments} WHERE comment_type NOT IN('order_note','action_log') GROUP BY comment_approved", ARRAY_A );
|
||||
$total = 0;
|
||||
$stats = array();
|
||||
$approved = array( '0' => 'moderated', '1' => 'approved', 'spam' => 'spam', 'trash' => 'trash', 'post-trashed' => 'post-trashed' );
|
||||
foreach ( (array) $count as $row ) {
|
||||
// Don't count post-trashed toward totals
|
||||
if ( 'post-trashed' != $row['comment_approved'] && 'trash' != $row['comment_approved'] ) {
|
||||
$total += $row['num_comments'];
|
||||
}
|
||||
if ( isset( $approved[ $row['comment_approved'] ] ) ) {
|
||||
$stats[ $approved[ $row['comment_approved'] ] ] = $row['num_comments'];
|
||||
}
|
||||
}
|
||||
$stats['total_comments'] = $total;
|
||||
$stats['all'] = $total;
|
||||
foreach ( $approved as $key ) {
|
||||
if ( empty( $stats[ $key ] ) ) {
|
||||
$stats[ $key ] = 0;
|
||||
}
|
||||
}
|
||||
$stats = (object) $stats;
|
||||
set_transient( 'as_comment_count', $stats );
|
||||
}
|
||||
return $stats;
|
||||
}
|
||||
public function delete_comment_count_cache() {
|
||||
delete_transient( 'as_comment_count' );
|
||||
}
|
||||
public function init() {
|
||||
add_action( 'action_scheduler_before_process_queue', array( $this, 'disable_comment_counting' ), 10, 0 );
|
||||
add_action( 'action_scheduler_after_process_queue', array( $this, 'enable_comment_counting' ), 10, 0 );
|
||||
parent::init();
|
||||
add_action( 'pre_get_comments', array( $this, 'filter_comment_queries' ), 10, 1 );
|
||||
add_action( 'wp_count_comments', array( $this, 'filter_comment_count' ), 20, 2 ); // run after WC_Comments::wp_count_comments() to make sure we exclude order notes and action logs
|
||||
add_action( 'comment_feed_where', array( $this, 'filter_comment_feed' ), 10, 2 );
|
||||
// Delete comments count cache whenever there is a new comment or a comment status changes
|
||||
add_action( 'wp_insert_comment', array( $this, 'delete_comment_count_cache' ) );
|
||||
add_action( 'wp_set_comment_status', array( $this, 'delete_comment_count_cache' ) );
|
||||
}
|
||||
public function disable_comment_counting() {
|
||||
wp_defer_comment_counting(true);
|
||||
}
|
||||
public function enable_comment_counting() {
|
||||
wp_defer_comment_counting(false);
|
||||
}
|
||||
}
|
||||
+594
@@ -0,0 +1,594 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class ActionScheduler_wpPostStore extends ActionScheduler_Store {
|
||||
const POST_TYPE = 'scheduled-action';
|
||||
const GROUP_TAXONOMY = 'action-group';
|
||||
const SCHEDULE_META_KEY = '_action_manager_schedule';
|
||||
const DEPENDENCIES_MET = 'as-post-store-dependencies-met';
|
||||
private $claim_before_date = null;
|
||||
protected $local_timezone = null;
|
||||
public function save_action( ActionScheduler_Action $action, DateTime $scheduled_date = null ) {
|
||||
try {
|
||||
$this->validate_action( $action );
|
||||
$post_array = $this->create_post_array( $action, $scheduled_date );
|
||||
$post_id = $this->save_post_array( $post_array );
|
||||
$this->save_post_schedule( $post_id, $action->get_schedule() );
|
||||
$this->save_action_group( $post_id, $action->get_group() );
|
||||
do_action( 'action_scheduler_stored_action', $post_id );
|
||||
return $post_id;
|
||||
} catch ( Exception $e ) {
|
||||
throw new RuntimeException( sprintf( __( 'Error saving action: %s', 'action-scheduler' ), $e->getMessage() ), 0 );
|
||||
}
|
||||
}
|
||||
protected function create_post_array( ActionScheduler_Action $action, DateTime $scheduled_date = null ) {
|
||||
$post = array(
|
||||
'post_type' => self::POST_TYPE,
|
||||
'post_title' => $action->get_hook(),
|
||||
'post_content' => wp_json_encode( $action->get_args() ),
|
||||
'post_status' => ( $action->is_finished() ? 'publish' : 'pending' ),
|
||||
'post_date_gmt' => $this->get_scheduled_date_string( $action, $scheduled_date ),
|
||||
'post_date' => $this->get_scheduled_date_string_local( $action, $scheduled_date ),
|
||||
);
|
||||
return $post;
|
||||
}
|
||||
protected function save_post_array( $post_array ) {
|
||||
add_filter( 'wp_insert_post_data', array( $this, 'filter_insert_post_data' ), 10, 1 );
|
||||
add_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10, 5 );
|
||||
$has_kses = false !== has_filter( 'content_save_pre', 'wp_filter_post_kses' );
|
||||
if ( $has_kses ) {
|
||||
// Prevent KSES from corrupting JSON in post_content.
|
||||
kses_remove_filters();
|
||||
}
|
||||
$post_id = wp_insert_post( $post_array );
|
||||
if ( $has_kses ) {
|
||||
kses_init_filters();
|
||||
}
|
||||
remove_filter( 'wp_insert_post_data', array( $this, 'filter_insert_post_data' ), 10 );
|
||||
remove_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10 );
|
||||
if ( is_wp_error( $post_id ) || empty( $post_id ) ) {
|
||||
throw new RuntimeException( __( 'Unable to save action.', 'action-scheduler' ) );
|
||||
}
|
||||
return $post_id;
|
||||
}
|
||||
public function filter_insert_post_data( $postdata ) {
|
||||
if ( self::POST_TYPE === $postdata['post_type'] ) {
|
||||
$postdata['post_author'] = 0;
|
||||
if ( 'future' === $postdata['post_status'] ) {
|
||||
$postdata['post_status'] = 'publish';
|
||||
}
|
||||
}
|
||||
return $postdata;
|
||||
}
|
||||
public function set_unique_post_slug( $override_slug, $slug, $post_ID, $post_status, $post_type ) {
|
||||
if ( self::POST_TYPE === $post_type ) {
|
||||
$override_slug = uniqid( self::POST_TYPE . '-', true ) . '-' . wp_generate_password( 32, false );
|
||||
}
|
||||
return $override_slug;
|
||||
}
|
||||
protected function save_post_schedule( $post_id, $schedule ) {
|
||||
update_post_meta( $post_id, self::SCHEDULE_META_KEY, $schedule );
|
||||
}
|
||||
protected function save_action_group( $post_id, $group ) {
|
||||
if ( empty( $group ) ) {
|
||||
wp_set_object_terms( $post_id, array(), self::GROUP_TAXONOMY, false );
|
||||
} else {
|
||||
wp_set_object_terms( $post_id, array( $group ), self::GROUP_TAXONOMY, false );
|
||||
}
|
||||
}
|
||||
public function fetch_action( $action_id ) {
|
||||
$post = $this->get_post( $action_id );
|
||||
if ( empty( $post ) || self::POST_TYPE !== $post->post_type ) {
|
||||
return $this->get_null_action();
|
||||
}
|
||||
try {
|
||||
$action = $this->make_action_from_post( $post );
|
||||
} catch ( ActionScheduler_InvalidActionException $exception ) {
|
||||
do_action( 'action_scheduler_failed_fetch_action', $post->ID, $exception );
|
||||
return $this->get_null_action();
|
||||
}
|
||||
return $action;
|
||||
}
|
||||
protected function get_post( $action_id ) {
|
||||
if ( empty( $action_id ) ) {
|
||||
return null;
|
||||
}
|
||||
return get_post( $action_id );
|
||||
}
|
||||
protected function get_null_action() {
|
||||
return new ActionScheduler_NullAction();
|
||||
}
|
||||
protected function make_action_from_post( $post ) {
|
||||
$hook = $post->post_title;
|
||||
$args = json_decode( $post->post_content, true );
|
||||
$this->validate_args( $args, $post->ID );
|
||||
$schedule = get_post_meta( $post->ID, self::SCHEDULE_META_KEY, true );
|
||||
$this->validate_schedule( $schedule, $post->ID );
|
||||
$group = wp_get_object_terms( $post->ID, self::GROUP_TAXONOMY, array( 'fields' => 'names' ) );
|
||||
$group = empty( $group ) ? '' : reset( $group );
|
||||
return ActionScheduler::factory()->get_stored_action( $this->get_action_status_by_post_status( $post->post_status ), $hook, $args, $schedule, $group );
|
||||
}
|
||||
protected function get_action_status_by_post_status( $post_status ) {
|
||||
switch ( $post_status ) {
|
||||
case 'publish':
|
||||
$action_status = self::STATUS_COMPLETE;
|
||||
break;
|
||||
case 'trash':
|
||||
$action_status = self::STATUS_CANCELED;
|
||||
break;
|
||||
default:
|
||||
if ( ! array_key_exists( $post_status, $this->get_status_labels() ) ) {
|
||||
throw new InvalidArgumentException( sprintf( 'Invalid post status: "%s". No matching action status available.', $post_status ) );
|
||||
}
|
||||
$action_status = $post_status;
|
||||
break;
|
||||
}
|
||||
return $action_status;
|
||||
}
|
||||
protected function get_post_status_by_action_status( $action_status ) {
|
||||
switch ( $action_status ) {
|
||||
case self::STATUS_COMPLETE:
|
||||
$post_status = 'publish';
|
||||
break;
|
||||
case self::STATUS_CANCELED:
|
||||
$post_status = 'trash';
|
||||
break;
|
||||
default:
|
||||
if ( ! array_key_exists( $action_status, $this->get_status_labels() ) ) {
|
||||
throw new InvalidArgumentException( sprintf( 'Invalid action status: "%s".', $action_status ) );
|
||||
}
|
||||
$post_status = $action_status;
|
||||
break;
|
||||
}
|
||||
return $post_status;
|
||||
}
|
||||
protected function get_query_actions_sql( array $query, $select_or_count = 'select' ) {
|
||||
if ( ! in_array( $select_or_count, array( 'select', 'count' ), true ) ) {
|
||||
throw new InvalidArgumentException( __( 'Invalid schedule. Cannot save action.', 'action-scheduler' ) );
|
||||
}
|
||||
$query = wp_parse_args(
|
||||
$query,
|
||||
array(
|
||||
'hook' => '',
|
||||
'args' => null,
|
||||
'date' => null,
|
||||
'date_compare' => '<=',
|
||||
'modified' => null,
|
||||
'modified_compare' => '<=',
|
||||
'group' => '',
|
||||
'status' => '',
|
||||
'claimed' => null,
|
||||
'per_page' => 5,
|
||||
'offset' => 0,
|
||||
'orderby' => 'date',
|
||||
'order' => 'ASC',
|
||||
'search' => '',
|
||||
)
|
||||
);
|
||||
global $wpdb;
|
||||
$sql = ( 'count' === $select_or_count ) ? 'SELECT count(p.ID)' : 'SELECT p.ID ';
|
||||
$sql .= "FROM {$wpdb->posts} p";
|
||||
$sql_params = array();
|
||||
if ( empty( $query['group'] ) && 'group' === $query['orderby'] ) {
|
||||
$sql .= " LEFT JOIN {$wpdb->term_relationships} tr ON tr.object_id=p.ID";
|
||||
$sql .= " LEFT JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id=tt.term_taxonomy_id";
|
||||
$sql .= " LEFT JOIN {$wpdb->terms} t ON tt.term_id=t.term_id";
|
||||
} elseif ( ! empty( $query['group'] ) ) {
|
||||
$sql .= " INNER JOIN {$wpdb->term_relationships} tr ON tr.object_id=p.ID";
|
||||
$sql .= " INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id=tt.term_taxonomy_id";
|
||||
$sql .= " INNER JOIN {$wpdb->terms} t ON tt.term_id=t.term_id";
|
||||
$sql .= ' AND t.slug=%s';
|
||||
$sql_params[] = $query['group'];
|
||||
}
|
||||
$sql .= ' WHERE post_type=%s';
|
||||
$sql_params[] = self::POST_TYPE;
|
||||
if ( $query['hook'] ) {
|
||||
$sql .= ' AND p.post_title=%s';
|
||||
$sql_params[] = $query['hook'];
|
||||
}
|
||||
if ( ! is_null( $query['args'] ) ) {
|
||||
$sql .= ' AND p.post_content=%s';
|
||||
$sql_params[] = wp_json_encode( $query['args'] );
|
||||
}
|
||||
if ( $query['status'] ) {
|
||||
$post_statuses = array_map( array( $this, 'get_post_status_by_action_status' ), (array) $query['status'] );
|
||||
$placeholders = array_fill( 0, count( $post_statuses ), '%s' );
|
||||
$sql .= ' AND p.post_status IN (' . join( ', ', $placeholders ) . ')';
|
||||
$sql_params = array_merge( $sql_params, array_values( $post_statuses ) );
|
||||
}
|
||||
if ( $query['date'] instanceof DateTime ) {
|
||||
$date = clone $query['date'];
|
||||
$date->setTimezone( new DateTimeZone( 'UTC' ) );
|
||||
$date_string = $date->format( 'Y-m-d H:i:s' );
|
||||
$comparator = $this->validate_sql_comparator( $query['date_compare'] );
|
||||
$sql .= " AND p.post_date_gmt $comparator %s";
|
||||
$sql_params[] = $date_string;
|
||||
}
|
||||
if ( $query['modified'] instanceof DateTime ) {
|
||||
$modified = clone $query['modified'];
|
||||
$modified->setTimezone( new DateTimeZone( 'UTC' ) );
|
||||
$date_string = $modified->format( 'Y-m-d H:i:s' );
|
||||
$comparator = $this->validate_sql_comparator( $query['modified_compare'] );
|
||||
$sql .= " AND p.post_modified_gmt $comparator %s";
|
||||
$sql_params[] = $date_string;
|
||||
}
|
||||
if ( true === $query['claimed'] ) {
|
||||
$sql .= " AND p.post_password != ''";
|
||||
} elseif ( false === $query['claimed'] ) {
|
||||
$sql .= " AND p.post_password = ''";
|
||||
} elseif ( ! is_null( $query['claimed'] ) ) {
|
||||
$sql .= ' AND p.post_password = %s';
|
||||
$sql_params[] = $query['claimed'];
|
||||
}
|
||||
if ( ! empty( $query['search'] ) ) {
|
||||
$sql .= ' AND (p.post_title LIKE %s OR p.post_content LIKE %s OR p.post_password LIKE %s)';
|
||||
for ( $i = 0; $i < 3; $i++ ) {
|
||||
$sql_params[] = sprintf( '%%%s%%', $query['search'] );
|
||||
}
|
||||
}
|
||||
if ( 'select' === $select_or_count ) {
|
||||
switch ( $query['orderby'] ) {
|
||||
case 'hook':
|
||||
$orderby = 'p.post_title';
|
||||
break;
|
||||
case 'group':
|
||||
$orderby = 't.name';
|
||||
break;
|
||||
case 'status':
|
||||
$orderby = 'p.post_status';
|
||||
break;
|
||||
case 'modified':
|
||||
$orderby = 'p.post_modified';
|
||||
break;
|
||||
case 'claim_id':
|
||||
$orderby = 'p.post_password';
|
||||
break;
|
||||
case 'schedule':
|
||||
case 'date':
|
||||
default:
|
||||
$orderby = 'p.post_date_gmt';
|
||||
break;
|
||||
}
|
||||
if ( 'ASC' === strtoupper( $query['order'] ) ) {
|
||||
$order = 'ASC';
|
||||
} else {
|
||||
$order = 'DESC';
|
||||
}
|
||||
$sql .= " ORDER BY $orderby $order";
|
||||
if ( $query['per_page'] > 0 ) {
|
||||
$sql .= ' LIMIT %d, %d';
|
||||
$sql_params[] = $query['offset'];
|
||||
$sql_params[] = $query['per_page'];
|
||||
}
|
||||
}
|
||||
return $wpdb->prepare( $sql, $sql_params ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
}
|
||||
public function query_actions( $query = array(), $query_type = 'select' ) {
|
||||
global $wpdb;
|
||||
$sql = $this->get_query_actions_sql( $query, $query_type );
|
||||
return ( 'count' === $query_type ) ? $wpdb->get_var( $sql ) : $wpdb->get_col( $sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared
|
||||
}
|
||||
public function action_counts() {
|
||||
$action_counts_by_status = array();
|
||||
$action_stati_and_labels = $this->get_status_labels();
|
||||
$posts_count_by_status = (array) wp_count_posts( self::POST_TYPE, 'readable' );
|
||||
foreach ( $posts_count_by_status as $post_status_name => $count ) {
|
||||
try {
|
||||
$action_status_name = $this->get_action_status_by_post_status( $post_status_name );
|
||||
} catch ( Exception $e ) {
|
||||
// Ignore any post statuses that aren't for actions.
|
||||
continue;
|
||||
}
|
||||
if ( array_key_exists( $action_status_name, $action_stati_and_labels ) ) {
|
||||
$action_counts_by_status[ $action_status_name ] = $count;
|
||||
}
|
||||
}
|
||||
return $action_counts_by_status;
|
||||
}
|
||||
public function cancel_action( $action_id ) {
|
||||
$post = get_post( $action_id );
|
||||
if ( empty( $post ) || ( self::POST_TYPE !== $post->post_type ) ) {
|
||||
throw new InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) );
|
||||
}
|
||||
do_action( 'action_scheduler_canceled_action', $action_id );
|
||||
add_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10, 5 );
|
||||
wp_trash_post( $action_id );
|
||||
remove_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10 );
|
||||
}
|
||||
public function delete_action( $action_id ) {
|
||||
$post = get_post( $action_id );
|
||||
if ( empty( $post ) || ( self::POST_TYPE !== $post->post_type ) ) {
|
||||
throw new InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) );
|
||||
}
|
||||
do_action( 'action_scheduler_deleted_action', $action_id );
|
||||
wp_delete_post( $action_id, true );
|
||||
}
|
||||
public function get_date( $action_id ) {
|
||||
$next = $this->get_date_gmt( $action_id );
|
||||
return ActionScheduler_TimezoneHelper::set_local_timezone( $next );
|
||||
}
|
||||
public function get_date_gmt( $action_id ) {
|
||||
$post = get_post( $action_id );
|
||||
if ( empty( $post ) || ( self::POST_TYPE !== $post->post_type ) ) {
|
||||
throw new InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) );
|
||||
}
|
||||
if ( 'publish' === $post->post_status ) {
|
||||
return as_get_datetime_object( $post->post_modified_gmt );
|
||||
} else {
|
||||
return as_get_datetime_object( $post->post_date_gmt );
|
||||
}
|
||||
}
|
||||
public function stake_claim( $max_actions = 10, DateTime $before_date = null, $hooks = array(), $group = '' ) {
|
||||
$this->claim_before_date = $before_date;
|
||||
$claim_id = $this->generate_claim_id();
|
||||
$this->claim_actions( $claim_id, $max_actions, $before_date, $hooks, $group );
|
||||
$action_ids = $this->find_actions_by_claim_id( $claim_id );
|
||||
$this->claim_before_date = null;
|
||||
return new ActionScheduler_ActionClaim( $claim_id, $action_ids );
|
||||
}
|
||||
public function get_claim_count() {
|
||||
global $wpdb;
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
|
||||
return $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT COUNT(DISTINCT post_password) FROM {$wpdb->posts} WHERE post_password != '' AND post_type = %s AND post_status IN ('in-progress','pending')",
|
||||
array( self::POST_TYPE )
|
||||
)
|
||||
);
|
||||
}
|
||||
protected function generate_claim_id() {
|
||||
$claim_id = md5( microtime( true ) . wp_rand( 0, 1000 ) );
|
||||
return substr( $claim_id, 0, 20 ); // to fit in db field with 20 char limit.
|
||||
}
|
||||
protected function claim_actions( $claim_id, $limit, DateTime $before_date = null, $hooks = array(), $group = '' ) {
|
||||
// Set up initial variables.
|
||||
$date = null === $before_date ? as_get_datetime_object() : clone $before_date;
|
||||
$limit_ids = ! empty( $group );
|
||||
$ids = $limit_ids ? $this->get_actions_by_group( $group, $limit, $date ) : array();
|
||||
// If limiting by IDs and no posts found, then return early since we have nothing to update.
|
||||
if ( $limit_ids && 0 === count( $ids ) ) {
|
||||
return 0;
|
||||
}
|
||||
global $wpdb;
|
||||
$update = "UPDATE {$wpdb->posts} SET post_password = %s, post_modified_gmt = %s, post_modified = %s";
|
||||
$params = array(
|
||||
$claim_id,
|
||||
current_time( 'mysql', true ),
|
||||
current_time( 'mysql' ),
|
||||
);
|
||||
// Build initial WHERE clause.
|
||||
$where = "WHERE post_type = %s AND post_status = %s AND post_password = ''";
|
||||
$params[] = self::POST_TYPE;
|
||||
$params[] = ActionScheduler_Store::STATUS_PENDING;
|
||||
if ( ! empty( $hooks ) ) {
|
||||
$placeholders = array_fill( 0, count( $hooks ), '%s' );
|
||||
$where .= ' AND post_title IN (' . join( ', ', $placeholders ) . ')';
|
||||
$params = array_merge( $params, array_values( $hooks ) );
|
||||
}
|
||||
if ( $limit_ids ) {
|
||||
$where .= ' AND ID IN (' . join( ',', $ids ) . ')';
|
||||
} else {
|
||||
$where .= ' AND post_date_gmt <= %s';
|
||||
$params[] = $date->format( 'Y-m-d H:i:s' );
|
||||
}
|
||||
// Add the ORDER BY clause and,ms limit.
|
||||
$order = 'ORDER BY menu_order ASC, post_date_gmt ASC, ID ASC LIMIT %d';
|
||||
$params[] = $limit;
|
||||
// Run the query and gather results.
|
||||
$rows_affected = $wpdb->query( $wpdb->prepare( "{$update} {$where} {$order}", $params ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
|
||||
if ( false === $rows_affected ) {
|
||||
throw new RuntimeException( __( 'Unable to claim actions. Database error.', 'action-scheduler' ) );
|
||||
}
|
||||
return (int) $rows_affected;
|
||||
}
|
||||
protected function get_actions_by_group( $group, $limit, DateTime $date ) {
|
||||
// Ensure the group exists before continuing.
|
||||
if ( ! term_exists( $group, self::GROUP_TAXONOMY ) ) {
|
||||
throw new InvalidArgumentException( sprintf( __( 'The group "%s" does not exist.', 'action-scheduler' ), $group ) );
|
||||
}
|
||||
// Set up a query for post IDs to use later.
|
||||
$query = new WP_Query();
|
||||
$query_args = array(
|
||||
'fields' => 'ids',
|
||||
'post_type' => self::POST_TYPE,
|
||||
'post_status' => ActionScheduler_Store::STATUS_PENDING,
|
||||
'has_password' => false,
|
||||
'posts_per_page' => $limit * 3,
|
||||
'suppress_filters' => true, // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.SuppressFilters_suppress_filters
|
||||
'no_found_rows' => true,
|
||||
'orderby' => array(
|
||||
'menu_order' => 'ASC',
|
||||
'date' => 'ASC',
|
||||
'ID' => 'ASC',
|
||||
),
|
||||
'date_query' => array(
|
||||
'column' => 'post_date_gmt',
|
||||
'before' => $date->format( 'Y-m-d H:i' ),
|
||||
'inclusive' => true,
|
||||
),
|
||||
'tax_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery
|
||||
array(
|
||||
'taxonomy' => self::GROUP_TAXONOMY,
|
||||
'field' => 'slug',
|
||||
'terms' => $group,
|
||||
'include_children' => false,
|
||||
),
|
||||
),
|
||||
);
|
||||
return $query->query( $query_args );
|
||||
}
|
||||
public function find_actions_by_claim_id( $claim_id ) {
|
||||
global $wpdb;
|
||||
$action_ids = array();
|
||||
$before_date = isset( $this->claim_before_date ) ? $this->claim_before_date : as_get_datetime_object();
|
||||
$cut_off = $before_date->format( 'Y-m-d H:i:s' );
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
|
||||
$results = $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
"SELECT ID, post_date_gmt FROM {$wpdb->posts} WHERE post_type = %s AND post_password = %s",
|
||||
array(
|
||||
self::POST_TYPE,
|
||||
$claim_id,
|
||||
)
|
||||
)
|
||||
);
|
||||
// Verify that the scheduled date for each action is within the expected bounds (in some unusual
|
||||
// cases, we cannot depend on MySQL to honor all of the WHERE conditions we specify).
|
||||
foreach ( $results as $claimed_action ) {
|
||||
if ( $claimed_action->post_date_gmt <= $cut_off ) {
|
||||
$action_ids[] = absint( $claimed_action->ID );
|
||||
}
|
||||
}
|
||||
return $action_ids;
|
||||
}
|
||||
public function release_claim( ActionScheduler_ActionClaim $claim ) {
|
||||
$action_ids = $this->find_actions_by_claim_id( $claim->get_id() );
|
||||
if ( empty( $action_ids ) ) {
|
||||
return; // nothing to do.
|
||||
}
|
||||
$action_id_string = implode( ',', array_map( 'intval', $action_ids ) );
|
||||
global $wpdb;
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
|
||||
$result = $wpdb->query(
|
||||
$wpdb->prepare(
|
||||
"UPDATE {$wpdb->posts} SET post_password = '' WHERE ID IN ($action_id_string) AND post_password = %s", //phpcs:ignore
|
||||
array(
|
||||
$claim->get_id(),
|
||||
)
|
||||
)
|
||||
);
|
||||
if ( false === $result ) {
|
||||
throw new RuntimeException( sprintf( __( 'Unable to unlock claim %s. Database error.', 'action-scheduler' ), $claim->get_id() ) );
|
||||
}
|
||||
}
|
||||
public function unclaim_action( $action_id ) {
|
||||
global $wpdb;
|
||||
//phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
|
||||
$result = $wpdb->query(
|
||||
$wpdb->prepare(
|
||||
"UPDATE {$wpdb->posts} SET post_password = '' WHERE ID = %d AND post_type = %s",
|
||||
$action_id,
|
||||
self::POST_TYPE
|
||||
)
|
||||
);
|
||||
if ( false === $result ) {
|
||||
throw new RuntimeException( sprintf( __( 'Unable to unlock claim on action %s. Database error.', 'action-scheduler' ), $action_id ) );
|
||||
}
|
||||
}
|
||||
public function mark_failure( $action_id ) {
|
||||
global $wpdb;
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
|
||||
$result = $wpdb->query(
|
||||
$wpdb->prepare( "UPDATE {$wpdb->posts} SET post_status = %s WHERE ID = %d AND post_type = %s", self::STATUS_FAILED, $action_id, self::POST_TYPE )
|
||||
);
|
||||
if ( false === $result ) {
|
||||
throw new RuntimeException( sprintf( __( 'Unable to mark failure on action %s. Database error.', 'action-scheduler' ), $action_id ) );
|
||||
}
|
||||
}
|
||||
public function get_claim_id( $action_id ) {
|
||||
return $this->get_post_column( $action_id, 'post_password' );
|
||||
}
|
||||
public function get_status( $action_id ) {
|
||||
$status = $this->get_post_column( $action_id, 'post_status' );
|
||||
if ( null === $status ) {
|
||||
throw new InvalidArgumentException( __( 'Invalid action ID. No status found.', 'action-scheduler' ) );
|
||||
}
|
||||
return $this->get_action_status_by_post_status( $status );
|
||||
}
|
||||
private function get_post_column( $action_id, $column_name ) {
|
||||
global $wpdb;
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
|
||||
return $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT {$column_name} FROM {$wpdb->posts} WHERE ID=%d AND post_type=%s", // phpcs:ignore
|
||||
$action_id,
|
||||
self::POST_TYPE
|
||||
)
|
||||
);
|
||||
}
|
||||
public function log_execution( $action_id ) {
|
||||
global $wpdb;
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
|
||||
$status_updated = $wpdb->query(
|
||||
$wpdb->prepare(
|
||||
"UPDATE {$wpdb->posts} SET menu_order = menu_order+1, post_status=%s, post_modified_gmt = %s, post_modified = %s WHERE ID = %d AND post_type = %s",
|
||||
self::STATUS_RUNNING,
|
||||
current_time( 'mysql', true ),
|
||||
current_time( 'mysql' ),
|
||||
$action_id,
|
||||
self::POST_TYPE
|
||||
)
|
||||
);
|
||||
if ( ! $status_updated ) {
|
||||
throw new Exception(
|
||||
sprintf(
|
||||
__( 'Unable to update the status of action %1$d to %2$s.', 'action-scheduler' ),
|
||||
$action_id,
|
||||
self::STATUS_RUNNING
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
public function mark_complete( $action_id ) {
|
||||
$post = get_post( $action_id );
|
||||
if ( empty( $post ) || ( self::POST_TYPE !== $post->post_type ) ) {
|
||||
throw new InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) );
|
||||
}
|
||||
add_filter( 'wp_insert_post_data', array( $this, 'filter_insert_post_data' ), 10, 1 );
|
||||
add_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10, 5 );
|
||||
$result = wp_update_post(
|
||||
array(
|
||||
'ID' => $action_id,
|
||||
'post_status' => 'publish',
|
||||
),
|
||||
true
|
||||
);
|
||||
remove_filter( 'wp_insert_post_data', array( $this, 'filter_insert_post_data' ), 10 );
|
||||
remove_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10 );
|
||||
if ( is_wp_error( $result ) ) {
|
||||
throw new RuntimeException( $result->get_error_message() );
|
||||
}
|
||||
do_action( 'action_scheduler_completed_action', $action_id );
|
||||
}
|
||||
public function mark_migrated( $action_id ) {
|
||||
wp_update_post(
|
||||
array(
|
||||
'ID' => $action_id,
|
||||
'post_status' => 'migrated',
|
||||
)
|
||||
);
|
||||
}
|
||||
public function migration_dependencies_met( $setting ) {
|
||||
global $wpdb;
|
||||
$dependencies_met = get_transient( self::DEPENDENCIES_MET );
|
||||
if ( empty( $dependencies_met ) ) {
|
||||
$maximum_args_length = apply_filters( 'action_scheduler_maximum_args_length', 191 );
|
||||
$found_action = $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
|
||||
$wpdb->prepare(
|
||||
"SELECT ID FROM {$wpdb->posts} WHERE post_type = %s AND CHAR_LENGTH(post_content) > %d LIMIT 1",
|
||||
$maximum_args_length,
|
||||
self::POST_TYPE
|
||||
)
|
||||
);
|
||||
$dependencies_met = $found_action ? 'no' : 'yes';
|
||||
set_transient( self::DEPENDENCIES_MET, $dependencies_met, DAY_IN_SECONDS );
|
||||
}
|
||||
return 'yes' === $dependencies_met ? $setting : false;
|
||||
}
|
||||
protected function validate_action( ActionScheduler_Action $action ) {
|
||||
try {
|
||||
parent::validate_action( $action );
|
||||
} catch ( Exception $e ) {
|
||||
$message = sprintf( __( '%s Support for strings longer than this will be removed in a future version.', 'action-scheduler' ), $e->getMessage() );
|
||||
_doing_it_wrong( 'ActionScheduler_Action::$args', esc_html( $message ), '2.1.0' );
|
||||
}
|
||||
}
|
||||
public function init() {
|
||||
add_filter( 'action_scheduler_migration_dependencies_met', array( $this, 'migration_dependencies_met' ) );
|
||||
$post_type_registrar = new ActionScheduler_wpPostStore_PostTypeRegistrar();
|
||||
$post_type_registrar->register();
|
||||
$post_status_registrar = new ActionScheduler_wpPostStore_PostStatusRegistrar();
|
||||
$post_status_registrar->register();
|
||||
$taxonomy_registrar = new ActionScheduler_wpPostStore_TaxonomyRegistrar();
|
||||
$taxonomy_registrar->register();
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class ActionScheduler_wpPostStore_PostStatusRegistrar {
|
||||
public function register() {
|
||||
register_post_status( ActionScheduler_Store::STATUS_RUNNING, array_merge( $this->post_status_args(), $this->post_status_running_labels() ) );
|
||||
register_post_status( ActionScheduler_Store::STATUS_FAILED, array_merge( $this->post_status_args(), $this->post_status_failed_labels() ) );
|
||||
}
|
||||
protected function post_status_args() {
|
||||
$args = array(
|
||||
'public' => false,
|
||||
'exclude_from_search' => false,
|
||||
'show_in_admin_all_list' => true,
|
||||
'show_in_admin_status_list' => true,
|
||||
);
|
||||
return apply_filters( 'action_scheduler_post_status_args', $args );
|
||||
}
|
||||
protected function post_status_failed_labels() {
|
||||
$labels = array(
|
||||
'label' => _x( 'Failed', 'post', 'action-scheduler' ),
|
||||
'label_count' => _n_noop( 'Failed <span class="count">(%s)</span>', 'Failed <span class="count">(%s)</span>', 'action-scheduler' ),
|
||||
);
|
||||
return apply_filters( 'action_scheduler_post_status_failed_labels', $labels );
|
||||
}
|
||||
protected function post_status_running_labels() {
|
||||
$labels = array(
|
||||
'label' => _x( 'In-Progress', 'post', 'action-scheduler' ),
|
||||
'label_count' => _n_noop( 'In-Progress <span class="count">(%s)</span>', 'In-Progress <span class="count">(%s)</span>', 'action-scheduler' ),
|
||||
);
|
||||
return apply_filters( 'action_scheduler_post_status_running_labels', $labels );
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class ActionScheduler_wpPostStore_PostTypeRegistrar {
|
||||
public function register() {
|
||||
register_post_type( ActionScheduler_wpPostStore::POST_TYPE, $this->post_type_args() );
|
||||
}
|
||||
protected function post_type_args() {
|
||||
$args = array(
|
||||
'label' => __( 'Scheduled Actions', 'action-scheduler' ),
|
||||
'description' => __( 'Scheduled actions are hooks triggered on a cetain date and time.', 'action-scheduler' ),
|
||||
'public' => false,
|
||||
'map_meta_cap' => true,
|
||||
'hierarchical' => false,
|
||||
'supports' => array('title', 'editor','comments'),
|
||||
'rewrite' => false,
|
||||
'query_var' => false,
|
||||
'can_export' => true,
|
||||
'ep_mask' => EP_NONE,
|
||||
'labels' => array(
|
||||
'name' => __( 'Scheduled Actions', 'action-scheduler' ),
|
||||
'singular_name' => __( 'Scheduled Action', 'action-scheduler' ),
|
||||
'menu_name' => _x( 'Scheduled Actions', 'Admin menu name', 'action-scheduler' ),
|
||||
'add_new' => __( 'Add', 'action-scheduler' ),
|
||||
'add_new_item' => __( 'Add New Scheduled Action', 'action-scheduler' ),
|
||||
'edit' => __( 'Edit', 'action-scheduler' ),
|
||||
'edit_item' => __( 'Edit Scheduled Action', 'action-scheduler' ),
|
||||
'new_item' => __( 'New Scheduled Action', 'action-scheduler' ),
|
||||
'view' => __( 'View Action', 'action-scheduler' ),
|
||||
'view_item' => __( 'View Action', 'action-scheduler' ),
|
||||
'search_items' => __( 'Search Scheduled Actions', 'action-scheduler' ),
|
||||
'not_found' => __( 'No actions found', 'action-scheduler' ),
|
||||
'not_found_in_trash' => __( 'No actions found in trash', 'action-scheduler' ),
|
||||
),
|
||||
);
|
||||
$args = apply_filters('action_scheduler_post_type_args', $args);
|
||||
return $args;
|
||||
}
|
||||
}
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class ActionScheduler_wpPostStore_TaxonomyRegistrar {
|
||||
public function register() {
|
||||
register_taxonomy( ActionScheduler_wpPostStore::GROUP_TAXONOMY, ActionScheduler_wpPostStore::POST_TYPE, $this->taxonomy_args() );
|
||||
}
|
||||
protected function taxonomy_args() {
|
||||
$args = array(
|
||||
'label' => __( 'Action Group', 'action-scheduler' ),
|
||||
'public' => false,
|
||||
'hierarchical' => false,
|
||||
'show_admin_column' => true,
|
||||
'query_var' => false,
|
||||
'rewrite' => false,
|
||||
);
|
||||
$args = apply_filters( 'action_scheduler_taxonomy_args', $args );
|
||||
return $args;
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
<?php
|
||||
+1
@@ -0,0 +1 @@
|
||||
<?php
|
||||
wp-content/plugins/mailpoet/vendor/woocommerce/action-scheduler/classes/migration/ActionMigrator.php
Vendored
+67
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
namespace Action_Scheduler\Migration;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class ActionMigrator {
|
||||
private $source;
|
||||
private $destination;
|
||||
private $log_migrator;
|
||||
public function __construct( \ActionScheduler_Store $source_store, \ActionScheduler_Store $destination_store, LogMigrator $log_migrator ) {
|
||||
$this->source = $source_store;
|
||||
$this->destination = $destination_store;
|
||||
$this->log_migrator = $log_migrator;
|
||||
}
|
||||
public function migrate( $source_action_id ) {
|
||||
try {
|
||||
$action = $this->source->fetch_action( $source_action_id );
|
||||
$status = $this->source->get_status( $source_action_id );
|
||||
} catch ( \Exception $e ) {
|
||||
$action = null;
|
||||
$status = '';
|
||||
}
|
||||
if ( is_null( $action ) || empty( $status ) || ! $action->get_schedule()->get_date() ) {
|
||||
// null action or empty status means the fetch operation failed or the action didn't exist
|
||||
// null schedule means it's missing vital data
|
||||
// delete it and move on
|
||||
try {
|
||||
$this->source->delete_action( $source_action_id );
|
||||
} catch ( \Exception $e ) {
|
||||
// nothing to do, it didn't exist in the first place
|
||||
}
|
||||
do_action( 'action_scheduler/no_action_to_migrate', $source_action_id, $this->source, $this->destination );
|
||||
return 0;
|
||||
}
|
||||
try {
|
||||
// Make sure the last attempt date is set correctly for completed and failed actions
|
||||
$last_attempt_date = ( $status !== \ActionScheduler_Store::STATUS_PENDING ) ? $this->source->get_date( $source_action_id ) : null;
|
||||
$destination_action_id = $this->destination->save_action( $action, null, $last_attempt_date );
|
||||
} catch ( \Exception $e ) {
|
||||
do_action( 'action_scheduler/migrate_action_failed', $source_action_id, $this->source, $this->destination );
|
||||
return 0; // could not save the action in the new store
|
||||
}
|
||||
try {
|
||||
switch ( $status ) {
|
||||
case \ActionScheduler_Store::STATUS_FAILED :
|
||||
$this->destination->mark_failure( $destination_action_id );
|
||||
break;
|
||||
case \ActionScheduler_Store::STATUS_CANCELED :
|
||||
$this->destination->cancel_action( $destination_action_id );
|
||||
break;
|
||||
}
|
||||
$this->log_migrator->migrate( $source_action_id, $destination_action_id );
|
||||
$this->source->delete_action( $source_action_id );
|
||||
$test_action = $this->source->fetch_action( $source_action_id );
|
||||
if ( ! is_a( $test_action, 'ActionScheduler_NullAction' ) ) {
|
||||
// translators: %s is an action ID.
|
||||
throw new \RuntimeException( sprintf( __( 'Unable to remove source migrated action %s', 'action-scheduler' ), $source_action_id ) );
|
||||
}
|
||||
do_action( 'action_scheduler/migrated_action', $source_action_id, $destination_action_id, $this->source, $this->destination );
|
||||
return $destination_action_id;
|
||||
} catch ( \Exception $e ) {
|
||||
// could not delete from the old store
|
||||
$this->source->mark_migrated( $source_action_id );
|
||||
do_action( 'action_scheduler/migrate_action_incomplete', $source_action_id, $destination_action_id, $this->source, $this->destination );
|
||||
do_action( 'action_scheduler/migrated_action', $source_action_id, $destination_action_id, $this->source, $this->destination );
|
||||
return $destination_action_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class ActionScheduler_DBStoreMigrator extends ActionScheduler_DBStore {
|
||||
public function save_action( ActionScheduler_Action $action, \DateTime $scheduled_date = null, \DateTime $last_attempt_date = null ){
|
||||
try {
|
||||
global $wpdb;
|
||||
$action_id = parent::save_action( $action, $scheduled_date );
|
||||
if ( null !== $last_attempt_date ) {
|
||||
$data = [
|
||||
'last_attempt_gmt' => $this->get_scheduled_date_string( $action, $last_attempt_date ),
|
||||
'last_attempt_local' => $this->get_scheduled_date_string_local( $action, $last_attempt_date ),
|
||||
];
|
||||
$wpdb->update( $wpdb->actionscheduler_actions, $data, array( 'action_id' => $action_id ), array( '%s', '%s' ), array( '%d' ) );
|
||||
}
|
||||
return $action_id;
|
||||
} catch ( \Exception $e ) {
|
||||
// translators: %s is an error message.
|
||||
throw new \RuntimeException( sprintf( __( 'Error saving action: %s', 'action-scheduler' ), $e->getMessage() ), 0 );
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+47
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
namespace Action_Scheduler\Migration;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use ActionScheduler_Store as Store;
|
||||
class BatchFetcher {
|
||||
private $store;
|
||||
public function __construct( Store $source_store ) {
|
||||
$this->store = $source_store;
|
||||
}
|
||||
public function fetch( $count = 10 ) {
|
||||
foreach ( $this->get_query_strategies( $count ) as $query ) {
|
||||
$action_ids = $this->store->query_actions( $query );
|
||||
if ( ! empty( $action_ids ) ) {
|
||||
return $action_ids;
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
private function get_query_strategies( $count ) {
|
||||
$now = as_get_datetime_object();
|
||||
$args = [
|
||||
'date' => $now,
|
||||
'per_page' => $count,
|
||||
'offset' => 0,
|
||||
'orderby' => 'date',
|
||||
'order' => 'ASC',
|
||||
];
|
||||
$priorities = [
|
||||
Store::STATUS_PENDING,
|
||||
Store::STATUS_FAILED,
|
||||
Store::STATUS_CANCELED,
|
||||
Store::STATUS_COMPLETE,
|
||||
Store::STATUS_RUNNING,
|
||||
'', // any other unanticipated status
|
||||
];
|
||||
foreach ( $priorities as $status ) {
|
||||
yield wp_parse_args( [
|
||||
'status' => $status,
|
||||
'date_compare' => '<=',
|
||||
], $args );
|
||||
yield wp_parse_args( [
|
||||
'status' => $status,
|
||||
'date_compare' => '>=',
|
||||
], $args );
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+64
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
namespace Action_Scheduler\Migration;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use Action_Scheduler\WP_CLI\ProgressBar;
|
||||
use ActionScheduler_Logger as Logger;
|
||||
use ActionScheduler_Store as Store;
|
||||
class Config {
|
||||
private $source_store;
|
||||
private $source_logger;
|
||||
private $destination_store;
|
||||
private $destination_logger;
|
||||
private $progress_bar;
|
||||
private $dry_run = false;
|
||||
public function __construct() {
|
||||
}
|
||||
public function get_source_store() {
|
||||
if ( empty( $this->source_store ) ) {
|
||||
throw new \RuntimeException( __( 'Source store must be configured before running a migration', 'action-scheduler' ) );
|
||||
}
|
||||
return $this->source_store;
|
||||
}
|
||||
public function set_source_store( Store $store ) {
|
||||
$this->source_store = $store;
|
||||
}
|
||||
public function get_source_logger() {
|
||||
if ( empty( $this->source_logger ) ) {
|
||||
throw new \RuntimeException( __( 'Source logger must be configured before running a migration', 'action-scheduler' ) );
|
||||
}
|
||||
return $this->source_logger;
|
||||
}
|
||||
public function set_source_logger( Logger $logger ) {
|
||||
$this->source_logger = $logger;
|
||||
}
|
||||
public function get_destination_store() {
|
||||
if ( empty( $this->destination_store ) ) {
|
||||
throw new \RuntimeException( __( 'Destination store must be configured before running a migration', 'action-scheduler' ) );
|
||||
}
|
||||
return $this->destination_store;
|
||||
}
|
||||
public function set_destination_store( Store $store ) {
|
||||
$this->destination_store = $store;
|
||||
}
|
||||
public function get_destination_logger() {
|
||||
if ( empty( $this->destination_logger ) ) {
|
||||
throw new \RuntimeException( __( 'Destination logger must be configured before running a migration', 'action-scheduler' ) );
|
||||
}
|
||||
return $this->destination_logger;
|
||||
}
|
||||
public function set_destination_logger( Logger $logger ) {
|
||||
$this->destination_logger = $logger;
|
||||
}
|
||||
public function get_dry_run() {
|
||||
return $this->dry_run;
|
||||
}
|
||||
public function set_dry_run( $dry_run ) {
|
||||
$this->dry_run = (bool) $dry_run;
|
||||
}
|
||||
public function get_progress_bar() {
|
||||
return $this->progress_bar;
|
||||
}
|
||||
public function set_progress_bar( ProgressBar $progress_bar ) {
|
||||
$this->progress_bar = $progress_bar;
|
||||
}
|
||||
}
|
||||
Vendored
+113
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
namespace Action_Scheduler\Migration;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use ActionScheduler_DataController;
|
||||
use ActionScheduler_LoggerSchema;
|
||||
use ActionScheduler_StoreSchema;
|
||||
use Action_Scheduler\WP_CLI\ProgressBar;
|
||||
class Controller {
|
||||
private static $instance;
|
||||
private $migration_scheduler;
|
||||
private $store_classname;
|
||||
private $logger_classname;
|
||||
private $migrate_custom_store;
|
||||
protected function __construct( Scheduler $migration_scheduler ) {
|
||||
$this->migration_scheduler = $migration_scheduler;
|
||||
$this->store_classname = '';
|
||||
}
|
||||
public function get_store_class( $class ) {
|
||||
if ( \ActionScheduler_DataController::is_migration_complete() ) {
|
||||
return \ActionScheduler_DataController::DATASTORE_CLASS;
|
||||
} elseif ( \ActionScheduler_Store::DEFAULT_CLASS !== $class ) {
|
||||
$this->store_classname = $class;
|
||||
return $class;
|
||||
} else {
|
||||
return 'ActionScheduler_HybridStore';
|
||||
}
|
||||
}
|
||||
public function get_logger_class( $class ) {
|
||||
\ActionScheduler_Store::instance();
|
||||
if ( $this->has_custom_datastore() ) {
|
||||
$this->logger_classname = $class;
|
||||
return $class;
|
||||
} else {
|
||||
return \ActionScheduler_DataController::LOGGER_CLASS;
|
||||
}
|
||||
}
|
||||
public function has_custom_datastore() {
|
||||
return (bool) $this->store_classname;
|
||||
}
|
||||
public function schedule_migration() {
|
||||
$logging_tables = new ActionScheduler_LoggerSchema();
|
||||
$store_tables = new ActionScheduler_StoreSchema();
|
||||
if (
|
||||
ActionScheduler_DataController::is_migration_complete()
|
||||
|| $this->migration_scheduler->is_migration_scheduled()
|
||||
|| ! $store_tables->tables_exist()
|
||||
|| ! $logging_tables->tables_exist()
|
||||
) {
|
||||
return;
|
||||
}
|
||||
$this->migration_scheduler->schedule_migration();
|
||||
}
|
||||
public function get_migration_config_object() {
|
||||
static $config = null;
|
||||
if ( ! $config ) {
|
||||
$source_store = $this->store_classname ? new $this->store_classname() : new \ActionScheduler_wpPostStore();
|
||||
$source_logger = $this->logger_classname ? new $this->logger_classname() : new \ActionScheduler_wpCommentLogger();
|
||||
$config = new Config();
|
||||
$config->set_source_store( $source_store );
|
||||
$config->set_source_logger( $source_logger );
|
||||
$config->set_destination_store( new \ActionScheduler_DBStoreMigrator() );
|
||||
$config->set_destination_logger( new \ActionScheduler_DBLogger() );
|
||||
if ( defined( 'WP_CLI' ) && WP_CLI ) {
|
||||
$config->set_progress_bar( new ProgressBar( '', 0 ) );
|
||||
}
|
||||
}
|
||||
return apply_filters( 'action_scheduler/migration_config', $config );
|
||||
}
|
||||
public function hook_admin_notices() {
|
||||
if ( ! $this->allow_migration() || \ActionScheduler_DataController::is_migration_complete() ) {
|
||||
return;
|
||||
}
|
||||
add_action( 'admin_notices', array( $this, 'display_migration_notice' ), 10, 0 );
|
||||
}
|
||||
public function display_migration_notice() {
|
||||
printf( '<div class="notice notice-warning"><p>%s</p></div>', esc_html__( 'Action Scheduler migration in progress. The list of scheduled actions may be incomplete.', 'action-scheduler' ) );
|
||||
}
|
||||
private function hook() {
|
||||
add_filter( 'action_scheduler_store_class', array( $this, 'get_store_class' ), 100, 1 );
|
||||
add_filter( 'action_scheduler_logger_class', array( $this, 'get_logger_class' ), 100, 1 );
|
||||
add_action( 'init', array( $this, 'maybe_hook_migration' ) );
|
||||
add_action( 'wp_loaded', array( $this, 'schedule_migration' ) );
|
||||
// Action Scheduler may be displayed as a Tools screen or WooCommerce > Status administration screen
|
||||
add_action( 'load-tools_page_action-scheduler', array( $this, 'hook_admin_notices' ), 10, 0 );
|
||||
add_action( 'load-woocommerce_page_wc-status', array( $this, 'hook_admin_notices' ), 10, 0 );
|
||||
}
|
||||
public function maybe_hook_migration() {
|
||||
if ( ! $this->allow_migration() || \ActionScheduler_DataController::is_migration_complete() ) {
|
||||
return;
|
||||
}
|
||||
$this->migration_scheduler->hook();
|
||||
}
|
||||
public function allow_migration() {
|
||||
if ( ! \ActionScheduler_DataController::dependencies_met() ) {
|
||||
return false;
|
||||
}
|
||||
if ( null === $this->migrate_custom_store ) {
|
||||
$this->migrate_custom_store = apply_filters( 'action_scheduler_migrate_data_store', false );
|
||||
}
|
||||
return ( ! $this->has_custom_datastore() ) || $this->migrate_custom_store;
|
||||
}
|
||||
public static function init() {
|
||||
if ( \ActionScheduler_DataController::dependencies_met() ) {
|
||||
self::instance()->hook();
|
||||
}
|
||||
}
|
||||
public static function instance() {
|
||||
if ( ! isset( self::$instance ) ) {
|
||||
self::$instance = new static( new Scheduler() );
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
namespace Action_Scheduler\Migration;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class DryRun_ActionMigrator extends ActionMigrator {
|
||||
public function migrate( $source_action_id ) {
|
||||
do_action( 'action_scheduler/migrate_action_dry_run', $source_action_id );
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
namespace Action_Scheduler\Migration;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class DryRun_LogMigrator extends LogMigrator {
|
||||
public function migrate( $source_action_id, $destination_action_id ) {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
namespace Action_Scheduler\Migration;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
use ActionScheduler_Logger;
|
||||
class LogMigrator {
|
||||
private $source;
|
||||
private $destination;
|
||||
public function __construct( ActionScheduler_Logger $source_logger, ActionScheduler_Logger $destination_Logger ) {
|
||||
$this->source = $source_logger;
|
||||
$this->destination = $destination_Logger;
|
||||
}
|
||||
public function migrate( $source_action_id, $destination_action_id ) {
|
||||
$logs = $this->source->get_logs( $source_action_id );
|
||||
foreach ( $logs as $log ) {
|
||||
if ( $log->get_action_id() == $source_action_id ) {
|
||||
$this->destination->log( $destination_action_id, $log->get_message(), $log->get_date() );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+72
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
namespace Action_Scheduler\Migration;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class Runner {
|
||||
private $source_store;
|
||||
private $destination_store;
|
||||
private $source_logger;
|
||||
private $destination_logger;
|
||||
private $batch_fetcher;
|
||||
private $action_migrator;
|
||||
private $log_migrator;
|
||||
private $progress_bar;
|
||||
public function __construct( Config $config ) {
|
||||
$this->source_store = $config->get_source_store();
|
||||
$this->destination_store = $config->get_destination_store();
|
||||
$this->source_logger = $config->get_source_logger();
|
||||
$this->destination_logger = $config->get_destination_logger();
|
||||
$this->batch_fetcher = new BatchFetcher( $this->source_store );
|
||||
if ( $config->get_dry_run() ) {
|
||||
$this->log_migrator = new DryRun_LogMigrator( $this->source_logger, $this->destination_logger );
|
||||
$this->action_migrator = new DryRun_ActionMigrator( $this->source_store, $this->destination_store, $this->log_migrator );
|
||||
} else {
|
||||
$this->log_migrator = new LogMigrator( $this->source_logger, $this->destination_logger );
|
||||
$this->action_migrator = new ActionMigrator( $this->source_store, $this->destination_store, $this->log_migrator );
|
||||
}
|
||||
if ( defined( 'WP_CLI' ) && WP_CLI ) {
|
||||
$this->progress_bar = $config->get_progress_bar();
|
||||
}
|
||||
}
|
||||
public function run( $batch_size = 10 ) {
|
||||
$batch = $this->batch_fetcher->fetch( $batch_size );
|
||||
$batch_size = count( $batch );
|
||||
if ( ! $batch_size ) {
|
||||
return 0;
|
||||
}
|
||||
if ( $this->progress_bar ) {
|
||||
$this->progress_bar->set_message( sprintf( _n( 'Migrating %d action', 'Migrating %d actions', $batch_size, 'action-scheduler' ), $batch_size ) );
|
||||
$this->progress_bar->set_count( $batch_size );
|
||||
}
|
||||
$this->migrate_actions( $batch );
|
||||
return $batch_size;
|
||||
}
|
||||
public function migrate_actions( array $action_ids ) {
|
||||
do_action( 'action_scheduler/migration_batch_starting', $action_ids );
|
||||
\ActionScheduler::logger()->unhook_stored_action();
|
||||
$this->destination_logger->unhook_stored_action();
|
||||
foreach ( $action_ids as $source_action_id ) {
|
||||
$destination_action_id = $this->action_migrator->migrate( $source_action_id );
|
||||
if ( $destination_action_id ) {
|
||||
$this->destination_logger->log( $destination_action_id, sprintf(
|
||||
__( 'Migrated action with ID %1$d in %2$s to ID %3$d in %4$s', 'action-scheduler' ),
|
||||
$source_action_id,
|
||||
get_class( $this->source_store ),
|
||||
$destination_action_id,
|
||||
get_class( $this->destination_store )
|
||||
) );
|
||||
}
|
||||
if ( $this->progress_bar ) {
|
||||
$this->progress_bar->tick();
|
||||
}
|
||||
}
|
||||
if ( $this->progress_bar ) {
|
||||
$this->progress_bar->finish();
|
||||
}
|
||||
\ActionScheduler::logger()->hook_stored_action();
|
||||
do_action( 'action_scheduler/migration_batch_complete', $action_ids );
|
||||
}
|
||||
public function init_destination() {
|
||||
$this->destination_store->init();
|
||||
$this->destination_logger->init();
|
||||
}
|
||||
}
|
||||
Vendored
+54
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
namespace Action_Scheduler\Migration;
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class Scheduler {
|
||||
const HOOK = 'action_scheduler/migration_hook';
|
||||
const GROUP = 'action-scheduler-migration';
|
||||
public function hook() {
|
||||
add_action( self::HOOK, array( $this, 'run_migration' ), 10, 0 );
|
||||
}
|
||||
public function unhook() {
|
||||
remove_action( self::HOOK, array( $this, 'run_migration' ), 10 );
|
||||
}
|
||||
public function run_migration() {
|
||||
$migration_runner = $this->get_migration_runner();
|
||||
$count = $migration_runner->run( $this->get_batch_size() );
|
||||
if ( $count === 0 ) {
|
||||
$this->mark_complete();
|
||||
} else {
|
||||
$this->schedule_migration( time() + $this->get_schedule_interval() );
|
||||
}
|
||||
}
|
||||
public function mark_complete() {
|
||||
$this->unschedule_migration();
|
||||
\ActionScheduler_DataController::mark_migration_complete();
|
||||
do_action( 'action_scheduler/migration_complete' );
|
||||
}
|
||||
public function is_migration_scheduled() {
|
||||
$next = as_next_scheduled_action( self::HOOK );
|
||||
return ! empty( $next );
|
||||
}
|
||||
public function schedule_migration( $when = 0 ) {
|
||||
$next = as_next_scheduled_action( self::HOOK );
|
||||
if ( ! empty( $next ) ) {
|
||||
return $next;
|
||||
}
|
||||
if ( empty( $when ) ) {
|
||||
$when = time() + MINUTE_IN_SECONDS;
|
||||
}
|
||||
return as_schedule_single_action( $when, self::HOOK, array(), self::GROUP );
|
||||
}
|
||||
public function unschedule_migration() {
|
||||
as_unschedule_action( self::HOOK, null, self::GROUP );
|
||||
}
|
||||
private function get_schedule_interval() {
|
||||
return (int) apply_filters( 'action_scheduler/migration_interval', 0 );
|
||||
}
|
||||
private function get_batch_size() {
|
||||
return (int) apply_filters( 'action_scheduler/migration_batch_size', 250 );
|
||||
}
|
||||
private function get_migration_runner() {
|
||||
$config = Controller::instance()->get_migration_config_object();
|
||||
return new Runner( $config );
|
||||
}
|
||||
}
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
<?php
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class ActionScheduler_CanceledSchedule extends ActionScheduler_SimpleSchedule {
|
||||
private $timestamp = NULL;
|
||||
public function calculate_next( DateTime $after ) {
|
||||
return null;
|
||||
}
|
||||
public function get_next( DateTime $after ) {
|
||||
return null;
|
||||
}
|
||||
public function is_recurring() {
|
||||
return false;
|
||||
}
|
||||
public function __wakeup() {
|
||||
if ( ! is_null( $this->timestamp ) ) {
|
||||
$this->scheduled_timestamp = $this->timestamp;
|
||||
unset( $this->timestamp );
|
||||
}
|
||||
parent::__wakeup();
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class ActionScheduler_CronSchedule extends ActionScheduler_Abstract_RecurringSchedule implements ActionScheduler_Schedule {
|
||||
private $start_timestamp = NULL;
|
||||
private $cron = NULL;
|
||||
public function __construct( DateTime $start, $recurrence, DateTime $first = null ) {
|
||||
if ( ! is_a( $recurrence, 'CronExpression' ) ) {
|
||||
$recurrence = CronExpression::factory( $recurrence );
|
||||
}
|
||||
// For backward compatibility, we need to make sure the date is set to the first matching cron date, not whatever date is passed in. Importantly, by passing true as the 3rd param, if $start matches the cron expression, then it will be used. This was previously handled in the now deprecated next() method.
|
||||
$date = $recurrence->getNextRunDate( $start, 0, true );
|
||||
// parent::__construct() will set this to $date by default, but that may be different to $start now.
|
||||
$first = empty( $first ) ? $start : $first;
|
||||
parent::__construct( $date, $recurrence, $first );
|
||||
}
|
||||
protected function calculate_next( DateTime $after ) {
|
||||
return $this->recurrence->getNextRunDate( $after, 0, false );
|
||||
}
|
||||
public function get_recurrence() {
|
||||
return strval( $this->recurrence );
|
||||
}
|
||||
public function __sleep() {
|
||||
$sleep_params = parent::__sleep();
|
||||
$this->start_timestamp = $this->scheduled_timestamp;
|
||||
$this->cron = $this->recurrence;
|
||||
return array_merge( $sleep_params, array(
|
||||
'start_timestamp',
|
||||
'cron'
|
||||
) );
|
||||
}
|
||||
public function __wakeup() {
|
||||
if ( is_null( $this->scheduled_timestamp ) && ! is_null( $this->start_timestamp ) ) {
|
||||
$this->scheduled_timestamp = $this->start_timestamp;
|
||||
unset( $this->start_timestamp );
|
||||
}
|
||||
if ( is_null( $this->recurrence ) && ! is_null( $this->cron ) ) {
|
||||
$this->recurrence = $this->cron;
|
||||
unset( $this->cron );
|
||||
}
|
||||
parent::__wakeup();
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class ActionScheduler_IntervalSchedule extends ActionScheduler_Abstract_RecurringSchedule implements ActionScheduler_Schedule {
|
||||
private $start_timestamp = NULL;
|
||||
private $interval_in_seconds = NULL;
|
||||
protected function calculate_next( DateTime $after ) {
|
||||
$after->modify( '+' . (int) $this->get_recurrence() . ' seconds' );
|
||||
return $after;
|
||||
}
|
||||
public function interval_in_seconds() {
|
||||
_deprecated_function( __METHOD__, '3.0.0', '(int)ActionScheduler_Abstract_RecurringSchedule::get_recurrence()' );
|
||||
return (int) $this->get_recurrence();
|
||||
}
|
||||
public function __sleep() {
|
||||
$sleep_params = parent::__sleep();
|
||||
$this->start_timestamp = $this->scheduled_timestamp;
|
||||
$this->interval_in_seconds = $this->recurrence;
|
||||
return array_merge( $sleep_params, array(
|
||||
'start_timestamp',
|
||||
'interval_in_seconds'
|
||||
) );
|
||||
}
|
||||
public function __wakeup() {
|
||||
if ( is_null( $this->scheduled_timestamp ) && ! is_null( $this->start_timestamp ) ) {
|
||||
$this->scheduled_timestamp = $this->start_timestamp;
|
||||
unset( $this->start_timestamp );
|
||||
}
|
||||
if ( is_null( $this->recurrence ) && ! is_null( $this->interval_in_seconds ) ) {
|
||||
$this->recurrence = $this->interval_in_seconds;
|
||||
unset( $this->interval_in_seconds );
|
||||
}
|
||||
parent::__wakeup();
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class ActionScheduler_NullSchedule extends ActionScheduler_SimpleSchedule {
|
||||
protected $scheduled_date;
|
||||
public function __construct( DateTime $date = null ) {
|
||||
$this->scheduled_date = null;
|
||||
}
|
||||
public function __sleep() {
|
||||
return array();
|
||||
}
|
||||
public function __wakeup() {
|
||||
$this->scheduled_date = null;
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
interface ActionScheduler_Schedule {
|
||||
public function next( DateTime $after = NULL );
|
||||
public function is_recurring();
|
||||
}
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class ActionScheduler_SimpleSchedule extends ActionScheduler_Abstract_Schedule {
|
||||
private $timestamp = NULL;
|
||||
public function calculate_next( DateTime $after ) {
|
||||
return null;
|
||||
}
|
||||
public function is_recurring() {
|
||||
return false;
|
||||
}
|
||||
public function __sleep() {
|
||||
$sleep_params = parent::__sleep();
|
||||
$this->timestamp = $this->scheduled_timestamp;
|
||||
return array_merge( $sleep_params, array(
|
||||
'timestamp',
|
||||
) );
|
||||
}
|
||||
public function __wakeup() {
|
||||
if ( is_null( $this->scheduled_timestamp ) && ! is_null( $this->timestamp ) ) {
|
||||
$this->scheduled_timestamp = $this->timestamp;
|
||||
unset( $this->timestamp );
|
||||
}
|
||||
parent::__wakeup();
|
||||
}
|
||||
}
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
<?php
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class ActionScheduler_LoggerSchema extends ActionScheduler_Abstract_Schema {
|
||||
const LOG_TABLE = 'actionscheduler_logs';
|
||||
protected $schema_version = 3;
|
||||
public function __construct() {
|
||||
$this->tables = [
|
||||
self::LOG_TABLE,
|
||||
];
|
||||
}
|
||||
public function init() {
|
||||
add_action( 'action_scheduler_before_schema_update', array( $this, 'update_schema_3_0' ), 10, 2 );
|
||||
}
|
||||
protected function get_table_definition( $table ) {
|
||||
global $wpdb;
|
||||
$table_name = $wpdb->$table;
|
||||
$charset_collate = $wpdb->get_charset_collate();
|
||||
switch ( $table ) {
|
||||
case self::LOG_TABLE:
|
||||
$default_date = ActionScheduler_StoreSchema::DEFAULT_DATE;
|
||||
return "CREATE TABLE $table_name (
|
||||
log_id bigint(20) unsigned NOT NULL auto_increment,
|
||||
action_id bigint(20) unsigned NOT NULL,
|
||||
message text NOT NULL,
|
||||
log_date_gmt datetime NULL default '{$default_date}',
|
||||
log_date_local datetime NULL default '{$default_date}',
|
||||
PRIMARY KEY (log_id),
|
||||
KEY action_id (action_id),
|
||||
KEY log_date_gmt (log_date_gmt)
|
||||
) $charset_collate";
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
public function update_schema_3_0( $table, $db_version ) {
|
||||
global $wpdb;
|
||||
if ( 'actionscheduler_logs' !== $table || version_compare( $db_version, '3', '>=' ) ) {
|
||||
return;
|
||||
}
|
||||
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$table_name = $wpdb->prefix . 'actionscheduler_logs';
|
||||
$table_list = $wpdb->get_col( "SHOW TABLES LIKE '{$table_name}'" );
|
||||
$default_date = ActionScheduler_StoreSchema::DEFAULT_DATE;
|
||||
if ( ! empty( $table_list ) ) {
|
||||
$query = "
|
||||
ALTER TABLE {$table_name}
|
||||
MODIFY COLUMN log_date_gmt datetime NULL default '{$default_date}',
|
||||
MODIFY COLUMN log_date_local datetime NULL default '{$default_date}'
|
||||
";
|
||||
$wpdb->query( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
}
|
||||
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
}
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class ActionScheduler_StoreSchema extends ActionScheduler_Abstract_Schema {
|
||||
const ACTIONS_TABLE = 'actionscheduler_actions';
|
||||
const CLAIMS_TABLE = 'actionscheduler_claims';
|
||||
const GROUPS_TABLE = 'actionscheduler_groups';
|
||||
const DEFAULT_DATE = '0000-00-00 00:00:00';
|
||||
protected $schema_version = 7;
|
||||
public function __construct() {
|
||||
$this->tables = [
|
||||
self::ACTIONS_TABLE,
|
||||
self::CLAIMS_TABLE,
|
||||
self::GROUPS_TABLE,
|
||||
];
|
||||
}
|
||||
public function init() {
|
||||
add_action( 'action_scheduler_before_schema_update', array( $this, 'update_schema_5_0' ), 10, 2 );
|
||||
}
|
||||
protected function get_table_definition( $table ) {
|
||||
global $wpdb;
|
||||
$table_name = $wpdb->$table;
|
||||
$charset_collate = $wpdb->get_charset_collate();
|
||||
$max_index_length = 191; // @see wp_get_db_schema()
|
||||
$hook_status_scheduled_date_gmt_max_index_length = $max_index_length - 20 - 8; // - status, - scheduled_date_gmt
|
||||
$default_date = self::DEFAULT_DATE;
|
||||
switch ( $table ) {
|
||||
case self::ACTIONS_TABLE:
|
||||
return "CREATE TABLE {$table_name} (
|
||||
action_id bigint(20) unsigned NOT NULL auto_increment,
|
||||
hook varchar(191) NOT NULL,
|
||||
status varchar(20) NOT NULL,
|
||||
scheduled_date_gmt datetime NULL default '{$default_date}',
|
||||
scheduled_date_local datetime NULL default '{$default_date}',
|
||||
priority tinyint unsigned NOT NULL default '10',
|
||||
args varchar($max_index_length),
|
||||
schedule longtext,
|
||||
group_id bigint(20) unsigned NOT NULL default '0',
|
||||
attempts int(11) NOT NULL default '0',
|
||||
last_attempt_gmt datetime NULL default '{$default_date}',
|
||||
last_attempt_local datetime NULL default '{$default_date}',
|
||||
claim_id bigint(20) unsigned NOT NULL default '0',
|
||||
extended_args varchar(8000) DEFAULT NULL,
|
||||
PRIMARY KEY (action_id),
|
||||
KEY hook_status_scheduled_date_gmt (hook($hook_status_scheduled_date_gmt_max_index_length), status, scheduled_date_gmt),
|
||||
KEY status_scheduled_date_gmt (status, scheduled_date_gmt),
|
||||
KEY scheduled_date_gmt (scheduled_date_gmt),
|
||||
KEY args (args($max_index_length)),
|
||||
KEY group_id (group_id),
|
||||
KEY last_attempt_gmt (last_attempt_gmt),
|
||||
KEY `claim_id_status_scheduled_date_gmt` (`claim_id`, `status`, `scheduled_date_gmt`)
|
||||
) $charset_collate";
|
||||
case self::CLAIMS_TABLE:
|
||||
return "CREATE TABLE {$table_name} (
|
||||
claim_id bigint(20) unsigned NOT NULL auto_increment,
|
||||
date_created_gmt datetime NULL default '{$default_date}',
|
||||
PRIMARY KEY (claim_id),
|
||||
KEY date_created_gmt (date_created_gmt)
|
||||
) $charset_collate";
|
||||
case self::GROUPS_TABLE:
|
||||
return "CREATE TABLE {$table_name} (
|
||||
group_id bigint(20) unsigned NOT NULL auto_increment,
|
||||
slug varchar(255) NOT NULL,
|
||||
PRIMARY KEY (group_id),
|
||||
KEY slug (slug($max_index_length))
|
||||
) $charset_collate";
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
public function update_schema_5_0( $table, $db_version ) {
|
||||
global $wpdb;
|
||||
if ( 'actionscheduler_actions' !== $table || version_compare( $db_version, '5', '>=' ) ) {
|
||||
return;
|
||||
}
|
||||
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$table_name = $wpdb->prefix . 'actionscheduler_actions';
|
||||
$table_list = $wpdb->get_col( "SHOW TABLES LIKE '{$table_name}'" );
|
||||
$default_date = self::DEFAULT_DATE;
|
||||
if ( ! empty( $table_list ) ) {
|
||||
$query = "
|
||||
ALTER TABLE {$table_name}
|
||||
MODIFY COLUMN scheduled_date_gmt datetime NULL default '{$default_date}',
|
||||
MODIFY COLUMN scheduled_date_local datetime NULL default '{$default_date}',
|
||||
MODIFY COLUMN last_attempt_gmt datetime NULL default '{$default_date}',
|
||||
MODIFY COLUMN last_attempt_local datetime NULL default '{$default_date}'
|
||||
";
|
||||
$wpdb->query( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
}
|
||||
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
<?php
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
abstract class ActionScheduler_Abstract_QueueRunner_Deprecated {
|
||||
protected function get_maximum_execution_time() {
|
||||
_deprecated_function( __METHOD__, '2.1.1', 'ActionScheduler_Abstract_QueueRunner::get_time_limit()' );
|
||||
$maximum_execution_time = 30;
|
||||
// Apply deprecated filter
|
||||
if ( has_filter( 'action_scheduler_maximum_execution_time' ) ) {
|
||||
_deprecated_function( 'action_scheduler_maximum_execution_time', '2.1.1', 'action_scheduler_queue_runner_time_limit' );
|
||||
$maximum_execution_time = apply_filters( 'action_scheduler_maximum_execution_time', $maximum_execution_time );
|
||||
}
|
||||
return absint( $maximum_execution_time );
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class ActionScheduler_AdminView_Deprecated {
|
||||
public function action_scheduler_post_type_args( $args ) {
|
||||
_deprecated_function( __METHOD__, '2.0.0' );
|
||||
return $args;
|
||||
}
|
||||
public function list_table_views( $views ) {
|
||||
_deprecated_function( __METHOD__, '2.0.0' );
|
||||
return $views;
|
||||
}
|
||||
public function bulk_actions( $actions ) {
|
||||
_deprecated_function( __METHOD__, '2.0.0' );
|
||||
return $actions;
|
||||
}
|
||||
public function list_table_columns( $columns ) {
|
||||
_deprecated_function( __METHOD__, '2.0.0' );
|
||||
return $columns;
|
||||
}
|
||||
public static function list_table_sortable_columns( $columns ) {
|
||||
_deprecated_function( __METHOD__, '2.0.0' );
|
||||
return $columns;
|
||||
}
|
||||
public static function list_table_column_content( $column_name, $post_id ) {
|
||||
_deprecated_function( __METHOD__, '2.0.0' );
|
||||
}
|
||||
public static function row_actions( $actions, $post ) {
|
||||
_deprecated_function( __METHOD__, '2.0.0' );
|
||||
return $actions;
|
||||
}
|
||||
public static function maybe_execute_action() {
|
||||
_deprecated_function( __METHOD__, '2.0.0' );
|
||||
}
|
||||
public static function admin_notices() {
|
||||
_deprecated_function( __METHOD__, '2.0.0' );
|
||||
}
|
||||
public function custom_orderby( $orderby, $query ){
|
||||
_deprecated_function( __METHOD__, '2.0.0' );
|
||||
}
|
||||
public function search_post_password( $search, $query ) {
|
||||
_deprecated_function( __METHOD__, '2.0.0' );
|
||||
}
|
||||
public function post_updated_messages( $messages ) {
|
||||
_deprecated_function( __METHOD__, '2.0.0' );
|
||||
return $messages;
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
abstract class ActionScheduler_Schedule_Deprecated implements ActionScheduler_Schedule {
|
||||
public function next( DateTime $after = null ) {
|
||||
if ( empty( $after ) ) {
|
||||
$return_value = $this->get_date();
|
||||
$replacement_method = 'get_date()';
|
||||
} else {
|
||||
$return_value = $this->get_next( $after );
|
||||
$replacement_method = 'get_next( $after )';
|
||||
}
|
||||
_deprecated_function( __METHOD__, '3.0.0', __CLASS__ . '::' . $replacement_method ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
return $return_value;
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
abstract class ActionScheduler_Store_Deprecated {
|
||||
public function mark_failed_fetch_action( $action_id ) {
|
||||
_deprecated_function( __METHOD__, '3.0.0', 'ActionScheduler_Store::mark_failure()' );
|
||||
self::$store->mark_failure( $action_id );
|
||||
}
|
||||
protected static function hook() {
|
||||
_deprecated_function( __METHOD__, '3.0.0' );
|
||||
}
|
||||
protected static function unhook() {
|
||||
_deprecated_function( __METHOD__, '3.0.0' );
|
||||
}
|
||||
protected function get_local_timezone() {
|
||||
_deprecated_function( __FUNCTION__, '2.1.0', 'ActionScheduler_TimezoneHelper::set_local_timezone()' );
|
||||
return ActionScheduler_TimezoneHelper::get_local_timezone();
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
function wc_schedule_single_action( $timestamp, $hook, $args = array(), $group = '' ) {
|
||||
_deprecated_function( __FUNCTION__, '2.1.0', 'as_schedule_single_action()' );
|
||||
return as_schedule_single_action( $timestamp, $hook, $args, $group );
|
||||
}
|
||||
function wc_schedule_recurring_action( $timestamp, $interval_in_seconds, $hook, $args = array(), $group = '' ) {
|
||||
_deprecated_function( __FUNCTION__, '2.1.0', 'as_schedule_recurring_action()' );
|
||||
return as_schedule_recurring_action( $timestamp, $interval_in_seconds, $hook, $args, $group );
|
||||
}
|
||||
function wc_schedule_cron_action( $timestamp, $schedule, $hook, $args = array(), $group = '' ) {
|
||||
_deprecated_function( __FUNCTION__, '2.1.0', 'as_schedule_cron_action()' );
|
||||
return as_schedule_cron_action( $timestamp, $schedule, $hook, $args, $group );
|
||||
}
|
||||
function wc_unschedule_action( $hook, $args = array(), $group = '' ) {
|
||||
_deprecated_function( __FUNCTION__, '2.1.0', 'as_unschedule_action()' );
|
||||
as_unschedule_action( $hook, $args, $group );
|
||||
}
|
||||
function wc_next_scheduled_action( $hook, $args = NULL, $group = '' ) {
|
||||
_deprecated_function( __FUNCTION__, '2.1.0', 'as_next_scheduled_action()' );
|
||||
return as_next_scheduled_action( $hook, $args, $group );
|
||||
}
|
||||
function wc_get_scheduled_actions( $args = array(), $return_format = OBJECT ) {
|
||||
_deprecated_function( __FUNCTION__, '2.1.0', 'as_get_scheduled_actions()' );
|
||||
return as_get_scheduled_actions( $args, $return_format );
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
<?php
|
||||
+231
@@ -0,0 +1,231 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
function as_enqueue_async_action( $hook, $args = array(), $group = '', $unique = false, $priority = 10 ) {
|
||||
if ( ! ActionScheduler::is_initialized( __FUNCTION__ ) ) {
|
||||
return 0;
|
||||
}
|
||||
$pre = apply_filters( 'pre_as_enqueue_async_action', null, $hook, $args, $group, $priority );
|
||||
if ( null !== $pre ) {
|
||||
return is_int( $pre ) ? $pre : 0;
|
||||
}
|
||||
return ActionScheduler::factory()->create(
|
||||
array(
|
||||
'type' => 'async',
|
||||
'hook' => $hook,
|
||||
'arguments' => $args,
|
||||
'group' => $group,
|
||||
'unique' => $unique,
|
||||
'priority' => $priority,
|
||||
)
|
||||
);
|
||||
}
|
||||
function as_schedule_single_action( $timestamp, $hook, $args = array(), $group = '', $unique = false, $priority = 10 ) {
|
||||
if ( ! ActionScheduler::is_initialized( __FUNCTION__ ) ) {
|
||||
return 0;
|
||||
}
|
||||
$pre = apply_filters( 'pre_as_schedule_single_action', null, $timestamp, $hook, $args, $group, $priority );
|
||||
if ( null !== $pre ) {
|
||||
return is_int( $pre ) ? $pre : 0;
|
||||
}
|
||||
return ActionScheduler::factory()->create(
|
||||
array(
|
||||
'type' => 'single',
|
||||
'hook' => $hook,
|
||||
'arguments' => $args,
|
||||
'when' => $timestamp,
|
||||
'group' => $group,
|
||||
'unique' => $unique,
|
||||
'priority' => $priority,
|
||||
)
|
||||
);
|
||||
}
|
||||
function as_schedule_recurring_action( $timestamp, $interval_in_seconds, $hook, $args = array(), $group = '', $unique = false, $priority = 10 ) {
|
||||
if ( ! ActionScheduler::is_initialized( __FUNCTION__ ) ) {
|
||||
return 0;
|
||||
}
|
||||
$interval = (int) $interval_in_seconds;
|
||||
// We expect an integer and allow it to be passed using float and string types, but otherwise
|
||||
// should reject unexpected values.
|
||||
if ( ! is_numeric( $interval_in_seconds ) || $interval_in_seconds != $interval ) {
|
||||
_doing_it_wrong(
|
||||
__METHOD__,
|
||||
sprintf(
|
||||
esc_html__( 'An integer was expected but "%1$s" (%2$s) was received.', 'action-scheduler' ),
|
||||
esc_html( $interval_in_seconds ),
|
||||
esc_html( gettype( $interval_in_seconds ) )
|
||||
),
|
||||
'3.6.0'
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
$pre = apply_filters( 'pre_as_schedule_recurring_action', null, $timestamp, $interval_in_seconds, $hook, $args, $group, $priority );
|
||||
if ( null !== $pre ) {
|
||||
return is_int( $pre ) ? $pre : 0;
|
||||
}
|
||||
return ActionScheduler::factory()->create(
|
||||
array(
|
||||
'type' => 'recurring',
|
||||
'hook' => $hook,
|
||||
'arguments' => $args,
|
||||
'when' => $timestamp,
|
||||
'pattern' => $interval_in_seconds,
|
||||
'group' => $group,
|
||||
'unique' => $unique,
|
||||
'priority' => $priority,
|
||||
)
|
||||
);
|
||||
}
|
||||
function as_schedule_cron_action( $timestamp, $schedule, $hook, $args = array(), $group = '', $unique = false, $priority = 10 ) {
|
||||
if ( ! ActionScheduler::is_initialized( __FUNCTION__ ) ) {
|
||||
return 0;
|
||||
}
|
||||
$pre = apply_filters( 'pre_as_schedule_cron_action', null, $timestamp, $schedule, $hook, $args, $group, $priority );
|
||||
if ( null !== $pre ) {
|
||||
return is_int( $pre ) ? $pre : 0;
|
||||
}
|
||||
return ActionScheduler::factory()->create(
|
||||
array(
|
||||
'type' => 'cron',
|
||||
'hook' => $hook,
|
||||
'arguments' => $args,
|
||||
'when' => $timestamp,
|
||||
'pattern' => $schedule,
|
||||
'group' => $group,
|
||||
'unique' => $unique,
|
||||
'priority' => $priority,
|
||||
)
|
||||
);
|
||||
}
|
||||
function as_unschedule_action( $hook, $args = array(), $group = '' ) {
|
||||
if ( ! ActionScheduler::is_initialized( __FUNCTION__ ) ) {
|
||||
return 0;
|
||||
}
|
||||
$params = array(
|
||||
'hook' => $hook,
|
||||
'status' => ActionScheduler_Store::STATUS_PENDING,
|
||||
'orderby' => 'date',
|
||||
'order' => 'ASC',
|
||||
'group' => $group,
|
||||
);
|
||||
if ( is_array( $args ) ) {
|
||||
$params['args'] = $args;
|
||||
}
|
||||
$action_id = ActionScheduler::store()->query_action( $params );
|
||||
if ( $action_id ) {
|
||||
try {
|
||||
ActionScheduler::store()->cancel_action( $action_id );
|
||||
} catch ( Exception $exception ) {
|
||||
ActionScheduler::logger()->log(
|
||||
$action_id,
|
||||
sprintf(
|
||||
__( 'Caught exception while cancelling action "%1$s": %2$s', 'action-scheduler' ),
|
||||
$hook,
|
||||
$exception->getMessage()
|
||||
)
|
||||
);
|
||||
$action_id = null;
|
||||
}
|
||||
}
|
||||
return $action_id;
|
||||
}
|
||||
function as_unschedule_all_actions( $hook, $args = array(), $group = '' ) {
|
||||
if ( ! ActionScheduler::is_initialized( __FUNCTION__ ) ) {
|
||||
return;
|
||||
}
|
||||
if ( empty( $args ) ) {
|
||||
if ( ! empty( $hook ) && empty( $group ) ) {
|
||||
ActionScheduler_Store::instance()->cancel_actions_by_hook( $hook );
|
||||
return;
|
||||
}
|
||||
if ( ! empty( $group ) && empty( $hook ) ) {
|
||||
ActionScheduler_Store::instance()->cancel_actions_by_group( $group );
|
||||
return;
|
||||
}
|
||||
}
|
||||
do {
|
||||
$unscheduled_action = as_unschedule_action( $hook, $args, $group );
|
||||
} while ( ! empty( $unscheduled_action ) );
|
||||
}
|
||||
function as_next_scheduled_action( $hook, $args = null, $group = '' ) {
|
||||
if ( ! ActionScheduler::is_initialized( __FUNCTION__ ) ) {
|
||||
return false;
|
||||
}
|
||||
$params = array(
|
||||
'hook' => $hook,
|
||||
'orderby' => 'date',
|
||||
'order' => 'ASC',
|
||||
'group' => $group,
|
||||
);
|
||||
if ( is_array( $args ) ) {
|
||||
$params['args'] = $args;
|
||||
}
|
||||
$params['status'] = ActionScheduler_Store::STATUS_RUNNING;
|
||||
$action_id = ActionScheduler::store()->query_action( $params );
|
||||
if ( $action_id ) {
|
||||
return true;
|
||||
}
|
||||
$params['status'] = ActionScheduler_Store::STATUS_PENDING;
|
||||
$action_id = ActionScheduler::store()->query_action( $params );
|
||||
if ( null === $action_id ) {
|
||||
return false;
|
||||
}
|
||||
$action = ActionScheduler::store()->fetch_action( $action_id );
|
||||
$scheduled_date = $action->get_schedule()->get_date();
|
||||
if ( $scheduled_date ) {
|
||||
return (int) $scheduled_date->format( 'U' );
|
||||
} elseif ( null === $scheduled_date ) { // pending async action with NullSchedule.
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function as_has_scheduled_action( $hook, $args = null, $group = '' ) {
|
||||
if ( ! ActionScheduler::is_initialized( __FUNCTION__ ) ) {
|
||||
return false;
|
||||
}
|
||||
$query_args = array(
|
||||
'hook' => $hook,
|
||||
'status' => array( ActionScheduler_Store::STATUS_RUNNING, ActionScheduler_Store::STATUS_PENDING ),
|
||||
'group' => $group,
|
||||
'orderby' => 'none',
|
||||
);
|
||||
if ( null !== $args ) {
|
||||
$query_args['args'] = $args;
|
||||
}
|
||||
$action_id = ActionScheduler::store()->query_action( $query_args );
|
||||
return null !== $action_id;
|
||||
}
|
||||
function as_get_scheduled_actions( $args = array(), $return_format = OBJECT ) {
|
||||
if ( ! ActionScheduler::is_initialized( __FUNCTION__ ) ) {
|
||||
return array();
|
||||
}
|
||||
$store = ActionScheduler::store();
|
||||
foreach ( array( 'date', 'modified' ) as $key ) {
|
||||
if ( isset( $args[ $key ] ) ) {
|
||||
$args[ $key ] = as_get_datetime_object( $args[ $key ] );
|
||||
}
|
||||
}
|
||||
$ids = $store->query_actions( $args );
|
||||
if ( 'ids' === $return_format || 'int' === $return_format ) {
|
||||
return $ids;
|
||||
}
|
||||
$actions = array();
|
||||
foreach ( $ids as $action_id ) {
|
||||
$actions[ $action_id ] = $store->fetch_action( $action_id );
|
||||
}
|
||||
if ( ARRAY_A == $return_format ) {
|
||||
foreach ( $actions as $action_id => $action_object ) {
|
||||
$actions[ $action_id ] = get_object_vars( $action_object );
|
||||
}
|
||||
}
|
||||
return $actions;
|
||||
}
|
||||
function as_get_datetime_object( $date_string = null, $timezone = 'UTC' ) {
|
||||
if ( is_object( $date_string ) && $date_string instanceof DateTime ) {
|
||||
$date = new ActionScheduler_DateTime( $date_string->format( 'Y-m-d H:i:s' ), new DateTimeZone( $timezone ) );
|
||||
} elseif ( is_numeric( $date_string ) ) {
|
||||
$date = new ActionScheduler_DateTime( '@' . $date_string, new DateTimeZone( $timezone ) );
|
||||
} else {
|
||||
$date = new ActionScheduler_DateTime( null === $date_string ? 'now' : $date_string, new DateTimeZone( $timezone ) );
|
||||
}
|
||||
return $date;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<?php
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
if ( ! class_exists( 'WP_Async_Request' ) ) {
|
||||
abstract class WP_Async_Request {
|
||||
protected $prefix = 'wp';
|
||||
protected $action = 'async_request';
|
||||
protected $identifier;
|
||||
protected $data = array();
|
||||
public function __construct() {
|
||||
$this->identifier = $this->prefix . '_' . $this->action;
|
||||
add_action( 'wp_ajax_' . $this->identifier, array( $this, 'maybe_handle' ) );
|
||||
add_action( 'wp_ajax_nopriv_' . $this->identifier, array( $this, 'maybe_handle' ) );
|
||||
}
|
||||
public function data( $data ) {
|
||||
$this->data = $data;
|
||||
return $this;
|
||||
}
|
||||
public function dispatch() {
|
||||
$url = add_query_arg( $this->get_query_args(), $this->get_query_url() );
|
||||
$args = $this->get_post_args();
|
||||
return wp_remote_post( esc_url_raw( $url ), $args );
|
||||
}
|
||||
protected function get_query_args() {
|
||||
if ( property_exists( $this, 'query_args' ) ) {
|
||||
return $this->query_args;
|
||||
}
|
||||
$args = array(
|
||||
'action' => $this->identifier,
|
||||
'nonce' => wp_create_nonce( $this->identifier ),
|
||||
);
|
||||
return apply_filters( $this->identifier . '_query_args', $args );
|
||||
}
|
||||
protected function get_query_url() {
|
||||
if ( property_exists( $this, 'query_url' ) ) {
|
||||
return $this->query_url;
|
||||
}
|
||||
$url = admin_url( 'admin-ajax.php' );
|
||||
return apply_filters( $this->identifier . '_query_url', $url );
|
||||
}
|
||||
protected function get_post_args() {
|
||||
if ( property_exists( $this, 'post_args' ) ) {
|
||||
return $this->post_args;
|
||||
}
|
||||
$args = array(
|
||||
'timeout' => 0.01,
|
||||
'blocking' => false,
|
||||
'body' => $this->data,
|
||||
'cookies' => $_COOKIE,
|
||||
'sslverify' => apply_filters( 'https_local_ssl_verify', false ),
|
||||
);
|
||||
return apply_filters( $this->identifier . '_post_args', $args );
|
||||
}
|
||||
public function maybe_handle() {
|
||||
// Don't lock up other requests while processing
|
||||
session_write_close();
|
||||
check_ajax_referer( $this->identifier, 'nonce' );
|
||||
$this->handle();
|
||||
wp_die();
|
||||
}
|
||||
abstract protected function handle();
|
||||
}
|
||||
}
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class CronExpression
|
||||
{
|
||||
const MINUTE = 0;
|
||||
const HOUR = 1;
|
||||
const DAY = 2;
|
||||
const MONTH = 3;
|
||||
const WEEKDAY = 4;
|
||||
const YEAR = 5;
|
||||
private $cronParts;
|
||||
private $fieldFactory;
|
||||
private static $order = array(self::YEAR, self::MONTH, self::DAY, self::WEEKDAY, self::HOUR, self::MINUTE);
|
||||
public static function factory($expression, CronExpression_FieldFactory $fieldFactory = null)
|
||||
{
|
||||
$mappings = array(
|
||||
'@yearly' => '0 0 1 1 *',
|
||||
'@annually' => '0 0 1 1 *',
|
||||
'@monthly' => '0 0 1 * *',
|
||||
'@weekly' => '0 0 * * 0',
|
||||
'@daily' => '0 0 * * *',
|
||||
'@hourly' => '0 * * * *'
|
||||
);
|
||||
if (isset($mappings[$expression])) {
|
||||
$expression = $mappings[$expression];
|
||||
}
|
||||
return new self($expression, $fieldFactory ? $fieldFactory : new CronExpression_FieldFactory());
|
||||
}
|
||||
public function __construct($expression, CronExpression_FieldFactory $fieldFactory)
|
||||
{
|
||||
$this->fieldFactory = $fieldFactory;
|
||||
$this->setExpression($expression);
|
||||
}
|
||||
public function setExpression($value)
|
||||
{
|
||||
$this->cronParts = preg_split('/\s/', $value, -1, PREG_SPLIT_NO_EMPTY);
|
||||
if (count($this->cronParts) < 5) {
|
||||
throw new InvalidArgumentException(
|
||||
$value . ' is not a valid CRON expression'
|
||||
);
|
||||
}
|
||||
foreach ($this->cronParts as $position => $part) {
|
||||
$this->setPart($position, $part);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
public function setPart($position, $value)
|
||||
{
|
||||
if (!$this->fieldFactory->getField($position)->validate($value)) {
|
||||
throw new InvalidArgumentException(
|
||||
'Invalid CRON field value ' . $value . ' as position ' . $position
|
||||
);
|
||||
}
|
||||
$this->cronParts[$position] = $value;
|
||||
return $this;
|
||||
}
|
||||
public function getNextRunDate($currentTime = 'now', $nth = 0, $allowCurrentDate = false)
|
||||
{
|
||||
return $this->getRunDate($currentTime, $nth, false, $allowCurrentDate);
|
||||
}
|
||||
public function getPreviousRunDate($currentTime = 'now', $nth = 0, $allowCurrentDate = false)
|
||||
{
|
||||
return $this->getRunDate($currentTime, $nth, true, $allowCurrentDate);
|
||||
}
|
||||
public function getMultipleRunDates($total, $currentTime = 'now', $invert = false, $allowCurrentDate = false)
|
||||
{
|
||||
$matches = array();
|
||||
for ($i = 0; $i < max(0, $total); $i++) {
|
||||
$matches[] = $this->getRunDate($currentTime, $i, $invert, $allowCurrentDate);
|
||||
}
|
||||
return $matches;
|
||||
}
|
||||
public function getExpression($part = null)
|
||||
{
|
||||
if (null === $part) {
|
||||
return implode(' ', $this->cronParts);
|
||||
} elseif (array_key_exists($part, $this->cronParts)) {
|
||||
return $this->cronParts[$part];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public function __toString()
|
||||
{
|
||||
return $this->getExpression();
|
||||
}
|
||||
public function isDue($currentTime = 'now')
|
||||
{
|
||||
if ('now' === $currentTime) {
|
||||
$currentDate = date('Y-m-d H:i');
|
||||
$currentTime = strtotime($currentDate);
|
||||
} elseif ($currentTime instanceof DateTime) {
|
||||
$currentDate = $currentTime->format('Y-m-d H:i');
|
||||
$currentTime = strtotime($currentDate);
|
||||
} else {
|
||||
$currentTime = new DateTime($currentTime);
|
||||
$currentTime->setTime($currentTime->format('H'), $currentTime->format('i'), 0);
|
||||
$currentDate = $currentTime->format('Y-m-d H:i');
|
||||
$currentTime = (int)($currentTime->getTimestamp());
|
||||
}
|
||||
return $this->getNextRunDate($currentDate, 0, true)->getTimestamp() == $currentTime;
|
||||
}
|
||||
protected function getRunDate($currentTime = null, $nth = 0, $invert = false, $allowCurrentDate = false)
|
||||
{
|
||||
if ($currentTime instanceof DateTime) {
|
||||
$currentDate = $currentTime;
|
||||
} else {
|
||||
$currentDate = new DateTime($currentTime ? $currentTime : 'now');
|
||||
$currentDate->setTimezone(new DateTimeZone(date_default_timezone_get()));
|
||||
}
|
||||
$currentDate->setTime($currentDate->format('H'), $currentDate->format('i'), 0);
|
||||
$nextRun = clone $currentDate;
|
||||
$nth = (int) $nth;
|
||||
// Set a hard limit to bail on an impossible date
|
||||
for ($i = 0; $i < 1000; $i++) {
|
||||
foreach (self::$order as $position) {
|
||||
$part = $this->getExpression($position);
|
||||
if (null === $part) {
|
||||
continue;
|
||||
}
|
||||
$satisfied = false;
|
||||
// Get the field object used to validate this part
|
||||
$field = $this->fieldFactory->getField($position);
|
||||
// Check if this is singular or a list
|
||||
if (strpos($part, ',') === false) {
|
||||
$satisfied = $field->isSatisfiedBy($nextRun, $part);
|
||||
} else {
|
||||
foreach (array_map('trim', explode(',', $part)) as $listPart) {
|
||||
if ($field->isSatisfiedBy($nextRun, $listPart)) {
|
||||
$satisfied = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// If the field is not satisfied, then start over
|
||||
if (!$satisfied) {
|
||||
$field->increment($nextRun, $invert);
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
// Skip this match if needed
|
||||
if ((!$allowCurrentDate && $nextRun == $currentDate) || --$nth > -1) {
|
||||
$this->fieldFactory->getField(0)->increment($nextRun, $invert);
|
||||
continue;
|
||||
}
|
||||
return $nextRun;
|
||||
}
|
||||
// @codeCoverageIgnoreStart
|
||||
throw new RuntimeException('Impossible CRON expression');
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
abstract class CronExpression_AbstractField implements CronExpression_FieldInterface
|
||||
{
|
||||
public function isSatisfied($dateValue, $value)
|
||||
{
|
||||
if ($this->isIncrementsOfRanges($value)) {
|
||||
return $this->isInIncrementsOfRanges($dateValue, $value);
|
||||
} elseif ($this->isRange($value)) {
|
||||
return $this->isInRange($dateValue, $value);
|
||||
}
|
||||
return $value == '*' || $dateValue == $value;
|
||||
}
|
||||
public function isRange($value)
|
||||
{
|
||||
return strpos($value, '-') !== false;
|
||||
}
|
||||
public function isIncrementsOfRanges($value)
|
||||
{
|
||||
return strpos($value, '/') !== false;
|
||||
}
|
||||
public function isInRange($dateValue, $value)
|
||||
{
|
||||
$parts = array_map('trim', explode('-', $value, 2));
|
||||
return $dateValue >= $parts[0] && $dateValue <= $parts[1];
|
||||
}
|
||||
public function isInIncrementsOfRanges($dateValue, $value)
|
||||
{
|
||||
$parts = array_map('trim', explode('/', $value, 2));
|
||||
$stepSize = isset($parts[1]) ? $parts[1] : 0;
|
||||
if ($parts[0] == '*' || $parts[0] === '0') {
|
||||
return (int) $dateValue % $stepSize == 0;
|
||||
}
|
||||
$range = explode('-', $parts[0], 2);
|
||||
$offset = $range[0];
|
||||
$to = isset($range[1]) ? $range[1] : $dateValue;
|
||||
// Ensure that the date value is within the range
|
||||
if ($dateValue < $offset || $dateValue > $to) {
|
||||
return false;
|
||||
}
|
||||
for ($i = $offset; $i <= $to; $i+= $stepSize) {
|
||||
if ($i == $dateValue) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class CronExpression_DayOfMonthField extends CronExpression_AbstractField
|
||||
{
|
||||
private static function getNearestWeekday($currentYear, $currentMonth, $targetDay)
|
||||
{
|
||||
$tday = str_pad($targetDay, 2, '0', STR_PAD_LEFT);
|
||||
$target = new DateTime("$currentYear-$currentMonth-$tday");
|
||||
$currentWeekday = (int) $target->format('N');
|
||||
if ($currentWeekday < 6) {
|
||||
return $target;
|
||||
}
|
||||
$lastDayOfMonth = $target->format('t');
|
||||
foreach (array(-1, 1, -2, 2) as $i) {
|
||||
$adjusted = $targetDay + $i;
|
||||
if ($adjusted > 0 && $adjusted <= $lastDayOfMonth) {
|
||||
$target->setDate($currentYear, $currentMonth, $adjusted);
|
||||
if ($target->format('N') < 6 && $target->format('m') == $currentMonth) {
|
||||
return $target;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public function isSatisfiedBy(DateTime $date, $value)
|
||||
{
|
||||
// ? states that the field value is to be skipped
|
||||
if ($value == '?') {
|
||||
return true;
|
||||
}
|
||||
$fieldValue = $date->format('d');
|
||||
// Check to see if this is the last day of the month
|
||||
if ($value == 'L') {
|
||||
return $fieldValue == $date->format('t');
|
||||
}
|
||||
// Check to see if this is the nearest weekday to a particular value
|
||||
if (strpos($value, 'W')) {
|
||||
// Parse the target day
|
||||
$targetDay = substr($value, 0, strpos($value, 'W'));
|
||||
// Find out if the current day is the nearest day of the week
|
||||
return $date->format('j') == self::getNearestWeekday(
|
||||
$date->format('Y'),
|
||||
$date->format('m'),
|
||||
$targetDay
|
||||
)->format('j');
|
||||
}
|
||||
return $this->isSatisfied($date->format('d'), $value);
|
||||
}
|
||||
public function increment(DateTime $date, $invert = false)
|
||||
{
|
||||
if ($invert) {
|
||||
$date->modify('previous day');
|
||||
$date->setTime(23, 59);
|
||||
} else {
|
||||
$date->modify('next day');
|
||||
$date->setTime(0, 0);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
public function validate($value)
|
||||
{
|
||||
return (bool) preg_match('/[\*,\/\-\?LW0-9A-Za-z]+/', $value);
|
||||
}
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class CronExpression_DayOfWeekField extends CronExpression_AbstractField
|
||||
{
|
||||
public function isSatisfiedBy(DateTime $date, $value)
|
||||
{
|
||||
if ($value == '?') {
|
||||
return true;
|
||||
}
|
||||
// Convert text day of the week values to integers
|
||||
$value = str_ireplace(
|
||||
array('SUN', 'MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT'),
|
||||
range(0, 6),
|
||||
$value
|
||||
);
|
||||
$currentYear = $date->format('Y');
|
||||
$currentMonth = $date->format('m');
|
||||
$lastDayOfMonth = $date->format('t');
|
||||
// Find out if this is the last specific weekday of the month
|
||||
if (strpos($value, 'L')) {
|
||||
$weekday = str_replace('7', '0', substr($value, 0, strpos($value, 'L')));
|
||||
$tdate = clone $date;
|
||||
$tdate->setDate($currentYear, $currentMonth, $lastDayOfMonth);
|
||||
while ($tdate->format('w') != $weekday) {
|
||||
$tdate->setDate($currentYear, $currentMonth, --$lastDayOfMonth);
|
||||
}
|
||||
return $date->format('j') == $lastDayOfMonth;
|
||||
}
|
||||
// Handle # hash tokens
|
||||
if (strpos($value, '#')) {
|
||||
list($weekday, $nth) = explode('#', $value);
|
||||
// Validate the hash fields
|
||||
if ($weekday < 1 || $weekday > 5) {
|
||||
throw new InvalidArgumentException("Weekday must be a value between 1 and 5. {$weekday} given");
|
||||
}
|
||||
if ($nth > 5) {
|
||||
throw new InvalidArgumentException('There are never more than 5 of a given weekday in a month');
|
||||
}
|
||||
// The current weekday must match the targeted weekday to proceed
|
||||
if ($date->format('N') != $weekday) {
|
||||
return false;
|
||||
}
|
||||
$tdate = clone $date;
|
||||
$tdate->setDate($currentYear, $currentMonth, 1);
|
||||
$dayCount = 0;
|
||||
$currentDay = 1;
|
||||
while ($currentDay < $lastDayOfMonth + 1) {
|
||||
if ($tdate->format('N') == $weekday) {
|
||||
if (++$dayCount >= $nth) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
$tdate->setDate($currentYear, $currentMonth, ++$currentDay);
|
||||
}
|
||||
return $date->format('j') == $currentDay;
|
||||
}
|
||||
// Handle day of the week values
|
||||
if (strpos($value, '-')) {
|
||||
$parts = explode('-', $value);
|
||||
if ($parts[0] == '7') {
|
||||
$parts[0] = '0';
|
||||
} elseif ($parts[1] == '0') {
|
||||
$parts[1] = '7';
|
||||
}
|
||||
$value = implode('-', $parts);
|
||||
}
|
||||
// Test to see which Sunday to use -- 0 == 7 == Sunday
|
||||
$format = in_array(7, str_split($value)) ? 'N' : 'w';
|
||||
$fieldValue = $date->format($format);
|
||||
return $this->isSatisfied($fieldValue, $value);
|
||||
}
|
||||
public function increment(DateTime $date, $invert = false)
|
||||
{
|
||||
if ($invert) {
|
||||
$date->modify('-1 day');
|
||||
$date->setTime(23, 59, 0);
|
||||
} else {
|
||||
$date->modify('+1 day');
|
||||
$date->setTime(0, 0, 0);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
public function validate($value)
|
||||
{
|
||||
return (bool) preg_match('/[\*,\/\-0-9A-Z]+/', $value);
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class CronExpression_FieldFactory
|
||||
{
|
||||
private $fields = array();
|
||||
public function getField($position)
|
||||
{
|
||||
if (!isset($this->fields[$position])) {
|
||||
switch ($position) {
|
||||
case 0:
|
||||
$this->fields[$position] = new CronExpression_MinutesField();
|
||||
break;
|
||||
case 1:
|
||||
$this->fields[$position] = new CronExpression_HoursField();
|
||||
break;
|
||||
case 2:
|
||||
$this->fields[$position] = new CronExpression_DayOfMonthField();
|
||||
break;
|
||||
case 3:
|
||||
$this->fields[$position] = new CronExpression_MonthField();
|
||||
break;
|
||||
case 4:
|
||||
$this->fields[$position] = new CronExpression_DayOfWeekField();
|
||||
break;
|
||||
case 5:
|
||||
$this->fields[$position] = new CronExpression_YearField();
|
||||
break;
|
||||
default:
|
||||
throw new InvalidArgumentException(
|
||||
$position . ' is not a valid position'
|
||||
);
|
||||
}
|
||||
}
|
||||
return $this->fields[$position];
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
interface CronExpression_FieldInterface
|
||||
{
|
||||
public function isSatisfiedBy(DateTime $date, $value);
|
||||
public function increment(DateTime $date, $invert = false);
|
||||
public function validate($value);
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class CronExpression_HoursField extends CronExpression_AbstractField
|
||||
{
|
||||
public function isSatisfiedBy(DateTime $date, $value)
|
||||
{
|
||||
return $this->isSatisfied($date->format('H'), $value);
|
||||
}
|
||||
public function increment(DateTime $date, $invert = false)
|
||||
{
|
||||
// Change timezone to UTC temporarily. This will
|
||||
// allow us to go back or forwards and hour even
|
||||
// if DST will be changed between the hours.
|
||||
$timezone = $date->getTimezone();
|
||||
$date->setTimezone(new DateTimeZone('UTC'));
|
||||
if ($invert) {
|
||||
$date->modify('-1 hour');
|
||||
$date->setTime($date->format('H'), 59);
|
||||
} else {
|
||||
$date->modify('+1 hour');
|
||||
$date->setTime($date->format('H'), 0);
|
||||
}
|
||||
$date->setTimezone($timezone);
|
||||
return $this;
|
||||
}
|
||||
public function validate($value)
|
||||
{
|
||||
return (bool) preg_match('/[\*,\/\-0-9]+/', $value);
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class CronExpression_MinutesField extends CronExpression_AbstractField
|
||||
{
|
||||
public function isSatisfiedBy(DateTime $date, $value)
|
||||
{
|
||||
return $this->isSatisfied($date->format('i'), $value);
|
||||
}
|
||||
public function increment(DateTime $date, $invert = false)
|
||||
{
|
||||
if ($invert) {
|
||||
$date->modify('-1 minute');
|
||||
} else {
|
||||
$date->modify('+1 minute');
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
public function validate($value)
|
||||
{
|
||||
return (bool) preg_match('/[\*,\/\-0-9]+/', $value);
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class CronExpression_MonthField extends CronExpression_AbstractField
|
||||
{
|
||||
public function isSatisfiedBy(DateTime $date, $value)
|
||||
{
|
||||
// Convert text month values to integers
|
||||
$value = str_ireplace(
|
||||
array(
|
||||
'JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN',
|
||||
'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'
|
||||
),
|
||||
range(1, 12),
|
||||
$value
|
||||
);
|
||||
return $this->isSatisfied($date->format('m'), $value);
|
||||
}
|
||||
public function increment(DateTime $date, $invert = false)
|
||||
{
|
||||
if ($invert) {
|
||||
// $date->modify('last day of previous month'); // remove for php 5.2 compat
|
||||
$date->modify('previous month');
|
||||
$date->modify($date->format('Y-m-t'));
|
||||
$date->setTime(23, 59);
|
||||
} else {
|
||||
//$date->modify('first day of next month'); // remove for php 5.2 compat
|
||||
$date->modify('next month');
|
||||
$date->modify($date->format('Y-m-01'));
|
||||
$date->setTime(0, 0);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
public function validate($value)
|
||||
{
|
||||
return (bool) preg_match('/[\*,\/\-0-9A-Z]+/', $value);
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) exit;
|
||||
class CronExpression_YearField extends CronExpression_AbstractField
|
||||
{
|
||||
public function isSatisfiedBy(DateTime $date, $value)
|
||||
{
|
||||
return $this->isSatisfied($date->format('Y'), $value);
|
||||
}
|
||||
public function increment(DateTime $date, $invert = false)
|
||||
{
|
||||
if ($invert) {
|
||||
$date->modify('-1 year');
|
||||
$date->setDate($date->format('Y'), 12, 31);
|
||||
$date->setTime(23, 59, 0);
|
||||
} else {
|
||||
$date->modify('+1 year');
|
||||
$date->setDate($date->format('Y'), 1, 1);
|
||||
$date->setTime(0, 0, 0);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
public function validate($value)
|
||||
{
|
||||
return (bool) preg_match('/[\*,\/\-0-9]+/', $value);
|
||||
}
|
||||
}
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
<?php
|
||||
@@ -0,0 +1 @@
|
||||
<?php
|
||||
@@ -0,0 +1 @@
|
||||
<?php
|
||||
Reference in New Issue
Block a user