[COT] Implement create() method in COT datastore (#33341)
This commit is contained in:
parent
04bef970ef
commit
a80e0fe203
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Implement `create()` in COT datastore.
|
|
@ -318,7 +318,7 @@ class OrdersTableDataStore extends \Abstract_WC_Order_Data_Store_CPT implements
|
|||
),
|
||||
'order_stock_reduced' => array(
|
||||
'type' => 'bool',
|
||||
'name' => 'stock_reduced',
|
||||
'name' => 'order_stock_reduced',
|
||||
),
|
||||
'date_paid_gmt' => array(
|
||||
'type' => 'date',
|
||||
|
@ -539,7 +539,28 @@ class OrdersTableDataStore extends \Abstract_WC_Order_Data_Store_CPT implements
|
|||
public function set_stock_reduced( $order, $set, $save = true ) {
|
||||
// XXX implement $save = true.
|
||||
$order = is_numeric( $order ) ? wc_get_order( $order ) : $order;
|
||||
return $order->update_meta_data( '_order_stock_reduced', wc_string_to_bool( $set ) );
|
||||
return $order->update_meta_data( '_order_stock_reduced', wc_bool_to_string( $set ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper getter for `order_stock_reduced`.
|
||||
*
|
||||
* @param \WC_Order $order Order object.
|
||||
* @return bool Whether stock was reduced.
|
||||
*/
|
||||
private function get_order_stock_reduced( $order ) {
|
||||
return $this->get_stock_reduced( $order );
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper setter for `order_stock_reduced`.
|
||||
*
|
||||
* @param \WC_Order $order Order ID or order object.
|
||||
* @param bool $set Whether stock was reduced.
|
||||
* @param bool $save Whether to persist changes to db immediately or not.
|
||||
*/
|
||||
private function set_order_stock_reduced( $order, $set, $save = true ) {
|
||||
return $this->set_stock_reduced( $order, $set, $save );
|
||||
}
|
||||
|
||||
//phpcs:disable Squiz.Commenting, Generic.Commenting
|
||||
|
@ -801,86 +822,144 @@ LEFT JOIN {$operational_data_clauses['join']}
|
|||
* Persists order changes to the database.
|
||||
*
|
||||
* @param \WC_Order $order The order.
|
||||
* @param boolean $only_changes Whether to persist all order data or just changes in the object.
|
||||
* @return void
|
||||
* @throws \Exception If order data is not valid.
|
||||
*
|
||||
* @since 6.8.0
|
||||
*/
|
||||
protected function persist_order_to_db( $order, $only_changes = true ) {
|
||||
protected function persist_order_to_db( &$order ) {
|
||||
global $wpdb;
|
||||
|
||||
// XXX implement case $only_changes = false.
|
||||
$changes = $only_changes ? $order->get_changes() : array();
|
||||
$context = ( 0 === absint( $order->get_id() ) ) ? 'create' : 'update';
|
||||
$data_sync = wc_get_container()->get( DataSynchronizer::class );
|
||||
|
||||
if ( 'create' === $context ) {
|
||||
// XXX: do we want to add some backwards compat for 'woocommerce_new_order_data'?
|
||||
$post_id = wp_insert_post(
|
||||
array(
|
||||
'post_type' => $data_sync->data_sync_is_enabled() ? 'shop_order' : $data_sync::PLACEHOLDER_ORDER_POST_TYPE,
|
||||
'post_status' => 'draft',
|
||||
)
|
||||
);
|
||||
|
||||
if ( ! $post_id ) {
|
||||
throw new \Exception( __( 'Could not create order in posts table.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
$order->set_id( $post_id );
|
||||
}
|
||||
|
||||
// Figure out what needs to be updated in the database.
|
||||
$db_updates = array();
|
||||
$db_updates = $this->get_db_rows_for_order( $order, $context, ( 'update' === $context ) );
|
||||
|
||||
// Persist changes.
|
||||
foreach ( $db_updates as $update ) {
|
||||
// Make sure 'data' and 'format' entries match before passing to $wpdb.
|
||||
ksort( $update['data'] );
|
||||
ksort( $update['format'] );
|
||||
|
||||
$result = empty( $update['where'] )
|
||||
? $wpdb->insert( $update['table'], $update['data'], array_values( $update['format'] ) )
|
||||
: $wpdb->update( $update['table'], $update['data'], $update['where'], array_values( $update['format'] ), $update['where_format'] );
|
||||
|
||||
if ( false === $result ) {
|
||||
// translators: %s is a table name.
|
||||
throw new \Exception( sprintf( __( 'Could not persist order to database table "%s".', 'woocommerce' ), $update['table'] ) );
|
||||
}
|
||||
}
|
||||
|
||||
// Backfill post record.
|
||||
if ( $data_sync->data_sync_is_enabled() ) {
|
||||
$this->backfill_post_record( $order );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an array of rows with all the details required to insert or update an order in the database.
|
||||
*
|
||||
* @param \WC_Order $order The order.
|
||||
* @param string $context The context: 'create' or 'update'.
|
||||
* @param boolean $only_changes Whether to consider only changes in the order for generating the rows.
|
||||
* @return array
|
||||
* @throws \Exception When invalid data is found for the given context.
|
||||
*
|
||||
* @since 6.8.0
|
||||
*/
|
||||
protected function get_db_rows_for_order( $order, $context = 'create', $only_changes = false ): array {
|
||||
$result = array();
|
||||
|
||||
// wc_orders.
|
||||
$row = $this->get_db_row_from_order_changes( $changes, $this->order_column_mapping );
|
||||
$row = $this->get_db_row_from_order( $order, $this->order_column_mapping, $only_changes );
|
||||
if ( 'create' === $context && ! $row ) {
|
||||
throw new \Exception( 'No data for new record.' ); // This shouldn't occur.
|
||||
}
|
||||
|
||||
if ( $row ) {
|
||||
$db_updates[] = array_merge(
|
||||
array(
|
||||
'table' => self::get_orders_table_name(),
|
||||
'where' => array( 'id' => $order->get_id() ),
|
||||
'where_format' => '%d',
|
||||
),
|
||||
$row
|
||||
$result[] = array(
|
||||
'table' => self::get_orders_table_name(),
|
||||
'data' => array_merge( $row['data'], array( 'id' => $order->get_id() ) ),
|
||||
'format' => array_merge( $row['format'], array( 'id' => '%d' ) ),
|
||||
'where' => 'update' === $context ? array( 'id' => $order->get_id() ) : null,
|
||||
'where_format' => 'update' === $context ? '%d' : null,
|
||||
);
|
||||
}
|
||||
|
||||
// wc_order_operational_data.
|
||||
$row = $this->get_db_row_from_order_changes(
|
||||
array_merge(
|
||||
$changes,
|
||||
// XXX: manually persist some of the properties until the datastore/property design is finalized.
|
||||
array(
|
||||
'stock_reduced' => $this->get_stock_reduced( $order ),
|
||||
'download_permissions_granted' => $this->get_download_permissions_granted( $order ),
|
||||
'new_order_email_sent' => $this->get_email_sent( $order ),
|
||||
'recorded_sales' => $this->get_recorded_sales( $order ),
|
||||
'recorded_coupon_usage_counts' => $this->get_recorded_coupon_usage_counts( $order ),
|
||||
)
|
||||
),
|
||||
$this->operational_data_column_mapping
|
||||
);
|
||||
$row = $this->get_db_row_from_order( $order, $this->operational_data_column_mapping, $only_changes );
|
||||
if ( $row ) {
|
||||
$db_updates[] = array_merge(
|
||||
array(
|
||||
'table' => self::get_operational_data_table_name(),
|
||||
'where' => array( 'order_id' => $order->get_id() ),
|
||||
'where_format' => '%d',
|
||||
),
|
||||
$row
|
||||
$result[] = array(
|
||||
'table' => self::get_operational_data_table_name(),
|
||||
'data' => array_merge( $row['data'], array( 'order_id' => $order->get_id() ) ),
|
||||
'format' => array_merge( $row['format'], array( 'order_id' => '%d' ) ),
|
||||
'where' => 'update' === $context ? array( 'order_id' => $order->get_id() ) : null,
|
||||
'where_format' => 'update' === $context ? '%d' : null,
|
||||
);
|
||||
}
|
||||
|
||||
// wc_order_addresses.
|
||||
foreach ( array( 'billing', 'shipping' ) as $address_type ) {
|
||||
$row = $this->get_db_row_from_order_changes( $changes, $this->{$address_type . '_address_column_mapping'} );
|
||||
$row = $this->get_db_row_from_order( $order, $this->{$address_type . '_address_column_mapping'}, $only_changes );
|
||||
|
||||
if ( $row ) {
|
||||
$db_updates[] = array_merge(
|
||||
array(
|
||||
'table' => self::get_addresses_table_name(),
|
||||
'where' => array(
|
||||
$result[] = array(
|
||||
'table' => self::get_addresses_table_name(),
|
||||
'data' => array_merge(
|
||||
$row['data'],
|
||||
array(
|
||||
'order_id' => $order->get_id(),
|
||||
'address_type' => $address_type,
|
||||
),
|
||||
'where_format' => array( '%d', '%s' ),
|
||||
)
|
||||
),
|
||||
$row
|
||||
'format' => array_merge(
|
||||
$row['format'],
|
||||
array(
|
||||
'order_id' => '%d',
|
||||
'address_type' => '%s',
|
||||
)
|
||||
),
|
||||
'where' => 'update' === $context
|
||||
? array(
|
||||
'order_id' => $order->get_id(),
|
||||
'address_type' => $address_type,
|
||||
)
|
||||
: null,
|
||||
'where_format' => 'update' === $context ? array( '%d', '%s' ) : null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Persist changes.
|
||||
foreach ( $db_updates as $update ) {
|
||||
$wpdb->update(
|
||||
$update['table'],
|
||||
$update['row'],
|
||||
$update['where'],
|
||||
array_values( $update['format'] ),
|
||||
$update['where_format']
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Allow third parties to include rows that need to be inserted/updated in custom tables when persisting an order.
|
||||
*
|
||||
* @since 6.8.0
|
||||
*
|
||||
* @param array Array of rows to be inserted/updated when persisting an order. Each entry should be an array with
|
||||
* keys 'table', 'data' (the row), 'format' (row format), 'where' and 'where_format'.
|
||||
* @param \WC_Order The order object.
|
||||
* @param string The context of the operation: 'create' or 'update'.
|
||||
*/
|
||||
$ext_rows = apply_filters( 'woocommerce_orders_table_datastore_extra_db_rows_for_order', array(), $order, $context );
|
||||
|
||||
return array_merge( $result, $ext_rows );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -888,11 +967,21 @@ LEFT JOIN {$operational_data_clauses['join']}
|
|||
* `$format` parameters. Values are taken from the order changes array and properly formatted for inclusion in the
|
||||
* database.
|
||||
*
|
||||
* @param array $changes Order changes array.
|
||||
* @param array $column_mapping Table column mapping.
|
||||
* @param \WC_Order $order Order.
|
||||
* @param array $column_mapping Table column mapping.
|
||||
* @param bool $only_changes Whether to consider only changes in the order object or all fields.
|
||||
* @return array
|
||||
*
|
||||
* @since 6.8.0
|
||||
*/
|
||||
private function get_db_row_from_order_changes( $changes, $column_mapping ) {
|
||||
protected function get_db_row_from_order( $order, $column_mapping, $only_changes = false ) {
|
||||
$changes = $only_changes ? $order->get_changes() : array_merge( $order->get_data(), $order->get_changes() );
|
||||
|
||||
// XXX: manually persist some of the properties until the datastore/property design is finalized.
|
||||
foreach ( $this->get_internal_data_store_keys() as $key ) {
|
||||
$changes[ $key ] = $this->{"get_$key"}( $order );
|
||||
}
|
||||
|
||||
$row = array();
|
||||
$row_format = array();
|
||||
|
||||
|
@ -910,7 +999,7 @@ LEFT JOIN {$operational_data_clauses['join']}
|
|||
}
|
||||
|
||||
return array(
|
||||
'row' => $row,
|
||||
'data' => $row,
|
||||
'format' => $row_format,
|
||||
);
|
||||
}
|
||||
|
@ -919,10 +1008,40 @@ LEFT JOIN {$operational_data_clauses['join']}
|
|||
//phpcs:disable Squiz.Commenting, Generic.Commenting
|
||||
|
||||
/**
|
||||
* Method to create an order in the database.
|
||||
*
|
||||
* @param \WC_Order $order
|
||||
*/
|
||||
public function create( &$order ) {
|
||||
throw new \Exception( 'Unimplemented' );
|
||||
if ( '' === $order->get_order_key() ) {
|
||||
$order->set_order_key( wc_generate_order_key() );
|
||||
}
|
||||
|
||||
$order->set_version( Constants::get_constant( 'WC_VERSION' ) );
|
||||
$order->set_currency( $order->get_currency() ? $order->get_currency() : get_woocommerce_currency() );
|
||||
|
||||
if ( ! $order->get_date_created( 'edit' ) ) {
|
||||
$order->set_date_created( time() );
|
||||
}
|
||||
|
||||
$this->update_post_meta( $order );
|
||||
|
||||
$this->persist_order_to_db( $order );
|
||||
|
||||
$order->save_meta_data();
|
||||
$order->apply_changes();
|
||||
|
||||
$this->clear_caches( $order );
|
||||
|
||||
/**
|
||||
* Fires when a new order is created.
|
||||
*
|
||||
* @since 2.7.0
|
||||
*
|
||||
* @param int Order ID.
|
||||
* @param \WC_Order Order object.
|
||||
*/
|
||||
do_action( 'woocommerce_new_order', $order->get_id(), $order );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -931,8 +1050,6 @@ LEFT JOIN {$operational_data_clauses['join']}
|
|||
* @param \WC_Order $order
|
||||
*/
|
||||
public function update( &$order ) {
|
||||
global $wpdb;
|
||||
|
||||
// Before updating, ensure date paid is set if missing.
|
||||
if (
|
||||
! $order->get_date_paid( 'edit' )
|
||||
|
@ -951,21 +1068,16 @@ LEFT JOIN {$operational_data_clauses['join']}
|
|||
// Fetch changes.
|
||||
$changes = $order->get_changes();
|
||||
|
||||
// If address changed, store concatenated version to make searches faster.
|
||||
foreach ( array( 'billing', 'shipping' ) as $address_type ) {
|
||||
if ( isset( $changes[ $address_type ] ) ) {
|
||||
$order->update_meta_data( "_{$address_type}_address_index", implode( ' ', $order->get_address( $address_type ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! isset( $changes['date_modified'] ) ) {
|
||||
$order->set_date_modified( gmdate( 'Y-m-d H:i:s' ) );
|
||||
}
|
||||
|
||||
$this->update_post_meta( $order );
|
||||
|
||||
// Update with latest changes.
|
||||
$changes = $order->get_changes();
|
||||
|
||||
$this->persist_order_to_db( $order, true );
|
||||
$this->persist_order_to_db( $order );
|
||||
|
||||
// Update download permissions if necessary.
|
||||
if ( array_key_exists( 'billing_email', $changes ) || array_key_exists( 'customer_id', $changes ) ) {
|
||||
|
@ -985,6 +1097,29 @@ LEFT JOIN {$operational_data_clauses['join']}
|
|||
do_action( 'woocommerce_update_order', $order->get_id(), $order ); // phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method that updates post meta based on an order object.
|
||||
* Mostly used for backwards compatibility purposes in this datastore.
|
||||
*
|
||||
* @param \WC_Order $order Order object.
|
||||
* @since 3.0.0
|
||||
*/
|
||||
protected function update_post_meta( &$order ) {
|
||||
$changes = $order->get_changes();
|
||||
|
||||
// If address changed, store concatenated version to make searches faster.
|
||||
foreach ( array( 'billing', 'shipping' ) as $address_type ) {
|
||||
if ( isset( $changes[ $address_type ] ) ) {
|
||||
$order->update_meta_data( "_{$address_type}_address_index", implode( ' ', $order->get_address( $address_type ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
// Sync some COT fields to meta keys for backwards compatibility.
|
||||
foreach ( $this->get_internal_data_store_keys() as $key ) {
|
||||
$this->{"set_$key"}( $order, $this->{"get_$key"}( $order ), false );
|
||||
}
|
||||
}
|
||||
|
||||
public function get_coupon_held_keys( $order, $coupon_id = null ) {
|
||||
return array();
|
||||
}
|
||||
|
@ -1203,4 +1338,20 @@ CREATE TABLE $meta_table (
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns keys currently handled by this datastore manually (not available through order properties).
|
||||
*
|
||||
* @return array List of keys.
|
||||
*/
|
||||
protected function get_internal_data_store_keys() {
|
||||
// XXX: Finalize design -- will these be turned into props?
|
||||
return array(
|
||||
'order_stock_reduced',
|
||||
'download_permissions_granted',
|
||||
'new_order_email_sent',
|
||||
'recorded_sales',
|
||||
'recorded_coupon_usage_counts',
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -38,7 +38,6 @@ class OrdersDataStoreServiceProvider extends AbstractServiceProvider {
|
|||
*/
|
||||
public function register() {
|
||||
$this->share( OrdersTableDataStoreMeta::class );
|
||||
$this->share( OrdersTableDataStoreHelper::class );
|
||||
|
||||
$this->share( OrdersTableDataStore::class )->addArguments( array( OrdersTableDataStoreMeta::class, DatabaseUtil::class ) );
|
||||
$this->share( DataSynchronizer::class )->addArguments( array( OrdersTableDataStore::class, DatabaseUtil::class, PostsToOrdersMigrationController::class ) );
|
||||
|
|
|
@ -195,6 +195,100 @@ class OrdersTableDataStoreTests extends WC_Unit_Test_Case {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests create() on the COT datastore.
|
||||
*/
|
||||
public function test_cot_datastore_create() {
|
||||
$order = new WC_Order();
|
||||
$this->switch_data_store( $order, $this->sut );
|
||||
|
||||
$product = \Automattic\WooCommerce\RestApi\UnitTests\Helpers\ProductHelper::create_simple_product();
|
||||
|
||||
$order->set_status( 'pending' );
|
||||
$order->set_created_via( 'unit-tests' );
|
||||
$order->set_currency( 'COP' );
|
||||
$order->set_customer_ip_address( '127.0.0.1' );
|
||||
|
||||
$item = new WC_Order_Item_Product();
|
||||
$item->set_props(
|
||||
array(
|
||||
'product' => $product,
|
||||
'quantity' => 2,
|
||||
'subtotal' => wc_get_price_excluding_tax( $product, array( 'qty' => 2 ) ),
|
||||
'total' => wc_get_price_excluding_tax( $product, array( 'qty' => 2 ) ),
|
||||
)
|
||||
);
|
||||
|
||||
$order->add_item( $item );
|
||||
|
||||
$order->set_billing_first_name( 'Jeroen' );
|
||||
$order->set_billing_last_name( 'Sormani' );
|
||||
$order->set_billing_company( 'WooCompany' );
|
||||
$order->set_billing_address_1( 'WooAddress' );
|
||||
$order->set_billing_address_2( '' );
|
||||
$order->set_billing_city( 'WooCity' );
|
||||
$order->set_billing_state( 'NY' );
|
||||
$order->set_billing_postcode( '123456' );
|
||||
$order->set_billing_country( 'US' );
|
||||
$order->set_billing_email( 'admin@example.org' );
|
||||
$order->set_billing_phone( '555-32123' );
|
||||
|
||||
$payment_gateways = WC()->payment_gateways->payment_gateways();
|
||||
$order->set_payment_method( $payment_gateways['bacs'] );
|
||||
|
||||
$order->set_shipping_total( 5.0 );
|
||||
$order->set_discount_total( 0.0 );
|
||||
$order->set_discount_tax( 0.0 );
|
||||
$order->set_cart_tax( 0.0 );
|
||||
$order->set_shipping_tax( 0.0 );
|
||||
$order->set_total( 25.0 );
|
||||
$order->save();
|
||||
|
||||
$order->get_data_store()->set_stock_reduced( $order, true, false );
|
||||
|
||||
$order->update_meta_data( 'my_meta', rand( 0, 255 ) );
|
||||
|
||||
$order_id = $order->save();
|
||||
|
||||
$this->assertIsInteger( $order_id );
|
||||
$this->assertLessThan( $order_id, 0 );
|
||||
|
||||
wp_cache_flush();
|
||||
|
||||
// Read the order again (fresh).
|
||||
$r_order = new WC_Order();
|
||||
$r_order->set_id( $order_id );
|
||||
$this->switch_data_store( $r_order, $this->sut );
|
||||
$this->sut->read( $r_order );
|
||||
|
||||
// Compare some of the prop/meta values to those that should've been persisted.
|
||||
$props_to_compare = array(
|
||||
'status',
|
||||
'created_via',
|
||||
'currency',
|
||||
'customer_ip_address',
|
||||
'billing_first_name',
|
||||
'billing_last_name',
|
||||
'billing_company',
|
||||
'billing_address_1',
|
||||
'billing_city',
|
||||
'billing_state',
|
||||
'billing_postcode',
|
||||
'billing_country',
|
||||
'billing_email',
|
||||
'billing_phone',
|
||||
'shipping_total',
|
||||
'total',
|
||||
);
|
||||
|
||||
foreach ( $props_to_compare as $prop ) {
|
||||
$this->assertEquals( $order->{"get_$prop"}( 'edit' ), $r_order->{"get_$prop"}( 'edit' ) );
|
||||
}
|
||||
|
||||
$this->assertEquals( $order->get_meta( 'my_meta', true, 'edit' ), $r_order->get_meta( 'my_meta', true, 'edit' ) );
|
||||
$this->assertEquals( $this->sut->get_stock_reduced( $order ), $this->sut->get_stock_reduced( $r_order ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to delete all meta for post.
|
||||
*
|
||||
|
|
Loading…
Reference in New Issue