Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 2
n/a
0 / 0
CRAP
n/a
0 / 0
Jetpack_WooCommerce_Analytics_Universal
n/a
0 / 0
n/a
0 / 0
68
n/a
0 / 0
 __construct
n/a
0 / 0
n/a
0 / 0
1
 loop_session_events
n/a
0 / 0
n/a
0 / 0
4
 remove_from_cart
n/a
0 / 0
n/a
0 / 0
1
 remove_from_cart_attributes
n/a
0 / 0
n/a
0 / 0
2
 get_shipping_option_for_item
n/a
0 / 0
n/a
0 / 0
13
 checkout_process
n/a
0 / 0
n/a
0 / 0
9
 order_process
n/a
0 / 0
n/a
0 / 0
16
 remove_from_cart_via_quantity
n/a
0 / 0
n/a
0 / 0
1
 get_inner_blocks
n/a
0 / 0
n/a
0 / 0
4
 capture_add_to_cart
n/a
0 / 0
n/a
0 / 0
4
 capture_event_in_session_data
n/a
0 / 0
n/a
0 / 0
6
 save_checkout_post_data
n/a
0 / 0
n/a
0 / 0
4
 capture_created_customer
n/a
0 / 0
n/a
0 / 0
3
1<?php
2/**
3 * Jetpack_WooCommerce_Analytics_Universal
4 *
5 * @deprecated 13.3
6 *
7 * @package automattic/jetpack
8 * @author Automattic
9 */
10
11/**
12 * Bail if accessed directly
13 */
14if ( ! defined( 'ABSPATH' ) ) {
15    exit( 0 );
16}
17
18/**
19 * Class Jetpack_WooCommerce_Analytics_Universal
20 * Filters and Actions added to Store pages to perform analytics
21 *
22 * @deprecated 13.3
23 */
24class Jetpack_WooCommerce_Analytics_Universal {
25
26    /**
27     * Trait to handle common analytics functions.
28     */
29    use Jetpack_WooCommerce_Analytics_Trait;
30
31    /**
32     * Jetpack_WooCommerce_Analytics_Universal constructor.
33     *
34     * @deprecated 13.3
35     */
36    public function __construct() {
37        $this->find_cart_checkout_content_sources();
38        $this->additional_blocks_on_cart_page     = $this->get_additional_blocks_on_page( 'cart' );
39        $this->additional_blocks_on_checkout_page = $this->get_additional_blocks_on_page( 'checkout' );
40
41        // add to carts from non-product pages or lists -- search, store etc.
42        add_action( 'wp_head', array( $this, 'loop_session_events' ), 2 );
43
44        // Capture cart events.
45        add_action( 'woocommerce_add_to_cart', array( $this, 'capture_add_to_cart' ), 10, 6 );
46
47        add_action( 'woocommerce_after_cart', array( $this, 'remove_from_cart' ) );
48        add_action( 'woocommerce_after_mini_cart', array( $this, 'remove_from_cart' ) );
49        add_action( 'wcct_before_cart_widget', array( $this, 'remove_from_cart' ) );
50        add_filter( 'woocommerce_cart_item_remove_link', array( $this, 'remove_from_cart_attributes' ), 10, 2 );
51
52        // Checkout.
53        // Send events after checkout template (shortcode).
54        add_action( 'woocommerce_after_checkout_form', array( $this, 'checkout_process' ) );
55        // Send events after checkout block.
56        add_action( 'woocommerce_blocks_enqueue_checkout_block_scripts_after', array( $this, 'checkout_process' ) );
57
58        // order confirmed.
59        add_action( 'woocommerce_thankyou', array( $this, 'order_process' ), 10, 1 );
60        add_action( 'woocommerce_after_cart', array( $this, 'remove_from_cart_via_quantity' ), 10, 1 );
61
62        add_filter( 'woocommerce_checkout_posted_data', array( $this, 'save_checkout_post_data' ), 10, 1 );
63
64        add_action( 'woocommerce_created_customer', array( $this, 'capture_created_customer' ), 10, 2 );
65    }
66
67    /**
68     * On product lists or other non-product pages, add an event listener to "Add to Cart" button click
69     *
70     * @deprecated 13.3
71     */
72    public function loop_session_events() {
73        // Check for previous events queued in session data.
74        if ( is_object( WC()->session ) ) {
75            $data = WC()->session->get( 'wca_session_data' );
76            if ( ! empty( $data ) ) {
77                foreach ( $data as $data_instance ) {
78                    $this->record_event(
79                        $data_instance['event'],
80                        array(
81                            'pq' => $data_instance['quantity'],
82                        ),
83                        $data_instance['product_id']
84                    );
85                }
86                // Clear data, now that these events have been recorded.
87                WC()->session->set( 'wca_session_data', '' );
88            }
89        }
90    }
91
92    /**
93     * On the cart page, add an event listener for removal of product click
94     *
95     * @deprecated 13.3
96     */
97    public function remove_from_cart() {
98        $common_props = wp_json_encode(
99            array_merge(
100                array(
101                    '_en' => 'woocommerceanalytics_remove_from_cart',
102                ),
103                $this->get_common_properties()
104            ),
105            JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP
106        );
107
108        // We listen at div.woocommerce because the cart 'form' contents get forcibly
109        // updated and subsequent removals from cart would then not have this click
110        // handler attached.
111        wc_enqueue_js(
112            "jQuery( 'div.woocommerce' ).on( 'click', 'a.remove', function() {
113                var productID = jQuery( this ).data( 'product_id' );
114                var quantity = jQuery( this ).parent().parent().find( '.qty' ).val()
115                var common_props = $common_props;
116                common_props.pi = productID;
117                common_props.pq = quantity ? quantity : '1';
118                _wca.push( common_props );
119            } );"
120        );
121    }
122
123    /**
124     * Adds the product ID to the remove product link (for use by remove_from_cart above) if not present
125     *
126     * @deprecated 13.3
127     *
128     * @param string $url Full HTML a tag of the link to remove an item from the cart.
129     * @param string $key Unique Key ID for a cart item.
130     *
131     * @return string
132     */
133    public function remove_from_cart_attributes( $url, $key ) {
134        if ( str_contains( $url, 'data-product_id' ) ) {
135            return $url;
136        }
137
138        $item    = WC()->cart->get_cart_item( $key );
139        $product = $item['data'];
140
141        $new_attributes = sprintf(
142            '" data-product_id="%s">',
143            esc_attr( $product->get_id() )
144        );
145
146        $url = str_replace( '">', $new_attributes, $url );
147        return $url;
148    }
149
150    /**
151     * Get the selected shipping option for a cart item. If the name cannot be found in the options table, the method's
152     * ID will be used.
153     *
154     * @deprecated 13.3
155     *
156     * @param string $cart_item_key the cart item key.
157     *
158     * @return mixed|bool
159     */
160    public function get_shipping_option_for_item( $cart_item_key ) {
161        $packages         = wc()->shipping()->get_packages();
162        $selected_options = wc()->session->get( 'chosen_shipping_methods' );
163
164        if ( ! is_array( $packages ) || ! is_array( $selected_options ) ) {
165            return false;
166        }
167
168        foreach ( $packages as $package_id => $package ) {
169
170            if ( ! isset( $package['contents'] ) || ! is_array( $package['contents'] ) ) {
171                return false;
172            }
173
174            foreach ( $package['contents'] as $package_item ) {
175                if ( ! isset( $package_item['key'] ) || $package_item['key'] !== $cart_item_key || ! isset( $selected_options[ $package_id ] ) ) {
176                    continue;
177                }
178                $selected_rate_id = $selected_options[ $package_id ];
179                $method_key_id    = sanitize_text_field( str_replace( ':', '_', $selected_rate_id ) );
180                $option_name      = 'woocommerce_' . $method_key_id . '_settings';
181                $option_value     = get_option( $option_name );
182                $title            = '';
183                if ( is_array( $option_value ) && isset( $option_value['title'] ) ) {
184                    $title = $option_value['title'];
185                }
186                if ( ! $title ) {
187                    return $selected_rate_id;
188                }
189                return $title;
190            }
191        }
192
193        return false;
194    }
195
196    /**
197     * On the Checkout page, trigger an event for each product in the cart
198     *
199     * @deprecated 13.3
200     */
201    public function checkout_process() {
202        global $post;
203        $checkout_page_id = wc_get_page_id( 'checkout' );
204        $cart             = WC()->cart->get_cart();
205
206        $enabled_payment_options = array_filter(
207            WC()->payment_gateways->get_available_payment_gateways(),
208            function ( $payment_gateway ) {
209                if ( ! $payment_gateway instanceof WC_Payment_Gateway ) {
210                    return false;
211                }
212
213                return $payment_gateway->is_available();
214            }
215        );
216
217        $enabled_payment_options = array_keys( $enabled_payment_options );
218
219        $is_in_checkout_page = $checkout_page_id === $post->ID ? 'Yes' : 'No';
220        $session             = WC()->session;
221        if ( is_object( $session ) ) {
222            $session->set( 'checkout_page_used', true );
223            $session->save_data();
224        }
225
226        foreach ( $cart as $cart_item_key => $cart_item ) {
227            /**
228            * This filter is already documented in woocommerce/templates/cart/cart.php
229            */
230            $product = apply_filters( 'woocommerce_cart_item_product', $cart_item['data'], $cart_item, $cart_item_key );
231
232            if ( ! $product || ! $product instanceof WC_Product ) {
233                continue;
234            }
235
236            $data = $this->get_cart_checkout_shared_data();
237
238            $data['from_checkout'] = $is_in_checkout_page;
239
240            if ( ! empty( $data['products'] ) ) {
241                unset( $data['products'] );
242            }
243
244            if ( ! empty( $data['shipping_options_count'] ) ) {
245                unset( $data['shipping_options_count'] );
246            }
247
248            $data['pq'] = $cart_item['quantity'];
249
250            $properties = $this->process_event_properties(
251                'woocommerceanalytics_product_checkout',
252                $data,
253                $product->get_id()
254            );
255
256            wc_enqueue_js(
257                "
258                var cartItem_{$cart_item_key}_logged = false;
259                var properties = {$properties};
260                // Check if jQuery is available
261                if ( typeof jQuery !== 'undefined' ) {
262                    // This is only triggered on the checkout shortcode.
263                    jQuery( document.body ).on( 'init_checkout', function () {
264                        if ( true === cartItem_{$cart_item_key}_logged ) {
265                            return;
266                        }
267                        wp.hooks.addAction( 'wcpay.payment-request.availability', 'wcpay', function ( args ) {
268                            properties.express_checkout = args.paymentRequestType;
269                        } );
270                            properties.checkout_page_contains_checkout_block = '0';
271                            properties.checkout_page_contains_checkout_shortcode = '1';
272
273                            _wca.push( properties );
274                            cartItem_{$cart_item_key}_logged = true;
275
276                    } );
277                }
278
279                if (
280                    typeof wp !== 'undefined' &&
281                    typeof wp.data !== 'undefined' &&
282                    typeof wp.data.subscribe !== 'undefined'
283                ) {
284                    wp.data.subscribe( function () {
285                        if ( true === cartItem_{$cart_item_key}_logged ) {
286                            return;
287                        }
288
289                        const checkoutDataStore = wp.data.select( 'wc/store/checkout' );
290                        // Ensures we're not in Cart, but in Checkout page.
291                        if (
292                            typeof checkoutDataStore !== 'undefined' &&
293                            checkoutDataStore.getOrderId() !== 0
294                        ) {
295                            properties.express_checkout = Object.keys( wc.wcBlocksRegistry.getExpressPaymentMethods() );
296                            properties.checkout_page_contains_checkout_block = '1';
297                            properties.checkout_page_contains_checkout_shortcode = '0';
298
299                            _wca.push( properties );
300                            cartItem_{$cart_item_key}_logged = true;
301                        }
302                    } );
303                }
304            "
305            );
306
307        }
308    }
309
310    /**
311     * After the checkout process, fire an event for each item in the order
312     *
313     * @deprecated 13.3
314     *
315     * @param string $order_id Order Id.
316     */
317    public function order_process( $order_id ) {
318        $order = wc_get_order( $order_id );
319
320        if (
321            ! $order
322            || ! $order instanceof WC_Order
323        ) {
324            return;
325        }
326
327        $payment_option = $order->get_payment_method();
328
329        if ( is_object( WC()->session ) ) {
330            $create_account     = true === WC()->session->get( 'wc_checkout_createaccount_used' ) ? 'Yes' : 'No';
331            $checkout_page_used = true === WC()->session->get( 'checkout_page_used' ) ? 'Yes' : 'No';
332
333        } else {
334            $create_account     = 'No';
335            $checkout_page_used = 'No';
336        }
337
338        $guest_checkout = $order->get_user() ? 'No' : 'Yes';
339
340        $express_checkout = 'null';
341        // When the payment option is woocommerce_payment
342        // See if Google Pay or Apple Pay was used.
343        if ( 'woocommerce_payments' === $payment_option ) {
344            $payment_option_title = $order->get_payment_method_title();
345            if ( 'Google Pay (WooCommerce Payments)' === $payment_option_title ) {
346                $express_checkout = array( 'google_pay' );
347            } elseif ( 'Apple Pay (WooCommerce Payments)' === $payment_option_title ) {
348                $express_checkout = array( 'apple_pay' );
349            }
350        }
351
352        $checkout_page_contains_checkout_block         = '0';
353            $checkout_page_contains_checkout_shortcode = '0';
354
355        $order_source = $order->get_created_via();
356        if ( 'store-api' === $order_source ) {
357            $checkout_page_contains_checkout_block = '1';
358        } elseif ( 'checkout' === $order_source ) {
359            $checkout_page_contains_checkout_shortcode = '1';
360        }
361
362        // loop through products in the order and queue a purchase event.
363        foreach ( $order->get_items() as $order_item ) {
364            // @phan-suppress-next-line PhanUndeclaredMethod -- Checked before being called. See also https://github.com/phan/phan/issues/1204.
365            $product_id = is_callable( array( $order_item, 'get_product_id' ) ) ? $order_item->get_product_id() : -1;
366
367            $order_items       = $order->get_items();
368            $order_items_count = 0;
369            if ( is_array( $order_items ) ) {
370                $order_items_count = count( $order_items );
371            }
372            $order_coupons       = $order->get_coupons();
373            $order_coupons_count = 0;
374            if ( is_array( $order_coupons ) ) {
375                $order_coupons_count = count( $order_coupons );
376            }
377            $this->record_event(
378                'woocommerceanalytics_product_purchase',
379                array(
380                    'oi'               => $order->get_order_number(),
381                    'pq'               => $order_item->get_quantity(),
382                    'payment_option'   => $payment_option,
383                    'create_account'   => $create_account,
384                    'guest_checkout'   => $guest_checkout,
385                    'express_checkout' => $express_checkout,
386                    'products_count'   => $order_items_count,
387                    'coupon_used'      => $order_coupons_count,
388                    'order_value'      => $order->get_total(),
389                    'from_checkout'    => $checkout_page_used,
390                    'checkout_page_contains_checkout_block' => $checkout_page_contains_checkout_block,
391                    'checkout_page_contains_checkout_shortcode' => $checkout_page_contains_checkout_shortcode,
392                ),
393                $product_id
394            );
395        }
396    }
397
398    /**
399     * Listen for clicks on the "Update Cart" button to know if an item has been removed by
400     * updating its quantity to zero
401     *
402     * @deprecated 13.3
403     */
404    public function remove_from_cart_via_quantity() {
405        $common_props = wp_json_encode(
406            array_merge(
407                array(
408                    '_en' => 'woocommerceanalytics_remove_from_cart',
409                ),
410                $this->get_common_properties()
411            ),
412            JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP
413        );
414
415        wc_enqueue_js(
416            "
417            jQuery( 'button[name=update_cart]' ).on( 'click', function() {
418                var cartItems = jQuery( '.cart_item' );
419                cartItems.each( function( item ) {
420                    var qty = jQuery( this ).find( 'input.qty' );
421                    if ( qty && qty.val() === '0' ) {
422                        var productID = jQuery( this ).find( '.product-remove a' ).data( 'product_id' );
423                        var common_props = $common_props;
424                        common_props.pi = productID;
425                        _wca.push( common_props );
426                    }
427                } );
428            } );"
429        );
430    }
431
432    /**
433     * Gets the inner blocks of a block.
434     *
435     * @deprecated 13.3
436     *
437     * @param array $inner_blocks The inner blocks.
438     *
439     * @return array
440     */
441    private function get_inner_blocks( $inner_blocks ) {
442        $block_names = array();
443        if ( ! empty( $inner_blocks['blockName'] ) ) {
444            $block_names[] = $inner_blocks['blockName'];
445        }
446        if ( isset( $inner_blocks['innerBlocks'] ) && is_array( $inner_blocks['innerBlocks'] ) ) {
447            $block_names = array_merge( $block_names, $this->get_inner_blocks( $inner_blocks['innerBlocks'] ) );
448        }
449        return $block_names;
450    }
451
452    /**
453     * Track adding items to the cart.
454     *
455     * @deprecated 13.3
456     *
457     * @param string $cart_item_key Cart item key.
458     * @param int    $product_id Product added to cart.
459     * @param int    $quantity Quantity added to cart.
460     * @param int    $variation_id Product variation.
461     * @param array  $variation Variation attributes..
462     * @param array  $cart_item_data Other cart data.
463     */
464    public function capture_add_to_cart( $cart_item_key, $product_id, $quantity, $variation_id, $variation, $cart_item_data ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
465        $referer_postid = isset( $_SERVER['HTTP_REFERER'] ) ? url_to_postid( esc_url_raw( wp_unslash( $_SERVER['HTTP_REFERER'] ) ) ) : 0;
466        // if the referring post is not a product OR the product being added is not the same as post.
467        // (eg. related product list on single product page) then include a product view event.
468        $product_by_referer_postid = wc_get_product( $referer_postid );
469        if ( ! $product_by_referer_postid instanceof WC_Product || (int) $product_id !== $referer_postid ) {
470            $this->capture_event_in_session_data( $product_id, $quantity, 'woocommerceanalytics_product_view' );
471        }
472        // add cart event to the session data.
473        $this->capture_event_in_session_data( $product_id, $quantity, 'woocommerceanalytics_add_to_cart' );
474    }
475
476    /**
477     * Track in-session data.
478     *
479     * @deprecated 13.3
480     *
481     * @param int    $product_id Product ID.
482     * @param int    $quantity Quantity.
483     * @param string $event Fired event.
484     */
485    public function capture_event_in_session_data( $product_id, $quantity, $event ) {
486
487        $product = wc_get_product( $product_id );
488        if ( ! $product instanceof WC_Product ) {
489            return;
490        }
491
492        $quantity = ( 0 === $quantity ) ? 1 : $quantity;
493
494        // check for existing data.
495        if ( is_object( WC()->session ) ) {
496            $data = WC()->session->get( 'wca_session_data' );
497            if ( empty( $data ) || ! is_array( $data ) ) {
498                $data = array();
499            }
500        } else {
501            $data = array();
502        }
503
504        // extract new event data.
505        $new_data = array(
506            'event'      => $event,
507            'product_id' => (string) $product_id,
508            'quantity'   => (string) $quantity,
509        );
510
511        // append new data.
512        $data[] = $new_data;
513
514        WC()->session->set( 'wca_session_data', $data );
515    }
516
517    /**
518     * Save createaccount post data to be used in $this->order_process.
519     *
520     * @deprecated 13.3
521     *
522     * @param array $data post data from the checkout page.
523     *
524     * @return array
525     */
526    public function save_checkout_post_data( array $data ) {
527        $session = WC()->session;
528        if ( is_object( $session ) ) {
529            if ( isset( $data['createaccount'] ) && ! empty( $data['createaccount'] ) ) {
530                $session->set( 'wc_checkout_createaccount_used', true );
531                $session->save_data();
532            }
533        }
534        return $data;
535    }
536
537    /**
538     * Capture the create account event. Similar to save_checkout_post_data but works with Store API.
539     *
540     * @deprecated 13.3
541     *
542     * @param int   $customer_id Customer ID.
543     * @param array $new_customer_data New customer data.
544     */
545    public function capture_created_customer( $customer_id, $new_customer_data ) {
546        $session = WC()->session;
547        if ( is_object( $session ) ) {
548            if ( str_contains( $new_customer_data['source'], 'store-api' ) ) {
549                $session->set( 'wc_checkout_createaccount_used', true );
550                $session->save_data();
551            }
552        }
553    }
554}