analytics.php 13.1 KB
Newer Older
alain's avatar
alain committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
<?php

if (!defined('UPDRAFTCENTRAL_CLIENT_DIR')) die('No access.');

/**
 * Handles Analytics Commands
 *
 * @method array ga_checker()
 * @method array get_access_token()
 * @method array set_authorization_code()
 */
class UpdraftCentral_Analytics_Commands extends UpdraftCentral_Commands {

	private $scope = 'https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/analytics.readonly';
	
	private $endpoint = 'https://accounts.google.com/o/oauth2/auth';
	
	private $token_info_endpoint = 'https://www.googleapis.com/oauth2/v1/tokeninfo';
	
	private $access_key = 'updraftcentral_auth_server_access';
	
	private $auth_endpoint;
	
	private $client_id;
	
	private $view_key = 'updraftcentral_analytics_views';
	
	private $tracking_id_key = 'updraftcentral_analytics_tracking_id';
	
	private $expiration;

	/**
	 * Constructor
	 */
	public function __construct() {
		$this->auth_endpoint = defined('UPDRAFTPLUS_GOOGLECLOUD_CALLBACK_URL') ? UPDRAFTPLUS_GOOGLECLOUD_CALLBACK_URL : 'https://auth.updraftplus.com/auth/googleanalytics';
		$this->client_id = defined('UPDRAFTPLUS_GOOGLECLOUD_CLIENT_ID') ? UPDRAFTPLUS_GOOGLECLOUD_CLIENT_ID : '306245874349-6s896c3tjpra26ns3dpplhqcl6rv6qlb.apps.googleusercontent.com';

		// Set transient expiration - default for 24 hours
		$this->expiration = 86400;
	}
	
	/**
	 * Checks whether Google Analytics (GA) is installed or setup
	 *
	 * N.B. This check assumes GA is installed either using "wp_head" or "wp_footer" (e.g. attached
	 * to the <head/> or somewhere before </body>). It does not recursively check all the pages
	 * of the website to find if GA is installed on each or one of those pages, but only on the main/root page.
	 *
	 * @return array $result An array containing "ga_installed" property which returns "true" if GA (Google Analytics) is installed, "false" otherwise.
	 */
	public function ga_checker() {

		try {
			
			// Retrieves the tracking code/id if available
			$tracking_id = $this->get_tracking_id();
			$installed = true;

			// If tracking code/id is currently not available then we
			// parse the needed information from the buffered content through
			// the "wp_head" and "wp_footer" hooks.
			if (false === $tracking_id) {
				$info = $this->extract_tracking_id();

				$installed = $info['installed'];
				$tracking_id = $info['tracking_id'];
			}

			// Get access token to be use to generate the report.
			$access_token = $this->_get_token();

			if (empty($access_token)) {
				// If we don't get a valid access token then that would mean
				// the access has been revoked by the user or UpdraftCentral was not authorized yet
				// to access the user's analytics data, thus, we're clearing
				// any previously stored user access so we're doing some housekeeping here.
				$this->clear_user_access();
			}

			// Wrap and combined information for the requesting
			// client's consumption
			$result = array(
				'ga_installed' => $installed,
				'tracking_id' => $tracking_id,
				'client_id' => $this->client_id,
				'redirect_uri' => $this->auth_endpoint,
				'scope' => $this->scope,
				'access_token' => $access_token,
				'endpoint' => $this->endpoint
			);

		} catch (Exception $e) {
			$result = array('error' => true, 'message' => 'generic_response_error', 'values' => array($e->getMessage()));
		}
		
		return $this->_response($result);
	}

	/**
	 * Extracts Google Tracking ID from contents rendered through the "wp_head" and "wp_footer" action hooks
	 *
	 * @internal
	 * @return array $result An array containing the result of the extraction.
	 */
	private function extract_tracking_id() {

		// Define result array
		$result = array();

		// Retrieve header content
		ob_start();
		do_action('wp_head');
		$header_content = ob_get_clean();
		
		// Extract analytics information if available.
		$output = $this->parse_content($header_content);
		$result['installed'] = $output['installed'];
		$result['tracking_id'] = $output['tracking_id'];
		
		// If it was not found, then now try the footer
		if (empty($result['tracking_id'])) {
			// Retrieve footer content
			ob_start();
			do_action('wp_footer');
			$footer_content = ob_get_clean();
			$output = $this->parse_content($footer_content);
			$result['installed'] = $output['installed'];
			$result['tracking_id'] = $output['tracking_id'];
		}

		if (!empty($result['tracking_id'])) {
			set_transient($this->tracking_id_key, $result['tracking_id'], $this->expiration);
		}

		return $result;
	}
	
