Completed
Push — jetpack-fusion-mock-files ( e51750...3b1561 )
by
unknown
13:31
created

Jetpack_Google_Analytics_Universal   B

Complexity

Total Complexity 50

Size/Duplication

Total Lines 395
Duplicated Lines 7.09 %

Coupling/Cohesion

Components 0
Dependencies 2

Importance

Changes 0
Metric Value
dl 28
loc 395
rs 8.6206
c 0
b 0
f 0
wmc 50
lcom 0
cbo 2

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 20 1
B wp_head() 0 45 4
A maybe_anonymize_ip() 0 7 2
C maybe_track_purchases() 14 68 11
B add_to_cart() 0 27 3
B loop_add_to_cart() 0 30 5
B remove_from_cart() 0 27 3
A remove_from_cart_attributes() 0 16 2
B listing_impression() 0 27 4
B listing_click() 0 39 4
A product_detail() 0 23 3
B checkout_process() 14 34 4
A send_pageview_in_footer() 0 15 4

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Jetpack_Google_Analytics_Universal often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Jetpack_Google_Analytics_Universal, and based on these observations, apply Extract Interface, too.

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
		// At this time, we only leverage universal analytics for enhanced ecommerce. If WooCommerce is not
53
		// present, don't bother emitting the tracking ID or fetching analytics.js
54
		if ( ! class_exists( 'WooCommerce' ) ) {
55
			return;
56
		}
57
58
		/**
59
		 * Allow for additional elements to be added to the universal Google Analytics queue (ga) array
60
		 *
61
		 * @since 5.6.0
62
		 *
63
		 * @param array $custom_vars Array of universal Google Analytics queue elements
64
		 */
65
		$universal_commands = apply_filters( 'jetpack_wga_universal_commands', array() );
66
67
		$async_code = "
68
			<!-- Jetpack Google Analytics -->
69
			<script>
70
				window.ga = window.ga || function(){ ( ga.q = ga.q || [] ).push( arguments ) }; ga.l=+new Date;
71
				ga( 'create', '%tracking_id%', 'auto' );
72
				ga( 'require', 'ec' );
73
				%universal_commands%
74
			</script>
75
			<script async src='https://www.google-analytics.com/analytics.js'></script>
76
			<!-- End Jetpack Google Analytics -->
77
		";
78
		$async_code = str_replace( '%tracking_id%', $tracking_code, $async_code );
79
80
		$universal_commands_string = implode( "\r\n", $universal_commands );
81
		$async_code = str_replace( '%universal_commands%', $universal_commands_string, $async_code );
82
83
		echo "$async_code\r\n";
84
	}
85
86
	public function maybe_anonymize_ip( $command_array ) {
87
		if ( Jetpack_Google_Analytics_Options::anonymize_ip_is_enabled() ) {
88
			array_push( $command_array, "ga( 'set', 'anonymizeIp', true );" );
89
		}
90
91
		return $command_array;
92
	}
