HPOS: Implement data synchronization for deleted orders.

This includes the following:

- Fix the "upshifting" that was implemented in 36218 so that it only
  applies when the post type of the order being deleted is hierarchical
  (this conforms to the WordPress behavior when deleting a post).

- Now when an order is deleted while sync is off a record will be
  created in wp_wc_orders_meta (regardless of which table is the
  authoritative one) with 'deleted_from' as the key and the
  authoritative table name (from where the order has been deleted)
  as the value...

- ...then DataSynchronizer will detect the presence of these
  'deleted_from' records and delete them from the backup table
  as part of the batch processing procedure.

- Exception to the above: when an order is deleted from the orders
  table and the corresponding records in the posts table are
  placeholders, these are deleted immediately too, even if sync is off.

- Add an "order_exists" method in both order data stores
  (Abstract_WC_Order_Data_Store_CPT and OrdersTableDataStore).

- Add a pair of get/set_verify_parent_id methods in WC_Abstract_Order
  (turning off the check in set_parent_id is necessary when deleting
  an order using the backup data store during synchronization).

- Placeholder records are now created with the appropriate parent id
  for child orders (e.g. refund), this is necessary in order to
  properly find which records need to be deleted.
This commit is contained in:
Nestor Soriano 2023-03-16 17:46:28 +01:00
parent 55af1d1365
commit 23a605c14c
No known key found for this signature in database
GPG Key ID: 08110F3518C12CAD
6 changed files with 437 additions and 52 deletions

View File

@ -101,6 +101,14 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
*/
protected $object_type = 'order';
/**
* Indicates if set_parent_id will throw an error if no order exists with the supplied id.
*
* @since 7.7.0
* @var bool
*/
private $verify_parent_id = true;
/**
* Get the order if ID is passed, otherwise the order is new and empty.
* This class should NOT be instantiated, but the wc_get_order function or new WC_Order_Factory
@ -569,10 +577,10 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
*
* @since 3.0.0
* @param int $value Value to set.
* @throws WC_Data_Exception Exception thrown if parent ID does not exist or is invalid.
* @throws WC_Data_Exception Exception thrown if parent ID does not exist or is invalid (only if $verify_parent_id is true).
*/
public function set_parent_id( $value ) {
if ( $value && ( $value === $this->get_id() || ! wc_get_order( $value ) ) ) {
if ( $this->verify_parent_id && $value && ( $value === $this->get_id() || ! wc_get_order( $value ) ) ) {
$this->error( 'order_invalid_parent_id', __( 'Invalid parent ID', 'woocommerce' ) );
}
$this->set_prop( 'parent_id', absint( $value ) );
@ -2321,4 +2329,27 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
return __( 'Order', 'woocommerce' );
}
}
/**
* Sets the value of the $verify_parent_id flag.
*
* If the flag is set, set_parent_id will throw an error if no order exists with the supplied id.
*
* @param bool $value
* @return void
*/
public function set_verify_parent_id( bool $value ) {
$this->verify_parent_id = $value;
}
/**
* Gets the value of the $verify_parent_id flag.
*
* If the flag is set, set_parent_id will throw an error if no order exists with the supplied id.
*
* @return bool
*/
public function get_verify_parent_id(): bool {
return $this->verify_parent_id;
}
}

View File

