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