← All changes
|
jetpack_vendor/automattic/jetpack-sync/src/modules/class-woocommerce-analytics.php
+78
-9
16.2
→
16.3-beta
View file →
| @@ -6,9 +6,9 @@ | ||
| 6 | 6 | * order product/coupon/tax lookup tables) to WordPress.com. |
| 7 | 7 | * |
| 8 | 8 | * Sync module name: `woocommerce_analytics`. The name, the action names, and the |
| 9 | 9 | * payload shapes are consumed by the WPCOM receiving side and by consumer packages |
| 10 | - * (Premium Analytics, WooCommerce AI); treat them as a public contract. | |
| 10 | + * (Premium Analytics, the standalone WooCommerce Analytics plugin); treat them as a public contract. | |
| 11 | 11 | * |
| 12 | 12 | * This module is NOT registered by default. Consumers own its registration, |
| 13 | 13 | * WooCommerce runtime guard, full-sync policy, and any additional Sync data |
| 14 | 14 | * configuration. The module provides the minimum option and post meta requirements. |
| @@ -65,8 +65,18 @@ | ||
| 65 | 65 | '_global_unique_id', |
| 66 | 66 | ); |
| 67 | 67 | |
| 68 | 68 | /** |
| 69 | + * WooCommerce Analytics' order class for each plain order class, which adds the report methods used here. | |
| 70 | + * | |
| 71 | + * @var string[] | |
| 72 | + */ | |
| 73 | + private static $analytics_order_classes = array( | |
| 74 | + 'WC_Order' => 'Automattic\WooCommerce\Admin\Overrides\Order', | |
| 75 | + 'WC_Order_Refund' => 'Automattic\WooCommerce\Admin\Overrides\OrderRefund', | |
| 76 | + ); | |
| 77 | + | |
| 78 | + /** | |
| 69 | 79 | * Constructor. |
| 70 | 80 | */ |
| 71 | 81 | public function __construct() { |
| 72 | 82 | add_filter( 'jetpack_sync_options_whitelist', array( $this, 'add_woocommerce_analytics_options_whitelist' ), 10 ); |
| @@ -210,9 +220,9 @@ | ||
| 210 | 220 | if ( ! in_array( $object_type, $this->get_supported_object_types(), true ) ) { |
| 211 | 221 | return array(); |
| 212 | 222 | } |
| 213 | 223 | |
| 214 | - $orders = wc_get_orders( | |
| 224 | + $orders = self::get_analytics_orders( | |
| 215 | 225 | array( |
| 216 | 226 | 'post__in' => $ids, |
| 217 | 227 | 'post_status' => WooCommerce_HPOS_Orders::get_all_possible_order_status_keys(), |
| 218 | 228 | 'limit' => -1, |
| @@ -512,14 +522,23 @@ | ||
| 512 | 522 | * @param mixed $order The order ID or the WC_Order object. |
| 513 | 523 | * @return array The reports data. |
| 514 | 524 | */ |
| 515 | 525 | protected function build_woocommerce_analytics_reports_data( $order ) { |
| 526 | + // Reload once here, or each getter below would read the order again. | |
| 527 | + $report_order = $order; | |
| 528 | + if ( $order instanceof WC_Abstract_Order ) { | |
| 529 | + $analytics_order = self::get_analytics_order( $order ); | |
| 530 | + if ( $analytics_order ) { | |
| 531 | + $report_order = $analytics_order; | |
| 532 | + } | |
| 533 | + } | |
| 534 | + | |
| 516 | 535 | $data_types = array( |
| 517 | - 'order_stats' => $this->get_order_stats_data( $order ), | |
| 518 | - 'order_attribution_data' => $this->get_order_attribution_data( $order ), | |
| 519 | - 'order_product_data' => $this->get_order_product_data( $order ), | |
| 520 | - 'order_coupon_data' => $this->get_order_coupon_data( $order ), | |
| 521 | - 'order_tax_data' => $this->get_order_tax_data( $order ), | |
| 536 | + 'order_stats' => $this->get_order_stats_data( $report_order ), | |
| 537 | + 'order_attribution_data' => $this->get_order_attribution_data( $report_order ), | |
| 538 | + 'order_product_data' => $this->get_order_product_data( $report_order ), | |
| 539 | + 'order_coupon_data' => $this->get_order_coupon_data( $report_order ), | |
| 540 | + 'order_tax_data' => $this->get_order_tax_data( $report_order ), | |
| 522 | 541 | ); |
| 523 | 542 | |
| 524 | 543 | $reports_data = array_filter( $data_types ); |
| 525 | 544 | |
| @@ -651,9 +670,10 @@ | ||
| 651 | 670 | } else { |
| 652 | 671 | return false; |
| 653 | 672 | } |
| 654 | 673 | |
| 655 | - // If the order does not exit, check if the stats item is present in the wc_order_stats table. | |
| 674 | + // If the order does not exist or cannot have report methods, read its wc_order_stats row instead. | |
| 675 | + $order = self::get_analytics_order( $order ); | |
| 656 | 676 | if ( ! $order ) { |
| 657 | 677 | $order_stats_data_from_db = $this->get_order_stats_data_from_db( $order_id ); |
| 658 | 678 | return $order_stats_data_from_db; |
| 659 | 679 | } |
| @@ -815,8 +835,56 @@ | ||
| 815 | 835 | return $wpdb->get_results( $query, ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching |
| 816 | 836 | } |
| 817 | 837 | |
| 818 | 838 | /** |
| 839 | + * Get the order as WooCommerce Analytics' order class, which adds the report methods used here. | |
| 840 | + * | |
| 841 | + * WooCommerce swaps that class in only while Analytics is enabled, and an object cache can outlive the switch. | |
| 842 | + * | |
| 843 | + * @param WC_Abstract_Order|false $order The order object. | |
| 844 | + * @return WC_Abstract_Order|false The order with report methods, or false if it cannot have them. | |
| 845 | + */ | |
| 846 | + private static function get_analytics_order( $order ) { | |
| 847 | + if ( ! $order || method_exists( $order, 'get_report_customer_id' ) ) { | |
| 848 | + return $order; | |
| 849 | + } | |
| 850 | + | |
| 851 | + // Mirrors the exact-class check in WooCommerce's order_class_name filters, which leave subclasses alone. | |
| 852 | + $analytics_class = self::$analytics_order_classes[ get_class( $order ) ] ?? null; | |
| 853 | + if ( null === $analytics_class || ! class_exists( $analytics_class ) ) { | |
| 854 | + return false; | |
| 855 | + } | |
| 856 | + | |
| 857 | + return new $analytics_class( $order->get_id() ); | |
| 858 | + } | |
| 859 | + | |
| 860 | + /** | |
| 861 | + * Query orders, loading them as WooCommerce Analytics' order classes even while Analytics is disabled. | |
| 862 | + * | |
| 863 | + * @param array $query_args Arguments for wc_get_orders(). | |
| 864 | + * @return WC_Order[]|\stdClass What wc_get_orders() returns. | |
| 865 | + */ | |
| 866 | + private static function get_analytics_orders( $query_args ) { | |
| 867 | + $added_callbacks = array(); | |
| 868 | + foreach ( self::$analytics_order_classes as $analytics_class ) { | |
| 869 | + $callback = array( $analytics_class, 'order_class_name' ); | |
| 870 | + // Only add, and later remove, what WooCommerce has not, or its own filter goes with it. | |
| 871 | + if ( class_exists( $analytics_class ) && false === has_filter( 'woocommerce_order_class', $callback ) ) { | |
| 872 | + add_filter( 'woocommerce_order_class', $callback, 10, 3 ); | |
| 873 | + $added_callbacks[] = $callback; | |
| 874 | + } | |
| 875 | + } | |
| 876 | + | |
| 877 | + try { | |
| 878 | + return wc_get_orders( $query_args ); | |
| 879 | + } finally { | |
| 880 | + foreach ( $added_callbacks as $callback ) { | |
| 881 | + remove_filter( 'woocommerce_order_class', $callback, 10 ); | |
| 882 | + } | |
| 883 | + } | |
| 884 | + } | |
| 885 | + | |
| 886 | + /** | |
| 819 | 887 | * Check if the COGS feature is enabled. |
| 820 | 888 | * |
| 821 | 889 | * @return bool True if the COGS feature is enabled, false otherwise. |
| 822 | 890 | */ |
| @@ -839,9 +907,10 @@ | ||
| 839 | 907 | } else { |
| 840 | 908 | return false; |
| 841 | 909 | } |
| 842 | 910 | |
| 843 | - // If the order does not exist, check if product lookup data exists in the database. | |
| 911 | + // If the order does not exist or cannot have report methods, read its lookup rows instead. | |
| 912 | + $order = self::get_analytics_order( $order ); | |
| 844 | 913 | if ( ! $order ) { |
| 845 | 914 | return $this->get_order_product_data_from_db( $order_id ); |
| 846 | 915 | } |
| 847 | 916 | |