PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-beta
Jetpack – WP Security, Backup, Speed, & Growth v16.3-beta
16.3-beta 16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 All 507 releases
← All changes | jetpack_vendor/automattic/woocommerce-analytics/src/class-universal.php +439 -238 13.5.2 → 16.3-beta View file →
@@ -6,14 +6,14 @@
6 6 */
7 7
8 8 namespace Automattic\Woocommerce_Analytics;
9 9
10 +use Automattic\Jetpack\Constants;
10 11 use WC_Order;
11 -use WC_Payment_Gateway;
12 12 use WC_Product;
13 13
14 14 /**
15 - * Filters and Actions added to Store pages to perform analytics
15 + * Filters and Actions added to Store pages to perform analytics.
16 16 */
17 17 class Universal {
18 18 /**
19 19 * Trait to handle common analytics functions.
@@ -27,17 +27,15 @@
27 27 $this->find_cart_checkout_content_sources();
28 28 $this->additional_blocks_on_cart_page = $this->get_additional_blocks_on_page( 'cart' );
29 29 $this->additional_blocks_on_checkout_page = $this->get_additional_blocks_on_page( 'checkout' );
30 30
31 - // add to carts from non-product pages or lists -- search, store etc.
32 - add_action( 'wp_head', array( $this, 'loop_session_events' ), 2 );
31 + // Capture search
32 + add_action( 'template_redirect', array( $this, 'capture_search_query' ), 11 );
33 33
34 34 // Capture cart events.
35 35 add_action( 'woocommerce_add_to_cart', array( $this, 'capture_add_to_cart' ), 10, 6 );
36 -
37 - add_action( 'woocommerce_after_cart', array( $this, 'remove_from_cart' ) );
38 - add_action( 'woocommerce_after_mini_cart', array( $this, 'remove_from_cart' ) );
39 - add_action( 'wcct_before_cart_widget', array( $this, 'remove_from_cart' ) );
36 + add_action( 'woocommerce_after_cart_item_quantity_update', array( $this, 'capture_cart_quantity_update' ), 10, 4 );
37 + add_action( 'woocommerce_cart_item_removed', array( $this, 'capture_remove_from_cart' ), 10, 2 );
40 38 add_filter( 'woocommerce_cart_item_remove_link', array( $this, 'remove_from_cart_attributes' ), 10, 2 );
41 39
42 40 // Checkout.
43 41 // Send events after checkout template (shortcode).
@@ -44,67 +42,159 @@
44 42 add_action( 'woocommerce_after_checkout_form', array( $this, 'checkout_process' ) );
45 43 // Send events after checkout block.
46 44 add_action( 'woocommerce_blocks_enqueue_checkout_block_scripts_after', array( $this, 'checkout_process' ) );
47 45
48 - // order confirmed.
49 - add_action( 'woocommerce_thankyou', array( $this, 'order_process' ), 10, 1 );
50 - add_action( 'woocommerce_after_cart', array( $this, 'remove_from_cart_via_quantity' ), 10, 1 );
46 + // order processed.
47 + add_action( 'woocommerce_checkout_order_processed', array( $this, 'order_process' ), 10, 1 );
48 + add_action( 'woocommerce_store_api_checkout_order_processed', array( $this, 'order_process' ), 10, 1 );
51 49
52 50 add_filter( 'woocommerce_checkout_posted_data', array( $this, 'save_checkout_post_data' ), 10, 1 );
53 51
54 52 add_action( 'woocommerce_created_customer', array( $this, 'capture_created_customer' ), 10, 2 );
53 +
54 + add_action( 'woocommerce_created_customer', array( $this, 'capture_post_checkout_created_customer' ), 10, 2 );
55 +
56 + // single product page view.
57 + add_action( 'woocommerce_after_single_product', array( $this, 'capture_product_view' ) );
58 +
59 + // order confirmed page view
60 + add_action( 'woocommerce_thankyou', array( $this, 'capture_order_confirmation_view' ), 10, 1 );
61 +
62 + // checkout page view
63 + add_action( 'wp_footer', array( $this, 'capture_checkout_view' ), 11 );
64 +
65 + // cart page view
66 + add_action( 'wp_footer', array( $this, 'capture_cart_view' ), 11 );
67 +
68 + // Enqueue events to track.
69 + add_action( 'wp_footer', array( $this, 'inject_analytics_data' ), 999 );
55 70 }
56 71
57 72 /**
58 - * On product lists or other non-product pages, add an event listener to "Add to Cart" button click
73 + * Inject analytics data into the window object
59 74 */
60 - public function loop_session_events() {
61 - // Check for previous events queued in session data.
62 - if ( is_object( WC()->session ) ) {
63 - $data = WC()->session->get( 'wca_session_data' );
64 - if ( ! empty( $data ) ) {
65 - foreach ( $data as $data_instance ) {
66 - $this->record_event(
67 - $data_instance['event'],
68 - array(
69 - 'pq' => $data_instance['quantity'],
70 - ),
71 - $data_instance['product_id']
72 - );
75 + public function inject_analytics_data() {
76 + $is_clickhouse_enabled = Features::is_clickhouse_enabled();
77 + $is_proxy_tracking_enabled = Features::is_proxy_tracking_enabled();
78 + // When proxy tracking is enabled, we don't need to send the common properties to the client.
79 + // Otherwise send only the page-safe properties: this markup is cacheable, so nothing
80 + // derived from the current request may go into it.
81 + $common_properties = $is_proxy_tracking_enabled ? array() : $this->get_page_common_properties();
82 + ?>
83 + <script type="text/javascript">
84 + (function() {
85 + // Always start from a fresh object: a `||` fallback would keep a same-named DOM element alive.
86 + window.wcAnalytics = {};
87 + const wcAnalytics = window.wcAnalytics;
88 +
89 + // Set the assets URL for webpack to find the split assets.
90 + wcAnalytics.assets_url = '<?php echo esc_url( plugins_url( '../build/', __DIR__ . '/class-woocommerce-analytics.php' ) ); ?>';
91 +
92 + // Set the REST API tracking endpoint URL.
93 + <?php
94 + $track_endpoint = rest_url( 'woocommerce-analytics/v1/track' );
95 + // Include the WP REST nonce for logged-in users so WordPress can identify
96 + // the current user via cookie auth. This is needed for the store_admin
97 + // property detection, not for endpoint authorization.
98 + if ( is_user_logged_in() ) {
99 + $track_endpoint = add_query_arg( '_wpnonce', wp_create_nonce( 'wp_rest' ), $track_endpoint );
73 100 }
74 - // Clear data, now that these events have been recorded.
75 - WC()->session->set( 'wca_session_data', '' );
76 - }
101 + ?>
102 + wcAnalytics.trackEndpoint = <?php echo wp_json_encode( esc_url_raw( $track_endpoint ), JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ); ?>;
103 +
104 + // Set common properties for all events.
105 + wcAnalytics.commonProps = <?php echo wp_json_encode( $common_properties, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ); ?>;
106 +
107 + // Set the event queue.
108 + wcAnalytics.eventQueue = <?php echo wp_json_encode( WC_Analytics_Tracking::get_event_queue(), JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ); ?>;
109 +
110 + // Features.
111 + wcAnalytics.features = {
112 + ch: <?php echo $is_clickhouse_enabled ? 'true' : 'false'; ?>,
113 + sessionTracking: <?php echo $is_clickhouse_enabled ? 'true' : 'false'; ?>,
114 + proxy: <?php echo $is_proxy_tracking_enabled ? 'true' : 'false'; ?>,
115 + };
116 +
117 + wcAnalytics.breadcrumbs = <?php echo wp_json_encode( $this->get_breadcrumb_titles(), JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ); ?>;
118 +
119 + // Page context flags.
120 + wcAnalytics.pages = {
121 + isAccountPage: <?php echo is_account_page() ? 'true' : 'false'; ?>,
122 + isCart: <?php echo is_cart() ? 'true' : 'false'; ?>,
123 + };
124 + })();
125 + </script>
126 + <?php
127 + }
128 +
129 + /**
130 + * Capture remove from cart events in mini-cart and cart blocks
131 + *
132 + * @param string $cart_item_key The cart item removed.
133 + * @param \WC_Cart $cart The cart.
134 + *
135 + * @return void
136 + */
137 + public function capture_remove_from_cart( $cart_item_key, $cart ) {
138 + $item = $cart->removed_cart_contents[ $cart_item_key ] ?? null;
139 +
140 + if ( ! is_array( $item ) || ! isset( $item['product_id'], $item['quantity'] ) ) {
141 + return;
77 142 }
143 +
144 + WC_Analytics_Tracking::record_event(
145 + 'remove_from_cart',
146 + $this->get_cart_checkout_event_properties(
147 + array(
148 + 'pi' => (int) $item['product_id'],
149 + 'pq' => (int) $item['quantity'],
150 + )
151 + )
152 + );
78 153 }
79 154
80 155 /**
81 - * On the cart page, add an event listener for removal of product click
156 + * Capture remove/add from cart events using the Cart Controller
157 + *
158 + * @param string $cart_item_key The cart item updated.
159 + * @param int $quantity Contains the new quantity of the item.
160 + * @param int $old_quantity Contains the old quantity of the item.
161 + * @param \WC_Cart $cart The cart.
162 + *
163 + * @return void
82 164 */
83 - public function remove_from_cart() {
84 - $common_props = $this->render_properties_as_js(
85 - $this->get_common_properties()
86 - );
165 + public function capture_cart_quantity_update( $cart_item_key, $quantity, $old_quantity, $cart ) {
166 + if ( ! isset( $cart->cart_contents[ $cart_item_key ]['product_id'] ) ) {
167 + return;
168 + }
87 169
88 - // We listen at div.woocommerce because the cart 'form' contents get forcibly
89 - // updated and subsequent removals from cart would then not have this click
90 - // handler attached.
91 - wc_enqueue_js(
92 - "jQuery( 'div.woocommerce' ).on( 'click', 'a.remove', function() {
93 - var productID = jQuery( this ).data( 'product_id' );
94 - var quantity = jQuery( this ).parent().parent().find( '.qty' ).val()
95 - var productDetails = {
96 - 'id': productID,
97 - 'quantity': quantity ? quantity : '1',
98 - };
99 - _wca.push( {
100 - '_en': 'woocommerceanalytics_remove_from_cart',
101 - 'pi': productDetails.id,
102 - 'pq': productDetails.quantity, " .
103 - $common_props . '
104 - } );
105 - } );'
106 - );
170 + $product_id = $cart->cart_contents[ $cart_item_key ]['product_id'];
171 + if ( $quantity > $old_quantity ) {
172 + WC_Analytics_Tracking::record_event(
173 + 'add_to_cart',
174 + $this->get_cart_checkout_event_properties(
175 + array(
176 + 'pi' => $product_id,
177 + 'pq' => $quantity,
178 + )
179 + )
180 + );
181 + $this->lock_add_to_cart_events = true;
182 + return;
183 + }
184 +
185 + if ( $quantity < $old_quantity ) {
186 + WC_Analytics_Tracking::record_event(
187 + 'remove_from_cart',
188 + $this->get_cart_checkout_event_properties(
189 + array(
190 + 'pi' => $product_id,
191 + 'pq' => $quantity,
192 + )
193 + )
194 + );
195 + return;
196 + }
107 197 }
108 198
109 199 /**
110 200 * Adds the product ID to the remove product link (for use by remove_from_cart above) if not present
@@ -179,35 +269,21 @@
179 269 * On the Checkout page, trigger an event for each product in the cart
180 270 */
181 271 public function checkout_process() {
182 272 global $post;
183 - $checkout_page_id = wc_get_page_id( 'checkout' );
184 - $cart = WC()->cart->get_cart();
185 -
186 - $enabled_payment_options = array_filter(
187 - WC()->payment_gateways->get_available_payment_gateways(),
188 - function ( $payment_gateway ) {
189 - if ( ! $payment_gateway instanceof WC_Payment_Gateway ) {
190 - return false;
191 - }
192 -
193 - return $payment_gateway->is_available();
194 - }
195 - );
196 -
197 - $enabled_payment_options = array_keys( $enabled_payment_options );
198 -
199 - $is_in_checkout_page = $checkout_page_id === $post->ID ? 'Yes' : 'No';
273 + $checkout_page_id = wc_get_page_id( 'checkout' );
274 + $cart = WC()->cart->get_cart();
275 + $is_in_checkout_page = isset( $post->ID ) && $checkout_page_id === $post->ID ? 'Yes' : 'No';
200 276 $session = WC()->session;
201 277 if ( is_object( $session ) ) {
202 - $session->set( 'checkout_page_used', true );
278 + $session->set( 'checkout_page_used', 'Yes' === $is_in_checkout_page );
203 279 $session->save_data();
204 280 }
205 281
206 282 foreach ( $cart as $cart_item_key => $cart_item ) {
207 283 /**
208 - * This filter is already documented in woocommerce/templates/cart/cart.php
209 - */
284 + * This filter is already documented in woocommerce/templates/cart/cart.php
285 + */
210 286 $product = apply_filters( 'woocommerce_cart_item_product', $cart_item['data'], $cart_item, $cart_item_key );
211 287
212 288 if ( ! $product || ! $product instanceof WC_Product ) {
213 289 continue;
@@ -225,83 +301,21 @@
225 301 unset( $data['shipping_options_count'] );
226 302 }
227 303
228 304 $data['pq'] = $cart_item['quantity'];
229 -
230 - $properties = $this->process_event_properties(
231 - 'woocommerceanalytics_product_checkout',
232 - $data,
233 - $product->get_id()
234 - );
235 -
236 - wc_enqueue_js(
237 - "
238 - var cartItem_{$cart_item_key}_logged = false;
239 - var properties = {$properties};
240 - // Check if jQuery is available
241 - if ( typeof jQuery !== 'undefined' ) {
242 - // This is only triggered on the checkout shortcode.
243 - jQuery( document.body ).on( 'init_checkout', function () {
244 - if ( true === cartItem_{$cart_item_key}_logged ) {
245 - return;
246 - }
247 - if ( typeof wp !== 'undefined' && typeof wp.hooks !== 'undefined' && typeof wp.hooks.addAction === 'function' ) {
248 - wp.hooks.addAction( 'wcpay.payment-request.availability', 'wcpay', function ( args ) {
249 - properties.express_checkout = args.paymentRequestType;
250 - } );
251 - }
252 - properties.checkout_page_contains_checkout_block = '0';
253 - properties.checkout_page_contains_checkout_shortcode = '1';
254 -
255 - _wca.push( properties );
256 - cartItem_{$cart_item_key}_logged = true;
257 -
258 - } );
259 - }
260 -
261 - if (
262 - typeof wp !== 'undefined' &&
263 - typeof wp.data !== 'undefined' &&
264 - typeof wp.data.subscribe !== 'undefined'
265 - ) {
266 - wp.data.subscribe( function () {
267 - if ( true === cartItem_{$cart_item_key}_logged ) {
268 - return;
269 - }
270 -
271 - const checkoutDataStore = wp.data.select( 'wc/store/checkout' );
272 - // Ensures we're not in Cart, but in Checkout page.
273 - if (
274 - typeof checkoutDataStore !== 'undefined' &&
275 - checkoutDataStore.getOrderId() !== 0
276 - ) {
277 - properties.express_checkout = Object.keys( wc.wcBlocksRegistry.getExpressPaymentMethods() );
278 - properties.checkout_page_contains_checkout_block = '1';
279 - properties.checkout_page_contains_checkout_shortcode = '0';
280 -
281 - _wca.push( properties );
282 - cartItem_{$cart_item_key}_logged = true;
283 - }
284 - } );
285 - }
286 - "
287 - );
288 -
305 + $this->enqueue_event( 'product_checkout', $this->get_cart_checkout_event_properties( $data ), $product->get_id() );
289 306 }
290 307 }
291 308
292 309 /**
293 - * After the checkout process, fire an event for each item in the order
310 + * After the order processed, fire an event for each item in the order
294 311 *
295 - * @param string $order_id Order Id.
312 + * @param int|string|WC_Order $order_id_or_order Order Id or Order object.
296 313 */
297 - public function order_process( $order_id ) {
298 - $order = wc_get_order( $order_id );
314 + public function order_process( $order_id_or_order ) {
315 + $order = wc_get_order( $order_id_or_order );
299 316
300 - if (
301 - ! $order
302 - || ! $order instanceof WC_Order
303 - ) {
317 + if ( ! $order ) {
304 318 return;
305 319 }
306 320
307 321 $payment_option = $order->get_payment_method();
@@ -314,8 +328,10 @@
314 328 $create_account = 'No';
315 329 $checkout_page_used = 'No';
316 330 }
317 331
332 + $delayed_account_creation = ucfirst( get_option( 'woocommerce_enable_delayed_account_creation', 'Yes' ) );
333 +
318 334 $guest_checkout = $order->get_user() ? 'No' : 'Yes';
319 335
320 336 $express_checkout = 'null';
321 337 // When the payment option is woocommerce_payment
@@ -328,17 +344,15 @@
328 344 $express_checkout = array( 'apple_pay' );
329 345 }
330 346 }
331 347
332 - $checkout_page_contains_checkout_block = '0';
333 - $checkout_page_contains_checkout_shortcode = '0';
348 + $checkout_page_contains_checkout_block = '0';
349 + $checkout_page_contains_checkout_shortcode = '0';
334 350
335 351 $order_source = $order->get_created_via();
336 352 if ( 'store-api' === $order_source ) {
337 - $checkout_page_contains_checkout_block = '1';
338 - $checkout_page_contains_checkout_shortcode = '0';
353 + $checkout_page_contains_checkout_block = '1';
339 354 } elseif ( 'checkout' === $order_source ) {
340 - $checkout_page_contains_checkout_block = '0';
341 355 $checkout_page_contains_checkout_shortcode = '1';
342 356 }
343 357
344 358 // loop through products in the order and queue a purchase event.
@@ -355,58 +369,37 @@
355 369 $order_coupons_count = 0;
356 370 if ( is_array( $order_coupons ) ) {
357 371 $order_coupons_count = count( $order_coupons );
358 372 }
359 - $this->record_event(
360 - 'woocommerceanalytics_product_purchase',
361 - array(
362 - 'oi' => $order->get_order_number(),
363 - 'pq' => $order_item->get_quantity(),
364 - 'payment_option' => $payment_option,
365 - 'create_account' => $create_account,
366 - 'guest_checkout' => $guest_checkout,
367 - 'express_checkout' => $express_checkout,
368 - 'products_count' => $order_items_count,
369 - 'coupon_used' => $order_coupons_count,
370 - 'order_value' => $order->get_total(),
371 - 'from_checkout' => $checkout_page_used,
372 - 'checkout_page_contains_checkout_block' => $checkout_page_contains_checkout_block,
373 - 'checkout_page_contains_checkout_shortcode' => $checkout_page_contains_checkout_shortcode,
374 - ),
375 - $product_id
373 +
374 + WC_Analytics_Tracking::record_event(
375 + 'product_purchase',
376 + $this->get_cart_checkout_event_properties(
377 + array(
378 + 'oi' => $order->get_order_number(),
379 + 'pi' => $product_id,
380 + 'pq' => $order_item->get_quantity(),
381 + 'payment_option' => $payment_option,
382 + 'create_account' => $create_account,
383 + 'guest_checkout' => $guest_checkout,
384 + 'delayed_account_creation' => $delayed_account_creation,
385 + 'express_checkout' => $express_checkout,
386 + 'coupon_used' => $order_coupons_count,
387 + 'products_count' => $order_items_count,
388 + 'order_value' => $order->get_subtotal(),
389 + 'order_total' => $order->get_total(),
390 + 'total_discount' => $order->get_discount_total(),
391 + 'total_taxes' => $order->get_total_tax(),
392 + 'total_shipping' => $order->get_shipping_total(),
393 + 'from_checkout' => $checkout_page_used,
394 + 'checkout_page_contains_checkout_block' => $checkout_page_contains_checkout_block,
395 + 'checkout_page_contains_checkout_shortcode' => $checkout_page_contains_checkout_shortcode,
396 + )
397 + )
376 398 );
377 399 }
378 400 }
379 -
380 401 /**
381 - * Listen for clicks on the "Update Cart" button to know if an item has been removed by
382 - * updating its quantity to zero
383 - */
384 - public function remove_from_cart_via_quantity() {
385 - $common_props = $this->render_properties_as_js(
386 - $this->get_common_properties()
387 - );
388 -
389 - wc_enqueue_js(
390 - "
391 - jQuery( 'button[name=update_cart]' ).on( 'click', function() {
392 - var cartItems = jQuery( '.cart_item' );
393 - cartItems.each( function( item ) {
394 - var qty = jQuery( this ).find( 'input.qty' );
395 - if ( qty && qty.val() === '0' ) {
396 - var productID = jQuery( this ).find( '.product-remove a' ).data( 'product_id' );
397 - _wca.push( {
398 - '_en': 'woocommerceanalytics_remove_from_cart',
399 - 'pi': productID, " .
400 - $common_props . '
401 - } );
402 - }
403 - } );
404 - } );'
405 - );
406 - }
407 -
408 - /**
409 402 * Gets the inner blocks of a block.
410 403 *
411 404 * @param array $inner_blocks The inner blocks.
412 405 *
@@ -433,72 +426,64 @@
433 426 * @param array $variation Variation attributes..
434 427 * @param array $cart_item_data Other cart data.
435 428 */
436 429 public function capture_add_to_cart( $cart_item_key, $product_id, $quantity, $variation_id, $variation, $cart_item_data ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
437 - $referer_postid = isset( $_SERVER['HTTP_REFERER'] ) ? url_to_postid( esc_url_raw( wp_unslash( $_SERVER['HTTP_REFERER'] ) ) ) : 0;
438 - // if the referring post is not a product OR the product being added is not the same as post.
439 - // (eg. related product list on single product page) then include a product view event.
440 - $product_by_referer_postid = wc_get_product( $referer_postid );
441 - if ( ! $product_by_referer_postid instanceof WC_Product || (int) $product_id !== $referer_postid ) {
442 - $this->capture_event_in_session_data( $product_id, $quantity, 'woocommerceanalytics_product_view' );
430 + if ( $this->lock_add_to_cart_events ) {
431 + return;
443 432 }
444 - // add cart event to the session data.
445 - $this->capture_event_in_session_data( $product_id, $quantity, 'woocommerceanalytics_add_to_cart' );
433 + WC_Analytics_Tracking::record_event(
434 + 'add_to_cart',
435 + $this->get_cart_checkout_event_properties(
436 + array(
437 + 'pi' => $product_id,
438 + 'pq' => $quantity,
439 + )
440 + )
441 + );
446 442 }
447 443
448 444 /**
449 - * Track in-session data.
445 + * Get the event properties for the cart and checkout events.
450 446 *
451 - * @param int $product_id Product ID.
452 - * @param int $quantity Quantity.
453 - * @param string $event Fired event.
447 + * @param array $event_properties Event properties.
454 448 */
455 - public function capture_event_in_session_data( $product_id, $quantity, $event ) {
456 -
457 - $product = wc_get_product( $product_id );
458 - if ( ! $product instanceof WC_Product ) {
459 - return;
449 + public function get_cart_checkout_event_properties( $event_properties = array() ) {
450 + if ( isset( $event_properties['pq'] ) ) {
451 + $event_properties['pq'] = 0 === $event_properties['pq'] ? 1 : $event_properties['pq'];
452 + $event_properties['pq'] = (string) $event_properties['pq'];
460 453 }
454 + $product = isset( $event_properties['pi'] ) ? wc_get_product( $event_properties['pi'] ) : null;
455 + $product_details = $product instanceof WC_Product ? $this->get_product_details( $product ) : array();
461 456
462 - $quantity = ( 0 === $quantity ) ? 1 : $quantity;
463 -
464 - // check for existing data.
465 - if ( is_object( WC()->session ) ) {
466 - $data = WC()->session->get( 'wca_session_data' );
467 - if ( empty( $data ) || ! is_array( $data ) ) {
468 - $data = array();
469 - }
470 - } else {
471 - $data = array();
472 - }
473 -
474 - // extract new event data.
475 - $new_data = array(
476 - 'event' => $event,
477 - 'product_id' => (string) $product_id,
478 - 'quantity' => (string) $quantity,
457 + $checkout_cart_details = array(
458 + 'template_used' => $this->cart_checkout_templates_in_use ? '1' : '0',
459 + 'additional_blocks_on_cart_page' => $this->additional_blocks_on_cart_page,
460 + 'additional_blocks_on_checkout_page' => $this->additional_blocks_on_checkout_page,
461 + 'order_value' => $this->get_cart_subtotal(),
462 + 'order_total' => $this->get_cart_total(),
463 + 'total_tax' => $this->get_cart_taxes(),
464 + 'total_discount' => $this->get_total_discounts(),
465 + 'total_shipping' => $this->get_cart_shipping_total(),
466 + 'products_count' => $this->get_cart_items_count(),
479 467 );
468 + $cart_checkout_info = $this->get_cart_checkout_info();
480 469
481 - // append new data.
482 - $data[] = $new_data;
470 + $event_properties = array_merge( $product_details, $checkout_cart_details, $cart_checkout_info, $event_properties ); // event properties should be last to allow for overrides
483 471
484 - WC()->session->set( 'wca_session_data', $data );
472 + return $event_properties;
485 473 }
486 474
487 475 /**
488 - * Save createaccount post data to be used in $this->order_process.
476 + * Save create account post data to be used in $this->order_process.
489 477 *
490 - * @param array $data post data from the checkout page.
478 + * @param array|null $data Post data from the checkout page.
491 479 *
492 - * @return array
480 + * @return array|null
493 481 */
494 - public function save_checkout_post_data( array $data ) {
495 - $session = WC()->session;
496 - if ( is_object( $session ) ) {
497 - if ( isset( $data['createaccount'] ) && ! empty( $data['createaccount'] ) ) {
498 - $session->set( 'wc_checkout_createaccount_used', true );
499 - $session->save_data();
500 - }
482 + public function save_checkout_post_data( ?array $data ) {
483 + if ( is_object( WC()->session ) && ! empty( $data['createaccount'] ) ) {
484 + WC()->session->set( 'wc_checkout_createaccount_used', true );
485 + WC()->session->save_data();
501 486 }
502 487 return $data;
503 488 }
504 489
@@ -519,6 +504,222 @@
519 504 $session->set( 'wc_checkout_createaccount_used', true );
520 505 $session->save_data();
521 506 }
522 507 }
508 + }
509 +
510 + /**
511 + * Capture the post checkout create account event.
512 + *
513 + * @param int $customer_id Customer ID.
514 + * @param array $new_customer_data New customer data.
515 + */
516 + public function capture_post_checkout_created_customer( $customer_id, $new_customer_data ) {
517 + if (
518 + is_array( $new_customer_data )
519 + && ! empty( $new_customer_data['source'] )
520 + && str_contains( $new_customer_data['source'], 'delayed-account-creation' )
521 + ) {
522 +
523 + $checkout_page_used = true === WC()->session->get( 'checkout_page_used' ) ? 'Yes' : 'No';
524 + $checkout_page_contains_checkout_block = '1';
525 + $checkout_page_contains_checkout_shortcode = '0';
526 +
527 + $this->enqueue_event(
528 + 'post_account_creation',
529 + $this->get_cart_checkout_event_properties(
530 + array(
531 + 'from_checkout' => $checkout_page_used,
532 + 'checkout_page_contains_checkout_block' => $checkout_page_contains_checkout_block,
533 + 'checkout_page_contains_checkout_shortcode' => $checkout_page_contains_checkout_shortcode,
534 + )
535 + )
536 + );
537 + }
538 + }
539 +
540 + /**
541 + * Capture a search event.
542 + *
543 + * The term is capped because this event is assembled here but fired by the
544 + * client, so it travels through the page markup and then reaches the pixel
545 + * URL. See `cap_page_string()`.
546 + */
547 + public function capture_search_query() {
548 + if ( is_search() ) {
549 + global $wp_query;
550 + $this->enqueue_event(
551 + 'search_performed',
552 + array(
553 + 'search_query' => $this->cap_page_string( $wp_query->get( 's' ) ),
554 + 'qty' => $wp_query->found_posts,
555 + )
556 + );
557 + }
558 + }
559 +
560 + /**
561 + * Track the cart page view
562 + */
563 + public function capture_cart_view() {
564 + global $post;
565 + $cart_page_id = wc_get_page_id( 'cart' );
566 +
567 + $is_cart = $cart_page_id && is_page( $cart_page_id )
568 + || wc_post_content_has_shortcode( 'woocommerce_cart' )
569 + || has_block( 'woocommerce/cart', $post )
570 + || apply_filters( 'woocommerce_is_cart', false )
571 + || Constants::is_defined( 'WOOCOMMERCE_CART' )
572 + || is_cart();
573 +
574 + if ( ! $is_cart ) {
575 + return;
576 + }
577 +
578 + $this->enqueue_event(
579 + 'cart_view',
580 + $this->get_cart_checkout_event_properties(
581 + $this->get_cart_checkout_shared_data()
582 + )
583 + );
584 + }
585 +
586 + /**
587 + * Track a product page view
588 + */
589 + public function capture_product_view() {
590 + global $product;
591 + if ( ! $product instanceof WC_Product ) {
592 + return;
593 + }
594 +
595 + $this->enqueue_event(
596 + 'product_view',
597 + array(),
598 + $product->get_id()
599 + );
600 + }
601 +
602 + /**
603 + * Track the order confirmation page view
604 + */
605 + public function capture_order_confirmation_view() {
606 + $order_id = absint( get_query_var( 'order-received' ) );
607 + if ( ! $order_id ) {
608 + return;
609 + }
610 +
611 + if ( ! is_order_received_page() ) {
612 + return;
613 + }
614 +
615 + $order = wc_get_order( $order_id );
616 +
617 + $order_source = $order->get_created_via();
618 + $checkout_page_contains_checkout_block = '0';
619 + $checkout_page_contains_checkout_shortcode = '0';
620 +
621 + if ( 'store-api' === $order_source ) {
622 + $checkout_page_contains_checkout_block = '1';
623 + } elseif ( 'checkout' === $order_source ) {
624 + $checkout_page_contains_checkout_shortcode = '1';
625 + }
626 +
627 + $coupons = $order->get_coupons();
628 + $coupon_used = 0;
629 + if ( is_countable( $coupons ) ) {
630 + $coupon_used = count( $coupons ) ? 1 : 0;
631 + }
632 +
633 + if ( is_object( WC()->session ) ) {
634 + $create_account = true === WC()->session->get( 'wc_checkout_createaccount_used' ) ? 'Yes' : 'No';
635 + $checkout_page_used = true === WC()->session->get( 'checkout_page_used' ) ? 'Yes' : 'No';
636 + } else {
637 + $create_account = 'No';
638 + $checkout_page_used = 'No';
639 + }
640 +
641 + $delayed_account_creation = ucfirst( get_option( 'woocommerce_enable_delayed_account_creation', 'Yes' ) );
642 + $this->enqueue_event(
643 + 'order_confirmation_view',
644 + $this->get_cart_checkout_event_properties(
645 + array(
646 + 'coupon_used' => $coupon_used,
647 + 'create_account' => $create_account,
648 + 'express_checkout' => 'null', // TODO: not solved yet.
649 + 'guest_checkout' => $order->get_customer_id() ? 'No' : 'Yes',
650 + 'delayed_account_creation' => $delayed_account_creation,
651 + 'oi' => $order->get_id(),
652 + 'order_value' => $order->get_subtotal(),
653 + 'order_total' => $order->get_total(),
654 + 'products_count' => $order->get_item_count(),
655 + 'total_discount' => $order->get_discount_total(),
656 + 'total_shipping' => $order->get_shipping_total(),
657 + 'total_tax' => $order->get_total_tax(),
658 + 'payment_option' => $order->get_payment_method(),
659 + 'products' => $this->format_items_to_json( $order->get_items() ),
660 + 'order_note' => $order->get_customer_note(),
661 + 'shipping_option' => $order->get_shipping_method(),
662 + 'from_checkout' => $checkout_page_used,
663 + 'checkout_page_contains_checkout_block' => $checkout_page_contains_checkout_block,
664 + 'checkout_page_contains_checkout_shortcode' => $checkout_page_contains_checkout_shortcode,
665 + )
666 + )
667 + );
668 + }
669 +
670 + /**
671 + * Track the checkout page view
672 + */
673 + public function capture_checkout_view() {
674 + global $post;
675 + if ( ! $post instanceof \WP_Post ) {
676 + return;
677 + }
678 +
679 + $checkout_page_id = wc_get_page_id( 'checkout' );
680 +
681 + $is_checkout = $checkout_page_id && is_page( $checkout_page_id )
682 + || wc_post_content_has_shortcode( 'woocommerce_checkout' )
683 + || has_block( 'woocommerce/checkout', $post )
684 + || apply_filters( 'woocommerce_is_checkout', false )
685 + || Constants::is_defined( 'WOOCOMMERCE_CHECKOUT' );
686 +
687 + if ( ! $is_checkout ) {
688 + return;
689 + }
690 +
691 + $is_in_checkout_page = isset( $post->ID ) && $checkout_page_id === $post->ID ? 'Yes' : 'No';
692 + $checkout_page_contains_checkout_block = '0';
693 + $checkout_page_contains_checkout_shortcode = '1';
694 +
695 + $session = WC()->session;
696 + if ( is_object( $session ) ) {
697 + $session->set( 'checkout_page_used', true );
698 + $session->save_data();
699 + $draft_order_id = $session->get( 'store_api_draft_order', 0 );
700 + if ( $draft_order_id ) {
701 + $checkout_page_contains_checkout_block = '1';
702 + $checkout_page_contains_checkout_shortcode = '0';
703 + }
704 + }
705 +
706 + // Order received page is also a checkout page, so we need to bail out if we are on that page.
707 + if ( is_order_received_page() ) {
708 + return;
709 + }
710 +
711 + $this->enqueue_event(
712 + 'checkout_view',
713 + $this->get_cart_checkout_event_properties(
714 + array_merge(
715 + $this->get_cart_checkout_shared_data(),
716 + array(
717 + 'from_checkout' => $is_in_checkout_page,
718 + 'checkout_page_contains_checkout_block' => $checkout_page_contains_checkout_block,
719 + 'checkout_page_contains_checkout_shortcode' => $checkout_page_contains_checkout_shortcode,
720 + )
721 + )
722 + )
723 + );
523 724 }
524 725 }