← All changes
|
includes/admin/importers/class-export-controller.php
+22
-6
2.3.0
→
2.12.0
View file →
| @@ -429,14 +429,21 @@ | ||
| 429 | 429 | // The slug becomes an option name via Snapshot_Store, so a hand-edited |
| 430 | 430 | // file must not be able to steer where the chunks land. The original key |
| 431 | 431 | // is carried alongside it because that is what the records are filed |
| 432 | 432 | // under in `data` — sanitising is for the destination, not the lookup. |
| 433 | - $present = []; | |
| 433 | + // | |
| 434 | + // sanitize_key() bounds the alphabet but not the length, and an | |
| 435 | + // over-long slug makes a chunk key MySQL truncates on write and cannot | |
| 436 | + // find on read: from the next request on, the type counts records that | |
| 437 | + // read back as nothing. Keeping such a type would only preserve a | |
| 438 | + // promise the storage cannot honour, so it is dropped at the door. | |
| 439 | + $max_slug = Snapshot_Store::max_type_length(Thinkrank_Exporter::SLUG); | |
| 440 | + $present = []; | |
| 434 | 441 | foreach (array_keys((array) $payload['data']) as $key) { |
| 435 | 442 | $slug = sanitize_key((string) $key); |
| 436 | 443 | // First one wins, so two keys that sanitise alike cannot have the |
| 437 | 444 | // second silently overwrite the first's chunks. |
| 438 | - if ($slug !== '' && !isset($present[$slug])) { | |
| 445 | + if ($slug !== '' && strlen($slug) <= $max_slug && !isset($present[$slug])) { | |
| 439 | 446 | $present[$slug] = (string) $key; |
| 440 | 447 | } |
| 441 | 448 | } |
| 442 | 449 | |
| @@ -575,17 +582,26 @@ | ||
| 575 | 582 | * not the count), so an untouched type shows up as |
| 576 | 583 | * `total_records: 0, total_chunks: 1`. Writing that into the file would |
| 577 | 584 | * advertise data the file does not contain. |
| 578 | 585 | * |
| 586 | + * The question is what the *snapshot* holds, not what this build can | |
| 587 | + * produce. Filtering on get_exportable_types() here conflated the two and | |
| 588 | + * broke the guarantee payload_types() makes on the way in: a file exported | |
| 589 | + * with Pro active and uploaded to a free build keeps its Pro records so a | |
| 590 | + * later Pro activation can drain the same snapshot — and then re-downloading | |
| 591 | + * it on that free build stripped exactly those records from both `types` and | |
| 592 | + * `data` (#589). A fresh export cannot smuggle a type in this way: the run | |
| 593 | + * starts from an empty slot (the `reset` flag on the first chunk) and the | |
| 594 | + * manifest only ever gains the types that build actually exported. | |
| 595 | + * | |
| 596 | + * @since 2.12.0 Tests the stored payload rather than the running build's | |
| 597 | + * exportable types. | |
| 598 | + * | |
| 579 | 599 | * @param array $manifest Snapshot manifest |
| 580 | 600 | * @param string $type Data type |
| 581 | 601 | * @return bool |
| 582 | 602 | */ |
| 583 | 603 | private function type_has_records(array $manifest, string $type): bool { |
| 584 | - if (!in_array($type, Thinkrank_Exporter::get_exportable_types(), true)) { | |
| 585 | - return false; | |
| 586 | - } | |
| 587 | - | |
| 588 | 604 | return (int) ($manifest['types'][$type]['total_records'] ?? 0) > 0; |
| 589 | 605 | } |
| 590 | 606 | |
| 591 | 607 | /** |