Make dashboard status widget compatible with HPOS (#44734)

* Remove unnecessary import

* Make top seller query compatible with HPOS

* Add compat `get_count_for_type()` method

* Make dashboard widget compatible with HPOS

* Add changelog

* PHPCS fixes

* Add unit test

* PHPCS — we meet again.
This commit is contained in:
Jorge A. Torres 2024-02-28 03:10:33 -03:00 committed by GitHub
parent b95c15fc2f
commit c83b030834
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 131 additions and 15 deletions

View File

@ -0,0 +1,4 @@
Significance: patch
Type: fix
Fixes order counts in WooCommerce Status dashboard widget.

View File

@ -8,6 +8,7 @@
use Automattic\Jetpack\Constants;
use Automattic\WooCommerce\Admin\Features\Features;
use Automattic\WooCommerce\Utilities\OrderUtil;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
@ -81,23 +82,49 @@ if ( ! class_exists( 'WC_Admin_Dashboard', false ) ) :
private function get_top_seller() {
global $wpdb;
$query = array();
$query['fields'] = "SELECT SUM( order_item_meta.meta_value ) as qty, order_item_meta_2.meta_value as product_id
FROM {$wpdb->posts} as posts";
$query['join'] = "INNER JOIN {$wpdb->prefix}woocommerce_order_items AS order_items ON posts.ID = order_id ";
$query['join'] .= "INNER JOIN {$wpdb->prefix}woocommerce_order_itemmeta AS order_item_meta ON order_items.order_item_id = order_item_meta.order_item_id ";
$query['join'] .= "INNER JOIN {$wpdb->prefix}woocommerce_order_itemmeta AS order_item_meta_2 ON order_items.order_item_id = order_item_meta_2.order_item_id ";
$query['where'] = "WHERE posts.post_type IN ( '" . implode( "','", wc_get_order_types( 'order-count' ) ) . "' ) ";
$query['where'] .= "AND posts.post_status IN ( 'wc-" . implode( "','wc-", apply_filters( 'woocommerce_reports_order_statuses', array( 'completed', 'processing', 'on-hold' ) ) ) . "' ) ";
$hpos_enabled = OrderUtil::custom_orders_table_usage_is_enabled();
$orders_table = OrderUtil::get_table_for_orders();
$orders_column_id = $hpos_enabled ? 'id' : 'ID';
$orders_column_type = $hpos_enabled ? 'type' : 'post_type';
$orders_column_status = $hpos_enabled ? 'status' : 'post_status';
$orders_column_date = $hpos_enabled ? 'date_created_gmt' : 'post_date_gmt';
$query = array();
$query['fields'] = "SELECT SUM( order_item_meta.meta_value ) as qty, order_item_meta_2.meta_value as product_id FROM {$orders_table} AS orders";
$query['join'] = "INNER JOIN {$wpdb->prefix}woocommerce_order_items AS order_items ON orders.{$orders_column_id} = order_id ";
$query['join'] .= "INNER JOIN {$wpdb->prefix}woocommerce_order_itemmeta AS order_item_meta ON order_items.order_item_id = order_item_meta.order_item_id ";
$query['join'] .= "INNER JOIN {$wpdb->prefix}woocommerce_order_itemmeta AS order_item_meta_2 ON order_items.order_item_id = order_item_meta_2.order_item_id ";
$query['where'] = "WHERE orders.{$orders_column_type} IN ( '" . implode( "','", wc_get_order_types( 'order-count' ) ) . "' ) ";
/**
* Allows modifying the order statuses used in the top seller query inside the dashboard status widget.
*
* @since 2.2.0
*
* @param string[] $order_statuses Order statuses.
*/
$order_statuses = apply_filters( 'woocommerce_reports_order_statuses', array( 'completed', 'processing', 'on-hold' ) );
$query['where'] .= "AND orders.{$orders_column_status} IN ( 'wc-" . implode( "','wc-", $order_statuses ) . "' ) ";
$query['where'] .= "AND order_item_meta.meta_key = '_qty' ";
$query['where'] .= "AND order_item_meta_2.meta_key = '_product_id' ";
$query['where'] .= "AND posts.post_date >= '" . gmdate( 'Y-m-01', current_time( 'timestamp' ) ) . "' ";
$query['where'] .= "AND posts.post_date <= '" . gmdate( 'Y-m-d H:i:s', current_time( 'timestamp' ) ) . "' ";
$query['where'] .= "AND orders.{$orders_column_date} >= '" . gmdate( 'Y-m-01', current_time( 'timestamp' ) ) . "' "; // phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp.Requested
$query['where'] .= "AND orders.{$orders_column_date} <= '" . gmdate( 'Y-m-d H:i:s', current_time( 'timestamp' ) ) . "' "; // phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp.Requested
$query['groupby'] = 'GROUP BY product_id';
$query['orderby'] = 'ORDER BY qty DESC';
$query['limits'] = 'LIMIT 1';
return $wpdb->get_row( implode( ' ', apply_filters( 'woocommerce_dashboard_status_widget_top_seller_query', $query ) ) ); //phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
/**
* Allows modification of the query to determine the top seller product in the dashboard status widget.
*
* @since 2.2.0
*
* @param array $query SQL query parts.
*/
$query = apply_filters( 'woocommerce_dashboard_status_widget_top_seller_query', $query );
$sql = implode( ' ', $query );
return $wpdb->get_row( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
}
/**
@ -202,9 +229,9 @@ if ( ! class_exists( 'WC_Admin_Dashboard', false ) ) :
$processing_count = 0;
foreach ( wc_get_order_types( 'order-count' ) as $type ) {
$counts = (array) wp_count_posts( $type );
$on_hold_count += isset( $counts['wc-on-hold'] ) ? $counts['wc-on-hold'] : 0;
$processing_count += isset( $counts['wc-processing'] ) ? $counts['wc-processing'] : 0;
$counts = OrderUtil::get_count_for_type( $type );
$on_hold_count += $counts['wc-on-hold'];
$processing_count += $counts['wc-processing'];
}
?>
<li class="processing-orders">

View File

@ -8,7 +8,6 @@ namespace Automattic\WooCommerce\Utilities;
use Automattic\WooCommerce\Caches\OrderCacheController;
use Automattic\WooCommerce\Internal\Admin\Orders\PageController;
use Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController;
use Automattic\WooCommerce\Internal\Features\FeaturesController;
use Automattic\WooCommerce\Internal\Utilities\COTMigrationUtil;
use WC_Order;
use WP_Post;
@ -185,4 +184,48 @@ final class OrderUtil {
public static function get_table_for_order_meta() {
return wc_get_container()->get( COTMigrationUtil::class )->get_table_for_order_meta();
}
/**
* Counts number of orders of a given type.
*
* @since 8.7.0
*
* @param string $order_type Order type.
* @return array<string,int> Array of order counts indexed by order type.
*/
public static function get_count_for_type( $order_type ) {
global $wpdb;
$cache_key = 'order-count-' . $order_type;
$count_per_status = wp_cache_get( $cache_key, 'orders' );
if ( false === $count_per_status ) {
if ( self::custom_orders_table_usage_is_enabled() ) {
// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
$results = $wpdb->get_results(
$wpdb->prepare(
'SELECT `status`, COUNT(*) AS `count` FROM ' . self::get_table_for_orders() . ' WHERE `type` = %s GROUP BY `status`',
$order_type
),
ARRAY_A
);
// phpcs:enable
$count_per_status = array_map( 'absint', array_column( $results, 'count', 'status' ) );
} else {
$count_per_status = (array) wp_count_posts( $order_type );
}
// Make sure all order statuses are included just in case.
$count_per_status = array_merge(
array_fill_keys( array_keys( wc_get_order_statuses() ), 0 ),
$count_per_status
);
wp_cache_set( $cache_key, $count_per_status, 'orders' );
}
return $count_per_status;
}
}

View File

@ -3387,4 +3387,46 @@ class OrdersTableDataStoreTests extends HposTestCase {
$this->assertEquals( 'encountered an order meta value of type __PHP_Incomplete_Class during `delete_meta` in order with ID ' . $order->get_id() . ': "\'O:11:"geoiprecord":14:{s:12:"country_code";s:2:"BE";s:13:"country_code3";s:3:"BEL";s:12:"country_name";s:7:"Belgium";s:6:"region";s:3:"BRU";s:4:"city";s:8:"Brussels";s:11:"postal_code";s:4:"1000";s:8:"latitude";d:50.8333;s:9:"longitude";d:4.3333;s:9:"area_code";N;s:8:"dma_code";N;s:10:"metro_code";N;s:14:"continent_code";s:2:"EU";s:11:"region_name";s:16:"Brussels Capital";s:8:"timezone";s:15:"Europe/Brussels";}\'"', end( $fake_logger->warnings )['message'] );
}
/**
* Tests that OrderUtil::get_count_for_type() counts orders correctly.
*
* @testWith ["hpos"]
* ["posts"]
*
* @param string $datastore_to_use Which datastore to use. Either 'hpos' or 'posts'.
*/
public function test_order_util_get_count_for_type( $datastore_to_use ) {
$this->disable_cot_sync();
if ( 'hpos' === $datastore_to_use ) {
$this->toggle_cot_authoritative( true );
} else {
$this->toggle_cot_authoritative( false );
}
// Create a few orders in various states.
$order_statuses = array_keys( wc_get_order_statuses() );
$expected_counts = array_combine( $order_statuses, array_fill( 0, count( $order_statuses ), 0 ) );
foreach ( $order_statuses as $i => $status ) {
foreach ( range( 0, $i ) as $_ ) {
$expected_counts[ $status ] = $i + 1;
$order = WC_Helper_Order::create_order();
$order->set_status( $status );
$order->save();
}
}
$real_counts = OrderUtil::get_count_for_type( 'shop_order' );
foreach ( $expected_counts as $status => $count ) {
$this->assertArrayHasKey( $status, $real_counts );
$this->assertEquals( $count, $real_counts[ $status ] );
}
$other_counts = OrderUtil::get_count_for_type( 'shop_something' );
$this->assertEquals( 0, array_pop( $other_counts ) );
}
}