File "DataSynchronizer.php"
Full Path: /home/warrior1/public_html/wp-content/plugins/woocommerce/src/Internal/DataStores/Orders/DataSynchronizer.php
File size: 12.05 KB
MIME-type: text/x-php
Charset: utf-8
<?php
/**
* DataSynchronizer class file.
*/
namespace Automattic\WooCommerce\Internal\DataStores\Orders;
use Automattic\WooCommerce\Database\Migrations\CustomOrderTable\PostsToOrdersMigrationController;
use Automattic\WooCommerce\Internal\BatchProcessing\BatchProcessorInterface;
use Automattic\WooCommerce\Internal\Utilities\DatabaseUtil;
defined( 'ABSPATH' ) || exit;
/**
* This class handles the database structure creation and the data synchronization for the custom orders tables. Its responsibilites are:
*
* - Providing entry points for creating and deleting the required database tables.
* - Synchronizing changes between the custom orders tables and the posts table whenever changes in orders happen.
*/
class DataSynchronizer implements BatchProcessorInterface {
public const ORDERS_DATA_SYNC_ENABLED_OPTION = 'woocommerce_custom_orders_table_data_sync_enabled';
private const INITIAL_ORDERS_PENDING_SYNC_COUNT_OPTION = 'woocommerce_initial_orders_pending_sync_count';
public const PENDING_SYNCHRONIZATION_FINISHED_ACTION = 'woocommerce_orders_sync_finished';
public const PLACEHOLDER_ORDER_POST_TYPE = 'shop_order_placehold';
private const ORDERS_SYNC_BATCH_SIZE = 250;
// Allowed values for $type in get_ids_of_orders_pending_sync method.
public const ID_TYPE_MISSING_IN_ORDERS_TABLE = 0;
public const ID_TYPE_MISSING_IN_POSTS_TABLE = 1;
public const ID_TYPE_DIFFERENT_UPDATE_DATE = 2;
/**
* The data store object to use.
*
* @var OrdersTableDataStore
*/
private $data_store;
/**
* The database util object to use.
*
* @var DatabaseUtil
*/
private $database_util;
/**
* The posts to COT migrator to use.
*
* @var PostsToOrdersMigrationController
*/
private $posts_to_cot_migrator;
/**
* Class constructor.
*/
public function __construct() {
// When posts is authoritative and sync is enabled, deleting a post also deletes COT data.
add_action(
'deleted_post',
function( $postid, $post ) {
if ( 'shop_order' === $post->post_type && ! $this->custom_orders_table_is_authoritative() && $this->data_sync_is_enabled() ) {
$this->data_store->delete_order_data_from_custom_order_tables( $postid );
}
},
10,
2
);
}
/**
* Class initialization, invoked by the DI container.
*
* @param OrdersTableDataStore $data_store The data store to use.
* @param DatabaseUtil $database_util The database util class to use.
* @param PostsToOrdersMigrationController $posts_to_cot_migrator The posts to COT migration class to use.
*@internal
*/
final public function init( OrdersTableDataStore $data_store, DatabaseUtil $database_util, PostsToOrdersMigrationController $posts_to_cot_migrator ) {
$this->data_store = $data_store;
$this->database_util = $database_util;
$this->posts_to_cot_migrator = $posts_to_cot_migrator;
}
/**
* Does the custom orders tables exist in the database?
*
* @return bool True if the custom orders tables exist in the database.
*/
public function check_orders_table_exists(): bool {
$missing_tables = $this->database_util->get_missing_tables( $this->data_store->get_database_schema() );
return count( $missing_tables ) === 0;
}
/**
* Create the custom orders database tables.
*/
public function create_database_tables() {
$this->database_util->dbdelta( $this->data_store->get_database_schema() );
}
/**
* Delete the custom orders database tables.
*/
public function delete_database_tables() {
$table_names = $this->data_store->get_all_table_names();
foreach ( $table_names as $table_name ) {
$this->database_util->drop_database_table( $table_name );
}
}
/**
* Is the data sync between old and new tables currently enabled?
*
* @return bool
*/
public function data_sync_is_enabled(): bool {
return 'yes' === get_option( self::ORDERS_DATA_SYNC_ENABLED_OPTION );
}
/**
* Get the current sync process status.
* The information is meaningful only if pending_data_sync_is_in_progress return true.
*
* @return array
*/
public function get_sync_status() {
return array(
'initial_pending_count' => (int) get_option( self::INITIAL_ORDERS_PENDING_SYNC_COUNT_OPTION, 0 ),
'current_pending_count' => $this->get_total_pending_count(),
);
}
/**
* Calculate how many orders need to be synchronized currently.
* A database query is performed to get how many orders match one of the following:
*
* - Existing in the authoritative table but not in the backup table.
* - Existing in both tables, but they have a different update date.
*/
public function get_current_orders_pending_sync_count(): int {
global $wpdb;
$orders_table = $this->data_store::get_orders_table_name();
$order_post_types = wc_get_order_types( 'cot-migration' );
$order_post_type_placeholder = implode( ', ', array_fill( 0, count( $order_post_types ), '%s' ) );
if ( $this->custom_orders_table_is_authoritative() ) {
$missing_orders_count_sql = "
SELECT COUNT(1) FROM $wpdb->posts posts
INNER JOIN $orders_table orders ON posts.id=orders.id
WHERE posts.post_type = '" . self::PLACEHOLDER_ORDER_POST_TYPE . "'
AND orders.status not in ( 'auto-draft' )
";
$operator = '>';
} else {
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- $order_post_type_placeholder is prepared.
$missing_orders_count_sql = $wpdb->prepare(
"
SELECT COUNT(1) FROM $wpdb->posts posts
LEFT JOIN $orders_table orders ON posts.id=orders.id
WHERE
posts.post_type in ($order_post_type_placeholder)
AND posts.post_status != 'auto-draft'
AND orders.id IS NULL",
$order_post_types
);
// phpcs:enable
$operator = '<';
}
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- $missing_orders_count_sql is prepared.
$sql = $wpdb->prepare(
"
SELECT(
($missing_orders_count_sql)
+
(SELECT COUNT(1) FROM (
SELECT orders.id FROM $orders_table orders
JOIN $wpdb->posts posts on posts.ID = orders.id
WHERE
posts.post_type IN ($order_post_type_placeholder)
AND orders.date_updated_gmt $operator posts.post_modified_gmt
) x)
) count",
$order_post_types
);
// phpcs:enable
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
return (int) $wpdb->get_var( $sql );
}
/**
* Is the custom orders table the authoritative data source for orders currently?
*
* @return bool Whether the custom orders table the authoritative data source for orders currently.
*/
public function custom_orders_table_is_authoritative(): bool {
return wc_string_to_bool( get_option( CustomOrdersTableController::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION ) );
}
/**
* Get a list of ids of orders than are out of sync.
*
* Valid values for $type are:
*
* ID_TYPE_MISSING_IN_ORDERS_TABLE: orders that exist in posts table but not in orders table.
* ID_TYPE_MISSING_IN_POSTS_TABLE: orders that exist in orders table but not in posts table (the corresponding post entries are placeholders).
* ID_TYPE_DIFFERENT_UPDATE_DATE: orders that exist in both tables but have different last update dates.
*
* @param int $type One of ID_TYPE_MISSING_IN_ORDERS_TABLE, ID_TYPE_MISSING_IN_POSTS_TABLE, ID_TYPE_DIFFERENT_UPDATE_DATE.
* @param int $limit Maximum number of ids to return.
* @return array An array of order ids.
* @throws \Exception Invalid parameter.
*/
public function get_ids_of_orders_pending_sync( int $type, int $limit ) {
global $wpdb;
if ( $limit < 1 ) {
throw new \Exception( '$limit must be at least 1' );
}
$orders_table = $this->data_store::get_orders_table_name();
$order_post_types = wc_get_order_types( 'cot-migration' );
$order_post_type_placeholders = implode( ', ', array_fill( 0, count( $order_post_types ), '%s' ) );
switch ( $type ) {
case self::ID_TYPE_MISSING_IN_ORDERS_TABLE:
$sql = $wpdb->prepare(
"
SELECT posts.ID FROM $wpdb->posts posts
LEFT JOIN $orders_table orders ON posts.ID = orders.id
WHERE
posts.post_type IN ($order_post_type_placeholders)
AND posts.post_status != 'auto-draft'
AND orders.id IS NULL",
$order_post_types
);
break;
case self::ID_TYPE_MISSING_IN_POSTS_TABLE:
$sql = "
SELECT posts.ID FROM $wpdb->posts posts
INNER JOIN $orders_table orders ON posts.id=orders.id
WHERE posts.post_type = '" . self::PLACEHOLDER_ORDER_POST_TYPE . "'
AND orders.status not in ( 'auto-draft' )
";
break;
case self::ID_TYPE_DIFFERENT_UPDATE_DATE:
$operator = $this->custom_orders_table_is_authoritative() ? '>' : '<';
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- $order_post_type_placeholders is prepared.
$sql = $wpdb->prepare(
"
SELECT orders.id FROM $orders_table orders
JOIN $wpdb->posts posts on posts.ID = orders.id
WHERE
posts.post_type IN ($order_post_type_placeholders)
AND orders.date_updated_gmt $operator posts.post_modified_gmt
",
$order_post_types
);
// phpcs:enable
break;
default:
throw new \Exception( 'Invalid $type, must be one of the ID_TYPE_... constants.' );
}
// phpcs:ignore WordPress.DB
return array_map( 'intval', $wpdb->get_col( $sql . " LIMIT $limit" ) );
}
/**
* Cleanup all the synchronization status information,
* because the process has been disabled by the user via settings,
* or because there's nothing left to synchronize.
*/
public function cleanup_synchronization_state() {
delete_option( self::INITIAL_ORDERS_PENDING_SYNC_COUNT_OPTION );
}
/**
* Process data for current batch.
*
* @param array $batch Batch details.
*/
public function process_batch( array $batch ) : void {
if ( $this->custom_orders_table_is_authoritative() ) {
foreach ( $batch as $id ) {
$order = wc_get_order( $id );
$data_store = $order->get_data_store();
$data_store->backfill_post_record( $order );
}
} else {
$this->posts_to_cot_migrator->migrate_orders( $batch );
}
if ( 0 === $this->get_total_pending_count() ) {
$this->cleanup_synchronization_state();
}
}
/**
* Get total number of pending records that require update.
*
* @return int Number of pending records.
*/
public function get_total_pending_count(): int {
return $this->get_current_orders_pending_sync_count();
}
/**
* Returns the batch with records that needs to be processed for a given size.
*
* @param int $size Size of the batch.
*
* @return array Batch of records.
*/
public function get_next_batch_to_process( int $size ): array {
if ( $this->custom_orders_table_is_authoritative() ) {
$order_ids = $this->get_ids_of_orders_pending_sync( self::ID_TYPE_MISSING_IN_POSTS_TABLE, $size );
} else {
$order_ids = $this->get_ids_of_orders_pending_sync( self::ID_TYPE_MISSING_IN_ORDERS_TABLE, $size );
}
if ( count( $order_ids ) >= $size ) {
return $order_ids;
}
$order_ids = $order_ids + $this->get_ids_of_orders_pending_sync( self::ID_TYPE_DIFFERENT_UPDATE_DATE, $size - count( $order_ids ) );
return $order_ids;
}
/**
* Default batch size to use.
*
* @return int Default batch size.
*/
public function get_default_batch_size(): int {
$batch_size = self::ORDERS_SYNC_BATCH_SIZE;
if ( $this->custom_orders_table_is_authoritative() ) {
// Back-filling is slower than migration.
$batch_size = absint( self::ORDERS_SYNC_BATCH_SIZE / 10 ) + 1;
}
/**
* Filter to customize the count of orders that will be synchronized in each step of the custom orders table to/from posts table synchronization process.
*
* @since 6.6.0
*
* @param int Default value for the count.
*/
return apply_filters( 'woocommerce_orders_cot_and_posts_sync_step_size', $batch_size );
}
/**
* A user friendly name for this process.
*
* @return string Name of the process.
*/
public function get_name(): string {
return 'Order synchronizer';
}
/**
* A user friendly description for this process.
*
* @return string Description.
*/
public function get_description(): string {
return 'Synchronizes orders between posts and custom order tables.';
}
}