	/**
	 * Gets access token
	 *
	 * Validates whether the system currently have a valid token to use when connecting to Google Analytics API.
	 * If not, then it will send a token request based on the authorization code we stored during the
	 * authorization phase. Otherwise, it will return an empty token.
	 *
	 * @return array $result An array containing the Google Analytics API access token.
	 */
	public function get_access_token() {
		
		try {

			// Loads or request a valid token to use
			$access_token = $this->_get_token();
			
			if (!empty($access_token)) {
				$result = array('access_token' => $access_token);
			} else {
				$result = array('error' => true, 'message' => 'ga_token_retrieval_failed', 'values' => array());
			}
		
		} catch (Exception $e) {
			$result = array('error' => true, 'message' => 'generic_response_error', 'values' => array($e->getMessage()));
		}
		
		return $this->_response($result);
	}

	/**
	 * Clears any previously stored user access
	 *
	 * @return bool
	 */
	public function clear_user_access() {
		return delete_option($this->access_key);
	}

	/**
	 * Saves user is and access token received from the auth server
	 *
	 * @param array $query Parameter array containing the user id and access token from the auth server.
	 * @return array $result An array containing a "success" or "failure" message as a result of the current process.
	 */
	public function save_user_access($query) {
		
		try {

			$token = get_option($this->access_key, false);
			$result = array();

			if (false === $token) {
				$token = array(
					'user_id' => base64_decode(urldecode($query['user_id'])),
					'access_token' => base64_decode(urldecode($query['access_token']))
				);

				if (false !== update_option($this->access_key, $token)) {
					$result = array('error' => false, 'message' => 'ga_access_saved', 'values' => array());
				} else {
					$result = array('error' => true, 'message' => 'ga_access_saving_failed', 'values' => array($query['access_token']));
				}
			}
		
		} catch (Exception $e) {
			$result = array('error' => true, 'message' => 'generic_response_error', 'values' => array($e->getMessage()));
		}
		
		return $this->_response($result);
	}

	/**
	 * Saves the tracking code/id manually (user input)
	 *
	 * @param array $query Parameter array containing the tracking code/id to save.
	 * @return array $result An array containing the result of the process.
	 */
	public function save_tracking_id($query) {
		try {
			$tracking_id = $query['tracking_id'];
			$saved = false;

			if (!empty($tracking_id)) {
				$saved = set_transient($this->tracking_id_key, $tracking_id, $this->expiration);
			}

			$result = array('saved' => $saved);
		} catch (Exception $e) {
			$result = array('error' => true, 'message' => 'generic_response_error', 'values' => array($e->getMessage()));
		}

		return $this->_response($result);
	}

	/**
	 * Retrieves any available access token either previously saved info or
	 * from a new request from the Google Server.
	 *
	 * @internal
	 * @return string $authorization_code
	 */
	private function _get_token() {

		// Retrieves the tracking code/id if available
		$tracking_id = $this->get_tracking_id();
		$access_token = '';
		
		$token = get_option($this->access_key, false);
		if (false !== $token) {
			$access_token = isset($token['access_token']) ? $token['access_token'] : '';
			$user_id = isset($token['user_id']) ? $token['user_id'] : '';

			if ((!empty($access_token) && !$this->_token_valid($access_token)) || (!empty($user_id) && empty($access_token) && !empty($tracking_id))) {
				if (!empty($user_id)) {
					$args = array(
						'headers' => apply_filters('updraftplus_auth_headers', array())
					);
					
					$response = wp_remote_get($this->auth_endpoint.'?user_id='.$user_id.'&code=ud_googleanalytics_code', $args);
					if (is_wp_error($response)) {
						throw new Exception($response->get_error_message());
					} else {
						if (is_array($response)) {
							
							$body = json_decode($response['body'], true);
							$token_response = array();

							if (is_array($body) && !isset($body['error'])) {
								$token_response = json_decode(base64_decode($body[0]), true);
							}

							if (is_array($token_response) && isset($token_response['access_token'])) {
								$access_token = $token_response['access_token'];
							} else {
								// If we don't get any valid response then that would mean that the
								// permission was already revoked. Thus, we need to re-authorize the
								// user before using the analytics feature once again.
								$access_token = '';
							}

							$token['access_token'] = $access_token;
							update_option($this->access_key, $token);
						}
					}
				}
			}
		}
		
		return $access_token;
	}
	
