[COT] Implement metadata CRUD and `update()` method in COT datastore (#33026)

* Add `OrdersTableDataStoreMeta` to handle metadata for orders

* Add `OrdersTableDataStoreHelper` with various helper functions used in the COT datastore

* Pass some helper classes as args to the COT datastore

* Use `OrdersTableDataStoreMeta` for meta in COT datastore

* Minor fixes to columns definition in COT datastore

* First pass at update() in the COT datastore

* PHPCS fixes

* Remove duplicate `read_meta` calls.

* Register `OrdersTableDataStore` earlier to make container happy

* Do not hardcode table metadata in `OrdersTableDataStoreMeta`

* Correctly format decimals for storing in the db

* read() shouldn’t success on non-existing orders

* Rework persisting to db in OrdersTableDataStore

* Correctly handle some props in OrdersTableDataStore

* Add changelog

* Add missing TODOs

* Remove unused variables

* No need to query db before deleting meta in `OrdersTableDataStoreMeta`

* Simplify OrdersTableDataStoreMeta::update_meta()

* Explicitly enumerate columns in OrdersTableDataStoreMeta::get_metadata_by_id()

* Make COT metadata implementation more generic

* Do not use property_exists() to determine existence of meta value

* Move some methods over to DatabaseUtil and get rid of COT datastore helper

* Rename `CustomDataStoreMeta` to `CustomMetaDataStore`

* Make PHPCS happy

* Add unit test.

* Correct arg passed to persist_order_to_db()

* Remove comment

* Split conditional on multiple lines

Co-authored-by: vedanshujain <vedanshu.jain.2012@gmail.com>
This commit is contained in:
Jorge A. Torres 2022-06-15 05:39:41 -03:00 committed by GitHub
parent 090822eabf
commit 4b63f34841
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 709 additions and 44 deletions

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Implements update/saving logic for orders in the COT datastore.

View File

@ -0,0 +1,198 @@
<?php
/**
* CustomMetaDataStore class file.
*/
namespace Automattic\WooCommerce\Internal\DataStores;
/**
* Implements functions similar to WP's add_metadata(), get_metadata(), and friends using a custom table.
*
* @see WC_Data_Store_WP For an implementation using WP's metadata functions and tables.
*/
abstract class CustomMetaDataStore {
/**
* Returns the name of the table used for storage.
*
* @return string
*/
abstract protected function get_table_name();
/**
* Returns the name of the field/column used for identifiying metadata entries.
*
* @return string
*/
protected function get_meta_id_field() {
return 'id';
}
/**
* Returns the name of the field/column used for associating meta with objects.
*
* @return string
*/
protected function get_object_id_field() {
return 'object_id';
}
/**
* Describes the structure of the metadata table.
*
* @return array Array elements: table, object_id_field, meta_id_field.
*/
protected function get_db_info() {
return array(
'table' => $this->get_table_name(),
'meta_id_field' => $this->get_meta_id_field(),
'object_id_field' => $this->get_object_id_field(),
);
}
/**
* Returns an array of meta for an object.
*
* @param WC_Data $object WC_Data object.
* @return array
*/
public function read_meta( &$object ) {
global $wpdb;
$db_info = $this->get_db_info();
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$raw_meta_data = $wpdb->get_results(
$wpdb->prepare(
"SELECT {$db_info['meta_id_field']} AS meta_id, meta_key, meta_value FROM {$db_info['table']} WHERE {$db_info['object_id_field']} = %d ORDER BY meta_id",
$object->get_id()
)
);
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
return $raw_meta_data;
}
/**
* Deletes meta based on meta ID.
*
* @param WC_Data $object WC_Data object.
* @param stdClass $meta (containing at least ->id).
*/
public function delete_meta( &$object, $meta ) {
global $wpdb;
if ( ! isset( $meta->id ) ) {
return false;
}
$db_info = $this->get_db_info();
$meta_id = absint( $meta->id );
return (bool) $wpdb->delete( $db_info['table'], array( $db_info['meta_id_field'] => $meta_id ) );
}
/**
* Add new piece of meta.
*
* @param WC_Data $object WC_Data object.
* @param stdClass $meta (containing ->key and ->value).
* @return int meta ID
*/
public function add_meta( &$object, $meta ) {
global $wpdb;
if ( ! is_a( $meta, 'WC_Meta_Data' ) ) {
return false;
}
$db_info = $this->get_db_info();
$object_id = $object->get_id();
$meta_key = wp_unslash( wp_slash( $meta->key ) );
$meta_value = maybe_serialize( is_string( $meta->value ) ? wp_unslash( wp_slash( $meta->value ) ) : $meta->value );
// phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_value,WordPress.DB.SlowDBQuery.slow_db_query_meta_key
$result = $wpdb->insert(
$db_info['table'],
array(
$db_info['object_id_field'] => $object_id,
'meta_key' => $meta_key,
'meta_value' => $meta_value,
)
);
// phpcs:enable WordPress.DB.SlowDBQuery.slow_db_query_meta_value,WordPress.DB.SlowDBQuery.slow_db_query_meta_key
return $result ? (int) $wpdb->insert_id : false;
}
/**
* Update meta.
*
* @param WC_Data $object WC_Data object.
* @param stdClass $meta (containing ->id, ->key and ->value).
*/
public function update_meta( &$object, $meta ) {
global $wpdb;
if ( ! isset( $meta->id ) || empty( $meta->key ) ) {
return false;
}
// phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_value,WordPress.DB.SlowDBQuery.slow_db_query_meta_key
$data = array(
'meta_key' => $meta->key,
'meta_value' => maybe_serialize( $meta->value ),
);
// phpcs:enable WordPress.DB.SlowDBQuery.slow_db_query_meta_value,WordPress.DB.SlowDBQuery.slow_db_query_meta_key
$db_info = $this->get_db_info();
$result = $wpdb->update(
$db_info['table'],
$data,
array( $db_info['meta_id_field'] => $meta->id ),
'%s',
'%d'
);
return 1 === $result;
}
/**
* Retrieves metadata by meta ID.
*
* @param int $meta_id Meta ID.
* @return object|bool Metadata object or FALSE if not found.
*/
public function get_metadata_by_id( $meta_id ) {
global $wpdb;
if ( ! is_numeric( $meta_id ) || floor( $meta_id ) != $meta_id ) { // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
return false;
}
$db_info = $this->get_db_info();
$meta_id = absint( $meta_id );
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$meta = $wpdb->get_row(
$wpdb->prepare(
"SELECT {$db_info['meta_id_field']}, meta_key, meta_value, {$db_info['object_id_field']} FROM {$db_info['table']} WHERE {$db_info['meta_id_field']} = %d",
$meta_id
)
);
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
if ( empty( $meta ) ) {
return false;
}
if ( isset( $meta->meta_value ) ) {
$meta->meta_value = maybe_unserialize( $meta->meta_value );
}
return $meta;
}
}

View File

@ -5,6 +5,9 @@
namespace Automattic\WooCommerce\Internal\DataStores\Orders;
use Automattic\Jetpack\Constants;
use Automattic\WooCommerce\Internal\Utilities\DatabaseUtil;
defined( 'ABSPATH' ) || exit;
/**
@ -12,6 +15,34 @@ defined( 'ABSPATH' ) || exit;
*/
class OrdersTableDataStore extends \Abstract_WC_Order_Data_Store_CPT implements \WC_Object_Data_Store_Interface, \WC_Order_Data_Store_Interface {
/**
* Handles custom metadata in the wc_orders_meta table.
*
* @var OrdersTableDataStoreMeta
*/
protected $data_store_meta;
/**
* The database util object to use.
*
* @var DatabaseUtil
*/
protected $database_util;
/**
* Initialize the object.
*
* @internal
* @param OrdersTableDataStoreMeta $data_store_meta Metadata instance.
* @param DatabaseUtil $database_util The database util instance to use.
* @return void
*/
final public function init( OrdersTableDataStoreMeta $data_store_meta, DatabaseUtil $database_util ) {
$this->data_store_meta = $data_store_meta;
$this->database_util = $database_util;
}
/**
* Get the custom orders table name.
*
@ -101,7 +132,7 @@ class OrdersTableDataStore extends \Abstract_WC_Order_Data_Store_CPT implements
'name' => 'customer_id',
),
'billing_email' => array(
'type' => 'int',
'type' => 'string',
'name' => 'billing_email',
),
'date_created_gmt' => array(
@ -262,7 +293,7 @@ class OrdersTableDataStore extends \Abstract_WC_Order_Data_Store_CPT implements
'name' => 'version',
),
'prices_include_tax' => array(
'type' => 'string',
'type' => 'bool',
'name' => 'prices_include_tax',
),
'coupon_usages_are_counted' => array(
@ -278,7 +309,7 @@ class OrdersTableDataStore extends \Abstract_WC_Order_Data_Store_CPT implements
'name' => 'cart_hash',
),
'new_order_email_sent' => array(
'type' => 'string',
'type' => 'bool',
'name' => 'new_order_email_sent',
),
'order_key' => array(
@ -313,7 +344,10 @@ class OrdersTableDataStore extends \Abstract_WC_Order_Data_Store_CPT implements
'type' => 'decimal',
'name' => 'discount_total',
),
'recorded_sales' => array( 'type' => 'bool' ),
'recorded_sales' => array(
'type' => 'bool',
'name' => 'recorded_sales',
),
);
/**
@ -384,8 +418,10 @@ class OrdersTableDataStore extends \Abstract_WC_Order_Data_Store_CPT implements
*
* @param \WC_Order $order Order ID or order object.
* @param bool $set True or false.
* @param bool $save Whether to persist changes to db immediately or not.
*/
public function set_download_permissions_granted( $order, $set ) {
public function set_download_permissions_granted( $order, $set, $save = true ) {
// XXX implement $save = true.
return $order->update_meta_data( '_download_permissions_granted', wc_bool_to_string( $set ) );
}
@ -405,8 +441,10 @@ class OrdersTableDataStore extends \Abstract_WC_Order_Data_Store_CPT implements
*
* @param \WC_Order $order Order object.
* @param bool $set True or false.
* @param bool $save Whether to persist changes to db immediately or not.
*/
public function set_recorded_sales( $order, $set ) {
public function set_recorded_sales( $order, $set, $save = true ) {
// XXX implement $save = true.
return $order->update_meta_data( '_recorded_sales', wc_bool_to_string( $set ) );
}
@ -426,8 +464,9 @@ class OrdersTableDataStore extends \Abstract_WC_Order_Data_Store_CPT implements
*
* @param \WC_Order $order Order object.
* @param bool $set True or false.
* @param bool $save Whether to persist changes to db immediately or not.
*/
public function set_recorded_coupon_usage_counts( $order, $set ) {
public function set_recorded_coupon_usage_counts( $order, $set, $save = true ) {
return $order->update_meta_data( '_recorded_coupon_usage_counts', wc_bool_to_string( $set ) );
}
@ -446,10 +485,11 @@ class OrdersTableDataStore extends \Abstract_WC_Order_Data_Store_CPT implements
* Stores information about whether email was sent.
*
* @param \WC_Order $order Order object.
*
* @param bool $set True or false.
* @param bool $save Whether to persist changes to db immediately or not.
*/
public function set_email_sent( $order, $set ) {
public function set_email_sent( $order, $set, $save = true ) {
// XXX implement $save = true.
return $order->update_meta_data( '_new_order_email_sent', wc_bool_to_string( $set ) );
}
@ -469,10 +509,11 @@ class OrdersTableDataStore extends \Abstract_WC_Order_Data_Store_CPT implements
*
* @param \WC_Order $order Order object.
* @param bool $set True or false.
*
* @param bool $save Whether to persist changes to db immediately or not.
* @return bool Whether email was sent.
*/
private function set_new_order_email_sent( $order, $set ) {
private function set_new_order_email_sent( $order, $set, $save = true ) {
// XXX implement $save = true.
return $this->set_email_sent( $order, $set );
}
@ -484,6 +525,7 @@ class OrdersTableDataStore extends \Abstract_WC_Order_Data_Store_CPT implements
* @return bool Whether stock was reduced.
*/
public function get_stock_reduced( $order ) {
$order = is_numeric( $order ) ? wc_get_order( $order ) : $order;
return wc_string_to_bool( $order->get_meta( '_order_stock_reduced', true ) );
}
@ -492,8 +534,11 @@ class OrdersTableDataStore extends \Abstract_WC_Order_Data_Store_CPT implements
*
* @param \WC_Order $order Order ID or order object.
* @param bool $set True or false.
* @param bool $save Whether to persist changes to db immediately or not.
*/
public function set_stock_reduced( $order, $set ) {
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 ) );
}
@ -557,10 +602,16 @@ class OrdersTableDataStore extends \Abstract_WC_Order_Data_Store_CPT implements
public function read( &$order ) {
$order->set_defaults();
if ( ! $order->get_id() ) {
throw new \Exception( __( 'ID must be set for an order to be read', 'woocommerce' ) );
throw new \Exception( __( 'ID must be set for an order to be read.', 'woocommerce' ) );
}
$order->read_meta_data();
$order_data = $this->get_order_data_for_id( $order->get_id() );
if ( ! $order_data ) {
throw new \Exception( __( 'Invalid order.', 'woocommerce' ) );
}
$order->read_meta_data();
foreach ( $this->get_all_order_column_mappings() as $table_name => $column_mapping ) {
foreach ( $column_mapping as $column_name => $prop_details ) {
if ( ! isset( $prop_details['name'] ) ) {
@ -570,39 +621,12 @@ class OrdersTableDataStore extends \Abstract_WC_Order_Data_Store_CPT implements
if ( is_callable( array( $order, $prop_setter_function_name ) ) ) {
$order->{$prop_setter_function_name}( $order_data->{$prop_details['name']} );
} elseif ( is_callable( array( $this, $prop_setter_function_name ) ) ) {
$this->{$prop_setter_function_name}( $order, $order_data->{$prop_details['name']} );
$this->{$prop_setter_function_name}( $order, $order_data->{$prop_details['name']}, false );
}
}
}
$order->set_object_read();
}
/**
* Read metadata directly from database.
*
* @param \WC_Order $order Order object.
*
* @return array Metadata array.
*/
public function read_meta( &$order ) {
global $wpdb;
$meta_table = $this::get_meta_table_name();
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $meta_table is hardcoded.
$raw_meta_data = $wpdb->get_results(
$wpdb->prepare(
"
SELECT id as meta_id, meta_key, meta_value
FROM $meta_table
WHERE order_id = %d
ORDER BY meta_id;
",
$order->get_id()
)
);
// phpcs:enable
return $this->filter_raw_meta_data( $order, $raw_meta_data );
$order->set_object_read( true );
}
/**
@ -773,6 +797,124 @@ LEFT JOIN {$operational_data_clauses['join']}
return implode( ', ', $select_clauses );
}
/**
* 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
*/
protected function persist_order_to_db( $order, $only_changes = true ) {
global $wpdb;
// XXX implement case $only_changes = false.
$changes = $only_changes ? $order->get_changes() : array();
// Figure out what needs to be updated in the database.
$db_updates = array();
// wc_orders.
$row = $this->get_db_row_from_order_changes( $changes, $this->order_column_mapping );
if ( $row ) {
$db_updates[] = array_merge(
array(
'table' => self::get_orders_table_name(),
'where' => array( 'id' => $order->get_id() ),
'where_format' => '%d',
),
$row
);
}
// 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
);
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
);
}
// 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'} );
if ( $row ) {
$db_updates[] = array_merge(
array(
'table' => self::get_addresses_table_name(),
'where' => array(
'order_id' => $order->get_id(),
'address_type' => $address_type,
),
'where_format' => array( '%d', '%s' ),
),
$row
);
}
}
// Persist changes.
foreach ( $db_updates as $update ) {
$wpdb->update(
$update['table'],
$update['row'],
$update['where'],
array_values( $update['format'] ),
$update['where_format']
);
}
}
/**
* Produces an array with keys 'row' and 'format' that can be passed to `$wpdb->update()` as the `$data` and
* `$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.
* @return array
*/
private function get_db_row_from_order_changes( $changes, $column_mapping ) {
$row = array();
$row_format = array();
foreach ( $column_mapping as $column => $details ) {
if ( ! isset( $details['name'] ) || ! array_key_exists( $details['name'], $changes ) ) {
continue;
}
$row[ $column ] = $this->database_util->format_object_value_for_db( $changes[ $details['name'] ], $details['type'] );
$row_format[ $column ] = $this->database_util->get_wpdb_format_for_type( $details['type'] );
}
if ( ! $row ) {
return false;
}
return array(
'row' => $row,
'format' => $row_format,
);
}
//phpcs:disable Squiz.Commenting, Generic.Commenting
@ -783,8 +925,64 @@ LEFT JOIN {$operational_data_clauses['join']}
throw new \Exception( 'Unimplemented' );
}
/**
* Method to update an order in the database.
*
* @param \WC_Order $order
*/
public function update( &$order ) {
throw new \Exception( 'Unimplemented' );
global $wpdb;
// Before updating, ensure date paid is set if missing.
if (
! $order->get_date_paid( 'edit' )
&& version_compare( $order->get_version( 'edit' ), '3.0', '<' )
&& $order->has_status( apply_filters( 'woocommerce_payment_complete_order_status', $order->needs_processing() ? 'processing' : 'completed', $order->get_id(), $order ) ) // phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
) {
$order->set_date_paid( $order->get_date_created( 'edit' ) );
}
if ( null === $order->get_date_created( 'edit' ) ) {
$order->set_date_created( time() );
}
$order->set_version( Constants::get_constant( 'WC_VERSION' ) );
// 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' ) );
}
// Update with latest changes.
$changes = $order->get_changes();
$this->persist_order_to_db( $order, true );
// Update download permissions if necessary.
if ( array_key_exists( 'billing_email', $changes ) || array_key_exists( 'customer_id', $changes ) ) {
$data_store = \WC_Data_Store::load( 'customer-download' );
$data_store->update_user_by_order_id( $order->get_id(), $order->get_customer_id(), $order->get_billing_email() );
}
// Mark user account as active.
if ( array_key_exists( 'customer_id', $changes ) ) {
wc_update_user_last_active( $order->get_customer_id() );
}
$order->save_meta_data();
$order->apply_changes();
$this->clear_caches( $order );
do_action( 'woocommerce_update_order', $order->get_id(), $order ); // phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
}
public function get_coupon_held_keys( $order, $coupon_id = null ) {
@ -901,4 +1099,108 @@ CREATE TABLE $meta_table (
return $sql;
}
/**
* Returns an array of meta for an object.
*
* @param WC_Data $object WC_Data object.
* @return array
*/
public function read_meta( &$object ) {
return $this->data_store_meta->read_meta( $object );
}
/**
* Deletes meta based on meta ID.
*
* @param WC_Data $object WC_Data object.
* @param stdClass $meta (containing at least ->id).
*/
public function delete_meta( &$object, $meta ) {
return $this->data_store_meta->delete_meta( $object, $meta );
}
/**
* Add new piece of meta.
*
* @param WC_Data $object WC_Data object.
* @param stdClass $meta (containing ->key and ->value).
* @return int meta ID
*/
public function add_meta( &$object, $meta ) {
return $this->data_store_meta->add_meta( $object, $meta );
}
/**
* Update meta.
*
* @param WC_Data $object WC_Data object.
* @param stdClass $meta (containing ->id, ->key and ->value).
*/
public function update_meta( &$object, $meta ) {
return $this->data_store_meta->update_meta( $object, $meta );
}
/**
* Returns list of metadata that is considered "internal".
*
* @return array
*/
public function get_internal_meta_keys() {
// XXX: This is mostly just to trick `WC_Data_Store_WP` for the time being.
return array(
'_customer_user',
'_order_key',
'_order_currency',
'_billing_first_name',
'_billing_last_name',
'_billing_company',
'_billing_address_1',
'_billing_address_2',
'_billing_city',
'_billing_state',
'_billing_postcode',
'_billing_country',
'_billing_email',
'_billing_phone',
'_shipping_first_name',
'_shipping_last_name',
'_shipping_company',
'_shipping_address_1',
'_shipping_address_2',
'_shipping_city',
'_shipping_state',
'_shipping_postcode',
'_shipping_country',
'_shipping_phone',
'_completed_date',
'_paid_date',
'_edit_lock',
'_edit_last',
'_cart_discount',
'_cart_discount_tax',
'_order_shipping',
'_order_shipping_tax',
'_order_tax',
'_order_total',
'_payment_method',
'_payment_method_title',
'_transaction_id',
'_customer_ip_address',
'_customer_user_agent',
'_created_via',
'_order_version',
'_prices_include_tax',
'_date_completed',
'_date_paid',
'_payment_tokens',
'_billing_address_index',
'_shipping_address_index',
'_recorded_sales',
'_recorded_coupon_usage_counts',
'_download_permissions_granted',
'_order_stock_reduced',
);
}
}

