Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 92
0.00% covered (danger)
0.00%
0 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
Configuration
0.00% covered (danger)
0.00%
0 / 91
0.00% covered (danger)
0.00%
0 / 8
306
0.00% covered (danger)
0.00%
0 / 1
 register
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 is_woocommerce_active
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 configure_sync
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 add_woocommerce_analytics_module
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 get_jetpack_sync_config
0.00% covered (danger)
0.00%
0 / 25
0.00% covered (danger)
0.00%
0 / 1
2
 expand_full_sync_config
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
20
 add_order_stats_to_checksum
0.00% covered (danger)
0.00%
0 / 42
0.00% covered (danger)
0.00%
0 / 1
6
 add_meta_to_sync_post_meta_whitelist
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * TEMPORARY: interim port for WOOA7S-1550 — remove when the shared sync-modules composer package lands.
4 *
5 * Plain replacement for woocommerce-analytics' src/Internal/Jetpack/Sync/Configuration.php.
6 * The upstream class is wired through a PHP-DI container and RegistrableInterface; the
7 * monorepo package has no DI container, so this is a plain class invoked from
8 * {@see \Automattic\Jetpack\PremiumAnalytics\Analytics::init()}.
9 *
10 * It registers the same Jetpack Sync filters upstream's Configuration does and ensures the
11 * Sync feature so the `woocommerce_analytics` full-sync module runs. Connection bootstrap and
12 * the admin-script enqueue from the upstream class are intentionally omitted — they are handled
13 * elsewhere in the monorepo and are out of scope for this sync port.
14 *
15 * WooCommerce is a runtime (not composer) dependency, so {@see register()} guards on WooCommerce
16 * being active before hooking anything; the ported module is only ever instantiated in that case.
17 *
18 * @package automattic/jetpack-premium-analytics
19 */
20
21namespace Automattic\Jetpack\PremiumAnalytics\Sync;
22
23use Automattic\Jetpack\Config;
24use Automattic\Jetpack\Sync\Data_Settings;
25use Automattic\Jetpack\Sync\Modules as JetpackSyncModules;
26use Automattic\Jetpack\Sync\Modules\Meta as Meta_Module;
27use Automattic\Jetpack\Sync\Modules\Posts as Posts_Module;
28use Automattic\Jetpack\Sync\Modules\Term_Relationships as Term_Relationships_Module;
29use Automattic\Jetpack\Sync\Modules\Terms as Terms_Module;
30
31defined( 'ABSPATH' ) || exit;
32
33/**
34 * Registers the WooCommerce Analytics Jetpack Sync module and its supporting filters.
35 */
36class Configuration {
37
38    use Utilities;
39
40    /**
41     * List of post meta to add to Sync's post meta whitelist.
42     * Any changes to these meta will by synced to WordPress.com.
43     *
44     * @static
45     * @var array
46     */
47    private static $postmeta_to_sync = array(
48        // Products.
49        '_stock',
50        '_stock_quantity',
51        '_cogs_total_value',
52        '_global_unique_id',
53        // Bookings.
54        '_booking_parent_id',
55        '_booking_duplicate_of',
56        '_booking_product_id',
57        '_booking_resource_id',
58        '_booking_order_id',
59        '_booking_order_item_id',
60        '_booking_customer_id',
61        '_booking_start',
62        '_booking_end',
63        '_booking_all_day',
64        '_booking_persons',
65        '_booking_cost',
66        '_booking_date_cancelled',
67        '_booking_attendance_status',
68    );
69
70    /**
71     * Entry point called from Analytics::init(). Schedules the Sync hookups on plugins_loaded;
72     * the actual registration is a no-op unless WooCommerce is active (see {@see configure_sync()}).
73     *
74     * @return void
75     */
76    public static function register(): void {
77        $instance = new self();
78
79        // Defer the WooCommerce-active guard and all hookups to plugins_loaded so they run after
80        // every plugin (including WooCommerce) has loaded, regardless of plugin load order.
81        // Analytics::init() runs during plugin include, before plugins_loaded fires; the priority-1
82        // timing also lets the Jetpack Config constructed below run its own on_plugins_loaded
83        // (priority 2) handler in the same cycle.
84        if ( did_action( 'plugins_loaded' ) ) {
85            $instance->configure_sync();
86        } else {
87            add_action( 'plugins_loaded', array( $instance, 'configure_sync' ), 1 );
88        }
89    }
90
91    /**
92     * Whether WooCommerce is active in the current request.
93     *
94     * Public so the sync milestone tracker can decide which full sync gates the
95     * dashboard: the `woocommerce_analytics` module when WooCommerce is active,
96     * or Jetpack's generic initial full sync when it is not.
97     *
98     * @return bool
99     */
100    public static function is_woocommerce_active(): bool {
101        return class_exists( 'WooCommerce' ) || function_exists( 'WC' );
102    }
103
104    /**
105     * Register the Jetpack Sync filters and ensure the Sync feature, when WooCommerce is active.
106     *
107     * No-op unless WooCommerce is active, since the module relies on WooCommerce runtime symbols
108     * (WC_Order, the wc_order_stats table, OrderUtil, etc.).
109     *
110     * @return void
111     */
112    public function configure_sync(): void {
113        if ( ! self::is_woocommerce_active() ) {
114            return;
115        }
116
117        add_filter( 'jetpack_sync_modules', array( $this, 'add_woocommerce_analytics_module' ) );
118        add_filter( 'jetpack_full_sync_config', array( $this, 'expand_full_sync_config' ) );
119        add_filter( 'jetpack_sync_checksum_allowed_tables', array( $this, 'add_order_stats_to_checksum' ) );
120        add_filter( 'jetpack_sync_post_meta_whitelist', array( $this, 'add_meta_to_sync_post_meta_whitelist' ) );
121
122        ( new Config() )->ensure( 'sync', $this->get_jetpack_sync_config() );
123    }
124
125    /**
126     * Add the WooCommerce Analytics module to the list of Jetpack Sync modules.
127     *
128     * Additive: appends to whatever module list is already configured rather than replacing it.
129     *
130     * @param array $modules The current list of sync module class names.
131     * @return array
132     */
133    public function add_woocommerce_analytics_module( $modules ) {
134        if ( is_array( $modules ) && ! in_array( WooCommerce_Analytics_Module::class, $modules, true ) ) {
135            $modules[] = WooCommerce_Analytics_Module::class;
136        }
137
138        return $modules;
139    }
140
141    /**
142     * Jetpack Sync module configuration.
143     *
144     * @return array Jetpack Sync config array.
145     */
146    private function get_jetpack_sync_config(): array {
147        $jetpack_sync_modules = array_keys(
148            array_filter(
149                array(
150                    WooCommerce_Analytics_Module::class => true, // WooCommerce Analytics module.
151                    Meta_Module::class                  => true,
152                    Posts_Module::class                 => true,
153                    Terms_Module::class                 => true,
154                    Term_Relationships_Module::class    => true,
155                )
156            )
157        );
158
159        return array_merge_recursive(
160            Data_Settings::MUST_SYNC_DATA_SETTINGS,
161            array(
162                'jetpack_sync_modules'             => $jetpack_sync_modules,
163                'jetpack_sync_options_whitelist'   => array(
164                    'woocommerce_custom_orders_table_enabled', // Required for HPOS checksums.
165                    'woocommerce_excluded_report_order_statuses', // Required for generating analytics reports.
166                    'woocommerce_date_type', // Date used to determine the date range for analytics reports.
167                ),
168                'jetpack_sync_constants_whitelist' => array(
169                    'WC_ANALYTICS_VERSION',
170                ),
171            )
172        );
173    }
174
175    /**
176     * Expand full sync config with module required by WooCommerce Analytics if not already present.
177     *
178     * @param array $config The current full sync configuration.
179     * @return array The modified full sync configuration.
180     */
181    public function expand_full_sync_config( array $config ): array {
182        if ( ! $this->can_site_sync_orders() ) {
183            return $config;
184        }
185
186        // Let's ensure Terms and Term_Relationships will always get synced before Posts during Full Sync.
187        if ( isset( $config['posts'] ) ) {
188            unset( $config['posts'] );
189            $config += array( 'posts' => 1 );
190        }
191
192        if ( ! isset( $config['woocommerce_analytics'] ) ) {
193            $config = array( 'woocommerce_analytics' => 1 ) + $config;
194        }
195
196        return $config;
197    }
198
199    /**
200     * Adds the order stats table to the checksum allowed tables.
201     *
202     * @param array $tables The current checksum allowed tables.
203     * @return array The modified checksum allowed tables.
204     */
205    public function add_order_stats_to_checksum( array $tables ): array {
206        if ( ! $this->can_site_sync_orders() ) {
207            return $tables;
208        }
209
210        global $wpdb;
211        $order_stats_checksum_table = array(
212            'wc_order_stats'          => array(
213                'table'                     => "{$wpdb->prefix}wc_order_stats",
214                'range_field'               => 'order_id',
215                'key_fields'                => array( 'order_id' ),
216                'checksum_fields'           => array( 'date_paid', 'date_completed', 'total_sales' ),
217                'checksum_text_fields'      => array( 'status' ),
218                'is_table_enabled_callback' => function () {
219                    return false !== JetpackSyncModules::get_module( 'woocommerce_analytics' );
220                },
221            ),
222            'wc_order_product_lookup' => array(
223                'table'                     => "{$wpdb->prefix}wc_order_product_lookup",
224                'range_field'               => 'order_id',
225                'key_fields'                => array( 'order_id', 'order_item_id' ),
226                'checksum_fields'           => array( 'product_id', 'variation_id', 'product_qty', 'product_net_revenue', 'date_created' ),
227                'is_table_enabled_callback' => function () {
228                    return false !== JetpackSyncModules::get_module( 'woocommerce_analytics' );
229                },
230            ),
231            'wc_order_coupon_lookup'  => array(
232                'table'                     => "{$wpdb->prefix}wc_order_coupon_lookup",
233                'range_field'               => 'order_id',
234                'key_fields'                => array( 'order_id', 'coupon_id' ),
235                'checksum_fields'           => array( 'discount_amount', 'date_created' ),
236                'is_table_enabled_callback' => function () {
237                    return false !== JetpackSyncModules::get_module( 'woocommerce_analytics' );
238                },
239            ),
240            'wc_order_tax_lookup'     => array(
241                'table'                     => "{$wpdb->prefix}wc_order_tax_lookup",
242                'range_field'               => 'order_id',
243                'key_fields'                => array( 'order_id', 'tax_rate_id' ),
244                'checksum_fields'           => array( 'order_tax', 'total_tax', 'shipping_tax', 'date_created' ),
245                'is_table_enabled_callback' => function () {
246                    return false !== JetpackSyncModules::get_module( 'woocommerce_analytics' );
247                },
248            ),
249        );
250        return array_merge( $tables, $order_stats_checksum_table );
251    }
252
253    /**
254     * Add WC Analytics post meta to Sync's post meta whitelist.
255     * Any changes to these meta will by synced to WordPress.com.
256     *
257     * @param array $whitelist Existing post meta whitelist.
258     * @return array Updated post meta whitelist.
259     */
260    public function add_meta_to_sync_post_meta_whitelist( array $whitelist ): array {
261        return array_merge( self::$postmeta_to_sync, $whitelist );
262    }
263}