93
94
	public function maybe_track_purchases( $command_array ) {
95
		global $wp;
96
97
		if ( ! Jetpack_Google_Analytics_Options::track_purchases_is_enabled() ) {
98
			return $command_array;
99
		}
100
101
		if ( ! class_exists( 'WooCommerce' ) ) {
102
			return $command_array;
103
		}
104
105
		$minimum_woocommerce_active = class_exists( 'WooCommerce' ) && version_compare( WC_VERSION, '3.0', '>=' );
106
		if ( ! $minimum_woocommerce_active ) {
107
			return $command_array;
108
		}
109
110
		if ( ! is_order_received_page() ) {
111
			return $command_array;
112
		}
113
114
		$order_id = isset( $wp->query_vars['order-received'] ) ? $wp->query_vars['order-received'] : 0;
115
		if ( 0 == $order_id ) {
116
			return $command_array;
117
		}
118
119
		// A 1 indicates we've already tracked this order - don't do it again
120
		if ( 1 == get_post_meta( $order_id, '_ga_tracked', true ) ) {
121
			return $command_array;
122
		}
123
124
		$order = new WC_Order( $order_id );
125
		$order_currency = $order->get_currency();
126
		$command = "ga( 'set', '&cu', '" . esc_js( $order_currency ) . "' );";
127
		array_push( $command_array, $command );
128
129
		// Order items
130
		if ( $order->get_items() ) {
131 View Code Duplication
			foreach ( $order->get_items() as $item ) {
132
				$product = $order->get_product_from_item( $item );
133
				$product_sku_or_id = Jetpack_Google_Analytics_Utils::get_product_sku_or_id( $product );
134
135
				$item_details = array(
136
					'id' => $product_sku_or_id,
137
					'name' => $item['name'],
138
					'category' => Jetpack_Google_Analytics_Utils::get_product_categories_concatenated( $product ),
139
					'price' => $order->get_item_total( $item ),
140
					'quantity' => $item['qty'],
141
				);
142
				$command = "ga( 'ec:addProduct', " . wp_json_encode( $item_details ) . " );";
143
				array_push( $command_array, $command );
144
			}
145
		}
146
147
		// Order summary
148
		$summary = array(
149
			'id' => $order->get_order_number(),
150
			'affiliation' => get_bloginfo( 'name' ),
151
			'revenue' => $order->get_total(),
152
			'tax' => $order->get_total_tax(),
153
			'shipping' => $order->get_total_shipping()
154
		);
155
		$command = "ga( 'ec:setAction', 'purchase', " . wp_json_encode( $summary ) . " );";
156
		array_push( $command_array, $command );
157
158
		update_post_meta( $order_id, '_ga_tracked', 1 );
159
160
		return $command_array;
161
	}
162
163
	public function add_to_cart() {
164
		if ( ! Jetpack_Google_Analytics_Options::track_add_to_cart_is_enabled() ) {
165
			return;
166
		}
167
168
		if ( ! is_single() ) {
169
			return;
170
		}
171
172
		global $product;
173
174
		$product_sku_or_id = Jetpack_Google_Analytics_Utils::get_product_sku_or_id( $product );
175
		$selector = ".single_add_to_cart_button";
176
177
		wc_enqueue_js(
178
			"$( '" . esc_js( $selector ) . "' ).click( function() {
179
				var productDetails = {
180
					'id': '" . esc_js( $product_sku_or_id ) . "',
181
					'name' : '" . esc_js( $product->get_title() ) . "',
182
					'quantity': $( 'input.qty' ).val() ? $( 'input.qty' ).val() : '1',
183
				};
184
				ga( 'ec:addProduct', productDetails );
185
				ga( 'ec:setAction', 'add' );
186
				ga( 'send', 'event', 'UX', 'click', 'add to cart' );
187
			} );"
188
		);
189
	}
190
191
	public function loop_add_to_cart() {
192
		if ( ! Jetpack_Google_Analytics_Options::track_add_to_cart_is_enabled() ) {
193
			return;
194
		}
195
196
		if ( ! class_exists( 'WooCommerce' ) ) {
197
			return;
198
		}
199
200
		$minimum_woocommerce_active = class_exists( 'WooCommerce' ) && version_compare( WC_VERSION, '3.0', '>=' );
201
		if ( ! $minimum_woocommerce_active ) {
202
			return;
203
		}
204
205
		$selector = ".add_to_cart_button:not(.product_type_variable, .product_type_grouped)";
206
207
		wc_enqueue_js(
208
			"$( '" . esc_js( $selector ) . "' ).click( function() {
209
				var productSku = $( this ).data( 'product_sku' );
210
				var productID = $( this ).data( 'product_id' );
211
				var productDetails = {
212
					'id': productSku ? productSku : '#' + productID,
213
					'quantity': $( this ).data( 'quantity' ),
214
				};
215
				ga( 'ec:addProduct', productDetails );
216
				ga( 'ec:setAction', 'add' );
217
				ga( 'send', 'event', 'UX', 'click', 'add to cart' );
218
			} );"