View File

@ -0,0 +1,33 @@
<?php
/**
* OrdersTableDataStoreMeta class file.
*/
namespace Automattic\WooCommerce\Internal\DataStores\Orders;
use Automattic\WooCommerce\Internal\DataStores\CustomMetaDataStore;
/**
* Mimics a WP metadata (i.e. add_metadata(), get_metadata() and friends) implementation using a custom table.
*/
class OrdersTableDataStoreMeta extends CustomMetaDataStore {
/**
* Returns the name of the table used for storage.
*
* @return string
*/
protected function get_table_name() {
return OrdersTableDataStore::get_meta_table_name();
}
/**
* Returns the name of the field/column used for associating meta with objects.
*
* @return string
*/
protected function get_object_id_field() {
return 'order_id';
}
}

View File

@ -12,6 +12,7 @@ use Automattic\WooCommerce\Internal\DependencyManagement\AbstractServiceProvider
use Automattic\WooCommerce\Internal\DataStores\Orders\DataSynchronizer;
use Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController;
use Automattic\WooCommerce\Internal\DataStores\Orders\OrdersTableDataStore;
use Automattic\WooCommerce\Internal\DataStores\Orders\OrdersTableDataStoreMeta;
use Automattic\WooCommerce\Internal\Utilities\DatabaseUtil;
/**
@ -29,12 +30,17 @@ class OrdersDataStoreServiceProvider extends AbstractServiceProvider {
CustomOrdersTableController::class,
OrdersTableDataStore::class,
CLIRunner::class,
OrdersTableDataStoreMeta::class,
);
/**
* Register the classes.
*/
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 ) );
$this->share( CustomOrdersTableController::class )->addArguments( array( OrdersTableDataStore::class, DataSynchronizer::class ) );
$this->share( OrdersTableDataStore::class );

