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    );
548
549    /**
550     * Whitelist for constants we are interested to sync.
551     *
552     * @access private
553     * @static
554     *
555     * @var array
556     */
557    private static $wc_constants_whitelist = array(
558        // WooCommerce constants.
559        'WC_PLUGIN_FILE',
560        'WC_ABSPATH',
561        'WC_PLUGIN_BASENAME',
562        'WC_VERSION',
563        'WOOCOMMERCE_VERSION',
564        'WC_ROUNDING_PRECISION',
565        'WC_DISCOUNT_ROUNDING_MODE',
566        'WC_TAX_ROUNDING_MODE',
567        'WC_DELIMITER',
568        'WC_LOG_DIR',
569        'WC_SESSION_CACHE_GROUP',
570        'WC_TEMPLATE_DEBUG_MODE',
571    );
572
573    /**
574     * Whitelist for post meta we are interested to sync.
575     *
576     * @access private
577     * @static
578     *
579     * @var array
580     */
581    public static $wc_post_meta_whitelist = array(
582        // WooCommerce products.
583        // See https://github.com/woocommerce/woocommerce/blob/8ed6e7436ff87c2153ed30edd83c1ab8abbdd3e9/includes/data-stores/class-wc-product-data-store-cpt.php#L21 .
584        '_visibility',
585        '_sku',
586        '_price',
587        '_regular_price',
588        '_sale_price',
589        '_sale_price_dates_from',
590        '_sale_price_dates_to',
591        'total_sales',
592        '_tax_status',
593        '_tax_class',
594        '_manage_stock',
595        '_backorders',
596        '_sold_individually',
597        '_weight',
598        '_length',
599        '_width',
600        '_height',
601        '_upsell_ids',
602        '_crosssell_ids',
603        '_purchase_note',
604        '_default_attributes',
605        '_product_attributes',
606        '_virtual',
607        '_downloadable',
608        '_download_limit',
609        '_download_expiry',
610        '_featured',
611        '_downloadable_files',
612        '_wc_rating_count',
613        '_wc_average_rating',
614        '_wc_review_count',
615        '_variation_description',
616        '_thumbnail_id',
617        '_file_paths',
618        '_product_image_gallery',
619        '_product_version',
620        '_wp_old_slug',
621
622        // Woocommerce orders.
623        // See https://github.com/woocommerce/woocommerce/blob/8ed6e7436ff87c2153ed30edd83c1ab8abbdd3e9/includes/data-stores/class-wc-order-data-store-cpt.php#L27 .
624        '_order_key',
625        '_order_currency',
626        // '_billing_first_name', do not sync these as they contain personal data
627        // '_billing_last_name',
628        // '_billing_company',
629        // '_billing_address_1',
630        // '_billing_address_2',
631        '_billing_city',
632        '_billing_state',
633        '_billing_postcode',
634        '_billing_country',
635        // '_billing_email', do not sync these as they contain personal data.
636        // '_billing_phone',
637        // '_shipping_first_name',
638        // '_shipping_last_name',
639        // '_shipping_company',
640        // '_shipping_address_1',
641        // '_shipping_address_2',
642        '_shipping_city',
643        '_shipping_state',
644        '_shipping_postcode',
645        '_shipping_country',
646        '_completed_date',
647        '_paid_date',
648        '_cart_discount',
649        '_cart_discount_tax',
650        '_order_shipping',
651        '_order_shipping_tax',
652        '_order_tax',
653        '_order_total',
654        '_payment_method',
655        '_payment_method_title',
656        // '_transaction_id', do not sync these as they contain personal data.
657        // '_customer_ip_address',
658        // '_customer_user_agent',
659        '_created_via',
660        '_order_version',
661        '_prices_include_tax',
662        '_date_completed',
663        '_date_paid',
664        '_payment_tokens',
665        // '_billing_address_index', do not sync these as they contain personal data.
666        // '_shipping_address_index',
667        '_recorded_sales',
668        '_recorded_coupon_usage_counts',
669        // See https://github.com/woocommerce/woocommerce/blob/8ed6e7436ff87c2153ed30edd83c1ab8abbdd3e9/includes/data-stores/class-wc-order-data-store-cpt.php#L539 .
670        '_download_permissions_granted',
671        // See https://github.com/woocommerce/woocommerce/blob/8ed6e7436ff87c2153ed30edd83c1ab8abbdd3e9/includes/data-stores/class-wc-order-data-store-cpt.php#L594 .
672        '_order_stock_reduced',
673        '_cart_hash',
674
675        // Woocommerce order refunds.
676        // See https://github.com/woocommerce/woocommerce/blob/b8a2815ae546c836467008739e7ff5150cb08e93/includes/data-stores/class-wc-order-refund-data-store-cpt.php#L20 .
677        '_order_currency',
678        '_refund_amount',
679        '_refunded_by',
680        '_refund_reason',
681        '_order_shipping',
682        '_order_shipping_tax',
683        '_order_tax',
684        '_order_total',
685        '_order_version',
686        '_prices_include_tax',
687        '_payment_tokens',
688    );
689
690    /**
691     * Whitelist for comment meta we are interested to sync.
692     *
693     * @access private
694     * @static
695     *
696     * @var array
697     */
698    private static $wc_comment_meta_whitelist = array(
699        'rating',
700    );
701
702    /**
703     * Return a list of objects by their type and IDs
704     *
705     * @param string $object_type Object type.
706     * @param array  $ids IDs of objects to return.
707     *
708     * @access public
709     *
710     * @return array|object|WP_Error|null
711     */
712    public function get_objects_by_id( $object_type, $ids ) {
713        switch ( $object_type ) {
714            case 'order_item':
715                return $this->get_order_item_by_ids( $ids );
716        }
717
718        return new WP_Error( 'unsupported_object_type', 'Unsupported object type' );
719    }
720
721    /**
722     * Returns a list of order_item objects by their IDs.
723     *
724     * @param array  $ids List of order_item IDs to fetch.
725     * @param string $order Either 'ASC' or 'DESC'.
726     *
727     * @access public
728     *
729     * @return array|object|null
730     */
731    public function get_order_item_by_ids( $ids, $order = '' ) {
732        global $wpdb;
733
734        if ( ! is_array( $ids ) ) {
735            return array();
736        }
737
738        // Make sure the IDs are numeric and are non-zero.
739        $ids = array_filter( array_map( 'intval', $ids ) );
740
741        if ( empty( $ids ) ) {
742            return array();
743        }
744
745        // Prepare the placeholders for the prepared query below.
746        $placeholders = implode( ',', array_fill( 0, count( $ids ), '%d' ) );
747
748        $query = "SELECT * FROM {$this->order_item_table_name} WHERE order_item_id IN ( $placeholders )";
749        if ( ! empty( $order ) && in_array( $order, array( 'ASC', 'DESC' ), true ) ) {
750            $query .= " ORDER BY order_item_id $order";
751        }
752
753        // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
754        return $wpdb->get_results( $wpdb->prepare( $query, $ids ), ARRAY_A );
755    }
756
757    /**
758     * Build the full sync action object for WooCommerce order items.
759     *
760     * @access public
761     *
762     * @param array $args An array with the order items and the previous end.
763     *
764     * @return array An array with the order items, order item meta and the previous end.
765     */
766    public function build_full_sync_action_array( $args ) {
767        list( $filtered_order_items, $previous_end ) = $args;
768        return array(
769            'order_items'     => $filtered_order_items['objects'],
770            'order_item_meta' => $filtered_order_items['meta'],
771            'previous_end'    => $previous_end,
772        );
773    }
774
775    /**
776     * Given the Module Configuration and Status return the next chunk of items to send.
777     * This function also expands the posts and metadata and filters them based on the maximum size constraints.
778     *
779     * @param array $config This module Full Sync configuration.
780     * @param array $status This module Full Sync status.
781     * @param int   $chunk_size Chunk size.
782     *
783     * @return array
784     */
785    public function get_next_chunk( $config, $status, $chunk_size ) {
786
787        $order_item_ids = parent::get_next_chunk( $config, $status, $chunk_size );
788
789        if ( empty( $order_item_ids ) ) {
790            return array();
791        }
792        // Fetch the order items in DESC order for the next chunk logic to work.
793        $order_items = $this->get_order_item_by_ids( $order_item_ids, 'DESC' );
794
795        // If no orders were fetched, make sure to return the expected structure so that status is updated correctly.
796        if ( empty( $order_items ) ) {
797            return array(
798                'object_ids' => $order_item_ids,
799                'objects'    => array(),
800            );
801        }
802
803        // Get the order IDs from the orders that were fetched.
804        $fetched_order_item_ids = wp_list_pluck( $order_items, 'order_item_id' );
805        $metadata               = $this->get_metadata( $fetched_order_item_ids, 'order_item', static::$order_item_meta_whitelist );
806
807        // Filter the orders and metadata based on the maximum size constraints.
808        list( $filtered_order_item_ids, $filtered_order_items, $filtered_order_items_metadata ) = $this->filter_objects_and_metadata_by_size(
809            'order_item',
810            $order_items,
811            $metadata,
812            self::MAX_META_LENGTH,
813            self::MAX_SIZE_FULL_SYNC
814        );
815
816        return array(
817            'object_ids' => $filtered_order_item_ids,
818            'objects'    => $filtered_order_items,
819            'meta'       => $filtered_order_items_metadata,
820        );
821    }
822}