PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 7.6.2
Jetpack – WP Security, Backup, Speed, & Growth v7.6.2
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 13.8.3 13.9.2 14.0.1 All 504 releases
jetpack / modules / woocommerce-analytics / classes / wp-woocommerce-analytics-universal.php

wp-woocommerce-analytics-universal.php in Jetpack – WP Security, Backup, Speed, & Growth 7.6.2, at modules/woocommerce-analytics/classes/wp-woocommerce-analytics-universal.php

400 lines 12.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Jetpack_WooCommerce_Analytics_Universal
4 *
5 * @package Jetpack
6 * @author Automattic
7 */
8
9 /**
10 * Bail if accessed directly
11 */
12 if ( ! defined( 'ABSPATH' ) ) {
13 exit;
14 }
15
16 /**
17 * Class Jetpack_WooCommerce_Analytics_Universal
18 * Filters and Actions added to Store pages to perform analytics
19 */
20 class Jetpack_WooCommerce_Analytics_Universal {
21 /**
22 * Jetpack_WooCommerce_Analytics_Universal constructor.
23 */
24 public function __construct() {
25 // loading _wca
26 add_action( 'wp_head', array( $this, 'wp_head_top' ), 1 );
27
28 // add to carts from non-product pages or lists (search, store etc.)
29 add_action( 'wp_head', array( $this, 'loop_session_events' ), 2 );
30
31 // loading s.js
32 add_action( 'wp_head', array( $this, 'wp_head_bottom' ), 999999 );
33
34 // Capture cart events
35 add_action( 'woocommerce_add_to_cart', array( $this, 'capture_add_to_cart' ), 10, 6 );
36
37 // single product page view
38 add_action( 'woocommerce_after_single_product', array( $this, 'capture_product_view' ) );
39
40 add_action( 'woocommerce_after_cart', array( $this, 'remove_from_cart' ) );
41 add_action( 'woocommerce_after_mini_cart', array( $this, 'remove_from_cart' ) );
42 add_action( 'wcct_before_cart_widget', array( $this, 'remove_from_cart' ) );
43 add_filter( 'woocommerce_cart_item_remove_link', array( $this, 'remove_from_cart_attributes' ), 10, 2 );
44
45 // cart checkout
46 add_action( 'woocommerce_after_checkout_form', array( $this, 'checkout_process' ) );
47
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 );
51 }
52
53 /**
54 * Make _wca available to queue events
55 */
56 public function wp_head_top() {
57 if ( is_cart() || is_checkout() || is_checkout_pay_page() || is_order_received_page() || is_add_payment_method_page() ) {
58 $prevent_referrer_code = '<script>window._wca_prevent_referrer = true;</script>';
59 echo "$prevent_referrer_code\r\n";
60 }
61 $wca_code = '<script>window._wca = window._wca || [];</script>';
62 echo "$wca_code\r\n";
63 }
64
65
66 /**
67 * Place script to call s.js, Store Analytics
68 */
69 public function wp_head_bottom() {
70 $filename = 's-' . gmdate( 'YW' ) . '.js';
71 $async_code = "<script async src='https://stats.wp.com/" . $filename . "'></script>";
72 echo "$async_code\r\n";
73 }
74
75 /**
76 * On product lists or other non-product pages, add an event listener to "Add to Cart" button click
77 */
78 public function loop_session_events() {
79 $blogid = Jetpack::get_option( 'id' );
80
81 // check for previous add-to-cart cart events
82 if ( is_object( WC()->session ) ) {
83 $data = WC()->session->get( 'wca_session_data' );
84 if ( ! empty( $data ) ) {
85 foreach ( $data as $data_instance ) {
86 $product = wc_get_product( $data_instance['product_id'] );
87 if ( ! $product ) {
88 continue;
89 }
90 $product_details = $this->get_product_details( $product );
91 wc_enqueue_js(
92 "_wca.push( {
93 '_en': '" . esc_js( $data_instance['event'] ) . "',
94 'blog_id': '" . esc_js( $blogid ) . "',
95 'pi': '" . esc_js( $data_instance['product_id'] ) . "',
96 'pn': '" . esc_js( $product_details['name'] ) . "',
97 'pc': '" . esc_js( $product_details['category'] ) . "',
98 'pp': '" . esc_js( $product_details['price'] ) . "',
99 'pq': '" . esc_js( $data_instance['quantity'] ) . "',
100 'pt': '" . esc_js( $product_details['type'] ) . "',
101 'ui': '" . esc_js( $this->get_user_id() ) . "',
102 } );"
103 );
104 }
105 // clear data
106 WC()->session->set( 'wca_session_data', '' );
107 }
108 }
109 }
110
111 /**
112 * On the cart page, add an event listener for removal of product click
113 */
114 public function remove_from_cart() {
115
116 // We listen at div.woocommerce because the cart 'form' contents get forcibly
117 // updated and subsequent removals from cart would then not have this click
118 // handler attached.
119 $blogid = Jetpack::get_option( 'id' );
120 wc_enqueue_js(
121 "jQuery( 'div.woocommerce' ).on( 'click', 'a.remove', function() {
122 var productID = jQuery( this ).data( 'product_id' );
123 var quantity = jQuery( this ).parent().parent().find( '.qty' ).val()
124 var productDetails = {
125 'id': productID,
126 'quantity': quantity ? quantity : '1',
127 };
128 _wca.push( {
129 '_en': 'woocommerceanalytics_remove_from_cart',
130 'blog_id': '" . esc_js( $blogid ) . "',
131 'pi': productDetails.id,
132 'pq': productDetails.quantity,
133 'ui': '" . esc_js( $this->get_user_id() ) . "',
134 } );
135 } );"
136 );
137 }
138
139 /**
140 * Adds the product ID to the remove product link (for use by remove_from_cart above) if not present
141 *
142 * @param string $url Full HTML a tag of the link to remove an item from the cart.
143 * @param string $key Unique Key ID for a cart item.
144 *
145 * @return mixed.
146 */
147 public function remove_from_cart_attributes( $url, $key ) {
148 if ( false !== strpos( $url, 'data-product_id' ) ) {
149 return $url;
150 }
151
152 $item = WC()->cart->get_cart_item( $key );
153 $product = $item['data'];
154
155 $new_attributes = sprintf(
156 '" data-product_id="%s">',
157 esc_attr( $product->get_id() )
158 );
159
160 $url = str_replace( '">', $new_attributes, $url );
161 return $url;
162 }
163
164 /**
165 * Gather relevant product information
166 *
167 * @param array $product product
168 * @return array
169 */
170 public function get_product_details( $product ) {
171 return array(
172 'id' => $product->get_id(),
173 'name' => $product->get_title(),
174 'category' => $this->get_product_categories_concatenated( $product ),
175 'price' => $product->get_price(),
176 'type' => $product->get_type(),
177 );
178 }
179
180 /**
181 * Track a product page view
182 */
183 public function capture_product_view() {
184
185 global $product;
186 $blogid = Jetpack::get_option( 'id' );
187 $product_details = $this->get_product_details( $product );
188
189 wc_enqueue_js(
190 "_wca.push( {
191 '_en': 'woocommerceanalytics_product_view',
192 'blog_id': '" . esc_js( $blogid ) . "',
193 'pi': '" . esc_js( $product_details['id'] ) . "',
194 'pn': '" . esc_js( $product_details['name'] ) . "',
195 'pc': '" . esc_js( $product_details['category'] ) . "',
196 'pp': '" . esc_js( $product_details['price'] ) . "',
197 'pt': '" . esc_js( $product_details['type'] ) . "',
198 'ui': '" . esc_js( $this->get_user_id() ) . "',
199 } );"
200 );
201 }
202
203 /**
204 * On the Checkout page, trigger an event for each product in the cart
205 */
206 public function checkout_process() {
207
208 $universal_commands = array();
209 $cart = WC()->cart->get_cart();
210 $blogid = Jetpack::get_option( 'id' );
211
212 foreach ( $cart as $cart_item_key => $cart_item ) {
213 /**
214 * This filter is already documented in woocommerce/templates/cart/cart.php
215 */
216 $product = apply_filters( 'woocommerce_cart_item_product', $cart_item['data'], $cart_item, $cart_item_key );
217
218 if ( ! $product ) {
219 continue;
220 }
221
222 $product_details = $this->get_product_details( $product );
223
224 $universal_commands[] = "_wca.push( {
225 '_en': 'woocommerceanalytics_product_checkout',
226 'blog_id': '" . esc_js( $blogid ) . "',
227 'pi': '" . esc_js( $product_details['id'] ) . "',
228 'pn': '" . esc_js( $product_details['name'] ) . "',
229 'pc': '" . esc_js( $product_details['category'] ) . "',
230 'pp': '" . esc_js( $product_details['price'] ) . "',
231 'pq': '" . esc_js( $cart_item['quantity'] ) . "',
232 'pt': '" . esc_js( $product_details['type'] ) . "',
233 'ui': '" . esc_js( $this->get_user_id() ) . "',
234 } );";
235 }
236
237 wc_enqueue_js( implode( "\r\n", $universal_commands ) );
238 }
239
240 /**
241 * After the checkout process, fire an event for each item in the order
242 *
243 * @param string $order_id Order Id.
244 */
245 public function order_process( $order_id ) {
246 $order = wc_get_order( $order_id );
247 $universal_commands = array();
248 $blogid = Jetpack::get_option( 'id' );
249
250 // loop through products in the order and queue a purchase event.
251 foreach ( $order->get_items() as $order_item_id => $order_item ) {
252 $product = $order->get_product_from_item( $order_item );
253
254 $product_details = $this->get_product_details( $product );
255
256 $universal_commands[] = "_wca.push( {
257 '_en': 'woocommerceanalytics_product_purchase',
258 'blog_id': '" . esc_js( $blogid ) . "',
259 'pi': '" . esc_js( $product_details['id'] ) . "',
260 'pn': '" . esc_js( $product_details['name'] ) . "',
261 'pc': '" . esc_js( $product_details['category'] ) . "',
262 'pp': '" . esc_js( $product_details['price'] ) . "',
263 'pq': '" . esc_js( $order_item->get_quantity() ) . "',
264 'pt': '" . esc_js( $product_details['type'] ) . "',
265 'oi': '" . esc_js( $order->get_order_number() ) . "',
266 'ui': '" . esc_js( $this->get_user_id() ) . "',
267 } );";
268 }
269
270 wc_enqueue_js( implode( "\r\n", $universal_commands ) );
271 }
272
273 /**
274 * Listen for clicks on the "Update Cart" button to know if an item has been removed by
275 * updating its quantity to zero
276 */
277 public function remove_from_cart_via_quantity() {
278 $blogid = Jetpack::get_option( 'id' );
279
280 wc_enqueue_js(
281 "
282 jQuery( 'button[name=update_cart]' ).on( 'click', function() {
283 var cartItems = jQuery( '.cart_item' );
284 cartItems.each( function( item ) {
285 var qty = jQuery( this ).find( 'input.qty' );
286 if ( qty && qty.val() === '0' ) {
287 var productID = jQuery( this ).find( '.product-remove a' ).data( 'product_id' );
288 _wca.push( {
289 '_en': 'woocommerceanalytics_remove_from_cart',
290 'blog_id': '" . esc_js( $blogid ) . "',
291 'pi': productID,
292 'ui': '" . esc_js( $this->get_user_id() ) . "',
293 } );
294 }
295 } );
296 } );
297 "
298 );
299 }
300
301 /**
302 * Get the current user id
303 *
304 * @return int
305 */
306 public function get_user_id() {
307 if ( is_user_logged_in() ) {
308 $blogid = Jetpack::get_option( 'id' );
309 $userid = get_current_user_id();
310 return $blogid . ':' . $userid;
311 }
312 return 'null';
313 }
314
315 /**
316 * @param $cart_item_key
317 * @param $product_id
318 * @param $quantity
319 * @param $variation_id
320 * @param $variation
321 * @param $cart_item_data
322 */
323 public function capture_add_to_cart( $cart_item_key, $product_id, $quantity, $variation_id, $variation, $cart_item_data ) {
324 $referer_postid = isset( $_SERVER['HTTP_REFERER'] ) ? url_to_postid( $_SERVER['HTTP_REFERER'] ) : 0;
325 // if the referring post is not a product OR the product being added is not the same as post
326 // (eg. related product list on single product page) then include a product view event
327 if ( ! wc_get_product( $referer_postid ) || $product_id != $referer_postid ) {
328 $this->capture_event_in_session_data( $product_id, $quantity, 'woocommerceanalytics_product_view' );
329 }
330 // add cart event to the session data
331 $this->capture_event_in_session_data( $product_id, $quantity, 'woocommerceanalytics_add_to_cart' );
332 }
333
334 /**
335 * @param $product_id
336 * @param $quantity
337 * @param $event
338 */
339 public function capture_event_in_session_data( $product_id, $quantity, $event ) {
340
341 $product = wc_get_product( $product_id );
342 if ( ! $product ) {
343 return;
344 }
345
346 $quantity = ( $quantity == 0 ) ? 1 : $quantity;
347
348 // check for existing data
349 if ( is_object( WC()->session ) ) {
350 $data = WC()->session->get( 'wca_session_data' );
351 if ( empty( $data ) || ! is_array( $data ) ) {
352 $data = array();
353 }
354 } else {
355 $data = array();
356 }
357
358 // extract new event data
359 $new_data = array(
360 'event' => $event,
361 'product_id' => (string) $product_id,
362 'quantity' => (string) $quantity,
363 );
364
365 // append new data
366 $data[] = $new_data;
367
368 WC()->session->set( 'wca_session_data', $data );
369 }
370
371 /**
372 * Gets product categories or varation attributes as a formatted concatenated string
373 *
374 * @param object $product WC_Product.
375 * @return string
376 */
377 public function get_product_categories_concatenated( $product ) {
378
379 if ( ! $product ) {
380 return '';
381 }
382
383 $variation_data = $product->is_type( 'variation' ) ? wc_get_product_variation_attributes( $product->get_id() ) : '';
384 if ( is_array( $variation_data ) && ! empty( $variation_data ) ) {
385 $line = wc_get_formatted_variation( $variation_data, true );
386 } else {
387 $out = array();
388 $categories = get_the_terms( $product->get_id(), 'product_cat' );
389 if ( $categories ) {
390 foreach ( $categories as $category ) {
391 $out[] = $category->name;
392 }
393 }
394 $line = join( '/', $out );
395 }
396 return $line;
397 }
398
399 }
400