View File

@ -133,4 +133,64 @@ AND index_name='$index_name'"
);
// phpcs:enable WordPress.DB.PreparedSQL
}
/**
* Formats an object value of type `$type` for inclusion in the database.
*
* @param mixed $value Raw value.
* @param string $type Data type.
* @return mixed
* @throws \Exception When an invalid type is passed.
*/
public function format_object_value_for_db( $value, string $type ) {
switch ( $type ) {
case 'decimal':
$value = wc_format_decimal( $value, false, true );
break;
case 'int':
$value = (int) $value;
break;
case 'bool':
$value = wc_string_to_bool( $value );
break;
case 'string':
$value = strval( $value );
break;
case 'date':
$value = $value ? ( new \DateTime( $value ) )->format( 'Y-m-d H:i:s' ) : null;
break;
case 'date_epoch':
$value = $value ? ( new \DateTime( "@{$value}" ) )->format( 'Y-m-d H:i:s' ) : null;
break;
default:
throw new \Exception( 'Invalid type received: ' . $type );
}
return $value;
}
/**
* Returns the `$wpdb` placeholder to use for data type `$type`.
*
* @param string $type Data type.
* @return string
* @throws \Exception When an invalid type is passed.
*/
public function get_wpdb_format_for_type( string $type ) {
static $wpdb_placeholder_for_type = array(
'int' => '%d',
'decimal' => '%f',
'string' => '%s',
'date' => '%s',
'date_epoch' => '%s',
'bool' => '%d',
);
if ( ! isset( $wpdb_placeholder_for_type[ $type ] ) ) {
throw new \Exception( 'Invalid column type: ' . $type );
}
return $wpdb_placeholder_for_type[ $type ];
}
}

