Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 116
0.00% covered (danger)
0.00%
0 / 27
CRAP
0.00% covered (danger)
0.00%
0 / 1
WooCommerce
0.00% covered (danger)
0.00%
0 / 114
0.00% covered (danger)
0.00%
0 / 27
1980
0.00% covered (danger)
0.00%
0 / 1
 table_name
n/a
0 / 0
n/a
0 / 0
1
 table
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 id_field
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 full_sync_action_name
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 __construct
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
2
 name
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 init_listeners
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 1
2
 init_full_sync_listeners
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_full_sync_actions
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 init_before_send
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 filter_order_item
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 filter_meta
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 is_whitelisted_order_item_meta
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 action_woocommerce_remove_order_items
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 expand_order_item_ids
n/a
0 / 0
n/a
0 / 0
1
 build_order_item
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 enqueue_full_sync_actions
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 estimate_full_sync_actions
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 get_where_sql
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 add_woocommerce_options_whitelist
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 add_woocommerce_constants_whitelist
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 add_woocommerce_post_meta_whitelist
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 add_woocommerce_comment_meta_whitelist
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 add_review_comment_types
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 filter_action_scheduler_comments
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 get_objects_by_id
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 get_order_item_by_ids
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
30
 build_full_sync_action_array
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 get_next_chunk
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2/**
3 * WooCommerce sync module.
4 *
5 * @package automattic/jetpack-sync
6 */
7
8namespace Automattic\Jetpack\Sync\Modules;
9
10use WC_Order;
11use WP_Error;
12
13if ( ! defined( 'ABSPATH' ) ) {
14    exit( 0 );
15}
16
17/**
18 * Class to handle sync for WooCommerce.
19 */
20class WooCommerce extends Module {
21    /**
22     * Whitelist for order item meta we are interested to sync.
23     *
24     * @access private
25     *
26     * @var array
27     */
28    public static $order_item_meta_whitelist = array(
29        // See https://github.com/woocommerce/woocommerce/blob/master/includes/data-stores/class-wc-order-item-product-store.php#L20 .
30        '_product_id',
31        '_variation_id',
32        '_qty',
33        // Tax ones also included in below class
34        // See https://github.com/woocommerce/woocommerce/blob/master/includes/data-stores/class-wc-order-item-fee-data-store.php#L20 .
35        '_tax_class',
36        '_tax_status',
37        '_line_subtotal',
38        '_line_subtotal_tax',
39        '_line_total',
40        '_line_tax',
41        '_line_tax_data',
42        // See https://github.com/woocommerce/woocommerce/blob/master/includes/data-stores/class-wc-order-item-shipping-data-store.php#L20 .
43        'method_id',
44        'cost',
45        'total_tax',
46        'taxes',
47        // See https://github.com/woocommerce/woocommerce/blob/master/includes/data-stores/class-wc-order-item-tax-data-store.php#L20 .
48        'rate_id',
49        'label',
50        'compound',
51        'tax_amount',
52        'shipping_tax_amount',
53        // See https://github.com/woocommerce/woocommerce/blob/master/includes/data-stores/class-wc-order-item-coupon-data-store.php .
54        'discount_amount',
55        'discount_amount_tax',
56    );
57
58    /**
59     * Name of the order item database table.
60     *
61     * @access private
62     *
63     * @var string
64     */
65    private $order_item_table_name;
66
67    /**
68     * The table name.
69     *
70     * @access public
71     *
72     * @return string
73     * @deprecated since 3.11.0 Use table() instead.
74     */
75    public function table_name() {
76        _deprecated_function( __METHOD__, '3.11.0', 'Automattic\\Jetpack\\Sync\\WooCommerce->table' );
77        return $this->order_item_table_name;
78    }
79
80    /**
81     * The table in the database with the prefix.
82     *
83     * @access public
84     *
85     * @return string|bool
86     */
87    public function table() {
88        global $wpdb;
89        return $wpdb->prefix . 'woocommerce_order_items';
90    }
91
92    /**
93     * The id field in the database.
94     *
95     * @access public
96     *
97     * @return string
98     */
99    public function id_field() {
100        return 'order_item_id';
101    }
102
103    /**
104     * The full sync action name for this module.
105     *
106     * @access public
107     *
108     * @return string
109     */
110    public function full_sync_action_name() {
111        return 'jetpack_full_sync_woocommerce_order_items';
112    }
113
114    /**
115     * Constructor.
116     *
117     * @global $wpdb
118     *
119     * @todo Should we refactor this to use $this->set_defaults() instead?
120     */
121    public function __construct() {
122        global $wpdb;
123        $this->order_item_table_name = $wpdb->prefix . 'woocommerce_order_items';
124
125        // Options, constants and post meta whitelists.
126        add_filter( 'jetpack_sync_options_whitelist', array( $this, 'add_woocommerce_options_whitelist' ), 10 );
127        add_filter( 'jetpack_sync_constants_whitelist', array( $this, 'add_woocommerce_constants_whitelist' ), 10 );
128        add_filter( 'jetpack_sync_post_meta_whitelist', array( $this, 'add_woocommerce_post_meta_whitelist' ), 10 );
129        add_filter( 'jetpack_sync_comment_meta_whitelist', array( $this, 'add_woocommerce_comment_meta_whitelist' ), 10 );
130
131        add_filter( 'jetpack_sync_before_enqueue_woocommerce_new_order_item', array( $this, 'filter_order_item' ) );
132        add_filter( 'jetpack_sync_whitelisted_comment_types', array( $this, 'add_review_comment_types' ) );
133
134        // Blacklist Action Scheduler comment types.
135        add_filter( 'jetpack_sync_prevent_sending_comment_data', array( $this, 'filter_action_scheduler_comments' ), 10, 2 );
136
137        // Preprocess action to be sent by Jetpack sync.
138        add_action( 'woocommerce_remove_order_items', array( $this, 'action_woocommerce_remove_order_items' ), 10, 2 );
139    }
140
141    /**
142     * Sync module name.
143     *
144     * @access public
145     *
146     * @return string
147     */
148    public function name() {
149        return 'woocommerce';
150    }
151
152    /**
153     * Initialize WooCommerce action listeners.
154     *
155     * @access public
156     *
157     * @param callable $callable Action handler callable.
158     */
159    public function init_listeners( $callable ) {
160        // Attributes.
161        add_action( 'woocommerce_attribute_added', $callable, 10, 2 );
162        add_action( 'woocommerce_attribute_updated', $callable, 10, 3 );
163        add_action( 'woocommerce_attribute_deleted', $callable, 10, 3 );
164
165        // Orders.
166        add_action( 'woocommerce_new_order', $callable, 10, 1 );
167        add_action( 'woocommerce_order_status_changed', $callable, 10, 3 );
168        add_action( 'woocommerce_payment_complete', $callable, 10, 1 );
169
170        // Order items.
171        add_action( 'woocommerce_new_order_item', $callable, 10, 4 );
172        add_action( 'woocommerce_delete_order_item', $callable, 10, 1 );
173        add_action( 'woocommerce_remove_order_item_ids', $callable, 10, 1 );
174        $this->init_listeners_for_meta_type( 'order_item', $callable );
175        $this->init_meta_whitelist_handler( 'order_item', array( $this, 'filter_meta' ) );
176
177        // Payment tokens.
178        add_action( 'woocommerce_new_payment_token', $callable, 10, 1 );
179        add_action( 'woocommerce_payment_token_deleted', $callable, 10, 2 );
180        add_action( 'woocommerce_payment_token_updated', $callable, 10, 1 );
181        $this->init_listeners_for_meta_type( 'payment_token', $callable );
182
183        // Product downloads.
184        add_action( 'woocommerce_downloadable_product_download_log_insert', $callable, 10, 1 );
185        add_action( 'woocommerce_grant_product_download_access', $callable, 10, 1 );
186
187        // Tax rates.
188        // These are ignored on WP.com: tax items are derived from order data via wc_order_tax_lookup, which isn’t present there.
189        add_action( 'woocommerce_tax_rate_added', $callable, 10, 2 );
190        add_action( 'woocommerce_tax_rate_updated', $callable, 10, 2 );
191        add_action( 'woocommerce_tax_rate_deleted', $callable, 10, 1 );
192
193        // Webhooks.
194        add_action( 'woocommerce_new_webhook', $callable, 10, 1 );
195        add_action( 'woocommerce_webhook_deleted', $callable, 10, 2 );
196        add_action( 'woocommerce_webhook_updated', $callable, 10, 1 );
197    }
198
199    /**
200     * Initialize WooCommerce action listeners for full sync.
201     *
202     * @access public
203     *
204     * @param callable $callable Action handler callable.
205     */
206    public function init_full_sync_listeners( $callable ) {
207        add_action( 'jetpack_full_sync_woocommerce_order_items', $callable ); // Also sends post meta.
208    }
209
210    /**
211     * Retrieve the actions that will be sent for this module during a full sync.
212     *
213     * @access public
214     *
215     * @return array Full sync actions of this module.
216     */
217    public function get_full_sync_actions() {
218        return array( 'jetpack_full_sync_woocommerce_order_items' );
219    }
220
221    /**
222     * Initialize the module in the sender.
223     *
224     * @access public
225     */
226    public function init_before_send() {
227        // Full sync.
228        add_filter( 'jetpack_sync_before_send_jetpack_full_sync_woocommerce_order_items', array( $this, 'build_full_sync_action_array' ) );
229    }
230
231    /**
232     * Expand the order items properly.
233     *
234     * @access public
235     *
236     * @param array $args The hook arguments.
237     * @return array $args The hook arguments.
238     */
239    public function filter_order_item( $args ) {
240        // Make sure we always have all the data - prior to WooCommerce 3.0 we only have the user supplied data in the second argument and not the full details.
241        $args[1] = $this->build_order_item( $args[0] );
242        return $args;
243    }
244
245    /**
246     * Handler for filtering out non-whitelisted order item meta.
247     *
248     * @since 4.22.3
249     *
250     * @param array $args Hook arguments.
251     * @return array|false False if not whitelisted, the original hook args otherwise.
252     */
253    public function filter_meta( $args ) {
254        if (
255            ! empty( $args[2] ) && $this->is_whitelisted_order_item_meta( $args[2] )
256        ) {
257            return $args;
258        }
259
260        return false;
261    }
262
263    /**
264     * Whether an order item meta key is whitelisted for sync.
265     *
266     * @access public
267     *
268     * @since 4.22.3
269     *
270     * @param string $meta_key Order item meta key.
271     * @return bool True if whitelisted.
272     */
273    public function is_whitelisted_order_item_meta( $meta_key ) {
274        return is_string( $meta_key ) && in_array( $meta_key, self::$order_item_meta_whitelist, true );
275    }
276
277    /**
278     * Retrieve the order item ids to be removed and send them as one action
279     *
280     * @param WC_Order $order The order argument.
281     * @param string   $type Order item type.
282     */
283    public function action_woocommerce_remove_order_items( WC_Order $order, $type ) {
284        if ( $type ) {
285            $order_items = $order->get_items( $type );
286        } else {
287            $order_items = $order->get_items();
288        }
289        $order_item_ids = array_keys( $order_items );
290
291        if ( $order_item_ids ) {
292            do_action( 'woocommerce_remove_order_item_ids', $order_item_ids );
293        }
294    }
295
296    /**
297     * Expand order item IDs to order items and their meta.
298     *
299     * @access public
300     *
301     * @todo Refactor table name to use a $wpdb->prepare placeholder.
302     *
303     * @param array $args The hook arguments.
304     * @return array $args Expanded order items with meta.
305     * @deprecated since 4.7.0
306     */
307    public function expand_order_item_ids( $args ) {
308        _deprecated_function( __METHOD__, '4.7.0' );
309        $order_item_ids = $args[0];
310
311        global $wpdb;
312
313        $order_item_ids_sql = implode( ', ', array_map( 'intval', $order_item_ids ) );
314
315        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
316        $order_items = $wpdb->get_results(
317            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
318            "SELECT * FROM $this->order_item_table_name WHERE order_item_id IN ( $order_item_ids_sql )"
319        );
320
321        return array(
322            $order_items,
323            $this->get_metadata( $order_item_ids, 'order_item', static::$order_item_meta_whitelist ),
324        );
325    }
326    /**
327     * Extract the full order item from the database by its ID.
328     *
329     * @access public
330     *
331     * @param int $order_item_id Order item ID.
332     * @return object Order item.
333     */
334    public function build_order_item( $order_item_id ) {
335        global $wpdb;
336        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Direct database access is intentional; caching is not required for this query.
337        return $wpdb->get_row( $wpdb->prepare( 'SELECT * FROM %i WHERE order_item_id = %d', $this->order_item_table_name, $order_item_id ) );
338    }
339
340    /**
341     * Enqueue the WooCommerce actions for full sync.
342     *
343     * @access public
344     *
345     * @param array   $config               Full sync configuration for this sync module.
346     * @param int     $max_items_to_enqueue Maximum number of items to enqueue.
347     * @param boolean $state                True if full sync has finished enqueueing this module, false otherwise.
348     * @return array Number of actions enqueued, and next module state.
349     */
350    public function enqueue_full_sync_actions( $config, $max_items_to_enqueue, $state ) {
351        return $this->enqueue_all_ids_as_action( 'jetpack_full_sync_woocommerce_order_items', $this->order_item_table_name, 'order_item_id', $this->get_where_sql( $config ), $max_items_to_enqueue, $state );
352    }
353
354    /**
355     * Retrieve an estimated number of actions that will be enqueued.
356     *
357     * @access public
358     *
359     * @todo Refactor the SQL query to use $wpdb->prepare().
360     *
361     * @param array $config Full sync configuration for this sync module.
362     * @return int Number of items yet to be enqueued.
363     */
364    public function estimate_full_sync_actions( $config ) {
365        global $wpdb;
366
367        $query = "SELECT count(*) FROM $this->order_item_table_name WHERE " . $this->get_where_sql( $config );
368        // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
369        $count = (int) $wpdb->get_var( $query );
370
371        return (int) ceil( $count / self::ARRAY_CHUNK_SIZE );
372    }
373
374    /**
375     * Retrieve the WHERE SQL clause based on the module config.
376     *
377     * @access private
378     *
379     * @param array $config Full sync configuration for this sync module.
380     * @return string WHERE SQL clause.
381     */
382    public function get_where_sql( $config ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
383        return '1=1';
384    }
385
386    /**
387     * Add WooCommerce options to the options whitelist.
388     *
389     * @param array $list Existing options whitelist.
390     * @return array Updated options whitelist.
391     */
392    public function add_woocommerce_options_whitelist( $list ) {
393        return array_merge( $list, self::$wc_options_whitelist );
394    }
395
396    /**
397     * Add WooCommerce constants to the constants whitelist.
398     *
399     * @param array $list Existing constants whitelist.
400     * @return array Updated constants whitelist.
401     */
402    public function add_woocommerce_constants_whitelist( $list ) {
403        return array_merge( $list, self::$wc_constants_whitelist );
404    }
405
406    /**
407     * Add WooCommerce post meta to the post meta whitelist.
408     *
409     * @param array $list Existing post meta whitelist.
410     * @return array Updated post meta whitelist.
411     */
412    public function add_woocommerce_post_meta_whitelist( $list ) {
413        return array_merge( $list, self::$wc_post_meta_whitelist );
414    }
415
416    /**
417     * Add WooCommerce comment meta to the comment meta whitelist.
418     *
419     * @param array $list Existing comment meta whitelist.
420     * @return array Updated comment meta whitelist.
421     */
422    public function add_woocommerce_comment_meta_whitelist( $list ) {
423        return array_merge( $list, self::$wc_comment_meta_whitelist );
424    }
425
426    /**
427     * Adds 'revew' to the list of comment types so Sync will listen for status changes on 'reviews'.
428     *
429     * @access public
430     *
431     * @param array $comment_types The list of comment types prior to this filter.
432     * return array                The list of comment types with 'review' added.
433     */
434    public function add_review_comment_types( $comment_types ) {
435        if ( is_array( $comment_types ) ) {
436            $comment_types[] = 'review';
437        }
438        return $comment_types;
439    }
440
441    /**
442     * Stop comments from the Action Scheduler from being synced.
443     * https://github.com/woocommerce/woocommerce/tree/e7762627c37ec1f7590e6cac4218ba0c6a20024d/includes/libraries/action-scheduler
444     *
445     * @since 1.6.3
446     * @since-jetpack 7.7.0
447     *
448     * @param boolean $can_sync Should we prevent comment data from bing synced to WordPress.com.
449     * @param mixed   $comment  WP_COMMENT object.
450     *
451     * @return bool
452     */
453    public function filter_action_scheduler_comments( $can_sync, $comment ) {
454        if ( isset( $comment->comment_agent ) && 'ActionScheduler' === $comment->comment_agent ) {
455            return true;
456        }
457        return $can_sync;
458    }
459
460    /**
461     * Whitelist for options we are interested to sync.
462     *
463     * @access private
464     * @static
465     *
466     * @var array
467     */
468    private static $wc_options_whitelist = array(
469        'woocommerce_currency',
470        'woocommerce_db_version',
471        'woocommerce_weight_unit',
472        'woocommerce_version',
473        'woocommerce_unforce_ssl_checkout',
474        'woocommerce_tax_total_display',
475        'woocommerce_tax_round_at_subtotal',
476        'woocommerce_tax_display_shop',
477        'woocommerce_tax_display_cart',
478        'woocommerce_prices_include_tax',
479        'woocommerce_price_thousand_sep',
480        'woocommerce_price_num_decimals',
481        'woocommerce_price_decimal_sep',
482        'woocommerce_notify_low_stock',
483        'woocommerce_notify_low_stock_amount',
484        'woocommerce_notify_no_stock',
485        'woocommerce_notify_no_stock_amount',
486        'woocommerce_manage_stock',
487        'woocommerce_force_ssl_checkout',
488        'woocommerce_hide_out_of_stock_items',
489        'woocommerce_file_download_method',
490        'woocommerce_enable_signup_and_login_from_checkout',
491        'woocommerce_enable_shipping_calc',
492        'woocommerce_enable_review_rating',
493        'woocommerce_enable_guest_checkout',
494        'woocommerce_enable_coupons',
495        'woocommerce_enable_checkout_login_reminder',
496        'woocommerce_enable_ajax_add_to_cart',
497        'woocommerce_dimension_unit',
498        'woocommerce_default_country',
499        'woocommerce_default_customer_address',
500        'woocommerce_currency_pos',
501        'woocommerce_api_enabled',
502        'woocommerce_allow_tracking',
503        'woocommerce_task_list_hidden',
504        'woocommerce_cod_settings',
505        'woocommerce_store_address',
506        'woocommerce_store_address_2',
507        'woocommerce_store_city',
508        'woocommerce_store_postcode',
509        'woocommerce_admin_install_timestamp',
510        'woocommerce_enable_signup_from_checkout_for_subscriptions',
511        'woocommerce_enable_myaccount_registration',
512        'woocommerce_registration_generate_password',
513        'woocommerce_erasure_request_removes_order_data',
514        'woocommerce_erasure_request_removes_subscription_data',
515        'woocommerce_erasure_request_removes_download_data',
516        'woocommerce_allow_bulk_remove_personal_data',
517        'woocommerce_registration_privacy_policy_text',
518        'woocommerce_checkout_privacy_policy_text',
519        'woocommerce_delete_inactive_accounts',
520        'woocommerce_trash_pending_orders',
521        'woocommerce_trash_failed_orders',
522        'woocommerce_trash_cancelled_orders',
523        'woocommerce_anonymize_refunded_orders',
524        'woocommerce_anonymize_completed_orders',
525        'woocommerce_anonymize_ended_subscriptions',
526        'woocommerce_enable_delayed_account_creation',
527        'woocommerce_gateway_stripe_retention',
528        'wc_downloads_approved_directories_mode', // This and the below options relate to the WooCommerce Products settings page. Required for the Activity Log.
529        'woocommerce_attribute_lookup_direct_updates',
530        'woocommerce_attribute_lookup_enabled',
531        'woocommerce_attribute_lookup_optimized_updates',
532        'woocommerce_cart_redirect_after_add',
533        'woocommerce_downloads_add_hash_to_filename',
534        'woocommerce_downloads_count_partial',
535        'woocommerce_downloads_deliver_inline',
536        'woocommerce_downloads_grant_access_after_payment',
537        'woocommerce_downloads_redirect_fallback_allowed',
538        'woocommerce_downloads_require_login',
539        'woocommerce_enable_reviews',
540        'woocommerce_hold_stock_minutes',
541        'woocommerce_review_rating_required',
542        'woocommerce_review_rating_verification_label',
543        'woocommerce_review_rating_verification_required',
544        'woocommerce_shop_page_id',
545        'woocommerce_stock_email_recipient',
546        'woocommerce_stock_format',
547        'woocommerce_allowed_countries',
548        'woocommerce_specific_allowed_countries',
549        'woocommerce_ship_to_countries',
550        'woocommerce_specific_ship_to_countries',
551        'woocommerce_calc_taxes',
552        'woocommerce_calc_discounts_sequentially',
553    );
554
555    /**
556     * Whitelist for constants we are interested to sync.
557     *
558     * @access private
559     * @static
560     *
561     * @var array
562     */
563    private static $wc_constants_whitelist = array(
564        // WooCommerce constants.
565        'WC_PLUGIN_FILE',
566        'WC_ABSPATH',
567        'WC_PLUGIN_BASENAME',
568        'WC_VERSION',
569        'WOOCOMMERCE_VERSION',
570        'WC_ROUNDING_PRECISION',
571        'WC_DISCOUNT_ROUNDING_MODE',
572        'WC_TAX_ROUNDING_MODE',
573        'WC_DELIMITER',
574        'WC_LOG_DIR',
575        'WC_SESSION_CACHE_GROUP',
576        'WC_TEMPLATE_DEBUG_MODE',
577    );
578
579    /**
580     * Whitelist for post meta we are interested to sync.
581     *
582     * @access private
583     * @static
584     *
585     * @var array
586     */
587    public static $wc_post_meta_whitelist = array(
588        // WooCommerce products.
589        // See https://github.com/woocommerce/woocommerce/blob/8ed6e7436ff87c2153ed30edd83c1ab8abbdd3e9/includes/data-stores/class-wc-product-data-store-cpt.php#L21 .
590        '_visibility',
591        '_sku',
592        '_price',
593        '_regular_price',
594        '_sale_price',
595        '_sale_price_dates_from',
596        '_sale_price_dates_to',
597        'total_sales',
598        '_tax_status',
599        '_tax_class',
600        '_manage_stock',
601        '_backorders',
602        '_sold_individually',
603        '_weight',
604        '_length',
605        '_width',
606        '_height',
607        '_upsell_ids',
608        '_crosssell_ids',
609        '_purchase_note',
610        '_default_attributes',
611        '_product_attributes',
612        '_virtual',
613        '_downloadable',
614        '_download_limit',
615        '_download_expiry',
616        '_featured',
617        '_downloadable_files',
618        '_wc_rating_count',
619        '_wc_average_rating',
620        '_wc_review_count',
621        '_variation_description',
622        '_thumbnail_id',
623        '_file_paths',
624        '_product_image_gallery',
625        '_product_version',
626        '_wp_old_slug',
627
628        // Woocommerce orders.
629        // See https://github.com/woocommerce/woocommerce/blob/8ed6e7436ff87c2153ed30edd83c1ab8abbdd3e9/includes/data-stores/class-wc-order-data-store-cpt.php#L27 .
630        '_order_key',
631        '_order_currency',
632        // '_billing_first_name', do not sync these as they contain personal data
633        // '_billing_last_name',
634        // '_billing_company',
635        // '_billing_address_1',
636        // '_billing_address_2',
637        '_billing_city',
638        '_billing_state',
639        '_billing_postcode',
640        '_billing_country',
641        // '_billing_email', do not sync these as they contain personal data.
642        // '_billing_phone',
643        // '_shipping_first_name',
644        // '_shipping_last_name',
645        // '_shipping_company',
646        // '_shipping_address_1',
647        // '_shipping_address_2',
648        '_shipping_city',
649        '_shipping_state',
650        '_shipping_postcode',
651        '_shipping_country',
652        '_completed_date',
653        '_paid_date',
654        '_cart_discount',
655        '_cart_discount_tax',
656        '_order_shipping',
657        '_order_shipping_tax',
658        '_order_tax',
659        '_order_total',
660        '_payment_method',
661        '_payment_method_title',
662        // '_transaction_id', do not sync these as they contain personal data.
663        // '_customer_ip_address',
664        // '_customer_user_agent',
665        '_created_via',
666        '_order_version',
667        '_prices_include_tax',
668        '_date_completed',
669        '_date_paid',
670        '_payment_tokens',
671        // '_billing_address_index', do not sync these as they contain personal data.
672        // '_shipping_address_index',
673        '_recorded_sales',
674        '_recorded_coupon_usage_counts',
675        // See https://github.com/woocommerce/woocommerce/blob/8ed6e7436ff87c2153ed30edd83c1ab8abbdd3e9/includes/data-stores/class-wc-order-data-store-cpt.php#L539 .
676        '_download_permissions_granted',
677        // See https://github.com/woocommerce/woocommerce/blob/8ed6e7436ff87c2153ed30edd83c1ab8abbdd3e9/includes/data-stores/class-wc-order-data-store-cpt.php#L594 .
678        '_order_stock_reduced',
679        '_cart_hash',
680
681        // Woocommerce order refunds.
682        // See https://github.com/woocommerce/woocommerce/blob/b8a2815ae546c836467008739e7ff5150cb08e93/includes/data-stores/class-wc-order-refund-data-store-cpt.php#L20 .
683        '_order_currency',
684        '_refund_amount',
685        '_refunded_by',
686        '_refund_reason',
687        '_order_shipping',
688        '_order_shipping_tax',
689        '_order_tax',
690        '_order_total',
691        '_order_version',
692        '_prices_include_tax',
693        '_payment_tokens',
694    );
695
696    /**
697     * Whitelist for comment meta we are interested to sync.
698     *
699     * @access private
700     * @static
701     *
702     * @var array
703     */
704    private static $wc_comment_meta_whitelist = array(
705        'rating',
706    );
707
708    /**
709     * Return a list of objects by their type and IDs
710     *
711     * @param string $object_type Object type.
712     * @param array  $ids IDs of objects to return.
713     *
714     * @access public
715     *
716     * @return array|object|WP_Error|null
717     */
718    public function get_objects_by_id( $object_type, $ids ) {
719        switch ( $object_type ) {
720            case 'order_item':
721                return $this->get_order_item_by_ids( $ids );
722        }
723
724        return new WP_Error( 'unsupported_object_type', 'Unsupported object type' );
725    }
726
727    /**
728     * Returns a list of order_item objects by their IDs.
729     *
730     * @param array  $ids List of order_item IDs to fetch.
731     * @param string $order Either 'ASC' or 'DESC'.
732     *
733     * @access public
734     *
735     * @return array|object|null
736     */
737    public function get_order_item_by_ids( $ids, $order = '' ) {
738        global $wpdb;
739
740        if ( ! is_array( $ids ) ) {
741            return array();
742        }
743
744        // Make sure the IDs are numeric and are non-zero.
745        $ids = array_filter( array_map( 'intval', $ids ) );
746
747        if ( empty( $ids ) ) {
748            return array();
749        }
750
751        // Prepare the placeholders for the prepared query below.
752        $placeholders = implode( ',', array_fill( 0, count( $ids ), '%d' ) );
753
754        $query = "SELECT * FROM {$this->order_item_table_name} WHERE order_item_id IN ( $placeholders )";
755        if ( ! empty( $order ) && in_array( $order, array( 'ASC', 'DESC' ), true ) ) {
756            $query .= " ORDER BY order_item_id $order";
757        }
758
759        // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
760        return $wpdb->get_results( $wpdb->prepare( $query, $ids ), ARRAY_A );
761    }
762
763    /**
764     * Build the full sync action object for WooCommerce order items.
765     *
766     * @access public
767     *
768     * @param array $args An array with the order items and the previous end.
769     *
770     * @return array An array with the order items, order item meta and the previous end.
771     */
772    public function build_full_sync_action_array( $args ) {
773        list( $filtered_order_items, $previous_end ) = $args;
774        return array(
775            'order_items'     => $filtered_order_items['objects'],
776            'order_item_meta' => $filtered_order_items['meta'],
777            'previous_end'    => $previous_end,
778        );
779    }
780
781    /**
782     * Given the Module Configuration and Status return the next chunk of items to send.
783     * This function also expands the posts and metadata and filters them based on the maximum size constraints.
784     *
785     * @param array $config This module Full Sync configuration.
786     * @param array $status This module Full Sync status.
787     * @param int   $chunk_size Chunk size.
788     *
789     * @return array
790     */
791    public function get_next_chunk( $config, $status, $chunk_size ) {
792
793        $order_item_ids = parent::get_next_chunk( $config, $status, $chunk_size );
794
795        if ( empty( $order_item_ids ) ) {
796            return array();
797        }
798        // Fetch the order items in DESC order for the next chunk logic to work.
799        $order_items = $this->get_order_item_by_ids( $order_item_ids, 'DESC' );
800
801        // If no orders were fetched, make sure to return the expected structure so that status is updated correctly.
802        if ( empty( $order_items ) ) {
803            return array(
804                'object_ids' => $order_item_ids,
805                'objects'    => array(),
806            );
807        }
808
809        // Get the order IDs from the orders that were fetched.
810        $fetched_order_item_ids = wp_list_pluck( $order_items, 'order_item_id' );
811        $metadata               = $this->get_metadata( $fetched_order_item_ids, 'order_item', static::$order_item_meta_whitelist );
812
813        // Filter the orders and metadata based on the maximum size constraints.
814        list( $filtered_order_item_ids, $filtered_order_items, $filtered_order_items_metadata ) = $this->filter_objects_and_metadata_by_size(
815            'order_item',
816            $order_items,
817            $metadata,
818            self::MAX_META_LENGTH,
819            self::MAX_SIZE_FULL_SYNC
820        );
821
822        return array(
823            'object_ids' => $filtered_order_item_ids,
824            'objects'    => $filtered_order_items,
825            'meta'       => $filtered_order_items_metadata,
826        );
827    }
828}