Completed
Push — gm-17/payment-widget ( cb2702...55e70e )
by
unknown
13:32 queued 03:19
created

Jetpack_Google_Analytics   B

Complexity

Total Complexity 53

Size/Duplication

Total Lines 308
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 0

Importance

Changes 0
Metric Value
dl 0
loc 308
rs 7.4757
c 0
b 0
f 0
wmc 53
lcom 2
cbo 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
A get_instance() 0 7 2
C _get_url() 0 22 9
A _output_or_return() 0 7 2
B insert_code() 0 64 6
A _get_tracking_code() 0 9 3
A jetpack_wga_classic_anonymize_ip() 0 9 3
C jetpack_wga_classic_track_purchases() 0 69 13
B get_product_categories_concatenated() 0 16 6
C jetpack_wga_classic_track_add_to_cart() 0 35 8

How to fix   Complexity   

Complex Class

Complex classes like Jetpack_Google_Analytics 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, and based on these observations, apply Extract Interface, too.

1
<?php
2
/*
3
		Copyright 2006  Aaron D. Campbell  (email : [email protected])
4
5
    This program is free software; you can redistribute it and/or modify
6
    it under the terms of the GNU General Public License as published by
7
    the Free Software Foundation; either version 2 of the License, or
8
    (at your option) any later version.
9
10
    This program is distributed in the hope that it will be useful,
11
    but WITHOUT ANY WARRANTY; without even the implied warranty of
12
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
    GNU General Public License for more details.
14
15
    You should have received a copy of the GNU General Public License
16
    along with this program; if not, write to the Free Software
17
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18
*/
19
20
/**
21
 * Jetpack_Google_Analytics is the class that handles ALL of the plugin functionality.
22
 * It helps us avoid name collisions
23
 * http://codex.wordpress.org/Writing_a_Plugin#Avoiding_Function_Name_Collisions
24
 */
25
class Jetpack_Google_Analytics {
26
27
	/**
28
	 * @var Jetpack_Google_Analytics - Static property to hold our singleton instance
29
	 */
30
	static $instance = false;
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $instance.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
31
32
	/**
33
	 * This is our constructor, which is private to force the use of get_instance()
34
	 *
35
	 * @return void
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
36
	 */
37
	private function __construct() {
38
		add_filter( 'jetpack_wga_classic_custom_vars', array( $this, 'jetpack_wga_classic_anonymize_ip' ) );
39
		add_filter( 'jetpack_wga_classic_custom_vars', array( $this, 'jetpack_wga_classic_track_purchases' ) );
40
		add_action( 'wp_footer', array( $this, 'insert_code' ) );
41
		add_action( 'wp_footer', array( $this, 'jetpack_wga_classic_track_add_to_cart' ) );
42
	}
43
44
	/**
45
	 * Function to instantiate our class and make it a singleton
46
	 */
47
	public static function get_instance() {
48
		if ( ! self::$instance ) {
49
			self::$instance = new self;
50
		}
51
52
		return self::$instance;
53
	}
54
55
	/**
56
	 * Used to generate a tracking URL
57
	 *
58
	 * @param array $track - Must have ['data'] and ['code'].
59
	 * @return string - Tracking URL
60
	 */
61
	private function _get_url( $track ) {
62
		$site_url = ( is_ssl() ? 'https://':'http://' ) . sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) ); // Input var okay.
63
		foreach ( $track as $k => $value ) {
64
			if ( strpos( strtolower( $value ), strtolower( $site_url ) ) === 0 ) {
65
				$track[ $k ] = substr( $track[ $k ], strlen( $site_url ) );
66
			}
67
			if ( 'data' === $k ) {
68
				$track[ $k ] = preg_replace( '/^https?:\/\/|^\/+/i', '', $track[ $k ] );
69
			}
70
71
			// This way we don't lose search data.
72
			if ( 'data' === $k && 'search' === $track['code'] ) {
73
				$track[ $k ] = rawurlencode( $track[ $k ] );
74
			} else {
75
				$track[ $k ] = preg_replace( '/[^a-z0-9\.\/\+\?=-]+/i', '_', $track[ $k ] );
76
			}
77
78
			$track[ $k ] = trim( $track[ $k ], '_' );
79
		}
80
		$char = ( strpos( $track['data'], '?' ) === false ) ? '?' : '&amp;';
81
		return str_replace( "'", "\'", "/{$track['code']}/{$track['data']}{$char}referer=" . rawurlencode( isset( $_SERVER['HTTP_REFERER'] ) ? $_SERVER['HTTP_REFERER'] : '' ) ); // Input var okay.
82
	}
83
84
	/**
85
	 * Maybe output or return, depending on the context
86
	 */
87
	private function _output_or_return( $val, $maybe ) {
88
		if ( $maybe ) {
89
			echo $val . "\r\n";
90
		} else {
91
			return $val;
92
		}
93
	}
94
95
	/**
96
	 * This injects the Google Analytics code into the footer of the page.
97
	 *
98
	 * @param bool[optional] $output - defaults to true, false returns but does NOT echo the code.
99
	 */
