Merge pull request woocommerce/woocommerce-admin#2034 from woocommerce/add/1850-import-endpoint

Add import endpoint and controller
This commit is contained in:
Jeff Stieler 2019-05-01 13:29:00 -06:00 committed by GitHub
commit a36e3cd024
12 changed files with 888 additions and 193 deletions

View File

@ -58,9 +58,9 @@ export const analyticsSettings = applyFilters( SETTINGS_FILTER, [
'woocommerce-admin'
);
apiFetch( { path: '/wc/v3/system_status/tools/rebuild_stats', method: 'PUT' } )
apiFetch( { path: '/wc/v4/reports/import', method: 'PUT' } )
.then( response => {
if ( response.success ) {
if ( 'success' === response.status ) {
addNotice( { status: 'success', message: response.message } );
// @todo This should be changed to detect when the lookup table population is complete.
setTimeout( () => resolve(), 300000 );

View File

@ -0,0 +1,252 @@
<?php
/**
* REST API Reports Import Controller
*
* Handles requests to /reports/import
*
* @package WooCommerce Admin/API
*/
defined( 'ABSPATH' ) || exit;
/**
* Reports Imports controller.
*
* @package WooCommerce Admin/API
* @extends WC_REST_Data_Controller
*/
class WC_Admin_REST_Reports_Import_Controller extends WC_Admin_REST_Reports_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v4';
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'reports/import';
/**
* Register routes.
*/
public function register_routes() {
register_rest_route(
$this->namespace,
'/' . $this->rest_base,
array(
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'import_items' ),
'permission_callback' => array( $this, 'import_permissions_check' ),
'args' => $this->get_import_collection_params(),
),
'schema' => array( $this, 'get_import_public_schema' ),
)
);
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/cancel',
array(
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'cancel_import' ),
'permission_callback' => array( $this, 'import_permissions_check' ),
),
'schema' => array( $this, 'get_import_public_schema' ),
)
);
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/delete',
array(
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'delete_imported_items' ),
'permission_callback' => array( $this, 'import_permissions_check' ),
),
'schema' => array( $this, 'get_import_public_schema' ),
)
);
}
/**
* Makes sure the current user has access to WRITE the settings APIs.
*
* @param WP_REST_Request $request Full data about the request.
* @return WP_Error|bool
*/
public function import_permissions_check( $request ) {
if ( ! wc_rest_check_manager_permissions( 'settings', 'edit' ) ) {
return new WP_Error( 'woocommerce_rest_cannot_edit', __( 'Sorry, you cannot edit this resource.', 'woocommerce-admin' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Import data based on user request params.
*
* @param WP_REST_Request $request Request data.
* @return WP_Error|WP_REST_Response
*/
public function import_items( $request ) {
$query_args = $this->prepare_objects_query( $request );
$import = WC_Admin_Reports_Sync::regenerate_report_data( $query_args['days'], $query_args['skip_existing'] );
if ( is_wp_error( $import ) ) {
$result = array(
'status' => 'error',
'message' => $import->get_error_message(),
);
} else {
$result = array(
'status' => 'success',
'message' => $import,
);
}
$response = $this->prepare_item_for_response( $result, $request );
$data = $this->prepare_response_for_collection( $response );
return rest_ensure_response( $data );
}
/**
* Prepare request object as query args.
*
* @param WP_REST_Request $request Request data.
* @return array
*/
protected function prepare_objects_query( $request ) {
$args = array();
$args['skip_existing'] = $request['skip_existing'];
$args['days'] = $request['days'];
return $args;
}
/**
* Prepare the data object for response.
*
* @param object $item Data object.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response $response Response data.
*/
public function prepare_item_for_response( $item, $request ) {
$data = $this->add_additional_fields_to_object( $item, $request );
$data = $this->filter_response_by_context( $data, 'view' );
$response = rest_ensure_response( $data );
/**
* Filter the list returned from the API.
*
* @param WP_REST_Response $response The response object.
* @param array $item The original item.
* @param WP_REST_Request $request Request used to generate the response.
*/
return apply_filters( 'woocommerce_rest_prepare_reports_import', $response, $item, $request );
}
/**
* Get the query params for collections.
*
* @return array
*/
public function get_import_collection_params() {
$params = array();
$params['days'] = array(
'description' => __( 'Number of days to import.', 'woocommerce-admin' ),
'type' => 'integer',
'sanitize_callback' => 'absint',
'validate_callback' => 'rest_validate_request_arg',
'minimum' => 1,
);
$params['skip_existing'] = array(
'description' => __( 'Skip importing existing order data.', 'woocommerce-admin' ),
'type' => 'boolean',
'default' => false,
'sanitize_callback' => 'wc_string_to_bool',
'validate_callback' => 'rest_validate_request_arg',
);
return $params;
}
/**
* Get the Report's schema, conforming to JSON Schema.
*
* @return array
*/
public function get_import_public_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'report_import',
'type' => 'object',
'properties' => array(
'status' => array(
'description' => __( 'Regeneration status.', 'woocommerce-admin' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'message' => array(
'description' => __( 'Regenerate data message.', 'woocommerce-admin' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
),
);
return $this->add_additional_fields_schema( $schema );
}
/**
* Cancel all queued import actions.
*
* @param WP_REST_Request $request Request data.
* @return WP_Error|WP_REST_Response
*/
public function cancel_import( $request ) {
WC_Admin_Reports_Sync::clear_queued_actions();
$result = array(
'status' => 'success',
'message' => __( 'All pending and in-progress import actions have been cancelled.', 'woocommerce-admin' ),
);
$response = $this->prepare_item_for_response( $result, $request );
$data = $this->prepare_response_for_collection( $response );
return rest_ensure_response( $data );
}
/**
* Delete all imported items.
*
* @param WP_REST_Request $request Request data.
* @return WP_Error|WP_REST_Response
*/
public function delete_imported_items( $request ) {
$delete = WC_Admin_Reports_Sync::delete_report_data();
if ( is_wp_error( $delete ) ) {
$result = array(
'status' => 'error',
'message' => $delete->get_error_message(),
);
} else {
$result = array(
'status' => 'success',
'message' => $delete,
);
}
$response = $this->prepare_item_for_response( $result, $request );
$data = $this->prepare_response_for_collection( $response );
return rest_ensure_response( $data );
}
}

View File

@ -1,70 +0,0 @@
<?php
/**
* REST API WC System Status Tools Controller
*
* Handles requests to the /system_status/tools/* endpoints.
*
* @package WooCommerce Admin/API
*/
defined( 'ABSPATH' ) || exit;
/**
* System status tools controller.
*
* @package WooCommerce Admin/API
* @extends WC_REST_System_Status_Tools_Controller
*/
class WC_Admin_REST_System_Status_Tools_Controller extends WC_REST_System_Status_Tools_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v4';
/**
* A list of available tools for use in the system status section.
* 'button' becomes 'action' in the API.
*
* @return array
*/
public function get_tools() {
return array_merge(
parent::get_tools(),
array(
'rebuild_stats' => array(
'name' => __( 'Rebuild reports data', 'woocommerce-admin' ),
'button' => __( 'Rebuild reports', 'woocommerce-admin' ),
'desc' => __( 'This tool will rebuild all of the information used by the reports.', 'woocommerce-admin' ),
),
)
);
}
/**
* Actually executes a tool.
*
* @param string $tool Tool.
* @return array
*/
public function execute_tool( $tool ) {
$ran = true;
$message = '';
switch ( $tool ) {
case 'rebuild_stats':
WC_Admin_Api_Init::regenerate_report_data();
$message = __( 'Rebuilding reports data in the background . . .', 'woocommerce-admin' );
break;
default:
return parent::execute_tool( $tool );
}
return array(
'success' => $ran,
'message' => $message,
);
}
}

View File

@ -47,10 +47,10 @@ class WC_Admin_ActionScheduler_WPPostStore extends ActionScheduler_wpPostStore {
$action_types = array(
WC_Admin_Reports_Sync::QUEUE_BATCH_ACTION,
WC_Admin_Reports_Sync::QUEUE_DEPEDENT_ACTION,
WC_Admin_Reports_Sync::CUSTOMERS_BATCH_ACTION,
WC_Admin_Reports_Sync::ORDERS_BATCH_ACTION,
WC_Admin_Reports_Sync::ORDERS_LOOKUP_BATCH_INIT,
WC_Admin_Reports_Sync::SINGLE_ORDER_ACTION,
WC_Admin_Reports_Sync::CUSTOMERS_IMPORT_BATCH_ACTION,
WC_Admin_Reports_Sync::ORDERS_IMPORT_BATCH_ACTION,
WC_Admin_Reports_Sync::ORDERS_IMPORT_BATCH_INIT,
WC_Admin_Reports_Sync::SINGLE_ORDER_IMPORT_ACTION,
);
foreach ( $action_types as $action_type ) {

View File

@ -115,7 +115,6 @@ class WC_Admin_Api_Init {
require_once WC_ADMIN_ABSPATH . 'includes/api/class-wc-admin-rest-product-variations-controller.php';
require_once WC_ADMIN_ABSPATH . 'includes/api/class-wc-admin-rest-reports-controller.php';
require_once WC_ADMIN_ABSPATH . 'includes/api/class-wc-admin-rest-setting-options-controller.php';
require_once WC_ADMIN_ABSPATH . 'includes/api/class-wc-admin-rest-system-status-tools-controller.php';
require_once WC_ADMIN_ABSPATH . 'includes/api/class-wc-admin-rest-reports-categories-controller.php';
require_once WC_ADMIN_ABSPATH . 'includes/api/class-wc-admin-rest-reports-coupons-controller.php';
require_once WC_ADMIN_ABSPATH . 'includes/api/class-wc-admin-rest-reports-coupons-stats-controller.php';
@ -124,6 +123,7 @@ class WC_Admin_Api_Init {
require_once WC_ADMIN_ABSPATH . 'includes/api/class-wc-admin-rest-reports-downloads-controller.php';
require_once WC_ADMIN_ABSPATH . 'includes/api/class-wc-admin-rest-reports-downloads-files-controller.php';
require_once WC_ADMIN_ABSPATH . 'includes/api/class-wc-admin-rest-reports-downloads-stats-controller.php';
require_once WC_ADMIN_ABSPATH . 'includes/api/class-wc-admin-rest-reports-import-controller.php';
require_once WC_ADMIN_ABSPATH . 'includes/api/class-wc-admin-rest-reports-orders-controller.php';
require_once WC_ADMIN_ABSPATH . 'includes/api/class-wc-admin-rest-reports-orders-stats-controller.php';
require_once WC_ADMIN_ABSPATH . 'includes/api/class-wc-admin-rest-reports-products-controller.php';
@ -157,7 +157,7 @@ class WC_Admin_Api_Init {
'WC_Admin_REST_Product_Variations_Controller',
'WC_Admin_REST_Reports_Controller',
'WC_Admin_REST_Setting_Options_Controller',
'WC_Admin_REST_System_Status_Tools_Controller',
'WC_Admin_REST_Reports_Import_Controller',
'WC_Admin_REST_Reports_Products_Controller',
'WC_Admin_REST_Reports_Variations_Controller',
'WC_Admin_REST_Reports_Products_Stats_Controller',

View File

@ -22,24 +22,44 @@ class WC_Admin_Reports_Sync {
const QUEUE_DEPEDENT_ACTION = 'wc-admin_queue_dependent_action';
/**
* Action hook for processing a batch of customers.
* Action hook for importing 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';
const CUSTOMERS_IMPORT_BATCH_ACTION = 'wc-admin_import_customers_batch';
/**
* Action hook for initializing the orders lookup batch creation.
*/
const ORDERS_LOOKUP_BATCH_INIT = 'wc-admin_orders_lookup_batch_init';
const CUSTOMERS_DELETE_BATCH_INIT = 'wc-admin_delete_customers_batch_init';
/**
* Action hook for processing a batch of orders.
* Action hook for deleting a batch of customers.
*/
const SINGLE_ORDER_ACTION = 'wc-admin_process_order';
const CUSTOMERS_DELETE_BATCH_ACTION = 'wc-admin_delete_customers_batch';
/**
* Action hook for importing a batch of orders.
*/
const ORDERS_IMPORT_BATCH_ACTION = 'wc-admin_import_orders_batch';
/**
* Action hook for initializing the orders lookup batch creation.
*/
const ORDERS_IMPORT_BATCH_INIT = 'wc-admin_orders_lookup_import_batch_init';
/**
* Action hook for initializing the orders lookup batch deletion.
*/
const ORDERS_DELETE_BATCH_INIT = 'wc-admin_orders_lookup_delete_batch_init';
/**
* Action hook for deleting a batch of orders.
*/
const ORDERS_DELETE_BATCH_ACTION = 'wc-admin_delete_orders_batch';
/**
* Action hook for importing a batch of orders.
*/
const SINGLE_ORDER_IMPORT_ACTION = 'wc-admin_import_order';
/**
* Action scheduler group.
@ -79,30 +99,32 @@ class WC_Admin_Reports_Sync {
* Hook in sync methods.
*/
public static function init() {
// Add report regeneration to tools REST API.
add_filter( 'woocommerce_debug_tools', array( __CLASS__, 'add_regenerate_tool' ) );
// Initialize syncing hooks.
add_action( 'wp_loaded', array( __CLASS__, 'orders_lookup_update_init' ) );
// Initialize scheduled action handlers.
add_action( self::QUEUE_BATCH_ACTION, array( __CLASS__, 'queue_batches' ), 10, 3 );
add_action( self::QUEUE_BATCH_ACTION, array( __CLASS__, 'queue_batches' ), 10, 4 );
add_action( self::QUEUE_DEPEDENT_ACTION, array( __CLASS__, 'queue_dependent_action' ), 10, 3 );
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' ) );
add_action( self::SINGLE_ORDER_ACTION, array( __CLASS__, 'orders_lookup_process_order' ) );
add_action( self::CUSTOMERS_IMPORT_BATCH_ACTION, array( __CLASS__, 'customer_lookup_import_batch' ), 10, 3 );
add_action( self::CUSTOMERS_DELETE_BATCH_INIT, array( __CLASS__, 'customer_lookup_delete_batch_init' ) );
add_action( self::CUSTOMERS_DELETE_BATCH_ACTION, array( __CLASS__, 'customer_lookup_delete_batch' ) );
add_action( self::ORDERS_IMPORT_BATCH_ACTION, array( __CLASS__, 'orders_lookup_import_batch' ), 10, 4 );
add_action( self::ORDERS_IMPORT_BATCH_INIT, array( __CLASS__, 'orders_lookup_import_batch_init' ), 10, 3 );
add_action( self::ORDERS_DELETE_BATCH_ACTION, array( __CLASS__, 'orders_lookup_delete_batch' ), 10, 4 );
add_action( self::ORDERS_DELETE_BATCH_INIT, array( __CLASS__, 'orders_lookup_delete_batch_init' ), 10, 3 );
add_action( self::SINGLE_ORDER_IMPORT_ACTION, array( __CLASS__, 'orders_lookup_import_order' ) );
}
/**
* Regenerate data for reports.
*
* @param int|bool $days Number of days to import.
* @param bool $skip_existing Skip exisiting records.
* @return string
*/
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_batch_init();
// Queue orders lookup to occur after customers lookup generation is done.
self::queue_dependent_action( self::ORDERS_LOOKUP_BATCH_INIT, array(), self::CUSTOMERS_BATCH_ACTION );
public static function regenerate_report_data( $days, $skip_existing ) {
self::customer_lookup_import_batch_init( $days, $skip_existing );
self::queue_dependent_action( self::ORDERS_IMPORT_BATCH_INIT, array( $days, $skip_existing ), self::CUSTOMERS_IMPORT_BATCH_ACTION );
return __( 'Report table data is being rebuilt. Please allow some time for data to fully populate.', 'woocommerce-admin' );
}
@ -122,42 +144,36 @@ class WC_Admin_Reports_Sync {
}
/**
* Adds regenerate tool to WC system status tools API.
* Delete all data for reports.
*
* @param array $tools List of tools.
* @return array
* @return string
*/
public static function add_regenerate_tool( $tools ) {
if ( isset( $_GET['page'] ) && 'wc-status' === $_GET['page'] ) { // phpcs:ignore WordPress.Security.NonceVerification
return $tools;
}
public static function delete_report_data() {
// Cancel all pending import jobs.
self::clear_queued_actions();
return array_merge(
$tools,
array(
'rebuild_stats' => array(
'name' => __( 'Rebuild reports data', 'woocommerce-admin' ),
'button' => __( 'Rebuild reports', 'woocommerce-admin' ),
'desc' => __( 'This tool will rebuild all of the information used by the reports.', 'woocommerce-admin' ),
'callback' => array( __CLASS__, 'regenerate_report_data' ),
),
)
);
// Delete orders in batches.
self::queue()->schedule_single( time() + 5, self::ORDERS_DELETE_BATCH_INIT, array(), self::QUEUE_GROUP );
// Delete customers after order data is deleted.
self::queue_dependent_action( self::CUSTOMERS_DELETE_BATCH_INIT, array(), self::ORDERS_DELETE_BATCH_INIT );
return __( 'Report table data is being deleted.', 'woocommerce-admin' );
}
/**
* Schedule an action to process a single Order.
* Schedule an action to import a single Order.
*
* @param int $order_id Order ID.
* @return void
*/
public static function schedule_single_order_process( $order_id ) {
public static function schedule_single_order_import( $order_id ) {
if ( 'shop_order' !== get_post_type( $order_id ) ) {
return;
}
if ( apply_filters( 'woocommerce_disable_order_scheduling', false ) ) {
self::orders_lookup_process_order( $order_id );
self::orders_lookup_import_order( $order_id );
return;
}
@ -178,10 +194,10 @@ class WC_Admin_Reports_Sync {
// Bail out if there's a pending single order action, or a pending dependent action.
if (
( self::SINGLE_ORDER_ACTION === $existing_job->get_hook() ) ||
( self::SINGLE_ORDER_IMPORT_ACTION === $existing_job->get_hook() ) ||
(
self::QUEUE_DEPEDENT_ACTION === $existing_job->get_hook() &&
in_array( self::SINGLE_ORDER_ACTION, $existing_job->get_args() )
in_array( self::SINGLE_ORDER_IMPORT_ACTION, $existing_job->get_args() )
)
) {
return;
@ -189,7 +205,7 @@ class WC_Admin_Reports_Sync {
}
// We want to ensure that customer lookup updates are scheduled before order updates.
self::queue_dependent_action( self::SINGLE_ORDER_ACTION, array( $order_id ), self::CUSTOMERS_BATCH_ACTION );
self::queue_dependent_action( self::SINGLE_ORDER_IMPORT_ACTION, array( $order_id ), self::CUSTOMERS_IMPORT_BATCH_ACTION );
}
/**
@ -199,8 +215,8 @@ class WC_Admin_Reports_Sync {
// Activate WC_Order extension.
WC_Admin_Order::add_filters();
add_action( 'save_post', array( __CLASS__, 'schedule_single_order_process' ) );
add_action( 'woocommerce_order_refunded', array( __CLASS__, 'schedule_single_order_process' ) );
add_action( 'save_post', array( __CLASS__, 'schedule_single_order_import' ) );
add_action( 'woocommerce_order_refunded', array( __CLASS__, 'schedule_single_order_import' ) );
WC_Admin_Reports_Orders_Stats_Data_Store::init();
WC_Admin_Reports_Customers_Data_Store::init();
@ -211,59 +227,98 @@ class WC_Admin_Reports_Sync {
/**
* Init order/product lookup tables update (in batches).
*
* @param integer|boolean $days Number of days to import.
* @param boolean $skip_existing Skip exisiting records.
*/
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();
public static function orders_lookup_import_batch_init( $days, $skip_existing ) {
$batch_size = self::get_batch_size( self::ORDERS_IMPORT_BATCH_ACTION );
$orders = self::get_orders( 1, 1, $days, $skip_existing );
if ( 0 === $result->total ) {
if ( 0 === $orders->total ) {
return;
}
$num_batches = ceil( $result->total / $batch_size );
$num_batches = ceil( $orders->total / $batch_size );
self::queue_batches( 1, $num_batches, self::ORDERS_BATCH_ACTION );
self::queue_batches( 1, $num_batches, self::ORDERS_IMPORT_BATCH_ACTION, array( $days, $skip_existing ) );
}
/**
* Process a batch of orders to update (stats and products).
* Get the order IDs and total count that need to be synced.
*
* @param int $batch_number Batch number to process (essentially a query page number).
* @param int $limit Number of records to retrieve.
* @param int $page Page number.
* @param int|bool $days Number of days prior to current date to limit search results.
* @param bool $skip_existing Skip already imported orders.
*/
public static function get_orders( $limit = 10, $page = 1, $days = false, $skip_existing = false ) {
global $wpdb;
$where_clause = '';
$offset = $page > 1 ? $page * $limit : 0;
if ( $days ) {
$days_ago = date( 'Y-m-d 00:00:00', time() - ( DAY_IN_SECONDS * $days ) );
$where_clause .= " AND post_date >= '{$days_ago}'";
}
if ( $skip_existing ) {
$where_clause .= " AND NOT EXISTS (
SELECT 1 FROM {$wpdb->prefix}wc_order_stats
WHERE {$wpdb->prefix}wc_order_stats.order_id = {$wpdb->posts}.ID
)";
}
$count = $wpdb->get_var(
"SELECT COUNT(*) FROM {$wpdb->posts}
WHERE post_type = 'shop_order'
{$where_clause}"
); // WPCS: unprepared SQL ok.
$order_ids = absint( $count ) > 0 ? $wpdb->get_col(
$wpdb->prepare(
"SELECT ID FROM {$wpdb->posts}
WHERE post_type = 'shop_order'
{$where_clause}
ORDER BY post_date ASC
LIMIT %d
OFFSET %d",
$limit,
$offset
)
) : array(); // WPCS: unprepared SQL ok.
return (object) array(
'total' => absint( $count ),
'order_ids' => $order_ids,
);
}
/**
* Imports a batch of orders to update (stats and products).
*
* @param int $batch_number Batch number to import (essentially a query page number).
* @param int|bool $days Number of days to import.
* @param bool $skip_existing Skip exisiting records.
* @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();
public static function orders_lookup_import_batch( $batch_number, $days, $skip_existing ) {
$batch_size = self::get_batch_size( self::ORDERS_IMPORT_BATCH_ACTION );
$orders = self::get_orders( $batch_size, $batch_number, $days, $skip_existing );
foreach ( $order_ids as $order_id ) {
self::orders_lookup_process_order( $order_id );
foreach ( $orders->order_ids as $order_id ) {
self::orders_lookup_import_order( $order_id );
}
}
/**
* Process a single order to update lookup tables for.
* Imports a single order to update lookup tables for.
* If an error is encountered in one of the updates, a retry action is scheduled.
*
* @param int $order_id Order ID.
* @return void
*/
public static function orders_lookup_process_order( $order_id ) {
public static function orders_lookup_import_order( $order_id ) {
$result = array_sum(
array(
WC_Admin_Reports_Orders_Stats_Data_Store::sync_order( $order_id ),
@ -280,7 +335,7 @@ class WC_Admin_Reports_Sync {
}
// Otherwise assume an error occurred and reschedule.
self::schedule_single_order_process( $order_id );
self::schedule_single_order_import( $order_id );
}
/**
@ -293,8 +348,10 @@ class WC_Admin_Reports_Sync {
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,
self::CUSTOMERS_IMPORT_BATCH_ACTION => 25,
self::CUSTOMERS_DELETE_BATCH_ACTION => 25,
self::ORDERS_IMPORT_BATCH_ACTION => 10,
self::ORDERS_DELETE_BATCH_ACTION => 10,
);
$batch_size = isset( $batch_sizes[ $action ] ) ? $batch_sizes[ $action ] : 25;
@ -314,9 +371,10 @@ class WC_Admin_Reports_Sync {
* @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.
* @param array $action_args Action arguments.
* @return void
*/
public static function queue_batches( $range_start, $range_end, $single_batch_action ) {
public static function queue_batches( $range_start, $range_end, $single_batch_action, $action_args = array() ) {
$batch_size = self::get_batch_size( self::QUEUE_BATCH_ACTION );
$range_size = 1 + ( $range_end - $range_start );
$action_timestamp = time() + 5;
@ -333,14 +391,15 @@ class WC_Admin_Reports_Sync {
self::queue()->schedule_single(
$action_timestamp,
self::QUEUE_BATCH_ACTION,
array( $batch_start, $batch_end, $single_batch_action ),
array( $batch_start, $batch_end, $single_batch_action, $action_args ),
self::QUEUE_GROUP
);
}
} 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 ), self::QUEUE_GROUP );
$batch_action_args = array_merge( array( $i ), $action_args );
self::queue()->schedule_single( $action_timestamp, $single_batch_action, $batch_action_args, self::QUEUE_GROUP );
}
}
}
@ -393,11 +452,64 @@ class WC_Admin_Reports_Sync {
}
/**
* Init customer lookup table update (in batches).
* Exclude users that already exist in our customer lookup table.
*
* Meant to be hooked into 'pre_user_query' action.
*
* @param WP_User_Query $wp_user_query WP_User_Query to modify.
*/
public static function customer_lookup_batch_init() {
$batch_size = self::get_batch_size( self::CUSTOMERS_BATCH_ACTION );
$customer_query = new WP_User_Query(
public static function exclude_existing_customers_from_query( $wp_user_query ) {
global $wpdb;
$wp_user_query->query_where .= " AND NOT EXISTS (
SELECT ID FROM {$wpdb->prefix}wc_customer_lookup
WHERE {$wpdb->prefix}wc_customer_lookup.user_id = {$wpdb->users}.ID
)";
}
/**
* Retrieve user IDs given import criteria.
*
* @param int|bool $days Number of days to process.
* @param bool $skip_existing Skip exisiting records.
* @param array $query_args Optional. WP_User_Query args.
* @return WP_User_Query
*/
public static function get_user_ids_for_batch( $days, $skip_existing, $query_args = array() ) {
if ( ! is_array( $query_args ) ) {
$query_args = array();
}
if ( $days ) {
$query_args['date_query'] = array(
'after' => date( 'Y-m-d 00:00:00', time() - ( DAY_IN_SECONDS * $days ) ),
);
}
if ( $skip_existing ) {
add_action( 'pre_user_query', array( __CLASS__, 'exclude_existing_customers_from_query' ) );
}
$customer_query = new WP_User_Query( $query_args );
remove_action( 'pre_user_query', array( __CLASS__, 'exclude_existing_customers_from_query' ) );
return $customer_query;
}
/**
* Init customer lookup table update (in batches).
*
* @param int|bool $days Number of days to process.
* @param bool $skip_existing Skip exisiting records.
*/
public static function customer_lookup_import_batch_init( $days, $skip_existing ) {
$batch_size = self::get_batch_size( self::CUSTOMERS_IMPORT_BATCH_ACTION );
$customer_query = self::get_user_ids_for_batch(
$days,
$skip_existing,
array(
'fields' => 'ID',
'number' => 1,
@ -411,18 +523,22 @@ class WC_Admin_Reports_Sync {
$num_batches = ceil( $total_customers / $batch_size );
self::queue_batches( 1, $num_batches, self::CUSTOMERS_BATCH_ACTION );
self::queue_batches( 1, $num_batches, self::CUSTOMERS_IMPORT_BATCH_ACTION, array( $days, $skip_existing ) );
}
/**
* Process a batch of customers to update.
*
* @param int $batch_number Batch number to process (essentially a query page number).
* @param int|bool $days Number of days to process.
* @param bool $skip_existing Skip exisiting records.
* @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(
public static function customer_lookup_import_batch( $batch_number, $days, $skip_existing ) {
$batch_size = self::get_batch_size( self::CUSTOMERS_IMPORT_BATCH_ACTION );
$customer_query = self::get_user_ids_for_batch(
$days,
$skip_existing,
array(
'fields' => 'ID',
'orderby' => 'ID',
@ -439,6 +555,79 @@ class WC_Admin_Reports_Sync {
WC_Admin_Reports_Customers_Data_Store::update_registered_customer( $customer_id );
}
}
/**
* Delete customer lookup table rows (in batches).
*/
public static function customer_lookup_delete_batch_init() {
global $wpdb;
$batch_size = self::get_batch_size( self::CUSTOMERS_DELETE_BATCH_ACTION );
$count = $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}wc_customer_lookup" );
if ( 0 === $count ) {
return;
}
$num_batches = ceil( $count / $batch_size );
self::queue_batches( 1, $num_batches, self::CUSTOMERS_DELETE_BATCH_ACTION );
}
/**
* Delete a batch of customers.
*/
public static function customer_lookup_delete_batch() {
global $wpdb;
$batch_size = self::get_batch_size( self::CUSTOMERS_DELETE_BATCH_ACTION );
$customer_ids = $wpdb->get_col(
$wpdb->prepare(
"SELECT customer_id FROM {$wpdb->prefix}wc_customer_lookup ORDER BY customer_id ASC LIMIT %d",
$batch_size
)
);
foreach ( $customer_ids as $customer_id ) {
WC_Admin_Reports_Customers_Data_Store::delete_customer( $customer_id );
}
}
/**
* Delete orders lookup table rows (in batches).
*/
public static function orders_lookup_delete_batch_init() {
global $wpdb;
$batch_size = self::get_batch_size( self::ORDERS_DELETE_BATCH_ACTION );
$count = $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}wc_order_stats" );
if ( 0 === $count ) {
return;
}
$num_batches = ceil( $count / $batch_size );
self::queue_batches( 1, $num_batches, self::ORDERS_DELETE_BATCH_ACTION );
}
/**
* Delete a batch of orders.
*
* @return void
*/
public static function orders_lookup_delete_batch() {
global $wpdb;
$batch_size = self::get_batch_size( self::ORDERS_DELETE_BATCH_ACTION );
$order_ids = $wpdb->get_col(
$wpdb->prepare(
"SELECT order_id FROM {$wpdb->prefix}wc_order_stats ORDER BY order_id ASC LIMIT %d",
$batch_size
)
);
foreach ( $order_ids as $order_id ) {
WC_Admin_Reports_Orders_Stats_Data_Store::delete_order( $order_id );
}
}
}
WC_Admin_Reports_Sync::init();

View File

@ -520,8 +520,8 @@ class WC_Admin_Reports_Customers_Data_Store extends WC_Admin_Reports_Data_Store
* @return array
*/
public static function get_customer_name( $user_id = 0, $order = null ) {
$first_name = null;
$last_name = null;
$first_name = '';
$last_name = '';
if (
$user_id &&
@ -683,7 +683,7 @@ class WC_Admin_Reports_Customers_Data_Store extends WC_Admin_Reports_Data_Store
protected static function is_valid_customer( $user_id ) {
$customer = new WC_Customer( $user_id );
if ( $customer->get_id() !== $user_id ) {
if ( $customer->get_id() != $user_id ) {
return false;
}
@ -694,6 +694,31 @@ class WC_Admin_Reports_Customers_Data_Store extends WC_Admin_Reports_Data_Store
return true;
}
/**
* Delete a customer lookup row.
*
* @param int $customer_id Customer ID.
*/
public static function delete_customer( $customer_id ) {
global $wpdb;
$customer_id = (int) $customer_id;
$table_name = $wpdb->prefix . self::TABLE_NAME;
$wpdb->query(
$wpdb->prepare(
"DELETE FROM ${table_name} WHERE customer_id = %d",
$customer_id
)
);
/**
* Fires when a customer is deleted.
*
* @param int $order_id Order ID.
*/
do_action( 'woocommerce_reports_delete_customer', $customer_id );
}
/**
* Returns string to be used as cache key for the data.
*

View File

@ -72,13 +72,13 @@ class WC_Tests_API_Init extends WC_REST_Unit_Test_Case {
add_filter( 'query', array( $this, 'filter_order_query' ) );
// Initiate sync.
WC_Admin_Reports_Sync::orders_lookup_process_order( $order->get_id() );
WC_Admin_Reports_Sync::orders_lookup_import_order( $order->get_id() );
// Verify that a retry job was scheduled.
$this->assertCount( 1, $this->queue->actions );
$this->assertArraySubset(
array(
'hook' => WC_Admin_Reports_Sync::SINGLE_ORDER_ACTION,
'hook' => WC_Admin_Reports_Sync::SINGLE_ORDER_IMPORT_ACTION,
'args' => array( $order->get_id() ),
),
$this->queue->actions[0]

View File

@ -0,0 +1,288 @@
<?php
/**
* Reports Import REST API Test
*
* @package WooCommerce\Tests\API
*/
/**
* Reports Import REST API Test Class
*
* @package WooCommerce\Tests\API
*/
class WC_Tests_API_Reports_Import extends WC_REST_Unit_Test_Case {
/**
* Endpoint.
*
* @var string
*/
protected $endpoint = '/wc/v4/reports/import';
/**
* Setup test reports products data.
*/
public function setUp() {
parent::setUp();
$this->user = $this->factory->user->create(
array(
'role' => 'administrator',
)
);
$this->customer = $this->factory->user->create(
array(
'first_name' => 'Steve',
'last_name' => 'User',
'role' => 'customer',
)
);
}
/**
* Test route registration.
*/
public function test_register_routes() {
$routes = $this->server->get_routes();
$this->assertArrayHasKey( $this->endpoint, $routes );
}
/**
* Asserts the report item schema is correct.
*
* @param array $schema Item to check schema.
*/
public function assert_report_item_schema( $schema ) {
$this->assertArrayHasKey( 'status', $schema );
$this->assertArrayHasKey( 'message', $schema );
}
/**
* Test reports schema.
*/
public function test_reports_schema() {
wp_set_current_user( $this->user );
$request = new WP_REST_Request( 'OPTIONS', $this->endpoint );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$properties = $data['schema']['properties'];
$this->assertCount( 2, $properties );
$this->assert_report_item_schema( $properties );
}
/**
* Test getting reports without valid permissions.
*/
public function test_get_reports_without_permission() {
wp_set_current_user( 0 );
$response = $this->server->dispatch( new WP_REST_Request( 'POST', $this->endpoint ) );
$this->assertEquals( 401, $response->get_status() );
}
/**
* Test the import paramaters.
*/
public function test_import_params() {
global $wpdb;
wp_set_current_user( $this->user );
// Populate all of the data.
$product = new WC_Product_Simple();
$product->set_name( 'Test Product' );
$product->set_regular_price( 25 );
$product->save();
$order_1 = WC_Helper_Order::create_order( 1, $product );
$order_1->set_status( 'completed' );
$order_1->set_date_created( time() - ( 3 * DAY_IN_SECONDS ) );
$order_1->save();
$order_2 = WC_Helper_Order::create_order( 1, $product );
$order_2->set_total( 100 );
$order_2->set_status( 'completed' );
$order_2->save();
// Delete order stats so we can test import API.
$wpdb->query( "DELETE FROM {$wpdb->posts} WHERE post_type = 'scheduled-action'" );
$wpdb->query( "DELETE FROM {$wpdb->prefix}wc_order_stats" );
// Use the days param to only process orders in the last day.
$request = new WP_REST_Request( 'POST', $this->endpoint );
$request->set_query_params( array( 'days' => '1' ) );
$response = $this->server->dispatch( $request );
$report = $response->get_data();
$this->assertEquals( 200, $response->get_status() );
$this->assertEquals( 'success', $report['status'] );
// Run pending thrice to process batch and order.
WC_Helper_Queue::run_all_pending();
WC_Helper_Queue::run_all_pending();
WC_Helper_Queue::run_all_pending();
$request = new WP_REST_Request( 'GET', '/wc/v4/reports/customers' );
$response = $this->server->dispatch( $request );
$reports = $response->get_data();
$this->assertEquals( 200, $response->get_status() );
$this->assertCount( 2, $reports );
$this->assertEquals( $this->customer, $reports[0]['user_id'] );
$request = new WP_REST_Request( 'GET', '/wc/v4/reports/orders' );
$response = $this->server->dispatch( $request );
$reports = $response->get_data();
$this->assertEquals( 200, $response->get_status() );
$this->assertCount( 1, $reports );
$this->assertEquals( $order_2->get_id(), $reports[0]['order_id'] );
// Use the skip existing params to skip processing customers/orders.
// Compare against order status to make sure previously imported order was skipped.
$order_2->set_status( 'processing' );
$order_2->save();
// Compare against name to make sure previously imported customer was skipped.
wp_update_user(
array(
'ID' => $this->customer,
'first_name' => 'Changed',
)
);
// Delete scheduled actions to avoid default order processing.
$wpdb->query( "DELETE FROM {$wpdb->posts} WHERE post_type = 'scheduled-action'" );
$request = new WP_REST_Request( 'POST', $this->endpoint );
$request->set_query_params( array( 'skip_existing' => '1' ) );
$response = $this->server->dispatch( $request );
$report = $response->get_data();
$this->assertEquals( 200, $response->get_status() );
$this->assertEquals( 'success', $report['status'] );
// Run pending thrice to process batch and order.
WC_Helper_Queue::run_all_pending();
WC_Helper_Queue::run_all_pending();
WC_Helper_Queue::run_all_pending();
$request = new WP_REST_Request( 'GET', '/wc/v4/reports/customers' );
$response = $this->server->dispatch( $request );
$reports = $response->get_data();
$this->assertEquals( 200, $response->get_status() );
$this->assertCount( 2, $reports );
$this->assertEquals( 'Steve User', $reports[0]['name'] );
$request = new WP_REST_Request( 'GET', '/wc/v4/reports/orders' );
$request->set_query_params( array( 'per_page' => 5 ) );
$response = $this->server->dispatch( $request );
$reports = $response->get_data();
$this->assertEquals( 200, $response->get_status() );
$this->assertCount( 2, $reports );
$this->assertEquals( 'completed', $reports[0]['status'] );
}
/**
* Test cancelling import actions.
*/
public function test_cancel_import() {
wp_set_current_user( $this->user );
// Populate all of the data.
$product = new WC_Product_Simple();
$product->set_name( 'Test Product' );
$product->set_regular_price( 25 );
$product->save();
$order = WC_Helper_Order::create_order( 1, $product );
$order->set_status( 'completed' );
$order->set_date_created( time() - ( 3 * DAY_IN_SECONDS ) );
$order->save();
// Verify there are actions to cancel.
$pending_actions = WC_Helper_Queue::get_all_pending();
$this->assertCount( 1, $pending_actions );
// Cancel outstanding actions.
$request = new WP_REST_Request( 'POST', $this->endpoint . '/cancel' );
$response = $this->server->dispatch( $request );
$report = $response->get_data();
$this->assertEquals( 200, $response->get_status() );
$this->assertEquals( 'success', $report['status'] );
// Verify there are no pending actions.
$pending_actions = WC_Helper_Queue::get_all_pending();
$this->assertCount( 0, $pending_actions );
}
/**
* Test import deletion.
*/
public function test_delete_stats() {
global $wpdb;
wp_set_current_user( $this->user );
// Populate all of the data.
$product = new WC_Product_Simple();
$product->set_name( 'Test Product' );
$product->set_regular_price( 25 );
$product->save();
for ( $i = 0; $i < 25; $i++ ) {
$order = WC_Helper_Order::create_order( 1, $product );
$order->set_status( 'completed' );
$order->save();
}
// Check that stats exist before deleting.
WC_Helper_Queue::run_all_pending();
$request = new WP_REST_Request( 'GET', '/wc/v4/reports/orders' );
$request->set_query_params( array( 'per_page' => 25 ) );
$response = $this->server->dispatch( $request );
$reports = $response->get_data();
$this->assertEquals( 200, $response->get_status() );
$this->assertCount( 25, $reports );
$request = new WP_REST_Request( 'GET', '/wc/v4/reports/customers' );
$request->set_query_params( array( 'per_page' => 25 ) );
$response = $this->server->dispatch( $request );
$reports = $response->get_data();
$this->assertEquals( 200, $response->get_status() );
$this->assertCount( 1, $reports );
// Delete all stats.
$request = new WP_REST_Request( 'POST', $this->endpoint . '/delete' );
$response = $this->server->dispatch( $request );
$report = $response->get_data();
$this->assertEquals( 200, $response->get_status() );
$this->assertEquals( 'success', $report['status'] );
// Run pending three times to process batches and dependent actions.
WC_Helper_Queue::run_all_pending();
WC_Helper_Queue::run_all_pending();
WC_Helper_Queue::run_all_pending();
// Check that stats have been deleted.
$request = new WP_REST_Request( 'GET', '/wc/v4/reports/orders' );
$response = $this->server->dispatch( $request );
$reports = $response->get_data();
$this->assertEquals( 200, $response->get_status() );
$this->assertCount( 0, $reports );
$request = new WP_REST_Request( 'GET', '/wc/v4/reports/customers' );
$response = $this->server->dispatch( $request );
$reports = $response->get_data();
$this->assertEquals( 200, $response->get_status() );
$this->assertCount( 0, $reports );
}
}

View File

@ -45,7 +45,7 @@ class WC_Tests_Reports_Regenerate_Batching extends WC_REST_Unit_Test_Case {
switch ( $action ) {
case WC_Admin_Reports_Sync::QUEUE_BATCH_ACTION:
return $this->queue_batch_size;
case WC_Admin_Reports_Sync::CUSTOMERS_BATCH_ACTION:
case WC_Admin_Reports_Sync::CUSTOMERS_IMPORT_BATCH_ACTION:
return $this->customers_batch_size;
case WC_Admin_Reports_Sync::ORDERS_BATCH_ACTION:
return $this->orders_batch_size;
@ -81,20 +81,20 @@ class WC_Tests_Reports_Regenerate_Batching extends WC_REST_Unit_Test_Case {
$num_customers = 1234; // 1234 / 5 = 247 batches
$num_batches = ceil( $num_customers / $this->customers_batch_size );
WC_Admin_Reports_Sync::queue_batches( 1, $num_batches, WC_Admin_Reports_Sync::CUSTOMERS_BATCH_ACTION );
WC_Admin_Reports_Sync::queue_batches( 1, $num_batches, WC_Admin_Reports_Sync::CUSTOMERS_IMPORT_BATCH_ACTION );
$this->assertCount( $this->queue_batch_size, $this->queue->actions );
$this->assertArraySubset(
array(
'hook' => WC_Admin_Reports_Sync::QUEUE_BATCH_ACTION,
'args' => array( 1, 25, WC_Admin_Reports_Sync::CUSTOMERS_BATCH_ACTION ),
'args' => array( 1, 25, WC_Admin_Reports_Sync::CUSTOMERS_IMPORT_BATCH_ACTION ),
),
$this->queue->actions[0]
);
$this->assertArraySubset(
array(
'hook' => WC_Admin_Reports_Sync::QUEUE_BATCH_ACTION,
'args' => array( 226, 247, WC_Admin_Reports_Sync::CUSTOMERS_BATCH_ACTION ),
'args' => array( 226, 247, WC_Admin_Reports_Sync::CUSTOMERS_IMPORT_BATCH_ACTION ),
),
$this->queue->actions[ $this->queue_batch_size - 1 ]
);
@ -107,19 +107,19 @@ class WC_Tests_Reports_Regenerate_Batching extends WC_REST_Unit_Test_Case {
$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_Reports_Sync::queue_batches( 1, $num_batches, WC_Admin_Reports_Sync::CUSTOMERS_BATCH_ACTION );
WC_Admin_Reports_Sync::queue_batches( 1, $num_batches, WC_Admin_Reports_Sync::CUSTOMERS_IMPORT_BATCH_ACTION );
$this->assertCount( 9, $this->queue->actions );
$this->assertArraySubset(
array(
'hook' => WC_Admin_Reports_Sync::CUSTOMERS_BATCH_ACTION,
'hook' => WC_Admin_Reports_Sync::CUSTOMERS_IMPORT_BATCH_ACTION,
'args' => array( 1 ),
),
$this->queue->actions[0]
);
$this->assertArraySubset(
array(
'hook' => WC_Admin_Reports_Sync::CUSTOMERS_BATCH_ACTION,
'hook' => WC_Admin_Reports_Sync::CUSTOMERS_IMPORT_BATCH_ACTION,
'args' => array( 9 ),
),
$this->queue->actions[8]

View File

@ -12,11 +12,11 @@
*/
class WC_Helper_Queue {
/**
* Run all pending queued actions.
* Get all pending queued actions.
*
* @return void
* @return array Pending jobs.
*/
public static function run_all_pending() {
public static function get_all_pending() {
$jobs = WC()->queue()->search(
array(
'per_page' => -1,
@ -25,6 +25,17 @@ class WC_Helper_Queue {
)
);
return $jobs;
}
/**
* Run all pending queued actions.
*
* @return void
*/
public static function run_all_pending() {
$jobs = self::get_all_pending();
foreach ( $jobs as $job ) {
$job->execute();
}

View File

@ -37,13 +37,13 @@ class WC_Tests_Reports_Queue_Prioritization extends WC_REST_Unit_Test_Case {
* Test that we're setting a priority on our actions.
*/
public function test_queue_action_sets_priority() {
WC_Admin_Reports_Sync::queue()->schedule_single( time(), WC_Admin_Reports_Sync::SINGLE_ORDER_ACTION );
WC_Admin_Reports_Sync::queue()->schedule_single( time(), WC_Admin_Reports_Sync::SINGLE_ORDER_IMPORT_ACTION );
$actions = WC_Admin_Reports_Sync::queue()->search(
array(
'status' => 'pending',
'claimed' => false,
'hook' => WC_Admin_Reports_Sync::SINGLE_ORDER_ACTION,
'hook' => WC_Admin_Reports_Sync::SINGLE_ORDER_IMPORT_ACTION,
)
);
@ -55,7 +55,7 @@ class WC_Tests_Reports_Queue_Prioritization extends WC_REST_Unit_Test_Case {
$this->assertEquals( WC_Admin_ActionScheduler_wpPostStore::JOB_PRIORITY, $action->menu_order );
WC_Admin_Reports_Sync::queue()->cancel_all( WC_Admin_Reports_Sync::SINGLE_ORDER_ACTION );
WC_Admin_Reports_Sync::queue()->cancel_all( WC_Admin_Reports_Sync::SINGLE_ORDER_IMPORT_ACTION );
}
}