Completed
Push — update/editor-blocks-icon-colo... ( 093ab2...3cfb5e )
by
unknown
08:47
created

classes/wp-google-analytics-universal.php (1 issue)

Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
/**
4
* Jetpack_Google_Analytics_Universal hooks and and enqueues support for analytics.js
5
* https://developers.google.com/analytics/devguides/collection/analyticsjs/
6
* https://developers.google.com/analytics/devguides/collection/analyticsjs/enhanced-ecommerce
7
*
8
* @author allendav
9
*/
10
11
/**
12
* Bail if accessed directly
13
*/
14
if ( ! defined( 'ABSPATH' ) ) {
15
	exit;
16
}
17
18
class Jetpack_Google_Analytics_Universal {
19
	public function __construct() {
20
		add_filter( 'jetpack_wga_universal_commands', array( $this, 'maybe_anonymize_ip' ) );
21
		add_filter( 'jetpack_wga_universal_commands', array( $this, 'maybe_track_purchases' ) );
22
23
		add_action( 'wp_head', array( $this, 'wp_head' ), 999999 );
24
25
		add_action( 'woocommerce_after_add_to_cart_button', array( $this, 'add_to_cart' ) );
26
		add_action( 'wp_footer', array( $this, 'loop_add_to_cart' ) );
27
		add_action( 'woocommerce_after_cart', array( $this, 'remove_from_cart' ) );
28
		add_action( 'woocommerce_after_mini_cart', array( $this, 'remove_from_cart' ) );
29
		add_filter( 'woocommerce_cart_item_remove_link', array( $this, 'remove_from_cart_attributes' ), 10, 2 );
30
		add_action( 'woocommerce_after_shop_loop_item', array( $this, 'listing_impression' ) );
31
		add_action( 'woocommerce_after_shop_loop_item', array( $this, 'listing_click' ) );
32
		add_action( 'woocommerce_after_single_product', array( $this, 'product_detail' ) );
33
		add_action( 'woocommerce_after_checkout_form', array( $this, 'checkout_process' ) );
34
35
		// we need to send a pageview command last - so we use priority 24 to add
36
		// this command's JavaScript just before wc_print_js is called (pri 25)
37
		add_action( 'wp_footer', array( $this, 'send_pageview_in_footer' ), 24 );
38
	}
39
40
	public function wp_head() {
41
		$tracking_code = Jetpack_Google_Analytics_Options::get_tracking_code();
42
		if ( empty( $tracking_code ) ) {
43
			echo "<!-- No tracking ID configured for Jetpack Google Analytics -->\r\n";
44
			return;
45
		}
46
47
		// If we're in the admin_area, return without inserting code.
48
		if ( is_admin() ) {
49
			return;
50
		}
51
52
		if ( Jetpack_AMP_Support::is_amp_request() ) {
53
			// For Reader mode — legacy.
54
			add_filter( 'amp_post_template_analytics', 'Jetpack_Google_Analytics::amp_analytics_entries', 1000 );
55
			// For Standard and Transitional modes.
56
			add_filter( 'amp_analytics_entries', 'Jetpack_Google_Analytics::amp_analytics_entries', 1000 );
57
			return;
58
		}
59
60
		/**
61
		 * Allow for additional elements to be added to the universal Google Analytics queue (ga) array
62
		 *
63
		 * @since 5.6.0
64
		 *
65
		 * @param array $custom_vars Array of universal Google Analytics queue elements
66
		 */
67
		$universal_commands = apply_filters( 'jetpack_wga_universal_commands', array() );
68
69
		$async_code = "
70
			<!-- Jetpack Google Analytics -->
71
			<script>
72
				window.ga = window.ga || function(){ ( ga.q = ga.q || [] ).push( arguments ) }; ga.l=+new Date;
73
				ga( 'create', '%tracking_id%', 'auto' );
74
				ga( 'require', 'ec' );
75
				%universal_commands%
76
			</script>
77
			<script async src='https://www.google-analytics.com/analytics.js'></script>
78
			<!-- End Jetpack Google Analytics -->
79
		";
80
		$async_code = str_replace( '%tracking_id%', $tracking_code, $async_code );
81
82
		$universal_commands_string = implode( "\r\n", $universal_commands );
83
		$async_code = str_replace( '%universal_commands%', $universal_commands_string, $async_code );
84
85
		echo "$async_code\r\n";
86
	}
87
88
	public function maybe_anonymize_ip( $command_array ) {
89
		if ( Jetpack_Google_Analytics_Options::anonymize_ip_is_enabled() ) {
90
			array_push( $command_array, "ga( 'set', 'anonymizeIp', true );" );
91
		}
92
93
		return $command_array;
94
	}
95
96
	public function maybe_track_purchases( $command_array ) {
97
		global $wp;
98
99
		if ( ! Jetpack_Google_Analytics_Options::track_purchases_is_enabled() ) {
100
			return $command_array;
101
		}
102
103
		if ( ! class_exists( 'WooCommerce' ) ) {
104
			return $command_array;
105
		}
106
107
		$minimum_woocommerce_active = class_exists( 'WooCommerce' ) && version_compare( WC_VERSION, '3.0', '>=' );
108
		if ( ! $minimum_woocommerce_active ) {
109
			return $command_array;
110
		}
111
112
		if ( ! is_order_received_page() ) {
113
			return $command_array;
114
		}
115
116
		$order_id = isset( $wp->query_vars['order-received'] ) ? $wp->query_vars['order-received'] : 0;
117
		if ( 0 == $order_id ) {
118
			return $command_array;
119
		}
120
121
		// A 1 indicates we've already tracked this order - don't do it again
122
		if ( 1 == get_post_meta( $order_id, '_ga_tracked', true ) ) {
123
			return $command_array;
124
		}
125
126
		$order = new WC_Order( $order_id );
127
		$order_currency = $order->get_currency();
128
		$command = "ga( 'set', '&cu', '" . esc_js( $order_currency ) . "' );";
129
		array_push( $command_array, $command );
130
131
		// Order items
132
		if ( $order->get_items() ) {
133 View Code Duplication
			foreach ( $order->get_items() as $item ) {
134
				$product = $order->get_product_from_item( $item );
135
				$product_sku_or_id = Jetpack_Google_Analytics_Utils::get_product_sku_or_id( $product );
136
137
				$item_details = array(
138
					'id' => $product_sku_or_id,
139
					'name' => $item['name'],
140
					'category' => Jetpack_Google_Analytics_Utils::get_product_categories_concatenated( $product ),
141
					'price' => $order->get_item_total( $item ),
142
					'quantity' => $item['qty'],
143
				);
144
				$command = "ga( 'ec:addProduct', " . wp_json_encode( $item_details ) . " );";
145
				array_push( $command_array, $command );
146
			}
147
		}
148
149
		// Order summary
150
		$summary = array(
151
			'id' => $order->get_order_number(),
152
			'affiliation' => get_bloginfo( 'name' ),
153
			'revenue' => $order->get_total(),
154
			'tax' => $order->get_total_tax(),
155
			'shipping' => $order->get_total_shipping()
156
		);
157
		$command = "ga( 'ec:setAction', 'purchase', " . wp_json_encode( $summary ) . " );";
158
		array_push( $command_array, $command );
159
160
		update_post_meta( $order_id, '_ga_tracked', 1 );
161
162
		return $command_array;
163
	}
164
165
	public function add_to_cart() {
166
		if ( ! Jetpack_Google_Analytics_Options::track_add_to_cart_is_enabled() ) {
167
			return;
168
		}
169
170
		if ( ! is_single() ) {
171
			return;
172
		}
173
174
		global $product;
175
176
		$product_sku_or_id = Jetpack_Google_Analytics_Utils::get_product_sku_or_id( $product );
177
		$selector = ".single_add_to_cart_button";
178
179
		wc_enqueue_js(
180
			"$( '" . esc_js( $selector ) . "' ).click( function() {
181
				var productDetails = {
182
					'id': '" . esc_js( $product_sku_or_id ) . "',
183
					'name' : '" . esc_js( $product->get_title() ) . "',
184
					'quantity': $( 'input.qty' ).val() ? $( 'input.qty' ).val() : '1',
185
				};
186
				ga( 'ec:addProduct', productDetails );
187
				ga( 'ec:setAction', 'add' );
188
				ga( 'send', 'event', 'UX', 'click', 'add to cart' );
189
			} );"
190
		);
