fix: harden the Divi BlockTree against opaque/invalid writes
Final fix wave for stage 2a, load-bearing for 2b's writers:
- Block::setAttrs()/Container::insertChild()/removeChild() refuse on an opaque
block (undecodable attrs or a global module) instead of silently corrupting it.
- Container::append() is now @internal (Parser only) and refuses on a void
block, so a child can no longer be silently dropped from the serialized output.
- Block::create() validates the block name against namespace/block-name.
- Json::decodeObject() returns null (-> opaque) for a top-level object whose
keys are a plain list ("0","1",...) rather than returning [] and letting the
Serializer re-encode it as a corrupting JSON array.
- AttrMap::set() rejects empty path segments and keeps an existing stdClass
value an object when rewrapping it (no silent object->list flip); AttrMap::get()
is updated in step so it can still read back through such an object.
- Block::copy() deep-clones stdClass attribute values instead of sharing them.
- Json::encode()'s existing JsonException-on-invalid-UTF-8 behaviour is documented.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -15,7 +15,11 @@ use stdClass;
|
||||
*/
|
||||
final class Json
|
||||
{
|
||||
/** @return array<string, mixed>|null Null when $json is not a JSON object. */
|
||||
/**
|
||||
* @return array<string, mixed>|null Null when $json is not a JSON object, or its top level
|
||||
* is one only nominally (keys "0", "1", … – see the class docblock): re-encoding those
|
||||
* keys as a PHP array would silently turn the object back into a JSON list.
|
||||
*/
|
||||
public static function decodeObject(string $json): ?array
|
||||
{
|
||||
try {
|
||||
@@ -28,6 +32,11 @@ final class Json
|
||||
return null;
|
||||
}
|
||||
|
||||
$keys = get_object_vars($value);
|
||||
if ($keys !== [] && array_is_list($keys)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$converted = self::fromJson($value);
|
||||
|
||||
return is_array($converted) ? $converted : [];
|
||||
@@ -52,7 +61,13 @@ final class Json
|
||||
return $value;
|
||||
}
|
||||
|
||||
/** @throws JsonException */
|
||||
/**
|
||||
* @throws JsonException When $value cannot be represented as JSON – including a string
|
||||
* with an invalid UTF-8 byte sequence (JSON_ERROR_UTF8). 2a only ever encodes
|
||||
* attributes this plugin already decoded from valid block markup, so that never
|
||||
* happens here; a caller that later encodes user-supplied strings (2b's writers)
|
||||
* must catch this and map it to a validation error instead of a fatal.
|
||||
*/
|
||||
public static function encode(mixed $value): string
|
||||
{
|
||||
// phpcs:ignore WordPress.WP.AlternativeFunctions.json_encode_json_encode -- pure PHP (spec §4.4).
|
||||
|
||||
Reference in New Issue
Block a user