PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.13.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.13.0
2.13.0 2.12.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 All 54 releases
← All changes | includes/admin/importers/class-import-controller.php +139 -3 2.9.0 → 2.13.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', 'squirrly'];
45 + private const SOURCE_PLUGINS = ['yoast', 'rankmath', 'seopress', 'aioseo', 'squirrly', 'slimseo'];
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', 'squirrly', 'thinkrank'];
56 + private const ALLOWED_PLUGINS = ['yoast', 'rankmath', 'seopress', 'aioseo', 'squirrly', 'slimseo', '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 —
@@ -60,9 +60,17 @@
60 60 * the frontend workflow (useImportWorkflow.js EXPORT_TYPES) can send —
61 61 * 404_logs was missing, so Rank Math's 404 Monitor could never be
62 62 * exported or migrated through REST.
63 63 */
64 - private const ALLOWED_TYPES = ['postmeta', 'termmeta', 'usermeta', 'redirections', '404_logs', 'settings'];
64 + private const ALLOWED_TYPES = [
65 + 'postmeta',
66 + 'termmeta',
67 + 'usermeta',
68 + 'redirections',
69 + '404_logs',
70 + 'settings',
71 + Block_Converter::TYPE,
72 + ];
65 73
66 74 /**
67 75 * Types the export and migrate endpoints accept.
68 76 *
@@ -150,8 +158,41 @@
150 158 ],
151 159 ],
152 160 ]);
153 161
162 + // Undo for the Rank Math FAQ/HowTo block conversion on sites with
163 + // revisions disabled, where Block_Converter kept the original content
164 + // in post meta. With revisions on, the post's revision history is the
165 + // undo and this route has nothing to restore.
166 + register_rest_route($this->namespace, '/' . $this->rest_base . '/content-blocks/restore', [
167 + [
168 + 'methods' => \WP_REST_Server::CREATABLE,
169 + 'callback' => [$this, 'restore_content_blocks'],
170 + 'permission_callback' => [$this, 'check_permissions'],
171 + 'args' => [
172 + 'post_id' => [
173 + 'type' => 'integer',
174 + 'default' => 0,
175 + 'minimum' => 0,
176 + 'sanitize_callback' => 'absint',
177 + 'description' => __('Restore one post. Omit to restore every post with a backup, one page at a time.', 'thinkrank'),
178 + ],
179 + 'after' => [
180 + 'type' => 'integer',
181 + 'default' => 0,
182 + 'minimum' => 0,
183 + 'sanitize_callback' => 'absint',
184 + 'description' => __('Paging cursor: pass the previous response\'s next_after.', 'thinkrank'),
185 + ],
186 + 'force' => [
187 + 'type' => 'boolean',
188 + 'default' => false,
189 + 'description' => __('Restore posts that were edited after the conversion, discarding those edits.', 'thinkrank'),
190 + ],
191 + ],
192 + ],
193 + ]);
194 +
154 195 register_rest_route($this->namespace, '/' . $this->rest_base . '/snapshot', [
155 196 [
156 197 'methods' => \WP_REST_Server::DELETABLE,
157 198 'callback' => [$this, 'delete_snapshot'],
@@ -344,8 +385,11 @@
344 385 'rankmath' => 'rank_math_',
345 386 'seopress' => '_seopress_',
346 387 'aioseo' => null, // Custom table
347 388 'squirrly' => '_sq_', // Fallback meta only; the SEO is in the qss table below
389 + // Slim SEO is cleaned by exact key below: a `slim_seo` prefix also
390 + // matches Slim SEO Pro's and its add-ons' own keys (#886).
391 + 'slimseo' => null,
348 392 ];
349 393
350 394 $prefix = $prefix_map[$plugin] ?? null;
351 395
@@ -398,8 +442,48 @@
398 442 }
399 443 }
400 444 }
401 445
446 + if ($plugin === 'slimseo') {
447 + // Exactly the keys the exporter reads. A `slim_seo%` prefix would
448 + // also take data nothing imported (Slim SEO Link Manager's and
449 + // Auto Link's settings), and those stay with their plugin.
450 + $deleted += (int) $wpdb->query(
451 + $wpdb->prepare(
452 + "DELETE FROM {$wpdb->postmeta} WHERE meta_key IN (%s, %s, %s, %s, %s) OR meta_key LIKE %s",
453 + Slim_SEO_Exporter::META_KEY,
454 + Slim_SEO_Exporter::PRO_META_KEY,
455 + Slim_SEO_Exporter::SCHEMA_META_KEY,
456 + Slim_SEO_Exporter::ALLOW_GLOBAL_META,
457 + Slim_SEO_Exporter::OLD_PERMALINK_META,
458 + $wpdb->esc_like(Slim_SEO_Exporter::PRIMARY_TERM_PREFIX) . '%'
459 + )
460 + );
461 + $deleted += (int) $wpdb->query(
462 + $wpdb->prepare("DELETE FROM {$wpdb->termmeta} WHERE meta_key = %s", Slim_SEO_Exporter::META_KEY)
463 + );
464 + $options = [
465 + Slim_SEO_Exporter::OPTION,
466 + Slim_SEO_Exporter::REDIRECTS_OPTION,
467 + Slim_SEO_Exporter::PRO_OPTION,
468 + Slim_SEO_Exporter::SCHEMAS_OPTION,
469 + 'slim_seo_db_version',
470 + 'ss_redirection_db_version',
471 + ];
472 + foreach ($options as $option_name) {
473 + if (delete_option($option_name)) {
474 + $deleted++;
475 + }
476 + }
477 + $table = $wpdb->prefix . Slim_SEO_Exporter::LOG_404_TABLE;
478 + if ($wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table))) {
479 + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name is $wpdb->prefix plus a literal.
480 + $deleted += (int) $wpdb->get_var("SELECT COUNT(*) FROM {$table}");
481 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.SchemaChange, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name is $wpdb->prefix plus a literal.
482 + $wpdb->query("DROP TABLE {$table}");
483 + }
484 + }
485 +
402 486 if ($plugin === 'squirrly') {
403 487 // Drop Squirrly's custom tables: per-URL SEO, Advanced Pack
404 488 // redirects and their logs, reusable JSON-LD templates.
405 489 foreach (['qss', 'qss_redirects', 'qss_redirects_logs', 'qss_jsonld'] as $suffix) {
@@ -461,8 +545,58 @@
461 545 ], 200);
462 546 }
463 547
464 548 /**
549 + * POST /import/content-blocks/restore: put converted posts back.
550 + *
551 + * Restores the pre-conversion `post_content` Block_Converter kept for posts
552 + * converted while revisions were disabled. A post edited since is reported
553 + * under `modified` and left alone unless `force` is set.
554 + *
555 + * Without `post_id` it works through one page of posts per call; while
556 + * `next_after` is non-null, call again with `after` set to it.
557 + *
558 + * @since 2.10.0
559 + *
560 + * @param \WP_REST_Request $request Request object
561 + * @return \WP_REST_Response
562 + */
563 + public function restore_content_blocks(\WP_REST_Request $request): \WP_REST_Response {
564 + $post_id = (int) $request->get_param('post_id');
565 + $force = (bool) $request->get_param('force');
566 + $limit = Block_Converter::chunk_size();
567 +
568 + $ids = $post_id > 0
569 + ? [$post_id]
570 + : Block_Converter::get_backup_post_ids((int) $request->get_param('after'), $limit);
571 +
572 + $report = [
573 + 'restored' => [],
574 + 'modified' => [],
575 + 'no_backup' => [],
576 + 'errors' => [],
577 + 'next_after' => null,
578 + ];
579 +
580 + foreach ($ids as $id) {
581 + $result = Block_Converter::restore_post($id, $force);
582 +
583 + if ('error' === $result['status']) {
584 + $report['errors'][] = ['post_id' => $id, 'message' => $result['message']];
585 + continue;
586 + }
587 +
588 + $report[$result['status']][] = $id;
589 + }
590 +
591 + if ($post_id < 1 && count($ids) === $limit) {
592 + $report['next_after'] = (int) end($ids);
593 + }
594 +
595 + return new \WP_REST_Response($report, 200);
596 + }
597 +
598 + /**
465 599 * DELETE /import/snapshot — Delete snapshot data from wp_options
466 600 *
467 601 * @param \WP_REST_Request $request Request object
468 602 * @return \WP_REST_Response
@@ -495,8 +629,10 @@
495 629 case 'aioseo':
496 630 return new AIOSEO_Exporter();
497 631 case Squirrly_Exporter::SLUG:
498 632 return new Squirrly_Exporter();
633 + case Slim_SEO_Exporter::SLUG:
634 + return new Slim_SEO_Exporter();
499 635 case Thinkrank_Exporter::SLUG:
500 636 return new Thinkrank_Exporter();
501 637 default:
502 638 return new \WP_Error('invalid_plugin', 'Unsupported plugin: ' . $plugin, ['status' => 400]);