219
		);
220
	}
221
222
	public function remove_from_cart() {
223
		if ( ! Jetpack_Google_Analytics_Options::enhanced_ecommerce_tracking_is_enabled() ) {
224
			return;
225
		}
226
227
		if ( ! Jetpack_Google_Analytics_Options::track_remove_from_cart_is_enabled() ) {
228
			return;
229
		}
230
231
		// We listen at div.woocommerce because the cart 'form' contents get forcibly
232
		// updated and subsequent removals from cart would then not have this click
233
		// handler attached
234
		wc_enqueue_js(
235
			"$( 'div.woocommerce' ).on( 'click', 'a.remove', function() {
236
				var productSku = $( this ).data( 'product_sku' );
237
				var productID = $( this ).data( 'product_id' );
238
				var quantity = $( this ).parent().parent().find( '.qty' ).val()
239
				var productDetails = {
240
					'id': productSku ? productSku : '#' + productID,
241
					'quantity': quantity ? quantity : '1',
242
				};
243
				ga( 'ec:addProduct', productDetails );
244
				ga( 'ec:setAction', 'remove' );
245
				ga( 'send', 'event', 'UX', 'click', 'remove from cart' );
246
			} );"
247
		);
248
	}
249
250
	/**
251
	* Adds the product ID and SKU to the remove product link (for use by remove_from_cart above) if not present
252
	*/
253
	public function remove_from_cart_attributes( $url, $key ) {
254
		if ( false !== strpos( $url, 'data-product_id' ) ) {
255
			return $url;
256
		}
257
258
		$item = WC()->cart->get_cart_item( $key );
259
		$product = $item[ 'data' ];
260
261
		$new_attributes = sprintf( 'href="%s" data-product_id="%s" data-product_sku="%s"',
262
			esc_attr( $url ),
263
			esc_attr( $product->get_id() ),
264
			esc_attr( $product->get_sku() )
265
			);
266
		$url = str_replace( 'href=', $new_attributes );
267
		return $url;
268
	}
269
270
	public function listing_impression() {
271
		if ( ! Jetpack_Google_Analytics_Options::enhanced_ecommerce_tracking_is_enabled() ) {
272
			return;
273
		}
274
275
		if ( ! Jetpack_Google_Analytics_Options::track_product_impressions_is_enabled() ) {
276
			return;
277
		}
278
279
		if ( isset( $_GET['s'] ) ) {
280
			$list = "Search Results";
281
		} else {
282
			$list = "Product List";
283
		}
284
285
		global $product, $woocommerce_loop;
286
		$product_sku_or_id = Jetpack_Google_Analytics_Utils::get_product_sku_or_id( $product );
287
288
		$item_details = array(
289
			'id' => $product_sku_or_id,
290
			'name' => $product->get_title(),
291
			'category' => Jetpack_Google_Analytics_Utils::get_product_categories_concatenated( $product ),
292
			'list' => $list,
293
			'position' => $woocommerce_loop['loop']
294
		);
295
		wc_enqueue_js( "ga( 'ec:addImpression', " . wp_json_encode( $item_details ) . " );" );
296
	}
