PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.10.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.10.0
2.11.0 2.10.0 2.9.0 2.8.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 All 52 releases
← All changes | includes/admin/importers/class-import-detector.php +81 -1 2.4.0 → 2.10.0 View file →
@@ -68,8 +68,16 @@
68 68 'meta_prefix' => '',
69 69 'option_keys' => ['aioseo_options'],
70 70 'plugin_files' => ['all-in-one-seo-pack/all_in_one_seo_pack.php', 'all-in-one-seo-pack-pro/all_in_one_seo_pack.php'],
71 71 ],
72 + 'squirrly' => [
73 + 'name' => 'Squirrly SEO',
74 + // Squirrly keeps SEO in its own `qss` table, not postmeta (#740);
75 + // `_sq_` only covers its few fallback meta keys.
76 + 'meta_prefix' => '_sq_',
77 + 'option_keys' => ['sq_options'],
78 + 'plugin_files' => ['squirrly-seo/squirrly.php'],
79 + ],
72 80 ];
73 81
74 82 /**
75 83 * Detect all source plugins present in the database
@@ -158,10 +166,18 @@
158 166 // Only surface source plugins that are currently installed AND active.
159 167 // Migrating from a plugin the user no longer runs isn't actionable, so
160 168 // leftover data from a deactivated/uninstalled plugin is intentionally
161 169 // excluded from the migration screen.
170 + //
171 + // The one exception is Rank Math's FAQ / HowTo blocks. Those live in
172 + // post_content, and deactivating Rank Math is precisely what breaks
173 + // them: the blocks turn into "unsupported block" in the editor and lose
174 + // their schema on the front end. Hiding the only screen that can repair
175 + // them, at the exact moment they need repairing, left users with no way
176 + // out at all (#777) — so an inactive Rank Math stays listed whenever
177 + // convertible blocks remain, carrying that type and nothing else.
162 178 if (!$this->is_source_active($config['plugin_files'] ?? [])) {
163 - return null;
179 + return $this->detect_orphaned_blocks($slug, $config);
164 180 }
165 181
166 182 $counts = [];
167 183 $total = 0;
@@ -168,8 +184,11 @@
168 184
169 185 if ($slug === 'aioseo') {
170 186 // AIOSEO uses a custom table
171 187 $counts = $this->detect_aioseo();
188 + } elseif ($slug === 'squirrly') {
189 + // Squirrly uses a custom table too; the exporter classifies rows.
190 + $counts = $this->detect_squirrly();
172 191 } else {
173 192 // Standard postmeta-based plugins
174 193 $prefix = $config['meta_prefix'];
175 194
@@ -203,8 +222,18 @@
203 222 // when the exporter supports it (Rank Math Pro's redirects + 404
204 223 // Monitor and Yoast Premium's redirects were never offered).
205 224 $counts = array_merge($counts, $this->detect_redirect_data($slug));
206 225
226 + // FAQ / HowTo blocks, offered while Rank Math is still active too, so
227 + // the wizard converts them on the way out rather than leaving the user
228 + // to discover the breakage after deactivation.
229 + if ('rankmath' === $slug) {
230 + $block_count = Block_Converter::count_posts();
231 + if ($block_count > 0) {
232 + $counts[Block_Converter::TYPE] = $block_count;
233 + }
234 + }
235 +
207 236 // Check for settings
208 237 $has_settings = false;
209 238 foreach ($config['option_keys'] as $option_key) {
210 239 if (get_option($option_key, null) !== null) {
@@ -234,8 +263,39 @@
234 263 ];
235 264 }
236 265
237 266 /**
267 + * Detection result for a deactivated source that has left convertible
268 + * content blocks behind, or null when there is nothing to repair.
269 + *
270 + * Only the block type is reported. The plugin's postmeta and settings are
271 + * deliberately still withheld: migrating those from a plugin the site no
272 + * longer runs is the case the active-only rule exists to prevent, and it is
273 + * not what broke.
274 + *
275 + * @param string $slug Plugin slug.
276 + * @param array $config Plugin configuration.
277 + * @return array|null
278 + */
279 + private function detect_orphaned_blocks(string $slug, array $config): ?array {
280 + if ('rankmath' !== $slug) {
281 + return null;
282 + }
283 +
284 + $count = Block_Converter::count_posts();
285 + if ($count < 1) {
286 + return null;
287 + }
288 +
289 + return [
290 + 'plugin' => $slug,
291 + 'plugin_name' => $config['name'],
292 + 'counts' => [Block_Converter::TYPE => $count],
293 + 'total' => $count,
294 + ];
295 + }
296 +
297 + /**
238 298 * Whether any of a source plugin's known main files is active.
239 299 *
240 300 * Checks both single-site and network activation. A plugin that is merely
241 301 * installed but not active returns false.
@@ -303,8 +363,28 @@
303 363 if ($redirects > 0) {
304 364 $counts['redirections'] = $redirects;
305 365 }
306 366 }
367 + // Squirrly's Advanced Pack redirects are counted by the exporter in
368 + // detect_squirrly(), since only its `url` actions are redirects.
369 +
370 + return $counts;
371 + }
372 +
373 + /**
374 + * Detect Squirrly SEO data (custom `qss` table + Advanced Pack redirects).
375 + *
376 + * Delegated to the exporter: the table mixes posts, terms, author
377 + * profiles and the homepage, and only the exporter knows how to tell
378 + * them apart from the serialized `post` column.
379 + *
380 + * @return array Counts array
381 + */
382 + private function detect_squirrly(): array {
383 + $exporter = new Squirrly_Exporter();
384 + $counts = $exporter->get_available_types();
385 + // Settings are added by the caller from option_keys.
386 + unset($counts['settings']);
307 387
308 388 return $counts;
309 389 }
310 390