@ -106,6 +106,23 @@ abstract class Abstract_WC_Order_Data_Store_CPT extends WC_Data_Store_WP impleme
}
}
/**
* Check if an order exists by id.
*
* @since 7.7.0
*
* @param int $order_id The order id to check.
* @return bool True if an order exists with the given name.
*/
public function order_exists( $order_id ) : bool {
if ( ! $order_id ) {
return false;
}
$post_object = get_post( $order_id );
return ! is_null( $post_object ) && in_array( $post_object->post_type, wc_get_order_types(), true );
}
/**
* Method to read an order from the database.
*

View File

@ -34,6 +34,8 @@ class DataSynchronizer implements BatchProcessorInterface {
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;
public const ID_TYPE_DELETED_FROM_ORDERS_TABLE = 3;
public const ID_TYPE_DELETED_FROM_POSTS_TABLE = 4;
/**
* The data store object to use.
@ -69,6 +71,7 @@ class DataSynchronizer implements BatchProcessorInterface {
public function __construct() {
self::add_action( 'deleted_post', array( $this, 'handle_deleted_post' ), 10, 2 );
self::add_action( 'woocommerce_new_order', array( $this, 'handle_updated_order' ), 100 );
self::add_action( 'woocommerce_refund_created', array( $this, 'handle_updated_order' ), 100 );
self::add_action( 'woocommerce_update_order', array( $this, 'handle_updated_order' ), 100 );
self::add_filter( 'woocommerce_feature_description_tip', array( $this, 'handle_feature_description_tip' ), 10, 3 );
}
@ -228,6 +231,16 @@ SELECT(
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
$pending_count = (int) $wpdb->get_var( $sql );
$deleted_from_table = $this->custom_orders_table_is_authoritative() ? $orders_table : $wpdb->posts;
$deleted_count = $wpdb->get_var(
$wpdb->prepare(
"SELECT count(1) FROM {$wpdb->prefix}wc_orders_meta WHERE meta_key=%s AND meta_value=%s",
array( 'deleted_from', $deleted_from_table )
)
);
$pending_count += $deleted_count;
wp_cache_set( 'woocommerce_hpos_pending_sync_count', $pending_count );
return $pending_count;
}
@ -249,6 +262,8 @@ SELECT(
* 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.
* ID_TYPE_DELETED_FROM_ORDERS_TABLE: orders deleted from the orders table but not yet from the posts table.
* ID_TYPE_DELETED_FROM_POSTS_TABLE: orders deleted from the posts table but not yet from the orders table.
*
* @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.
@ -305,6 +320,10 @@ WHERE
);
// phpcs:enable
break;
case self::ID_TYPE_DELETED_FROM_ORDERS_TABLE:
return $this->get_deleted_order_ids( true, $limit );
case self::ID_TYPE_DELETED_FROM_POSTS_TABLE:
return $this->get_deleted_order_ids( false, $limit );
default:
throw new \Exception( 'Invalid $type, must be one of the ID_TYPE_... constants.' );
}
@ -314,6 +333,29 @@ WHERE
return array_map( 'intval', $wpdb->get_col( $sql . " LIMIT $limit" ) );
}
/**
* Get the ids of the orders that are marked as deleted in the orders meta table.
*
* @param bool $deleted_from_orders_table True to get the ids of the orders deleted from the orders table, false o get the ids of the orders deleted from the posts table.
* @param int $limit The maximum count of orders to return.
* @return array An array of order ids.
*/
private function get_deleted_order_ids( bool $deleted_from_orders_table, int $limit ) {
global $wpdb;
$deleted_from_table = $deleted_from_orders_table ? $this->data_store::get_orders_table_name() : $wpdb->posts;
$order_ids = $wpdb->get_col(
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$wpdb->prepare(
"SELECT DISTINCT(order_id) FROM {$wpdb->prefix}wc_orders_meta WHERE meta_key=%s AND meta_value=%s LIMIT {$limit}",
array( 'deleted_from', $deleted_from_table )
)
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
);
return $order_ids;
}
/**
* Cleanup all the synchronization status information,
* because the process has been disabled by the user via settings,
@ -329,7 +371,12 @@ WHERE
* @param array $batch Batch details.
*/
public function process_batch( array $batch ) : void {
if ( $this->custom_orders_table_is_authoritative() ) {
$custom_orders_table_is_authoritative = $this->custom_orders_table_is_authoritative();
$deleted_order_ids = $this->process_deleted_orders( $batch, $custom_orders_table_is_authoritative );
$batch = array_diff( $batch, $deleted_order_ids );
if ( ! empty( $batch ) ) {
if ( $custom_orders_table_is_authoritative ) {
foreach ( $batch as $id ) {
$order = wc_get_order( $id );
if ( ! $order ) {
@ -342,11 +389,99 @@ WHERE
} else {
$this->posts_to_cot_migrator->migrate_orders( $batch );
}
}
if ( 0 === $this->get_total_pending_count() ) {
$this->cleanup_synchronization_state();
}
}
/**
* Take a batch of order ids pending synchronization and process those that were deleted, ignoring the others
* (which will be orders that were created or modified) and returning the ids of the orders actually processed.
*
* @param array $batch Array of ids of order pending synchronization.
* @param bool $custom_orders_table_is_authoritative True if the custom orders table is currently authoritative.
* @return array Order ids that have been actually processed.
*/
private function process_deleted_orders( array $batch, bool $custom_orders_table_is_authoritative ): array {
global $wpdb;
$deleted_from_table_name = $custom_orders_table_is_authoritative ? $this->data_store::get_orders_table_name() : $wpdb->posts;
$data_store_for_deletion =
$custom_orders_table_is_authoritative ?
new \WC_Order_Data_Store_CPT() :
wc_get_container()->get( OrdersTableDataStore::class );
$order_ids_as_sql_list = '(' . implode( ',', $batch ) . ')';
$deleted_order_ids = array();
$meta_ids_to_delete = array();
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$deletion_data = $wpdb->get_results(
$wpdb->prepare(
"SELECT id, order_id FROM {$wpdb->prefix}wc_orders_meta WHERE meta_key=%s AND meta_value=%s AND order_id IN $order_ids_as_sql_list ORDER BY order_id DESC",
'deleted_from',
$deleted_from_table_name
),
ARRAY_A
);
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
if ( empty( $deletion_data ) ) {
return array();
}
foreach ( $deletion_data as $item ) {
$meta_id = $item['id'];
$order_id = $item['order_id'];
if ( isset( $deleted_order_ids[ $order_id ] ) ) {
$meta_ids_to_delete[] = $meta_id;
continue;
}
if ( ! $data_store_for_deletion->order_exists( $order_id ) ) {
$this->error_logger->warning( "Order {$order_id} doesn't exist in the backup table, thus it can't be deleted" );
$deleted_order_ids[] = $order_id;
$meta_ids_to_delete[] = $meta_id;
continue;
}
try {
$order = new \WC_Order();
//We need to turn parent order verification off to avoid an exception for child orders (e.g. refunds).
//The 'read' method in the order class invokes 'set_parent_id', which by default verifies
//if the order the parent id refers to actually exists, and throws an exception if not.
//To check if the order exists 'wc_get_order' is used, but this function searches the order
//using the authoritative data store, and at this point the order doesn't exist anymore in the
//authoritative table ('read' below is being invoked in the backup data store).
$order->set_verify_parent_id( false );
$order->set_id( $order_id );
$data_store_for_deletion->read( $order );
$data_store_for_deletion->delete( $order, array( 'force_delete' => true ) );
} catch ( \Exception $ex ) {
$this->error_logger->error( "Couldn't delete order {$order_id} from the backup table: {$ex->getMessage()}" );
continue;
}
$deleted_order_ids[] = $order_id;
$meta_ids_to_delete[] = $meta_id;
}
if ( ! empty( $meta_ids_to_delete ) ) {
$order_id_rows_as_sql_list = '(' . implode( ',', $meta_ids_to_delete ) . ')';
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$wpdb->query( "DELETE FROM {$wpdb->prefix}wc_orders_meta WHERE id IN {$order_id_rows_as_sql_list}" );
}
return $deleted_order_ids;
}
/**
* Get total number of pending records that require update.
*
@ -364,19 +499,33 @@ WHERE
* @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 );
}
$orders_table_is_authoritative = $this->custom_orders_table_is_authoritative();
$order_ids = $this->get_ids_of_orders_pending_sync(
$orders_table_is_authoritative ? self::ID_TYPE_MISSING_IN_POSTS_TABLE : 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 ) );
if ( count( $order_ids ) > 0 ) {
return $order_ids;
}
// Deleted orders pending sync have the lowest priority, and additionally,
// if such orders exist then we return just those, even if they don't fill a batch.
// Returning "missing orders that are to be deleted" and "missing orders that are to be created"
// in the same batch is confusing and potentially dangerous.
$deleted_order_ids = $this->get_ids_of_orders_pending_sync(
$orders_table_is_authoritative ? self::ID_TYPE_DELETED_FROM_ORDERS_TABLE : self::ID_TYPE_DELETED_FROM_POSTS_TABLE,
$size
);
return $deleted_order_ids;
}
/**
* Default batch size to use.
*
@ -426,9 +575,42 @@ WHERE
* @param WP_Post $post The deleted post.
*/
private function handle_deleted_post( $postid, $post ): void {
if ( 'shop_order' === $post->post_type && $this->data_sync_is_enabled() ) {
$this->data_store->delete_order_data_from_custom_order_tables( $postid );
global $wpdb;
if ( 'shop_order' !== $post->post_type && 'shop_order_refund' !== $post->post_type ) {
return;
}
$features_controller = wc_get_container()->get( FeaturesController::class );
$feature_is_enabled = $features_controller->feature_is_enabled( 'custom_order_tables' );
if ( ! $feature_is_enabled ) {
return;
}
if ( $this->data_sync_is_enabled() ) {
$this->data_store->delete_order_data_from_custom_order_tables( $postid );
} elseif ( $this->custom_orders_table_is_authoritative() ) {
return;
}
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.SlowDBQuery
if ( $wpdb->get_var(
$wpdb->prepare(
"SELECT EXISTS (SELECT id FROM {$this->data_store::get_orders_table_name()} WHERE ID=%d)",
$postid
)
)
) {
$wpdb->insert(
$this->data_store::get_meta_table_name(),
array(
'order_id' => $postid,
'meta_key' => 'deleted_from',
'meta_value' => $wpdb->posts,
)
);
}
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.SlowDBQuery
}
/**

View File

@ -116,6 +116,20 @@ class OrdersTableDataStore extends \Abstract_WC_Order_Data_Store_CPT implements
*/
private $error_logger;
/**
* The name of the main orders table.
*
* @var string
*/
private $orders_table_name;
/**
* The instance of the LegacyProxy object to use.
*
* @var LegacyProxy
*/
private $legacy_proxy;
/**
* Initialize the object.
*
@ -129,8 +143,11 @@ class OrdersTableDataStore extends \Abstract_WC_Order_Data_Store_CPT implements
final public function init( OrdersTableDataStoreMeta $data_store_meta, DatabaseUtil $database_util, LegacyProxy $legacy_proxy ) {
$this->data_store_meta = $data_store_meta;
$this->database_util = $database_util;
$this->legacy_proxy = $legacy_proxy;
$this->error_logger = $legacy_proxy->call_function( 'wc_get_logger' );
$this->internal_meta_keys = $this->get_internal_meta_keys();
$this->orders_table_name = self::get_orders_table_name();
}
/**
@ -955,6 +972,29 @@ WHERE
return $type[ $order_id ] ?? '';
}
/**
* Check if an order exists by id.
*
* @since 7.7.0
*
* @param int $order_id The order id to check.
* @return bool True if an order exists with the given name.
*/
public function order_exists( $order_id ) : bool {
global $wpdb;
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$exists = $wpdb->get_var(
$wpdb->prepare(
"SELECT EXISTS (SELECT id FROM {$this->orders_table_name} WHERE id=%d)",
$order_id
)
);
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
return (bool) $exists;
}
/**
* Method to read an order from custom tables.
*
@ -1576,6 +1616,7 @@ FROM $order_meta_table
array(
'post_type' => $data_sync->data_sync_is_enabled() ? $order->get_type() : $data_sync::PLACEHOLDER_ORDER_POST_TYPE,
'post_status' => 'draft',
'post_parent' => $order->get_changes()['parent_id'] ?? $order->get_data()['parent_id'] ?? 0,
)
);
@ -1761,18 +1802,24 @@ FROM $order_meta_table
*/
do_action( 'woocommerce_before_delete_order', $order_id, $order );
$this->upshift_child_orders( $order );
$this->upshift_or_delete_child_orders( $order );
$this->delete_order_data_from_custom_order_tables( $order_id );
$this->delete_items( $order );
$order->set_id( 0 );
// Only delete post data if the posts table is authoritative and synchronization is enabled.
// Only delete post data if the orders table is authoritative and synchronization is enabled.
$orders_table_is_authoritative = $order->get_data_store()->get_current_class_name() === self::class;
if ( $orders_table_is_authoritative ) {
$data_synchronizer = wc_get_container()->get( DataSynchronizer::class );
if ( $data_synchronizer->data_sync_is_enabled() && $order->get_data_store()->get_current_class_name() === self::class ) {
if ( $data_synchronizer->data_sync_is_enabled() ) {
// Delete the associated post, which in turn deletes order items, etc. through {@see WC_Post_Data}.
// Once we stop creating posts for orders, we should do the cleanup here instead.
wp_delete_post( $order_id );
} else {
$this->handle_order_deletion_with_sync_disabled( $order_id );
}
}
do_action( 'woocommerce_delete_order', $order_id ); // phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
@ -1794,23 +1841,89 @@ FROM $order_meta_table
}
/**
* Helper method to set child orders to the parent order's parent.
* Handles the deletion of an order from the orders table when sync is disabled:
*
* If the corresponding row in the posts table is of placeholder type,
* it's just deleted; otherwise a "deleted_from" record is created in the meta table
* and the sync process will detect these and take care of deleting the appropriate post records.
*
* @param int $order_id Th id of the order that has been deleted from the orders table.
* @return void
*/
protected function handle_order_deletion_with_sync_disabled( $order_id ): void {
global $wpdb;
$post_type = $wpdb->get_var(
$wpdb->prepare( "SELECT post_type FROM {$wpdb->posts} WHERE ID=%d", $order_id )
);
if ( DataSynchronizer::PLACEHOLDER_ORDER_POST_TYPE === $post_type ) {
$wpdb->query(
$wpdb->prepare(
"DELETE FROM {$wpdb->posts} WHERE ID=%d OR post_parent=%d",
$order_id,
$order_id
)
);
} else {
$related_order_ids = $wpdb->get_col(
$wpdb->prepare(
"SELECT id FROM {$wpdb->posts} WHERE post_parent = %d",
$order_id
)
);
$related_order_ids[] = $order_id;
// phpcs:disable WordPress.DB.SlowDBQuery
foreach ( $related_order_ids as $id ) {
$wpdb->insert(
self::get_meta_table_name(),
array(
'order_id' => $id,
'meta_key' => 'deleted_from',
'meta_value' => self::get_orders_table_name(),
)
);
}
// phpcs:enable WordPress.DB.SlowDBQuery
}
}
/**
* Set the parent id of child orders to the parent order's parent if the post type
* for the order is hierarchical, just deletes the child orders otherwise.
*
* @param \WC_Abstract_Order $order Order object.
*
* @return void
*/
private function upshift_child_orders( $order ) {
private function upshift_or_delete_child_orders( $order ) {
global $wpdb;
$order_table = self::get_orders_table_name();
$order_parent = $order->get_parent_id();
$order_parent_id = $order->get_parent_id();
if ( $this->legacy_proxy->call_function( 'is_post_type_hierarchical', $order->get_type() ) ) {
$wpdb->update(
$order_table,
array( 'parent_order_id' => $order_parent ),
array( 'parent_order_id' => $order_parent_id ),
array( 'parent_order_id' => $order->get_id() ),
array( '%d' ),
array( '%d' )
);
} else {
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$child_order_ids = $wpdb->get_col(
$wpdb->prepare(
"SELECT id FROM $order_table WHERE parent_order_id=%d",
$order->get_id()
)
);
foreach ( $child_order_ids as $child_order_id ) {
$this->delete_order_data_from_custom_order_tables( $child_order_id );
}
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
}
}
/**

View File

@ -66,14 +66,18 @@ class OrdersTableRefundDataStore extends OrdersTableDataStore {
$this->delete_order_data_from_custom_order_tables( $refund_id );
$refund->set_id( 0 );
// If this datastore method is called while the posts table is authoritative, refrain from deleting post data.
if ( ! is_a( $refund->get_data_store(), self::class ) ) {
return;
}
$orders_table_is_authoritative = $refund->get_data_store()->get_current_class_name() === self::class;
if ( $orders_table_is_authoritative ) {
$data_synchronizer = wc_get_container()->get( DataSynchronizer::class );
if ( $data_synchronizer->data_sync_is_enabled() ) {
// Delete the associated post, which in turn deletes order items, etc. through {@see WC_Post_Data}.
// Once we stop creating posts for orders, we should do the cleanup here instead.
wp_delete_post( $refund_id );
} else {
$this->handle_order_deletion_with_sync_disabled( $refund_id );
}
}
}
/**

View File

@ -43,6 +43,7 @@ class OrdersTableDataStoreTests extends WC_Unit_Test_Case {
* Initializes system under test.
*/
public function setUp(): void {
$this->reset_legacy_proxy_mocks();
$this->original_time_zone = wp_timezone_string();
//phpcs:ignore WordPress.DateTime.RestrictedFunctions.timezone_change_date_default_timezone_set -- We need to change the timezone to test the date sync fields.
update_option( 'timezone_string', 'Asia/Kolkata' );
@ -50,8 +51,10 @@ class OrdersTableDataStoreTests extends WC_Unit_Test_Case {
// Remove the Test Suites use of temporary tables https://wordpress.stackexchange.com/a/220308.
$this->setup_cot();
$this->toggle_cot( false );
$this->sut = wc_get_container()->get( OrdersTableDataStore::class );
$this->migrator = wc_get_container()->get( PostsToOrdersMigrationController::class );
$container = wc_get_container();
$container->reset_all_resolved();
$this->sut = $container->get( OrdersTableDataStore::class );
$this->migrator = $container->get( PostsToOrdersMigrationController::class );
$this->cpt_data_store = new WC_Order_Data_Store_CPT();
}
@ -2004,9 +2007,17 @@ class OrdersTableDataStoreTests extends WC_Unit_Test_Case {
}
/**
* @testDox When parent order is deleted, child orders should be upshifted.
* @testDox When parent order is deleted, and the post order type is hierarchical, child orders should be upshifted.
*/
public function test_child_orders_are_promoted_when_parent_is_deleted() {
public function test_child_orders_are_promoted_when_parent_is_deleted_if_order_type_is_hierarchical() {
$this->register_legacy_proxy_function_mocks(
array(
'is_post_type_hierarchical' => function( $post_type ) {
return 'shop_order' === $post_type || is_post_type_hierarchical( $post_type );
},
)
);
$this->toggle_cot( true );
$order = new WC_Order();
$order->save();
@ -2022,6 +2033,33 @@ class OrdersTableDataStoreTests extends WC_Unit_Test_Case {
$this->assertEquals( 0, $child_order->get_parent_id() );
}
/**
* @testDox When parent order is deleted, and the post order type is NOT hierarchical, child orders should be deleted.
*/
public function test_child_orders_are_promoted_when_parent_is_deleted_if_order_type_is_not_hierarchical() {
$this->register_legacy_proxy_function_mocks(
array(
'is_post_type_hierarchical' => function( $post_type ) {
return 'shop_order' === $post_type ? false : is_post_type_hierarchical( $post_type );
},
)
);
$this->toggle_cot( true );
$order = new WC_Order();
$order->save();
$child_order = new WC_Order();
$child_order->set_parent_id( $order->get_id() );
$child_order->save();
$this->assertEquals( $order->get_id(), $child_order->get_parent_id() );
$this->sut->delete( $order, array( 'force_delete' => true ) );
$child_order = wc_get_order( $child_order->get_id() );
$this->assertFalse( $child_order );
}
/**
* @testDox Make sure get_order return false when checking an order of different order types without warning.
*/