diff --git a/src/Integrations/Divi/Design/SnapshotStore.php b/src/Integrations/Divi/Design/SnapshotStore.php index 7fa208b..5ddff26 100644 --- a/src/Integrations/Divi/Design/SnapshotStore.php +++ b/src/Integrations/Divi/Design/SnapshotStore.php @@ -38,11 +38,16 @@ class SnapshotStore /** * @param array $data The area's raw inventory. * @throws SnapshotTooLarge - * @throws RuntimeException When the option could not be written. + * @throws RuntimeException When the snapshot cannot be encoded or the option could not be written. */ public function save(string $area, array $data, string $ability): string { - $size = strlen((string) wp_json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); + $json = wp_json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); + if (! is_string($json)) { + throw new RuntimeException(sprintf('snapshot %s could not be encoded', $area)); + } + + $size = strlen($json); if ($size > self::MAX_BYTES) { throw new SnapshotTooLarge($size); } diff --git a/tests/Unit/Integrations/Divi/Design/SnapshotStoreTest.php b/tests/Unit/Integrations/Divi/Design/SnapshotStoreTest.php index 2ea67a4..1d30d46 100644 --- a/tests/Unit/Integrations/Divi/Design/SnapshotStoreTest.php +++ b/tests/Unit/Integrations/Divi/Design/SnapshotStoreTest.php @@ -87,6 +87,25 @@ final class SnapshotStoreTest extends TestCase (new SnapshotStore())->save('presets', ['a' => 1], 'lit/divi-presets-set'); } + public function testAnUnencodableSnapshotIsRefusedBeforeAnyWrite(): void + { + Functions\when('wp_json_encode')->justReturn(false); + $written = false; + Functions\when('update_option')->alias(function () use (&$written): bool { + $written = true; + + return true; + }); + + try { + (new SnapshotStore())->save('presets', ['a' => 1], 'lit/divi-presets-set'); + self::fail('an unencodable snapshot must be refused'); + } catch (RuntimeException $e) { + self::assertSame('snapshot presets could not be encoded', $e->getMessage()); + } + self::assertFalse($written, 'nothing may be written when the snapshot cannot be encoded'); + } + public function testCorruptOptionReadsAsEmpty(): void { $this->options['lit_mcp_snapshots_presets'] = 'kaputt';