297
298
	public function listing_click() {
299
		if ( ! Jetpack_Google_Analytics_Options::enhanced_ecommerce_tracking_is_enabled() ) {
300
			return;
301
		}
302
303
		if ( ! Jetpack_Google_Analytics_Options::track_product_clicks_is_enabled() ) {
304
			return;
305
		}
306
307
		if ( isset( $_GET['s'] ) ) {
308
			$list = "Search Results";
309
		} else {
310
			$list = "Product List";
311
		}
312
313
		global $product, $woocommerce_loop;
314
		$product_sku_or_id = Jetpack_Google_Analytics_Utils::get_product_sku_or_id( $product );
315
316
		$selector = ".products .post-" . esc_js( $product->get_id() ) . " a";
317
318
		$item_details = array(
319
			'id' => $product_sku_or_id,
320
			'name' => $product->get_title(),
321
			'category' => Jetpack_Google_Analytics_Utils::get_product_categories_concatenated( $product ),
322
			'position' => $woocommerce_loop['loop']
323
		);
324
325
		wc_enqueue_js(
326
			"$( '" . esc_js( $selector ) . "' ).click( function() {
327
				if ( true === $( this ).hasClass( 'add_to_cart_button' ) ) {
328
					return;
329
				}
330
331
				ga( 'ec:addProduct', " . wp_json_encode( $item_details ) . " );
332
				ga( 'ec:setAction', 'click', { list: '" . esc_js( $list ) . "' } );
333
				ga( 'send', 'event', 'UX', 'click', { list: '" . esc_js( $list ) . "' } );
334
			} );"
335
		);
336
	}
337
338
	public function product_detail() {
339
		if ( ! Jetpack_Google_Analytics_Options::enhanced_ecommerce_tracking_is_enabled() ) {
340
			return;
341
		}
342
343
		if ( ! Jetpack_Google_Analytics_Options::track_product_detail_view_is_enabled() ) {
344
			return;
345
		}
346
347
		global $product;
348
		$product_sku_or_id = Jetpack_Google_Analytics_Utils::get_product_sku_or_id( $product );
349
350
		$item_details = array(
351
			'id' => $product_sku_or_id,
352
			'name' => $product->get_title(),
353
			'category' => Jetpack_Google_Analytics_Utils::get_product_categories_concatenated( $product ),
354
			'price' => $product->get_price()
355
		);
356
		wc_enqueue_js(
357
			"ga( 'ec:addProduct', " . wp_json_encode( $item_details ) . " );" .
358
			"ga( 'ec:setAction', 'detail' );"
359
		);
360
	}
361
362
	public function checkout_process() {
363
		if ( ! Jetpack_Google_Analytics_Options::enhanced_ecommerce_tracking_is_enabled() ) {
364
			return;
365
		}
366
367
		if ( ! Jetpack_Google_Analytics_Options::track_checkout_started_is_enabled() ) {
368
			return;
369
		}
370
371
		$universal_commands = array();
372
		$cart = WC()->cart->get_cart();
373
374 View Code Duplication
		foreach ( $cart as $cart_item_key => $cart_item ) {
375
			/**
376
			* This filter is already documented in woocommerce/templates/cart/cart.php
377
			*/
378
			$product = apply_filters( 'woocommerce_cart_item_product', $cart_item['data'], $cart_item, $cart_item_key );
379
			$product_sku_or_id = Jetpack_Google_Analytics_Utils::get_product_sku_or_id( $product );
380
381
			$item_details = array(
382
				'id' => $product_sku_or_id,
383
				'name' => $product->get_title(),
384
				'category' => Jetpack_Google_Analytics_Utils::get_product_categories_concatenated( $product ),
385
				'price' => $product->get_price(),
386
				'quantity' => $cart_item[ 'quantity' ]
387
			);
388
389
			array_push( $universal_commands, "ga( 'ec:addProduct', " . wp_json_encode( $item_details ) . " );" );
390
		}
391
392
		array_push( $universal_commands, "ga( 'ec:setAction','checkout' );" );
393
394
		wc_enqueue_js( implode( "\r\n", $universal_commands ) );
395
	}
396
397
	public function send_pageview_in_footer() {
398
		if ( ! Jetpack_Google_Analytics_Options::has_tracking_code() ) {
399
			return;
400
		}
401
402
		if ( is_admin() ) {
403
			return;
404
		}
405
406
		if ( ! class_exists( 'WooCommerce' ) ) {
407
			return;
408
		}
409
410
		wc_enqueue_js( "ga( 'send', 'pageview' );" );
411
	}
412
}