PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.7.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.7.0
2.7.0 2.6.0 2.5.0 2.4.0 2.3.0 2.2.0 2.1.1 2.1.0 2.0.2 2.0.1 2.0.0 1.32.0 1.31.0 1.30.0 1.29.0 1.28.0 1.27.0 1.26.0 1.25.0 trunk 1.0.0 1.0.1 1.0.2 1.1.0 1.10.0 All 48 releases
thinkrank / includes / admin / class-seo-notice.php

class-seo-notice.php in ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO 2.7.0, at includes/admin/class-seo-notice.php

252 lines 9.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * SEO Plugin Notice Manager
4 *
5 * Handles admin notices for SEO plugin conflicts
6 *
7 * @package ThinkRank\Admin
8 * @since 1.0.0
9 */
10
11 declare(strict_types=1);
12
13 namespace ThinkRank\Admin;
14
15 use ThinkRank\Core\SEO_Plugin_Detector;
16
17 // Prevent direct access
18 if (!defined('ABSPATH')) {
19 exit;
20 }
21
22 /**
23 * SEO Notice Manager Class
24 *
25 * Single Responsibility: Display admin notices for SEO plugin conflicts
26 *
27 * @since 1.0.0
28 */
29 class SEO_Notice {
30
31 /**
32 * Initialize notice manager
33 *
34 * @return void
35 */
36 public function init(): void {
37 add_action('admin_notices', [$this, 'display_seo_plugin_notice']);
38 add_action('wp_ajax_thinkrank_dismiss_seo_notice', [$this, 'ajax_dismiss_notice']);
39 add_action('wp_ajax_thinkrank_deactivate_seo_plugin', [$this, 'ajax_deactivate_plugin']);
40 add_action('admin_enqueue_scripts', [$this, 'enqueue_notice_scripts']);
41 }
42
43 /**
44 * Display SEO plugin conflict notice
45 *
46 * @return void
47 */
48 public function display_seo_plugin_notice(): void {
49 if (!SEO_Plugin_Detector::should_show_notice()) {
50 return;
51 }
52
53 $detected = SEO_Plugin_Detector::detect_plugins();
54 $message = SEO_Plugin_Detector::get_notice_message();
55 if (empty($detected) || empty($message)) {
56 return;
57 }
58
59 $names = array_column($detected, 'name');
60 $heading = count($names) === 1
61 /* translators: %s: detected SEO plugin name */
62 ? sprintf(__('%s is running alongside ThinkRank', 'thinkrank'), $names[0])
63 : __('Other SEO plugins are running alongside ThinkRank', 'thinkrank');
64
65 $can_deactivate = current_user_can('deactivate_plugins');
66 $migrate_url = $this->get_migration_url();
67 ?>
68 <div class="notice notice-warning is-dismissible thinkrank-notice thinkrank-seo-notice">
69 <div class="thinkrank-notice__inner">
70 <div class="thinkrank-notice__body">
71 <p class="thinkrank-notice__title"><?php echo esc_html($heading); ?></p>
72 <?php if (count($names) > 1) : ?>
73 <p class="thinkrank-notice__badges">
74 <?php foreach ($names as $name) : ?>
75 <span class="thinkrank-notice__badge"><?php echo esc_html($name); ?></span>
76 <?php endforeach; ?>
77 </p>
78 <?php endif; ?>
79 <p class="thinkrank-notice__text"><?php echo esc_html($message); ?></p>
80 <?php if ($migrate_url) : ?>
81 <p class="thinkrank-notice__hint">
82 <?php echo self::get_shield_icon(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- static, trusted SVG markup ?>
83 <span><?php esc_html_e('Your existing SEO data is safe — migrate titles, descriptions, and other metadata into ThinkRank before deactivating.', 'thinkrank'); ?></span>
84 </p>
85 <?php endif; ?>
86 <p class="thinkrank-notice__actions">
87 <?php if ($can_deactivate) : ?>
88 <?php foreach ($detected as $slug => $plugin) : ?>
89 <button type="button" class="button button-primary thinkrank-deactivate-seo-plugin" data-plugin="<?php echo esc_attr($slug); ?>">
90 <?php
91 /* translators: %s: detected SEO plugin name */
92 echo esc_html(sprintf(__('Deactivate %s', 'thinkrank'), $plugin['name']));
93 ?>
94 </button>
95 <?php endforeach; ?>
96 <?php endif; ?>
97 <?php if ($migrate_url) : ?>
98 <a href="<?php echo esc_url($migrate_url); ?>" class="button button-secondary">
99 <?php esc_html_e('Migrate SEO data first', 'thinkrank'); ?>
100 </a>
101 <?php endif; ?>
102 <a href="#" class="thinkrank-notice__dismiss thinkrank-dismiss-seo-notice"><?php esc_html_e('Dismiss', 'thinkrank'); ?></a>
103 </p>
104 </div>
105 </div>
106 </div>
107 <?php
108 }
109
110 /**
111 * Small shield glyph for the data-safety reassurance chip.
112 *
113 * @return string Inline SVG markup
114 */
115 private static function get_shield_icon(): string {
116 return '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false">'
117 . '<path d="M12 2 4 5v6c0 5 3.4 8.4 8 10 4.6-1.6 8-5 8-10V5l-8-3zm-1 13-3-3 1.4-1.4 1.6 1.6 3.6-3.6L16 10l-5 5z" fill="currentColor"/>'
118 . '</svg>';
119 }
120
121 /**
122 * URL of the Setup Wizard migration step, when migration is still possible.
123 *
124 * The wizard redirects to the dashboard once completed, so the link is
125 * only offered while the wizard is still accessible and at least one
126 * detected plugin has a ThinkRank importer.
127 *
128 * @return string Migration URL or '' when unavailable
129 */
130 private function get_migration_url(): string {
131 if (!SEO_Plugin_Detector::has_importable_plugin()) {
132 return '';
133 }
134
135 if (get_option(Setup_Wizard::OPT_COMPLETED, false)) {
136 return '';
137 }
138
139 return admin_url('admin.php?page=' . Setup_Wizard::PAGE_SLUG);
140 }
141
142 /**
143 * Enqueue notice scripts
144 *
145 * @param string $hook Current admin page hook
146 * @return void
147 */
148 public function enqueue_notice_scripts(string $hook): void {
149 if (!SEO_Plugin_Detector::should_show_notice()) {
150 return;
151 }
152
153 wp_enqueue_style(
154 'thinkrank-admin-notices',
155 THINKRANK_PLUGIN_URL . 'static/css/admin-notices.css',
156 [],
157 THINKRANK_VERSION
158 );
159
160 wp_add_inline_script('jquery', '
161 jQuery(document).ready(function($) {
162 function thinkrankDismissSeoNotice() {
163 $.post(ajaxurl, {
164 action: "thinkrank_dismiss_seo_notice",
165 nonce: "' . wp_create_nonce('thinkrank_seo_notice_nonce') . '"
166 }, function(response) {
167 if (response.success) {
168 $(".thinkrank-seo-notice").fadeOut();
169 }
170 });
171 }
172
173 $(".thinkrank-dismiss-seo-notice").on("click", function(e) {
174 e.preventDefault();
175 thinkrankDismissSeoNotice();
176 });
177
178 // Persist dismissal from the core notice X button too
179 // (delegated — core injects the button after DOM ready)
180 $(document).on("click", ".thinkrank-seo-notice .notice-dismiss", thinkrankDismissSeoNotice);
181
182 $(".thinkrank-deactivate-seo-plugin").on("click", function(e) {
183 e.preventDefault();
184 var $button = $(this);
185 $button.prop("disabled", true);
186
187 $.post(ajaxurl, {
188 action: "thinkrank_deactivate_seo_plugin",
189 plugin: $button.data("plugin"),
190 nonce: "' . wp_create_nonce('thinkrank_seo_notice_nonce') . '"
191 }, function(response) {
192 if (response.success) {
193 window.location.reload();
194 } else {
195 $button.prop("disabled", false);
196 }
197 });
198 });
199 });
200 ');
201 }
202
203 /**
204 * AJAX handler to dismiss SEO notice
205 *
206 * @return void
207 */
208 public function ajax_dismiss_notice(): void {
209 $nonce = sanitize_text_field(wp_unslash($_POST['nonce'] ?? ''));
210 if (!wp_verify_nonce($nonce, 'thinkrank_seo_notice_nonce')) {
211 wp_send_json_error('Security check failed');
212 }
213
214 // The notice is an admin-facing plugin-conflict warning — only users who
215 // can manage the plugin should be able to dismiss it.
216 if (!current_user_can('edit_posts')) {
217 wp_send_json_error('Insufficient permissions');
218 }
219
220 SEO_Plugin_Detector::dismiss_notice();
221 wp_send_json_success();
222 }
223
224 /**
225 * AJAX handler to deactivate a conflicting SEO plugin.
226 *
227 * Only accepts a known-plugin slug; the plugin file(s) — including
228 * premium companions — are resolved server-side by the detector.
229 *
230 * @return void
231 */
232 public function ajax_deactivate_plugin(): void {
233 $nonce = sanitize_text_field(wp_unslash($_POST['nonce'] ?? ''));
234 if (!wp_verify_nonce($nonce, 'thinkrank_seo_notice_nonce')) {
235 wp_send_json_error('Security check failed');
236 }
237
238 if (!current_user_can('deactivate_plugins')) {
239 wp_send_json_error('Insufficient permissions');
240 }
241
242 $slug = sanitize_key(wp_unslash($_POST['plugin'] ?? ''));
243 $files = SEO_Plugin_Detector::get_deactivatable_files($slug);
244 if (empty($files)) {
245 wp_send_json_error('Unknown plugin');
246 }
247
248 deactivate_plugins($files);
249 wp_send_json_success();
250 }
251 }
252