Merge pull request woocommerce/woocommerce-admin#1291 from woocommerce/update/860-regenerate-reports-action-scheduler

Regenerate report tables using Action Scheduler
This commit is contained in:
Jeff Stieler 2019-01-16 10:31:32 -07:00 committed by GitHub
commit 219a36cb8e
9 changed files with 459 additions and 198 deletions

View File

@ -55,7 +55,7 @@ class WC_Admin_REST_System_Status_Tools_Controller extends WC_REST_System_Status
switch ( $tool ) {
case 'rebuild_stats':
WC_Admin_Reports_Orders_Stats_Data_Store::queue_order_stats_repopulate_database();
WC_Admin_Api_Init::regenerate_report_data();
$message = __( 'Rebuilding reports data in the background . . .', 'wc-admin' );
break;
default:

View File

@ -12,6 +12,38 @@ defined( 'ABSPATH' ) || exit;
*/
class WC_Admin_Api_Init {
/**
* Action hook for reducing a range of batches down to single actions.
*/
const QUEUE_BATCH_ACTION = 'wc-admin_queue_batches';
/**
* Action hook for queuing an action after another is complete.
*/
const QUEUE_DEPEDENT_ACTION = 'wc-admin_queue_dependent_action';
/**
* Action hook for processing a batch of customers.
*/
const CUSTOMERS_BATCH_ACTION = 'wc-admin_process_customers_batch';
/**
* Action hook for processing a batch of orders.
*/
const ORDERS_BATCH_ACTION = 'wc-admin_process_orders_batch';
/**
* Action hook for initializing the orders lookup batch creation.
*/
const ORDERS_LOOKUP_BATCH_INIT = 'wc-admin_orders_lookup_batch_init';
/**
* Queue instance.
*
* @var WC_Queue_Interface
*/
protected static $queue = null;
/**
* Boostrap REST API.
*/
@ -30,9 +62,38 @@ class WC_Admin_Api_Init {
// Initialize Orders data store class's static vars.
add_action( 'woocommerce_after_register_post_type', array( 'WC_Admin_Api_Init', 'orders_data_store_init' ), 20 );
// Initialize Customers Report data store sync hooks.
// Note: we need to hook into 'wp' before `wc_current_user_is_active`.
// Note: we need to hook in before `wc_current_user_is_active`.
// See: https://github.com/woocommerce/woocommerce/blob/942615101ba00c939c107c3a4820c3d466864872/includes/wc-user-functions.php#L749.
add_action( 'wp', array( 'WC_Admin_Api_Init', 'customers_report_data_store_init' ), 9 );
add_action( 'wp_loaded', array( 'WC_Admin_Api_Init', 'customers_report_data_store_init' ) );
// Initialize scheduled action handlers.
add_action( self::QUEUE_BATCH_ACTION, array( __CLASS__, 'queue_batches' ), 10, 3 );
add_action( self::QUEUE_DEPEDENT_ACTION, array( __CLASS__, 'queue_dependent_action' ), 10, 2 );
add_action( self::CUSTOMERS_BATCH_ACTION, array( __CLASS__, 'customer_lookup_process_batch' ) );
add_action( self::ORDERS_BATCH_ACTION, array( __CLASS__, 'orders_lookup_process_batch' ) );
add_action( self::ORDERS_LOOKUP_BATCH_INIT, array( __CLASS__, 'orders_lookup_batch_init' ) );
}
/**
* Get queue instance.
*
* @return WC_Queue_Interface
*/
public static function queue() {
if ( is_null( self::$queue ) ) {
self::$queue = WC()->queue();
}
return self::$queue;
}
/**
* Set queue instance.
*
* @param WC_Queue_Interface $queue Queue instance.
*/
public static function set_queue( $queue ) {
self::$queue = $queue;
}
/**
@ -280,9 +341,9 @@ class WC_Admin_Api_Init {
public static function regenerate_report_data() {
// Add registered customers to the lookup table before updating order stats
// so that the orders can be associated with the `customer_id` column.
self::customer_lookup_store_init();
WC_Admin_Reports_Orders_Stats_Data_Store::queue_order_stats_repopulate_database();
self::order_product_lookup_store_init();
self::customer_lookup_batch_init();
// Queue orders lookup to occur after customers lookup generation is done.
self::queue_dependent_action( self::ORDERS_LOOKUP_BATCH_INIT, self::CUSTOMERS_BATCH_ACTION );
}
/**
@ -316,39 +377,54 @@ class WC_Admin_Api_Init {
}
/**
* Init orders product lookup store.
*
* @param WC_Background_Updater|null $updater Updater instance.
* @return bool
* Init order/product lookup tables update (in batches).
*/
public static function order_product_lookup_store_init( $updater = null ) {
// TODO: this needs to be updated a bit, as it no longer runs as a part of WC_Install, there is no bg updater.
global $wpdb;
public static function orders_lookup_batch_init() {
$batch_size = self::get_batch_size( self::ORDERS_BATCH_ACTION );
$order_query = new WC_Order_Query(
array(
'return' => 'ids',
'limit' => 1,
'paginate' => true,
)
);
$result = $order_query->get_orders();
$orders = get_transient( 'wc_update_350_all_orders' );
if ( false === $orders ) {
$orders = wc_get_orders(
array(
'limit' => -1,
'return' => 'ids',
)
);
set_transient( 'wc_update_350_all_orders', $orders, DAY_IN_SECONDS );
if ( 0 === $result->total ) {
return;
}
// Process orders until close to running out of memory timeouts on large sites then requeue.
foreach ( $orders as $order_id ) {
$num_batches = ceil( $result->total / $batch_size );
self::queue_batches( 1, $num_batches, self::ORDERS_BATCH_ACTION );
}
/**
* Process a batch of orders to update (stats and products).
*
* @param int $batch_number Batch number to process (essentially a query page number).
* @return void
*/
public static function orders_lookup_process_batch( $batch_number ) {
$batch_size = self::get_batch_size( self::ORDERS_BATCH_ACTION );
$order_query = new WC_Order_Query(
array(
'return' => 'ids',
'limit' => $batch_size,
'page' => $batch_number,
'orderby' => 'ID',
'order' => 'ASC',
)
);
$order_ids = $order_query->get_orders();
foreach ( $order_ids as $order_id ) {
// TODO: schedule single order update if this fails?
WC_Admin_Reports_Orders_Stats_Data_Store::sync_order( $order_id );
WC_Admin_Reports_Products_Data_Store::sync_order_products( $order_id );
// Pop the order ID from the array for updating the transient later should we near memory exhaustion.
unset( $orders[ $order_id ] );
if ( $updater instanceof WC_Background_Updater && $updater->is_memory_exceeded() ) {
// Update the transient for the next run to avoid processing the same orders again.
set_transient( 'wc_update_350_all_orders', $orders, DAY_IN_SECONDS );
return true;
}
WC_Admin_Reports_Coupons_Data_Store::sync_order_coupons( $order_id );
WC_Admin_Reports_Taxes_Data_Store::sync_order_taxes( $order_id );
}
return true;
}
/**
@ -359,49 +435,144 @@ class WC_Admin_Api_Init {
}
/**
* Init customer lookup store.
* Returns the batch size for regenerating reports.
* Note: can differ per batch action.
*
* @param WC_Background_Updater|null $updater Updater instance.
* @return bool
* @param string $action Single batch action name.
* @return int Batch size.
*/
public static function customer_lookup_store_init( $updater = null ) {
// TODO: this needs to be updated a bit, as it no longer runs as a part of WC_Install, there is no bg updater.
global $wpdb;
public static function get_batch_size( $action ) {
$batch_sizes = array(
self::QUEUE_BATCH_ACTION => 100,
self::CUSTOMERS_BATCH_ACTION => 25,
self::ORDERS_BATCH_ACTION => 10,
);
$batch_size = isset( $batch_sizes[ $action ] ) ? $batch_sizes[ $action ] : 25;
// Backfill customer lookup table with registered customers.
$customer_ids = get_transient( 'wc_update_350_all_customers' );
/**
* Filter the batch size for regenerating a report table.
*
* @param int $batch_size Batch size.
* @param string $action Batch action name.
*/
return apply_filters( 'wc_admin_report_regenerate_batch_size', $batch_size, $action );
}
if ( false === $customer_ids ) {
$customer_query = new WP_User_Query(
array(
'fields' => 'ID',
'role' => 'customer',
'number' => -1,
)
/**
* Queue a large number of batch jobs, respecting the batch size limit.
* Reduces a range of batches down to "single batch" jobs.
*
* @param int $range_start Starting batch number.
* @param int $range_end Ending batch number.
* @param string $single_batch_action Action to schedule for a single batch.
* @return void
*/
public static function queue_batches( $range_start, $range_end, $single_batch_action ) {
$batch_size = self::get_batch_size( self::QUEUE_BATCH_ACTION );
$range_size = 1 + ( $range_end - $range_start );
$action_timestamp = time() + 5;
if ( $range_size > $batch_size ) {
// If the current batch range is larger than a single batch,
// split the range into $queue_batch_size chunks.
$chunk_size = ceil( $range_size / $batch_size );
for ( $i = 0; $i < $batch_size; $i++ ) {
$batch_start = $range_start + ( $i * $chunk_size );
$batch_end = min( $range_end, $range_start + ( $chunk_size * ( $i + 1 ) ) - 1 );
self::queue()->schedule_single(
$action_timestamp,
self::QUEUE_BATCH_ACTION,
array( $batch_start, $batch_end, $single_batch_action )
);
}
} else {
// Otherwise, queue the single batches.
for ( $i = $range_start; $i <= $range_end; $i++ ) {
self::queue()->schedule_single( $action_timestamp, $single_batch_action, array( $i ) );
}
}
}
/**
* Queue an action to run after another.
*
* @param string $action Action to run after prerequisite.
* @param string $prerequisite_action Prerequisite action.
*/
public static function queue_dependent_action( $action, $prerequisite_action ) {
$blocking_jobs = self::queue()->search(
array(
'status' => 'pending',
'orderby' => 'date',
'order' => 'DESC',
'per_page' => 1,
'claimed' => false,
'search' => $prerequisite_action, // search is used instead of hook to find queued batch creation.
)
);
if ( $blocking_jobs ) {
$blocking_job = current( $blocking_jobs );
$after_blocking_job = $blocking_job->get_schedule()->next()->getTimestamp() + 5;
self::queue()->schedule_single(
$after_blocking_job,
self::QUEUE_DEPEDENT_ACTION,
array( $action, $prerequisite_action )
);
} else {
self::queue()->schedule_single( time() + 5, $action );
}
}
$customer_ids = $customer_query->get_results();
/**
* Init customer lookup table update (in batches).
*/
public static function customer_lookup_batch_init() {
$batch_size = self::get_batch_size( self::CUSTOMERS_BATCH_ACTION );
$customer_query = new WP_User_Query(
array(
'fields' => 'ID',
'number' => 1,
)
);
$total_customers = $customer_query->get_total();
set_transient( 'wc_update_350_all_customers', $customer_ids, DAY_IN_SECONDS );
if ( 0 === $total_customers ) {
return;
}
// Process customers until close to running out of memory timeouts on large sites then requeue.
$num_batches = ceil( $total_customers / $batch_size );
self::queue_batches( 1, $num_batches, self::CUSTOMERS_BATCH_ACTION );
}
/**
* Process a batch of customers to update.
*
* @param int $batch_number Batch number to process (essentially a query page number).
* @return void
*/
public static function customer_lookup_process_batch( $batch_number ) {
$batch_size = self::get_batch_size( self::CUSTOMERS_BATCH_ACTION );
$customer_query = new WP_User_Query(
array(
'fields' => 'ID',
'orderby' => 'ID',
'order' => 'ASC',
'number' => $batch_size,
'paged' => $batch_number,
)
);
$customer_ids = $customer_query->get_results();
foreach ( $customer_ids as $customer_id ) {
$result = WC_Admin_Reports_Customers_Data_Store::update_registered_customer( $customer_id );
if ( $result ) {
// Pop the customer ID from the array for updating the transient later should we near memory exhaustion.
unset( $customer_ids[ $customer_id ] );
}
if ( $updater instanceof WC_Background_Updater && $updater->is_memory_exceeded() ) {
// Update the transient for the next run to avoid processing the same orders again.
set_transient( 'wc_update_350_all_customers', $customer_ids, DAY_IN_SECONDS );
return true;
}
// TODO: schedule single customer update if this fails?
WC_Admin_Reports_Customers_Data_Store::update_registered_customer( $customer_id );
}
return true;
}
/**
@ -583,8 +754,7 @@ class WC_Admin_Api_Init {
self::create_db_tables();
// Initialize report tables.
add_action( 'woocommerce_after_register_post_type', array( 'WC_Admin_Api_Init', 'order_product_lookup_store_init' ), 20 );
add_action( 'woocommerce_after_register_post_type', array( 'WC_Admin_Api_Init', 'customer_lookup_store_init' ), 20 );
add_action( 'woocommerce_after_register_post_type', array( __CLASS__, 'regenerate_report_data' ), 20 );
}
}

View File

@ -1,77 +0,0 @@
<?php
/**
* Order stats background process.
*
* @package WooCommerce Admin/Classes
*/
defined( 'ABSPATH' ) || exit;
if ( ! class_exists( 'WC_Background_Process', false ) ) {
include_once WC_ABSPATH . '/includes/abstracts/class-wc-background-process.php';
}
/**
* WC_Admin_Order_Stats_Background_Process class.
*
* @todo use Action Scheduler instead of this.
*/
class WC_Admin_Order_Stats_Background_Process extends WC_Background_Process {
/**
* Initiate new background process.
*/
public function __construct() {
// Uses unique prefix per blog so each blog has separate queue.
$this->prefix = 'wp_' . get_current_blog_id();
$this->action = 'wc_order_stats';
parent::__construct();
}
/**
* Push to queue without scheduling duplicate recalculation events.
* Overrides WC_Background_Process::push_to_queue.
*
* @param integer $data Timestamp of hour to generate stats.
*/
public function push_to_queue( $data ) {
$data = absint( $data );
if ( ! in_array( $data, $this->data, true ) ) {
$this->data[] = $data;
}
return $this;
}
/**
* Dispatch but only if there is data to update.
* Overrides WC_Background_Process::dispatch.
*/
public function dispatch() {
if ( ! $this->data ) {
return false;
}
return parent::dispatch();
}
/**
* Code to execute for each item in the queue
*
* @param string $item Queue item to iterate over.
* @return bool
*/
protected function task( $item ) {
if ( ! $item ) {
return false;
}
$order = wc_get_order( $item );
if ( ! $order ) {
return false;
}
WC_Admin_Reports_Orders_Stats_Data_Store::update( $order );
return false;
}
}

View File

@ -7,10 +7,6 @@
defined( 'ABSPATH' ) || exit;
if ( ! class_exists( 'WC_Admin_Order_Stats_Background_Process', false ) ) {
include_once WC_ADMIN_ABSPATH . '/includes/class-wc-admin-order-stats-background-process.php';
}
/**
* WC_Admin_Reports_Orders_Stats_Data_Store.
*
@ -71,22 +67,6 @@ class WC_Admin_Reports_Orders_Stats_Data_Store extends WC_Admin_Reports_Data_Sto
'num_new_customers' => 'SUM(returning_customer = 0) AS num_new_customers',
);
/**
* Background process to populate order stats.
*
* @var WC_Admin_Order_Stats_Background_Process
*/
protected static $background_process;
/**
* Constructor.
*/
public function __construct() {
if ( ! self::$background_process ) {
self::$background_process = new WC_Admin_Order_Stats_Background_Process();
}
}
/**
* Set up all the hooks for maintaining and populating table data.
*/
@ -96,10 +76,6 @@ class WC_Admin_Reports_Orders_Stats_Data_Store extends WC_Admin_Reports_Data_Sto
add_action( 'clean_post_cache', array( __CLASS__, 'sync_order' ) );
add_action( 'woocommerce_order_refunded', array( __CLASS__, 'sync_order' ) );
add_action( 'woocommerce_refund_deleted', array( __CLASS__, 'sync_on_refund_delete' ), 10, 2 );
if ( ! self::$background_process ) {
self::$background_process = new WC_Admin_Order_Stats_Background_Process();
}
}
/**
@ -373,31 +349,6 @@ class WC_Admin_Reports_Orders_Stats_Data_Store extends WC_Admin_Reports_Data_Sto
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
}
/**
* Queue a background process that will repopulate the entire orders stats database.
*
* @todo Make this work on large DBs.
*/
public static function queue_order_stats_repopulate_database() {
// This needs to be updated to work in batches instead of getting all orders, as
// that will not work well on DBs with more than a few hundred orders.
$order_ids = wc_get_orders(
array(
'limit' => -1,
'type' => 'shop_order',
'return' => 'ids',
)
);
foreach ( $order_ids as $id ) {
self::$background_process->push_to_queue( $id );
}
self::$background_process->save();
self::$background_process->dispatch();
}
/**
* Add order information to the lookup table when orders are created or modified.
*

View File

@ -122,10 +122,6 @@ class WC_Tests_API_Reports_Customers extends WC_REST_Unit_Test_Case {
$test_customers[] = WC_Helper_Customer::create_customer( "customer{$i}", 'password', "customer{$i}@example.com" );
}
// Initialize the report lookup table.
delete_transient( 'wc_update_350_all_customers' );
WC_Admin_Api_Init::customer_lookup_store_init();
// Create a test product for use in an order.
$product = new WC_Product_Simple();
$product->set_name( 'Test Product' );

View File

@ -67,6 +67,8 @@ class WC_Tests_API_Reports_Orders extends WC_REST_Unit_Test_Case {
$order->set_total( 100 ); // $25 x 4.
$order->save();
$expected_customer_id = WC_Admin_Reports_Customers_Data_Store::get_customer_id_by_user_id( 1 );
$response = $this->server->dispatch( new WP_REST_Request( 'GET', $this->endpoint ) );
$reports = $response->get_data();
@ -77,7 +79,7 @@ class WC_Tests_API_Reports_Orders extends WC_REST_Unit_Test_Case {
$this->assertEquals( $order->get_id(), $order_report['order_id'] );
$this->assertEquals( date( 'Y-m-d H:i:s', $order->get_date_created()->getTimestamp() ), $order_report['date_created'] );
$this->assertEquals( 0, $order_report['customer_id'] ); // @TODO: This should be 1, but customer_id is returning 0 in lookup table.
$this->assertEquals( $expected_customer_id, $order_report['customer_id'] );
$this->assertEquals( 4, $order_report['num_items_sold'] );
$this->assertEquals( 90.0, $order_report['net_total'] ); // 25 x 4 - 10 (shipping)
$this->assertEquals( 'new', $order_report['customer_type'] );

View File

@ -0,0 +1,184 @@
<?php
/**
* Reports Generation Batch Queue Tests
*
* @package WooCommerce\Tests\Reports
* @since 3.5.0
*/
/**
* Reports Generation Batch Queue Test Class
*
* @package WooCommerce\Tests\Reports
* @since 3.5.0
*/
class WC_Tests_Reports_Regenerate_Batching extends WC_REST_Unit_Test_Case {
/**
* Queue batch size.
*
* @var integer
*/
public $queue_batch_size = 10;
/**
* Customers batch size.
*
* @var integer
*/
public $customers_batch_size = 5;
/**
* Orders batch size.
*
* @var integer
*/
public $orders_batch_size = 2;
/**
* Force known values for batch size.
*
* @param int $batch_size Batch size.
* @param string $action Action.
* @return int
*/
public function filter_batch_size( $batch_size, $action ) {
switch ( $action ) {
case WC_Admin_Api_Init::QUEUE_BATCH_ACTION:
return $this->queue_batch_size;
case WC_Admin_Api_Init::CUSTOMERS_BATCH_ACTION:
return $this->customers_batch_size;
case WC_Admin_Api_Init::ORDERS_BATCH_ACTION:
return $this->orders_batch_size;
default:
return 1;
}
}
/**
* Set up.
*/
public function setUp() {
parent::setUp();
$this->queue = new WC_Admin_Test_Action_Queue();
WC_Admin_Api_Init::set_queue( $this->queue );
add_filter( 'wc_admin_report_regenerate_batch_size', array( $this, 'filter_batch_size' ), 10, 2 );
}
/**
* Tear down.
*/
public function tearDown() {
parent::tearDown();
WC_Admin_Api_Init::set_queue( null );
$this->queue->actions = array();
remove_filter( 'wc_admin_report_regenerate_batch_size', array( $this, 'filter_batch_size' ), 10, 2 );
}
/**
* Test that large batches get split properly.
*/
public function test_queue_batches_splits_into_batches_correctly() {
$num_customers = 1234; // 1234 / 5 = 247 batches
$num_batches = ceil( $num_customers / $this->customers_batch_size );
WC_Admin_Api_Init::queue_batches( 1, $num_batches, WC_Admin_Api_Init::CUSTOMERS_BATCH_ACTION );
$this->assertCount( $this->queue_batch_size, $this->queue->actions );
$this->assertArraySubset(
array(
'hook' => WC_Admin_Api_Init::QUEUE_BATCH_ACTION,
'args' => array( 1, 25, WC_Admin_Api_Init::CUSTOMERS_BATCH_ACTION ),
),
$this->queue->actions[0]
);
$this->assertArraySubset(
array(
'hook' => WC_Admin_Api_Init::QUEUE_BATCH_ACTION,
'args' => array( 226, 247, WC_Admin_Api_Init::CUSTOMERS_BATCH_ACTION ),
),
$this->queue->actions[ $this->queue_batch_size - 1 ]
);
}
/**
* Test that small enough batches have their "single" action queued.
*/
public function test_queue_batches_schedules_single_actions() {
$num_customers = 45; // 45 / 5 = 9 batches (which is less than the batch queue size)
$num_batches = ceil( $num_customers / $this->customers_batch_size );
WC_Admin_Api_Init::queue_batches( 1, $num_batches, WC_Admin_Api_Init::CUSTOMERS_BATCH_ACTION );
$this->assertCount( 9, $this->queue->actions );
$this->assertArraySubset(
array(
'hook' => WC_Admin_Api_Init::CUSTOMERS_BATCH_ACTION,
'args' => array( 1 ),
),
$this->queue->actions[0]
);
$this->assertArraySubset(
array(
'hook' => WC_Admin_Api_Init::CUSTOMERS_BATCH_ACTION,
'args' => array( 9 ),
),
$this->queue->actions[8]
);
}
/**
* Test that batch dependencies work.
*/
public function test_queue_dependent_action() {
// reset back to using a real queue.
WC_Admin_Api_Init::set_queue( null );
// insert a blocking job.
WC_Admin_Api_Init::queue()->schedule_single( time(), 'blocking_job', array( 'stuff' ) );
// queue an action that depends on blocking job.
WC_Admin_Api_Init::queue_dependent_action( 'dependent_action', 'blocking_job' );
// verify that the action was properly blocked.
$this->assertEmpty(
WC_Admin_Api_Init::queue()->search(
array(
'hook' => 'dependent_action',
)
)
);
// verify that a follow up action was queued.
$this->assertCount(
1,
WC_Admin_Api_Init::queue()->search(
array(
'hook' => WC_Admin_Api_Init::QUEUE_DEPEDENT_ACTION,
'args' => array( 'dependent_action', 'blocking_job' ),
)
)
);
// queue an action that isn't blocked.
WC_Admin_Api_Init::queue_dependent_action( 'another_dependent_action', 'nonexistant_blocking_job' );
// verify that the dependent action was queued.
$this->assertCount(
1,
WC_Admin_Api_Init::queue()->search(
array(
'hook' => 'another_dependent_action',
)
)
);
// verify that no follow up action was queued.
$this->assertEmpty(
WC_Admin_Api_Init::queue()->search(
array(
'hook' => WC_Admin_Api_Init::QUEUE_DEPEDENT_ACTION,
'args' => array( 'another_dependent_action', 'nonexistant_blocking_job' ),
)
)
);
// clean up.
WC_Admin_Api_Init::queue()->cancel_all( 'another_dependent_action' );
WC_Admin_Api_Init::queue()->cancel_all( WC_Admin_Api_Init::QUEUE_DEPEDENT_ACTION );
}
}

View File

@ -114,3 +114,4 @@ wc_test_includes();
// Include wc-admin helpers.
require_once dirname( __FILE__ ) . '/framework/helpers/class-wc-helper-reports.php';
require_once dirname( __FILE__ ) . '/framework/helpers/class-wc-helper-admin-notes.php';
require_once dirname( __FILE__ ) . '/framework/helpers/class-wc-test-action-queue.php';

View File

@ -0,0 +1,34 @@
<?php
/**
* Action Queue Test Helper
*
* @version 3.5.0
* @package WooCommerce/Tests
*/
/**
* WC_Admin_Test_Action_Queue class.
*/
class WC_Admin_Test_Action_Queue extends WC_Action_Queue {
/**
* Actions queue.
*
* @var array
*/
public $actions = array();
/**
* Override of WC_Action_Queue::schedule_single that just places
* actions into the $actions instance variable for future inspection.
*
* @param int $timestamp Timestamp.
* @param string $hook Hook.
* @param array $args Args.
* @param string $group Group.
* @return bool
*/
public function schedule_single( $timestamp, $hook, $args = array(), $group = '' ) {
$this->actions[] = compact( 'timestamp', 'hook', 'args', 'group' );
return true;
}
}