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
metasync / includes / class-metasync-analytics-consent-notice.php

class-metasync-analytics-consent-notice.php in Search Atlas SEO – OTTO AI SEO Automation for WordPress 2.7.2, at includes/class-metasync-analytics-consent-notice.php

254 lines 10.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Analytics Consent Notice
4 *
5 * Asks admins on existing installs, who never saw the setup wizard's consent
6 * toggle, whether they want to share usage analytics.
7 *
8 * @package Metasync
9 * @subpackage Metasync/includes
10 */
11
12 if (!defined('ABSPATH')) {
13 exit;
14 }
15
16 /**
17 * Class Metasync_Analytics_Consent_Notice
18 *
19 * Shown on this plugin's own admin pages while analytics consent is off.
20 * "Not now" (or the X) hides it for SNOOZE_DAYS; it comes back after that
21 * for as long as consent stays off.
22 */
23 class Metasync_Analytics_Consent_Notice {
24
25 /**
26 * Singleton instance
27 *
28 * @var Metasync_Analytics_Consent_Notice|null
29 */
30 private static $instance = null;
31
32 /**
33 * Days the notice stays hidden after "Not now".
34 */
35 public const SNOOZE_DAYS = 10;
36
37 /**
38 * Unix time until which the notice is hidden. Site-wide, like the consent itself.
39 */
40 public const SNOOZE_OPTION = 'metasync_analytics_notice_snoozed_until';
41
42 private const NONCE_ACTION = 'metasync_analytics_consent_notice';
43
44 /**
45 * Private constructor for singleton pattern
46 */
47 private function __construct() {
48 add_action('admin_notices', [$this, 'display_notice']);
49 add_action('wp_ajax_metasync_analytics_consent_allow', [$this, 'ajax_allow']);
50 add_action('wp_ajax_metasync_analytics_consent_snooze', [$this, 'ajax_snooze']);
51 add_action('admin_enqueue_scripts', [$this, 'enqueue_admin_scripts']);
52 }
53
54 /**
55 * Get singleton instance
56 *
57 * @return Metasync_Analytics_Consent_Notice
58 */
59 public static function get_instance() {
60 if (self::$instance === null) {
61 self::$instance = new self();
62 }
63 return self::$instance;
64 }
65
66 /**
67 * Whether the notice should show to the current user right now.
68 *
69 * @return bool
70 */
71 public function should_display() {
72 if (!current_user_can('manage_options')) {
73 return false;
74 }
75
76 if (get_option('metasync_analytics_opt_in', 'no') === 'yes') {
77 return false;
78 }
79
80 return time() >= (int) get_option(self::SNOOZE_OPTION, 0);
81 }
82
83 /**
84 * Whether the current admin screen is one of this plugin's own pages.
85 *
86 * The slug is whitelabellable, so match Metasync_Admin::$page_slug and its
87 * `{slug}-...` sub-pages rather than a fixed string.
88 *
89 * @return bool
90 */
91 public function is_plugin_admin_page() {
92 if (empty($_GET['page'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only page check for notice placement
93 return false;
94 }
95
96 $page = sanitize_text_field(wp_unslash($_GET['page'])); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only page check for notice placement
97 $slug = class_exists('Metasync_Admin') && !empty(Metasync_Admin::$page_slug)
98 ? Metasync_Admin::$page_slug
99 : 'searchatlas';
100
101 return $page === $slug || strpos($page, $slug . '-') === 0;
102 }
103
104 /**
105 * admin_notices callback.
106 */
107 public function display_notice() {
108 if (!$this->is_plugin_admin_page() || !$this->should_display()) {
109 return;
110 }
111
112 $plugin_name = class_exists('Metasync') ? Metasync::get_effective_plugin_name() : 'Search Atlas';
113 $slug = class_exists('Metasync_Admin') && !empty(Metasync_Admin::$page_slug)
114 ? Metasync_Admin::$page_slug
115 : 'searchatlas';
116 $settings_url = admin_url('admin.php?page=' . $slug);
117 ?>
118 <div id="metasync-analytics-consent-notice" class="notice notice-info is-dismissible" style="padding: 12px 16px; border-left-color: #2271b1;">
119 <div style="display: flex; align-items: flex-start; gap: 14px;">
120 <span class="dashicons dashicons-chart-bar" style="font-size: 28px; width: 28px; height: 28px; color: #2271b1;" aria-hidden="true"></span>
121 <div style="flex: 1;">
122 <p style="margin: 0 0 6px 0; font-size: 14px; font-weight: 600;">
123 <?php
124 /* translators: %s: Plugin name. */
125 echo esc_html(sprintf(__('Help improve %s', 'metasync'), $plugin_name));
126 ?>
127 </p>
128 <p class="metasync-analytics-consent-message" style="margin: 0 0 6px 0; font-size: 13px;">
129 <?php
130 /* translators: %s: Plugin name. */
131 echo esc_html(sprintf(__('Allow %s to collect limited usage analytics. This helps us understand which features are used, improve the product, and identify problems.', 'metasync'), $plugin_name));
132 ?>
133 </p>
134 <p class="metasync-analytics-consent-details" style="margin: 0 0 10px 0; font-size: 12px; color: #646970;">
135 <?php esc_html_e('We collect limited technical information such as your site URL, plugin version, WordPress and PHP versions, user role, and feature usage. We do not collect passwords, API keys, customer content, names, or email addresses.', 'metasync'); ?>
136 <a href="<?php echo esc_url($settings_url); ?>"><?php esc_html_e('You can change this anytime in Settings.', 'metasync'); ?></a>
137 </p>
138 <?php // no-loading: the dashboard script otherwise disables plugin buttons on click, and jQuery skips delegated clicks on disabled buttons. ?>
139 <p class="metasync-analytics-consent-actions" style="margin: 0; display: flex; gap: 10px; flex-wrap: wrap;">
140 <button type="button" class="button button-primary no-loading metasync-analytics-consent-allow">
141 <?php esc_html_e('Allow', 'metasync'); ?>
142 </button>
143 <button type="button" class="button no-loading metasync-analytics-consent-snooze">
144 <?php esc_html_e('Not now', 'metasync'); ?>
145 </button>
146 </p>
147 </div>
148 </div>
149 </div>
150 <?php
151 }
152
153 /**
154 * Enqueue the button handlers.
155 *
156 * @param string $hook Current admin page hook.
157 */
158 public function enqueue_admin_scripts($hook) {
159 if (!$this->is_plugin_admin_page() || !$this->should_display()) {
160 return;
161 }
162
163 wp_add_inline_script('jquery', $this->get_notice_script(), 'after');
164 }
165
166 /**
167 * Inline JS for the notice buttons.
168 *
169 * @return string JavaScript code.
170 */
171 private function get_notice_script() {
172 $nonce = wp_create_nonce(self::NONCE_ACTION);
173 $thanks = __('Thanks! Usage analytics are now on. You can turn them off anytime in Settings.', 'metasync');
174 $failed = __('Could not save your choice. Please try again, or use the toggle in Settings.', 'metasync');
175
176 return "
177 jQuery(document).ready(function($) {
178 var notice = $('#metasync-analytics-consent-notice');
179 var nonce = '" . esc_js($nonce) . "';
180
181 notice.on('click', '.metasync-analytics-consent-allow', function() {
182 var button = $(this).prop('disabled', true);
183 $.post(ajaxurl, { action: 'metasync_analytics_consent_allow', nonce: nonce })
184 .done(function(response) {
185 if (response && response.success) {
186 notice.removeClass('notice-info').addClass('notice-success');
187 notice.find('.metasync-analytics-consent-message').text('" . esc_js($thanks) . "');
188 notice.find('.metasync-analytics-consent-actions, .metasync-analytics-consent-details').remove();
189 } else {
190 button.prop('disabled', false);
191 notice.find('.metasync-analytics-consent-message').text('" . esc_js($failed) . "');
192 }
193 })
194 .fail(function() {
195 button.prop('disabled', false);
196 notice.find('.metasync-analytics-consent-message').text('" . esc_js($failed) . "');
197 });
198 });
199
200 // 'Not now' and the X both snooze the notice.
201 notice.on('click', '.metasync-analytics-consent-snooze, .notice-dismiss', function() {
202 if (notice.hasClass('notice-success')) {
203 return;
204 }
205 $.post(ajaxurl, { action: 'metasync_analytics_consent_snooze', nonce: nonce });
206 notice.fadeOut();
207 });
208 });
209 ";
210 }
211
212 /**
213 * AJAX handler: turn analytics on.
214 *
215 * Saves the same two options as the setup wizard and the settings toggle,
216 * so the choice counts as explicit consent.
217 */
218 public function ajax_allow() {
219 check_ajax_referer(self::NONCE_ACTION, 'nonce');
220
221 if (!current_user_can('manage_options')) {
222 wp_send_json_error(['message' => 'Insufficient permissions.']);
223 // Unreachable in WordPress; keeps the handler safe under the
224 // non-terminating stubs the unit tests run with.
225 // @phpstan-ignore-next-line deadCode.unreachable
226 return;
227 }
228
229 update_option('metasync_analytics_opt_in', 'yes');
230 update_option('metasync_analytics_consent_explicit', 'yes');
231 delete_option(self::SNOOZE_OPTION);
232
233 wp_send_json_success(['message' => 'Analytics enabled']);
234 }
235
236 /**
237 * AJAX handler: hide the notice for SNOOZE_DAYS. Consent stays off.
238 */
239 public function ajax_snooze() {
240 check_ajax_referer(self::NONCE_ACTION, 'nonce');
241
242 if (!current_user_can('manage_options')) {
243 wp_send_json_error(['message' => 'Insufficient permissions.']);
244 // Same as above: defensive under test stubs, unreachable in WordPress.
245 // @phpstan-ignore-next-line deadCode.unreachable
246 return;
247 }
248
249 update_option(self::SNOOZE_OPTION, time() + (self::SNOOZE_DAYS * DAY_IN_SECONDS), false);
250
251 wp_send_json_success(['message' => 'Notice snoozed']);
252 }
253 }
254