PluginProbe
ShareThis Dashboard for Google Analytics / 3.4.1
ShareThis Dashboard for Google Analytics v3.4.1
3.4.1 3.4.0 3.3.2 trunk 1.0.7 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 2.1 2.1.2 2.1.3 2.1.4 2.1.5 2.2.5 2.3.5 2.3.6 2.3.7 2.3.8 2.4.0 2.4.1 2.5.0 2.5.1 All 45 releases
googleanalytics / class / class-ga-admin.php

class-ga-admin.php in ShareThis Dashboard for Google Analytics 3.4.1, at class/class-ga-admin.php

1,832 lines 54.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Google Analytics admin class.
4 *
5 * @package GoogleAnalytics
6 */
7
8 if (!defined('ABSPATH')) exit;
9
10 use Google\Client;
11
12 /**
13 * Admin class.
14 */
15 class Ga_Admin {
16
17 /**
18 * GA Token info.
19 *
20 * @var array The GA token info.
21 */
22 public array $token;
23
24 const GA_WEB_PROPERTY_ID_OPTION_NAME = 'googleanalytics_web_property_id';
25 const GA_EXCLUDE_ROLES_OPTION_NAME = 'googleanalytics_exclude_roles';
26 const GA_SHARETHIS_TERMS_OPTION_NAME = 'googleanalytics_sharethis_terms';
27 const GA_HIDE_TERMS_OPTION_NAME = 'googleanalytics_hide_terms';
28 const GA_VERSION_OPTION_NAME = 'googleanalytics_version';
29 const GA_SELECTED_ACCOUNT = 'googleanalytics_selected_account';
30 const GA_OAUTH_AUTH_CODE_OPTION_NAME = 'googleanalytics_oauth_auth_code';
31 const GA_OAUTH_AUTH_TOKEN_OPTION_NAME = 'googleanalytics_oauth_auth_token';
32 const GA_ACCOUNT_DATA_OPTION_NAME = 'googleanalytics_account_data';
33 const GA_WEB_PROPERTY_ID_MANUALLY_OPTION_NAME = 'googleanalytics_web_property_id_manually';
34 const GA_WEB_PROPERTY_ID_MANUALLY_VALUE_OPTION_NAME = 'googleanalytics_web_property_id_manually_value';
35 const GA_SHARETHIS_PROPERTY_ID = 'googleanalytics_sherethis_property_id';
36 const GA_SHARETHIS_PROPERTY_SECRET = 'googleanalytics_sherethis_property_secret';
37 const GA_SHARETHIS_VERIFICATION_RESULT = 'googleanalytics_sherethis_verification_result';
38 const MIN_WP_VERSION = '3.8';
39 const GA_SHARETHIS_API_ALIAS = 'sharethis';
40 const GA_DISABLE_ALL_FEATURES = 'googleanalytics_disable_all_features';
41 const GA_HEARTBEAT_API_CACHE_UPDATE = false;
42 const NOTICE_SUCCESS = 'success';
43 const NOTICE_WARNING = 'warning';
44 const NOTICE_ERROR = 'error';
45
46 /**
47 * Instantiate API client.
48 *
49 * @param string $type Type string.
50 *
51 * @return Ga_Lib_Google_Api_Client|null
52 */
53 public static function api_client( $type = '' ) {
54 if ( self::GA_SHARETHIS_API_ALIAS === $type ) {
55 $instance = Ga_Lib_Sharethis_Api_Client::get_instance();
56 } else {
57 $instance = Ga_Lib_Google_Api_Client::get_instance();
58 $token = Ga_Helper::get_option( self::GA_OAUTH_AUTH_TOKEN_OPTION_NAME );
59 $token = true === empty( $token ) ? get_option( 'ga4-token' ) : $token;
60
61 try {
62 if ( ! empty( $token ) ) {
63 $token = json_decode( $token, true );
64 $instance->set_access_token( $token );
65 }
66 } catch ( Exception $e ) {
67 Ga_Helper::ga_oauth_notice( $e->getMessage() );
68 }
69 }
70
71 return $instance;
72 }
73
74 /**
75 * Initializes plugin's options during plugin activation process.
76 */
77 public static function activate_googleanalytics() {
78 add_option( self::GA_WEB_PROPERTY_ID_OPTION_NAME, Ga_Helper::GA_DEFAULT_WEB_ID );
79 add_option( self::GA_EXCLUDE_ROLES_OPTION_NAME, wp_json_encode( array() ) );
80 add_option( self::GA_VERSION_OPTION_NAME );
81 add_option( self::GA_OAUTH_AUTH_CODE_OPTION_NAME );
82 add_option( self::GA_OAUTH_AUTH_TOKEN_OPTION_NAME );
83 add_option( self::GA_ACCOUNT_DATA_OPTION_NAME );
84 add_option( self::GA_SELECTED_ACCOUNT );
85 add_option( self::GA_WEB_PROPERTY_ID_MANUALLY_OPTION_NAME, '0' );
86 add_option( self::GA_WEB_PROPERTY_ID_MANUALLY_VALUE_OPTION_NAME );
87 add_option( self::GA_DISABLE_ALL_FEATURES );
88 update_option( self::GA_SHARETHIS_TERMS_OPTION_NAME, true );
89 update_option( 'googleanalytics_fresh', 'true' );
90 Ga_Cache::add_cache_options();
91 }
92
93 /**
94 * Deletes plugin's options during plugin activation process.
95 */
96 public static function deactivate_googleanalytics() {
97 delete_option( self::GA_WEB_PROPERTY_ID_OPTION_NAME );
98 delete_option( self::GA_EXCLUDE_ROLES_OPTION_NAME );
99 delete_option( self::GA_OAUTH_AUTH_CODE_OPTION_NAME );
100 delete_option( self::GA_OAUTH_AUTH_TOKEN_OPTION_NAME );
101 delete_option( self::GA_ACCOUNT_DATA_OPTION_NAME );
102 delete_option( self::GA_SELECTED_ACCOUNT );
103 delete_option( self::GA_WEB_PROPERTY_ID_MANUALLY_OPTION_NAME );
104 delete_option( self::GA_WEB_PROPERTY_ID_MANUALLY_VALUE_OPTION_NAME );
105 delete_option( self::GA_DISABLE_ALL_FEATURES );
106 delete_option( Ga_SupportLogger::LOG_OPTION );
107 delete_option( 'googleanalytics_version' );
108 delete_option( 'googleanalytics_optimize_code' );
109 delete_option( 'googleanalytics_ip_anonymization' );
110 delete_option( 'googleanalytics-hide-review' );
111 delete_option( 'googleanalytics_enable_debug_mode' );
112 delete_option( 'googleanalytics_gdpr_config' );
113 delete_option( 'googleanalytics_demographic' );
114 delete_option( 'googleanalytics_demo_data' );
115 delete_option( 'googleanalytics_demo_date' );
116 delete_option( 'googleanalytics_send_data' );
117 delete_option( 'googleanalytics_hide_terms' );
118 delete_option( 'googleanalytics_sharethis_terms' );
119 delete_option( 'googleanalytics_sherethis_property_id' );
120 delete_option( 'googleanalytics_sherethis_property_secret' );
121 delete_option( 'sharethis_ga_refresh_token' );
122 delete_option( 'googleanalytics-ga4-gdpr' );
123 delete_option( 'googleanalytics-ga4-ip-anon' );
124 delete_option( 'googleanalytics-ga4-demo' );
125 delete_option( 'googleanalytics-ga4-exclude-roles' );
126 delete_option( 'googleanalytics-ga4-optimize' );
127 delete_option( 'ga4-token' );
128 delete_option( 'googleanalytics-ga4-property' );
129 delete_option( self::GA_SHARETHIS_TERMS_OPTION_NAME );
130 Ga_Cache::delete_cache_options();
131 }
132
133 /**
134 * Deletes plugin's options during plugin uninstallation process.
135 */
136 public static function uninstall_googleanalytics() {
137 delete_option( self::GA_SHARETHIS_TERMS_OPTION_NAME );
138 delete_option( self::GA_HIDE_TERMS_OPTION_NAME );
139 delete_option( self::GA_VERSION_OPTION_NAME );
140 delete_option( self::GA_SHARETHIS_PROPERTY_ID );
141 delete_option( self::GA_SHARETHIS_PROPERTY_SECRET );
142 delete_option( self::GA_SHARETHIS_VERIFICATION_RESULT );
143 }
144
145 /**
146 * Do actions during plugin load.
147 */
148 public static function loaded_googleanalytics() {
149 self::update_googleanalytics();
150 }
151
152 /**
153 * Update hook fires when plugin is being loaded.
154 */
155 public static function update_googleanalytics() {
156 $version = get_option( self::GA_VERSION_OPTION_NAME );
157 $installed_version = get_option( self::GA_VERSION_OPTION_NAME, '2.4.0' );
158 $old_property_value = Ga_Helper::get_option( 'web_property_id' );
159
160 if ( version_compare( $installed_version, GOOGLEANALYTICS_VERSION, 'eq' ) ) {
161 return;
162 }
163
164 if ( version_compare( $installed_version, GOOGLEANALYTICS_VERSION, 'lt' ) ) {
165
166 if ( ! empty( $old_property_value ) ) {
167 Ga_Helper::update_option( self::GA_WEB_PROPERTY_ID_MANUALLY_VALUE_OPTION_NAME, $old_property_value );
168 Ga_Helper::update_option( self::GA_WEB_PROPERTY_ID_MANUALLY_OPTION_NAME, 1 );
169 delete_option( 'web_property_id' );
170 }
171
172 /*
173 * Universal Analytics is retired. Installs that already have a GA4
174 * property selected but still carry a legacy UA view ID stay stuck
175 * in UA-mode gating (which requires an empty view ID). Clear the
176 * stale view ID once so those installs flip to GA4 automatically.
177 */
178 if ( ! empty( get_option( 'googleanalytics-ga4-property' ) ) && ! empty( get_option( 'googleanalytics-view-id' ) ) ) {
179 update_option( 'googleanalytics-view-id', '' );
180 }
181 }
182
183 update_option( self::GA_VERSION_OPTION_NAME, GOOGLEANALYTICS_VERSION );
184 }
185
186 /**
187 * Preupdate excluding rules.
188 *
189 * @param string $new_value New value.
190 * @param string $old_value Old value.
191 *
192 * @return false|string
193 */
194 public static function preupdate_exclude_roles( $new_value, $old_value ) {
195 if ( false === Ga_Helper::are_features_enabled() ) {
196 return '';
197 }
198
199 return wp_json_encode( $new_value );
200 }
201
202 /**
203 * Pre-update hook for preparing JSON structure.
204 *
205 * @param string $new_value New value.
206 * @param string $old_value Old value.
207 *
208 * @return mixed
209 */
210 public static function preupdate_selected_account( $new_value, $old_value ) {
211 $data = null;
212 if ( ! empty( $new_value ) ) {
213 $data = explode( '_', $new_value );
214
215 if ( ! empty( $data[1] ) ) {
216 Ga_Helper::update_option( self::GA_WEB_PROPERTY_ID_OPTION_NAME, $data[1] );
217 }
218 }
219
220 return wp_json_encode( $data );
221 }
222
223 /**
224 * Pre-update disable all features.
225 *
226 * @param string $new_value New value.
227 * @param string $old_value Old value.
228 *
229 * @return mixed
230 */
231 public static function preupdate_disable_all_features( $new_value, $old_value ) {
232 if ( 'on' === $old_value ) {
233 Ga_Helper::update_option( self::GA_WEB_PROPERTY_ID_MANUALLY_OPTION_NAME, false );
234 }
235
236 return $new_value;
237 }
238
239 /**
240 * Pre-update optimize code.
241 *
242 * @param string $new_value New value.
243 * @param string $old_value Old value.
244 *
245 * @return mixed|string
246 */
247 public static function preupdate_optimize_code( $new_value, $old_value ) {
248 if ( ! empty( $new_value ) ) {
249 $new_value = sanitize_text_field( wp_unslash( $new_value ) );
250 }
251
252 return $new_value;
253 }
254
255 /**
256 * Pre-update IP Anonymization.
257 *
258 * @param string $new_value New value.
259 * @param string $old_value Old value.
260 *
261 * @return mixed
262 */
263 public static function preupdate_ip_anonymization( $new_value, $old_value ) {
264 return $new_value;
265 }
266
267 /**
268 * Pre-update Enable GA Debugging.
269 *
270 * @param string $new_value New value.
271 * @param string $old_value Old value.
272 *
273 * @return mixed
274 */
275 public static function preupdate_enable_debug_mode( $new_value, $old_value ) {
276 return $new_value;
277 }
278
279 /**
280 * Registers plugin's settings.
281 */
282 public static function admin_init_googleanalytics() {
283 register_setting(
284 GA_NAME,
285 self::GA_WEB_PROPERTY_ID_OPTION_NAME,
286 array(
287 'type' => 'string',
288 'sanitize_callback' => 'sanitize_text_field',
289 )
290 );
291
292 register_setting(
293 GA_NAME,
294 self::GA_EXCLUDE_ROLES_OPTION_NAME,
295 array(
296 'type' => 'array',
297 'sanitize_callback' => array( __CLASS__, 'sanitize_roles_option' ),
298 )
299 );
300
301 register_setting(
302 GA_NAME,
303 self::GA_SELECTED_ACCOUNT,
304 array(
305 'type' => 'string',
306 'sanitize_callback' => array( __CLASS__, 'sanitize_selected_account' ),
307 )
308 );
309
310 register_setting(
311 GA_NAME,
312 self::GA_OAUTH_AUTH_CODE_OPTION_NAME,
313 array(
314 'type' => 'string',
315 'sanitize_callback' => 'sanitize_text_field',
316 )
317 );
318
319 register_setting(
320 GA_NAME,
321 self::GA_WEB_PROPERTY_ID_MANUALLY_OPTION_NAME,
322 array(
323 'type' => 'boolean',
324 'sanitize_callback' => array( __CLASS__, 'sanitize_checkbox' ),
325 )
326 );
327
328 register_setting(
329 GA_NAME,
330 self::GA_WEB_PROPERTY_ID_MANUALLY_VALUE_OPTION_NAME,
331 array(
332 'type' => 'string',
333 'sanitize_callback' => 'sanitize_text_field',
334 )
335 );
336
337 register_setting(
338 GA_NAME,
339 self::GA_DISABLE_ALL_FEATURES,
340 array(
341 'type' => 'boolean',
342 'sanitize_callback' => array( __CLASS__, 'sanitize_checkbox' ),
343 )
344 );
345
346 register_setting(
347 GA_NAME,
348 'googleanalytics_optimize_code',
349 array(
350 'type' => 'boolean',
351 'sanitize_callback' => array( __CLASS__, 'sanitize_checkbox' ),
352 )
353 );
354
355 register_setting(
356 GA_NAME,
357 'googleanalytics_ip_anonymization',
358 array(
359 'type' => 'boolean',
360 'sanitize_callback' => array( __CLASS__, 'sanitize_checkbox' ),
361 )
362 );
363
364 register_setting(
365 GA_NAME,
366 'googleanalytics_enable_debug_mode',
367 array(
368 'type' => 'boolean',
369 'sanitize_callback' => array( __CLASS__, 'sanitize_checkbox' ),
370 )
371 );
372
373 register_setting(
374 GA_NAME . 'ga4',
375 'googleanalytics-ga4-property',
376 array(
377 'type' => 'string',
378 'sanitize_callback' => 'Ga_Admin::sanitize_ga4_property',
379 )
380 );
381
382 register_setting(
383 GA_NAME . 'ga4',
384 'googleanalytics-ga4-optimize',
385 array(
386 'type' => 'boolean',
387 'sanitize_callback' => array( __CLASS__, 'sanitize_checkbox' ),
388 )
389 );
390
391 register_setting(
392 GA_NAME . 'ga4',
393 'googleanalytics-ga4-exclude-roles',
394 array(
395 'type' => 'array',
396 'sanitize_callback' => array( __CLASS__, 'sanitize_roles_option' ),
397 )
398 );
399
400 register_setting(
401 GA_NAME . 'ga4',
402 'googleanalytics-ga4-demo',
403 array(
404 'type' => 'boolean',
405 'sanitize_callback' => array( __CLASS__, 'sanitize_checkbox' ),
406 )
407 );
408
409 register_setting(
410 GA_NAME . 'ga4',
411 'googleanalytics-ga4-ip-anon',
412 array(
413 'type' => 'boolean',
414 'sanitize_callback' => array( __CLASS__, 'sanitize_checkbox' ),
415 )
416 );
417
418 register_setting(
419 GA_NAME . 'ga4',
420 'googleanalytics-ga4-gdpr',
421 array(
422 'type' => 'boolean',
423 'sanitize_callback' => array( __CLASS__, 'sanitize_checkbox' ),
424 )
425 );
426 }
427
428 public static function sanitize_checkbox( $value ) {
429 return ! empty( $value ) ? 1 : 0;
430 }
431
432 public static function sanitize_roles_option( $value ) {
433 if ( ! is_array( $value ) ) {
434 return array();
435 }
436
437 $value = array_map( 'sanitize_key', $value );
438 $value = array_filter( $value );
439
440 return array_values( array_unique( $value ) );
441 }
442
443 public static function sanitize_selected_account( $value ) {
444 if ( is_array( $value ) ) {
445 return array_map( 'sanitize_text_field', wp_unslash( $value ) );
446 }
447
448 return sanitize_text_field( wp_unslash( $value ) );
449 }
450
451 /**
452 * Builds plugin's menu structure.
453 */
454 public static function admin_menu_googleanalytics() {
455 $gdpr = get_option( 'googleanalytics_gdpr_config' );
456
457 if ( current_user_can( 'manage_options' ) ) {
458 add_menu_page( 'Google Analytics', 'Google Analytics', 'manage_options', 'googleanalytics', 'Ga_Admin::statistics_page_googleanalytics', 'dashicons-chart-line', 1000 );
459 add_submenu_page( 'googleanalytics', 'Google Analytics', __( 'Dashboard', 'googleanalytics' ), 'manage_options', 'googleanalytics', 'Ga_Admin::statistics_page_googleanalytics' );
460 add_submenu_page( 'googleanalytics', 'Google Analytics', __( 'Settings', 'googleanalytics' ), 'manage_options', 'googleanalytics/settings', 'Ga_Admin::options_page_googleanalytics' );
461
462 if ( ! empty( $gdpr ) ) {
463 add_submenu_page(
464 'googleanalytics',
465 'Google Analytics',
466 __( 'GDPR', 'googleanalytics' ),
467 'manage_options',
468 'googleanalytics/gdpr',
469 'Ga_Admin::gdpr_page_googleanalytics'
470 );
471 }
472 }
473 }
474
475 /**
476 * Prepares and displays plugin's stats page.
477 */
478 public static function statistics_page_googleanalytics() {
479
480 if ( ! Ga_Helper::is_wp_version_valid() || ! Ga_Helper::is_php_version_valid() ) {
481 return false;
482 }
483
484 $data = self::get_stats_page();
485 Ga_View_Core::load(
486 'statistics',
487 array(
488 'data' => $data,
489 )
490 );
491
492 self::display_api_errors();
493 }
494
495 /**
496 * Prepares and displays plugin's settings page.
497 */
498 public static function options_page_googleanalytics() {
499 if ( false === Ga_Helper::is_wp_version_valid() || false === Ga_Helper::is_php_version_valid() ) {
500 return false;
501 }
502
503 if ( true === Ga_Helper::are_features_enabled() && true === Ga_Helper::is_curl_disabled() ) {
504 echo wp_kses_post(
505 Ga_Helper::ga_wp_notice(
506 __(
507 'Looks like cURL is not configured on your server. In order to authenticate your Google Analytics account and display statistics, cURL is required. Please contact your server administrator to enable it, or manually enter your Tracking ID.', 'googleanalytics'
508 ),
509 'warning'
510 )
511 );
512 }
513 /**
514 * Keeps data to be extracted as variables in the view.
515 *
516 * @var array $data
517 */
518 $data = array();
519
520 $data[ self::GA_WEB_PROPERTY_ID_OPTION_NAME ] = get_option( self::GA_WEB_PROPERTY_ID_OPTION_NAME );
521 $data[ self::GA_WEB_PROPERTY_ID_MANUALLY_VALUE_OPTION_NAME ] = get_option( self::GA_WEB_PROPERTY_ID_MANUALLY_VALUE_OPTION_NAME, '' );
522 $data[ self::GA_WEB_PROPERTY_ID_MANUALLY_OPTION_NAME ] = get_option( self::GA_WEB_PROPERTY_ID_MANUALLY_OPTION_NAME, '0' );
523 $data[ self::GA_DISABLE_ALL_FEATURES ] = get_option( self::GA_DISABLE_ALL_FEATURES );
524
525 $roles = Ga_Helper::get_user_roles();
526 $saved = json_decode( get_option( self::GA_EXCLUDE_ROLES_OPTION_NAME ), true );
527
528 $tmp = array();
529 if ( false === empty( $roles ) ) {
530 foreach ( $roles as $role ) {
531 $role_id = Ga_Helper::prepare_role_id( $role );
532 $tmp[] = array(
533 'name' => $role,
534 'id' => $role_id,
535 'checked' => ( ! empty( $saved[ $role_id ] ) && 'on' === $saved[ $role_id ] ),
536 );
537 }
538 }
539 $data['roles'] = $tmp;
540
541 if ( Ga_Helper::is_authorized() ) {
542 $data['ga_accounts_selector'] = self::get_accounts_selector();
543 $data['auth_button'] = self::get_auth_button( 'reauth' );
544 } else {
545 $data['popup_url'] = self::get_auth_popup_url();
546 $data['auth_button'] = self::get_auth_button( 'auth' );
547 }
548
549 $data['debug_modal'] = self::get_debug_modal();
550 $data['debug_info'] = Ga_SupportLogger::$debug_info;
551
552 // Sanitize error.
553 $error = filter_input( INPUT_GET, 'err', FILTER_UNSAFE_RAW );
554
555 if ( false === empty( $error ) ) {
556 switch ( $error ) {
557 case 1:
558 $data['error_message'] = Ga_Helper::ga_oauth_notice( 'There was a problem with Google Oauth2 authentication process. Please verify your site has a valid SSL Certificate in place and is using the HTTPS protocol.' );
559 break;
560 case 2:
561 $data['error_message'] = Ga_Helper::ga_wp_notice(
562 'Authentication code is incorrect.',
563 'error',
564 true
565 );
566 break;
567 }
568 }
569
570 $universal = '' !== get_option( 'googleanalytics_oauth_auth_token', '' );
571
572 if ( false !== $universal ) {
573 Ga_View_Core::load(
574 'old-page',
575 array(
576 'data' => $data,
577 'tooltip' => Ga_Helper::get_tooltip(),
578 )
579 );
580 } else {
581 Ga_View_Core::load(
582 'page',
583 array(
584 'data' => $data,
585 'tooltip' => Ga_Helper::get_tooltip(),
586 )
587 );
588 }
589
590 self::display_api_errors();
591 }
592
593 /**
594 * Prepares and displays plugin's gdpr page.
595 */
596 public static function gdpr_page_googleanalytics() {
597
598 if ( false === Ga_Helper::is_wp_version_valid() || false === Ga_Helper::is_php_version_valid() ) {
599 return false;
600 }
601 if ( true === Ga_Helper::are_features_enabled() && true === Ga_Helper::is_curl_disabled() ) {
602 echo wp_kses_post( Ga_Helper::ga_wp_notice( __( 'Looks like cURL is not configured on your server. In order to authenticate your Google Analytics account and display statistics, cURL is required. Please contact your server administrator to enable it, or manually enter your Tracking ID.', 'googleanalytics' ), 'warning' ) );
603 }
604
605 $vendor_data = self::get_vendors();
606 $vendors = $vendor_data['vendors'];
607 $purposes = array_column( $vendor_data['purposes'], 'name', 'id' );
608
609 include plugin_dir_path( __FILE__ ) . '../view/templates/gdpr-config.php';
610 }
611
612 /**
613 * Prepares and returns a plugin's URL to be opened in a popup window
614 * during Google authentication process.
615 *
616 * @return mixed
617 */
618 public static function get_auth_popup_url() {
619 return admin_url( Ga_Helper::create_url( Ga_Helper::GA_SETTINGS_PAGE_URL, array( Ga_Controller_Core::ACTION_PARAM_NAME => 'ga_action_auth' ) ) );
620 }
621
622 /**
623 * Prepares and returns Google Account's dropdown code.
624 *
625 * @return string
626 */
627 public static function get_accounts_selector() {
628 $selected = Ga_Helper::get_selected_account_data();
629 $selector = json_decode( get_option( self::GA_ACCOUNT_DATA_OPTION_NAME ), true );
630
631 if ( false === Ga_Helper::is_code_manually_enabled() && true === empty( $selector ) ) {
632 echo wp_kses_post( Ga_Helper::ga_wp_notice( "Hi there! It seems like we weren't able to locate a Google Analytics account attached to your email account. Can you please register for Google Analytics and then deactivate and reactivate the plugin?", 'warning' ) );
633 }
634
635 return Ga_View_Core::load(
636 'ga-accounts-selector',
637 array(
638 'selector' => $selector,
639 'selected' => $selected ? implode( '_', $selected ) : null,
640 'add_manually_enabled' => Ga_Helper::is_code_manually_enabled() || Ga_Helper::is_all_feature_disabled(),
641 ),
642 true
643 );
644 }
645
646 /**
647 * Adds JS scripts for the settings page.
648 */
649 public static function enqueue_ga_scripts() {
650 $property_id = get_option( 'googleanalytics_sherethis_property_id', true );
651 $secret = get_option( 'googleanalytics_sherethis_property_secret', true );
652 $config = wp_json_encode( get_option( 'googleanalytics_gdpr_config' ) );
653
654 wp_register_script(
655 GA_NAME . '-page-js',
656 Ga_Helper::get_plugin_url_with_correct_protocol() . '/js/' . GA_NAME . '_page.js',
657 array( 'jquery' ),
658 time(),
659 false
660 );
661
662 wp_enqueue_script( GA_NAME . '-page-js' );
663 wp_localize_script(
664 GA_NAME . '-page-js',
665 'googleAnalyticsPage',
666 array(
667 'ajaxurl' => admin_url( 'admin-ajax.php' ),
668 'nonce' => wp_create_nonce( 'ga_page_nonce' ),
669 'settingsURL' => admin_url( 'admin.php?page=googleanalytics/settings' ),
670 )
671 );
672
673 // @deprecated - Phase this out in favor of wp_localize_script.
674 wp_add_inline_script(
675 GA_NAME . '-page-js',
676 'var siteAdminUrl = \'' .
677 admin_url() .
678 '\'; var gaGdprConfig = \'' .
679 $config .
680 '\'; var ga_demo_nonce = "' .
681 wp_create_nonce( 'ga_demo_nonce' ) .
682 '"; var ga_property_id = "' . $property_id .
683 '"; var ga_secret_id = "' .
684 $secret .
685 '";'
686 );
687 }
688
689 /**
690 * Adds CSS plugin's scripts.
691 */
692 public static function enqueue_ga_css() {
693 wp_enqueue_style(
694 GA_NAME . '-css',
695 Ga_Helper::get_plugin_url_with_correct_protocol() . '/css/' . GA_NAME . '.css',
696 false,
697 time(),
698 'all'
699 );
700
701 wp_enqueue_style(
702 GA_NAME . '-additional-css',
703 Ga_Helper::get_plugin_url_with_correct_protocol() . '/css/ga_additional.css',
704 false,
705 GOOGLEANALYTICS_VERSION,
706 'all'
707 );
708
709 if ( true === Ga_Helper::is_wp_old() ) {
710 wp_enqueue_style(
711 GA_NAME . '-old-wp-support-css',
712 Ga_Helper::get_plugin_url_with_correct_protocol() . '/css/ga_old_wp_support.css',
713 false,
714 GOOGLEANALYTICS_VERSION,
715 'all'
716 );
717 }
718
719 wp_enqueue_style(
720 GA_NAME . '-modal-css',
721 Ga_Helper::get_plugin_url_with_correct_protocol() . '/css/ga_modal.css',
722 false,
723 GOOGLEANALYTICS_VERSION,
724 'all'
725 );
726 }
727
728 /**
729 * Enqueues dashboard JS scripts.
730 */
731 private static function enqueue_dashboard_scripts() {
732 wp_enqueue_script(
733 GA_NAME . '-dashboard-js',
734 Ga_Helper::get_plugin_url_with_correct_protocol() . '/js/' . GA_NAME . '_dashboard.js',
735 array( 'jquery' ),
736 GOOGLEANALYTICS_VERSION,
737 false
738 );
739 }
740
741 /**
742 * Enqueues plugin's JS and CSS scripts.
743 */
744 public static function enqueue_scripts() {
745 $domain = str_replace( 'http://', '', str_replace( 'https://', '', str_replace( '/wp-admin/', '', admin_url() ) ) );
746 $st_prop = get_option( self::GA_SHARETHIS_PROPERTY_ID );
747 $st_secret = get_option( self::GA_SHARETHIS_PROPERTY_SECRET );
748
749 if ( Ga_Helper::is_dashboard_page() || Ga_Helper::is_plugin_page() ) {
750 wp_register_script(
751 GA_NAME . '-js',
752 Ga_Helper::get_plugin_url_with_correct_protocol() . '/js/' . GA_NAME . '.js',
753 array( 'jquery' ),
754 GOOGLEANALYTICS_VERSION,
755 false
756 );
757 wp_enqueue_script( GA_NAME . '-js' );
758
759 wp_enqueue_script( 'googlecharts', 'https://www.gstatic.com/charts/loader.js', null, 1, false );
760 wp_add_inline_script( GA_NAME . '-js', 'var ga_demo_nonce = "' . wp_create_nonce( 'ga_demo_nonce' ) . '";' );
761
762 if ( empty( $st_prop ) || empty( $st_secret ) ) {
763 wp_register_script( 'googlecreateprop', Ga_Helper::get_plugin_url_with_correct_protocol() . '/js/googleanalytics_createprop.js', array( 'jquery', 'wp-util' ), time(), false );
764 wp_enqueue_script( 'googlecreateprop' );
765 wp_add_inline_script(
766 'googlecreateprop',
767 '
768 var gaNonce = "' . wp_create_nonce( 'googleanalyticsnonce' ) . '";
769 var gasiteURL = "' . $domain . '";
770 var gaAdminEmail = "' . get_option( 'admin_email' ) . '";
771 var gaFresh = "' . get_option( 'googleanalytics_fresh', 'false' ) . '";'
772 );
773 }
774
775 self::enqueue_ga_css();
776 }
777
778 if ( Ga_Helper::is_dashboard_page() ) {
779 self::enqueue_dashboard_scripts();
780 }
781
782 if ( Ga_Helper::is_plugin_page() ) {
783 self::enqueue_ga_scripts();
784 }
785 }
786
787 /**
788 * Prepares plugin's statistics page and return HTML code.
789 *
790 * @return string HTML code
791 */
792 public static function get_stats_page() {
793 $age_chart = null;
794 $boxes = null;
795 $chart = null;
796 $device_chart = null;
797 $gender_chart = null;
798 $labels = null;
799 $sources = null;
800
801 if ( false === Ga_Helper::is_all_feature_disabled() ) {
802 [ $chart, $age_chart, $device_chart, $gender_chart, $boxes, $labels, $sources ] = self::generate_stats_data();
803 }
804
805 return Ga_Helper::get_chart_page(
806 'stats',
807 compact(
808 'age_chart',
809 'boxes',
810 'chart',
811 'device_chart',
812 'gender_chart',
813 'labels',
814 'sources'
815 )
816 );
817 }
818
819 /**
820 * Prepare required PHP version warning.
821 */
822 public static function admin_notice_googleanalytics_php_version() {
823 echo wp_kses_post( Ga_Helper::ga_wp_notice( 'Cannot use Google Analytics plugin. PHP version ' . phpversion() . ' is to low. Required PHP version: ' . Ga_Helper::PHP_VERSION_REQUIRED, 'error' ) );
824 }
825
826 /**
827 * Shows plugin's notice on the admin area.
828 */
829 public static function admin_notice_googleanalytics() {
830 if ( ( ! get_option( self::GA_SHARETHIS_TERMS_OPTION_NAME ) && true === Ga_Helper::is_plugin_page() ) ||
831 ( ! get_option( self::GA_SHARETHIS_TERMS_OPTION_NAME ) && ! get_option( self::GA_HIDE_TERMS_OPTION_NAME ) ) ) {
832 $current_url = Ga_Helper::get_current_url();
833 $url = ( strstr(
834 $current_url,
835 '?'
836 ) ? $current_url . '&' : $current_url . '?' ) . http_build_query( array( Ga_Controller_Core::ACTION_PARAM_NAME => 'ga_action_update_terms' ) );
837 Ga_View_Core::load(
838 'ga-notice',
839 array(
840 'url' => $url,
841 )
842 );
843 }
844
845 $settings_updated = filter_input( INPUT_GET, 'settings-updated', FILTER_UNSAFE_RAW );
846
847 if ( false === empty( $settings_updated ) && Ga_Helper::is_plugin_page() ) {
848 echo wp_kses_post( Ga_Helper::ga_wp_notice( __( 'Settings saved', 'googleanalytics' ), self::NOTICE_SUCCESS ) );
849 }
850
851 if ( true === boolval( Ga_Helper::get_option( self::GA_DISABLE_ALL_FEATURES ) ) ) {
852 echo wp_kses(
853 Ga_Helper::ga_wp_notice(
854 __( 'You have disabled all extra features, click here to enable Dashboards, Viral Alerts and Google API.', 'googleanalytics' ),
855 'warning',
856 false,
857 array(
858 'url' => admin_url(
859 Ga_Helper::create_url(
860 Ga_Helper::GA_SETTINGS_PAGE_URL,
861 array( Ga_Controller_Core::ACTION_PARAM_NAME => 'ga_action_enable_all_features' )
862 )
863 ),
864 'label' => __( 'Enable', 'googleanalytics' ),
865 )
866 ),
867 array(
868 'button' => array(
869 'class' => array(),
870 'onclick' => array(),
871 ),
872 'div' => array(
873 'class' => array(),
874 ),
875 'p' => array(),
876 )
877 );
878 }
879 }
880
881 /**
882 * Prepare required WP version warning
883 */
884 public static function admin_notice_googleanalytics_wp_version() {
885 echo wp_kses(
886 Ga_Helper::ga_wp_notice(
887 'Google Analytics plugin requires at least WordPress version ' . self::MIN_WP_VERSION,
888 'error'
889 ),
890 array(
891 'button' => array(
892 'class' => array(),
893 'onclick' => array(),
894 ),
895 'div' => array(
896 'class' => array(),
897 ),
898 'p' => array(),
899 )
900 );
901 }
902
903 /**
904 * Hides plugin's notice
905 */
906 public static function admin_notice_hide_googleanalytics() {
907 if ( false === current_user_can( 'manage_options' ) ) {
908 wp_send_json_error( 'user not authorized' );
909 };
910
911 update_option( self::GA_HIDE_TERMS_OPTION_NAME, true );
912 }
913
914 /**
915 * Adds GA dashboard widget only for administrators.
916 */
917 public static function add_dashboard_device_widget() {
918 if (true === Ga_Helper::is_administrator() && true === Ga_Helper::is_dashboard_page()) {
919 wp_add_dashboard_widget(
920 'ga-dashboard-widget',
921 __( 'Google Analytics Dashboard', 'googleanalytics' ),
922 'Ga_Helper::add_ga_dashboard_widget'
923 );
924 }
925 }
926
927 /**
928 * Adds plugin's actions
929 */
930 public static function add_actions() {
931 add_action( 'admin_init', 'Ga_Admin::admin_init_googleanalytics' );
932 add_action( 'admin_menu', 'Ga_Admin::admin_menu_googleanalytics' );
933 add_action( 'admin_enqueue_scripts', 'Ga_Admin::enqueue_scripts' );
934 add_action( 'wp_dashboard_setup', 'Ga_Admin::add_dashboard_device_widget' );
935 add_action( 'wp_ajax_ga_ajax_data_change', 'Ga_Admin::ga_ajax_data_change' );
936 add_action( 'wp_ajax_ga_ajax_hide_review', 'Ga_Admin::ga_ajax_hide_review' );
937 add_action( 'wp_ajax_save_ga4_property_selection', 'Ga_Admin::save_ga4_property_selection' );
938 add_action( 'wp_ajax_save_ga4_final_setup', 'Ga_Admin::save_ga4_final_setup' );
939 add_action( 'wp_ajax_ga_ajax_enable_gdpr', 'Ga_Admin::ga_ajax_gdpr_enable' );
940 add_action( 'wp_ajax_ga_ajax_enable_demographic', 'Ga_Admin::ga_ajax_enable_demo' );
941 add_action( 'wp_ajax_ga_ajax_sign_out', 'Ga_Admin::ga_ajax_sign_out' );
942 add_action( 'wp_ajax_ga4_ajax_sign_out', 'Ga_Admin::ga4_ajax_sign_out' );
943 add_action( 'admin_notices', 'Ga_Admin::admin_notice_googleanalytics' );
944 add_action( 'heartbeat_tick', 'Ga_Admin::run_heartbeat_jobs' );
945 add_action( 'wp_ajax_googleanalytics_send_debug_email', 'Ga_SupportLogger::send_email' );
946 add_action( 'wp_ajax_set_ga_credentials', 'Ga_Admin::create_ga_property' );
947
948 if ( ! get_option( self::GA_SHARETHIS_TERMS_OPTION_NAME ) && ! get_option( self::GA_HIDE_TERMS_OPTION_NAME ) ) {
949 add_action( 'wp_ajax_googleanalytics_hide_terms', 'Ga_Admin::admin_notice_hide_googleanalytics' );
950 }
951 }
952
953 /**
954 * Runs jobs
955 *
956 * @param string $response Response string.
957 * @param string $screen_id Screen ID string.
958 */
959 public static function run_heartbeat_jobs( $response, $screen_id = '' ) {
960
961 if ( self::GA_HEARTBEAT_API_CACHE_UPDATE ) {
962 // Disable cache for ajax request.
963 self::api_client()->set_disable_cache( true );
964
965 // Try to regenerate cache if needed.
966 self::generate_stats_data();
967 }
968 }
969
970 /**
971 * Adds plugin's filters
972 */
973 public static function add_filters() {
974 add_filter( 'plugin_action_links', 'Ga_Admin::ga_action_links', 10, 5 );
975 }
976
977 /**
978 * Adds new action links on the plugin list.
979 *
980 * @param array $actions Actions array.
981 * @param string $plugin_file Plugin file path string.
982 *
983 * @return mixed
984 */
985 public static function ga_action_links( $actions, $plugin_file ) {
986 if ( basename( $plugin_file ) === GA_NAME . '.php' ) {
987 array_unshift( $actions, '<a href="' . esc_url( get_admin_url( null, Ga_Helper::GA_SETTINGS_PAGE_URL ) ) . '">' . __( 'Settings', 'googleanalytics' ) . '</a>' );
988 }
989
990 return $actions;
991 }
992
993 /**
994 * Init OAuth.
995 *
996 * @return false|void
997 */
998 public static function init_oauth() {
999 $ua_add = filter_input( INPUT_GET, 'ua', FILTER_UNSAFE_RAW );
1000
1001 if ( 't' !== $ua_add ) {
1002 return false;
1003 }
1004
1005 $token = get_option( 'ga4-token' );
1006
1007 if ( false === empty( $token ) ) {
1008 self::api_client()->set_access_token( json_decode( $token, true ) );
1009 // Get accounts data.
1010 $account_summaries = self::api_client()->call( 'ga_api_account_summaries' );
1011 self::save_ga_account_summaries( $account_summaries->get_data() );
1012 update_option( self::GA_SELECTED_ACCOUNT, '' );
1013
1014 wp_safe_redirect( admin_url( Ga_Helper::GA_SETTINGS_PAGE_URL ) );
1015 }
1016 }
1017
1018 /**
1019 * Save access token.
1020 *
1021 * @param Ga_Lib_Api_Response $response API response.
1022 * @param string $refresh_token Refresh token string.
1023 *
1024 * @return boolean
1025 */
1026 public static function save_access_token( $response, $refresh_token = '' ) {
1027 $access_token = $response->get_data();
1028 if ( ! empty( $access_token ) ) {
1029 $access_token['created'] = time();
1030 } else {
1031 return false;
1032 }
1033
1034 if ( ! empty( $refresh_token ) ) {
1035 $access_token['refresh_token'] = $refresh_token;
1036 }
1037
1038 return update_option( self::GA_OAUTH_AUTH_TOKEN_OPTION_NAME, wp_json_encode( $access_token ) );
1039 }
1040
1041 /**
1042 * Saves Google Analytics account data.
1043 *
1044 * @param array $data Data array.
1045 *
1046 * @return array
1047 */
1048 public static function save_ga_account_summaries( $data ) {
1049 $return = array();
1050 if ( ! empty( $data['items'] ) ) {
1051 foreach ( $data['items'] as $item ) {
1052 $tmp = array();
1053 $tmp['id'] = $item['id'];
1054 $tmp['name'] = $item['name'];
1055 if ( is_array( $item['webProperties'] ) ) {
1056 foreach ( $item['webProperties'] as $property ) {
1057 $profiles = array();
1058 if ( is_array( $property['profiles'] ) ) {
1059 foreach ( $property['profiles'] as $profile ) {
1060 $profiles[] = array(
1061 'id' => $profile['id'],
1062 'name' => $profile['name'],
1063 );
1064 }
1065 }
1066
1067 $tmp['webProperties'][] = array(
1068 'internalWebPropertyId' => $property['internalWebPropertyId'],
1069 'webPropertyId' => $property['id'],
1070 'name' => $property['name'],
1071 'profiles' => $profiles,
1072 );
1073 }
1074 }
1075
1076 $return[] = $tmp;
1077 }
1078
1079 update_option( self::GA_ACCOUNT_DATA_OPTION_NAME, wp_json_encode( $return ) );
1080 } else {
1081 update_option( self::GA_ACCOUNT_DATA_OPTION_NAME, '' );
1082 }
1083 update_option( self::GA_WEB_PROPERTY_ID_OPTION_NAME, '' );
1084
1085 return $return;
1086 }
1087
1088 /**
1089 * Handle AJAX data for the GA dashboard widget.
1090 */
1091 public static function ga_ajax_data_change() {
1092 if ( false === current_user_can( 'manage_options' ) ) {
1093 wp_send_json_error( 'user not authorized' );
1094 };
1095
1096 if ( Ga_Admin_Controller::validate_ajax_data_change_post() ) {
1097 $date_range = filter_input( INPUT_POST, 'date_range', FILTER_UNSAFE_RAW );
1098 $metric = filter_input( INPUT_POST, 'metric', FILTER_UNSAFE_RAW );
1099
1100 echo wp_kses_post( Ga_Helper::get_ga_dashboard_widget_data_json( sanitize_text_field( wp_unslash( $date_range ) ), sanitize_text_field( wp_unslash( $metric ) ), false, true ) );
1101 } else {
1102 echo wp_json_encode( array( 'error' => __( 'Invalid request.', 'googleanalytics' ) ) );
1103 }
1104
1105 wp_die();
1106 }
1107
1108 /**
1109 * Displays API error messages.
1110 *
1111 * @param string $alias Alias string.
1112 */
1113 public static function display_api_errors( $alias = '' ) {
1114 $errors = self::api_client( $alias )->get_errors();
1115
1116 if ( false === empty( get_option( 'ga4-token' ) ) && true === empty( get_option( 'googleanalytics-view-id' ) ) ) {
1117 return;
1118 };
1119
1120 if ( ! empty( $errors ) ) {
1121 foreach ( $errors as $error ) {
1122 echo wp_kses_post( Ga_Notice::get_message( $error ) );
1123 }
1124 }
1125 }
1126
1127 /**
1128 * Gets dashboard data.
1129 *
1130 * @return array
1131 */
1132 public static function generate_stats_data() {
1133 $selected = Ga_Helper::get_selected_account_data( true );
1134
1135 if ( true === empty( $selected ) ) {
1136 $view_id = get_option( 'googleanalytics-view-id', '' );
1137
1138 $selected = array( 'view_id' => $view_id );
1139 }
1140
1141 $update_data = self::check_data_date();
1142
1143 $current_period = Ga_Helper::get_date_range_from_request();
1144
1145 $current_period['start'] = false === empty( $current_period['from'] ) ?
1146 $current_period['from'] : gmdate( 'Y-m-d', strtotime( '-1 week' ) );
1147 $current_period['end'] = false === empty( $current_period['to'] ) ?
1148 $current_period['to'] : gmdate( 'Y-m-d', strtotime( 'now' ) );
1149
1150 $previous_period = Ga_Helper::get_previous_period_for_dates( $current_period['start'], $current_period['end'] );
1151
1152 $period_in_days = Ga_Helper::get_period_in_days( $current_period['start'], $current_period['end'] );
1153
1154 $date_ranges = Ga_Stats::set_date_ranges(
1155 $current_period['start'],
1156 $current_period['end'],
1157 $previous_period['start'],
1158 $previous_period['end']
1159 );
1160
1161 $query_params = Ga_Stats::get_query( 'main_chart', $selected['view_id'], $date_ranges );
1162
1163 $stats_data = self::api_client()->call(
1164 'ga_api_data',
1165 array( $query_params )
1166 );
1167
1168 $gender_params = Ga_Stats::get_query( 'gender', $selected['view_id'], $date_ranges );
1169 $gender_data = self::api_client()->call(
1170 'ga_api_data',
1171 array( $gender_params )
1172 );
1173 $age_params = Ga_Stats::get_query( 'age', $selected['view_id'], $date_ranges );
1174 $age_data = self::api_client()->call(
1175 'ga_api_data',
1176 array( $age_params )
1177 );
1178 $device_params = Ga_Stats::get_query( 'device', $selected['view_id'], $date_ranges );
1179 $device_data = self::api_client()->call(
1180 'ga_api_data',
1181 array( $device_params )
1182 );
1183
1184 $boxes_data = self::api_client()->call(
1185 'ga_api_data',
1186 array( Ga_Stats::get_query( 'boxes', $selected['view_id'] ) )
1187 );
1188 $sources_data = self::api_client()->call(
1189 'ga_api_data',
1190 array( Ga_Stats::get_query( 'sources', $selected['view_id'] ) )
1191 );
1192
1193 $chart = ! empty( $stats_data ) ? Ga_Stats::get_chart( $stats_data->get_data(), $period_in_days ) : array();
1194 $device_chart = false === empty( $device_data ) ? Ga_Stats::get_device_chart( $device_data->get_data() ) : array();
1195 $gender_chart = ! empty( $gender_data ) ? Ga_Stats::get_gender_chart( $gender_data->get_data() ) : array();
1196 $age_chart = ! empty( $age_data ) ? Ga_Stats::get_age_chart( $age_data->get_data() ) : array();
1197 $boxes = ! empty( $boxes_data ) ? Ga_Stats::get_boxes( $boxes_data->get_data() ) : array();
1198 $last_chart_date = ! empty( $chart ) ? $chart['date'] : strtotime( 'now' );
1199
1200 unset( $chart['date'] );
1201 $labels = array(
1202 'thisWeek' => gmdate( 'M d, Y', strtotime( '-6 day', $last_chart_date ) ) . ' - ' . gmdate( 'M d, Y', $last_chart_date ),
1203 'thisMonth' => gmdate( 'M d, Y', strtotime( '-29 day', $last_chart_date ) ) . ' - ' . gmdate( 'M d, Y', $last_chart_date ),
1204 );
1205 $sources = ! empty( $sources_data ) ? Ga_Stats::get_sources( $sources_data->get_data() ) : array();
1206
1207 // Add gender/age data for default period (1 week ago vs 2 weeks ago).
1208 if ( $update_data ) {
1209 $gender_params = Ga_Stats::get_query( 'gender', $selected['view_id'] );
1210 $gender_data = self::api_client()->call(
1211 'ga_api_data',
1212 array( $gender_params )
1213 );
1214 $age_params = Ga_Stats::get_query( 'age', $selected['view_id'] );
1215 $age_data = self::api_client()->call(
1216 'ga_api_data',
1217 array( $age_params )
1218 );
1219
1220 $gender_chart = ! empty( $gender_data ) ? Ga_Stats::get_gender_chart( $gender_data->get_data() ) : array();
1221 $age_chart = ! empty( $age_data ) ? Ga_Stats::get_age_chart( $age_data->get_data() ) : array();
1222
1223 self::update_demo_data(
1224 $gender_chart,
1225 $age_chart
1226 );
1227 }
1228
1229 return array( $chart, $age_chart, $device_chart, $gender_chart, $boxes, $labels, $sources );
1230 }
1231
1232 /**
1233 * Update demo data.
1234 *
1235 * @param array|bool $gender_response Gender response array.
1236 * @param array|bool $age_response Age response array.
1237 *
1238 * @return void
1239 */
1240 private static function update_demo_data( $gender_response = false, $age_response = false ) {
1241 $x = 0;
1242
1243 $demo_send_data = array();
1244 if ( $gender_response && $age_response ) {
1245 foreach ( $age_response as $type => $amount ) {
1246 $demo_send_data['age'][ $type ] = intval( $amount );
1247 $x ++;
1248 }
1249 foreach ( $gender_response as $type => $amount ) {
1250 $demo_send_data['gender'][ ucfirst( $type ) ] = intval( $amount );
1251 $x ++;
1252 }
1253 }
1254
1255 // Add data for send.
1256 update_option( 'googleanalytics_demo_data', wp_json_encode( $demo_send_data ) );
1257
1258 // Trigger send.
1259 update_option( 'googleanalytics_send_data', 'true' );
1260 }
1261
1262 /**
1263 * Check if we should send batch of demo data.
1264 *
1265 * @return bool
1266 */
1267 private static function check_data_date() {
1268 $demo_enabled = get_option( 'googleanalytics_demographic' );
1269 $demo_date = get_option( 'googleanalytics_demo_date' );
1270 $demo_date = ! empty( $demo_date ) ? strtotime( $demo_date ) : '';
1271 $current_date = gmdate( 'Y-m-d' );
1272 $thirty_date = '' !== $demo_date ? gmdate( 'Y-m-d', strtotime( '+1 month', $demo_date ) ) : '';
1273
1274 if ( empty( $demo_enabled ) || ! $demo_enabled ) {
1275 return false;
1276 }
1277
1278 if ( '' !== $demo_date && $thirty_date <= $current_date ) {
1279 return true;
1280 } elseif ( '' === $demo_date ) {
1281 return true;
1282 }
1283
1284 return false;
1285 }
1286
1287 /**
1288 * Returns auth or re-auth button
1289 *
1290 * @param string $button_type Button type.
1291 *
1292 * @return string
1293 */
1294 public static function get_auth_button( $button_type ) {
1295 return Ga_View_Core::load(
1296 'ga-auth-button',
1297 array(
1298 'label' => 'auth' === $button_type ? 'Authenticate with Google' : 'Re-authenticate with Google',
1299 'button_type' => $button_type,
1300 'url' => self::get_auth_popup_url(),
1301 'manually_id' => get_option( self::GA_WEB_PROPERTY_ID_MANUALLY_OPTION_NAME ),
1302 ),
1303 true
1304 );
1305 }
1306
1307 /**
1308 * Returns debug modal
1309 *
1310 * @return string
1311 */
1312 public static function get_debug_modal() {
1313 return Ga_View_Core::load(
1314 'ga-debug-modal',
1315 array(
1316 'debug_info' => Ga_SupportLogger::$debug_info,
1317 'debug_help_message' => Ga_SupportLogger::$debug_help_message,
1318 ),
1319 true
1320 );
1321 }
1322
1323 /**
1324 * Ajax hide review.
1325 *
1326 * @param \WP_Post $post Post object.
1327 *
1328 * @return void
1329 */
1330 public static function ga_ajax_hide_review( $post ) {
1331 if ( false === current_user_can( 'manage_options' ) ) {
1332 wp_send_json_error( 'user not authorized' );
1333 };
1334
1335 if ( true === Ga_Controller_Core::verify_nonce( 'ga_ajax_data_change' ) ) {
1336 update_option( 'googleanalytics-hide-review', true );
1337 }
1338
1339 wp_send_json_success( 'hidden' );
1340 }
1341
1342 /**
1343 * Ajax property selection for ga4.
1344 *
1345 * @return void
1346 */
1347 public static function save_ga4_property_selection() {
1348 if ( check_ajax_referer( 'ga4-setup', 'nonce' ) && false === current_user_can( 'manage_options' ) ) {
1349 wp_send_json_error( 'user not authorized' );
1350 };
1351
1352 $property = self::sanitize_ga4_property( wp_unslash( filter_input( INPUT_POST, 'property', FILTER_UNSAFE_RAW ) ) );
1353
1354 if ( '' === $property ) {
1355 wp_send_json_error( 'property not saved' );
1356 }
1357
1358 update_option( 'googleanalytics-ga4-property', $property );
1359
1360 // Selecting a GA4 property clears any legacy UA view ID so GA4-mode
1361 // gating (which requires an empty view ID) engages for installs that
1362 // were previously on Universal Analytics.
1363 update_option( 'googleanalytics-view-id', '' );
1364
1365 wp_send_json_success( $property );
1366 }
1367
1368 /**
1369 * Validate a GA4 property resource name.
1370 *
1371 * GA4 properties are identified as "properties/<numeric id>". Anything that
1372 * does not match (empty selection, placeholder text, legacy UA values) is
1373 * rejected so it can never reach the reporting API.
1374 *
1375 * @param mixed $value Raw property value.
1376 *
1377 * @return string The valid property resource name, or an empty string.
1378 */
1379 public static function sanitize_ga4_property( $value ) {
1380 $value = sanitize_text_field( (string) $value );
1381
1382 return 1 === preg_match( '#^properties/\d+$#', $value ) ? $value : '';
1383 }
1384
1385 /**
1386 * Ajax final ga4 setup.
1387 *
1388 * @return void
1389 */
1390 public static function save_ga4_final_setup()
1391 {
1392 if ( check_ajax_referer( 'ga4-setup', 'nonce' ) && false === current_user_can( 'manage_options' ) ) {
1393 wp_send_json_error( 'user not authorized' );
1394 };
1395
1396 $optimize = 'on' === filter_input( INPUT_POST, 'optimize', FILTER_UNSAFE_RAW );
1397 $exclude_roles = filter_input( INPUT_POST, 'exclude_roles', FILTER_UNSAFE_RAW );
1398 $enable_demo = 'on' === filter_input( INPUT_POST, 'enable_demo', FILTER_UNSAFE_RAW );
1399 $ip_anon = 'on' === filter_input( INPUT_POST, 'ip_anon', FILTER_UNSAFE_RAW );
1400 $enable_gdpr = 'on' === filter_input( INPUT_POST, 'enable_gdpr', FILTER_UNSAFE_RAW );
1401 $worked = '';
1402
1403 if (false === empty($optimize)) {
1404 update_option( 'googleanalytics-ga4-optimize', sanitize_text_field( wp_unslash( $optimize ) ) );
1405 $worked .= 'optimize worked : ';
1406 }
1407
1408
1409 if (false === empty($exclude_roles)) {
1410 $exclude_roles = explode( ',', sanitize_text_field( wp_unslash( $exclude_roles ) ) );
1411 $exclude_roles_array = [];
1412
1413 foreach($exclude_roles as $exclude_role) {
1414 $exclude_roles_array[$exclude_role] = 'on';
1415 }
1416 update_option('googleanalytics-ga4-exclude-roles', $exclude_roles_array);
1417 $worked .= 'exclude roles worked : ';
1418 } else {
1419 update_option('googleanalytics-ga4-exclude-roles', []);
1420 }
1421
1422 if (false === empty($enable_demo)) {
1423 update_option('googleanalytics-ga4-demo', '1');
1424 $worked .= 'enable demo worked : ';
1425 } else {
1426 update_option('googleanalytics-ga4-demo', '');
1427 }
1428
1429 if (false === empty($ip_anon)) {
1430 update_option('googleanalytics-ga4-ip-anon', 'on');
1431 $worked .= 'ip anon worked : ';
1432 } else {
1433 update_option('googleanalytics-ga4-ip-anon', '');
1434 }
1435
1436 if (false === empty($enable_gdpr)) {
1437 update_option('googleanalytics-ga4-gdpr', 'on');
1438 $worked .= 'gdpr worked';
1439 } else {
1440 update_option('googleanalytics-ga4-gdpr', '');
1441 }
1442
1443 if (false === empty($worked)) {
1444 wp_send_json_success($worked);
1445 }
1446
1447 wp_send_json_error('final setup not saved');
1448 }
1449
1450 /**
1451 * GA: Ajax callback for GDPR Enable.
1452 *
1453 * @param object $post Post object.
1454 *
1455 * @return void
1456 */
1457 public static function ga_ajax_gdpr_enable( $post ) {
1458 if ( check_ajax_referer( 'ga4-setup', 'nonce' ) && false === current_user_can( 'manage_options' ) ) {
1459 wp_send_json_error( 'user not authorized' );
1460 };
1461
1462 $post = filter_input_array( INPUT_POST, FILTER_UNSAFE_RAW );
1463
1464 array_walk_recursive(
1465 $post['config'],
1466 function ( &$value ) {
1467 $value = filter_var( trim( $value ), FILTER_UNSAFE_RAW );
1468 }
1469 );
1470
1471 if ( true === empty( $post['config'] ) ) {
1472 wp_send_json_error( 'No config found.' );
1473 }
1474
1475 update_option( 'googleanalytics_gdpr_config', $post['config'] );
1476
1477 wp_send_json_success( 'gdpr_on' );
1478 }
1479
1480 /**
1481 * GA: Ajax callback for Demo Enable.
1482 *
1483 * @param object $post Post object.
1484 *
1485 * @return void
1486 */
1487 public static function ga_ajax_enable_demo( $post ) {
1488 if ( false === current_user_can( 'manage_options' ) ) {
1489 wp_send_json_error( 'user not authorized' );
1490 };
1491
1492 check_ajax_referer( 'ga_demo_nonce', 'nonce' );
1493
1494 $enabled = 'true' === filter_input( INPUT_POST, 'enabled', FILTER_UNSAFE_RAW );
1495
1496 update_option( 'googleanalytics_demographic', sanitize_text_field( wp_unslash( $enabled ) ) );
1497 update_option( 'googleanalytics-ga4-demo', '1' );
1498
1499 wp_send_json_success( 'demo_on' );
1500 }
1501
1502 /**
1503 * GA: Ajax callback for signing out of GA UA.
1504 *
1505 * @param object $post Post object.
1506 *
1507 * @return void
1508 */
1509 public static function ga_ajax_sign_out( $post ) {
1510 if ( false === current_user_can( 'manage_options' ) ) {
1511 wp_send_json_error( 'user not authorized' );
1512 };
1513
1514 check_ajax_referer( 'ga_page_nonce', 'nonce' );
1515
1516 update_option( self::GA_OAUTH_AUTH_CODE_OPTION_NAME, '' );
1517 update_option( self::GA_OAUTH_AUTH_TOKEN_OPTION_NAME, '' );
1518
1519 wp_send_json_success();
1520 }
1521
1522 /**
1523 * GA: Ajax callback for signing out of GA4.
1524 *
1525 * @param object $post Post object.
1526 *
1527 * @return void
1528 */
1529 public static function ga4_ajax_sign_out( $post ) {
1530 if ( false === current_user_can( 'manage_options' ) ) {
1531 wp_send_json_error( 'user not authorized' );
1532 };
1533
1534 check_ajax_referer( 'ga_page_nonce', 'nonce' );
1535
1536 delete_option( 'googleanalytics-ga4-gdpr' );
1537 delete_option( 'googleanalytics-ga4-ip-anon' );
1538 delete_option( 'googleanalytics-ga4-demo' );
1539 delete_option( 'googleanalytics-ga4-exclude-roles' );
1540 delete_option( 'googleanalytics-ga4-optimize' );
1541 delete_option( 'ga4-token' );
1542 delete_option( 'googleanalytics-ga4-property' );
1543
1544 wp_send_json_success();
1545 }
1546
1547 /**
1548 * New property creation method.
1549 */
1550 public static function create_ga_property() {
1551 if ( false === current_user_can( 'manage_options' ) ) {
1552 wp_send_json_error( 'user not authorized' );
1553 };
1554
1555 check_ajax_referer( 'googleanalyticsnonce', 'nonce' );
1556
1557 $property_id = filter_input( INPUT_POST, 'propid', FILTER_UNSAFE_RAW );
1558
1559 $secret = filter_input( INPUT_POST, 'secret', FILTER_UNSAFE_RAW );
1560
1561 if ( true === empty( $property_id ) || true === empty( $secret ) ) {
1562 wp_send_json_error( 'Set credentials failed.' );
1563 }
1564
1565 $secret = sanitize_text_field( wp_unslash( $secret ) );
1566 $property_id = sanitize_text_field( wp_unslash( $property_id ) );
1567
1568 update_option( self::GA_SHARETHIS_PROPERTY_ID, $property_id );
1569 update_option( self::GA_SHARETHIS_PROPERTY_SECRET, $secret );
1570 }
1571
1572 /**
1573 * Helper function to get vendors.
1574 *
1575 * @return array Array of vendors.
1576 */
1577 private static function get_vendors() {
1578 $response = wp_remote_get( 'https://vendor-list.consensu.org/v2/vendor-list.json' );
1579
1580 return json_decode( wp_remote_retrieve_body( $response ), true );
1581 }
1582
1583 /**
1584 * Get the ga4 client info.
1585 *
1586 * @return Client
1587 * @throws \Google\Exception Exception thrown.
1588 */
1589 public function getGa4Client(): Client {
1590 $client = new Client();
1591 $client->setApplicationName( 'Google Analytics Plugin' );
1592 $client->setScopes(
1593 array( 'https://www.googleapis.com/auth/analytics.readonly' )
1594 );
1595 $client->setAccessType( 'offline' );
1596
1597 $token_info = get_option( 'ga4-token' );
1598
1599 if ( false === empty( $token_info ) ) {
1600 $access_token = json_decode( $token_info, true );
1601
1602 if ( is_array( $access_token ) ) {
1603 $client->setAccessToken( $access_token );
1604 }
1605 }
1606
1607 /*
1608 * If there is no valid token and the site is not connected yet,
1609 * return the client as-is. The caller can use this to show the
1610 * auth/connect URL instead of throwing.
1611 */
1612 if ( $client->isAccessTokenExpired() ) {
1613 if ( ! $this->hasGa4BrokerConnection() ) {
1614 return $client;
1615 }
1616
1617 $fresh_token = $this->refreshGa4AccessTokenFromBroker();
1618
1619 if ( ! is_array( $fresh_token ) || empty( $fresh_token['access_token'] ) ) {
1620 throw new Exception( esc_html__( 'Unable to refresh Google Analytics access token.', 'googleanalytics' ) );
1621 }
1622
1623 $client->setAccessToken( $fresh_token );
1624
1625 update_option( 'ga4-token', wp_json_encode( $fresh_token ) );
1626
1627 $this->token = $fresh_token;
1628 }
1629
1630 return $client;
1631 }
1632
1633 private function hasGa4BrokerConnection(): bool {
1634 $client_id = (string) get_option( self::GA_SHARETHIS_PROPERTY_ID, '' );
1635 $refresh_token = (string) get_option( 'sharethis_ga_refresh_token', '' );
1636
1637 return (
1638 '' !== $client_id &&
1639 '' !== $refresh_token
1640 );
1641 }
1642
1643 private function refreshGa4AccessTokenFromBroker(): array {
1644 $client_id = (string) get_option( self::GA_SHARETHIS_PROPERTY_ID, '' );
1645 $refresh_token = (string) get_option( 'sharethis_ga_refresh_token', '' );
1646
1647 if ( '' === $client_id ) {
1648 throw new Exception( esc_html__( 'Missing ShareThis installation ID.', 'googleanalytics' ) );
1649 }
1650
1651 if ( '' === $refresh_token ) {
1652 throw new Exception( esc_html__( 'Missing Google Analytics refresh token.', 'googleanalytics' ) );
1653 }
1654
1655 $response = wp_remote_post(
1656 trailingslashit( SHARETHIS_GA_BROKER_BASE ) . 'token',
1657 array(
1658 'timeout' => 20,
1659 'headers' => array(
1660 'Content-Type' => 'application/x-www-form-urlencoded',
1661 ),
1662 'body' => array(
1663 'grant_type' => 'refresh_token',
1664 'refresh_token' => $refresh_token,
1665 'client_id' => $client_id,
1666 ),
1667 )
1668 );
1669
1670 if ( is_wp_error( $response ) ) {
1671 throw new Exception( esc_html( $response->get_error_message() ) );
1672 }
1673
1674 $status_code = wp_remote_retrieve_response_code( $response );
1675 $body = wp_remote_retrieve_body( $response );
1676 $data = json_decode( $body, true );
1677
1678 if ( 200 !== $status_code || ! is_array( $data ) || empty( $data['access_token'] ) ) {
1679 throw new Exception( esc_html__( 'Broker token refresh failed.', 'googleanalytics' ) );
1680 }
1681
1682 return array(
1683 'access_token' => sanitize_text_field( (string) $data['access_token'] ),
1684 'expires_in' => ! empty( $data['expires_in'] ) ? absint( $data['expires_in'] ) : 3600,
1685 'created' => time(),
1686 'token_type' => 'Bearer',
1687 );
1688 }
1689
1690 /**
1691 * Get the authentication info for ga4 setup.
1692 *
1693 * @return array
1694 * @throws Exception Throws exception.
1695 */
1696 public function getGa4AuthInfo() {
1697 $client = $this->getGa4Client();
1698 $token_response = $client->getAccessToken();
1699 $properties = array();
1700
1701 /*
1702 * Not connected yet, or no usable token yet.
1703 * Return the connect URL and no properties.
1704 */
1705 if ( ! is_array( $token_response ) || empty( $token_response['access_token'] ) ) {
1706 return array(
1707 'properties' => array(),
1708 'auth_url' => $this->getGa4ConnectUrl(),
1709 );
1710 }
1711
1712 $args = array(
1713 'headers' => array(
1714 'Authorization' => 'Bearer ' . sanitize_text_field( (string) $token_response['access_token'] ),
1715 ),
1716 'timeout' => 20,
1717 );
1718
1719 /*
1720 * Enumerate accounts and their GA4 properties in a single call via the
1721 * GA4 Admin API. accountSummaries returns each account along with its
1722 * propertySummaries, so there is no dependency on the retired
1723 * Universal Analytics Management API (which returns nothing for
1724 * GA4-only accounts).
1725 */
1726 $page_token = '';
1727
1728 do {
1729 $summaries_url = 'https://analyticsadmin.googleapis.com/v1beta/accountSummaries?pageSize=200';
1730
1731 if ( '' !== $page_token ) {
1732 $summaries_url .= '&pageToken=' . rawurlencode( $page_token );
1733 }
1734
1735 $account_response = wp_remote_get( $summaries_url, $args );
1736
1737 if ( is_wp_error( $account_response ) ) {
1738 return array(
1739 'properties' => array(),
1740 'auth_url' => $this->getGa4ConnectUrl(),
1741 'error' => $account_response->get_error_message(),
1742 );
1743 }
1744
1745 $status_code = (int) wp_remote_retrieve_response_code( $account_response );
1746 $account_body = json_decode( wp_remote_retrieve_body( $account_response ), true );
1747
1748 /*
1749 * Surface API failures (expired/revoked token, missing scope, quota)
1750 * instead of returning an empty list, which is indistinguishable from
1751 * "this account has no properties".
1752 */
1753 if ( 200 !== $status_code ) {
1754 $api_message = is_array( $account_body ) && ! empty( $account_body['error']['message'] )
1755 ? (string) $account_body['error']['message']
1756 : __( 'Google Analytics returned an unexpected response. Please sign out and reconnect your account.', 'googleanalytics' );
1757
1758 return array(
1759 'properties' => array(),
1760 'auth_url' => $this->getGa4ConnectUrl(),
1761 'error' => $api_message,
1762 );
1763 }
1764
1765 if ( ! is_array( $account_body ) ) {
1766 break;
1767 }
1768
1769 $account_summaries = ! empty( $account_body['accountSummaries'] ) && is_array( $account_body['accountSummaries'] )
1770 ? $account_body['accountSummaries']
1771 : array();
1772
1773 foreach ( $account_summaries as $account ) {
1774 // Key by the unique account resource name so two accounts that
1775 // share a display name do not overwrite each other.
1776 $account_key = ! empty( $account['account'] ) ? sanitize_text_field( (string) $account['account'] ) : '';
1777
1778 if ( '' === $account_key ) {
1779 continue;
1780 }
1781
1782 $account_label = ! empty( $account['displayName'] )
1783 ? sanitize_text_field( (string) $account['displayName'] )
1784 : $account_key;
1785
1786 $properties_array = array();
1787
1788 if ( ! empty( $account['propertySummaries'] ) && is_array( $account['propertySummaries'] ) ) {
1789 foreach ( $account['propertySummaries'] as $property_summary ) {
1790 if ( empty( $property_summary['property'] ) ) {
1791 continue;
1792 }
1793
1794 $properties_array[] = array(
1795 'name' => sanitize_text_field( (string) $property_summary['property'] ),
1796 'displayName' => ! empty( $property_summary['displayName'] )
1797 ? sanitize_text_field( (string) $property_summary['displayName'] )
1798 : sanitize_text_field( (string) $property_summary['property'] ),
1799 );
1800 }
1801 }
1802
1803 $properties[ $account_key ] = array(
1804 'label' => $account_label,
1805 'properties' => $properties_array,
1806 );
1807 }
1808
1809 $page_token = ! empty( $account_body['nextPageToken'] )
1810 ? sanitize_text_field( (string) $account_body['nextPageToken'] )
1811 : '';
1812 } while ( '' !== $page_token );
1813
1814 return array(
1815 'properties' => $properties,
1816 'auth_url' => $this->getGa4ConnectUrl(),
1817 );
1818 }
1819
1820 /**
1821 * Get the local connect URL that starts the brokered OAuth flow.
1822 *
1823 * @return string
1824 */
1825 private function getGa4ConnectUrl(): string {
1826 return wp_nonce_url(
1827 admin_url( 'admin-post.php?action=sharethis_ga_connect' ),
1828 'sharethis_ga_connect'
1829 );
1830 }
1831 }
1832