Files
WP-LIT-MCP/src/Integrations/Divi/Design/VariableDetachRewrite.php
T

168 lines
5.7 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace LIT\MCP\Integrations\Divi\Design;
use LIT\MCP\Integrations\Divi\BlockTree\Block;
use LIT\MCP\Integrations\Divi\Compose\VariableRef;
defined('ABSPATH') || exit;
/**
* Replaces the references to global variable $id in the attributes of Divi blocks by its value (lit/divi-design-detach
* with variable, ruling V9): every attribute value that is exactly one reference $variable({"type":"content",
* "value":{"name":"<id>",…}})$ 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;
}
}