| @@ -30,8 +30,13 @@ | ||
| 30 | 30 | */ |
| 31 | 31 | private const CACHE_KEY = 'thinkrank_import_detection'; |
| 32 | 32 | |
| 33 | 33 | /** |
| 34 | + * Transient key for caching the native (ThinkRank's own data) counts | |
| 35 | + */ | |
| 36 | + private const NATIVE_CACHE_KEY = 'thinkrank_export_detection'; | |
| 37 | + | |
| 38 | + /** | |
| 34 | 39 | * Cache TTL in seconds (1 hour) |
| 35 | 40 | */ |
| 36 | 41 | private const CACHE_TTL = 3600; |
| 37 | 42 | |
| @@ -63,8 +68,16 @@ | ||
| 63 | 68 | 'meta_prefix' => '', |
| 64 | 69 | 'option_keys' => ['aioseo_options'], |
| 65 | 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'], |
| 66 | 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 | + ], | |
| 67 | 80 | ]; |
| 68 | 81 | |
| 69 | 82 | /** |
| 70 | 83 | * Detect all source plugins present in the database |
| @@ -94,8 +107,44 @@ | ||
| 94 | 107 | return $detected; |
| 95 | 108 | } |
| 96 | 109 | |
| 97 | 110 | /** |
| 111 | + * Detect ThinkRank's own exportable data. | |
| 112 | + * | |
| 113 | + * Deliberately NOT part of the PLUGINS registry that detect() walks. That | |
| 114 | + * registry describes plugins to migrate FROM: every entry is gated on | |
| 115 | + * is_source_active() (which would reject us), feeds the "import from" cards | |
| 116 | + * in the UI, and — most importantly — drives cleanup()'s prefix maps, which | |
| 117 | + * delete the listed plugin's live data. Keeping the native source on its | |
| 118 | + * own path avoids all three problems instead of special-casing each. | |
| 119 | + * | |
| 120 | + * @param bool $use_cache Whether to use cached results | |
| 121 | + * @return array|null Detection result, or null when there is nothing to export | |
| 122 | + */ | |
| 123 | + public function detect_native(bool $use_cache = true): ?array { | |
| 124 | + if ($use_cache) { | |
| 125 | + $cached = get_transient(self::NATIVE_CACHE_KEY); | |
| 126 | + if ($cached !== false) { | |
| 127 | + return is_array($cached) ? $cached : null; | |
| 128 | + } | |
| 129 | + } | |
| 130 | + | |
| 131 | + $exporter = new Thinkrank_Exporter(); | |
| 132 | + $counts = $exporter->get_available_types(); | |
| 133 | + | |
| 134 | + $result = [ | |
| 135 | + 'plugin' => $exporter->get_plugin_slug(), | |
| 136 | + 'plugin_name' => $exporter->get_plugin_name(), | |
| 137 | + 'counts' => $counts, | |
| 138 | + 'total' => array_sum($counts), | |
| 139 | + ]; | |
| 140 | + | |
| 141 | + set_transient(self::NATIVE_CACHE_KEY, $result, self::CACHE_TTL); | |
| 142 | + | |
| 143 | + return $result; | |
| 144 | + } | |
| 145 | + | |
| 146 | + /** | |
| 98 | 147 | * Clear the detection cache |
| 99 | 148 | * |
| 100 | 149 | * @return void |
| 101 | 150 | */ |
| @@ -100,8 +149,9 @@ | ||
| 100 | 149 | * @return void |
| 101 | 150 | */ |
| 102 | 151 | public function clear_cache(): void { |
| 103 | 152 | delete_transient(self::CACHE_KEY); |
| 153 | + delete_transient(self::NATIVE_CACHE_KEY); | |
| 104 | 154 | } |
| 105 | 155 | |
| 106 | 156 | /** |
| 107 | 157 | * Detect a single plugin's data presence |
| @@ -126,8 +176,11 @@ | ||
| 126 | 176 | |
| 127 | 177 | if ($slug === 'aioseo') { |
| 128 | 178 | // AIOSEO uses a custom table |
| 129 | 179 | $counts = $this->detect_aioseo(); |
| 180 | + } elseif ($slug === 'squirrly') { | |
| 181 | + // Squirrly uses a custom table too; the exporter classifies rows. | |
| 182 | + $counts = $this->detect_squirrly(); | |
| 130 | 183 | } else { |
| 131 | 184 | // Standard postmeta-based plugins |
| 132 | 185 | $prefix = $config['meta_prefix']; |
| 133 | 186 | |
| @@ -261,8 +314,28 @@ | ||
| 261 | 314 | if ($redirects > 0) { |
| 262 | 315 | $counts['redirections'] = $redirects; |
| 263 | 316 | } |
| 264 | 317 | } |
| 318 | + // Squirrly's Advanced Pack redirects are counted by the exporter in | |
| 319 | + // detect_squirrly(), since only its `url` actions are redirects. | |
| 320 | + | |
| 321 | + return $counts; | |
| 322 | + } | |
| 323 | + | |
| 324 | + /** | |
| 325 | + * Detect Squirrly SEO data (custom `qss` table + Advanced Pack redirects). | |
| 326 | + * | |
| 327 | + * Delegated to the exporter: the table mixes posts, terms, author | |
| 328 | + * profiles and the homepage, and only the exporter knows how to tell | |
| 329 | + * them apart from the serialized `post` column. | |
| 330 | + * | |
| 331 | + * @return array Counts array | |
| 332 | + */ | |
| 333 | + private function detect_squirrly(): array { | |
| 334 | + $exporter = new Squirrly_Exporter(); | |
| 335 | + $counts = $exporter->get_available_types(); | |
| 336 | + // Settings are added by the caller from option_keys. | |
| 337 | + unset($counts['settings']); | |
| 265 | 338 | |
| 266 | 339 | return $counts; |
| 267 | 340 | } |
| 268 | 341 | |