plugin updates
This commit is contained in:
@@ -12,6 +12,7 @@ use Automattic\WooCommerce\Internal\Features\FeaturesController;
|
||||
use Automattic\WooCommerce\Internal\Traits\AccessiblePrivateMethods;
|
||||
use Automattic\WooCommerce\Utilities\PluginUtil;
|
||||
use ActionScheduler;
|
||||
use WC_Admin_Settings;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
@@ -326,9 +327,16 @@ class CustomOrdersTableController {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( filter_input( INPUT_GET, self::SYNC_QUERY_ARG, FILTER_VALIDATE_BOOLEAN ) ) {
|
||||
$this->batch_processing_controller->enqueue_processor( DataSynchronizer::class );
|
||||
if ( ! filter_input( INPUT_GET, self::SYNC_QUERY_ARG, FILTER_VALIDATE_BOOLEAN ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ?? '' ) ), 'hpos-sync-now' ) ) {
|
||||
WC_Admin_Settings::add_error( esc_html__( 'Unable to start synchronization. The link you followed may have expired.', 'woocommerce' ) );
|
||||
return;
|
||||
}
|
||||
|
||||
$this->batch_processing_controller->enqueue_processor( DataSynchronizer::class );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -506,11 +514,14 @@ class CustomOrdersTableController {
|
||||
$orders_pending_sync_count
|
||||
);
|
||||
} elseif ( $sync_is_pending ) {
|
||||
$sync_now_url = add_query_arg(
|
||||
array(
|
||||
self::SYNC_QUERY_ARG => true,
|
||||
$sync_now_url = wp_nonce_url(
|
||||
add_query_arg(
|
||||
array(
|
||||
self::SYNC_QUERY_ARG => true,
|
||||
),
|
||||
wc_get_container()->get( FeaturesController::class )->get_features_page_url()
|
||||
),
|
||||
wc_get_container()->get( FeaturesController::class )->get_features_page_url()
|
||||
'hpos-sync-now'
|
||||
);
|
||||
|
||||
if ( ! $is_dangerous ) {
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\DataStores\Orders;
|
||||
|
||||
use Automattic\WooCommerce\Caches\OrderCache;
|
||||
use Automattic\WooCommerce\Caches\OrderCacheController;
|
||||
use Automattic\WooCommerce\Database\Migrations\CustomOrderTable\PostsToOrdersMigrationController;
|
||||
use Automattic\WooCommerce\Internal\Admin\Orders\EditLock;
|
||||
use Automattic\WooCommerce\Internal\BatchProcessing\{ BatchProcessingController, BatchProcessorInterface };
|
||||
use Automattic\WooCommerce\Internal\Features\FeaturesController;
|
||||
use Automattic\WooCommerce\Internal\Traits\AccessiblePrivateMethods;
|
||||
@@ -335,6 +335,33 @@ class DataSynchronizer implements BatchProcessorInterface {
|
||||
return $interval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Keys that can be ignored during synchronization or verification.
|
||||
*
|
||||
* @since 8.6.0
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function get_ignored_order_props() {
|
||||
/**
|
||||
* Allows modifying the list of order properties that are ignored during HPOS synchronization or verification.
|
||||
*
|
||||
* @param string[] List of order properties or meta keys.
|
||||
* @since 8.6.0
|
||||
*/
|
||||
$ignored_props = apply_filters( 'woocommerce_hpos_sync_ignored_order_props', array() );
|
||||
$ignored_props = array_filter( array_map( 'trim', array_filter( $ignored_props, 'is_string' ) ) );
|
||||
|
||||
return array_merge(
|
||||
$ignored_props,
|
||||
array(
|
||||
'_paid_date', // This has been deprecated and replaced by '_date_paid' in the CPT datastore.
|
||||
'_completed_date', // This has been deprecated and replaced by '_date_completed' in the CPT datastore.
|
||||
EditLock::META_KEY_NAME,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule an event to run background sync when the mode is set to interval.
|
||||
*
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\DataStores\Orders;
|
||||
|
||||
use Automattic\WooCommerce\Utilities\ArrayUtil;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
@@ -171,13 +173,11 @@ class LegacyDataHandler {
|
||||
/**
|
||||
* Checks whether an HPOS-backed order is newer than the corresponding post.
|
||||
*
|
||||
* @param int|\WC_Order $order An HPOS order.
|
||||
* @param \WC_Abstract_Order $order An HPOS order.
|
||||
* @return bool TRUE if the order is up to date with the corresponding post.
|
||||
* @throws \Exception When the order is not an HPOS order.
|
||||
*/
|
||||
private function is_order_newer_than_post( $order ): bool {
|
||||
$order = is_a( $order, 'WC_Order' ) ? $order : wc_get_order( absint( $order ) );
|
||||
|
||||
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' ) );
|
||||
}
|
||||
@@ -195,6 +195,137 @@ class LegacyDataHandler {
|
||||
return $order_modified_gmt >= $post_modified_gmt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an array with properties and metadata for which HPOS and post record have different values.
|
||||
* Given it's mostly informative nature, it doesn't perform any deep or recursive searches and operates only on top-level properties/metadata.
|
||||
*
|
||||
* @since 8.6.0
|
||||
*
|
||||
* @param int $order_id Order ID.
|
||||
* @return array Array of [HPOS value, post value] keyed by property, for all properties where HPOS and post value differ.
|
||||
*/
|
||||
public function get_diff_for_order( int $order_id ): array {
|
||||
$diff = array();
|
||||
|
||||
$hpos_order = $this->get_order_from_datastore( $order_id, 'hpos' );
|
||||
$cpt_order = $this->get_order_from_datastore( $order_id, 'cpt' );
|
||||
|
||||
if ( $hpos_order->get_type() !== $cpt_order->get_type() ) {
|
||||
$diff['type'] = array( $hpos_order->get_type(), $cpt_order->get_type() );
|
||||
}
|
||||
|
||||
$hpos_meta = $this->order_meta_to_array( $hpos_order );
|
||||
$cpt_meta = $this->order_meta_to_array( $cpt_order );
|
||||
|
||||
// Consider only keys for which we actually have a corresponding HPOS column or are meta.
|
||||
$all_keys = array_unique(
|
||||
array_diff(
|
||||
array_merge(
|
||||
$this->get_order_base_props(),
|
||||
array_keys( $hpos_meta ),
|
||||
array_keys( $cpt_meta )
|
||||
),
|
||||
$this->data_synchronizer->get_ignored_order_props()
|
||||
)
|
||||
);
|
||||
|
||||
foreach ( $all_keys as $key ) {
|
||||
$val1 = in_array( $key, $this->get_order_base_props(), true ) ? $hpos_order->{"get_$key"}() : ( $hpos_meta[ $key ] ?? null );
|
||||
$val2 = in_array( $key, $this->get_order_base_props(), true ) ? $cpt_order->{"get_$key"}() : ( $cpt_meta[ $key ] ?? null );
|
||||
|
||||
// Workaround for https://github.com/woocommerce/woocommerce/issues/43126.
|
||||
if ( ! $val2 && in_array( $key, array( '_billing_address_index', '_shipping_address_index' ), true ) ) {
|
||||
$val2 = get_post_meta( $order_id, $key, true );
|
||||
}
|
||||
|
||||
if ( $val1 != $val2 ) { // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
|
||||
$diff[ $key ] = array( $val1, $val2 );
|
||||
}
|
||||
}
|
||||
|
||||
return $diff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an order object as seen by either the HPOS or CPT datastores.
|
||||
*
|
||||
* @since 8.6.0
|
||||
*
|
||||
* @param int $order_id Order ID.
|
||||
* @param string $data_store_id Datastore to use. Should be either 'hpos' or 'cpt'. Defaults to 'hpos'.
|
||||
* @return \WC_Order Order instance.
|
||||
*/
|
||||
public function get_order_from_datastore( int $order_id, string $data_store_id = 'hpos' ) {
|
||||
$data_store = ( 'hpos' === $data_store_id ) ? $this->data_store : $this->data_store->get_cpt_data_store_instance();
|
||||
|
||||
wp_cache_delete( \WC_Order::generate_meta_cache_key( $order_id, 'orders' ), 'orders' );
|
||||
|
||||
// Prime caches if we can.
|
||||
if ( method_exists( $data_store, 'prime_caches_for_orders' ) ) {
|
||||
$data_store->prime_caches_for_orders( array( $order_id ), array() );
|
||||
}
|
||||
|
||||
$classname = wc_get_order_type( $data_store->get_order_type( $order_id ) )['class_name'];
|
||||
$order = new $classname();
|
||||
$order->set_id( $order_id );
|
||||
|
||||
// Switch datastore if necessary.
|
||||
$update_data_store_func = function ( $data_store ) {
|
||||
// Each order object contains a reference to its data store, but this reference is itself
|
||||
// held inside of an instance of WC_Data_Store, so we create that first.
|
||||
$data_store_wrapper = \WC_Data_Store::load( 'order' );
|
||||
|
||||
// Bind $data_store to our WC_Data_Store.
|
||||
( function ( $data_store ) {
|
||||
$this->current_class_name = get_class( $data_store );
|
||||
$this->instance = $data_store;
|
||||
} )->call( $data_store_wrapper, $data_store );
|
||||
|
||||
// Finally, update the $order object with our WC_Data_Store( $data_store ) instance.
|
||||
$this->data_store = $data_store_wrapper;
|
||||
};
|
||||
$update_data_store_func->call( $order, $data_store );
|
||||
|
||||
// Read order.
|
||||
$data_store->read( $order );
|
||||
|
||||
return $order;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all metadata in an order object as an array.
|
||||
*
|
||||
* @param \WC_Order $order Order instance.
|
||||
* @return array Array of metadata grouped by meta key.
|
||||
*/
|
||||
private function order_meta_to_array( \WC_Order &$order ): array {
|
||||
$result = array();
|
||||
|
||||
foreach ( ArrayUtil::select( $order->get_meta_data(), 'get_data', ArrayUtil::SELECT_BY_OBJECT_METHOD ) as &$meta ) {
|
||||
if ( array_key_exists( $meta['key'], $result ) ) {
|
||||
$result[ $meta['key'] ] = array( $result[ $meta['key'] ] );
|
||||
$result[ $meta['key'] ][] = $meta['value'];
|
||||
} else {
|
||||
$result[ $meta['key'] ] = $meta['value'];
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns names of all order base properties supported by HPOS.
|
||||
*
|
||||
* @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'
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -97,6 +97,15 @@ class OrdersTableDataStore extends \Abstract_WC_Order_Data_Store_CPT implements
|
||||
'_new_order_email_sent',
|
||||
);
|
||||
|
||||
/**
|
||||
* Meta keys that are considered ephemereal and do not trigger a full save (updating modified date) when changed.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $ephemeral_meta_keys = array(
|
||||
EditLock::META_KEY_NAME,
|
||||
);
|
||||
|
||||
/**
|
||||
* Handles custom metadata in the wc_orders_meta table.
|
||||
*
|
||||
@@ -1253,12 +1262,10 @@ WHERE
|
||||
$post_order_modified_date = is_null( $post_order_modified_date ) ? 0 : $post_order_modified_date->getTimestamp();
|
||||
|
||||
/**
|
||||
* We are here because there was difference in posts and order data, although the sync is enabled.
|
||||
* When order modified date is more recent than post modified date, it can only mean that COT definitely has more updated version of the order.
|
||||
*
|
||||
* In a case where post meta was updated (without updating post_modified date), post_modified would be equal to order_modified date.
|
||||
*
|
||||
* So we write back to the order table when order modified date is more recent than post modified date. Otherwise, we write to the post table.
|
||||
* We are here because there was difference in the post and order data even though sync is enabled. If the modified date in
|
||||
* the post is the same or more recent than the modified date in the order object, we update the order object with the data
|
||||
* from the post. The opposite case is handled in 'backfill_post_record'. This mitigates the case where other plugins write
|
||||
* to the post or postmeta directly.
|
||||
*/
|
||||
if ( $post_order_modified_date >= $order_modified_date ) {
|
||||
$this->migrate_post_record( $order, $post_order );
|
||||
@@ -1547,7 +1554,7 @@ WHERE
|
||||
*
|
||||
* @param array $ids List of order IDs.
|
||||
*
|
||||
* @return \stdClass[]|object|null DB Order objects or error.
|
||||
* @return \stdClass[] DB Order objects or error.
|
||||
*/
|
||||
protected function get_order_data_for_ids( $ids ) {
|
||||
global $wpdb;
|
||||
@@ -2978,9 +2985,7 @@ CREATE TABLE $meta_table (
|
||||
private function should_save_after_meta_change( $order, $meta = null ) {
|
||||
$current_time = $this->legacy_proxy->call_function( 'current_time', 'mysql', 1 );
|
||||
$current_date_time = new \WC_DateTime( $current_time, new \DateTimeZone( 'GMT' ) );
|
||||
$skip_for = array(
|
||||
EditLock::META_KEY_NAME,
|
||||
);
|
||||
return $order->get_date_modified() < $current_date_time && empty( $order->get_changes() ) && ( ! is_object( $meta ) || ! in_array( $meta->key, $skip_for, true ) );
|
||||
|
||||
return $order->get_date_modified() < $current_date_time && empty( $order->get_changes() ) && ( ! is_object( $meta ) || ! in_array( $meta->key, $this->ephemeral_meta_keys, true ) );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,7 +185,7 @@ class OrdersTableFieldQuery {
|
||||
} else {
|
||||
$relation = $q['relation'];
|
||||
unset( $q['relation'] );
|
||||
|
||||
$chunks = array();
|
||||
foreach ( $q as $query ) {
|
||||
$chunks[] = $this->process( $query );
|
||||
}
|
||||
@@ -292,11 +292,10 @@ class OrdersTableFieldQuery {
|
||||
}
|
||||
|
||||
$clause_compare = $clause['compare'];
|
||||
|
||||
switch ( $clause_compare ) {
|
||||
case 'IN':
|
||||
case 'NOT IN':
|
||||
$where = $wpdb->prepare( '(' . substr( str_repeat( ',%s', count( $clause_value ) ), 1 ) . ')', $clause_value ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
$where = $wpdb->prepare( '(' . substr( str_repeat( ',%s', count( (array) $clause_value ) ), 1 ) . ')', $clause_value ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
break;
|
||||
case 'BETWEEN':
|
||||
case 'NOT BETWEEN':
|
||||
@@ -327,7 +326,7 @@ class OrdersTableFieldQuery {
|
||||
break;
|
||||
}
|
||||
|
||||
if ( $where ) {
|
||||
if ( ! empty( $where ) ) {
|
||||
if ( 'CHAR' === $clause['cast'] ) {
|
||||
return "`{$clause['alias']}`.`{$clause['column']}` {$clause_compare} {$where}";
|
||||
} else {
|
||||
|
||||
@@ -389,7 +389,7 @@ class OrdersTableMetaQuery {
|
||||
// Nested.
|
||||
$relation = $arg['relation'];
|
||||
unset( $arg['relation'] );
|
||||
|
||||
$chunks = array();
|
||||
foreach ( $arg as $index => &$clause ) {
|
||||
$chunks[] = $this->process( $clause, $arg );
|
||||
}
|
||||
@@ -519,6 +519,9 @@ class OrdersTableMetaQuery {
|
||||
|
||||
$alias = $clause['alias'];
|
||||
|
||||
$meta_compare_string_start = '';
|
||||
$meta_compare_string_end = '';
|
||||
$subquery_alias = '';
|
||||
if ( in_array( $clause['compare_key'], array( '!=', 'NOT IN', 'NOT LIKE', 'NOT EXISTS', 'NOT REGEXP' ), true ) ) {
|
||||
$i = count( $this->table_aliases );
|
||||
$subquery_alias = self::ALIAS_PREFIX . $i;
|
||||
@@ -541,7 +544,7 @@ class OrdersTableMetaQuery {
|
||||
$where = $wpdb->prepare( "$alias.meta_key LIKE %s", $meta_compare_value ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
break;
|
||||
case 'IN':
|
||||
$meta_compare_string = "$alias.meta_key IN (" . substr( str_repeat( ',%s', count( $clause['key'] ) ), 1 ) . ')';
|
||||
$meta_compare_string = "$alias.meta_key IN (" . substr( str_repeat( ',%s', count( (array) $clause['key'] ) ), 1 ) . ')';
|
||||
$where = $wpdb->prepare( $meta_compare_string, $clause['key'] ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
break;
|
||||
case 'RLIKE':
|
||||
@@ -566,7 +569,7 @@ class OrdersTableMetaQuery {
|
||||
$where = $wpdb->prepare( $meta_compare_string, $meta_compare_value ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
break;
|
||||
case 'NOT IN':
|
||||
$array_subclause = '(' . substr( str_repeat( ',%s', count( $clause['key'] ) ), 1 ) . ') ';
|
||||
$array_subclause = '(' . substr( str_repeat( ',%s', count( (array) $clause['key'] ) ), 1 ) . ') ';
|
||||
$meta_compare_string = $meta_compare_string_start . "AND $subquery_alias.meta_key IN " . $array_subclause . $meta_compare_string_end;
|
||||
$where = $wpdb->prepare( $meta_compare_string, $clause['key'] ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
break;
|
||||
@@ -618,7 +621,7 @@ class OrdersTableMetaQuery {
|
||||
switch ( $meta_compare ) {
|
||||
case 'IN':
|
||||
case 'NOT IN':
|
||||
$where = $wpdb->prepare( '(' . substr( str_repeat( ',%s', count( $meta_value ) ), 1 ) . ')', $meta_value ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
$where = $wpdb->prepare( '(' . substr( str_repeat( ',%s', count( (array) $meta_value ) ), 1 ) . ')', $meta_value ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
break;
|
||||
|
||||
case 'BETWEEN':
|
||||
|
||||
@@ -346,6 +346,7 @@ class OrdersTableQuery {
|
||||
'day' => '',
|
||||
);
|
||||
|
||||
$precision = null;
|
||||
if ( is_numeric( $date ) ) {
|
||||
$date = new \WC_DateTime( "@{$date}", new \DateTimeZone( 'UTC' ) );
|
||||
$precision = 'second';
|
||||
@@ -919,6 +920,23 @@ class OrdersTableQuery {
|
||||
}
|
||||
$orders_table = $this->tables['orders'];
|
||||
$this->count_sql = "SELECT COUNT(DISTINCT $fields) FROM $orders_table $join WHERE $where";
|
||||
|
||||
if ( ! $this->suppress_filters ) {
|
||||
/**
|
||||
* Filters the count SQL query.
|
||||
*
|
||||
* @since 8.6.0
|
||||
*
|
||||
* @param string $sql The count SQL query.
|
||||
* @param OrdersTableQuery $query The OrdersTableQuery instance (passed by reference).
|
||||
* @param array $args Query args.
|
||||
* @param string $fields Prepared fields for SELECT clause.
|
||||
* @param string $join Prepared JOIN clause.
|
||||
* @param string $where Prepared WHERE clause.
|
||||
* @param string $groupby Prepared GROUP BY clause.
|
||||
*/
|
||||
$this->count_sql = apply_filters_ref_array( 'woocommerce_orders_table_query_count_sql', array( $this->count_sql, &$this, $this->args, $fields, $join, $where, $groupby ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1131,7 +1149,7 @@ class OrdersTableQuery {
|
||||
$values = is_array( $values ) ? $values : array( $values );
|
||||
$ids = array();
|
||||
$emails = array();
|
||||
|
||||
$pieces = array();
|
||||
foreach ( $values as $value ) {
|
||||
if ( is_array( $value ) ) {
|
||||
$sql = $this->generate_customer_query( $value, 'AND' );
|
||||
|
||||
@@ -24,6 +24,13 @@ class OrdersTableSearchQuery {
|
||||
*/
|
||||
private $search_term;
|
||||
|
||||
/**
|
||||
* Limits the search to a specific field.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $search_filters;
|
||||
|
||||
/**
|
||||
* Creates the JOIN and WHERE clauses needed to execute a search of orders.
|
||||
*
|
||||
@@ -32,8 +39,31 @@ class OrdersTableSearchQuery {
|
||||
* @param OrdersTableQuery $query The order query object.
|
||||
*/
|
||||
public function __construct( OrdersTableQuery $query ) {
|
||||
$this->query = $query;
|
||||
$this->search_term = urldecode( $query->get( 's' ) );
|
||||
$this->query = $query;
|
||||
$this->search_term = urldecode( $query->get( 's' ) );
|
||||
$this->search_filters = $this->sanitize_search_filters( urldecode( $query->get( 'search_filter' ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize search filter param.
|
||||
*
|
||||
* @param string $search_filter Search filter param.
|
||||
*
|
||||
* @return array Array of search filters.
|
||||
*/
|
||||
private function sanitize_search_filters( string $search_filter ) : array {
|
||||
$available_filters = array(
|
||||
'order_id',
|
||||
'customer_email',
|
||||
'customers', // customers also searches in meta.
|
||||
'products',
|
||||
);
|
||||
|
||||
if ( 'all' === $search_filter || '' === $search_filter ) {
|
||||
return $available_filters;
|
||||
} else {
|
||||
return array_intersect( $available_filters, array( $search_filter ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,12 +92,34 @@ class OrdersTableSearchQuery {
|
||||
* @return string
|
||||
*/
|
||||
private function generate_join(): string {
|
||||
$orders_table = $this->query->get_table_name( 'orders' );
|
||||
$items_table = $this->query->get_table_name( 'items' );
|
||||
$join = array();
|
||||
|
||||
return "
|
||||
LEFT JOIN $items_table AS search_query_items ON search_query_items.order_id = $orders_table.id
|
||||
";
|
||||
foreach ( $this->search_filters as $search_filter ) {
|
||||
$join[] = $this->generate_join_for_search_filter( $search_filter );
|
||||
}
|
||||
|
||||
return implode( ' ', $join );
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate JOIN clause for a given search filter.
|
||||
* Right now we only have the products filter that actually does a JOIN, but in the future we may add more -- for example, custom order fields, payment tokens, and so on. This function makes it easier to add more filters in the future.
|
||||
*
|
||||
* If a search filter needs a JOIN, it will also need a WHERE clause.
|
||||
*
|
||||
* @param string $search_filter Name of the search filter.
|
||||
*
|
||||
* @return string JOIN clause.
|
||||
*/
|
||||
private function generate_join_for_search_filter( $search_filter ) : string {
|
||||
if ( 'products' === $search_filter ) {
|
||||
$orders_table = $this->query->get_table_name( 'orders' );
|
||||
$items_table = $this->query->get_table_name( 'items' );
|
||||
return "
|
||||
LEFT JOIN $items_table AS search_query_items ON search_query_items.order_id = $orders_table.id
|
||||
";
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,27 +130,66 @@ class OrdersTableSearchQuery {
|
||||
* @return string
|
||||
*/
|
||||
private function generate_where(): string {
|
||||
global $wpdb;
|
||||
$where = '';
|
||||
$where = array();
|
||||
$possible_order_id = (string) absint( $this->search_term );
|
||||
$order_table = $this->query->get_table_name( 'orders' );
|
||||
|
||||
// Support the passing of an order ID as the search term.
|
||||
if ( (string) $this->query->get( 's' ) === $possible_order_id ) {
|
||||
$where = "`$order_table`.id = $possible_order_id OR ";
|
||||
$where[] = "`$order_table`.id = $possible_order_id";
|
||||
}
|
||||
|
||||
$meta_sub_query = $this->generate_where_for_meta_table();
|
||||
foreach ( $this->search_filters as $search_filter ) {
|
||||
$search_where = $this->generate_where_for_search_filter( $search_filter );
|
||||
if ( ! empty( $search_where ) ) {
|
||||
$where[] = $search_where;
|
||||
}
|
||||
}
|
||||
|
||||
$where .= $wpdb->prepare(
|
||||
"
|
||||
search_query_items.order_item_name LIKE %s
|
||||
OR `$order_table`.id IN ( $meta_sub_query )
|
||||
",
|
||||
'%' . $wpdb->esc_like( $this->search_term ) . '%'
|
||||
);
|
||||
$where_statement = implode( ' OR ', $where );
|
||||
|
||||
return " ( $where ) ";
|
||||
return " ( $where_statement ) ";
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates WHERE clause for a given search filter. Right now we only have the products and customers filters that actually use WHERE, but in the future we may add more -- for example, custom order fields, payment tokens and so on. This function makes it easier to add more filters in the future.
|
||||
*
|
||||
* @param string $search_filter Name of the search filter.
|
||||
*
|
||||
* @return string WHERE clause.
|
||||
*/
|
||||
private function generate_where_for_search_filter( string $search_filter ) : string {
|
||||
global $wpdb;
|
||||
|
||||
$order_table = $this->query->get_table_name( 'orders' );
|
||||
|
||||
if ( 'customer_email' === $search_filter ) {
|
||||
return $wpdb->prepare(
|
||||
"`$order_table`.billing_email LIKE %s", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $order_table is hardcoded.
|
||||
$wpdb->esc_like( $this->search_term ) . '%'
|
||||
);
|
||||
}
|
||||
|
||||
if ( 'order_id' === $search_filter && is_numeric( $this->search_term ) ) {
|
||||
return $wpdb->prepare(
|
||||
"`$order_table`.id = %d", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $order_table is hardcoded.
|
||||
absint( $this->search_term )
|
||||
);
|
||||
}
|
||||
|
||||
if ( 'products' === $search_filter ) {
|
||||
return $wpdb->prepare(
|
||||
'search_query_items.order_item_name LIKE %s',
|
||||
'%' . $wpdb->esc_like( $this->search_term ) . '%'
|
||||
);
|
||||
}
|
||||
|
||||
if ( 'customers' === $search_filter ) {
|
||||
$meta_sub_query = $this->generate_where_for_meta_table();
|
||||
return "`$order_table`.id IN ( $meta_sub_query ) ";
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -114,6 +205,12 @@ class OrdersTableSearchQuery {
|
||||
global $wpdb;
|
||||
$meta_table = $this->query->get_table_name( 'meta' );
|
||||
$meta_fields = $this->get_meta_fields_to_be_searched();
|
||||
|
||||
if ( '' === $meta_fields ) {
|
||||
return '-1';
|
||||
}
|
||||
|
||||
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $meta_fields is already escaped before imploding, $meta_table is hardcoded.
|
||||
return $wpdb->prepare(
|
||||
"
|
||||
SELECT search_query_meta.order_id
|
||||
@@ -124,6 +221,7 @@ GROUP BY search_query_meta.order_id
|
||||
",
|
||||
'%' . $wpdb->esc_like( $this->search_term ) . '%'
|
||||
);
|
||||
// phpcs:enable
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -135,6 +233,11 @@ GROUP BY search_query_meta.order_id
|
||||
* @return string
|
||||
*/
|
||||
private function get_meta_fields_to_be_searched(): string {
|
||||
$meta_fields_to_search = array(
|
||||
'_billing_address_index',
|
||||
'_shipping_address_index',
|
||||
);
|
||||
|
||||
/**
|
||||
* Controls the order meta keys to be included in search queries.
|
||||
*
|
||||
@@ -147,10 +250,7 @@ GROUP BY search_query_meta.order_id
|
||||
*/
|
||||
$meta_keys = apply_filters(
|
||||
'woocommerce_order_table_search_query_meta_keys',
|
||||
array(
|
||||
'_billing_address_index',
|
||||
'_shipping_address_index',
|
||||
)
|
||||
$meta_fields_to_search
|
||||
);
|
||||
|
||||
$meta_keys = (array) array_map(
|
||||
|
||||
Reference in New Issue
Block a user