191
	}
192
193
	public function loop_add_to_cart() {
194
		if ( ! Jetpack_Google_Analytics_Options::track_add_to_cart_is_enabled() ) {
195
			return;
196
		}
197
198
		if ( ! class_exists( 'WooCommerce' ) ) {
199
			return;
200
		}
201
202
		$minimum_woocommerce_active = class_exists( 'WooCommerce' ) && version_compare( WC_VERSION, '3.0', '>=' );
203
		if ( ! $minimum_woocommerce_active ) {
204
			return;
205
		}
206
207
		$selector = ".add_to_cart_button:not(.product_type_variable, .product_type_grouped)";
208
209
		wc_enqueue_js(
210
			"$( '" . esc_js( $selector ) . "' ).click( function() {
211
				var productSku = $( this ).data( 'product_sku' );
212
				var productID = $( this ).data( 'product_id' );
213
				var productDetails = {
214
					'id': productSku ? productSku : '#' + productID,
215
					'quantity': $( this ).data( 'quantity' ),
216
				};
217
				ga( 'ec:addProduct', productDetails );
218
				ga( 'ec:setAction', 'add' );
219
				ga( 'send', 'event', 'UX', 'click', 'add to cart' );
220
			} );"
221
		);
222
	}
223
224
	public function remove_from_cart() {
225
		if ( ! Jetpack_Google_Analytics_Options::enhanced_ecommerce_tracking_is_enabled() ) {
226
			return;
227
		}
228
229
		if ( ! Jetpack_Google_Analytics_Options::track_remove_from_cart_is_enabled() ) {
230
			return;
231
		}
232
233
		// We listen at div.woocommerce because the cart 'form' contents get forcibly
234
		// updated and subsequent removals from cart would then not have this click
235
		// handler attached
236
		wc_enqueue_js(
237
			"$( 'div.woocommerce' ).on( 'click', 'a.remove', function() {
238
				var productSku = $( this ).data( 'product_sku' );
239
				var productID = $( this ).data( 'product_id' );
240
				var quantity = $( this ).parent().parent().find( '.qty' ).val()
241
				var productDetails = {
242
					'id': productSku ? productSku : '#' + productID,
243
					'quantity': quantity ? quantity : '1',
244
				};
245
				ga( 'ec:addProduct', productDetails );
246
				ga( 'ec:setAction', 'remove' );
247
				ga( 'send', 'event', 'UX', 'click', 'remove from cart' );
248
			} );"
