PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.12.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.12.0
2.14.3 2.14.2 2.14.1 2.14.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 All 58 releases
← All changes | includes/admin/importers/class-abstract-plugin-exporter.php +78 -12 1.25.0 → 2.12.0 View file →
@@ -152,8 +152,26 @@
152 152 return [];
153 153 }
154 154
155 155 /**
156 + * Records for a data type outside the fixed set above.
157 + *
158 + * Nothing to hand over by default. It exists so an exporter can gain a type
159 + * (ThinkRank's own export lets Pro register its tables this way) without
160 + * every subclass having to reimplement export_chunk()'s chunk writing and
161 + * manifest bookkeeping.
162 + *
163 + * @since 2.2.0
164 + *
165 + * @param string $type Data type
166 + * @param int $page Page number (1-indexed)
167 + * @return array Records
168 + */
169 + protected function export_custom_type_page(string $type, int $page): array {
170 + return [];
171 + }
172 +
173 + /**
156 174 * Capture the source plugin's Role Manager assignments: role slug => the
157 175 * capabilities that role holds whose name starts with $prefix.
158 176 *
159 177 * Shared by every exporter because role capabilities live on the roles
@@ -197,15 +215,47 @@
197 215
198 216 /**
199 217 * Convert plugin-specific template variables to literal values
200 218 *
201 - * @param string $value String potentially containing template variables
219 + * Accepts mixed because the input is another plugin's stored data, over
220 + * which we have no schema guarantees. Rank Math in particular can hold
221 + * booleans inside its options arrays where a template string is expected,
222 + * and the `?? ''` at the call sites only guards against a MISSING key —
223 + * a present-but-boolean value sailed straight into a string-typed
224 + * parameter and fataled the whole migration at the snapshot step.
225 + * Implementations MUST start with stringify_template_value().
226 + *
227 + * @param mixed $value Value potentially containing template variables
202 228 * @param int|null $post_id Post ID for context-specific variables
203 229 * @return string Converted string
204 230 */
205 - abstract protected function convert_template_variables(string $value, ?int $post_id = null): string;
231 + abstract protected function convert_template_variables($value, ?int $post_id = null): string;
206 232
207 233 /**
234 + * Coerce a foreign settings/meta value into a template string.
235 + *
236 + * Strings pass through; ints and floats are kept as their string form (a
237 + * purely numeric title is odd but meaningful); everything else — booleans,
238 + * arrays, objects, null — has no sensible reading as a template, so it
239 + * becomes '', which downstream already treats as "not set" and replaces
240 + * with defaults. Dropping garbage beats failing the migration over it.
241 + *
242 + * @param mixed $value Raw value from the source plugin's storage.
243 + * @return string Usable template string, possibly ''.
244 + */
245 + final protected function stringify_template_value($value): string {
246 + if (is_string($value)) {
247 + return $value;
248 + }
249 +
250 + if (is_int($value) || is_float($value)) {
251 + return (string) $value;
252 + }
253 +
254 + return '';
255 + }
256 +
257 + /**
208 258 * Export a chunk of data and write to snapshot
209 259 *
210 260 * This is the main orchestration method. It calls the appropriate
211 261 * export_*_page() method, writes the chunk via Snapshot_Store, and
@@ -219,17 +269,30 @@
219 269 // Reset before the page method runs; it (or its id helper) records the
220 270 // raw fetched-row count here.
221 271 $this->last_page_row_count = null;
222 272
223 - $records = match ($type) {
224 - 'postmeta' => $this->export_postmeta_page($page),
225 - 'termmeta' => $this->export_termmeta_page($page),
226 - 'usermeta' => $this->export_usermeta_page($page),
227 - 'settings' => $this->export_settings(),
228 - 'redirections' => $this->export_redirections_page($page),
229 - '404_logs' => $this->export_404_logs_page($page),
230 - default => [],
231 - };
273 + switch ($type) {
274 + case 'postmeta':
275 + $records = $this->export_postmeta_page($page);
276 + break;
277 + case 'termmeta':
278 + $records = $this->export_termmeta_page($page);
279 + break;
280 + case 'usermeta':
281 + $records = $this->export_usermeta_page($page);
282 + break;
283 + case 'settings':
284 + $records = $this->export_settings();
285 + break;
286 + case 'redirections':
287 + $records = $this->export_redirections_page($page);
288 + break;
289 + case '404_logs':
290 + $records = $this->export_404_logs_page($page);
291 + break;
292 + default:
293 + $records = $this->export_custom_type_page($type, $page);
294 + }
232 295
233 296 $exported_count = count($records);
234 297
235 298 // Write chunk to snapshot store
@@ -339,9 +402,9 @@
339 402 $result = ['noindex' => 0, 'nofollow' => 0];
340 403
341 404 // Handle serialized array (Rank Math stores robots as serialized array)
342 405 if (is_string($noindex_value) && is_serialized($noindex_value)) {
343 - $noindex_value = maybe_unserialize($noindex_value);
406 + $noindex_value = Safe_Unserializer::unserialize($noindex_value);
344 407 }
345 408
346 409 if (is_array($noindex_value)) {
347 410 // Rank Math format: serialized array with 'noindex', 'nofollow' as values
@@ -401,8 +464,9 @@
401 464 // (oembed_cache, revisions, nav menu items, block/template CPTs, …) never
402 465 // enter the snapshot — SEO meta left on them is noise. Filtering in the
403 466 // query (rather than per-record) keeps the chunk-size based has_more
404 467 // pagination in export_chunk() accurate.
468 + // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- table name is $wpdb->prefix plus a literal, and every value is passed as a placeholder replacement.
405 469 $sql = $wpdb->prepare(
406 470 "SELECT DISTINCT pm.post_id
407 471 FROM {$wpdb->postmeta} pm
408 472 INNER JOIN {$wpdb->posts} p ON p.ID = pm.post_id
@@ -415,9 +479,11 @@
415 479 $post_types,
416 480 [$this->chunk_size, $offset]
417 481 )
418 482 );
483 + // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
419 484
485 + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- table name is $wpdb->prefix plus a literal, and every value is passed as a placeholder replacement.
420 486 $ids = $wpdb->get_col($sql);
421 487 $this->last_page_row_count = count($ids);
422 488
423 489 return $ids;