",…}})$ becomes the value – for a strings variable with the reference's own before/after * around it. References embedded in a longer text stay (Divi resolves them inside the text; cutting them out would * change the text) and are reported through leftover() (ReportsLeftovers). Other references – colors, other * variables, dynamic content – stay untouched. Pure PHP. */ final class VariableDetachRewrite implements NodeRewrite, ReportsLeftovers { private const EMBEDDED = 'Die Variable steht hier eingebettet in einem längeren Text; dieser Verweis bleibt ' . 'unverändert – im Divi-Builder prüfen.'; private const UNSAFE = 'Vor- oder Nachtext des Verweises enthält Markup oder einen Verweis; der Verweis bleibt ' . 'unverändert – im Divi-Builder prüfen.'; /** @param bool $markupAllowed Whether "<" and ">" may enter the content (unfiltered_html); markers never may. */ public function __construct( private string $id, private string $value, private string $variableType, private bool $markupAllowed = false ) { } /** * Whether $text must not be inlined into content: it holds a reference marker (it never went through * VariableRef::canonical()) or – without unfiltered_html – markup (D7, S1). */ public static function unsafe(string $text, bool $markupAllowed): bool { return str_contains($text, VariableRef::MARKER) || str_contains($text, VariableRef::END) || (! $markupAllowed && (str_contains($text, '<') || str_contains($text, '>'))); } /** * What one whole reference to a variable of $variableType becomes: its value, for strings with the reference's * before/after settings around it. */ public static function replacement(string $reference, string $value, string $variableType): string { if ($variableType !== 'strings') { return $value; } $settings = VariableRef::settingsOf($reference); $before = is_string($settings['before'] ?? null) ? $settings['before'] : ''; $after = is_string($settings['after'] ?? null) ? $settings['after'] : ''; return $before . $value . $after; } public function rewrite(Block $block): ?array { if (! $block->isDivi() || $this->hasUnsafeAffix($block->attrs())) { return null; // fail closed: the block stays whole and leftover() reports it } $changed = false; $attrs = $this->resolved($block->attrs(), $changed); return $changed && is_array($attrs) ? $attrs : null; } public function notes(Block $block, string $nodeId): array { return []; // leftovers go through leftover(): they also matter for blocks rewrite() did not change } public function leftover(Block $block): ?string { if (! $block->isDivi()) { return null; } if ($this->hasUnsafeAffix($block->attrs())) { return self::UNSAFE; } return $this->embeds($block->attrs()) ? self::EMBEDDED : null; } public function concernsInstance(Block $instance): bool { return DetachRewrite::mentions($instance, $this->id); } /** $value with every whole reference to the variable replaced; $changed tells whether one was. */ private function resolved(mixed $value, bool &$changed): mixed { if (is_string($value)) { if (VariableRef::nameOf($value) !== $this->id) { return $value; } $changed = true; return self::replacement($value, $this->value, $this->variableType); } if (! is_array($value)) { return $value; } foreach ($value as $key => $item) { $value[$key] = $this->resolved($item, $changed); } return $value; } /** Whether a whole reference below $value carries before/after text that must not be inlined. */ private function hasUnsafeAffix(mixed $value): bool { if (is_string($value)) { if ($this->variableType !== 'strings' || VariableRef::nameOf($value) !== $this->id) { return false; } $settings = VariableRef::settingsOf($value); foreach (['before', 'after'] as $key) { $text = is_string($settings[$key] ?? null) ? $settings[$key] : ''; if (self::unsafe($text, $this->markupAllowed)) { return true; } } return false; } if (! is_array($value)) { return false; } foreach ($value as $item) { if ($this->hasUnsafeAffix($item)) { return true; } } return false; } /** Whether a string below $value names the variable inside a longer text. */ private function embeds(mixed $value): bool { if (is_string($value)) { return VariableRef::contains($value) && str_contains($value, '"' . $this->id . '"'); } if (! is_array($value)) { return false; } foreach ($value as $item) { if ($this->embeds($item)) { return true; } } return false; } }