← All changes
|
includes/admin/importers/class-abstract-plugin-exporter.php
+78
-12
1.26.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; |