Initial Commit
This commit is contained in:
337
dependencies/woocommerce/action-scheduler/classes/abstracts/ActionScheduler.php
vendored
Normal file
337
dependencies/woocommerce/action-scheduler/classes/abstracts/ActionScheduler.php
vendored
Normal file
@ -0,0 +1,337 @@
|
||||
<?php
|
||||
|
||||
use Action_Scheduler\WP_CLI\Migration_Command;
|
||||
use Action_Scheduler\Migration\Controller;
|
||||
|
||||
/**
|
||||
* Class ActionScheduler
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
abstract class ActionScheduler {
|
||||
private static $plugin_file = '';
|
||||
/** @var ActionScheduler_ActionFactory */
|
||||
private static $factory = NULL;
|
||||
/** @var bool */
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the absolute system path to the plugin directory, or a file therein
|
||||
* @static
|
||||
* @param string $path
|
||||
* @return string
|
||||
*/
|
||||
public static function plugin_path( $path ) {
|
||||
$base = dirname(self::$plugin_file);
|
||||
if ( $path ) {
|
||||
return trailingslashit($base).$path;
|
||||
} else {
|
||||
return untrailingslashit($base);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the absolute URL to the plugin directory, or a file therein
|
||||
* @static
|
||||
* @param string $path
|
||||
* @return string
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the plugin
|
||||
*
|
||||
* @static
|
||||
* @param string $plugin_file
|
||||
*/
|
||||
public static function init( $plugin_file ) {
|
||||
self::$plugin_file = $plugin_file;
|
||||
spl_autoload_register( array( __CLASS__, 'autoload' ) );
|
||||
|
||||
/**
|
||||
* Fires in the early stages of Action Scheduler init hook.
|
||||
*/
|
||||
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',
|
||||
/**
|
||||
* Runs after the active store's init() method has been called.
|
||||
*
|
||||
* It would probably be preferable to have $store->init() (or it's parent method) set this itself,
|
||||
* once it has initialized, however that would cause problems in cases where a custom data store is in
|
||||
* use and it has not yet been updated to follow that same logic.
|
||||
*/
|
||||
function () {
|
||||
self::$data_store_initialized = true;
|
||||
|
||||
/**
|
||||
* Fires when Action Scheduler is ready: it is safe to use the procedural API after this point.
|
||||
*
|
||||
* @since 3.5.5
|
||||
*/
|
||||
do_action( 'action_scheduler_init' );
|
||||
},
|
||||
1
|
||||
);
|
||||
} else {
|
||||
$admin_view->init();
|
||||
$store->init();
|
||||
$logger->init();
|
||||
$runner->init();
|
||||
self::$data_store_initialized = true;
|
||||
|
||||
/**
|
||||
* Fires when Action Scheduler is ready: it is safe to use the procedural API after this point.
|
||||
*
|
||||
* @since 3.5.5
|
||||
*/
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle WP comment cleanup after migration.
|
||||
*/
|
||||
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' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the AS data store has been initialized.
|
||||
*
|
||||
* @param string $function_name The name of the function being called. Optional. Default `null`.
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_initialized( $function_name = null ) {
|
||||
if ( ! self::$data_store_initialized && ! empty( $function_name ) ) {
|
||||
$message = sprintf(
|
||||
/* translators: %s function name. */
|
||||
__( '%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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the class is one of our abstract classes.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @param string $class The class name.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
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 ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the class is one of our migration classes.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @param string $class The class name.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
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 ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the class is one of our WP CLI classes.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @param string $class The class name.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
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() {}
|
||||
|
||||
/** Deprecated **/
|
||||
|
||||
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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Issue deprecated warning if an Action Scheduler function is called in the shutdown hook.
|
||||
*
|
||||
* @param string $function_name The name of the function being called.
|
||||
* @deprecated 3.1.6.
|
||||
*/
|
||||
public static function check_shutdown_hook( $function_name ) {
|
||||
_deprecated_function( __FUNCTION__, '3.1.6' );
|
||||
}
|
||||
}
|
776
dependencies/woocommerce/action-scheduler/classes/abstracts/ActionScheduler_Abstract_ListTable.php
vendored
Normal file
776
dependencies/woocommerce/action-scheduler/classes/abstracts/ActionScheduler_Abstract_ListTable.php
vendored
Normal file
@ -0,0 +1,776 @@
|
||||
<?php
|
||||
|
||||
if ( ! class_exists( 'WP_List_Table' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Action Scheduler Abstract List Table class
|
||||
*
|
||||
* This abstract class enhances WP_List_Table making it ready to use.
|
||||
*
|
||||
* By extending this class we can focus on describing how our table looks like,
|
||||
* which columns needs to be shown, filter, ordered by and more and forget about the details.
|
||||
*
|
||||
* This class supports:
|
||||
* - Bulk actions
|
||||
* - Search
|
||||
* - Sortable columns
|
||||
* - Automatic translations of the columns
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @since 2.0.0
|
||||
*/
|
||||
abstract class ActionScheduler_Abstract_ListTable extends WP_List_Table {
|
||||
|
||||
/**
|
||||
* The table name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table_name;
|
||||
|
||||
/**
|
||||
* Package name, used to get options from WP_List_Table::get_items_per_page.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $package;
|
||||
|
||||
/**
|
||||
* How many items do we render per page?
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $items_per_page = 10;
|
||||
|
||||
/**
|
||||
* Enables search in this table listing. If this array
|
||||
* is empty it means the listing is not searchable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $search_by = array();
|
||||
|
||||
/**
|
||||
* Columns to show in the table listing. It is a key => value pair. The
|
||||
* key must much the table column name and the value is the label, which is
|
||||
* automatically translated.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $columns = array();
|
||||
|
||||
/**
|
||||
* Defines the row-actions. It expects an array where the key
|
||||
* is the column name and the value is an array of actions.
|
||||
*
|
||||
* The array of actions are key => value, where key is the method name
|
||||
* (with the prefix row_action_<key>) and the value is the label
|
||||
* and title.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $row_actions = array();
|
||||
|
||||
/**
|
||||
* The Primary key of our table
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $ID = 'ID';
|
||||
|
||||
/**
|
||||
* Enables sorting, it expects an array
|
||||
* of columns (the column names are the values)
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $sort_by = array();
|
||||
|
||||
/**
|
||||
* The default sort order
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $filter_by = array();
|
||||
|
||||
/**
|
||||
* The status name => count combinations for this table's items. Used to display status filters.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $status_counts = array();
|
||||
|
||||
/**
|
||||
* Notices to display when loading the table. Array of arrays of form array( 'class' => {updated|error}, 'message' => 'This is the notice text display.' ).
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $admin_notices = array();
|
||||
|
||||
/**
|
||||
* Localised string displayed in the <h1> element above the able.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table_header;
|
||||
|
||||
/**
|
||||
* Enables bulk actions. It must be an array where the key is the action name
|
||||
* and the value is the label (which is translated automatically). It is important
|
||||
* to notice that it will check that the method exists (`bulk_$name`) and will throw
|
||||
* an exception if it does not exists.
|
||||
*
|
||||
* This class will automatically check if the current request has a bulk action, will do the
|
||||
* validations and afterwards will execute the bulk method, with two arguments. The first argument
|
||||
* is the array with primary keys, the second argument is a string with a list of the primary keys,
|
||||
* escaped and ready to use (with `IN`).
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $bulk_actions = array();
|
||||
|
||||
/**
|
||||
* Makes translation easier, it basically just wraps
|
||||
* `_x` with some default (the package name).
|
||||
*
|
||||
* @param string $text The new text to translate.
|
||||
* @param string $context The context of the text.
|
||||
* @return string|void The translated text.
|
||||
*
|
||||
* @deprecated 3.0.0 Use `_x()` instead.
|
||||
*/
|
||||
protected function translate( $text, $context = '' ) {
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads `$this->bulk_actions` and returns an array that WP_List_Table understands. It
|
||||
* also validates that the bulk method handler exists. It throws an exception because
|
||||
* this is a library meant for developers and missing a bulk method is a development-time error.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws RuntimeException Throws RuntimeException when the bulk action does not have a callback method.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current request has a bulk action. If that is the case it will validate and will
|
||||
* execute the bulk method handler. Regardless if the action is valid or not it will redirect to
|
||||
* the previous page removing the current arguments that makes this request a bulk action.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Default code for deleting entries.
|
||||
* validated already by process_bulk_action()
|
||||
*
|
||||
* @param array $ids ids of the items to delete.
|
||||
* @param string $ids_sql the sql for the ids.
|
||||
* @return void
|
||||
*/
|
||||
protected function bulk_delete( array $ids, $ids_sql ) {
|
||||
$store = ActionScheduler::store();
|
||||
foreach ( $ids as $action_id ) {
|
||||
$store->delete( $action_id );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the _column_headers property which is used by WP_Table_List at rendering.
|
||||
* It merges the columns and the sortable columns.
|
||||
*/
|
||||
protected function prepare_column_headers() {
|
||||
$this->_column_headers = array(
|
||||
$this->get_columns(),
|
||||
get_hidden_columns( $this->screen ),
|
||||
$this->get_sortable_columns(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads $this->sort_by and returns the columns name in a format that WP_Table_List
|
||||
* expects
|
||||
*/
|
||||
public function get_sortable_columns() {
|
||||
$sort_by = array();
|
||||
foreach ( $this->sort_by as $column ) {
|
||||
$sort_by[ $column ] = array( $column, true );
|
||||
}
|
||||
return $sort_by;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the columns names for rendering. It adds a checkbox for selecting everything
|
||||
* as the first column
|
||||
*/
|
||||
public function get_columns() {
|
||||
$columns = array_merge(
|
||||
array( 'cb' => '<input type="checkbox" />' ),
|
||||
$this->columns
|
||||
);
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get prepared LIMIT clause for items query
|
||||
*
|
||||
* @global wpdb $wpdb
|
||||
*
|
||||
* @return string Prepared LIMIT clause for items query.
|
||||
*/
|
||||
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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of items to offset/skip for this current view.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get prepared OFFSET clause for items query
|
||||
*
|
||||
* @global wpdb $wpdb
|
||||
*
|
||||
* @return string Prepared OFFSET clause for items query.
|
||||
*/
|
||||
protected function get_items_query_offset() {
|
||||
global $wpdb;
|
||||
|
||||
return $wpdb->prepare( 'OFFSET %d', $this->get_items_offset() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the ORDER BY sql statement. It uses `$this->sort_by` to know which
|
||||
* columns are sortable. This requests validates the orderby $_GET parameter is a valid
|
||||
* column and sortable. It will also use order (ASC|DESC) using DESC by default.
|
||||
*/
|
||||
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}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the sortable column specified for this request to order the results by, if any.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the sortable column order specified for this request.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the status filter for this request, if any.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_request_status() {
|
||||
$status = ( ! empty( $_GET['status'] ) ) ? sanitize_text_field( wp_unslash( $_GET['status'] ) ) : ''; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the search filter for this request, if any.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process and return the columns name. This is meant for using with SQL, this means it
|
||||
* always includes the primary key.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_table_columns() {
|
||||
$columns = array_keys( $this->columns );
|
||||
if ( ! in_array( $this->ID, $columns, true ) ) {
|
||||
$columns[] = $this->ID;
|
||||
}
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current request is doing a "full text" search. If that is the case
|
||||
* prepares the SQL to search texts using LIKE.
|
||||
*
|
||||
* If the current request does not have any search or if this list table does not support
|
||||
* that feature it will return an empty string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the SQL to filter rows by the options defined at `$this->filter_by`. Before trusting
|
||||
* any data sent by the user it validates that it is a valid option.
|
||||
*/
|
||||
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 );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the data to feed WP_Table_List.
|
||||
*
|
||||
* This has the core for selecting, sorting and filting data. To keep the code simple
|
||||
* its logic is split among many methods (get_items_query_*).
|
||||
*
|
||||
* Beside populating the items this function will also count all the records that matches
|
||||
* the filtering criteria and will do fill the pagination variables.
|
||||
*/
|
||||
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 ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the table.
|
||||
*
|
||||
* @param string $which The name of the table.
|
||||
*/
|
||||
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>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the data for displaying. It will attempt to unserialize (There is a chance that some columns
|
||||
* are serialized). This can be override in child classes for futher data transformation.
|
||||
*
|
||||
* @param array $items Items array.
|
||||
*/
|
||||
protected function set_items( array $items ) {
|
||||
$this->items = array();
|
||||
foreach ( $items as $item ) {
|
||||
$this->items[ $item[ $this->ID ] ] = array_map( 'maybe_unserialize', $item );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the checkbox for each row, this is the first column and it is named ID regardless
|
||||
* of how the primary key is named (to keep the code simpler). The bulk actions will do the proper
|
||||
* name transformation though using `$this->ID`.
|
||||
*
|
||||
* @param array $row The row to render.
|
||||
*/
|
||||
public function column_cb( $row ) {
|
||||
return '<input name="ID[]" type="checkbox" value="' . esc_attr( $row[ $this->ID ] ) . '" />';
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the row-actions.
|
||||
*
|
||||
* This method renders the action menu, it reads the definition from the $row_actions property,
|
||||
* and it checks that the row action method exists before rendering it.
|
||||
*
|
||||
* @param array $row Row to be rendered.
|
||||
* @param string $column_name Column name.
|
||||
* @return string
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the bulk actions.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Default column formatting, it will escape everythig for security.
|
||||
*
|
||||
* @param array $item The item array.
|
||||
* @param string $column_name Column name to display.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the table heading and search query, if any
|
||||
*/
|
||||
protected function display_header() {
|
||||
echo '<h1 class="wp-heading-inline">' . esc_attr( $this->table_header ) . '</h1>';
|
||||
if ( $this->get_request_search_query() ) {
|
||||
/* translators: %s: 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">';
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the table heading and search query, if any
|
||||
*/
|
||||
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>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints the available statuses so the user can click to filter.
|
||||
*/
|
||||
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'] = _x( 'All', 'status labels', 'action-scheduler' );
|
||||
$status_labels['past-due'] = _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>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the table list, we override the original class to render the table inside a form
|
||||
* and to render any needed HTML (like the search box). By doing so the callee of a function can simple
|
||||
* forget about any extra HTML.
|
||||
*/
|
||||
protected function display_table() {
|
||||
echo '<form id="' . esc_attr( $this->_args['plural'] ) . '-filter" method="get">';
|
||||
foreach ( $_GET as $key => $value ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( '_' === $key[0] || 'paged' === $key || 'ID' === $key ) {
|
||||
continue;
|
||||
}
|
||||
echo '<input type="hidden" name="' . esc_attr( $key ) . '" value="' . esc_attr( $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>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Process any pending actions.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the list table page, including header, notices, status filters and table.
|
||||
*/
|
||||
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>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the text to display in the search box on the list table.
|
||||
*/
|
||||
protected function get_search_box_placeholder() {
|
||||
return esc_html__( 'Search', 'action-scheduler' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the screen per_page option name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_per_page_option_name() {
|
||||
return $this->package . '_items_per_page';
|
||||
}
|
||||
}
|
372
dependencies/woocommerce/action-scheduler/classes/abstracts/ActionScheduler_Abstract_QueueRunner.php
vendored
Normal file
372
dependencies/woocommerce/action-scheduler/classes/abstracts/ActionScheduler_Abstract_QueueRunner.php
vendored
Normal file
@ -0,0 +1,372 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Abstract class with common Queue Cleaner functionality.
|
||||
*/
|
||||
abstract class ActionScheduler_Abstract_QueueRunner extends ActionScheduler_Abstract_QueueRunner_Deprecated {
|
||||
|
||||
/** @var ActionScheduler_QueueCleaner */
|
||||
protected $cleaner;
|
||||
|
||||
/** @var ActionScheduler_FatalErrorMonitor */
|
||||
protected $monitor;
|
||||
|
||||
/** @var ActionScheduler_Store */
|
||||
protected $store;
|
||||
|
||||
/**
|
||||
* The created time.
|
||||
*
|
||||
* Represents when the queue runner was constructed and used when calculating how long a PHP request has been running.
|
||||
* For this reason it should be as close as possible to the PHP request start time.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $created_time;
|
||||
|
||||
/**
|
||||
* ActionScheduler_Abstract_QueueRunner constructor.
|
||||
*
|
||||
* @param ActionScheduler_Store $store
|
||||
* @param ActionScheduler_FatalErrorMonitor $monitor
|
||||
* @param ActionScheduler_QueueCleaner $cleaner
|
||||
*/
|
||||
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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an individual action.
|
||||
*
|
||||
* @param int $action_id The action ID to process.
|
||||
* @param string $context Optional identifer for the context in which this action is being processed, e.g. 'WP CLI' or 'WP Cron'
|
||||
* Generally, this should be capitalised and not localised as it's a proper noun.
|
||||
*/
|
||||
public function process_action( $action_id, $context = '' ) {
|
||||
// Temporarily override the error handler while we process the current action.
|
||||
set_error_handler(
|
||||
/**
|
||||
* Temporary error handler which can catch errors and convert them into exceptions. This faciliates more
|
||||
* robust error handling across all supported PHP versions.
|
||||
*
|
||||
* @throws Exception
|
||||
*
|
||||
* @param int $type Error level expressed as an integer.
|
||||
* @param string $message Error message.
|
||||
*/
|
||||
function ( $type, $message ) {
|
||||
throw new Exception( $message );
|
||||
},
|
||||
E_USER_ERROR | E_RECOVERABLE_ERROR
|
||||
);
|
||||
|
||||
/*
|
||||
* The nested try/catch structure is required because we potentially need to convert thrown errors into
|
||||
* exceptions (and an exception thrown from a catch block cannot be caught by a later catch block in the *same*
|
||||
* structure).
|
||||
*/
|
||||
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->getPrevious() );
|
||||
}
|
||||
} 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 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks actions as either having failed execution or failed validation, as appropriate.
|
||||
*
|
||||
* @param int $action_id Action ID.
|
||||
* @param Exception $e Exception instance.
|
||||
* @param string $context Execution context.
|
||||
* @param bool $valid_action If the action is valid.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function handle_action_error( $action_id, $e, $context, $valid_action ) {
|
||||
if ( $valid_action ) {
|
||||
$this->store->mark_failure( $action_id );
|
||||
/**
|
||||
* Runs when action execution fails.
|
||||
*
|
||||
* @param int $action_id Action ID.
|
||||
* @param Exception $e Exception instance.
|
||||
* @param string $context Execution context.
|
||||
*/
|
||||
do_action( 'action_scheduler_failed_execution', $action_id, $e, $context );
|
||||
} else {
|
||||
/**
|
||||
* Runs when action validation fails.
|
||||
*
|
||||
* @param int $action_id Action ID.
|
||||
* @param Exception $e Exception instance.
|
||||
* @param string $context Execution context.
|
||||
*/
|
||||
do_action( 'action_scheduler_failed_validation', $action_id, $e, $context );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule the next instance of the action if necessary.
|
||||
*
|
||||
* @param ActionScheduler_Action $action
|
||||
* @param int $action_id
|
||||
*/
|
||||
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 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the specified recurring action has been consistently failing.
|
||||
*
|
||||
* @param ActionScheduler_Action $action The recurring action to be rescheduled.
|
||||
* @param int $action_id The ID of the recurring action.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function recurring_action_is_consistently_failing( ActionScheduler_Action $action, $action_id ) {
|
||||
/**
|
||||
* Controls the failure threshold for recurring actions.
|
||||
*
|
||||
* Before rescheduling a recurring action, we look at its status. If it failed, we then check if all of the most
|
||||
* recent actions (upto the threshold set by this filter) sharing the same hook have also failed: if they have,
|
||||
* that is considered consistent failure and a new instance of the action will not be scheduled.
|
||||
*
|
||||
* @param int $failure_threshold Number of actions of the same hook to examine for failure. Defaults to 5.
|
||||
*/
|
||||
$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 );
|
||||
|
||||
/**
|
||||
* If a recurring action is assessed as consistently failing, it will not be rescheduled. This hook provides a
|
||||
* way to observe and optionally override that assessment.
|
||||
*
|
||||
* @param bool $is_consistently_failing If the action is considered to be consistently failing.
|
||||
* @param ActionScheduler_Action $action The action being assessed.
|
||||
*/
|
||||
return (bool) apply_filters(
|
||||
'action_scheduler_recurring_action_is_consistently_failing',
|
||||
$first_action_id_with_the_same_hook === $first_failing_action_id,
|
||||
$action
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the queue cleaner.
|
||||
*
|
||||
* @author Jeremy Pry
|
||||
*/
|
||||
protected function run_cleanup() {
|
||||
$this->cleaner->clean( 10 * $this->get_time_limit() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of concurrent batches a runner allows.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_allowed_concurrent_batches() {
|
||||
return apply_filters( 'action_scheduler_queue_runner_concurrent_batches', 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the number of allowed concurrent batches is met or exceeded.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has_maximum_concurrent_batches() {
|
||||
return $this->store->get_claim_count() >= $this->get_allowed_concurrent_batches();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the maximum number of seconds a batch can run for.
|
||||
*
|
||||
* @return int The number of seconds.
|
||||
*/
|
||||
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 ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of seconds the process has been running.
|
||||
*
|
||||
* @return int The number of seconds.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the host's max execution time is (likely) to be exceeded if processing more actions.
|
||||
*
|
||||
* @param int $processed_actions The number of actions processed so far - used to determine the likelihood of exceeding the time limit if processing another action
|
||||
* @return bool
|
||||
*/
|
||||
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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get memory limit
|
||||
*
|
||||
* Based on WP_Background_Process::get_memory_limit()
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Memory exceeded
|
||||
*
|
||||
* Ensures the batch process never exceeds 90% of the maximum WordPress memory.
|
||||
*
|
||||
* Based on WP_Background_Process::memory_exceeded()
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* See if the batch limits have been exceeded, which is when memory usage is almost at
|
||||
* the maximum limit, or the time to process more actions will exceed the max time limit.
|
||||
*
|
||||
* Based on WC_Background_Process::batch_limits_exceeded()
|
||||
*
|
||||
* @param int $processed_actions The number of actions processed so far - used to determine the likelihood of exceeding the time limit if processing another action
|
||||
* @return bool
|
||||
*/
|
||||
protected function batch_limits_exceeded( $processed_actions ) {
|
||||
return $this->memory_exceeded() || $this->time_likely_to_be_exceeded( $processed_actions );
|
||||
}
|
||||
|
||||
/**
|
||||
* Process actions in the queue.
|
||||
*
|
||||
* @author Jeremy Pry
|
||||
* @param string $context Optional identifer for the context in which this action is being processed, e.g. 'WP CLI' or 'WP Cron'
|
||||
* Generally, this should be capitalised and not localised as it's a proper noun.
|
||||
* @return int The number of actions processed.
|
||||
*/
|
||||
abstract public function run( $context = '' );
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class ActionScheduler_Abstract_RecurringSchedule
|
||||
*/
|
||||
abstract class ActionScheduler_Abstract_RecurringSchedule extends ActionScheduler_Abstract_Schedule {
|
||||
|
||||
/**
|
||||
* The date & time the first instance of this schedule was setup to run (which may not be this instance).
|
||||
*
|
||||
* Schedule objects are attached to an action object. Each schedule stores the run date for that
|
||||
* object as the start date - @see $this->start - and logic to calculate the next run date after
|
||||
* that - @see $this->calculate_next(). The $first_date property also keeps a record of when the very
|
||||
* first instance of this chain of schedules ran.
|
||||
*
|
||||
* @var DateTime
|
||||
*/
|
||||
private $first_date = NULL;
|
||||
|
||||
/**
|
||||
* Timestamp equivalent of @see $this->first_date
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $first_timestamp = NULL;
|
||||
|
||||
/**
|
||||
* The recurrance between each time an action is run using this schedule.
|
||||
* Used to calculate the start date & time. Can be a number of seconds, in the
|
||||
* case of ActionScheduler_IntervalSchedule, or a cron expression, as in the
|
||||
* case of ActionScheduler_CronSchedule. Or something else.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
protected $recurrence;
|
||||
|
||||
/**
|
||||
* @param DateTime $date The date & time to run the action.
|
||||
* @param mixed $recurrence The data used to determine the schedule's recurrance.
|
||||
* @param DateTime|null $first (Optional) The date & time the first instance of this interval schedule ran. Default null, meaning this is the first instance.
|
||||
*/
|
||||
public function __construct( DateTime $date, $recurrence, DateTime $first = null ) {
|
||||
parent::__construct( $date );
|
||||
$this->first_date = empty( $first ) ? $date : $first;
|
||||
$this->recurrence = $recurrence;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function is_recurring() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the date & time of the first schedule in this recurring series.
|
||||
*
|
||||
* @return DateTime|null
|
||||
*/
|
||||
public function get_first_date() {
|
||||
return clone $this->first_date;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function get_recurrence() {
|
||||
return $this->recurrence;
|
||||
}
|
||||
|
||||
/**
|
||||
* For PHP 5.2 compat, since DateTime objects can't be serialized
|
||||
* @return array
|
||||
*/
|
||||
public function __sleep() {
|
||||
$sleep_params = parent::__sleep();
|
||||
$this->first_timestamp = $this->first_date->getTimestamp();
|
||||
return array_merge( $sleep_params, array(
|
||||
'first_timestamp',
|
||||
'recurrence'
|
||||
) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Unserialize recurring schedules serialized/stored prior to AS 3.0.0
|
||||
*
|
||||
* Prior to Action Scheduler 3.0.0, schedules used different property names to refer
|
||||
* to equivalent data. For example, ActionScheduler_IntervalSchedule::start_timestamp
|
||||
* was the same as ActionScheduler_SimpleSchedule::timestamp. This was addressed in
|
||||
* Action Scheduler 3.0.0, where properties and property names were aligned for better
|
||||
* inheritance. To maintain backward compatibility with scheduled serialized and stored
|
||||
* prior to 3.0, we need to correctly map the old property names.
|
||||
*/
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class ActionScheduler_Abstract_Schedule
|
||||
*/
|
||||
abstract class ActionScheduler_Abstract_Schedule extends ActionScheduler_Schedule_Deprecated {
|
||||
|
||||
/**
|
||||
* The date & time the schedule is set to run.
|
||||
*
|
||||
* @var DateTime
|
||||
*/
|
||||
private $scheduled_date = NULL;
|
||||
|
||||
/**
|
||||
* Timestamp equivalent of @see $this->scheduled_date
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $scheduled_timestamp = NULL;
|
||||
|
||||
/**
|
||||
* @param DateTime $date The date & time to run the action.
|
||||
*/
|
||||
public function __construct( DateTime $date ) {
|
||||
$this->scheduled_date = $date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a schedule should recur.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
abstract public function is_recurring();
|
||||
|
||||
/**
|
||||
* Calculate when the next instance of this schedule would run based on a given date & time.
|
||||
*
|
||||
* @param DateTime $after
|
||||
* @return DateTime
|
||||
*/
|
||||
abstract protected function calculate_next( DateTime $after );
|
||||
|
||||
/**
|
||||
* Get the next date & time when this schedule should run after a given date & time.
|
||||
*
|
||||
* @param DateTime $after
|
||||
* @return DateTime|null
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the date & time the schedule is set to run.
|
||||
*
|
||||
* @return DateTime|null
|
||||
*/
|
||||
public function get_date() {
|
||||
return $this->scheduled_date;
|
||||
}
|
||||
|
||||
/**
|
||||
* For PHP 5.2 compat, since DateTime objects can't be serialized
|
||||
* @return array
|
||||
*/
|
||||
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 );
|
||||
}
|
||||
}
|
177
dependencies/woocommerce/action-scheduler/classes/abstracts/ActionScheduler_Abstract_Schema.php
vendored
Normal file
177
dependencies/woocommerce/action-scheduler/classes/abstracts/ActionScheduler_Abstract_Schema.php
vendored
Normal file
@ -0,0 +1,177 @@
|
||||
<?php
|
||||
|
||||
|
||||
/**
|
||||
* Class ActionScheduler_Abstract_Schema
|
||||
*
|
||||
* @package Action_Scheduler
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* Utility class for creating/updating custom tables
|
||||
*/
|
||||
abstract class ActionScheduler_Abstract_Schema {
|
||||
|
||||
/**
|
||||
* @var int Increment this value in derived class to trigger a schema update.
|
||||
*/
|
||||
protected $schema_version = 1;
|
||||
|
||||
/**
|
||||
* @var string Schema version stored in database.
|
||||
*/
|
||||
protected $db_version;
|
||||
|
||||
/**
|
||||
* @var array Names of tables that will be registered by this class.
|
||||
*/
|
||||
protected $tables = array();
|
||||
|
||||
/**
|
||||
* Can optionally be used by concrete classes to carry out additional initialization work
|
||||
* as needed.
|
||||
*/
|
||||
public function init() {}
|
||||
|
||||
/**
|
||||
* Register tables with WordPress, and create them if needed.
|
||||
*
|
||||
* @param bool $force_update Optional. Default false. Use true to always run the schema update.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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 ) {
|
||||
/**
|
||||
* Allow custom processing before updating a table schema.
|
||||
*
|
||||
* @param string $table Name of table being updated.
|
||||
* @param string $db_version Existing version of the table being updated.
|
||||
*/
|
||||
do_action( 'action_scheduler_before_schema_update', $table, $this->db_version );
|
||||
$this->update_table( $table );
|
||||
}
|
||||
$this->mark_schema_update_complete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $table The name of the table
|
||||
*
|
||||
* @return string The CREATE TABLE statement, suitable for passing to dbDelta
|
||||
*/
|
||||
abstract protected function get_table_definition( $table );
|
||||
|
||||
/**
|
||||
* Determine if the database schema is out of date
|
||||
* by comparing the integer found in $this->schema_version
|
||||
* with the option set in the WordPress options table
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
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, '<' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the option in WordPress to indicate that
|
||||
* our schema is now up to date
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the schema for the given table
|
||||
*
|
||||
* @param string $table The name of the table to update
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $table
|
||||
*
|
||||
* @return string The full name of the table, including the
|
||||
* table prefix for the current blog
|
||||
*/
|
||||
protected function get_full_table_name( $table ) {
|
||||
return $GLOBALS['wpdb']->prefix . $table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms that all of the tables registered by this schema class have been created.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
64
dependencies/woocommerce/action-scheduler/classes/abstracts/ActionScheduler_Lock.php
vendored
Normal file
64
dependencies/woocommerce/action-scheduler/classes/abstracts/ActionScheduler_Lock.php
vendored
Normal file
@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Abstract class for setting a basic lock to throttle some action.
|
||||
*
|
||||
* Class ActionScheduler_Lock
|
||||
*/
|
||||
abstract class ActionScheduler_Lock {
|
||||
|
||||
/** @var ActionScheduler_Lock */
|
||||
private static $locker = NULL;
|
||||
|
||||
/** @var int */
|
||||
protected static $lock_duration = MINUTE_IN_SECONDS;
|
||||
|
||||
/**
|
||||
* Check if a lock is set for a given lock type.
|
||||
*
|
||||
* @param string $lock_type A string to identify different lock types.
|
||||
* @return bool
|
||||
*/
|
||||
public function is_locked( $lock_type ) {
|
||||
return ( $this->get_expiration( $lock_type ) >= time() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a lock.
|
||||
*
|
||||
* To prevent race conditions, implementations should avoid setting the lock if the lock is already held.
|
||||
*
|
||||
* @param string $lock_type A string to identify different lock types.
|
||||
* @return bool
|
||||
*/
|
||||
abstract public function set( $lock_type );
|
||||
|
||||
/**
|
||||
* If a lock is set, return the timestamp it was set to expiry.
|
||||
*
|
||||
* @param string $lock_type A string to identify different lock types.
|
||||
* @return bool|int False if no lock is set, otherwise the timestamp for when the lock is set to expire.
|
||||
*/
|
||||
abstract public function get_expiration( $lock_type );
|
||||
|
||||
/**
|
||||
* Get the amount of time to set for a given lock. 60 seconds by default.
|
||||
*
|
||||
* @param string $lock_type A string to identify different lock types.
|
||||
* @return int
|
||||
*/
|
||||
protected function get_duration( $lock_type ) {
|
||||
return apply_filters( 'action_scheduler_lock_duration', self::$lock_duration, $lock_type );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ActionScheduler_Lock
|
||||
*/
|
||||
public static function instance() {
|
||||
if ( empty( self::$locker ) ) {
|
||||
$class = apply_filters( 'action_scheduler_lock_class', 'ActionScheduler_OptionLock' );
|
||||
self::$locker = new $class();
|
||||
}
|
||||
return self::$locker;
|
||||
}
|
||||
}
|
176
dependencies/woocommerce/action-scheduler/classes/abstracts/ActionScheduler_Logger.php
vendored
Normal file
176
dependencies/woocommerce/action-scheduler/classes/abstracts/ActionScheduler_Logger.php
vendored
Normal file
@ -0,0 +1,176 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class ActionScheduler_Logger
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
abstract class ActionScheduler_Logger {
|
||||
private static $logger = NULL;
|
||||
|
||||
/**
|
||||
* @return ActionScheduler_Logger
|
||||
*/
|
||||
public static function instance() {
|
||||
if ( empty(self::$logger) ) {
|
||||
$class = apply_filters('action_scheduler_logger_class', 'ActionScheduler_wpCommentLogger');
|
||||
self::$logger = new $class();
|
||||
}
|
||||
return self::$logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $action_id
|
||||
* @param string $message
|
||||
* @param DateTime $date
|
||||
*
|
||||
* @return string The log entry ID
|
||||
*/
|
||||
abstract public function log( $action_id, $message, DateTime $date = NULL );
|
||||
|
||||
/**
|
||||
* @param string $entry_id
|
||||
*
|
||||
* @return ActionScheduler_LogEntry
|
||||
*/
|
||||
abstract public function get_entry( $entry_id );
|
||||
|
||||
/**
|
||||
* @param string $action_id
|
||||
*
|
||||
* @return ActionScheduler_LogEntry[]
|
||||
*/
|
||||
abstract public function get_logs( $action_id );
|
||||
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
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 ) ) {
|
||||
/* translators: %s: 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 ) ) {
|
||||
/* translators: %s: 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 ) ) {
|
||||
/* translators: 1: context 2: exception message */
|
||||
$message = sprintf( __( 'action failed via %1$s: %2$s', 'action-scheduler' ), $context, $exception->getMessage() );
|
||||
} else {
|
||||
/* translators: %s: exception message */
|
||||
$message = sprintf( __( 'action failed: %s', 'action-scheduler' ), $exception->getMessage() );
|
||||
}
|
||||
$this->log( $action_id, $message );
|
||||
}
|
||||
|
||||
public function log_timed_out_action( $action_id, $timeout ) {
|
||||
/* translators: %s: amount of time */
|
||||
$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 ) ) {
|
||||
/* translators: 1: error message 2: filename 3: line */
|
||||
$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 ) ) {
|
||||
/* translators: %s: context */
|
||||
$message = sprintf( __( 'action ignored via %s', 'action-scheduler' ), $context );
|
||||
} else {
|
||||
$message = __( 'action ignored', 'action-scheduler' );
|
||||
}
|
||||
$this->log( $action_id, $message );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $action_id
|
||||
* @param Exception|NULL $exception The exception which occured when fetching the action. NULL by default for backward compatibility.
|
||||
*
|
||||
* @return ActionScheduler_LogEntry[]
|
||||
*/
|
||||
public function log_failed_fetch_action( $action_id, Exception $exception = NULL ) {
|
||||
|
||||
if ( ! is_null( $exception ) ) {
|
||||
/* translators: %s: exception message */
|
||||
$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 ) {
|
||||
/* translators: %s: exception message */
|
||||
$this->log( $action_id, sprintf( __( 'There was a failure scheduling the next instance of this action: %s', 'action-scheduler' ), $exception->getMessage() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk add cancel action log entries.
|
||||
*
|
||||
* Implemented here for backward compatibility. Should be implemented in parent loggers
|
||||
* for more performant bulk logging.
|
||||
*
|
||||
* @param array $action_ids List of action ID.
|
||||
*/
|
||||
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 );
|
||||
}
|
||||
}
|
||||
}
|
450
dependencies/woocommerce/action-scheduler/classes/abstracts/ActionScheduler_Store.php
vendored
Normal file
450
dependencies/woocommerce/action-scheduler/classes/abstracts/ActionScheduler_Store.php
vendored
Normal file
@ -0,0 +1,450 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class ActionScheduler_Store
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
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';
|
||||
|
||||
/** @var ActionScheduler_Store */
|
||||
private static $store = NULL;
|
||||
|
||||
/** @var int */
|
||||
protected static $max_args_length = 191;
|
||||
|
||||
/**
|
||||
* @param ActionScheduler_Action $action
|
||||
* @param DateTime $scheduled_date Optional Date of the first instance
|
||||
* to store. Otherwise uses the first date of the action's
|
||||
* schedule.
|
||||
*
|
||||
* @return int The action ID
|
||||
*/
|
||||
abstract public function save_action( ActionScheduler_Action $action, DateTime $scheduled_date = NULL );
|
||||
|
||||
/**
|
||||
* @param string $action_id
|
||||
*
|
||||
* @return ActionScheduler_Action
|
||||
*/
|
||||
abstract public function fetch_action( $action_id );
|
||||
|
||||
/**
|
||||
* Find an action.
|
||||
*
|
||||
* Note: the query ordering changes based on the passed 'status' value.
|
||||
*
|
||||
* @param string $hook Action hook.
|
||||
* @param array $params Parameters of the action to find.
|
||||
*
|
||||
* @return string|null ID of the next action matching the criteria or NULL if not found.
|
||||
*/
|
||||
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];
|
||||
}
|
||||
|
||||
/**
|
||||
* Query for action count or list of action IDs.
|
||||
*
|
||||
* @since 3.3.0 $query['status'] accepts array of statuses instead of a single status.
|
||||
*
|
||||
* @param array $query {
|
||||
* Query filtering options.
|
||||
*
|
||||
* @type string $hook The name of the actions. Optional.
|
||||
* @type string|array $status The status or statuses of the actions. Optional.
|
||||
* @type array $args The args array of the actions. Optional.
|
||||
* @type DateTime $date The scheduled date of the action. Used in UTC timezone. Optional.
|
||||
* @type string $date_compare Operator for selecting by $date param. Accepted values are '!=', '>', '>=', '<', '<=', '='. Defaults to '<='.
|
||||
* @type DateTime $modified The last modified date of the action. Used in UTC timezone. Optional.
|
||||
* @type string $modified_compare Operator for comparing $modified param. Accepted values are '!=', '>', '>=', '<', '<=', '='. Defaults to '<='.
|
||||
* @type string $group The group the action belongs to. Optional.
|
||||
* @type bool|int $claimed TRUE to find claimed actions, FALSE to find unclaimed actions, an int to find a specific claim ID. Optional.
|
||||
* @type int $per_page Number of results to return. Defaults to 5.
|
||||
* @type int $offset The query pagination offset. Defaults to 0.
|
||||
* @type int $orderby Accepted values are 'hook', 'group', 'modified', 'date' or 'none'. Defaults to 'date'.
|
||||
* @type string $order Accepted values are 'ASC' or 'DESC'. Defaults to 'ASC'.
|
||||
* }
|
||||
* @param string $query_type Whether to select or count the results. Default, select.
|
||||
*
|
||||
* @return string|array|null The IDs of actions matching the query. Null on failure.
|
||||
*/
|
||||
abstract public function query_actions( $query = array(), $query_type = 'select' );
|
||||
|
||||
/**
|
||||
* Run query to get a single action ID.
|
||||
*
|
||||
* @since 3.3.0
|
||||
*
|
||||
* @see ActionScheduler_Store::query_actions for $query arg usage but 'per_page' and 'offset' can't be used.
|
||||
*
|
||||
* @param array $query Query parameters.
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a count of all actions in the store, grouped by status
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
abstract public function action_counts();
|
||||
|
||||
/**
|
||||
* Get additional action counts.
|
||||
*
|
||||
* - add past-due actions
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows 3rd party code to add extra action counts (used in filters in the list table).
|
||||
*
|
||||
* @since 3.5.0
|
||||
* @param $extra_actions array Array with format action_count_identifier => action count.
|
||||
*/
|
||||
return apply_filters( 'action_scheduler_extra_action_counts', $extra_actions );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $action_id
|
||||
*/
|
||||
abstract public function cancel_action( $action_id );
|
||||
|
||||
/**
|
||||
* @param string $action_id
|
||||
*/
|
||||
abstract public function delete_action( $action_id );
|
||||
|
||||
/**
|
||||
* @param string $action_id
|
||||
*
|
||||
* @return DateTime The date the action is schedule to run, or the date that it ran.
|
||||
*/
|
||||
abstract public function get_date( $action_id );
|
||||
|
||||
|
||||
/**
|
||||
* @param int $max_actions
|
||||
* @param DateTime $before_date Claim only actions schedule before the given date. Defaults to now.
|
||||
* @param array $hooks Claim only actions with a hook or hooks.
|
||||
* @param string $group Claim only actions in the given group.
|
||||
*
|
||||
* @return ActionScheduler_ActionClaim
|
||||
*/
|
||||
abstract public function stake_claim( $max_actions = 10, DateTime $before_date = null, $hooks = array(), $group = '' );
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
abstract public function get_claim_count();
|
||||
|
||||
/**
|
||||
* @param ActionScheduler_ActionClaim $claim
|
||||
*/
|
||||
abstract public function release_claim( ActionScheduler_ActionClaim $claim );
|
||||
|
||||
/**
|
||||
* @param string $action_id
|
||||
*/
|
||||
abstract public function unclaim_action( $action_id );
|
||||
|
||||
/**
|
||||
* @param string $action_id
|
||||
*/
|
||||
abstract public function mark_failure( $action_id );
|
||||
|
||||
/**
|
||||
* @param string $action_id
|
||||
*/
|
||||
abstract public function log_execution( $action_id );
|
||||
|
||||
/**
|
||||
* @param string $action_id
|
||||
*/
|
||||
abstract public function mark_complete( $action_id );
|
||||
|
||||
/**
|
||||
* @param string $action_id
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function get_status( $action_id );
|
||||
|
||||
/**
|
||||
* @param string $action_id
|
||||
* @return mixed
|
||||
*/
|
||||
abstract public function get_claim_id( $action_id );
|
||||
|
||||
/**
|
||||
* @param string $claim_id
|
||||
* @return array
|
||||
*/
|
||||
abstract public function find_actions_by_claim_id( $claim_id );
|
||||
|
||||
/**
|
||||
* @param string $comparison_operator
|
||||
* @return string
|
||||
*/
|
||||
protected function validate_sql_comparator( $comparison_operator ) {
|
||||
if ( in_array( $comparison_operator, array('!=', '>', '>=', '<', '<=', '=') ) ) {
|
||||
return $comparison_operator;
|
||||
}
|
||||
return '=';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the time MySQL formated date/time string for an action's (next) scheduled date.
|
||||
*
|
||||
* @param ActionScheduler_Action $action
|
||||
* @param DateTime $scheduled_date (optional)
|
||||
* @return string
|
||||
*/
|
||||
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' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the time MySQL formated date/time string for an action's (next) scheduled date.
|
||||
*
|
||||
* @param ActionScheduler_Action $action
|
||||
* @param DateTime $scheduled_date (optional)
|
||||
* @return string
|
||||
*/
|
||||
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' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that we could decode action arguments.
|
||||
*
|
||||
* @param mixed $args The decoded arguments.
|
||||
* @param int $action_id The action ID.
|
||||
*
|
||||
* @throws ActionScheduler_InvalidActionException When the decoded arguments are invalid.
|
||||
*/
|
||||
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 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a ActionScheduler_Schedule object.
|
||||
*
|
||||
* @param mixed $schedule The unserialized ActionScheduler_Schedule object.
|
||||
* @param int $action_id The action ID.
|
||||
*
|
||||
* @throws ActionScheduler_InvalidActionException When the schedule is invalid.
|
||||
*/
|
||||
protected function validate_schedule( $schedule, $action_id ) {
|
||||
if ( empty( $schedule ) || ! is_a( $schedule, 'ActionScheduler_Schedule' ) ) {
|
||||
throw ActionScheduler_InvalidActionException::from_schedule( $action_id, $schedule );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* InnoDB indexes have a maximum size of 767 bytes by default, which is only 191 characters with utf8mb4.
|
||||
*
|
||||
* Previously, AS wasn't concerned about args length, as we used the (unindex) post_content column. However,
|
||||
* with custom tables, we use an indexed VARCHAR column instead.
|
||||
*
|
||||
* @param ActionScheduler_Action $action Action to be validated.
|
||||
* @throws InvalidArgumentException When json encoded args is too long.
|
||||
*/
|
||||
protected function validate_action( ActionScheduler_Action $action ) {
|
||||
if ( strlen( json_encode( $action->get_args() ) ) > static::$max_args_length ) {
|
||||
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 ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel pending actions by hook.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @param string $hook Hook name.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel pending actions by group.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @param string $group Group slug.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel a set of action IDs.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @param array $action_ids List of action IDs.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
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' ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there are any pending scheduled actions due to run.
|
||||
*
|
||||
* @param ActionScheduler_Action $action
|
||||
* @param DateTime $scheduled_date (optional)
|
||||
* @return string
|
||||
*/
|
||||
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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Callable initialization function optionally overridden in derived classes.
|
||||
*/
|
||||
public function init() {}
|
||||
|
||||
/**
|
||||
* Callable function to mark an action as migrated optionally overridden in derived classes.
|
||||
*/
|
||||
public function mark_migrated( $action_id ) {}
|
||||
|
||||
/**
|
||||
* @return ActionScheduler_Store
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
152
dependencies/woocommerce/action-scheduler/classes/abstracts/ActionScheduler_TimezoneHelper.php
vendored
Normal file
152
dependencies/woocommerce/action-scheduler/classes/abstracts/ActionScheduler_TimezoneHelper.php
vendored
Normal file
@ -0,0 +1,152 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class ActionScheduler_TimezoneHelper
|
||||
*/
|
||||
abstract class ActionScheduler_TimezoneHelper {
|
||||
private static $local_timezone = NULL;
|
||||
|
||||
/**
|
||||
* Set a DateTime's timezone to the WordPress site's timezone, or a UTC offset
|
||||
* if no timezone string is available.
|
||||
*
|
||||
* @since 2.1.0
|
||||
*
|
||||
* @param DateTime $date
|
||||
* @return ActionScheduler_DateTime
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to retrieve the timezone string for a site until a WP core method exists
|
||||
* (see https://core.trac.wordpress.org/ticket/24730).
|
||||
*
|
||||
* Adapted from wc_timezone_string() and https://secure.php.net/manual/en/function.timezone-name-from-abbr.php#89155.
|
||||
*
|
||||
* If no timezone string is set, and its not possible to match the UTC offset set for the site to a timezone
|
||||
* string, then an empty string will be returned, and the UTC offset should be used to set a DateTime's
|
||||
* timezone.
|
||||
*
|
||||
* @since 2.1.0
|
||||
* @return string PHP timezone string for the site or empty if no timezone string is available.
|
||||
*/
|
||||
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 ) {
|
||||
return $city['timezone_id'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No timezone string
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get timezone offset in seconds.
|
||||
*
|
||||
* @since 2.1.0
|
||||
* @return float
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 2.1.0
|
||||
*/
|
||||
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' );
|
||||
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;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user