auto-patch 638-dev-dev01-2024-05-14T20_44_36
This commit is contained in:
@@ -232,4 +232,37 @@ abstract class CustomMetaDataStore {
|
||||
return $meta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns distinct meta keys in use.
|
||||
*
|
||||
* @since 8.8.0
|
||||
*
|
||||
* @param int $limit Maximum number of meta keys to return. Defaults to 100.
|
||||
* @param string $order Order to use for the results. Either 'ASC' or 'DESC'. Defaults to 'ASC'.
|
||||
* @param bool $include_private Whether to include private meta keys in the results. Defaults to FALSE.
|
||||
* @return string[]
|
||||
*/
|
||||
public function get_meta_keys( $limit = 100, $order = 'ASC', $include_private = false ) {
|
||||
global $wpdb;
|
||||
|
||||
$db_info = $this->get_db_info();
|
||||
|
||||
$query = "SELECT DISTINCT meta_key FROM {$db_info['table']} ";
|
||||
|
||||
if ( ! $include_private ) {
|
||||
$query .= $wpdb->prepare( 'WHERE meta_key != \'\' AND meta_key NOT LIKE %s ', $wpdb->esc_like( '_' ) . '%' );
|
||||
} else {
|
||||
$query .= "WHERE meta_key != '' ";
|
||||
}
|
||||
|
||||
$order = in_array( strtoupper( $order ), array( 'ASC', 'DESC' ), true ) ? $order : 'ASC';
|
||||
$query .= 'ORDER BY meta_key ' . $order . ' ';
|
||||
|
||||
if ( $limit ) {
|
||||
$query .= $wpdb->prepare( 'LIMIT %d ', $limit );
|
||||
}
|
||||
|
||||
return $wpdb->get_col( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $query is prepared.
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -311,6 +311,10 @@ class CustomOrdersTableController {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if ( $old_value === $value ) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$this->order_cache->flush();
|
||||
if ( ! $this->data_synchronizer->check_orders_table_exists() ) {
|
||||
$this->data_synchronizer->create_database_tables();
|
||||
@@ -433,7 +437,7 @@ class CustomOrdersTableController {
|
||||
return array();
|
||||
}
|
||||
|
||||
$get_value = function() {
|
||||
$get_value = function () {
|
||||
return $this->custom_orders_table_usage_is_enabled() ? 'yes' : 'no';
|
||||
};
|
||||
|
||||
@@ -442,18 +446,20 @@ class CustomOrdersTableController {
|
||||
* gets called while it's still being instantiated and creates and endless loop.
|
||||
*/
|
||||
|
||||
$get_desc = function() {
|
||||
$get_desc = function () {
|
||||
$plugin_compatibility = $this->features_controller->get_compatible_plugins_for_feature( 'custom_order_tables', true );
|
||||
|
||||
return $this->plugin_util->generate_incompatible_plugin_feature_warning( 'custom_order_tables', $plugin_compatibility );
|
||||
};
|
||||
|
||||
$get_disabled = function() {
|
||||
$get_disabled = function () {
|
||||
$plugin_compatibility = $this->features_controller->get_compatible_plugins_for_feature( 'custom_order_tables', true );
|
||||
$sync_complete = 0 === $this->get_orders_pending_sync_count();
|
||||
$disabled = array();
|
||||
// Changing something here? might also want to look at `enable|disable` functions in CLIRunner.
|
||||
if ( count( array_merge( $plugin_compatibility['uncertain'], $plugin_compatibility['incompatible'] ) ) > 0 ) {
|
||||
$incompatible_plugins = array_merge( $plugin_compatibility['uncertain'], $plugin_compatibility['incompatible'] );
|
||||
$incompatible_plugins = array_diff( $incompatible_plugins, $this->plugin_util->get_plugins_excluded_from_compatibility_ui() );
|
||||
if ( count( $incompatible_plugins ) > 0 ) {
|
||||
$disabled = array( 'yes' );
|
||||
}
|
||||
if ( ! $sync_complete && ! $this->changing_data_source_with_sync_pending_is_allowed() ) {
|
||||
@@ -489,11 +495,11 @@ class CustomOrdersTableController {
|
||||
return array();
|
||||
}
|
||||
|
||||
$get_value = function() {
|
||||
$get_value = function () {
|
||||
return get_option( DataSynchronizer::ORDERS_DATA_SYNC_ENABLED_OPTION );
|
||||
};
|
||||
|
||||
$get_sync_message = function() {
|
||||
$get_sync_message = function () {
|
||||
$orders_pending_sync_count = $this->get_orders_pending_sync_count();
|
||||
$sync_in_progress = $this->batch_processing_controller->is_enqueued( get_class( $this->data_synchronizer ) );
|
||||
$sync_enabled = $this->data_synchronizer->data_sync_is_enabled();
|
||||
@@ -572,7 +578,7 @@ class CustomOrdersTableController {
|
||||
return implode( '<br />', $sync_message );
|
||||
};
|
||||
|
||||
$get_description_is_error = function() {
|
||||
$get_description_is_error = function () {
|
||||
$sync_is_pending = $this->get_orders_pending_sync_count() > 0;
|
||||
|
||||
return $sync_is_pending && $this->changing_data_source_with_sync_pending_is_allowed();
|
||||
|
||||
@@ -105,6 +105,7 @@ class DataSynchronizer implements BatchProcessorInterface {
|
||||
* Class constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
self::add_filter( 'pre_delete_post', array( $this, 'maybe_prevent_deletion_of_post' ), 10, 2 );
|
||||
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 );
|
||||
@@ -441,7 +442,7 @@ class DataSynchronizer implements BatchProcessorInterface {
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_current_orders_pending_sync_count_cached() : int {
|
||||
public function get_current_orders_pending_sync_count_cached(): int {
|
||||
return $this->get_current_orders_pending_sync_count( true );
|
||||
}
|
||||
|
||||
@@ -482,14 +483,14 @@ class DataSynchronizer implements BatchProcessorInterface {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare,WordPress.DB.PreparedSQL.NotPrepared --
|
||||
// -- $order_post_type_placeholder, $orders_table, self::PLACEHOLDER_ORDER_POST_TYPE are all safe to use in queries.
|
||||
if ( ! $this->get_table_exists() ) {
|
||||
$count = $wpdb->get_var(
|
||||
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- $order_post_type_placeholder is prepared.
|
||||
$wpdb->prepare(
|
||||
"SELECT COUNT(*) FROM $wpdb->posts where post_type in ( $order_post_type_placeholder )",
|
||||
$order_post_types
|
||||
)
|
||||
// phpcs:enable
|
||||
);
|
||||
return $count;
|
||||
}
|
||||
@@ -498,30 +499,28 @@ class DataSynchronizer implements BatchProcessorInterface {
|
||||
$missing_orders_count_sql = $wpdb->prepare(
|
||||
"
|
||||
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' )
|
||||
RIGHT JOIN $orders_table orders ON posts.ID=orders.id
|
||||
WHERE (posts.post_type IS NULL OR posts.post_type = '" . self::PLACEHOLDER_ORDER_POST_TYPE . "')
|
||||
AND orders.status NOT IN ( 'auto-draft' )
|
||||
AND orders.type IN ($order_post_type_placeholder)",
|
||||
$order_post_types
|
||||
);
|
||||
$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
|
||||
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(
|
||||
@@ -603,10 +602,9 @@ SELECT(
|
||||
$order_post_types = wc_get_order_types( 'cot-migration' );
|
||||
$order_post_type_placeholders = implode( ', ', array_fill( 0, count( $order_post_types ), '%s' ) );
|
||||
|
||||
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare,WordPress.DB.PreparedSQL.NotPrepared
|
||||
switch ( $type ) {
|
||||
case self::ID_TYPE_MISSING_IN_ORDERS_TABLE:
|
||||
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- $order_post_type_placeholders is prepared.
|
||||
$sql = $wpdb->prepare(
|
||||
"
|
||||
SELECT posts.ID FROM $wpdb->posts posts
|
||||
@@ -618,23 +616,22 @@ WHERE
|
||||
ORDER BY posts.ID ASC",
|
||||
$order_post_types
|
||||
);
|
||||
// phpcs:enable WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
|
||||
break;
|
||||
case self::ID_TYPE_MISSING_IN_POSTS_TABLE:
|
||||
$sql = $wpdb->prepare(
|
||||
"
|
||||
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' )
|
||||
SELECT orders.id FROM $wpdb->posts posts
|
||||
RIGHT JOIN $orders_table orders ON posts.ID=orders.id
|
||||
WHERE (posts.post_type IS NULL OR posts.post_type = '" . self::PLACEHOLDER_ORDER_POST_TYPE . "')
|
||||
AND orders.status NOT IN ( 'auto-draft' )
|
||||
AND orders.type IN ($order_post_type_placeholders)
|
||||
ORDER BY posts.id ASC",
|
||||
ORDER BY posts.ID ASC",
|
||||
$order_post_types
|
||||
);
|
||||
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
|
||||
@@ -646,7 +643,6 @@ ORDER BY orders.id ASC
|
||||
",
|
||||
$order_post_types
|
||||
);
|
||||
// phpcs:enable
|
||||
break;
|
||||
case self::ID_TYPE_DELETED_FROM_ORDERS_TABLE:
|
||||
return $this->get_deleted_order_ids( true, $limit );
|
||||
@@ -655,7 +651,7 @@ ORDER BY orders.id ASC
|
||||
default:
|
||||
throw new \Exception( 'Invalid $type, must be one of the ID_TYPE_... constants.' );
|
||||
}
|
||||
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
// phpcs:enable
|
||||
|
||||
// phpcs:ignore WordPress.DB
|
||||
return array_map( 'intval', $wpdb->get_col( $sql . " LIMIT $limit" ) );
|
||||
@@ -700,7 +696,7 @@ ORDER BY orders.id ASC
|
||||
*
|
||||
* @param array $batch Batch details.
|
||||
*/
|
||||
public function process_batch( array $batch ) : void {
|
||||
public function process_batch( array $batch ): void {
|
||||
if ( empty( $batch ) ) {
|
||||
return;
|
||||
}
|
||||
@@ -901,6 +897,26 @@ ORDER BY orders.id ASC
|
||||
return 'Synchronizes orders between posts and custom order tables.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevents deletion of order backup posts (regardless of sync setting) when HPOS is authoritative and the order
|
||||
* still exists in HPOS.
|
||||
* This should help with edge cases where wp_delete_post() would delete the HPOS record too or backfill would sync
|
||||
* incorrect data from an order with no metadata from the posts table.
|
||||
*
|
||||
* @since 8.8.0
|
||||
*
|
||||
* @param WP_Post|false|null $delete Whether to go forward with deletion.
|
||||
* @param WP_Post $post Post object.
|
||||
* @return WP_Post|false|null
|
||||
*/
|
||||
private function maybe_prevent_deletion_of_post( $delete, $post ) {
|
||||
if ( self::PLACEHOLDER_ORDER_POST_TYPE !== $post->post_type && $this->custom_orders_table_is_authoritative() && $this->data_store->order_exists( $post->ID ) ) {
|
||||
$delete = false;
|
||||
}
|
||||
|
||||
return $delete;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the 'deleted_post' action.
|
||||
*
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
namespace Automattic\WooCommerce\Internal\DataStores\Orders;
|
||||
|
||||
use Automattic\WooCommerce\Database\Migrations\CustomOrderTable\PostsToOrdersMigrationController;
|
||||
use Automattic\WooCommerce\Internal\Utilities\DatabaseUtil;
|
||||
use Automattic\WooCommerce\Utilities\ArrayUtil;
|
||||
use WC_Abstract_Order;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
@@ -57,7 +59,7 @@ class LegacyDataHandler {
|
||||
* @param array $order_ids If provided, total is computed only among IDs in this array, which can be either individual IDs or ranges like "100-200".
|
||||
* @return int Number of orders.
|
||||
*/
|
||||
public function count_orders_for_cleanup( $order_ids = array() ) : int {
|
||||
public function count_orders_for_cleanup( $order_ids = array() ): int {
|
||||
global $wpdb;
|
||||
return (int) $wpdb->get_var( $this->build_sql_query_for_cleanup( $order_ids, 'count' ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- prepared in build_sql_query_for_cleanup().
|
||||
}
|
||||
@@ -151,21 +153,26 @@ class LegacyDataHandler {
|
||||
public function cleanup_post_data( int $order_id, bool $skip_checks = false ): void {
|
||||
global $wpdb;
|
||||
|
||||
$order = wc_get_order( $order_id );
|
||||
if ( ! $order ) {
|
||||
// translators: %d is an order ID.
|
||||
throw new \Exception( sprintf( __( '%d is not a valid order ID.', 'woocommerce' ), $order_id ) );
|
||||
}
|
||||
$post_is_placeholder = get_post_type( $order_id ) === $this->data_synchronizer::PLACEHOLDER_ORDER_POST_TYPE;
|
||||
if ( ! $post_is_placeholder ) {
|
||||
$order = wc_get_order( $order_id );
|
||||
|
||||
if ( ! $skip_checks && ! $this->is_order_newer_than_post( $order ) ) {
|
||||
throw new \Exception( sprintf( __( 'Data in posts table appears to be more recent than in HPOS tables.', 'woocommerce' ) ) );
|
||||
if ( ! $order ) {
|
||||
// translators: %d is an order ID.
|
||||
throw new \Exception( esc_html( sprintf( __( '%d is not a valid order ID.', 'woocommerce' ), $order_id ) ) );
|
||||
}
|
||||
|
||||
if ( ! $skip_checks && ! $this->is_order_newer_than_post( $order ) ) {
|
||||
// translators: %1 is an order ID.
|
||||
throw new \Exception( esc_html( sprintf( __( 'Data in posts table appears to be more recent than in HPOS tables. Compare order data with `wp wc hpos diff %1$d` and use `wp wc hpos backfill %1$d --from=posts --to=hpos` to fix.', 'woocommerce' ), $order_id ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
// Delete all metadata.
|
||||
$wpdb->query(
|
||||
$wpdb->prepare(
|
||||
"DELETE FROM {$wpdb->postmeta} WHERE post_id = %d",
|
||||
$order->get_id()
|
||||
$order_id
|
||||
)
|
||||
);
|
||||
|
||||
@@ -176,11 +183,11 @@ class LegacyDataHandler {
|
||||
"UPDATE {$wpdb->posts} SET post_type = %s, post_status = %s WHERE ID = %d",
|
||||
$this->data_synchronizer::PLACEHOLDER_ORDER_POST_TYPE,
|
||||
'draft',
|
||||
$order->get_id()
|
||||
$order_id
|
||||
)
|
||||
);
|
||||
|
||||
clean_post_cache( $order->get_id() );
|
||||
clean_post_cache( $order_id );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -192,7 +199,7 @@ class LegacyDataHandler {
|
||||
*/
|
||||
private function is_order_newer_than_post( \WC_Abstract_Order $order ): bool {
|
||||
if ( ! is_a( $order->get_data_store()->get_current_class_name(), OrdersTableDataStore::class, true ) ) {
|
||||
throw new \Exception( __( 'Order is not an HPOS order.', 'woocommerce' ) );
|
||||
throw new \Exception( esc_html__( 'Order is not an HPOS order.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
$post = get_post( $order->get_id() );
|
||||
@@ -251,7 +258,7 @@ class LegacyDataHandler {
|
||||
$val2 = get_post_meta( $order_id, $key, true );
|
||||
}
|
||||
|
||||
if ( $val1 != $val2 ) { // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
|
||||
if ( $val1 != $val2 ) { // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison,Universal.Operators.StrictComparisons.LooseNotEqual
|
||||
$diff[ $key ] = array( $val1, $val2 );
|
||||
}
|
||||
}
|
||||
@@ -283,7 +290,7 @@ class LegacyDataHandler {
|
||||
|
||||
if ( ! $order_type ) {
|
||||
// translators: %d is an order ID.
|
||||
throw new \Exception( sprintf( __( '%d is not an order or has an invalid order type.', 'woocommerce' ), $order_id ) );
|
||||
throw new \Exception( esc_html( sprintf( __( '%d is not an order or has an invalid order type.', 'woocommerce' ), $order_id ) ) );
|
||||
}
|
||||
|
||||
$classname = $order_type['class_name'];
|
||||
@@ -321,27 +328,85 @@ class LegacyDataHandler {
|
||||
* @param int $order_id Order ID.
|
||||
* @param string $source_data_store Datastore to use as source. Should be either 'hpos' or 'posts'.
|
||||
* @param string $destination_data_store Datastore to use as destination. Should be either 'hpos' or 'posts'.
|
||||
* @param array $fields List of metakeys or order properties to limit the backfill to.
|
||||
* @return void
|
||||
* @throws \Exception When an error occurs.
|
||||
*/
|
||||
public function backfill_order_to_datastore( int $order_id, string $source_data_store, string $destination_data_store ) {
|
||||
public function backfill_order_to_datastore( int $order_id, string $source_data_store, string $destination_data_store, array $fields = array() ) {
|
||||
$valid_data_stores = array( 'posts', 'hpos' );
|
||||
|
||||
if ( ! in_array( $source_data_store, $valid_data_stores, true ) || ! in_array( $destination_data_store, $valid_data_stores, true ) || $destination_data_store === $source_data_store ) {
|
||||
throw new \Exception( sprintf( 'Invalid datastore arguments: %1$s -> %2$s.', $source_data_store, $destination_data_store ) );
|
||||
throw new \Exception( esc_html( sprintf( 'Invalid datastore arguments: %1$s -> %2$s.', $source_data_store, $destination_data_store ) ) );
|
||||
}
|
||||
|
||||
$order = $this->get_order_from_datastore( $order_id, $source_data_store );
|
||||
$fields = array_filter( $fields );
|
||||
$src_order = $this->get_order_from_datastore( $order_id, $source_data_store );
|
||||
|
||||
switch ( $destination_data_store ) {
|
||||
case 'posts':
|
||||
$order->get_data_store()->backfill_post_record( $order );
|
||||
break;
|
||||
case 'hpos':
|
||||
$this->posts_to_cot_migrator->migrate_orders( array( $order_id ) );
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
// Backfill entire orders.
|
||||
if ( ! $fields ) {
|
||||
if ( 'posts' === $destination_data_store ) {
|
||||
$src_order->get_data_store()->backfill_post_record( $src_order );
|
||||
} elseif ( 'hpos' === $destination_data_store ) {
|
||||
$this->posts_to_cot_migrator->migrate_orders( array( $src_order->get_id() ) );
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->validate_backfill_fields( $fields, $src_order );
|
||||
|
||||
$dest_order = $this->get_order_from_datastore( $src_order->get_id(), $destination_data_store );
|
||||
|
||||
if ( 'posts' === $destination_data_store ) {
|
||||
$datastore = $this->data_store->get_cpt_data_store_instance();
|
||||
} elseif ( 'hpos' === $destination_data_store ) {
|
||||
$datastore = $this->data_store;
|
||||
}
|
||||
|
||||
if ( ! $datastore || ! method_exists( $datastore, 'update_order_from_object' ) ) {
|
||||
throw new \Exception( esc_html__( 'The backup datastore does not support updating orders.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
// Backfill meta.
|
||||
if ( ! empty( $fields['meta_keys'] ) ) {
|
||||
foreach ( $fields['meta_keys'] as $meta_key ) {
|
||||
$dest_order->delete_meta_data( $meta_key );
|
||||
|
||||
foreach ( $src_order->get_meta( $meta_key, false, 'edit' ) as $meta ) {
|
||||
$dest_order->add_meta_data( $meta_key, $meta->value );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Backfill props.
|
||||
if ( ! empty( $fields['props'] ) ) {
|
||||
$new_values = array_combine(
|
||||
$fields['props'],
|
||||
array_map(
|
||||
fn( $prop_name ) => $src_order->{"get_{$prop_name}"}(),
|
||||
$fields['props']
|
||||
)
|
||||
);
|
||||
|
||||
$dest_order->set_props( $new_values );
|
||||
|
||||
if ( 'hpos' === $destination_data_store ) {
|
||||
$dest_order->apply_changes();
|
||||
$limit_cb = function ( $rows, $order ) use ( $dest_order, $fields ) {
|
||||
if ( $dest_order->get_id() === $order->get_id() ) {
|
||||
$rows = $this->limit_hpos_update_to_props( $rows, $fields['props'] );
|
||||
}
|
||||
|
||||
return $rows;
|
||||
};
|
||||
add_filter( 'woocommerce_orders_table_datastore_db_rows_for_order', $limit_cb, 10, 2 );
|
||||
}
|
||||
}
|
||||
|
||||
$datastore->update_order_from_object( $dest_order );
|
||||
|
||||
if ( 'hpos' === $destination_data_store && isset( $limit_cb ) ) {
|
||||
remove_filter( 'woocommerce_orders_table_datastore_db_rows_for_order', $limit_cb );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -372,13 +437,121 @@ class LegacyDataHandler {
|
||||
* @return string[] Property names.
|
||||
*/
|
||||
private function get_order_base_props(): array {
|
||||
return array_column(
|
||||
call_user_func_array(
|
||||
'array_merge',
|
||||
array_values( $this->data_store->get_all_order_column_mappings() )
|
||||
),
|
||||
'name'
|
||||
);
|
||||
$base_props = array();
|
||||
|
||||
foreach ( $this->data_store->get_all_order_column_mappings() as $mapping ) {
|
||||
$base_props = array_merge( $base_props, array_column( $mapping, 'name' ) );
|
||||
}
|
||||
|
||||
return $base_props;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters a set of HPOS row updates to those matching a specific set of order properties.
|
||||
* Called via the `woocommerce_orders_table_datastore_db_rows_for_order` filter in `backfill_order_to_datastore`.
|
||||
*
|
||||
* @param array $rows Details for the db update.
|
||||
* @param string[] $props Order property names.
|
||||
* @return array
|
||||
* @see OrdersTableDataStore::get_db_rows_for_order()
|
||||
*/
|
||||
private function limit_hpos_update_to_props( array $rows, array $props ) {
|
||||
// Determine HPOS columns corresponding to the props in the $props array.
|
||||
$allowed_columns = array();
|
||||
foreach ( $this->data_store->get_all_order_column_mappings() as &$mapping ) {
|
||||
foreach ( $mapping as $column_name => &$column_data ) {
|
||||
if ( ! isset( $column_data['name'] ) || ! in_array( $column_data['name'], $props, true ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$allowed_columns[ $column_data['name'] ] = $column_name;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ( $rows as $i => &$db_update ) {
|
||||
// Prevent accidental update of another prop by limiting columns to explicitly requested props.
|
||||
if ( ! array_intersect_key( $db_update['data'], array_flip( $allowed_columns ) ) ) {
|
||||
unset( $rows[ $i ] );
|
||||
continue;
|
||||
}
|
||||
|
||||
$allowed_column_names_with_ids = array_merge(
|
||||
$allowed_columns,
|
||||
array( 'id', 'order_id', 'address_type' )
|
||||
);
|
||||
|
||||
$db_update['data'] = array_intersect_key( $db_update['data'], array_flip( $allowed_column_names_with_ids ) );
|
||||
$db_update['format'] = array_intersect_key( $db_update['format'], array_flip( $allowed_column_names_with_ids ) );
|
||||
}
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates meta_keys and property names for a partial order backfill.
|
||||
*
|
||||
* @param array $fields An array possibly having entries with index 'meta_keys' and/or 'props',
|
||||
* corresponding to an array of order meta keys and/or order properties.
|
||||
* @param \WC_Abstract_Order $order The order being validated.
|
||||
* @throws \Exception When a validation error occurs.
|
||||
* @return void
|
||||
*/
|
||||
private function validate_backfill_fields( array $fields, \WC_Abstract_Order $order ) {
|
||||
if ( ! $fields ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! empty( $fields['meta_keys'] ) ) {
|
||||
$internal_meta_keys = array_unique(
|
||||
array_merge(
|
||||
$this->data_store->get_internal_meta_keys(),
|
||||
$this->data_store->get_cpt_data_store_instance()->get_internal_meta_keys()
|
||||
)
|
||||
);
|
||||
|
||||
$possibly_internal_keys = array_intersect( $internal_meta_keys, $fields['meta_keys'] );
|
||||
if ( ! empty( $possibly_internal_keys ) ) {
|
||||
throw new \Exception(
|
||||
esc_html(
|
||||
sprintf(
|
||||
// translators: %s is a comma separated list of metakey names.
|
||||
_n(
|
||||
'%s is an internal meta key. Use --props to set it.',
|
||||
'%s are internal meta keys. Use --props to set them.',
|
||||
count( $possibly_internal_keys ),
|
||||
'woocommerce'
|
||||
),
|
||||
implode( ', ', $possibly_internal_keys )
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $fields['props'] ) ) {
|
||||
$invalid_props = array_filter(
|
||||
$fields['props'],
|
||||
function ( $prop_name ) use ( $order ) {
|
||||
return ! method_exists( $order, "get_{$prop_name}" );
|
||||
}
|
||||
);
|
||||
|
||||
if ( ! empty( $invalid_props ) ) {
|
||||
throw new \Exception(
|
||||
esc_html(
|
||||
sprintf(
|
||||
// translators: %s is a list of order property names.
|
||||
_n(
|
||||
'%s is not a valid order property.',
|
||||
'%s are not valid order properties.',
|
||||
count( $invalid_props ),
|
||||
'woocommerce'
|
||||
),
|
||||
implode( ', ', $invalid_props )
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -531,7 +531,7 @@ class OrdersTableDataStore extends \Abstract_WC_Order_Data_Store_CPT implements
|
||||
*
|
||||
* @return string Alias.
|
||||
*/
|
||||
private function get_order_table_alias() : string {
|
||||
private function get_order_table_alias(): string {
|
||||
return 'o';
|
||||
}
|
||||
|
||||
@@ -540,7 +540,7 @@ class OrdersTableDataStore extends \Abstract_WC_Order_Data_Store_CPT implements
|
||||
*
|
||||
* @return string Alias.
|
||||
*/
|
||||
private function get_op_table_alias() : string {
|
||||
private function get_op_table_alias(): string {
|
||||
return 'p';
|
||||
}
|
||||
|
||||
@@ -551,7 +551,7 @@ class OrdersTableDataStore extends \Abstract_WC_Order_Data_Store_CPT implements
|
||||
*
|
||||
* @return string Alias.
|
||||
*/
|
||||
private function get_address_table_alias( string $type ) : string {
|
||||
private function get_address_table_alias( string $type ): string {
|
||||
return 'billing' === $type ? 'b' : 's';
|
||||
}
|
||||
|
||||
@@ -589,11 +589,24 @@ class OrdersTableDataStore extends \Abstract_WC_Order_Data_Store_CPT implements
|
||||
}
|
||||
|
||||
self::$backfilling_order_ids[] = $order->get_id();
|
||||
|
||||
// Attempt to create the backup post if missing.
|
||||
if ( $order->get_id() && is_null( get_post( $order->get_id() ) ) ) {
|
||||
if ( ! $this->maybe_create_backup_post( $order, 'backfill' ) ) {
|
||||
// translators: %d is an order ID.
|
||||
$this->error_logger->warning( sprintf( __( 'Unable to create backup post for order %d.', 'woocommerce' ), $order->get_id() ) );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$this->update_order_meta_from_object( $order );
|
||||
$order_class = get_class( $order );
|
||||
$post_order = new $order_class();
|
||||
$post_order->set_id( $order->get_id() );
|
||||
$cpt_data_store->read( $post_order );
|
||||
|
||||
if ( $cpt_data_store->order_exists( $order->get_id() ) ) {
|
||||
$cpt_data_store->read( $post_order );
|
||||
}
|
||||
|
||||
// This compares the order data to the post data and set changes array for props that are changed.
|
||||
$post_order->set_props( $order->get_data() );
|
||||
@@ -629,6 +642,41 @@ class OrdersTableDataStore extends \Abstract_WC_Order_Data_Store_CPT implements
|
||||
do_action( 'woocommerce_hpos_post_record_backfilled', $order );
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an order (in this datastore) from another order object.
|
||||
*
|
||||
* @param \WC_Abstract_Order $order Source order.
|
||||
* @return bool Whether the order was updated.
|
||||
*/
|
||||
public function update_order_from_object( $order ) {
|
||||
$hpos_order = new \WC_Order();
|
||||
$hpos_order->set_id( $order->get_id() );
|
||||
$this->read( $hpos_order );
|
||||
$hpos_order->set_props( $order->get_data() );
|
||||
|
||||
// Meta keys.
|
||||
foreach ( $hpos_order->get_meta_data() as &$meta ) {
|
||||
$hpos_order->delete_meta_data( $meta->key );
|
||||
}
|
||||
|
||||
foreach ( $order->get_meta_data() as &$meta ) {
|
||||
$hpos_order->add_meta_data( $meta->key, $meta->value );
|
||||
}
|
||||
|
||||
add_filter( 'woocommerce_orders_table_datastore_should_save_after_meta_change', '__return_false' );
|
||||
$hpos_order->save_meta_data();
|
||||
remove_filter( 'woocommerce_orders_table_datastore_should_save_after_meta_change', '__return_false' );
|
||||
|
||||
$db_rows = $this->get_db_rows_for_order( $hpos_order, 'update', true );
|
||||
foreach ( $db_rows as $db_update ) {
|
||||
ksort( $db_update['data'] );
|
||||
ksort( $db_update['format'] );
|
||||
$this->database_util->insert_on_duplicate_key_update( $db_update['table'], $db_update['data'], array_values( $db_update['format'] ) );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get information about whether permissions are granted yet.
|
||||
*
|
||||
@@ -1105,7 +1153,7 @@ WHERE
|
||||
* @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 {
|
||||
public function order_exists( $order_id ): bool {
|
||||
global $wpdb;
|
||||
|
||||
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
@@ -1144,7 +1192,7 @@ WHERE
|
||||
$data = $this->get_order_data_for_ids( $order_ids );
|
||||
|
||||
if ( count( $data ) !== count( $order_ids ) ) {
|
||||
throw new \Exception( __( 'Invalid order IDs in call to read_multiple()', 'woocommerce' ) );
|
||||
throw new \Exception( esc_html__( 'Invalid order IDs in call to read_multiple()', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
$data_synchronizer = wc_get_container()->get( DataSynchronizer::class );
|
||||
@@ -1188,7 +1236,7 @@ WHERE
|
||||
*
|
||||
* @return bool Whether the order should be synced.
|
||||
*/
|
||||
private function should_sync_order( \WC_Abstract_Order $order ) : bool {
|
||||
private function should_sync_order( \WC_Abstract_Order $order ): bool {
|
||||
$draft_order = in_array( $order->get_status(), array( 'draft', 'auto-draft' ), true );
|
||||
$already_synced = in_array( $order->get_id(), self::$reading_order_ids, true );
|
||||
return ! $draft_order && ! $already_synced;
|
||||
@@ -1225,7 +1273,7 @@ WHERE
|
||||
*
|
||||
* @return array Filtered meta data.
|
||||
*/
|
||||
public function filter_raw_meta_data( &$object, $raw_meta_data ) {
|
||||
public function filter_raw_meta_data( &$object, $raw_meta_data ) { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.objectFound
|
||||
$filtered_meta_data = parent::filter_raw_meta_data( $object, $raw_meta_data );
|
||||
$allowed_keys = array(
|
||||
'_billing_address_index',
|
||||
@@ -1233,7 +1281,7 @@ WHERE
|
||||
);
|
||||
$allowed_meta = array_filter(
|
||||
$raw_meta_data,
|
||||
function( $meta ) use ( $allowed_keys ) {
|
||||
function ( $meta ) use ( $allowed_keys ) {
|
||||
return in_array( $meta->meta_key, $allowed_keys, true );
|
||||
}
|
||||
);
|
||||
@@ -1583,15 +1631,7 @@ WHERE
|
||||
);
|
||||
// phpcs:enable
|
||||
|
||||
$meta_data_query = $this->get_order_meta_select_statement();
|
||||
$order_data = array();
|
||||
$meta_data = $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- $meta_data_query and $order_meta_table is autogenerated and should already be prepared. $id_placeholder is already prepared.
|
||||
"$meta_data_query WHERE $order_meta_table.order_id in ( $id_placeholder )",
|
||||
$ids
|
||||
)
|
||||
);
|
||||
$order_data = array();
|
||||
|
||||
foreach ( $table_data as $table_datum ) {
|
||||
$id = $table_datum->{"{$order_table_alias}_id"};
|
||||
@@ -1615,14 +1655,27 @@ WHERE
|
||||
$order_data[ $id ]->meta_data = array();
|
||||
}
|
||||
|
||||
foreach ( $meta_data as $meta_datum ) {
|
||||
// phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_key, WordPress.DB.SlowDBQuery.slow_db_query_meta_value -- Not a meta query.
|
||||
$order_data[ $meta_datum->order_id ]->meta_data[] = (object) array(
|
||||
'meta_id' => $meta_datum->id,
|
||||
'meta_key' => $meta_datum->meta_key,
|
||||
'meta_value' => $meta_datum->meta_value,
|
||||
if ( count( $order_data ) > 0 ) {
|
||||
$meta_order_ids = array_keys( $order_data );
|
||||
$meta_order_id_placeholder = implode( ', ', array_fill( 0, count( $meta_order_ids ), '%d' ) );
|
||||
$meta_data_query = $this->get_order_meta_select_statement();
|
||||
$meta_data = $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- $meta_data_query and $order_meta_table is autogenerated and should already be prepared. $id_placeholder is already prepared.
|
||||
"$meta_data_query WHERE $order_meta_table.order_id in ( $meta_order_id_placeholder )",
|
||||
$ids
|
||||
)
|
||||
);
|
||||
// phpcs:enable
|
||||
|
||||
foreach ( $meta_data as $meta_datum ) {
|
||||
// phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_key, WordPress.DB.SlowDBQuery.slow_db_query_meta_value -- Not a meta query.
|
||||
$order_data[ $meta_datum->order_id ]->meta_data[] = (object) array(
|
||||
'meta_id' => $meta_datum->id,
|
||||
'meta_key' => $meta_datum->meta_key,
|
||||
'meta_value' => $meta_datum->meta_value,
|
||||
);
|
||||
// phpcs:enable
|
||||
}
|
||||
}
|
||||
return $order_data;
|
||||
}
|
||||
@@ -1787,22 +1840,12 @@ FROM $order_meta_table
|
||||
* @since 6.8.0
|
||||
*/
|
||||
protected function persist_order_to_db( &$order, bool $force_all_fields = false ) {
|
||||
$context = ( 0 === absint( $order->get_id() ) ) ? 'create' : 'update';
|
||||
$data_sync = wc_get_container()->get( DataSynchronizer::class );
|
||||
$context = ( 0 === absint( $order->get_id() ) ) ? 'create' : 'update';
|
||||
|
||||
if ( 'create' === $context ) {
|
||||
$post_id = wp_insert_post(
|
||||
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,
|
||||
'post_date' => gmdate( 'Y-m-d H:i:s', $order->get_date_created( 'edit' )->getOffsetTimestamp() ),
|
||||
'post_date_gmt' => gmdate( 'Y-m-d H:i:s', $order->get_date_created( 'edit' )->getTimestamp() ),
|
||||
)
|
||||
);
|
||||
|
||||
$post_id = $this->maybe_create_backup_post( $order, 'create' );
|
||||
if ( ! $post_id ) {
|
||||
throw new \Exception( __( 'Could not create order in posts table.', 'woocommerce' ) );
|
||||
throw new \Exception( esc_html__( 'Could not create order in posts table.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
$order->set_id( $post_id );
|
||||
@@ -1826,7 +1869,7 @@ FROM $order_meta_table
|
||||
|
||||
if ( false === $result ) {
|
||||
// translators: %s is a table name.
|
||||
throw new \Exception( sprintf( __( 'Could not persist order to database table "%s".', 'woocommerce' ), $update['table'] ) );
|
||||
throw new \Exception( esc_html( sprintf( __( 'Could not persist order to database table "%s".', 'woocommerce' ), $update['table'] ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1836,6 +1879,37 @@ FROM $order_meta_table
|
||||
$this->set_custom_taxonomies( $order, $default_taxonomies );
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes care of creating the backup post in the posts table (placeholder or actual order post, depending on sync settings).
|
||||
*
|
||||
* @since 8.8.0
|
||||
*
|
||||
* @param \WC_Abstract_Order $order The order.
|
||||
* @param string $context The context: either 'create' or 'backfill'.
|
||||
* @return int The new post ID.
|
||||
*/
|
||||
protected function maybe_create_backup_post( &$order, string $context ): int {
|
||||
$data_sync = wc_get_container()->get( DataSynchronizer::class );
|
||||
|
||||
$data = 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,
|
||||
'post_date' => gmdate( 'Y-m-d H:i:s', $order->get_date_created( 'edit' )->getOffsetTimestamp() ),
|
||||
'post_date_gmt' => gmdate( 'Y-m-d H:i:s', $order->get_date_created( 'edit' )->getTimestamp() ),
|
||||
);
|
||||
|
||||
if ( 'backfill' === $context ) {
|
||||
if ( ! $order->get_id() ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$data['import_id'] = $order->get_id();
|
||||
}
|
||||
|
||||
return wp_insert_post( $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set default taxonomies for the order.
|
||||
*
|
||||
@@ -1999,7 +2073,24 @@ FROM $order_meta_table
|
||||
*/
|
||||
$ext_rows = apply_filters( 'woocommerce_orders_table_datastore_extra_db_rows_for_order', array(), $order, $context );
|
||||
|
||||
return array_merge( $result, $ext_rows );
|
||||
/**
|
||||
* Filters the rows that are going to be inserted or updated during an order save.
|
||||
*
|
||||
* @since 8.8.0
|
||||
* @internal Use 'woocommerce_orders_table_datastore_extra_db_rows_for_order' for adding rows to the database save.
|
||||
*
|
||||
* @param array $rows Array of rows to be inserted/updated. See 'woocommerce_orders_table_datastore_extra_db_rows_for_order' for exact format.
|
||||
* @param \WC_Order $order The order object.
|
||||
* @param string $context The context of the operation: 'create' or 'update'.
|
||||
*/
|
||||
$result = apply_filters(
|
||||
'woocommerce_orders_table_datastore_db_rows_for_order',
|
||||
array_merge( $result, $ext_rows ),
|
||||
$order,
|
||||
$context
|
||||
);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2112,7 +2203,7 @@ FROM $order_meta_table
|
||||
/**
|
||||
* Fires immediately after an order is deleted.
|
||||
*
|
||||
* @since
|
||||
* @since 2.7.0
|
||||
*
|
||||
* @param int $order_id ID of the order that has been deleted.
|
||||
*/
|
||||
@@ -2137,7 +2228,7 @@ FROM $order_meta_table
|
||||
/**
|
||||
* Fires immediately after an order is trashed.
|
||||
*
|
||||
* @since
|
||||
* @since 2.7.0
|
||||
*
|
||||
* @param int $order_id ID of the order that has been trashed.
|
||||
*/
|
||||
@@ -2198,7 +2289,7 @@ FROM $order_meta_table
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function upshift_or_delete_child_orders( $order ) : void {
|
||||
private function upshift_or_delete_child_orders( $order ): void {
|
||||
global $wpdb;
|
||||
|
||||
$order_table = self::get_orders_table_name();
|
||||
@@ -2703,7 +2794,6 @@ FROM $order_meta_table
|
||||
if ( $save ) {
|
||||
$order->save_meta_data();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2871,7 +2961,7 @@ CREATE TABLE $meta_table (
|
||||
* @param WC_Data $object WC_Data object.
|
||||
* @return array
|
||||
*/
|
||||
public function read_meta( &$object ) {
|
||||
public function read_meta( &$object ) { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.objectFound
|
||||
$raw_meta_data = $this->data_store_meta->read_meta( $object );
|
||||
return $this->filter_raw_meta_data( $object, $raw_meta_data );
|
||||
}
|
||||
@@ -2884,7 +2974,7 @@ CREATE TABLE $meta_table (
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function delete_meta( &$object, $meta ) {
|
||||
public function delete_meta( &$object, $meta ) { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.objectFound
|
||||
global $wpdb;
|
||||
|
||||
if ( $this->should_backfill_post_record() && isset( $meta->id ) ) {
|
||||
@@ -2932,7 +3022,7 @@ CREATE TABLE $meta_table (
|
||||
*
|
||||
* @return int|bool meta ID or false on failure
|
||||
*/
|
||||
public function add_meta( &$object, $meta ) {
|
||||
public function add_meta( &$object, $meta ) { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.objectFound
|
||||
$add_meta = $this->data_store_meta->add_meta( $object, $meta );
|
||||
$meta->id = $add_meta;
|
||||
$changes_applied = $this->after_meta_change( $object, $meta );
|
||||
@@ -2954,7 +3044,7 @@ CREATE TABLE $meta_table (
|
||||
*
|
||||
* @return bool The number of rows updated, or false on error.
|
||||
*/
|
||||
public function update_meta( &$object, $meta ) {
|
||||
public function update_meta( &$object, $meta ) { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.objectFound
|
||||
$update_meta = $this->data_store_meta->update_meta( $object, $meta );
|
||||
$changes_applied = $this->after_meta_change( $object, $meta );
|
||||
|
||||
@@ -3007,6 +3097,17 @@ CREATE TABLE $meta_table (
|
||||
$current_time = $this->legacy_proxy->call_function( 'current_time', 'mysql', 1 );
|
||||
$current_date_time = new \WC_DateTime( $current_time, new \DateTimeZone( 'GMT' ) );
|
||||
|
||||
return $order->get_date_modified() < $current_date_time && empty( $order->get_changes() ) && ( ! is_object( $meta ) || ! in_array( $meta->key, $this->ephemeral_meta_keys, true ) );
|
||||
$should_save =
|
||||
$order->get_date_modified() < $current_date_time && empty( $order->get_changes() )
|
||||
&& ( ! is_object( $meta ) || ! in_array( $meta->key, $this->ephemeral_meta_keys, true ) );
|
||||
|
||||
/**
|
||||
* Allows code to skip a full order save() when metadata is changed.
|
||||
*
|
||||
* @since 8.8.0
|
||||
*
|
||||
* @param bool $should_save Whether to trigger a full save after metadata is changed.
|
||||
*/
|
||||
return apply_filters( 'woocommerce_orders_table_datastore_should_save_after_meta_change', $should_save );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ class OrdersTableSearchQuery {
|
||||
/**
|
||||
* Limits the search to a specific field.
|
||||
*
|
||||
* @var string
|
||||
* @var string[]
|
||||
*/
|
||||
private $search_filters;
|
||||
|
||||
@@ -40,8 +40,8 @@ class OrdersTableSearchQuery {
|
||||
*/
|
||||
public function __construct( OrdersTableQuery $query ) {
|
||||
$this->query = $query;
|
||||
$this->search_term = urldecode( $query->get( 's' ) );
|
||||
$this->search_filters = $this->sanitize_search_filters( urldecode( $query->get( 'search_filter' ) ) );
|
||||
$this->search_term = $query->get( 's' );
|
||||
$this->search_filters = $this->sanitize_search_filters( $query->get( 'search_filter' ) ?? '' );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -52,17 +52,18 @@ class OrdersTableSearchQuery {
|
||||
* @return array Array of search filters.
|
||||
*/
|
||||
private function sanitize_search_filters( string $search_filter ) : array {
|
||||
$available_filters = array(
|
||||
$core_filters = array(
|
||||
'order_id',
|
||||
'transaction_id',
|
||||
'customer_email',
|
||||
'customers', // customers also searches in meta.
|
||||
'products',
|
||||
);
|
||||
|
||||
if ( 'all' === $search_filter || '' === $search_filter ) {
|
||||
return $available_filters;
|
||||
return $core_filters;
|
||||
} else {
|
||||
return array_intersect( $available_filters, array( $search_filter ) );
|
||||
return array( $search_filter );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,7 +120,29 @@ class OrdersTableSearchQuery {
|
||||
LEFT JOIN $items_table AS search_query_items ON search_query_items.order_id = $orders_table.id
|
||||
";
|
||||
}
|
||||
return '';
|
||||
|
||||
/**
|
||||
* Filter to support adding a custom order search filter.
|
||||
* Provide a JOIN clause for a new search filter. This should be used along with `woocommerce_hpos_admin_search_filters`
|
||||
* to declare a new custom filter, and `woocommerce_hpos_generate_where_for_search_filter` to generate the WHERE
|
||||
* clause.
|
||||
*
|
||||
* Hardcoded JOINS (products) cannot be modified using this filter for consistency.
|
||||
*
|
||||
* @since 8.9.0
|
||||
*
|
||||
* @param string $join The JOIN clause.
|
||||
* @param string $search_term The search term.
|
||||
* @param string $search_filter The search filter. Use this to bail early if this is not filter you are interested in.
|
||||
* @param OrdersTableQuery $query The order query object.
|
||||
*/
|
||||
return apply_filters(
|
||||
'woocommerce_hpos_generate_join_for_search_filter',
|
||||
'',
|
||||
$this->search_term,
|
||||
$search_filter,
|
||||
$this->query
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -177,6 +200,13 @@ class OrdersTableSearchQuery {
|
||||
);
|
||||
}
|
||||
|
||||
if ( 'transaction_id' === $search_filter ) {
|
||||
return $wpdb->prepare(
|
||||
"`$order_table`.transaction_id LIKE %s", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $order_table is hardcoded.
|
||||
'%' . $wpdb->esc_like( $this->search_term ) . '%'
|
||||
);
|
||||
}
|
||||
|
||||
if ( 'products' === $search_filter ) {
|
||||
return $wpdb->prepare(
|
||||
'search_query_items.order_item_name LIKE %s',
|
||||
@@ -189,7 +219,28 @@ class OrdersTableSearchQuery {
|
||||
return "`$order_table`.id IN ( $meta_sub_query ) ";
|
||||
}
|
||||
|
||||
return '';
|
||||
/**
|
||||
* Filter to support adding a custom order search filter.
|
||||
* Provide a WHERE clause for a custom search filter via this filter. This should be used with the
|
||||
* `woocommerce_hpos_admin_search_filters` to declare a new custom filter, and optionally also with the
|
||||
* `woocommerce_hpos_generate_join_for_search_filter` filter if a join is also needed.
|
||||
*
|
||||
* Hardcoded filters (products, customers, ID and email) cannot be modified using this filter for consistency.
|
||||
*
|
||||
* @since 8.9.0
|
||||
*
|
||||
* @param string $where WHERE clause to add to the search query.
|
||||
* @param string $search_term The search term.
|
||||
* @param string $search_filter Name of the search filter. Use this to bail early if this is not the filter you are looking for.
|
||||
* @param OrdersTableQuery $query The order query object.
|
||||
*/
|
||||
return apply_filters(
|
||||
'woocommerce_hpos_generate_where_for_search_filter',
|
||||
'',
|
||||
$this->search_term,
|
||||
$search_filter,
|
||||
$this->query
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user