PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.7.2
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.7.2
2.7.1 2.7.2 2.7.0 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 All 141 releases
← All changes | includes/class-metasync-ga4.php +33 -20 2.6.6 → 2.7.2 View file →
@@ -33,12 +33,8 @@
33 33 if (!$this->is_configured()) {
34 34 return;
35 35 }
36 36
37 - if (get_option(self::OPT_IN_OPTION) === false) {
38 - update_option(self::OPT_IN_OPTION, 'yes');
39 - }
40 -
41 37 add_action('admin_enqueue_scripts', array($this, 'enqueue_ga4_script'));
42 38 add_action('admin_head', array($this, 'track_admin_page_view'));
43 39 }
44 40
@@ -52,9 +48,9 @@
52 48 return $this->get_measurement_id() !== null;
53 49 }
54 50
55 51 public function is_opted_in() {
56 - return get_option(self::OPT_IN_OPTION, 'yes') === 'yes';
52 + return get_option(self::OPT_IN_OPTION, 'no') === 'yes';
57 53 }
58 54
59 55 private function get_anonymized_user_id() {
60 56 $current_user = wp_get_current_user();
@@ -70,13 +66,13 @@
70 66 return defined('METASYNC_VERSION') ? METASYNC_VERSION : 'unknown';
71 67 }
72 68
73 69 private function is_plugin_admin_page() {
74 - if (!isset($_GET['page'])) {
70 + if (!isset($_GET['page'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only page and tab check for GA4 assets
75 71 return false;
76 72 }
77 73
78 - $page = sanitize_text_field($_GET['page']);
74 + $page = sanitize_text_field(wp_unslash($_GET['page'])); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only page and tab check for GA4 assets
79 75
80 76 $plugin_slug = 'searchatlas';
81 77 if (class_exists('Metasync_Admin') && isset(Metasync_Admin::$page_slug)) {
82 78 $plugin_slug = Metasync_Admin::$page_slug;
@@ -85,13 +81,13 @@
85 81 return $page === $plugin_slug || strpos($page, $plugin_slug . '-') === 0;
86 82 }
87 83
88 84 private function get_admin_screen_name() {
89 - if (!isset($_GET['page'])) {
85 + if (!isset($_GET['page'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only page and tab check for GA4 assets
90 86 return 'Unknown';
91 87 }
92 88
93 - $page = sanitize_text_field($_GET['page']);
89 + $page = sanitize_text_field(wp_unslash($_GET['page'])); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only page and tab check for GA4 assets
94 90
95 91 $plugin_slug = 'searchatlas';
96 92 if (class_exists('Metasync_Admin') && isset(Metasync_Admin::$page_slug)) {
97 93 $plugin_slug = Metasync_Admin::$page_slug;
@@ -109,10 +105,10 @@
109 105 $plugin_slug . '-404-monitor' => '404 Monitor',
110 106 'ottodebug' => 'OTTO Debug',
111 107 );
112 108
113 - if ($page === $plugin_slug && isset($_GET['tab'])) {
114 - $tab = sanitize_text_field($_GET['tab']);
109 + if ($page === $plugin_slug && isset($_GET['tab'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only page and tab check for GA4 assets
110 + $tab = sanitize_text_field(wp_unslash($_GET['tab'])); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only page and tab check for GA4 assets
115 111 return 'Settings - ' . ucfirst(str_replace('_', ' ', $tab));
116 112 }
117 113
118 114 return $screen_map[$page] ?? 'Unknown Page';
@@ -169,13 +165,10 @@
169 165 array('screen_name' => $screen_name),
170 166 JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT
171 167 );
172 168
173 - echo "<script type='text/javascript'>
174 - if (typeof gtag !== 'undefined') {
175 - gtag('event', 'plugin_admin_accessed', " . $params . ");
176 - }
177 - </script>";
169 + $inline_tracking = "<script type='text/javascript'>\n if (typeof gtag !== 'undefined') {\n gtag('event', 'plugin_admin_accessed', " . $params . ");\n }\n </script>";
170 + echo $inline_tracking; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- $params is wp_json_encode() output with JSON_HEX_TAG/AMP/APOS/QUOT, safe inside an inline <script>
178 171 }
179 172
180 173 /**
181 174 * Track Content Genius article creation/update (server-side)
@@ -191,12 +184,32 @@
191 184 if (!$post) {
192 185 return;
193 186 }
194 187
195 - $this->send_mp_event('content_genius_article_' . strtolower($action), array(
196 - 'content_type' => $post->post_type,
197 - 'action' => ucfirst($action),
198 - ));
188 + // Bulk syncs fire this once per article; roll up to at most one event
189 + // per action per hour with accumulated counts so a sync of N articles
190 + // stays a single send (same shape as the PostHog rollup).
191 + $action = strtolower($action);
192 + $key = 'metasync_ga4_cg_' . $action;
193 + $state = get_option($key, array());
194 + if (!is_array($state)) {
195 + $state = array();
196 + }
197 + $counts = isset($state['counts']) && is_array($state['counts']) ? $state['counts'] : array();
198 + $sent_at = (int) ($state['sent_at'] ?? 0);
199 + $type = (string) $post->post_type;
200 + $counts[$type] = ($counts[$type] ?? 0) + 1;
201 +
202 + if (time() - $sent_at >= HOUR_IN_SECONDS) {
203 + $this->send_mp_event('content_genius_article_' . $action, array(
204 + 'content_type' => $type,
205 + 'action' => ucfirst($action),
206 + 'count' => array_sum($counts),
207 + ));
208 + update_option($key, array('counts' => array(), 'sent_at' => time()), false);
209 + } else {
210 + update_option($key, array('counts' => $counts, 'sent_at' => $sent_at), false);
211 + }
199 212 }
200 213
201 214 /**
202 215 * Track OTTO page optimization (server-side)