$attrs */ private function __construct( private string $name, private array $attrs, private ?string $rawOpen, private ?string $rawClose, private bool $void, private bool $opaque ) { } /** @param array $attrs */ public static function parsed(string $name, array $attrs, string $rawOpen, bool $void, bool $opaque): self { return new self($name, $attrs, $rawOpen, null, $void, $opaque); } /** * A new block (opener and closer are generated; written as a pair like Divi does). * * @param array $attrs * @param list $children */ public static function create(string $name, array $attrs = [], array $children = []): self { if (preg_match(self::NAME_PATTERN, $name) !== 1) { throw new InvalidArgumentException(sprintf('"%s" is not a valid block name.', $name)); } $block = new self($name, $attrs, null, null, false, false); foreach ($children as $child) { $block->parts[] = $child; } return $block; } /** Used by the Parser for the closing delimiter. */ public function closeWith(string $rawClose): void { $this->rawClose = $rawClose; } /** Full block name, e.g. "divi/section"; blocks without namespace are "core/…". */ public function name(): string { return $this->name; } /** Name without the "divi/" namespace, e.g. "button"; other namespaces stay. */ public function shortName(): string { return str_starts_with($this->name, 'divi/') ? substr($this->name, 5) : $this->name; } /** @return array */ public function attrs(): array { return $this->attrs; } /** @param array $attrs */ public function setAttrs(array $attrs): void { if ($this->opaque) { throw new LogicException('Cannot set attributes of an opaque block.'); } $this->attrs = $attrs; $this->rawOpen = null; } public function rawOpen(): ?string { return $this->rawOpen; } public function rawClose(): ?string { return $this->rawClose; } public function isVoid(): bool { return $this->void; } public function isOpaque(): bool { return $this->opaque; } public function isDivi(): bool { return str_starts_with($this->name, 'divi/'); } /** The divi/placeholder wrapper the builder puts around a page's sections (transparent for IDs). */ public function isPlaceholder(): bool { return $this->name === 'divi/placeholder'; } /** A global module reference or global layout (edited only in Etappe 3). */ public function isGlobal(): bool { return $this->name === 'divi/global-layout' || array_key_exists('globalModule', $this->attrs); } /** Deep copy – unchanged copies serialize to exactly the same bytes. */ public function copy(): self { return clone $this; } /** * A deep copy of this block under another name and attributes: opener and closer are * regenerated, children and the text between them are kept, the void flag too. Opaque exactly * when the Parser would make it so. GlobalInstance uses it to turn a library root into an * instance and back. * * @param array $attrs */ public function copyAs(string $name, array $attrs): self { if (preg_match(self::NAME_PATTERN, $name) !== 1) { throw new InvalidArgumentException(sprintf('"%s" is not a valid block name.', $name)); } $copy = clone $this; $copy->name = $name; $copy->attrs = self::deepCloneAttrs($attrs); $copy->rawOpen = null; $copy->rawClose = null; $copy->opaque = $name === 'divi/global-layout' || array_key_exists('globalModule', $attrs); return $copy; } public function __clone() { foreach ($this->parts as $position => $part) { if ($part instanceof Block) { $this->parts[$position] = clone $part; } } $this->attrs = self::deepCloneAttrs($this->attrs); } /** * PHP's own array-copy-on-write already deep-copies nested arrays, but stdClass values * (Json.php keeps empty/list-like JSON objects as stdClass) are objects – clone() alone * would leave the copy's attrs() sharing them with the original. * * @param array $attrs * @return array */ private static function deepCloneAttrs(array $attrs): array { foreach ($attrs as $key => $value) { if ($value instanceof stdClass) { $attrs[$key] = (object) self::deepCloneAttrs((array) $value); } elseif (is_array($value)) { $attrs[$key] = self::deepCloneAttrs($value); } } return $attrs; } protected function touched(): void { if ($this->void) { $this->void = false; $this->rawOpen = null; } } }