100
	public function insert_code( $output = true ) {
101
		// If $output is not a boolean false, set it to true (default).
102
		$output = ( false !== $output);
103
104
		$tracking_id = $this->_get_tracking_code();
105
		if ( empty( $tracking_id ) ) {
106
			return $this->_output_or_return( '<!-- Your Google Analytics Plugin is missing the tracking ID -->', $output );
107
		}
108
109
		// If we're in the admin_area, return without inserting code.
110
		if ( is_admin() ) {
111
			return $this->_output_or_return( '<!-- Your Google Analytics Plugin is set to ignore Admin area -->', $output );
112
		}
113
114
		$custom_vars = array(
115
			"_gaq.push(['_setAccount', '{$tracking_id}']);",
116
		);
117
118
		$track = array();
119
		if ( is_404() ) {
120
			// This is a 404 and we are supposed to track them.
121
			$custom_vars[] = "_gaq.push(['_trackEvent', '404', document.location.href, document.referrer]);";
122
		} elseif ( is_search() ) {
123
			// Set track for searches, if it's a search, and we are supposed to.
124
			$track['data'] = sanitize_text_field( wp_unslash( $_REQUEST['s'] ) ); // Input var okay.
125
			$track['code'] = 'search';
126
		}
127
128
		if ( ! empty( $track ) ) {
129
			$track['url'] = $this->_get_url( $track );
130
			// adjust the code that we output, account for both types of tracking.
131
			$track['url'] = esc_js( str_replace( '&', '&amp;', $track['url'] ) );
132
			$custom_vars[] = "_gaq.push(['_trackPageview','{$track['url']}']);";
133
		} else {
134
			$custom_vars[] = "_gaq.push(['_trackPageview']);";
135
		}
136
137
		/**
138
		 * Allow for additional elements to be added to the classic Google Analytics queue (_gaq) array
139
		 *
140
		 * @since 5.4.0
141
		 *
142
		 * @param array $custom_vars Array of classic Google Analytics queue elements
143
		 */
144
		$custom_vars = apply_filters( 'jetpack_wga_classic_custom_vars', $custom_vars );
145
146
		// Ref: https://developers.google.com/analytics/devguides/collection/gajs/gaTrackingEcommerce#Example
147
		$async_code = "<!-- Jetpack Google Analytics -->
148
			<script type='text/javascript'>
149
				var _gaq = _gaq || [];
150
				%custom_vars%
151
152
				(function() {
153
					var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
154
					ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
155
					var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
156
				})();
157
			</script>";
158
159
		$custom_vars_string = implode( "\r\n", $custom_vars );
160
		$async_code = str_replace( '%custom_vars%', $custom_vars_string, $async_code );
161
162
		return $this->_output_or_return( $async_code, $output );
163
	}
164
165
	/**
166
	 * Used to get the tracking code option
167
	 *
168
	 * @return tracking code option value.
169
	 */
170
	private function _get_tracking_code() {
171
		$o = get_option( 'jetpack_wga' );
172
173
		if ( isset( $o['code'] ) && preg_match( '#UA-[\d-]+#', $o['code'], $matches ) ) {
174
				return $o['code'];
175
		}
176
177
		return '';
178
	}
179
180
	/**
181
	 * Used to filter in the anonymize IP snippet to the custom vars array for classic analytics
182
	 * Ref https://developers.google.com/analytics/devguides/collection/gajs/methods/gaJSApi_gat#_gat._anonymizelp
183
	 * @param array custom vars to be filtered
184
	 * @return array possibly updated custom vars
185
	 */
186
	public function jetpack_wga_classic_anonymize_ip( $custom_vars ) {
187
		$o = get_option( 'jetpack_wga' );
188
		$anonymize_ip = isset( $o[ 'anonymize_ip' ] ) ? $o[ 'anonymize_ip' ] : false;
189
		if ( $anonymize_ip ) {
190
			array_push( $custom_vars, "_gaq.push(['_gat._anonymizeIp']);" );
191
		}
192
193
		return $custom_vars;
194
	}
195
196
	/**
197
	 * Used to filter in the order details to the custom vars array for classic analytics
198
	 * @param array custom vars to be filtered
199
	 * @return array possibly updated custom vars
200
	 */
