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_Trait
n/a
0 / 0
n/a
0 / 0
79
n/a
0 / 0
 format_items_to_json
n/a
0 / 0
n/a
0 / 0
6
 get_cart_checkout_shared_data
n/a
0 / 0
n/a
0 / 0
5
 find_cart_checkout_content_sources
n/a
0 / 0
n/a
0 / 0
22
 get_common_properties
n/a
0 / 0
n/a
0 / 0
4
 record_event
n/a
0 / 0
n/a
0 / 0
1
 get_product_details
n/a
0 / 0
n/a
0 / 0
1
 get_product_categories_concatenated
n/a
0 / 0
n/a
0 / 0
7
 process_event_properties
n/a
0 / 0
n/a
0 / 0
4
 get_user_id
n/a
0 / 0
n/a
0 / 0
2
 get_additional_blocks_on_page
n/a
0 / 0
n/a
0 / 0
16
 get_cart_page_block_usage
n/a
0 / 0
n/a
0 / 0
4
 get_checkout_page_block_usage
n/a
0 / 0
n/a
0 / 0
4
 get_cart_checkout_info
n/a
0 / 0
n/a
0 / 0
1
 post_contains_text
n/a
0 / 0
n/a
0 / 0
2
1<?php
2/**
3 * Jetpack_WooCommerce_Analytics_Trait
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 * Jetpack_WooCommerce_Analytics_Trait
20 * Common functionality for WooCommerce Analytics classes.
21 *
22 * @deprecated 13.3
23 */
24trait Jetpack_WooCommerce_Analytics_Trait {
25
26    /**
27     * Saves whether the cart/checkout templates are in use based on WC Blocks version.
28     *
29     * @var bool true if the templates are in use.
30     */
31    protected $cart_checkout_templates_in_use;
32
33    /**
34     * The content of the cart page or where the cart page is ultimately derived from if using a template.
35     *
36     * @var string
37     */
38    protected $cart_content_source = '';
39
40    /**
41     * The content of the checkout page or where the cart page is ultimately derived from if using a template.
42     *
43     * @var string
44     */
45    protected $checkout_content_source = '';
46
47    /**
48     * Tracks any additional blocks loaded on the Cart page.
49     *
50     * @var array
51     */
52    protected $additional_blocks_on_cart_page;
53
54    /**
55     * Tracks any additional blocks loaded on the Checkout page.
56     *
57     * @var array
58     */
59    protected $additional_blocks_on_checkout_page;
60
61    /**
62     * Format Cart Items or Order Items to an array
63     *
64     * @deprecated 13.3
65     *
66     * @param array|WC_Order_Item[] $items Cart Items or Order Items.
67     */
68    protected function format_items_to_json( $items ) {
69        $products = array();
70
71        foreach ( $items as $item ) {
72            if ( $item instanceof WC_Order_Item_Product ) {
73                $product = wc_get_product( $item->get_product_id() );
74            } else {
75                $product = $item['data'];
76            }
77
78            if ( ! $product || ! $product instanceof WC_Product ) {
79                continue;
80            }
81
82            $data = $this->get_product_details( $product );
83
84            if ( $item instanceof WC_Order_Item_Product ) {
85                $data['pq'] = $item->get_quantity();
86            } else {
87                $data['pq'] = $item['quantity'];
88            }
89            $products[] = $data;
90        }
91
92        return wp_json_encode( $products, JSON_UNESCAPED_SLASHES );
93    }
94
95    /**
96     * Get Cart/Checkout page view shared data
97     *
98     * @deprecated 13.3
99     */
100    protected function get_cart_checkout_shared_data() {
101        $cart = WC()->cart;
102
103        $guest_checkout = ucfirst( get_option( 'woocommerce_enable_guest_checkout', 'No' ) );
104        $create_account = ucfirst( get_option( 'woocommerce_enable_signup_and_login_from_checkout', 'No' ) );
105
106        $coupons     = $cart->get_coupons();
107        $coupon_used = 0;
108        if ( is_countable( $coupons ) ) {
109            $coupon_used = count( $coupons ) ? 1 : 0;
110        }
111
112        $enabled_payment_options = array_filter(
113            WC()->payment_gateways->get_available_payment_gateways(),
114            function ( $payment_gateway ) {
115                if ( ! $payment_gateway instanceof WC_Payment_Gateway ) {
116                    return false;
117                }
118
119                return $payment_gateway->is_available();
120            }
121        );
122
123        $enabled_payment_options = array_keys( $enabled_payment_options );
124        $cart_total              = wc_prices_include_tax() ? $cart->get_cart_contents_total() + $cart->get_cart_contents_tax() : $cart->get_cart_contents_total();
125        $shared_data             = array(
126            'products'               => $this->format_items_to_json( $cart->get_cart() ),
127            'create_account'         => $create_account,
128            'guest_checkout'         => $guest_checkout,
129            'express_checkout'       => 'null', // TODO: not solved yet.
130            'products_count'         => $cart->get_cart_contents_count(),
131            'order_value'            => $cart_total,
132            'shipping_options_count' => 'null', // TODO: not solved yet.
133            'coupon_used'            => $coupon_used,
134            'payment_options'        => $enabled_payment_options,
135        );
136
137        return $shared_data;
138    }
139
140    /**
141     * Gets the content of the cart/checkout page or where the cart/checkout page is ultimately derived from if using a template.
142     * This method sets the class properties $checkout_content_source and $cart_content_source.
143     *
144     * @deprecated 13.3
145     *
146     * @return void Does not return, but sets class properties.
147     */
148    public function find_cart_checkout_content_sources() {
149
150        /**
151         * The steps we take to find the content are:
152         * 1. Check the transient, if that contains content and is not expired, return that.
153         * 2. Check if the cart/checkout templates are in use. If *not in use*, get the content from the pages and
154         *    return it, there is no need to dig further.
155         * 3. If the templates *are* in use, check if the `page-content-wrapper` block is in use. If so, get the content
156         *    from the pages (same as step 2) and return it.
157         * 4. If the templates are in use but `page-content-wrapper` is not, then get the content directly from the
158         *    template and return it.
159         * 5. At the end of each step, assign the found content to the relevant class properties and save them in a
160         *    transient with a 1-day lifespan. This will prevent us from having to do this work on every page load.
161         */
162
163        $cart_checkout_content_cache_transient_name = 'jetpack_woocommerce_analytics_cart_checkout_content_sources';
164
165        $transient_value = get_transient( $cart_checkout_content_cache_transient_name );
166
167        if (
168            false !== $transient_value &&
169            ! empty( $transient_value['checkout_content_source'] ) &&
170            ! empty( $transient_value['cart_content_source'] )
171        ) {
172            $this->cart_content_source     = $transient_value['cart_content_source'];
173            $this->checkout_content_source = $transient_value['checkout_content_source'];
174            return;
175        }
176
177        $this->cart_checkout_templates_in_use = wp_is_block_theme() && class_exists( 'Automattic\WooCommerce\Blocks\Package' ) && version_compare( Automattic\WooCommerce\Blocks\Package::get_version(), '10.6.0', '>=' );
178
179        // Cart/Checkout *pages* are in use if the templates are not in use. Return their content and do nothing else.
180        if ( ! $this->cart_checkout_templates_in_use ) {
181            $cart_page     = get_post( wc_get_page_id( 'cart' ) );
182            $checkout_page = get_post( wc_get_page_id( 'checkout' ) );
183
184            if ( $cart_page && isset( $cart_page->post_content ) ) {
185                $this->cart_content_source = $cart_page->post_content;
186            }
187
188            if ( $checkout_page && isset( $checkout_page->post_content ) ) {
189                $this->checkout_content_source = $checkout_page->post_content;
190            }
191
192            set_transient(
193                $cart_checkout_content_cache_transient_name,
194                array(
195                    'cart_content_source'     => $this->cart_content_source,
196                    'checkout_content_source' => $this->checkout_content_source,
197                ),
198                DAY_IN_SECONDS
199            );
200            return;
201        }
202
203        // We are in a Block theme - so we need to find out if the templates are being used.
204        if ( function_exists( 'get_block_template' ) ) {
205            $checkout_template = get_block_template( 'woocommerce/woocommerce//page-checkout' );
206            $cart_template     = get_block_template( 'woocommerce/woocommerce//page-cart' );
207
208            if ( ! $checkout_template ) {
209                $checkout_template = get_block_template( 'woocommerce/woocommerce//checkout' );
210            }
211
212            if ( ! $cart_template ) {
213                $cart_template = get_block_template( 'woocommerce/woocommerce//cart' );
214            }
215        }
216
217        if ( ! empty( $checkout_template->content ) ) {
218            // Checkout template is in use, but we need to see if the page-content-wrapper is in use, or if the template is being used directly.
219            $this->checkout_content_source = $checkout_template->content;
220            $is_using_page_content         = str_contains( $checkout_template->content, '<!-- wp:woocommerce/page-content-wrapper {"page":"checkout"}' );
221
222            if ( $is_using_page_content ) {
223                // The page-content-wrapper is in use, so we need to get the page content.
224                $checkout_page = get_post( wc_get_page_id( 'checkout' ) );
225
226                if ( $checkout_page && isset( $checkout_page->post_content ) ) {
227                    $this->checkout_content_source = $checkout_page->post_content;
228                }
229            }
230        }
231
232        if ( ! empty( $cart_template->content ) ) {
233            // Cart template is in use, but we need to see if the page-content-wrapper is in use, or if the template is being used directly.
234            $this->cart_content_source = $cart_template->content;
235            $is_using_page_content     = str_contains( $cart_template->content, '<!-- wp:woocommerce/page-content-wrapper {"page":"cart"}' );
236
237            if ( $is_using_page_content ) {
238                // The page-content-wrapper is in use, so we need to get the page content.
239                $cart_page = get_post( wc_get_page_id( 'cart' ) );
240
241                if ( $cart_page && isset( $cart_page->post_content ) ) {
242                    $this->cart_content_source = $cart_page->post_content;
243                }
244            }
245        }
246
247        set_transient(
248            $cart_checkout_content_cache_transient_name,
249            array(
250                'cart_content_source'     => $this->cart_content_source,
251                'checkout_content_source' => $this->checkout_content_source,
252            ),
253            DAY_IN_SECONDS
254        );
255    }
256
257    /**
258     * Default event properties which should be included with all events.
259     *
260     * @deprecated 13.3
261     *
262     * @return array Array of standard event props.
263     */
264    public function get_common_properties() {
265        $site_info          = array(
266            'blog_id'                            => Jetpack::get_option( 'id' ),
267            'ui'                                 => $this->get_user_id(),
268            'url'                                => home_url(),
269            'woo_version'                        => WC()->version,
270            'store_admin'                        => in_array( array( 'administrator', 'shop_manager' ), wp_get_current_user()->roles, true ) ? 1 : 0,
271            'device'                             => wp_is_mobile() ? 'mobile' : 'desktop',
272            'template_used'                      => $this->cart_checkout_templates_in_use ? '1' : '0',
273            'additional_blocks_on_cart_page'     => $this->additional_blocks_on_cart_page,
274            'additional_blocks_on_checkout_page' => $this->additional_blocks_on_checkout_page,
275            'store_currency'                     => get_woocommerce_currency(),
276        );
277        $cart_checkout_info = $this->get_cart_checkout_info();
278        return array_merge( $site_info, $cart_checkout_info );
279    }
280
281        /**
282         * Record an event with optional product and custom properties.
283         *
284         * @deprecated 13.3
285         *
286         * @param string  $event_name The name of the event to record.
287         * @param array   $properties Optional array of (key => value) event properties.
288         * @param integer $product_id The id of the product relating to the event.
289         *
290         * @return string|void
291         */
292    public function record_event( $event_name, $properties = array(), $product_id = null ) {
293        $js = $this->process_event_properties( $event_name, $properties, $product_id );
294        wc_enqueue_js( "_wca.push({$js});" );
295    }
296
297        /**
298         * Gather relevant product information
299         *
300         * @deprecated 13.3
301         *
302         * @param \WC_Product $product product.
303         * @return array
304         */
305    public function get_product_details( $product ) {
306        return array(
307            'pi' => $product->get_id(),
308            'pn' => $product->get_title(),
309            'pc' => $this->get_product_categories_concatenated( $product ),
310            'pp' => $product->get_price(),
311            'pt' => $product->get_type(),
312        );
313    }
314
315        /**
316         * Gets product categories or varation attributes as a formatted concatenated string
317         *
318         * @deprecated 13.3
319         *
320         * @param object $product WC_Product.
321         * @return string
322         */
323    public function get_product_categories_concatenated( $product ) {
324
325        if ( ! $product instanceof WC_Product ) {
326            return '';
327        }
328
329        $variation_data = $product->is_type( 'variation' ) ? wc_get_product_variation_attributes( $product->get_id() ) : '';
330        if ( is_array( $variation_data ) && ! empty( $variation_data ) ) {
331            $line = wc_get_formatted_variation( $variation_data, true );
332        } else {
333            $out        = array();
334            $categories = get_the_terms( $product->get_id(), 'product_cat' );
335            if ( $categories ) {
336                foreach ( $categories as $category ) {
337                    $out[] = $category->name;
338                }
339            }
340            $line = implode( '/', $out );
341        }
342        return $line;
343    }
344
345    /**
346     * Compose event properties.
347     *
348     * @deprecated 13.3
349     *
350     * @param string  $event_name The name of the event to record.
351     * @param array   $properties Optional array of (key => value) event properties.
352     * @param integer $product_id Optional id of the product relating to the event.
353     *
354     * @return string|void
355     */
356    public function process_event_properties( $event_name, $properties = array(), $product_id = null ) {
357
358        // Only set product details if we have a product id.
359        if ( $product_id ) {
360            $product = wc_get_product( $product_id );
361            if ( ! $product instanceof WC_Product ) {
362                return;
363            }
364            $product_details = $this->get_product_details( $product );
365        }
366
367        /**
368         * Allow defining custom event properties in WooCommerce Analytics.
369         *
370         * @module woocommerce-analytics
371         *
372         * @since 12.5
373         *
374         * @param array $all_props Array of event props to be filtered.
375         */
376        $all_props = apply_filters(
377            'jetpack_woocommerce_analytics_event_props',
378            array_merge(
379                $this->get_common_properties(), // We put this here to allow override of common props.
380                $properties
381            )
382        );
383
384        if ( isset( $product_details ) ) {
385                $all_props = array_merge( $all_props, $product_details );
386        }
387
388        return wp_json_encode(
389            array_merge(
390                array( '_en' => $event_name ),
391                $all_props
392            ),
393            JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP
394        );
395    }
396
397    /**
398     * Get the current user id
399     *
400     * @deprecated 13.3
401     *
402     * @return int
403     */
404    public function get_user_id() {
405        if ( is_user_logged_in() ) {
406            $blogid = Jetpack::get_option( 'id' );
407            $userid = get_current_user_id();
408            return $blogid . ':' . $userid;
409        }
410        return 'null';
411    }
412
413    /**
414     * Gets the IDs of additional blocks on the Cart/Checkout pages or templates.
415     *
416     * @deprecated 13.3
417     *
418     * @param string $cart_or_checkout Whether to get blocks on the cart or checkout page.
419     * @return array All inner blocks on the page.
420     */
421    public function get_additional_blocks_on_page( $cart_or_checkout = 'cart' ) {
422
423        $additional_blocks_on_page_transient_name = 'jetpack_woocommerce_analytics_additional_blocks_on_' . $cart_or_checkout . '_page';
424        $additional_blocks_on_page                = get_transient( $additional_blocks_on_page_transient_name );
425
426        if ( false !== $additional_blocks_on_page ) {
427            return $additional_blocks_on_page;
428        }
429
430        $content = $this->cart_content_source;
431
432        if ( 'checkout' === $cart_or_checkout ) {
433            $content = $this->checkout_content_source;
434        }
435
436        $parsed_blocks = parse_blocks( $content );
437        $other_blocks  = array_filter(
438            $parsed_blocks,
439            function ( $block ) use ( $cart_or_checkout ) {
440                if ( ! isset( $block['blockName'] ) ) {
441                    return false;
442                }
443
444                if ( 'woocommerce/classic-shortcode' === $block['blockName'] ) {
445                    return false;
446                }
447
448                if ( 'core/shortcode' === $block['blockName'] ) {
449                    return false;
450                }
451
452                if ( 'checkout' === $cart_or_checkout && 'woocommerce/checkout' !== $block['blockName'] ) {
453                    return true;
454                }
455
456                if ( 'cart' === $cart_or_checkout && 'woocommerce/cart' !== $block['blockName'] ) {
457                    return true;
458                }
459
460                return false;
461            }
462        );
463
464        $all_inner_blocks = array();
465
466        // Loop over each "block group". In templates the blocks are grouped up.
467        foreach ( $other_blocks as $block ) {
468
469            // This check is necessary because sometimes this is null when using templates.
470            if ( ! empty( $block['blockName'] ) ) {
471                $all_inner_blocks[] = $block['blockName'];
472            }
473
474            if ( ! isset( $block['innerBlocks'] ) || ! is_array( $block['innerBlocks'] ) || 0 === count( $block['innerBlocks'] ) ) {
475                continue;
476            }
477
478            foreach ( $block['innerBlocks'] as $inner_content ) {
479                $all_inner_blocks = array_merge( $all_inner_blocks, $this->get_inner_blocks( $inner_content ) );
480            }
481        }
482        set_transient( $additional_blocks_on_page_transient_name, $all_inner_blocks, DAY_IN_SECONDS );
483        return $all_inner_blocks;
484    }
485
486    /**
487     * Gets an array containing the block or shortcode use properties for the Cart page.
488     *
489     * @deprecated 13.3
490     *
491     * @return array            An array containing the block or shortcode use properties for the Cart page.
492     */
493    public function get_cart_page_block_usage() {
494        $new_info = array();
495
496        $content                    = $this->cart_content_source;
497        $block_presence             = str_contains( $content, '<!-- wp:woocommerce/cart' );
498        $shortcode_presence         = str_contains( $content, '[woocommerce_cart]' );
499        $classic_shortcode_presence = str_contains( $content, '<!-- wp:woocommerce/classic-shortcode' );
500
501        $new_info['cart_page_contains_cart_block']     = $block_presence ? '1' : '0';
502        $new_info['cart_page_contains_cart_shortcode'] = $shortcode_presence || $classic_shortcode_presence ? '1' : '0';
503        return $new_info;
504    }
505
506    /**
507     * Gets an array containing the block or shortcode use properties for the Checkout page.
508     *
509     * @deprecated 13.3
510     *
511     * @return array                An array containing the block or shortcode use properties for the Checkout page.
512     */
513    public function get_checkout_page_block_usage() {
514        $new_info = array();
515
516        $content                    = $this->checkout_content_source;
517        $block_presence             = str_contains( $content, '<!-- wp:woocommerce/checkout' );
518        $shortcode_presence         = str_contains( $content, '[woocommerce_checkout]' );
519        $classic_shortcode_presence = str_contains( $content, '<!-- wp:woocommerce/classic-shortcode' );
520
521        $new_info['checkout_page_contains_checkout_block']     = $block_presence ? '1' : '0';
522        $new_info['checkout_page_contains_checkout_shortcode'] = $shortcode_presence || $classic_shortcode_presence ? '1' : '0';
523        return $new_info;
524    }
525
526    /**
527     * Get info about the cart & checkout pages, in particular
528     * whether the store is using shortcodes or Gutenberg blocks.
529     * This info is cached in a transient.
530     *
531     * Note: similar code is in a WooCommerce core PR:
532     * https://github.com/woocommerce/woocommerce/pull/25932
533     *
534     * @deprecated 13.3
535     *
536     * @return array
537     */
538    public function get_cart_checkout_info() {
539        $info = array_merge(
540            $this->get_cart_page_block_usage(),
541            $this->get_checkout_page_block_usage()
542        );
543        return $info;
544    }
545
546        /**
547         * Search a specific post for text content.
548         *
549         * Note: similar code is in a WooCommerce core PR:
550         * https://github.com/woocommerce/woocommerce/pull/25932
551         *
552         * @deprecated 13.3
553         *
554         * @param integer $post_id The id of the post to search.
555         * @param string  $text    The text to search for.
556         * @return integer 1 if post contains $text (otherwise 0).
557         */
558    public function post_contains_text( $post_id, $text ) {
559        global $wpdb;
560
561        // Search for the text anywhere in the post.
562        $wildcarded = "%{$text}%";
563
564        // No better way to search post content without having filters expanding blocks.
565        // This is already cached up in the parent function.
566        $result = $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
567            $wpdb->prepare(
568                "
569                SELECT COUNT( * ) FROM {$wpdb->prefix}posts
570                WHERE ID=%d
571                AND {$wpdb->prefix}posts.post_content LIKE %s
572                ",
573                array( $post_id, $wildcarded )
574            )
575        );
576
577        return ( '0' !== $result ) ? 1 : 0;
578    }
579}