PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.6.21
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.6.21
2.6.26 2.6.25 2.6.24 2.6.23 2.6.22 2.6.21 2.6.20 2.6.19 2.6.18 2.6.17 2.6.16 2.6.15 2.6.14 2.6.13 2.6.12 2.6.11 2.6.10 2.6.9 2.6.8 2.6.7 2.6.6 2.6.5 2.6.4 2.6.3 2.5.23 All 138 releases
metasync / includes / class-metasync-ga4.php

class-metasync-ga4.php in Search Atlas SEO – OTTO AI SEO Automation for WordPress 2.6.21, at includes/class-metasync-ga4.php

300 lines 9.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * GA4 Analytics Integration for MetaSync Plugin
4 *
5 * Client-side (gtag.js) for admin page views and 1-click activation.
6 * Server-side (Measurement Protocol) for Content Genius and OTTO events.
7 *
8 * @package Search Atlas SEO
9 * @copyright Copyright (C) 2021-2026, Search Atlas Group - support@searchatlas.com
10 * @link https://searchatlas.com/
11 * @since 2.6.0
12 */
13
14 // If this file is called directly, abort.
15 if (!defined('WPINC')) {
16 die;
17 }
18
19 class Metasync_GA4 {
20
21 private const OPT_IN_OPTION = 'metasync_analytics_opt_in';
22
23 private static $instance = null;
24
25 public static function get_instance() {
26 if (self::$instance === null) {
27 self::$instance = new self();
28 }
29 return self::$instance;
30 }
31
32 private function __construct() {
33 if (!$this->is_configured()) {
34 return;
35 }
36
37 if (get_option(self::OPT_IN_OPTION) === false) {
38 update_option(self::OPT_IN_OPTION, 'yes');
39 }
40
41 add_action('admin_enqueue_scripts', array($this, 'enqueue_ga4_script'));
42 add_action('admin_head', array($this, 'track_admin_page_view'));
43 }
44
45 private function get_measurement_id() {
46 return defined('METASYNC_GA4_MEASUREMENT_ID') && !empty(METASYNC_GA4_MEASUREMENT_ID)
47 ? METASYNC_GA4_MEASUREMENT_ID
48 : null;
49 }
50
51 private function is_configured() {
52 return $this->get_measurement_id() !== null;
53 }
54
55 public function is_opted_in() {
56 return get_option(self::OPT_IN_OPTION, 'yes') === 'yes';
57 }
58
59 private function get_anonymized_user_id() {
60 $current_user = wp_get_current_user();
61 if (!$current_user || !$current_user->ID) {
62 return 'guest';
63 }
64
65 $site_identifier = get_option('siteurl');
66 return 'user_' . wp_hash($current_user->ID . '|' . $site_identifier);
67 }
68
69 private function get_plugin_version() {
70 return defined('METASYNC_VERSION') ? METASYNC_VERSION : 'unknown';
71 }
72
73 private function is_plugin_admin_page() {
74 if (!isset($_GET['page'])) {
75 return false;
76 }
77
78 $page = sanitize_text_field($_GET['page']);
79
80 $plugin_slug = 'searchatlas';
81 if (class_exists('Metasync_Admin') && isset(Metasync_Admin::$page_slug)) {
82 $plugin_slug = Metasync_Admin::$page_slug;
83 }
84
85 return $page === $plugin_slug || strpos($page, $plugin_slug . '-') === 0;
86 }
87
88 private function get_admin_screen_name() {
89 if (!isset($_GET['page'])) {
90 return 'Unknown';
91 }
92
93 $page = sanitize_text_field($_GET['page']);
94
95 $plugin_slug = 'searchatlas';
96 if (class_exists('Metasync_Admin') && isset(Metasync_Admin::$page_slug)) {
97 $plugin_slug = Metasync_Admin::$page_slug;
98 }
99
100 $screen_map = array(
101 $plugin_slug => 'Settings',
102 $plugin_slug . '-dashboard' => 'Dashboard',
103 $plugin_slug . '-compatibility' => 'Compatibility',
104 $plugin_slug . '-sync-log' => 'Changes Log',
105 $plugin_slug . '-seo-controls' => 'Indexation Control',
106 $plugin_slug . '-redirections' => 'Redirections',
107 $plugin_slug . '-xml-sitemap' => 'XML Sitemap',
108 $plugin_slug . '-robots-txt' => 'Robots.txt',
109 $plugin_slug . '-404-monitor' => '404 Monitor',
110 'ottodebug' => 'OTTO Debug',
111 );
112
113 if ($page === $plugin_slug && isset($_GET['tab'])) {
114 $tab = sanitize_text_field($_GET['tab']);
115 return 'Settings - ' . ucfirst(str_replace('_', ' ', $tab));
116 }
117
118 return $screen_map[$page] ?? 'Unknown Page';
119 }
120
121 public function enqueue_ga4_script() {
122 if (!$this->is_configured() || !$this->is_plugin_admin_page() || !$this->is_opted_in()) {
123 return;
124 }
125
126 $measurement_id = esc_js($this->get_measurement_id());
127 $user_id = esc_js($this->get_anonymized_user_id());
128 $plugin_version = esc_js($this->get_plugin_version());
129 $wp_version = esc_js(get_bloginfo('version'));
130 $php_version = esc_js(PHP_VERSION);
131
132 $inline_script = "
133 var gtagScript = document.createElement('script');
134 gtagScript.async = true;
135 gtagScript.src = 'https://www.googletagmanager.com/gtag/js?id={$measurement_id}';
136 document.head.appendChild(gtagScript);
137
138 window.dataLayer = window.dataLayer || [];
139 function gtag(){dataLayer.push(arguments);}
140 gtag('js', new Date());
141 gtag('config', '{$measurement_id}', {
142 'user_id': '{$user_id}',
143 'send_page_view': false
144 });
145 gtag('set', {
146 'plugin_version': '{$plugin_version}',
147 'wp_version': '{$wp_version}',
148 'php_version': '{$php_version}'
149 });
150
151 window.metasyncGA4Track = function(eventName, params) {
152 if (typeof gtag !== 'undefined') {
153 gtag('event', eventName, params || {});
154 }
155 };";
156
157 wp_register_script('metasync-ga4-init', '', array(), $this->get_plugin_version(), false);
158 wp_enqueue_script('metasync-ga4-init');
159 wp_add_inline_script('metasync-ga4-init', $inline_script, 'before');
160 }
161
162 public function track_admin_page_view() {
163 if (!$this->is_configured() || !$this->is_plugin_admin_page() || !$this->is_opted_in()) {
164 return;
165 }
166
167 $screen_name = $this->get_admin_screen_name();
168 $params = wp_json_encode(
169 array('screen_name' => $screen_name),
170 JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT
171 );
172
173 echo "<script type='text/javascript'>
174 if (typeof gtag !== 'undefined') {
175 gtag('event', 'plugin_admin_accessed', " . $params . ");
176 }
177 </script>";
178 }
179
180 /**
181 * Track Content Genius article creation/update (server-side)
182 * @param int $post_id
183 * @param string $action 'created' or 'updated'
184 */
185 public function track_content_genius_event($post_id, $action) {
186 if (!$this->is_configured() || !$this->is_opted_in()) {
187 return;
188 }
189
190 $post = get_post($post_id);
191 if (!$post) {
192 return;
193 }
194
195 $this->send_mp_event('content_genius_article_' . strtolower($action), array(
196 'content_type' => $post->post_type,
197 'action' => ucfirst($action),
198 ));
199 }
200
201 /**
202 * Track OTTO page optimization (server-side)
203 * @param array $data Notification data from OTTO
204 */
205 public function track_otto_optimization($data) {
206 if (!$this->is_configured() || !$this->is_opted_in()) {
207 return;
208 }
209
210 $urls = isset($data['urls']) ? $data['urls'] : array();
211
212 foreach ($urls as $url) {
213 $post_id = url_to_postid((isset($data['domain']) ? $data['domain'] : '') . $url);
214 $content_type = 'unknown';
215
216 if ($post_id > 0) {
217 $post = get_post($post_id);
218 if ($post) {
219 $content_type = $post->post_type;
220 }
221 }
222
223 $this->send_mp_event('otto_page_optimized', array(
224 'content_type' => $content_type,
225 'url' => $url,
226 ));
227 }
228 }
229
230 /**
231 * Track 1-Click Activation (server-side)
232 * @param string $auth_method
233 * @param bool $is_reconnection
234 */
235 public function track_one_click_activation($auth_method = 'searchatlas_connect', $is_reconnection = false) {
236 if (!$this->is_configured() || !$this->is_opted_in()) {
237 return;
238 }
239
240 $this->send_mp_event('one_click_activation', array(
241 'auth_method' => $auth_method,
242 'is_reconnection' => $is_reconnection,
243 ));
244 }
245
246 /**
247 * Get GA4 Measurement Protocol API secret
248 * @return string|null
249 */
250 private function get_api_secret() {
251 return defined('METASYNC_GA4_API_SECRET') && !empty(METASYNC_GA4_API_SECRET)
252 ? METASYNC_GA4_API_SECRET
253 : null;
254 }
255
256 /**
257 * Send event via GA4 Measurement Protocol (server-side)
258 * Requires METASYNC_GA4_API_SECRET to be set; silently skips if not configured.
259 * @param string $event_name
260 * @param array $params
261 */
262 private function send_mp_event($event_name, $params = array()) {
263 $measurement_id = $this->get_measurement_id();
264 $api_secret = $this->get_api_secret();
265
266 if (!$measurement_id || !$api_secret) {
267 return;
268 }
269
270 $params['plugin_version'] = $this->get_plugin_version();
271 $params['wp_version'] = get_bloginfo('version');
272 $params['php_version'] = PHP_VERSION;
273
274 $body = wp_json_encode(array(
275 'client_id' => $this->get_anonymized_user_id(),
276 'events' => array(
277 array(
278 'name' => $event_name,
279 'params' => $params,
280 ),
281 ),
282 ));
283
284 $url = add_query_arg(
285 array(
286 'measurement_id' => $measurement_id,
287 'api_secret' => $api_secret,
288 ),
289 'https://www.google-analytics.com/mp/collect'
290 );
291
292 wp_remote_post($url, array(
293 'body' => $body,
294 'headers' => array('Content-Type' => 'application/json'),
295 'timeout' => 5,
296 'blocking' => false,
297 ));
298 }
299 }
300