PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / trunk
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO vtrunk
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 1.11.0 All 47 releases
thinkrank / includes / core / class-seo-plugin-detector.php

class-seo-plugin-detector.php in ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO trunk, at includes/core/class-seo-plugin-detector.php

269 lines 7.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * SEO Plugin Detector
4 *
5 * Simple detection of popular SEO plugins
6 *
7 * @package ThinkRank\Core
8 * @since 1.0.0
9 */
10
11 declare(strict_types=1);
12
13 namespace ThinkRank\Core;
14
15 // Prevent direct access
16 if (!defined('ABSPATH')) {
17 exit;
18 }
19
20 /**
21 * SEO Plugin Detector Class
22 *
23 * Single Responsibility: Detect popular SEO plugins
24 *
25 * @since 1.0.0
26 */
27 class SEO_Plugin_Detector {
28
29 /**
30 * Known SEO plugins
31 *
32 * @var array
33 */
34 private const KNOWN_PLUGINS = [
35 'yoast' => [
36 'name' => 'Yoast SEO',
37 'class' => 'WPSEO',
38 'function' => 'wpseo_init',
39 'file' => 'wordpress-seo/wp-seo.php',
40 'extra_files' => ['wordpress-seo-premium/wp-seo-premium.php'],
41 'importable' => true,
42 ],
43 'rankmath' => [
44 'name' => 'Rank Math',
45 'class' => 'RankMath',
46 'function' => 'rank_math',
47 'file' => 'seo-by-rank-math/rank-math.php',
48 'extra_files' => ['seo-by-rank-math-pro/rank-math-pro.php'],
49 'importable' => true,
50 ],
51 'seopress' => [
52 'name' => 'SEOPress',
53 'class' => 'SEOPress',
54 'function' => 'seopress_init',
55 'file' => 'wp-seopress/seopress.php',
56 'extra_files' => ['wp-seopress-pro/seopress-pro.php'],
57 'importable' => true,
58 ],
59 'aioseo' => [
60 'name' => 'All in One SEO',
61 'class' => 'AIOSEO',
62 'function' => 'aioseo',
63 'file' => 'all-in-one-seo-pack/all_in_one_seo_pack.php',
64 'extra_files' => ['all-in-one-seo-pack-pro/all_in_one_seo_pack.php'],
65 'importable' => true,
66 ],
67 'theseoframework' => [
68 'name' => 'The SEO Framework',
69 'class' => 'The_SEO_Framework\\Load',
70 'function' => 'the_seo_framework',
71 'file' => 'autodescription/autodescription.php',
72 'extra_files' => [],
73 'importable' => false,
74 ],
75 ];
76
77 /**
78 * Detected plugins cache
79 *
80 * @var array|null
81 */
82 private static ?array $detected_plugins = null;
83
84 /**
85 * Detect active SEO plugins
86 *
87 * @return array Detected plugins
88 */
89 public static function detect_plugins(): array {
90 if (self::$detected_plugins !== null) {
91 return self::$detected_plugins;
92 }
93
94 self::$detected_plugins = [];
95
96 foreach (self::KNOWN_PLUGINS as $slug => $plugin) {
97 if (self::is_plugin_active($plugin)) {
98 self::$detected_plugins[$slug] = $plugin;
99 }
100 }
101
102 return self::$detected_plugins;
103 }
104
105 /**
106 * Check if any SEO plugins are active
107 *
108 * @return bool True if SEO plugins detected
109 */
110 public static function has_seo_plugins(): bool {
111 return !empty(self::detect_plugins());
112 }
113
114 /**
115 * Get detected plugin names
116 *
117 * @return array Plugin names
118 */
119 public static function get_plugin_names(): array {
120 $detected = self::detect_plugins();
121 return array_column($detected, 'name');
122 }
123
124 /**
125 * Check if a specific plugin is active
126 *
127 * @param string $slug Plugin slug
128 * @return bool True if plugin is active
129 */
130 public static function is_plugin_detected(string $slug): bool {
131 $detected = self::detect_plugins();
132 return isset($detected[$slug]);
133 }
134
135 /**
136 * Check if a plugin is active
137 *
138 * @param array $plugin Plugin configuration
139 * @return bool True if plugin is active
140 */
141 private static function is_plugin_active(array $plugin): bool {
142 // Check by class
143 if (!empty($plugin['class']) && class_exists($plugin['class'])) {
144 return true;
145 }
146
147 // Check by function
148 if (!empty($plugin['function']) && function_exists($plugin['function'])) {
149 return true;
150 }
151
152 // Check by plugin file
153 if (!empty($plugin['file']) && function_exists('is_plugin_active') && is_plugin_active($plugin['file'])) {
154 return true;
155 }
156
157 return false;
158 }
159
160 /**
161 * Get admin notice message
162 *
163 * @return string Notice message
164 */
165 public static function get_notice_message(): string {
166 if (!self::has_seo_plugins()) {
167 return '';
168 }
169
170 return __('Running more than one SEO plugin can create conflicts — duplicate meta tags, sitemaps, and schema markup that may hurt your search rankings. To avoid any SEO-related conflicts, we recommend deactivating third-party SEO plugins.', 'thinkrank');
171 }
172
173 /**
174 * Whether any detected plugin's SEO data can be imported into ThinkRank.
175 *
176 * @return bool True if at least one detected plugin has an importer
177 */
178 public static function has_importable_plugin(): bool {
179 foreach (self::detect_plugins() as $plugin) {
180 if (!empty($plugin['importable'])) {
181 return true;
182 }
183 }
184
185 return false;
186 }
187
188 /**
189 * Get the human-readable name for a known plugin slug.
190 *
191 * Falls back to the slug itself when unknown, so callers always have a
192 * displayable label.
193 *
194 * @param string $slug Plugin slug from KNOWN_PLUGINS
195 * @return string Plugin name (or the slug if unknown)
196 */
197 public static function get_plugin_name(string $slug): string {
198 return self::KNOWN_PLUGINS[$slug]['name'] ?? $slug;
199 }
200
201 /**
202 * Get the active plugin files to deactivate for a detected plugin.
203 *
204 * Resolves the plugin file(s) — including premium companions such as
205 * Rank Math Pro — server-side from the known-plugins map, so callers
206 * never pass arbitrary plugin paths to deactivate_plugins().
207 *
208 * @param string $slug Plugin slug from KNOWN_PLUGINS
209 * @return array Active plugin file paths (may be empty)
210 */
211 public static function get_deactivatable_files(string $slug): array {
212 if (!isset(self::KNOWN_PLUGINS[$slug]) || !self::is_plugin_detected($slug)) {
213 return [];
214 }
215
216 if (!function_exists('is_plugin_active')) {
217 require_once ABSPATH . 'wp-admin/includes/plugin.php';
218 }
219
220 $plugin = self::KNOWN_PLUGINS[$slug];
221 $files = array_merge([$plugin['file']], $plugin['extra_files'] ?? []);
222
223 return array_values(array_filter($files, 'is_plugin_active'));
224 }
225
226 /**
227 * Should show admin notice
228 *
229 * @return bool True if should show notice
230 */
231 public static function should_show_notice(): bool {
232 // Only show if SEO plugins detected
233 if (!self::has_seo_plugins()) {
234 return false;
235 }
236
237 // Show on admin pages (not just ThinkRank pages)
238 if (!is_admin()) {
239 return false;
240 }
241
242 // Check if user dismissed the notice
243 $dismissed = get_user_meta(get_current_user_id(), 'thinkrank_seo_notice_dismissed', true);
244 if ($dismissed) {
245 return false;
246 }
247
248 return true;
249 }
250
251 /**
252 * Dismiss notice for current user
253 *
254 * @return void
255 */
256 public static function dismiss_notice(): void {
257 update_user_meta(get_current_user_id(), 'thinkrank_seo_notice_dismissed', true);
258 }
259
260 /**
261 * Reset notice dismissal (for testing)
262 *
263 * @return void
264 */
265 public static function reset_notice(): void {
266 delete_user_meta(get_current_user_id(), 'thinkrank_seo_notice_dismissed');
267 }
268 }
269