249
		);
250
	}
251
252
	/**
253
	 * Adds the product ID and SKU to the remove product link (for use by remove_from_cart above) if not present
254
	 *
255
	 * @param string $url Full HTML a tag of the link to remove an item from the cart.
256
	 * @param string $key Unique Key ID for a cart item.
257
	 */
258 View Code Duplication
	public function remove_from_cart_attributes( $url, $key ) {
259
		if ( false !== strpos( $url, 'data-product_id' ) ) {
260
			return $url;
261
		}
262
263
		$item    = WC()->cart->get_cart_item( $key );
264
		$product = $item['data'];
265
266
		$new_attributes = sprintf(
267
			'" data-product_id="%1$s" data-product_sku="%2$s">',
268
			esc_attr( $product->get_id() ),
269
			esc_attr( $product->get_sku() )
270
		);
271
272
		$url = str_replace( '">', $new_attributes, $url );
273
		return $url;
274
	}
275
276
	public function listing_impression() {
277
		if ( ! Jetpack_Google_Analytics_Options::enhanced_ecommerce_tracking_is_enabled() ) {
278
			return;
279
		}
280
281
		if ( ! Jetpack_Google_Analytics_Options::track_product_impressions_is_enabled() ) {
282
			return;
283
		}
284
285
		if ( isset( $_GET['s'] ) ) {
286
			$list = "Search Results";
287
		} else {
288
			$list = "Product List";
289
		}
290
291
		global $product, $woocommerce_loop;
292
		$product_sku_or_id = Jetpack_Google_Analytics_Utils::get_product_sku_or_id( $product );
293
294
		$item_details = array(
295
			'id' => $product_sku_or_id,
296
			'name' => $product->get_title(),
297
			'category' => Jetpack_Google_Analytics_Utils::get_product_categories_concatenated( $product ),
298
			'list' => $list,
299
			'position' => $woocommerce_loop['loop']
300
		);
301
		wc_enqueue_js( "ga( 'ec:addImpression', " . wp_json_encode( $item_details ) . " );" );
302
	}