View File

@ -133,6 +133,68 @@ class OrdersTableDataStoreTests extends WC_Unit_Test_Case {
}
}
/**
* Tests update() on the COT datastore.
*/
public function test_cot_datastore_update() {
static $props_to_update = array(
'billing_first_name' => 'John',
'billing_last_name' => 'Doe',
'shipping_phone' => '555-55-55',
'status' => 'on-hold',
'cart_hash' => 'YET-ANOTHER-CART-HASH',
);
static $datastore_updates = array(
'email_sent' => true,
);
static $meta_to_update = array(
'my_meta_key' => array( 'my', 'custom', 'meta' ),
);
// Set up order.
$post_order = OrderHelper::create_order();
$this->migrator->migrate_orders( array( $post_order->get_id() ) );
// Read order using the COT datastore.
wp_cache_flush();
$order = new WC_Order();
$order->set_id( $post_order->get_id() );
$this->switch_data_store( $order, $this->sut );
$this->sut->read( $order );
// Make some changes to the order and save.
$order->set_props( $props_to_update );
foreach ( $meta_to_update as $meta_key => $meta_value ) {
$order->add_meta_data( $meta_key, $meta_value, true );
}
foreach ( $datastore_updates as $prop => $value ) {
$this->sut->{"set_$prop"}( $order, $value );
}
$order->save();
// Re-read order and make sure changes were persisted.
wp_cache_flush();
$order = new WC_Order();
$order->set_id( $post_order->get_id() );
$this->switch_data_store( $order, $this->sut );
$this->sut->read( $order );
foreach ( $props_to_update as $prop => $value ) {
$this->assertEquals( $order->{"get_$prop"}( 'edit' ), $value );
}
foreach ( $meta_to_update as $meta_key => $meta_value ) {
$this->assertEquals( $order->get_meta( $meta_key, true, 'edit' ), $meta_value );
}
foreach ( $datastore_updates as $prop => $value ) {
$this->assertEquals( $this->sut->{"get_$prop"}( $order ), $value );
}
}
/**
* Helper function to delete all meta for post.
*