201
	public function jetpack_wga_classic_track_purchases( $custom_vars ) {
202
		global $wp;
203
204
		if ( ! class_exists( 'WooCommerce' ) ) {
205
			return $custom_vars;
206
		}
207
208
		// Ref: https://developers.google.com/analytics/devguides/collection/gajs/gaTrackingEcommerce#Example
209
		$o = get_option( 'jetpack_wga' );
210
		$ec_track_purchases = isset( $o[ 'ec_track_purchases' ] ) ? $o[ 'ec_track_purchases' ] : false;
211
		$minimum_woocommerce_active = class_exists( 'WooCommerce' ) && version_compare( WC_VERSION, '3.0', '>=' );
212
		if ( $ec_track_purchases && $minimum_woocommerce_active && is_order_received_page() ) {
213
			$order_id = isset( $wp->query_vars['order-received'] ) ? $wp->query_vars['order-received'] : 0;
214
			if ( 0 < $order_id && 1 != get_post_meta( $order_id, '_ga_tracked', true ) ) {
215
				$order = new WC_Order( $order_id );
216
217
				// [ '_add_Trans', '123', 'Site Title', '21.00', '1.00', '5.00', 'Snohomish', 'WA', 'USA' ]
218
				array_push(
219
					$custom_vars,
220
					sprintf(
221
						'_gaq.push( %s );', json_encode(
222
							array(
223
								'_addTrans',
224
								(string) $order->get_order_number(),
225
								get_bloginfo( 'name' ),
226
								(string) $order->get_total(),
227
								(string) $order->get_total_tax(),
228
								(string) $order->get_total_shipping(),
229
								(string) $order->get_billing_city(),
230
								(string) $order->get_billing_state(),
231
								(string) $order->get_billing_country()
232
							)
233
						)
234
					)
235
				);
236
237
				// Order items
238
				if ( $order->get_items() ) {
239
					foreach ( $order->get_items() as $item ) {
240
						$product = $order->get_product_from_item( $item );
241
						$product_sku_or_id = $product->get_sku() ? $product->get_sku() : $product->get_id();
242
243
						array_push(
244
							$custom_vars,
245
							sprintf(
246
								'_gaq.push( %s );', json_encode(
247
									array(
248
										'_addItem',
249
										(string) $order->get_order_number(),
250
										(string) $product_sku_or_id,
251
										$item['name'],
252
										self::get_product_categories_concatenated( $product ),
253
										(string) $order->get_item_total( $item ),
254
										(string) $item['qty']
255
									)
256
								)
257
							)
258
						);
259
					}
260
				} // get_items
261
262
				// Mark the order as tracked
263
				update_post_meta( $order_id, '_ga_tracked', 1 );
264
				array_push( $custom_vars, "_gaq.push(['_trackTrans']);" );
265
			} // order not yet tracked
266
		} // is order received page
267
268
		return $custom_vars;
269
	}
270
271
	/**
272
	 * Gets product categories or varation attributes as a formatted concatenated string
273
	 * @param WC_Product
274
	 * @return string
275
	 */
276
	public function get_product_categories_concatenated( $product ) {
277
		$variation_data = $product->is_type( 'variation' ) ? wc_get_product_variation_attributes( $product->get_id() ) : '';
278
		if ( is_array( $variation_data ) && ! empty( $variation_data ) ) {
279
			$line = wc_get_formatted_variation( $variation_data, true );
280
		} else {
281
			$out = array();
282
			$categories = get_the_terms( $product->get_id(), 'product_cat' );
283
			if ( $categories ) {
284
				foreach ( $categories as $category ) {
285
					$out[] = $category->name;
286
				}
287
			}
288
			$line = join( "/", $out );
289
		}
290
		return $line;
291
	}
292
293
	/**
294
	 * Used to add footer javascript to track user clicking on add-to-cart buttons
295
	 * on single views (.single_add_to_cart_button) and list views (.add_to_cart_button)
296
	 */
297
	public function jetpack_wga_classic_track_add_to_cart() {
298
		if ( ! class_exists( 'WooCommerce' ) ) {
299
			return;
300
		}
301
302
		$tracking_id = $this->_get_tracking_code();
303
		if ( empty( $tracking_id ) ) {
304
			return;
305
		}
306
307
		$o = get_option( 'jetpack_wga' );
308
		$ec_track_add_to_cart = isset( $o[ 'ec_track_add_to_cart' ] ) ? $o[ 'ec_track_add_to_cart' ] : false;
309
		if ( $ec_track_add_to_cart ) {
310
			if ( is_product() ) { // product page
311
				global $product;
312
				$product_sku_or_id = $product->get_sku() ? $product->get_sku() : "#" + $product->get_id();
313
				wc_enqueue_js(
314
					"jQuery( function( $ ) {
315
						$( '.single_add_to_cart_button' ).click( function() {
316
							_gaq.push(['_trackEvent', 'Products', 'Add to Cart', '#" . esc_js( $product_sku_or_id ) . "']);
317
						} );
318
					} );"
319
				);
320
			} else if ( is_woocommerce() ) { // any other page that uses templates (like product lists, archives, etc)
321
				wc_enqueue_js(
322
					"jQuery( function( $ ) {
323
						$( '.add_to_cart_button:not(.product_type_variable, .product_type_grouped)' ).click( function() {
324
							var label = $( this ).data( 'product_sku' ) ? $( this ).data( 'product_sku' ) : '#' + $( this ).data( 'product_id' );
325
							_gaq.push(['_trackEvent', 'Products', 'Add to Cart', label]);
326
						} );
327
					} );"
328
				);
329
			}
330
		}
331
	}
332
}
333
334
global $jetpack_google_analytics;
335
$jetpack_google_analytics = Jetpack_Google_Analytics::get_instance();
336