303
304
	public function listing_click() {
305
		if ( ! Jetpack_Google_Analytics_Options::enhanced_ecommerce_tracking_is_enabled() ) {
306
			return;
307
		}
308
309
		if ( ! Jetpack_Google_Analytics_Options::track_product_clicks_is_enabled() ) {
310
			return;
311
		}
312
313
		if ( isset( $_GET['s'] ) ) {
314
			$list = "Search Results";
315
		} else {
316
			$list = "Product List";
317
		}
318
319
		global $product, $woocommerce_loop;
320
		$product_sku_or_id = Jetpack_Google_Analytics_Utils::get_product_sku_or_id( $product );
321
322
		$selector = ".products .post-" . esc_js( $product->get_id() ) . " a";
323
324
		$item_details = array(
325
			'id' => $product_sku_or_id,
326
			'name' => $product->get_title(),
327
			'category' => Jetpack_Google_Analytics_Utils::get_product_categories_concatenated( $product ),
328
			'position' => $woocommerce_loop['loop']
329
		);
330
331
		wc_enqueue_js(
332
			"$( '" . esc_js( $selector ) . "' ).click( function() {
333
				if ( true === $( this ).hasClass( 'add_to_cart_button' ) ) {
334
					return;
335
				}
336
337
				ga( 'ec:addProduct', " . wp_json_encode( $item_details ) . " );
338
				ga( 'ec:setAction', 'click', { list: '" . esc_js( $list ) . "' } );
339
				ga( 'send', 'event', 'UX', 'click', { list: '" . esc_js( $list ) . "' } );
340
			} );"
341
		);
342
	}
343
344
	public function product_detail() {
345
		if ( ! Jetpack_Google_Analytics_Options::enhanced_ecommerce_tracking_is_enabled() ) {
346
			return;
347
		}
348
349
		if ( ! Jetpack_Google_Analytics_Options::track_product_detail_view_is_enabled() ) {
350
			return;
351
		}
352
353
		global $product;
354
		$product_sku_or_id = Jetpack_Google_Analytics_Utils::get_product_sku_or_id( $product );
355
356
		$item_details = array(
357
			'id' => $product_sku_or_id,
358
			'name' => $product->get_title(),
359
			'category' => Jetpack_Google_Analytics_Utils::get_product_categories_concatenated( $product ),
360
			'price' => $product->get_price()
361
		);
362
		wc_enqueue_js(
363
			"ga( 'ec:addProduct', " . wp_json_encode( $item_details ) . " );" .
364
			"ga( 'ec:setAction', 'detail' );"
365
		);
366
	}
367
368
	public function checkout_process() {
369
		if ( ! Jetpack_Google_Analytics_Options::enhanced_ecommerce_tracking_is_enabled() ) {
370
			return;
371
		}
372
373
		if ( ! Jetpack_Google_Analytics_Options::track_checkout_started_is_enabled() ) {
374
			return;
375
		}
376
377
		$universal_commands = array();
378
		$cart = WC()->cart->get_cart();
379
380 View Code Duplication
		foreach ( $cart as $cart_item_key => $cart_item ) {
381
			/**
382
			* This filter is already documented in woocommerce/templates/cart/cart.php
383
			*/
384
			$product = apply_filters( 'woocommerce_cart_item_product', $cart_item['data'], $cart_item, $cart_item_key );
0 ignored issues
show
The call to apply_filters() has too many arguments starting with $cart_item.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
385
			$product_sku_or_id = Jetpack_Google_Analytics_Utils::get_product_sku_or_id( $product );
386
387
			$item_details = array(
388
				'id' => $product_sku_or_id,
389
				'name' => $product->get_title(),
390
				'category' => Jetpack_Google_Analytics_Utils::get_product_categories_concatenated( $product ),
391
				'price' => $product->get_price(),
392
				'quantity' => $cart_item[ 'quantity' ]
393
			);
394
395
			array_push( $universal_commands, "ga( 'ec:addProduct', " . wp_json_encode( $item_details ) . " );" );
396
		}
397
398
		array_push( $universal_commands, "ga( 'ec:setAction','checkout' );" );
399
400
		wc_enqueue_js( implode( "\r\n", $universal_commands ) );
401
	}
402
403
	public function send_pageview_in_footer() {
404
		if ( ! Jetpack_Google_Analytics_Options::has_tracking_code() ) {
405
			return;
406
		}
407
408
		if ( is_admin() ) {
409
			return;
410
		}
411
412
		if ( ! class_exists( 'WooCommerce' ) ) {
413
			return;
414
		}
415
416
		wc_enqueue_js( "ga( 'send', 'pageview' );" );
417
	}
418
}
419