	/**
	 * Verifies whether the access token is still valid for use
	 *
	 * @internal
	 * @param string $token The access token to be check and validated
	 * @return bool
	 * @throws Exception If an error has occurred while connecting to the Google Server.
	 */
	private function _token_valid($token) {
		
		$response = wp_remote_get($this->token_info_endpoint.'?access_token='.$token);
		if (is_wp_error($response)) {
			throw new Exception($response->get_error_message());
		} else {
			if (is_array($response)) {
				$response = json_decode($response['body'], true);
				if (!empty($response)) {
					if (!isset($response['error']) && !isset($response['error_description'])) {
						return true;
					}
				}
			}
		}
		
		return false;
	}

	/**
	 * Parses and extracts the google analytics information (NEEDED)
	 *
	 * @internal
	 * @param string $content The content to parse
	 * @return array An array containing the status of the process along with the tracking code/id
	 */
	private function parse_content($content) {

		$installed = false;
		$gtm_installed = false;
		$tracking_id = '';
		$script_file_found = false;
		$tracking_id_found = false;

		// Pull google analytics script file(s)
		preg_match_all('/<script\b[^>]*>([\s\S]*?)<\/script>/i', $content, $scripts);
		for ($i=0; $i < count($scripts[0]); $i++) {
			// Check for Google Analytics file
			if (stristr($scripts[0][$i], 'ga.js') || stristr($scripts[0][$i], 'analytics.js')) {
				$script_file_found = true;
			}
			
			// Check for Google Tag Manager file
			// N.B. We are not checking for GTM but this check will be useful when
			// showing the notice to the user if we haven't found Google Analytics
			// directly being installed on the page.
			if (stristr($scripts[0][$i], 'gtm.js')) {
				$gtm_installed = true;
			}
		}

		// Pull tracking code
		preg_match_all('/UA-[0-9]{5,}-[0-9]{1,}/i', $content, $codes);
		if (count($codes) > 0) {
			if (!empty($codes[0])) {
				$tracking_id_found = true;
				$tracking_id = $codes[0][0];
			}
		}

		// If we found both the script and the tracking code then it is safe
		// to say that Google Analytics (GA) is installed. Thus, we're returning
		// "true" as a response.
		if ($script_file_found && $tracking_id_found) {
			$installed = true;
		}

		// Return result of process.
		return array(
			'installed' => $installed,
			'gtm_installed' => $gtm_installed,
			'tracking_id' => $tracking_id
		);
	}

	/**
	 * Retrieves the "analytics_tracking_id" transient
	 *
	 * @internal
	 * @return mixed Returns the value of the saved transient. Returns "false" if the transient does not exist.
	 */
	private function get_tracking_id() {
		return get_transient($this->tracking_id_key);
	}


	/**
	 * Returns the current tracking id
	 *
	 * @return array $result An array containing the Google Tracking ID.
	 */
	public function get_current_tracking_id() {
		try {

			// Get current site transient stored for this key
			$tracking_id = get_transient($this->tracking_id_key);

			// Checks whether we have a valid token
			$access_token = $this->_get_token();
			if (empty($access_token)) {
				$tracking_id = '';
			}

			if (false === $tracking_id) {
				$result = $this->extract_tracking_id();
			} else {
				$result = array(
					'installed' => true,
					'tracking_id' => $tracking_id
				);
			}
		
		} catch (Exception $e) {
			$result = array('error' => true, 'message' => 'generic_response_error', 'values' => array($e->getMessage()));
		}
		
		return $this->_response($result);
	}

	/**
	 * Clears user access from database
	 *
	 * @return array $result An array containing the "Remove" confirmation whether the action succeeded or not.
	 */
	public function remove_user_access() {
		try {

			// Clear user access
			$is_cleared = $this->clear_user_access();
			
			if (false !== $is_cleared) {
				$result = array('removed' => true);
			} else {
				$result = array('error' => true, 'message' => 'user_access_remove_failed', 'values' => array());
			}
		
		} catch (Exception $e) {
			$result = array('error' => true, 'message' => 'generic_response_error', 'values' => array($e->getMessage()));
		}
		
		return $this->_response($result);
	}
}