PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.8.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.8.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 1.0.1 1.0.2 1.1.0 All 49 releases
← All changes | includes/admin/importers/class-import-controller.php +42 -2 2.4.02.8.0 View file →
@@ -41,9 +41,9 @@
41 41 * Kept separate from ALLOWED_PLUGINS because cleanup() deletes the source
42 42 * plugin's live data: the native ThinkRank slug must never reach it, or the
43 43 * endpoint gains a path that wipes our own meta and options.
44 44 */
45 - private const SOURCE_PLUGINS = ['yoast', 'rankmath', 'seopress', 'aioseo'];
45 + private const SOURCE_PLUGINS = ['yoast', 'rankmath', 'seopress', 'aioseo', 'squirrly'];
46 46
47 47 /**
48 48 * Snapshot slug for ThinkRank's own data (export / backup / restore).
49 49 */
@@ -52,9 +52,9 @@
52 52 /**
53 53 * Allowed plugin slugs for the snapshot endpoints (export, migrate,
54 54 * snapshot delete). Includes the native slug; cleanup uses SOURCE_PLUGINS.
55 55 */
56 - private const ALLOWED_PLUGINS = ['yoast', 'rankmath', 'seopress', 'aioseo', 'thinkrank'];
56 + private const ALLOWED_PLUGINS = ['yoast', 'rankmath', 'seopress', 'aioseo', 'squirrly', 'thinkrank'];
57 57
58 58 /**
59 59 * Allowed export/migrate types. Must cover every type the exporters and
60 60 * the frontend workflow (useImportWorkflow.js EXPORT_TYPES) can send —
@@ -89,8 +89,16 @@
89 89 [
90 90 'methods' => \WP_REST_Server::READABLE,
91 91 'callback' => [$this, 'detect'],
92 92 'permission_callback' => [$this, 'check_permissions'],
93 + 'args' => [
94 + 'refresh' => [
95 + 'type' => 'boolean',
96 + 'default' => false,
97 + 'sanitize_callback' => 'rest_sanitize_boolean',
98 + 'description' => __('Discard the cached scan and re-read the database. Set by the Re-scan button; the screen\'s own load leaves it off so repeat navigation stays instant.', 'thinkrank'),
99 + ],
100 + ],
93 101 ],
94 102 ]);
95 103
96 104 register_rest_route($this->namespace, '/' . $this->rest_base . '/export', [
@@ -172,13 +180,22 @@
172 180 /**
173 181 * GET /import/detect — Detect source plugins, ThinkRank's own exportable
174 182 * data, and existing snapshots
175 183 *
184 + * `refresh` drops the hour-long detection transient before scanning. Without
185 + * it the Re-scan button could not do the one thing it exists for: pick up a
186 + * source plugin the user just installed, activated or added data to.
187 + *
176 188 * @param \WP_REST_Request $request Request object
177 189 * @return \WP_REST_Response
178 190 */
179 191 public function detect(\WP_REST_Request $request): \WP_REST_Response {
180 192 $detector = new Import_Detector();
193 +
194 + if ($request->get_param('refresh')) {
195 + $detector->clear_cache();
196 + }
197 +
181 198 $detected = $detector->detect();
182 199 $snapshots = Snapshot_Store::get_available_snapshots();
183 200
184 201 return new \WP_REST_Response([
@@ -326,8 +343,9 @@
326 343 'yoast' => '_yoast_wpseo_',
327 344 'rankmath' => 'rank_math_',
328 345 'seopress' => '_seopress_',
329 346 'aioseo' => null, // Custom table
347 + 'squirrly' => '_sq_', // Fallback meta only; the SEO is in the qss table below
330 348 ];
331 349
332 350 $prefix = $prefix_map[$plugin] ?? null;
333 351
@@ -354,8 +372,9 @@
354 372 $usermeta_prefix_map = [
355 373 'yoast' => 'wpseo_',
356 374 'rankmath' => 'rank_math_',
357 375 'seopress' => '_seopress_',
376 + 'squirrly' => '_sq_',
358 377 ];
359 378 $usermeta_prefix = $usermeta_prefix_map[$plugin] ?? $prefix;
360 379 $deleted += (int) $wpdb->query(
361 380 $wpdb->prepare(
@@ -370,8 +389,9 @@
370 389 $option_keys_map = [
371 390 'yoast' => ['wpseo', 'wpseo_titles', 'wpseo_social', 'wpseo_taxonomy_meta'],
372 391 'rankmath' => ['rank-math-options-general', 'rank-math-options-titles', 'rank-math-options-sitemap', 'rank-math-options-instant-indexing'],
373 392 'seopress' => ['seopress_titles_option_name', 'seopress_social_option_name', 'seopress_advanced_option_name', 'seopress_xml_sitemap_option_name', 'seopress_instant_indexing_option_name'],
393 + 'squirrly' => ['sq_options'],
374 394 ];
375 395 foreach ($option_keys_map[$plugin] ?? [] as $option_name) {
376 396 if (delete_option($option_name)) {
377 397 $deleted++;
@@ -378,8 +398,26 @@
378 398 }
379 399 }
380 400 }
381 401
402 + if ($plugin === 'squirrly') {
403 + // Drop Squirrly's custom tables: per-URL SEO, Advanced Pack
404 + // redirects and their logs, reusable JSON-LD templates.
405 + foreach (['qss', 'qss_redirects', 'qss_redirects_logs', 'qss_jsonld'] as $suffix) {
406 + $table = $wpdb->prefix . $suffix;
407 + $table_exists = $wpdb->get_var(
408 + $wpdb->prepare("SHOW TABLES LIKE %s", $table)
409 + );
410 + if ($table_exists) {
411 + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name is $wpdb->prefix plus a literal, and every value is passed as a placeholder replacement.
412 + $count = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$table}");
413 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.SchemaChange, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name is $wpdb->prefix plus a literal, and every value is passed as a placeholder replacement.
414 + $wpdb->query("DROP TABLE {$table}");
415 + $deleted += $count;
416 + }
417 + }
418 + }
419 +
382 420 if ($plugin === 'aioseo') {
383 421 // Drop the AIOSEO custom tables the exporter reads (posts + Pro terms).
384 422 foreach (['aioseo_posts', 'aioseo_terms'] as $suffix) {
385 423 $table = $wpdb->prefix . $suffix;
@@ -455,8 +493,10 @@
455 493 case 'seopress':
456 494 return new SEOPress_Exporter();
457 495 case 'aioseo':
458 496 return new AIOSEO_Exporter();
497 + case Squirrly_Exporter::SLUG:
498 + return new Squirrly_Exporter();
459 499 case Thinkrank_Exporter::SLUG:
460 500 return new Thinkrank_Exporter();
461 501 default:
462 502 return new \WP_Error('invalid_plugin', 'Unsupported plugin: ' . $plugin, ['status' => 400]);