PluginProbe
Analytics Insights – Google Analytics Dashboard for WordPress / trunk
Analytics Insights – Google Analytics Dashboard for WordPress vtrunk
6.3.6 6.3.7 6.3.8 6.3.9 trunk 5.4.1 5.4.2 5.4.2.1 5.4.3 5.4.4 5.4.5 5.4.6 5.4.7 5.5.2 5.5.3 5.5.4 5.5.5 5.5.6 5.6 5.6.1 5.6.2 5.6.3 5.6.4 5.6.5 5.6.6 All 61 releases
analytics-insights / tools / gapi.php

gapi.php in Analytics Insights – Google Analytics Dashboard for WordPress trunk, at tools/gapi.php

1,429 lines 56.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Author: Alin Marcu
4 * Author URI: https://deconf.com
5 * Copyright 2013 Alin Marcu
6 * License: GPLv2 or later
7 * License URI: https://www.gnu.org/licenses/gpl-2.0.html
8 */
9 // Exit if accessed directly
10 if ( ! defined( 'ABSPATH' ) )
11 exit();
12 if ( ! class_exists( 'AIWP_GAPI_Controller' ) ) {
13
14 final class AIWP_GAPI_Controller {
15
16 public $client;
17
18 public $service;
19
20 public $service_ga3_reporting;
21
22 public $service_ga4_admin;
23
24 public $service_ga4_data;
25
26 public $timeshift;
27
28 public $quotauser;
29
30 private $aiwp;
31
32 private $client_id;
33
34 private $client_secret;
35
36 private $redirect_uri;
37
38 private $state;
39
40 private $token_uri;
41
42 private $revoke_uri;
43
44 public function __construct() {
45 $this->aiwp = AIWP();
46 $this->quotauser = 'u' . get_current_user_id() . 's' . get_current_blog_id();
47 $security = wp_create_nonce( 'aiwp_state' );
48
49 if ( $this->aiwp->config->options['user_api'] ) {
50 $this->client_id = $this->aiwp->config->options['client_id'];
51 $this->client_secret = $this->aiwp->config->options['client_secret'];
52 if ( is_multisite() && $this->aiwp->config->options['network_mode'] ) {
53 $redirect_uri = network_admin_url( 'admin.php?page=aiwp_settings' );
54 } else {
55 $redirect_uri = admin_url( 'admin.php?page=aiwp_settings' );
56 }
57 $this->redirect_uri = $redirect_uri;
58 $this->token_uri = 'https://oauth2.googleapis.com/token';
59 $this->revoke_uri = 'https://oauth2.googleapis.com/revoke';
60 $this->state = $security;
61 } else {
62 $this->client_id = '220758964178-rhheb4146405g3fs6e4qjkk0rnf5q9q5.apps.googleusercontent.com';
63 $this->client_secret = 'GOCSPX';
64 $this->redirect_uri = AIWP_ENDPOINT_URL . 'oauth2callback.php';
65 $this->token_uri = AIWP_ENDPOINT_URL . 'aiwp-token.php';
66 $this->revoke_uri = AIWP_ENDPOINT_URL . 'aiwp-revoke.php';
67 if ( is_multisite() && $this->aiwp->config->options['network_mode'] ) {
68 $state_uri = network_admin_url( 'admin.php?page=aiwp_settings' ) . '&state=' . $security;
69 } else {
70 $state_uri = admin_url( 'admin.php?page=aiwp_settings' ) . '&state=' . $security;
71 }
72 $this->state = $state_uri;
73 }
74
75 if ( $this->aiwp->config->options['token'] ) {
76 $this->refresh_token();
77 }
78
79 }
80
81 /**
82 * Creates the oauth2 link for Google API authorization
83 * @return string
84 */
85 public function createAuthUrl() {
86 $scope = 'https://www.googleapis.com/auth/analytics.readonly';
87 // @formatter:off
88 $auth_url = 'https://accounts.google.com/o/oauth2/v2/auth?';
89 $query_arr = array(
90 'client_id' => $this->client_id,
91 'redirect_uri' => $this->redirect_uri,
92 'response_type' => 'code',
93 'scope' => $scope,
94 'state' => $this->state,
95 'access_type' => 'offline',
96 'prompt' => 'consent',
97 );
98 // @formatter:on
99 $auth_url = $auth_url . http_build_query( $query_arr );
100 return $auth_url;
101 }
102
103 /**
104 * Handles the exchange of an access code with a token
105 * @param string $access_code
106 * @return string|mixed
107 */
108 public function authenticate( $access_code ) {
109 // @formatter:off
110 $token_data = array(
111 'client_id' => $this->client_id,
112 'client_secret' => $this->client_secret,
113 'code' => $access_code,
114 'redirect_uri' => $this->redirect_uri,
115 'grant_type' => 'authorization_code',
116 );
117 $request_args = array( 'body' => $token_data, 'headers' => array( 'Referer' => AIWP_CURRENT_VERSION ) );
118 // @formatter:on
119 $response = wp_remote_post( $this->token_uri, $request_args );
120 if ( is_wp_error( $response ) ) {
121 $timeout = $this->get_timeouts();
122 AIWP_Tools::set_error( $response, $timeout );
123 return false;
124 }
125 $body = wp_remote_retrieve_body( $response );
126 $token = json_decode( $body, true );
127 if ( isset( $token['error'] ) ) {
128 $timeout = $this->get_timeouts();
129 $error = new WP_Error();
130 if ( isset( $token['error']['code'] ) && isset( $token['error']['status'] ) ) {
131 $error->add( $token['error']['code'], $token['error']['message'], array( $token['error']['status'], 'trying to exchange access code for token' ) );
132 } else if ( isset( $token['error'] ) && isset( $token['error_description'] ) ) {
133 $error->add( $token['error'], $token['error_description'], 'trying to exchange access code for token' );
134 } else if ( isset( $token['error']['code'] ) && isset( $token['error']['message'] ) ) {
135 $error->add( $token['error']['code'], $token['error']['message'], 'trying to verify site' );
136 }
137 AIWP_Tools::set_error( $error, $timeout );
138 return false;
139 }
140 if ( isset( $token['access_token'] ) ) {
141 return $token;
142 } else {
143 return false;
144 }
145 }
146
147 /**
148 * Handles the token refresh process
149 * @return string|number|boolean
150 */
151 public function refresh_token() {
152 $token = (array) $this->aiwp->config->options['token'];
153 $refresh_token = $token['refresh_token'];
154 $challenge = ( isset( $token['challenge'] ) && '' != $token['challenge'] ) ? $token['challenge'] : '';
155 if ( ! $token || ! isset( $token['expires_in'] ) || ( $token['created'] + ( $token['expires_in'] - 30 ) ) < time() ) {
156 // @formatter:off
157 $post_data = array(
158 'client_id' => $this->client_id,
159 'client_secret' => $this->aiwp->config->options['user_api'] ? $this->client_secret : $this->client_secret . '-' . $challenge,
160 'refresh_token' => $refresh_token,
161 'grant_type' => 'refresh_token'
162 );
163 // @formatter:on
164 $request_args = array( 'body' => $post_data, 'headers' => array( 'Referer' => AIWP_CURRENT_VERSION ) );
165 if ( $this->aiwp->config->options['user_api'] ) {
166 $token_uri = 'https://oauth2.googleapis.com/token';
167 } else {
168 $token_uri = $challenge ? 'https://oauth2.googleapis.com/token' : AIWP_ENDPOINT_URL . 'aiwp-token.php';
169 }
170 $response = wp_remote_post( $token_uri, $request_args );
171 if ( is_wp_error( $response ) ) {
172 $timeout = $this->get_timeouts();
173 AIWP_Tools::set_error( $response, $timeout );
174 } else {
175 $body = wp_remote_retrieve_body( $response );
176 if ( is_string( $body ) && ! empty( $body ) ) {
177 $newtoken = json_decode( $body, true );
178 if ( isset( $newtoken['error'] ) ) {
179 $timeout = $this->get_timeouts();
180 $error = new WP_Error();
181 if ( isset( $newtoken['error']['code'] ) && isset( $newtoken['error']['status'] ) ) {
182 $error->add( $newtoken['error']['code'], $newtoken['error']['message'], array( $newtoken['error']['status'], 'trying to refresh token' ) );
183 } else if ( isset( $newtoken['error'] ) && isset( $newtoken['error_description'] ) ) {
184 $error->add( $newtoken['error'], $newtoken['error_description'], 'trying to refresh token' );
185 } else if ( isset( $newtoken['error']['code'] ) && isset( $newtoken['error']['message'] ) ) {
186 $error->add( $newtoken['error']['code'], $newtoken['error']['message'], 'trying to verify site' );
187 }
188 AIWP_Tools::set_error( $error, $timeout );
189 return false;
190 }
191 if ( ! empty( $newtoken ) && isset( $newtoken['access_token'] ) ) {
192 if ( ! isset( $newtoken['created'] ) ) {
193 $newtoken['created'] = time();
194 }
195 if ( ! isset( $newtoken['refresh_token'] ) ) {
196 $newtoken['refresh_token'] = $refresh_token;
197 }
198 if ( ! isset( $newtoken['challenge'] ) && '' != $challenge ) {
199 $newtoken['challenge'] = $challenge;
200 }
201 $this->aiwp->config->options['token'] = $newtoken;
202 } else {
203 $this->aiwp->config->options['token'] = false;
204 }
205 } else {
206 $this->aiwp->config->options['token'] = false;
207 }
208 if ( is_multisite() && $this->aiwp->config->options['network_mode'] ) {
209 $this->aiwp->config->set_plugin_options( true );
210 } else {
211 $this->aiwp->config->set_plugin_options();
212 }
213 }
214 }
215 if ( $this->aiwp->config->options['token'] ) {
216 return true;
217 } else {
218 return false;
219 }
220 }
221
222 /**
223 * Handles the token reset process
224 *
225 * @param
226 * $all
227 */
228 public function reset_token( $all = false, $valid_token = true ) {
229 if ( $all ) {
230 $this->aiwp->config->options['ga4_webstreams_list'] = array();
231 $this->aiwp->config->options['webstream_jail'] = '';
232 }
233 $this->aiwp->config->options['token'] = false;
234 if ( is_multisite() && $this->aiwp->config->options['network_mode'] ) {
235 $this->aiwp->config->set_plugin_options( true );
236 } else {
237 $this->aiwp->config->set_plugin_options();
238 }
239 }
240
241 /**
242 * Handles the token revoke process
243 *
244 * @param
245 * $all
246 */
247 public function revoke_token( $all = false, $valid_token = true ) {
248 // See notes don't use unless mandatory
249 $token = (array) $this->aiwp->config->options['token'];
250 if ( isset( $token['refresh_token'] ) && $valid_token ) {
251 // @formatter:off
252 $post_data = array(
253 'token' => $token['refresh_token'],
254 );
255 // @formatter:on
256 $request_args = array( 'body' => $post_data, 'headers' => array( 'Referer' => AIWP_CURRENT_VERSION ) );
257 $response = wp_remote_post( $this->revoke_uri, $request_args );
258 }
259 }
260
261 /**
262 * Handles errors returned by GAPI Library
263 *
264 * @return boolean
265 */
266 public function api_errors_handler() {
267 $errors = AIWP_Tools::get_cache( 'api_errors' );
268 // Proceed as normal if we don't know the error
269 if ( false === $errors || ! isset( $errors[0] ) ) {
270 return false;
271 }
272 // Reset the token since these are unrecoverable errors and need user intervention
273 // We can also add 'INVALID_ARGUMENT'
274 if ( isset( $errors[2][0] ) && ( 'INVALID_ARGUMENTS' == $errors[2][0] || 'UNAUTHENTICATED' == $errors[2][0] || 'PERMISSION_DENIED' == $errors[2][0] ) ) {
275 $this->reset_token( false, false );
276 return $errors[0];
277 }
278 // Reset the token since these are unrecoverable errors and need user intervention
279 // We can also add 'invalid_grant'
280 if ( isset( $errors[0] ) && ( 'invalid_grant' == $errors[0] || 'invalid_token' == $errors[0] ) ) {
281 $this->reset_token( false, false );
282 return $errors[0];
283 }
284 if ( 401 == $errors[0] || 403 == $errors[0] ) {
285 return $errors[0];
286 }
287 // Back-off processing until the error timeouts, usually at midnight
288 if ( isset( $errors[1][0]['reason'] ) && ( 'dailyLimitExceeded' == $errors[1][0]['reason'] || 'userRateLimitExceeded' == $errors[1][0]['reason'] || 'rateLimitExceeded' == $errors[1][0]['reason'] || 'quotaExceeded' == $errors[1][0]['reason'] ) ) {
289 return $errors[0];
290 }
291 // Back-off system for subsequent requests - an Auth error generated after a Service request
292 if ( isset( $errors[1][0]['reason'] ) && ( 'authError' == $errors[1][0]['reason'] ) ) {
293 if ( $this->aiwp->config->options['api_backoff'] <= 5 ) {
294 usleep( $this->aiwp->config->options['api_backoff'] * 1000000 + rand( 100000, 1000000 ) );
295 $this->aiwp->config->options['api_backoff'] = $this->aiwp->config->options['api_backoff'] + 1;
296 $this->aiwp->config->set_plugin_options();
297 return false;
298 } else {
299 return $errors[0];
300 }
301 }
302 if ( 500 == $errors[0] || 503 == $errors[0] ) {
303 return $errors[0];
304 }
305 return false;
306 }
307
308 /**
309 * Calculates proper timeouts for each GAPI query
310 *
311 * @param
312 * $interval
313 * @return number
314 */
315 public function get_timeouts( $interval = '' ) {
316 $local_time = time() + $this->timeshift;
317 if ( 'daily' == $interval ) {
318 $nextday = explode( '-', date( 'n-j-Y', strtotime( ' +1 day', $local_time ) ) );
319 $midnight = mktime( 0, 0, 0, $nextday[0], $nextday[1], $nextday[2] );
320 return $midnight - $local_time;
321 } else if ( 'midnight' == $interval ) {
322 $midnight = strtotime( "tomorrow 00:00:00" ); // UTC midnight
323 $midnight = $midnight + 8 * 3600; // UTC 8 AM
324 return $midnight - time();
325 } else if ( 'hourly' == $interval ) {
326 $nexthour = explode( '-', date( 'H-n-j-Y', strtotime( ' +1 hour', $local_time ) ) );
327 $newhour = mktime( $nexthour[0], 0, 0, $nexthour[1], $nexthour[2], $nexthour[3] );
328 return $newhour - $local_time;
329 } else {
330 return 0;
331 }
332 }
333
334 /**
335 * Retrieves all Google Analytics 4 Properties with details
336 *
337 * @return array
338 */
339 public function refresh_profiles_ga4() {
340
341 $token = (array) $this->aiwp->config->options['token'];
342 $access_token = $token['access_token'];
343 $pageSize = 100;
344 // @formatter:off
345 $headers = array(
346 'Authorization' => 'Bearer ' . $access_token,
347 'Content-Type' => 'application/json'
348 );
349 // @formatter:on
350 $accountPageToken = null;
351 do {
352 $accountsEndpoint = "https://analyticsadmin.googleapis.com/v1beta/accounts?pageSize={$pageSize}";
353 if ( $accountPageToken ) {
354 $accountsEndpoint .= "&pageToken={$accountPageToken}";
355 }
356 $accountsResponse = wp_remote_get( $accountsEndpoint, array( 'headers' => $headers ) );
357 if ( is_wp_error( $accountsResponse ) ) {
358 $timeout = $this->get_timeouts();
359 AIWP_Tools::set_error( $accountsResponse, $timeout );
360 return $accountsResponse->get_error_code();
361 } else {
362 $accountsBody = wp_remote_retrieve_body( $accountsResponse );
363 $accountsData = json_decode( $accountsBody, true );
364 if ( isset( $accountsData['error'] ) ) {
365 $timeout = $this->get_timeouts();
366 $error = new WP_Error();
367 if ( isset( $accountsData['error']['code'] ) && isset( $accountsData['error']['status'] ) ) {
368 $error->add( $accountsData['error']['code'], $accountsData['error']['message'], array( $accountsData['error']['status'], 'trying to refresh token' ) );
369 } else if ( isset( $accountsData['error'] ) && isset( $accountsData['error_description'] ) ) {
370 $error->add( $accountsData['error'], $accountsData['error_description'], 'trying to refresh token' );
371 } else if ( isset( $accountsData['error']['code'] ) && isset( $accountsData['error']['message'] ) ) {
372 $error->add( $accountsData['error']['code'], $accountsData['error']['message'], 'trying to verify site' );
373 }
374 AIWP_Tools::set_error( $error, $timeout );
375 return $error->get_error_code();
376 }
377 if ( isset( $accountsData['accounts'] ) ) {
378 foreach ( $accountsData['accounts'] as $account ) {
379 $propertyPageToken = null;
380 do {
381 $propertiesEndpoint = "https://analyticsadmin.googleapis.com/v1beta/properties?filter=parent:{$account['name']}&pageSize={$pageSize}";
382 if ( $propertyPageToken ) {
383 $propertiesEndpoint .= "&pageToken={$propertyPageToken}";
384 }
385 $propertiesResponse = wp_remote_get( $propertiesEndpoint, array( 'headers' => $headers ) );
386 if ( is_wp_error( $propertiesResponse ) ) {
387 $timeout = $this->get_timeouts();
388 AIWP_Tools::set_error( $propertiesResponse, $timeout );
389 return $propertiesResponse->get_error_code();
390 } else {
391 $propertiesBody = wp_remote_retrieve_body( $propertiesResponse );
392 $propertiesData = json_decode( $propertiesBody, true );
393 if ( isset( $propertiesData['error'] ) ) {
394 $timeout = $this->get_timeouts();
395 $error = new WP_Error();
396 if ( isset( $propertiesData['error']['code'] ) && isset( $propertiesData['error']['status'] ) ) {
397 $error->add( $propertiesData['error']['code'], $propertiesData['error']['message'], array( $propertiesData['error']['status'], 'trying to refresh token' ) );
398 } else if ( isset( $propertiesData['error'] ) && isset( $propertiesData['error_description'] ) ) {
399 $error->add( $propertiesData['error'], $propertiesData['error_description'], 'trying to refresh token' );
400 } else if ( isset( $propertiesData['error']['code'] ) && isset( $propertiesData['error']['message'] ) ) {
401 $error->add( $propertiesData['error']['code'], $propertiesData['error']['message'], 'trying to verify site' );
402 }
403 AIWP_Tools::set_error( $error, $timeout );
404 return $error->get_error_code();
405 }
406 if ( isset( $propertiesData['properties'] ) ) {
407 foreach ( $propertiesData['properties'] as $property ) {
408 $datastreamsPageToken = null;
409 do {
410 $datastreamsEndpoint = "https://analyticsadmin.googleapis.com/v1beta/{$property['name']}/dataStreams?pageSize={$pageSize}";
411 if ( $datastreamsPageToken ) {
412 $datastreamsEndpoint .= "&pageToken={$datastreamsPageToken}";
413 }
414 $datastreamsResponse = wp_remote_get( $datastreamsEndpoint, array( 'headers' => $headers ) );
415 if ( is_wp_error( $datastreamsResponse ) ) {
416 $timeout = $this->get_timeouts();
417 AIWP_Tools::set_error( $datastreamsResponse, $timeout );
418 return $datastreamsResponse->get_error_code();
419 } else {
420 $datastreamsBody = wp_remote_retrieve_body( $datastreamsResponse );
421 $datastreamsData = json_decode( $datastreamsBody, true );
422 if ( isset( $datastreamsData['error'] ) ) {
423 $timeout = $this->get_timeouts();
424 $error = new WP_Error();
425 if ( isset( $datastreamsData['error']['code'] ) && isset( $datastreamsData['error']['status'] ) ) {
426 $error->add( $datastreamsData['error']['code'], $datastreamsData['error']['message'], array( $datastreamsData['error']['status'], 'trying to refresh token' ) );
427 } else if ( isset( $datastreamsData['error'] ) && isset( $datastreamsData['error_description'] ) ) {
428 $error->add( $datastreamsData['error'], $datastreamsData['error_description'], 'trying to refresh token' );
429 } else if ( isset( $datastreamsData['error']['code'] ) && isset( $datastreamsData['error']['message'] ) ) {
430 $error->add( $datastreamsData['error']['code'], $datastreamsData['error']['message'], 'trying to verify site' );
431 }
432 AIWP_Tools::set_error( $error, $timeout );
433 return $error->get_error_code();
434 }
435 if ( isset( $datastreamsData['dataStreams'] ) ) {
436 foreach ( $datastreamsData['dataStreams'] as $datastream ) {
437 if ( 'WEB_DATA_STREAM' == $datastream['type'] ) {
438 $timetz = new DateTimeZone( $property['timeZone'] );
439 $localtime = new DateTime( 'now', $timetz );
440 $timeshift = strtotime( $localtime->format( 'Y-m-d H:i:s' ) ) - time();
441 $ga4_webstreams_list[] = array( $datastream['displayName'], $datastream['name'], $datastream['webStreamData']['defaultUri'], $datastream['webStreamData']['measurementId'], $timeshift, $property['timeZone'] );
442 }
443 }
444 $datastreamsPageToken = isset( $datastreamsData['nextPageToken'] ) ? $datastreamsData['nextPageToken'] : null;
445 }
446 }
447 } while ( $datastreamsPageToken );
448 }
449 }
450 $propertyPageToken = isset( $propertiesData['nextPageToken'] ) ? $propertiesData['nextPageToken'] : null;
451 }
452 } while ( $propertyPageToken );
453 }
454 }
455 $accountPageToken = isset( $accountsData['nextPageToken'] ) ? $accountsData['nextPageToken'] : null;
456 }
457 } while ( $accountPageToken );
458 return $ga4_webstreams_list;
459 }
460
461 /**
462 * Generates serials for transients
463 *
464 * @param
465 * $serial
466 * @return string
467 */
468 public function get_serial( $serial ) {
469 return sprintf( "%u", crc32( $serial ) );
470 }
471
472 /**
473 * Google Analtyics 4 Reports Get and cache
474 *
475 * @param
476 * $projecId
477 * @param
478 * $from
479 * @param
480 * $to
481 * @param
482 * $metrics
483 * @param
484 * $options
485 * @param
486 * $serial
487 * @return int
488 */
489 private function handle_corereports_ga4( $projectId, $from, $to, $metrics, $dimensions, $sortby, $filters, $serial ) {
490
491 if ( 'today' == $from ) {
492 $interval = 'hourly';
493 } else {
494 $interval = 'daily';
495 }
496 $transient = AIWP_Tools::get_cache( $serial );
497 if ( false === $transient ) {
498 if ( $this->api_errors_handler() ) {
499 return $this->api_errors_handler();
500 }
501 $projectIdArr = explode( '/dataStreams/', $projectId );
502 $projectId = $projectIdArr[0];
503 $api_url = 'https://analyticsdata.googleapis.com/v1beta/' . $projectId . ':runReport';
504 $quotauser = $this->get_serial( $this->quotauser . $projectId );
505 $api_url = $api_url . '?quotaUser=' . $quotauser;
506 $token = (array) $this->aiwp->config->options['token'];
507 if ( isset( $token['access_token'] ) ) {
508 $access_token = $token['access_token'];
509 } else {
510 return 624;
511 }
512 // @formatter:off
513 $headers = array(
514 'Authorization' => 'Bearer ' . $access_token,
515 'Content-Type' => 'application/json',
516 );
517 // @formatter:on
518 $request_body = array( 'dateRanges' => array( 'startDate' => $from, 'endDate' => $to ) );
519 if ( is_array( $metrics ) ) {
520 $request_body['metrics'] = array();
521 foreach ( $metrics as $metric ) {
522 $metric = AIWP_Tools::ga3_ga4_mapping( $metric );
523 $request_body['metrics'][] = array( 'name' => $metric );
524 }
525 } else {
526 $metric = AIWP_Tools::ga3_ga4_mapping( $metrics );
527 $request_body['metrics'] = array();
528 $request_body['metrics'][] = array( 'name' => $metric );
529 }
530 $request_body['metricAggregations'] = 'TOTAL';
531 $request_body['keepEmptyRows'] = 'true';
532 if ( $dimensions ) {
533 if ( is_array( $dimensions ) ) {
534 $request_body['dimensions'] = array();
535 foreach ( $dimensions as $dimension ) {
536 $dimension = AIWP_Tools::ga3_ga4_mapping( $dimension );
537 $request_body['dimensions'][] = array( 'name' => $dimension );
538 }
539 } else {
540 $dimension = AIWP_Tools::ga3_ga4_mapping( $dimensions );
541 $request_body['dimensions'] = array();
542 $request_body['dimensions'][] = array( 'name' => $dimension );
543 }
544 }
545 if ( $filters ) {
546 if ( count( $filters ) === 1 ) {
547 $filter = $filters[0];
548 $filter[0] = AIWP_Tools::ga3_ga4_mapping( $filter[0] );
549 $fieldName = $filter[0];
550 $filterMatch = $filter[1];
551 $filterValue = $filter[2];
552 $filterData = array( 'fieldName' => $fieldName, 'stringFilter' => array( 'matchType' => $filterMatch, 'value' => $filterValue ) );
553 if ( $filter[3] ) {
554 $request_body['dimensionFilter']['notExpression']['filter'] = $filterData;
555 } else {
556 $request_body['dimensionFilter']['filter'] = $filterData;
557 }
558 } else {
559 $filterExpressions = array();
560 foreach ( $filters as $filter ) {
561 $filter[0] = AIWP_Tools::ga3_ga4_mapping( $filter[0] );
562 $fieldName = $filter[0];
563 $filterMatch = $filter[1];
564 $filterValue = $filter[2];
565 $filterData = array( 'fieldName' => $fieldName, 'stringFilter' => array( 'matchType' => $filterMatch, 'value' => $filterValue ) );
566 if ( $filter[3] ) {
567 $notExpression = array();
568 $notExpression['notExpression'] = array( 'filter' => $filterData );
569 $filterExpressions[] = $notExpression;
570 } else {
571 $filterExpressions[] = array( 'filter' => $filterData );
572 }
573 $request_body['dimensionFilter']['andGroup'] = array( 'expressions' => $filterExpressions );
574 }
575 }
576 }
577 if ( $sortby ) {
578 $metric = AIWP_Tools::ga3_ga4_mapping( $metrics );
579 $metricName = $metric;
580 $desc = true;
581 $orderCriteria = array( 'metricName' => $metricName );
582 $request_body['orderBys']['metric'] = $orderCriteria;
583 $request_body['orderBys']['desc'] = $desc;
584 } else {
585 if ( is_array( $dimensions ) ) {
586 $dimension = AIWP_Tools::ga3_ga4_mapping( $dimensions[0] );
587 $dimensionName = $dimension;
588 $orderType = 'NUMERIC';
589 $desc = false;
590 $orderCriteria = array( 'dimensionName' => $dimensionName, 'orderType' => $orderType );
591 $request_body['orderBys']['dimension'] = $orderCriteria;
592 $request_body['orderBys']['desc'] = $desc;
593 }
594 }
595 $request_body_json = json_encode( $request_body );
596 $args = array( 'headers' => $headers, 'body' => $request_body_json );
597 $response = wp_remote_post( $api_url, $args );
598 if ( is_wp_error( $response ) ) {
599 $timeout = $this->get_timeouts();
600 AIWP_Tools::set_error( $response, $timeout );
601 return $response->get_error_code();
602 } else {
603 $response_body = wp_remote_retrieve_body( $response );
604 $response_data = json_decode( $response_body, true );
605 if ( isset( $response_data['error'] ) ) {
606 $timeout = $this->get_timeouts();
607 $error = new WP_Error();
608 if ( isset( $response_data['error']['code'] ) && isset( $response_data['error']['status'] ) ) {
609 $error->add( $response_data['error']['code'], $response_data['error']['message'], array( $response_data['error']['status'], 'trying to refresh token' ) );
610 } else if ( isset( $response_data['error'] ) && isset( $response_data['error_description'] ) ) {
611 $error->add( $response_data['error'], $response_data['error_description'], 'trying to refresh token' );
612 } else if ( isset( $response_data['error']['code'] ) && isset( $response_data['error']['message'] ) ) {
613 $error->add( $response_data['error']['code'], $response_data['error']['message'], 'trying to verify site' );
614 }
615 AIWP_Tools::set_error( $error, $timeout );
616 return $error->get_error_code();
617 }
618 if ( isset( $response_data['rows'] ) ) {
619 $data['values'] = array();
620 foreach ( $response_data['rows'] as $row ) {
621 $values = array();
622 if ( isset( $row['dimensionValues'][0] ) ) {
623 foreach ( $row['dimensionValues'] as $item ) {
624 $values[] = $item['value'];
625 }
626 }
627 if ( isset( $row['metricValues'][0] ) ) {
628 foreach ( $row['metricValues'] as $item ) {
629 $values[] = $item['value'];
630 }
631 }
632 $data['values'][] = $values;
633 }
634 $data['totals'] = 0;
635 if ( isset( $response_data['totals'][0]['metricValues'][0]['value'] ) ) {
636 $data['totals'] = $response_data['totals'][0]['metricValues'][0]['value'];
637 }
638 AIWP_Tools::set_cache( $serial, $data, $this->get_timeouts( $interval ) );
639 } else {
640 return 621;
641 }
642 }
643 } else {
644 $data = $transient;
645 }
646 $this->aiwp->config->options['api_backoff'] = 0;
647 $this->aiwp->config->set_plugin_options();
648 return $data;
649 }
650
651 /**
652 * Google Analytics 4 data for Area Charts (Admin Dashboard Widget report)
653 *
654 * @param
655 * $projectId
656 * @param
657 * $from
658 * @param
659 * $to
660 * @param
661 * $query
662 * @param
663 * $filter
664 * @return array|int
665 */
666 private function get_areachart_data_ga4( $projectId, $from, $to, $query, $filter = '' ) {
667 $factor = 1;
668 switch ( $query ) {
669 case 'users' :
670 $title = __( "Users", 'analytics-insights' );
671 break;
672 case 'pageviews' :
673 $title = __( "Page Views", 'analytics-insights' );
674 break;
675 case 'visitBounceRate' :
676 $title = __( "Bounce Rate", 'analytics-insights' );
677 $factor = 100;
678 break;
679 case 'organicSearches' :
680 $title = __( "Engagement", 'analytics-insights' );
681 break;
682 case 'uniquePageviews' :
683 $title = __( "Unique Page Views", 'analytics-insights' );
684 break;
685 default :
686 $title = __( "Sessions", 'analytics-insights' );
687 }
688 $metrics = 'ga:' . $query;
689 if ( 'today' == $from || 'yesterday' == $from ) {
690 $dimensions = 'ga:hour';
691 $dayorhour = __( "Hour", 'analytics-insights' );
692 } else if ( '365daysAgo' == $from || '1095daysAgo' == $from ) {
693 $dimensions = array( 'ga:year', 'ga:month' );
694 $dayorhour = __( "Date", 'analytics-insights' );
695 } else {
696 $dimensions = array( 'ga:date', 'ga:dayOfWeekName' );
697 $dayorhour = __( "Date", 'analytics-insights' );
698 }
699 if ( $filter ) {
700 $filters[] = array( 'ga:pagePath', 'EXACT', $filter, false );
701 } else {
702 $filters = false;
703 }
704 $serial = 'qr2_' . $this->get_serial( $projectId . $from . $metrics . $filter );
705 $data = $this->handle_corereports_ga4( $projectId, $from, $to, $metrics, $dimensions, false, $filters, $serial );
706 if ( is_numeric( $data ) ) {
707 return $data;
708 }
709 if ( isset( $data['values'] ) && empty( $data['values'] ) ) {
710 return 621;
711 }
712 $aiwp_data = array( array( $dayorhour, $title ) );
713 if ( 'today' == $from || 'yesterday' == $from ) {
714 for ( $i = 0; $i < 24; $i++ ) {
715 $fill_data[$i] = 0;
716 }
717 foreach ( $data['values'] as $row ) {
718 if ( array_key_exists( (int) $row[0], $fill_data ) ) {
719 $fill_data[(int) $row[0]] = round( $row[1], 2 ) * $factor;
720 }
721 }
722 foreach ( $fill_data as $key => $value ) {
723 $aiwp_data[] = array( $key . ':00', $value );
724 }
725 } else if ( '365daysAgo' == $from || '1095daysAgo' == $from ) {
726 $yesterday = date( "Y-m-d", strtotime( "now" ) );
727 $offset = str_replace( 'daysAgo', '', $from );
728 $xdaysago = date( "Y-m-d", strtotime( "-" . $offset . " day" ) );
729 $period = new DatePeriod( new DateTime( $xdaysago ), new DateInterval( 'P1M' ), new DateTime( $yesterday ) );
730 foreach ( $period as $key => $value ) {
731 $fill_data[$value->format( 'Ym' )] = 0;
732 }
733 foreach ( $data['values'] as $row ) {
734 $key = $row[0] . $row[1];
735 if ( array_key_exists( $key, $fill_data ) ) {
736 $fill_data[$key] = round( $row[2], 2 ) * $factor;
737 }
738 }
739 foreach ( $fill_data as $key => $value ) {
740 /*
741 * translators:
742 * Example: 'F, Y' will become 'November, 2015'
743 * For details see: https://php.net/manual/en/function.date.php#refsect1-function.date-parameters
744 */
745 $aiwp_data[] = array( date_i18n( __( 'F, Y', 'analytics-insights' ), strtotime( $key . '01' ) ), $value );
746 }
747 } else {
748 $yesterday = date( "Y-m-d", strtotime( "now" ) );
749 $offset = str_replace( 'daysAgo', '', $from );
750 $xdaysago = date( "Y-m-d", strtotime( "-" . $offset . " day" ) );
751 $period = new DatePeriod( new DateTime( $xdaysago ), new DateInterval( 'P1D' ), new DateTime( $yesterday ) );
752 foreach ( $period as $key => $value ) {
753 $fill_data[$value->format( 'Ymd' )] = 0;
754 }
755 foreach ( $data['values'] as $row ) {
756 if ( array_key_exists( $row[0], $fill_data ) ) {
757 $fill_data[$row[0]] = round( $row[2], 2 ) * $factor;
758 }
759 }
760 foreach ( $fill_data as $key => $value ) {
761 /*
762 * translators:
763 * Example: 'l, F j, Y' will become 'Thusday, November 17, 2015'
764 * For details see: https://php.net/manual/en/function.date.php#refsect1-function.date-parameters
765 */
766 $aiwp_data[] = array( date_i18n( __( 'l, F j, Y', 'analytics-insights' ), strtotime( $key ) ), $value );
767 }
768 }
769 return $aiwp_data;
770 }
771
772 /**
773 * Google Analytics 4 data for Bottom Stats (bottom stats on main report)
774 *
775 * @param
776 * $projectId
777 * @param
778 * $from
779 * @param
780 * $to
781 * @param
782 * $filter
783 * @return array|int
784 */
785 private function get_bottomstats_ga4( $projectId, $from, $to, $filter = '' ) {
786 if ( $filter ) {
787 $filters[] = array( 'ga:pagePath', 'EXACT', $filter, false );
788 // @formatter:off
789 $metrics = array(
790 'ga:sessions',
791 'ga:users',
792 'ga:pageviews',
793 'ga:BounceRate',
794 'averageSessionDuration',
795 'ga:pageviewsPerSession',
796 'engagedSessions',
797 'userEngagementDuration',
798 );
799 // @formatter:on
800 } else {
801 $filters = false;
802 // @formatter:off
803 $metrics = array(
804 'ga:sessions',
805 'ga:users',
806 'ga:pageviews',
807 'ga:BounceRate',
808 'averageSessionDuration',
809 'ga:pageviewsPerSession',
810 'engagedSessions',
811 'userEngagementDuration',
812 );
813 // @formatter:on
814 }
815 $sortby = false;
816 $serial = 'qr30_' . $this->get_serial( $projectId . $from . $filter );
817 $data = $this->handle_corereports_ga4( $projectId, $from, $to, $metrics, false, $sortby, $filters, $serial );
818 if ( is_numeric( $data ) ) {
819 return $data;
820 }
821 $aiwp_data = array();
822 $aiwp_data = $data['values'][0];
823 // i18n support
824 $aiwp_data[0] = isset( $aiwp_data[0] ) ? number_format_i18n( $aiwp_data[0] ) : 0;
825 $aiwp_data[1] = isset( $aiwp_data[1] ) ? number_format_i18n( $aiwp_data[1] ) : 0;
826 $aiwp_data[2] = isset( $aiwp_data[2] ) ? number_format_i18n( $aiwp_data[2] ) : 0;
827 $aiwp_data[3] = isset( $aiwp_data[3] ) ? number_format_i18n( $aiwp_data[3] * 100, 2 ) . '%' : '0%';
828 $aiwp_data[4] = isset( $aiwp_data[4] ) ? AIWP_Tools::secondstohms( $aiwp_data[4] ) : '00:00:00';
829 $aiwp_data[5] = isset( $aiwp_data[5] ) ? number_format_i18n( $aiwp_data[5], 2 ) : 0;
830 $aiwp_data[6] = isset( $aiwp_data[6] ) ? number_format_i18n( $aiwp_data[6] ) : 0;
831 ;
832 $aiwp_data[7] = isset( $aiwp_data[7] ) ? AIWP_Tools::secondstohms( $aiwp_data[7] ) : '00:00:00';
833 // Get Organic Searches
834 $metrics = 'ga:sessions';
835 $dimensions = 'ga:' . 'channelGrouping';
836 $sortby = '-' . $metrics;
837 if ( $filter ) {
838 $filters[] = array( 'ga:pagePath', 'EXACT', $filter, false );
839 } else {
840 $filters = false;
841 }
842 $serial = 'qr9_' . $this->get_serial( $projectId . $from . 'channelGrouping' . $filter . 'ga:sessions' );
843 $data = $this->handle_corereports_ga4( $projectId, $from, $to, $metrics, $dimensions, $sortby, $filters, $serial );
844 $organic = 0;
845 if ( isset( $data['values'] ) && !empty( $data['values'] ) ) {
846 foreach ( $data['values'] as $row ) {
847 if ( 'Organic Search' == $row[0] ) {
848 $organic = number_format_i18n( $row[1] );
849 }
850 }
851 }
852 array_splice( $aiwp_data, 6, 0, $organic );
853 return $aiwp_data;
854 }
855
856 /**
857 * Google Analytics 4 data for Table Charts (location reports)
858 *
859 * @param
860 * $projectId
861 * @param
862 * $from
863 * @param
864 * $to
865 * @param
866 * $filter
867 * @return array|int
868 */
869 private function get_locations_ga4( $projectId, $from, $to, $metric, $filter = '' ) {
870 $metrics = 'ga:' . $metric;
871 $title = __( "Countries", 'analytics-insights' );
872 $dimensions = 'ga:country';
873 $serial = 'qr7_' . $this->get_serial( $projectId . $from . $filter . $metric . $dimensions );
874 $local_filter = '';
875 if ( 'None' !== $this->aiwp->config->options['ga_target_geomap'] ) {
876 $dimensions = array( 'ga:city', 'ga:region' );
877 if ( 'None' !== $this->aiwp->config->options['ga_target_geomap'] ) {
878 $local_filter = array( 'ga:country', 'EXACT', ( $this->aiwp->config->options['ga_target_geomap'] ), false );
879 $title = __( "Cities from", 'analytics-insights' ) . ' ' . __( $this->aiwp->config->options['ga_target_geomap'] );
880 $serial = 'qr7_' . $this->get_serial( $projectId . $from . $this->aiwp->config->options['ga_target_geomap'] . $filter . $metric );
881 }
882 }
883 $sortby = '-' . $metrics;
884
885 if ( $filter ) {
886 $filters[] = array( 'ga:pagePath', 'EXACT', $filter, false );
887 if ( $local_filter ) {
888 $filters[] = array( 'ga:pagePath', 'EXACT', $filter, false );
889 $filters[1] = $local_filter;
890 }
891 } else {
892 if ( $local_filter ) {
893 $filters[] = $local_filter;
894 }
895 }
896 if ( '' === $filter && '' === $local_filter ) {
897 $filters = false;
898 }
899 $data = $this->handle_corereports_ga4( $projectId, $from, $to, $metrics, $dimensions, $sortby, $filters, $serial );
900 if ( is_numeric( $data ) ) {
901 return $data;
902 }
903 if ( isset( $data['values'] ) && empty( $data['values'] ) ) {
904 return 621;
905 }
906 $aiwp_data = array( array( $title, __( ucfirst( $metric ), 'analytics-insights' ) ) );
907 foreach ( $data['values'] as $row ) {
908 if ( isset( $row[2] ) ) {
909 $aiwp_data[] = array( esc_html( $row[0] ) . ', ' . esc_html( $row[1] ), (int) $row[2] );
910 } else {
911 $aiwp_data[] = array( esc_html( $row[0] ), (int) $row[1] );
912 }
913 }
914 return $aiwp_data;
915 }
916
917 /**
918 * Google Analytics 4 data for Table Charts (content pages)
919 *
920 * @param
921 * $projectId
922 * @param
923 * $from
924 * @param
925 * $to
926 * @param
927 * $filter
928 * @return array|int
929 */
930 private function get_contentpages_ga4( $projectId, $from, $to, $metric, $filter = '' ) {
931 $metrics = 'ga:' . $metric;
932 $dimensions = 'ga:pageTitle';
933 $sortby = '-' . $metrics;
934 if ( $filter ) {
935 $filters[] = array( 'ga:pagePath', 'EXACT', $filter, false );
936 } else {
937 $filters = false;
938 }
939 $serial = 'qr4_' . $this->get_serial( $projectId . $from . $filter . $metric );
940 $data = $this->handle_corereports_ga4( $projectId, $from, $to, $metrics, $dimensions, $sortby, $filters, $serial );
941 if ( is_numeric( $data ) ) {
942 return $data;
943 }
944 if ( isset( $data['values'] ) && empty( $data['values'] ) ) {
945 return 621;
946 }
947 $aiwp_data = array( array( __( "Pages", 'analytics-insights' ), __( ucfirst( $metric ), 'analytics-insights' ) ) );
948 foreach ( $data['values'] as $row ) {
949 $aiwp_data[] = array( esc_html( $row[0] ), (int) $row[1] );
950 }
951 return $aiwp_data;
952 }
953
954 /**
955 * Google Analytics 4 data for Org Charts (traffic channels, device categories)
956 *
957 * @param
958 * $projectId
959 * @param
960 * $from
961 * @param
962 * $to
963 * @param
964 * $query
965 * @param
966 * $filter
967 * @return array|int
968 */
969 private function get_orgchart_data_ga4( $projectId, $from, $to, $query, $metric, $filter = '' ) {
970 $metrics = 'ga:' . $metric;
971 $dimensions = 'ga:' . $query;
972 $sortby = '-' . $metrics;
973 if ( $filter ) {
974 $filters[] = array( 'ga:pagePath', 'EXACT', $filter, false );
975 } else {
976 $filters = false;
977 }
978 $serial = 'qr8_' . $this->get_serial( $projectId . $from . $query . $filter . $metric );
979 $data = $this->handle_corereports_ga4( $projectId, $from, $to, $metrics, $dimensions, $sortby, $filters, $serial );
980 if ( is_numeric( $data ) ) {
981 return $data;
982 }
983 if ( isset( $data['values'] ) && empty( $data['values'] ) ) {
984 return 621;
985 }
986 $block = ( 'channelGrouping' == $query ) ? __( "Channels", 'analytics-insights' ) : __( "Devices", 'analytics-insights' );
987 $aiwp_data = array( array( '<div style="color:black; font-size:1.1em">' . $block . '</div><div style="color:darkblue; font-size:1.2em">' . (int) $data['totals'] . '</div>', "" ) );
988 foreach ( $data['values'] as $row ) {
989 $shrink = explode( " ", $row[0] );
990 if ( isset( $shrink[1] ) ) {
991 $shrink[0] = esc_html( $shrink[0] ) . '<br>' . esc_html( $shrink[1] );
992 }
993 if ( 'Unassigned' !== $shrink[0] ) {
994 $aiwp_data[] = array( '<div style="color:black; font-size:1.1em">' . $shrink[0] . '</div><div style="color:darkblue; font-size:1.2em">' . (int) $row[1] . '</div>', '<div style="color:black; font-size:1.1em">' . $block . '</div><div style="color:darkblue; font-size:1.2em">' . (int) $data['totals'] . '</div>' );
995 }
996 }
997 return $aiwp_data;
998 }
999
1000 /**
1001 * Google Analytics 4 data for Table Charts (referrers)
1002 *
1003 * @param
1004 * $projectId
1005 * @param
1006 * $from
1007 * @param
1008 * $to
1009 * @param
1010 * $filter
1011 * @return array|int
1012 */
1013 private function get_referrers_ga4( $projectId, $from, $to, $metric, $filter = '' ) {
1014 $metrics = 'ga:' . $metric;
1015 $dimensions = 'ga:source';
1016 $sortby = '-' . $metrics;
1017 if ( $filter ) {
1018 $filters[] = array( 'ga:pagePath', 'EXACT', $filter, false );
1019 $filters[] = array( 'ga:medium', 'EXACT', 'referral', false );
1020 } else {
1021 $filters[] = array( 'ga:medium', 'EXACT', 'referral', false );
1022 }
1023 $serial = 'qr5_' . $this->get_serial( $projectId . $from . $filter . $metric );
1024 $data = $this->handle_corereports_ga4( $projectId, $from, $to, $metrics, $dimensions, $sortby, $filters, $serial );
1025 if ( is_numeric( $data ) ) {
1026 return $data;
1027 }
1028 if ( isset( $data['values'] ) && empty( $data['values'] ) ) {
1029 return 621;
1030 }
1031 $aiwp_data = array( array( __( "Referrers", 'analytics-insights' ), __( ucfirst( $metric ), 'analytics-insights' ) ) );
1032 foreach ( $data['values'] as $row ) {
1033 $aiwp_data[] = array( esc_html( $row[0] ), (int) $row[1] );
1034 }
1035 return $aiwp_data;
1036 }
1037
1038 /**
1039 * Google Analytics 4 data for Table Charts (searches)
1040 *
1041 * @param
1042 * $projectId
1043 * @param
1044 * $from
1045 * @param
1046 * $to
1047 * @param
1048 * $filter
1049 * @return array|int
1050 */
1051 private function get_searches_ga4( $projectId, $from, $to, $metric, $filter = '' ) {
1052 $metrics = 'ga:' . $metric;
1053 $dimensions = 'ga:source';
1054 $sortby = '-' . $metrics;
1055 if ( $filter ) {
1056 $filters[] = array( 'ga:pagePath', 'EXACT', $filter, false );
1057 $filters[] = array( 'ga:medium', 'EXACT', 'organic', false );
1058 } else {
1059 $filters[] = array( 'ga:medium', 'EXACT', 'organic', false );
1060 }
1061 $serial = 'qr6_' . $this->get_serial( $projectId . $from . $filter . $metric );
1062 $data = $this->handle_corereports_ga4( $projectId, $from, $to, $metrics, $dimensions, $sortby, $filters, $serial );
1063 if ( is_numeric( $data ) ) {
1064 return $data;
1065 }
1066 if ( isset( $data['values'] ) && empty( $data['values'] ) ) {
1067 return 621;
1068 }
1069 $aiwp_data = array( array( __( "Search Engines", 'analytics-insights' ), __( ucfirst( $metric ), 'analytics-insights' ) ) );
1070 foreach ( $data['values'] as $row ) {
1071 $aiwp_data[] = array( esc_html( $row[0] ), (int) $row[1] );
1072 }
1073 return $aiwp_data;
1074 }
1075
1076 /**
1077 * Google Analytics 4 data for Pie Charts (traffic mediums, serach engines, languages, browsers, screen rsolutions, etc.)
1078 *
1079 * @param
1080 * $projectId
1081 * @param
1082 * $from
1083 * @param
1084 * $to
1085 * @param
1086 * $query
1087 * @param
1088 * $filter
1089 * @return array|int
1090 */
1091 private function get_piechart_data_ga4( $projectId, $from, $to, $query, $metric, $filter = '' ) {
1092 $metrics = 'ga:' . $metric;
1093 $dimensions = 'ga:' . $query;
1094 $sortby = false;
1095 if ( 'source' == $query ) {
1096 $sortby = '-' . $metrics;
1097 if ( $filter ) {
1098 $filters[] = array( 'ga:pagePath', 'EXACT', $filter, false );
1099 $filters[] = array( 'ga:medium', 'EXACT', 'organic', false );
1100 } else {
1101 $filters[] = array( 'ga:medium', 'EXACT', 'organic', false );
1102 }
1103 } else {
1104 $sortby = '-' . $metrics;
1105 if ( $filter ) {
1106 $filters[] = array( 'ga:pagePath', 'EXACT', $filter, false );
1107 $filters[] = array( 'ga:' . $query, 'EXACT', '(not set)', true );
1108 } else {
1109 $filters[] = array( 'ga:' . $query, 'EXACT', '(not set)', true );
1110 }
1111 }
1112
1113 $serial = 'qr10_' . $this->get_serial( $projectId . $from . $query . $filter . $metric );
1114 $data = $this->handle_corereports_ga4( $projectId, $from, $to, $metrics, $dimensions, $sortby, $filters, $serial );
1115 if ( is_numeric( $data ) ) {
1116 return $data;
1117 }
1118 if ( isset( $data['values'] ) && empty( $data['values'] ) ) {
1119 return 621;
1120 }
1121 $aiwp_data = array( array( __( "Type", 'analytics-insights' ), __( ucfirst( $metric ), 'analytics-insights' ) ) );
1122 $included = 0;
1123 foreach ( $data['values'] as $row ) {
1124 $aiwp_data[] = array( str_replace( "(none)", "direct", esc_html( $row[0] ) ), (int) $row[1] );
1125 $included += $row[1];
1126 }
1127 $totals = $data['totals'];
1128 $others = $totals - $included;
1129 if ( $others > 0 ) {
1130 $aiwp_data[] = array( __( 'Other', 'analytics-insights' ), $others );
1131 }
1132 return $aiwp_data;
1133 }
1134
1135 /**
1136 * Google Analytics 4 data for 404 Errors
1137 *
1138 * @param
1139 * $projectId
1140 * @param
1141 * $from
1142 * @param
1143 * $to
1144 * @return array|int
1145 */
1146 private function get_404errors_ga4( $projectId, $from, $to, $metric, $filter = "Page Not Found" ) {
1147 $metrics = 'ga:' . $metric;
1148 $dimensions = array( 'ga:pagePath', 'ga:fullReferrer', 'ga:pageTitle' );
1149 $sortby = '-' . $metrics;
1150 $filters[] = array( 'ga:pageTitle', 'CONTAINS', $filter, false );
1151 $serial = 'qr4_' . $this->get_serial( $projectId . $from . $filter . $metric );
1152 $data = $this->handle_corereports_ga4( $projectId, $from, $to, $metrics, $dimensions, $sortby, $filters, $serial );
1153 if ( is_numeric( $data ) ) {
1154 return $data;
1155 }
1156 if ( isset( $data['values'] ) && empty( $data['values'] ) ) {
1157 return 621;
1158 }
1159 $aiwp_data = array( array( __( "404 Errors", 'analytics-insights' ), __( ucfirst( $metric ), 'analytics-insights' ) ) );
1160 foreach ( $data['values'] as $row ) {
1161 $path = esc_html( $row[0] );
1162 $source = esc_html( $row[1] );
1163 $aiwp_data[] = array( "<strong>" . __( "URI:", 'analytics-insights' ) . "</strong> " . $path . "<br><strong>" . __( "Source:", 'analytics-insights' ) . "</strong> " . $source, (int) $row[3] );
1164 }
1165 return $aiwp_data;
1166 }
1167
1168 /**
1169 * Google Analytics 4 data for Frontend Widget (chart data and totals)
1170 *
1171 * @param
1172 * $projectId
1173 * @param
1174 * $period
1175 * @param
1176 * $anonim
1177 * @return array|int
1178 */
1179 public function frontend_widget_stats_ga4( $projectId, $from, $anonim ) {
1180 $to = 'yesterday';
1181 $metrics = 'ga:sessions';
1182 $dimensions = array( 'ga:date', 'ga:dayOfWeekName' );
1183 $serial = 'qr2_' . $this->get_serial( $projectId . $from . $metrics );
1184 $data = $this->handle_corereports_ga4( $projectId, $from, $to, $metrics, $dimensions, false, false, $serial );
1185 if ( is_numeric( $data ) ) {
1186 return $data;
1187 }
1188 if ( isset( $data['values'] ) && empty( $data['values'] ) ) {
1189 return 621;
1190 }
1191 $aiwp_data = array( array( __( "Date", 'analytics-insights' ), __( "Sessions", 'analytics-insights' ) ) );
1192 if ( $anonim ) {
1193 $max_array = array();
1194 foreach ( $data['values'] as $row ) {
1195 $max_array[] = $row[2];
1196 }
1197 $max = max( $max_array ) ? max( $max_array ) : 1;
1198 }
1199 foreach ( $data['values'] as $row ) {
1200 $aiwp_data[] = array( date_i18n( __( 'l, F j, Y', 'analytics-insights' ), strtotime( $row[0] ) ), ( $anonim ? round( $row[2] * 100 / $max, 2 ) : (int) $row[2] ) );
1201 }
1202 $totals = $data['totals'];
1203 return array( $aiwp_data, $anonim ? 0 : number_format_i18n( $totals ) );
1204 }
1205
1206 /**
1207 * Google Analytics 4 data for Realtime component (the real-time report)
1208 *
1209 * @param
1210 * $projectId
1211 * @return array|int
1212 */
1213 private function get_realtime_ga4( $projectId ) {
1214
1215 $metrics = 'activeUsers';
1216 $dimensions = array( 'unifiedScreenName' );
1217 $dimensions1 = array( 'deviceCategory' );
1218 $projectIdArr = explode( '/dataStreams/', $projectId );
1219 $projectId = $projectIdArr[0];
1220 $api_url = 'https://analyticsdata.googleapis.com/v1beta/' . $projectId . ':runRealtimeReport';
1221 $quotauser = $this->get_serial( $this->quotauser . $projectId );
1222 $api_url = $api_url . '?quotaUser=' . $quotauser;
1223 $serial = 'qr_realtimecache_' . $this->get_serial( $projectId );
1224 $transient = AIWP_Tools::get_cache( $serial );
1225 if ( false === $transient ) {
1226 if ( $this->api_errors_handler() ) {
1227 return $this->api_errors_handler();
1228 }
1229 if ( is_array( $metrics ) ) {
1230 $request_body['metrics'] = array();
1231 foreach ( $metrics as $metric ) {
1232 $metric = AIWP_Tools::ga3_ga4_mapping( $metric );
1233 $request_body['metrics'][] = array( 'name' => $metric );
1234 }
1235 } else {
1236 $metric = AIWP_Tools::ga3_ga4_mapping( $metrics );
1237 $request_body['metrics'] = array();
1238 $request_body['metrics'][] = array( 'name' => $metric );
1239 }
1240 $request_body['metricAggregations'] = 'TOTAL';
1241 if ( $dimensions ) {
1242 if ( is_array( $dimensions ) ) {
1243 $request_body['dimensions'] = array();
1244 foreach ( $dimensions as $dimension ) {
1245 $dimension = AIWP_Tools::ga3_ga4_mapping( $dimension );
1246 $request_body['dimensions'][] = array( 'name' => $dimension );
1247 }
1248 } else {
1249 $dimension = AIWP_Tools::ga3_ga4_mapping( $dimensions );
1250 $request_body['dimensions'] = array();
1251 $request_body['dimensions'][] = array( 'name' => $dimension );
1252 }
1253 }
1254 $token = (array) $this->aiwp->config->options['token'];
1255 if ( isset( $token['access_token'] ) ) {
1256 $access_token = $token['access_token'];
1257 } else {
1258 return 624;
1259 }
1260 // Get Page Data
1261 $headers = array( 'Authorization' => 'Bearer ' . $access_token, 'Content-Type' => 'application/json' );
1262 $request_body_json = json_encode( $request_body );
1263 $args = array( 'headers' => $headers, 'body' => $request_body_json );
1264 $response = wp_remote_post( $api_url, $args );
1265 if ( is_wp_error( $response ) ) {
1266 $timeout = $this->get_timeouts();
1267 AIWP_Tools::set_error( $response, $timeout );
1268 return $response->get_error_code();
1269 } else {
1270 $response_body = wp_remote_retrieve_body( $response );
1271 $response_data = json_decode( $response_body, true );
1272 if ( isset( $response_data['error'] ) ) {
1273 $timeout = $this->get_timeouts();
1274 $error = new WP_Error();
1275 if ( isset( $response_data['error']['code'] ) && isset( $response_data['error']['status'] ) ) {
1276 $error->add( $response_data['error']['code'], $response_data['error']['message'], array( $response_data['error']['status'], 'trying to refresh token' ) );
1277 } else if ( isset( $response_data['error'] ) && isset( $response_data['error_description'] ) ) {
1278 $error->add( $response_data['error'], $response_data['error_description'], 'trying to refresh token' );
1279 } else if ( isset( $response_data['error']['code'] ) && isset( $response_data['error']['message'] ) ) {
1280 $error->add( $response_data['error']['code'], $response_data['error']['message'], 'trying to verify site' );
1281 }
1282 AIWP_Tools::set_error( $error, $timeout );
1283 return $error->get_error_code();
1284 }
1285 $data = $response_data;
1286 }
1287 // Get Device Category Data
1288 if ( $dimensions1 ) {
1289 if ( is_array( $dimensions1 ) ) {
1290 $request_body['dimensions'] = array();
1291 foreach ( $dimensions1 as $dimension ) {
1292 $dimension = AIWP_Tools::ga3_ga4_mapping( $dimension );
1293 $request_body['dimensions'][] = array( 'name' => $dimension );
1294 }
1295 } else {
1296 $dimension = AIWP_Tools::ga3_ga4_mapping( $dimensions1 );
1297 $request_body['dimensions'] = array();
1298 $request_body['dimensions'][] = array( 'name' => $dimension );
1299 }
1300 }
1301 $headers = array( 'Authorization' => 'Bearer ' . $access_token, 'Content-Type' => 'application/json' );
1302 $request_body_json = json_encode( $request_body );
1303 $args = array( 'headers' => $headers, 'body' => $request_body_json );
1304 $response = wp_remote_post( $api_url, $args );
1305 if ( is_wp_error( $response ) ) {
1306 $timeout = $this->get_timeouts();
1307 AIWP_Tools::set_error( $response, $timeout );
1308 return $response->get_error_code();
1309 } else {
1310 $response_body = wp_remote_retrieve_body( $response );
1311 $response_data = json_decode( $response_body, true );
1312 if ( isset( $response_data['error'] ) ) {
1313 $timeout = $this->get_timeouts();
1314 $error = new WP_Error();
1315 if ( isset( $response_data['error']['code'] ) && isset( $response_data['error']['status'] ) ) {
1316 $error->add( $response_data['error']['code'], $response_data['error']['message'], array( $response_data['error']['status'], 'trying to refresh token' ) );
1317 } else if ( isset( $response_data['error'] ) && isset( $response_data['error_description'] ) ) {
1318 $error->add( $response_data['error'], $response_data['error_description'], 'trying to refresh token' );
1319 } else if ( isset( $response_data['error']['code'] ) && isset( $response_data['error']['message'] ) ) {
1320 $error->add( $response_data['error']['code'], $response_data['error']['message'], 'trying to verify site' );
1321 }
1322 AIWP_Tools::set_error( $error, $timeout );
1323 return $error->get_error_code();
1324 }
1325 $category = $response_data;
1326 }
1327 AIWP_Tools::set_cache( $serial, array( $data, $category ), 55 );
1328 } else {
1329 $data = $transient[0];
1330 $category = $transient[1];
1331 }
1332 if ( ! isset( $data['rows'] ) ) {
1333 return 621;
1334 }
1335 if ( isset( $data['rows'] ) ) {
1336 $aiwp_data['rows'] = array();
1337 foreach ( $data['rows'] as $row ) {
1338 $values = array();
1339 if ( isset( $row['dimensionValues'][0] ) ) {
1340 foreach ( $row['dimensionValues'] as $item ) {
1341 $values[] = $item['value'];
1342 }
1343 }
1344 if ( isset( $row['metricValues'][0] ) ) {
1345 foreach ( $row['metricValues'] as $item ) {
1346 $values[] = $item['value'];
1347 }
1348 }
1349 $aiwp_data['rows'][] = $values;
1350 }
1351 }
1352 if ( isset( $category['rows'] ) ) {
1353 $aiwp_data['category'] = array();
1354 foreach ( $category['rows'] as $row ) {
1355 $values = array();
1356 if ( isset( $row['dimensionValues'][0] ) ) {
1357 foreach ( $row['dimensionValues'] as $item ) {
1358 $values[] = $item['value'];
1359 }
1360 }
1361 if ( isset( $row['metricValues'][0] ) ) {
1362 foreach ( $row['metricValues'] as $item ) {
1363 $values[] = $item['value'];
1364 }
1365 }
1366 $aiwp_data['category'][] = $values;
1367 }
1368 }
1369
1370 // Leveraging total counts to JavaScript
1371 $aiwp_data['totals'] = 0;
1372
1373 return $aiwp_data;
1374 }
1375
1376 /**
1377 * Handles ajax requests and calls the needed methods
1378 * @param
1379 * $projectId
1380 * @param
1381 * $query
1382 * @param
1383 * $from
1384 * @param
1385 * $to
1386 * @param
1387 * $filter
1388 * @return number
1389 */
1390 public function get( $projectId, $query, $from = false, $to = false, $filter = '', $metric = 'sessions' ) {
1391 if ( empty( $projectId ) || '' == $projectId || 'Disabled' == $projectId ) {
1392 wp_die( 626 );
1393 }
1394 if ( in_array( $query, array( 'sessions', 'users', 'organicSearches', 'visitBounceRate', 'pageviews', 'uniquePageviews' ) ) ) {
1395 return $this->get_areachart_data_ga4( $projectId, $from, $to, $query, $filter );
1396 }
1397 if ( 'bottomstats' == $query ) {
1398 return $this->get_bottomstats_ga4( $projectId, $from, $to, $filter );
1399 }
1400 if ( 'locations' == $query ) {
1401 return $this->get_locations_ga4( $projectId, $from, $to, $metric, $filter );
1402 }
1403 if ( 'contentpages' == $query ) {
1404 return $this->get_contentpages_ga4( $projectId, $from, $to, $metric, $filter );
1405 }
1406 if ( 'referrers' == $query ) {
1407 return $this->get_referrers_ga4( $projectId, $from, $to, $metric, $filter );
1408 }
1409 if ( 'searches' == $query ) {
1410 return $this->get_searches_ga4( $projectId, $from, $to, $metric, $filter );
1411 }
1412 if ( '404errors' == $query ) {
1413 $filter = $this->aiwp->config->options['pagetitle_404'];
1414 return $this->get_404errors_ga4( $projectId, $from, $to, $metric, $filter );
1415 }
1416 if ( 'realtime' == $query ) {
1417 return $this->get_realtime_ga4( $projectId );
1418 }
1419 if ( 'channelGrouping' == $query || 'deviceCategory' == $query ) {
1420 return $this->get_orgchart_data_ga4( $projectId, $from, $to, $query, $metric, $filter );
1421 }
1422 if ( in_array( $query, array( 'medium', 'visitorType', 'socialNetwork', 'source', 'browser', 'operatingSystem', 'screenResolution', 'mobileDeviceBranding' ) ) ) {
1423 return $this->get_piechart_data_ga4( $projectId, $from, $to, $query, $metric, $filter );
1424 }
1425 wp_die( 627 );
1426 }
1427 }
1428 }
1429