diff --git a/php-transformer/src/HtmlToBlocks/HtmlTransformer.php b/php-transformer/src/HtmlToBlocks/HtmlTransformer.php index 37ab88a2..c493effd 100644 --- a/php-transformer/src/HtmlToBlocks/HtmlTransformer.php +++ b/php-transformer/src/HtmlToBlocks/HtmlTransformer.php @@ -422,7 +422,6 @@ private function createStyleResolutionContext(): StyleResolutionContext fn (string $value): string => $this->cssComparableValue($value), fn (string $selector): array => $this->parsedCssSelector($selector), fn (string $className): string => $this->promotedClassName($className), - fn (string $value, ?DOMElement $element = null): string => $this->resolveCssVariablesInValue($value, $element), fn (string $url): string => $this->resolvedAssetImageUrl($url), fn (string $id): string => $this->safeAnchor($id) ); @@ -3519,7 +3518,7 @@ function (DOMElement $sourceElement, array $excludedTags): PatternConversionResu new NavigationPatternContext( $includeRuntimeDomTarget ? fn (DOMElement $sourceElement): bool => $this->runtimeIslands->isRuntimeDomTarget($sourceElement) : null, fn (DOMElement $item, DOMElement $anchor): string => $this->navigationUnderlineColor($item, $anchor), - fn (DOMElement $sourceElement): string => $this->resolveCssVariablesInValue($this->styleResolver->specificityResolvedPresentationStyle($sourceElement)), + fn (DOMElement $sourceElement): string => $this->styleResolver->resolveCssVariablesInValue($this->styleResolver->specificityResolvedPresentationStyle($sourceElement)), fn (DOMElement $sourceElement): array => $this->navigationStyleProjector->navigationColorInteractionStates($sourceElement), fn (DOMElement $sourceElement): string => $this->navigationToggleSuppressor->navigationOverlayMenu($sourceElement) ), @@ -3539,7 +3538,7 @@ function (DOMElement $sourceElement, array $excludedTags): PatternConversionResu ), new ButtonPatternContext( fn (DOMElement $anchor): ?array => $this->fileBlockFromAnchor($anchor), - fn (DOMElement $sourceElement): string => $this->resolveCssVariablesInValue($this->styleResolver->specificityResolvedPresentationStyle($sourceElement)), + fn (DOMElement $sourceElement): string => $this->styleResolver->resolveCssVariablesInValue($this->styleResolver->specificityResolvedPresentationStyle($sourceElement)), fn (DOMElement $sourceElement): string => $this->richTextContentWithMaterializedInlineStyles($sourceElement), fn (DOMElement $sourceElement, string $content): ?string => $this->richTextContentWithMaterializedSvgImages($sourceElement, $content), fn (DOMElement $sourceElement, string $name): string => $this->attr($sourceElement, $name), @@ -3606,7 +3605,7 @@ private function createProbePatternContext(): PatternContext navigationContext: new NavigationPatternContext( null, fn (DOMElement $item, DOMElement $anchor): string => $this->navigationUnderlineColor($item, $anchor), - fn (DOMElement $sourceElement): string => $this->resolveCssVariablesInValue($this->styleResolver->specificityResolvedPresentationStyle($sourceElement)) + fn (DOMElement $sourceElement): string => $this->styleResolver->resolveCssVariablesInValue($this->styleResolver->specificityResolvedPresentationStyle($sourceElement)) ), markupContext: new MarkupPatternContext( fn (DOMElement $sourceElement): string => $this->safeFallbackHtml($sourceElement), @@ -4483,12 +4482,12 @@ private function applySyntheticHeaderAnchorCarrier(array &$attrs, DOMElement $an foreach ( array( 'display', 'align-items', 'justify-content' ) as $property ) { $value = trim((string) ($direct[$property] ?? '')); if ( '' !== $value && ! str_contains(strtolower($value), '!important') ) { - $declarations[$property] = $this->resolveCssVariablesInValue($value); + $declarations[$property] = $this->styleResolver->resolveCssVariablesInValue($value); } } foreach ( $this->styleResolver->specificityResolvedGapDeclarations($anchor) as $property => $value ) { if ( ! str_contains(strtolower($value), '!important') ) { - $declarations[$property] = $this->resolveCssVariablesInValue($value); + $declarations[$property] = $this->styleResolver->resolveCssVariablesInValue($value); } } if ( array() === $declarations ) { @@ -4792,7 +4791,7 @@ private function sourceAnchorHasNoTextDecoration(DOMElement $anchor): bool { $decorationLine = null; foreach ( $this->styleResolver->cssDeclarations($this->styleResolver->mergedPresentationStyle($anchor)) as $property => $value ) { - $value = $this->cssComparableValue($this->resolveCssVariablesInValue($value)); + $value = $this->cssComparableValue($this->styleResolver->resolveCssVariablesInValue($value)); if ( 'text-decoration' === $property ) { if ( preg_match('/\b(?:underline|overline|line-through)\b/', $value) ) { $decorationLine = 'line'; @@ -6981,7 +6980,7 @@ private function hasRenderableEmptyBlockBox(DOMElement $element): bool { $declarations = $this->styleResolver->structuralPresentationDeclarations($element); foreach ( array( 'height', 'min-height', 'padding', 'padding-top', 'padding-right', 'padding-bottom', 'padding-left' ) as $property ) { - if ( isset($declarations[$property]) && $this->isPositiveCssLength($this->resolveCssVariablesInValue($declarations[$property], $element)) ) { + if ( isset($declarations[$property]) && $this->isPositiveCssLength($this->styleResolver->resolveCssVariablesInValue($declarations[$property], $element)) ) { return true; } } @@ -7162,8 +7161,8 @@ private function emptyVisualSpacerBlock(DOMElement $element): array if ( array() !== $paint['style'] ) { $attrs['style'] = array_replace_recursive($attrs['style'] ?? array(), $paint['style']); } - $attrs['height'] = $this->resolveCssVariablesInValue($declarations['height']); - $attrs['width'] = $this->resolveCssVariablesInValue($declarations['width']); + $attrs['height'] = $this->styleResolver->resolveCssVariablesInValue($declarations['height']); + $attrs['width'] = $this->styleResolver->resolveCssVariablesInValue($declarations['width']); return $this->createBlock('core/spacer', $attrs, array(), $element); } @@ -7200,7 +7199,7 @@ private function flankedSeparatorBlockFromElement(DOMElement $element): ?array } $height = SpacerPattern::heightFromStyle($this->attr($flank, 'style')); - if ( '' === $height || ! $this->isPositiveCssLength($this->resolveCssVariablesInValue($height, $flank)) ) { + if ( '' === $height || ! $this->isPositiveCssLength($this->styleResolver->resolveCssVariablesInValue($height, $flank)) ) { return null; } $margins[ $side ] = $height; @@ -7227,7 +7226,7 @@ private function isEmptyVisualInlineCandidate(DOMElement $element): bool private function hasExplicitEmptyVisualDimensions(array $declarations): bool { foreach ( array( 'width', 'height' ) as $property ) { - if ( ! isset($declarations[$property]) || ! $this->isPositiveCssLength($this->resolveCssVariablesInValue($declarations[$property])) ) { + if ( ! isset($declarations[$property]) || ! $this->isPositiveCssLength($this->styleResolver->resolveCssVariablesInValue($declarations[$property])) ) { return false; } } @@ -7248,20 +7247,20 @@ private function isPositiveCssLength(string $value): bool private function hasVisibleEmptyVisualPaint(array $declarations, ?DOMElement $element = null): bool { foreach ( array( 'background', 'background-color', 'box-shadow', 'outline' ) as $property ) { - if ( isset($declarations[$property]) && $this->isVisibleEmptyVisualPaint($this->resolveCssVariablesInValue($declarations[$property], $element)) ) { + if ( isset($declarations[$property]) && $this->isVisibleEmptyVisualPaint($this->styleResolver->resolveCssVariablesInValue($declarations[$property], $element)) ) { return true; } } foreach ( array( 'border', 'border-top', 'border-right', 'border-bottom', 'border-left' ) as $property ) { - if ( isset($declarations[$property]) && $this->isVisibleEmptyVisualBorder($this->resolveCssVariablesInValue($declarations[$property], $element)) ) { + if ( isset($declarations[$property]) && $this->isVisibleEmptyVisualBorder($this->styleResolver->resolveCssVariablesInValue($declarations[$property], $element)) ) { return true; } } return isset($declarations['border-color'], $declarations['border-width']) - && $this->isVisibleEmptyVisualPaint($this->resolveCssVariablesInValue($declarations['border-color'], $element)) - && $this->isPositiveCssLength($this->resolveCssVariablesInValue($declarations['border-width'], $element)); + && $this->isVisibleEmptyVisualPaint($this->styleResolver->resolveCssVariablesInValue($declarations['border-color'], $element)) + && $this->isPositiveCssLength($this->styleResolver->resolveCssVariablesInValue($declarations['border-width'], $element)); } private function isVisibleEmptyVisualPaint(string $value): bool @@ -9414,7 +9413,7 @@ private function shouldPreserveEmptyVisualFigure(DOMElement $figure): bool $declarations = $this->styleResolver->structuralPresentationDeclarations($figure); $hasBoundedHeight = false; foreach ( array( 'height', 'min-height' ) as $property ) { - if ( isset($declarations[$property]) && $this->isPositiveCssLength($this->resolveCssVariablesInValue($declarations[$property], $figure)) ) { + if ( isset($declarations[$property]) && $this->isPositiveCssLength($this->styleResolver->resolveCssVariablesInValue($declarations[$property], $figure)) ) { $hasBoundedHeight = true; break; } diff --git a/php-transformer/src/HtmlToBlocks/Style/StyleResolutionContext.php b/php-transformer/src/HtmlToBlocks/Style/StyleResolutionContext.php index a2209b79..164fb99d 100644 --- a/php-transformer/src/HtmlToBlocks/Style/StyleResolutionContext.php +++ b/php-transformer/src/HtmlToBlocks/Style/StyleResolutionContext.php @@ -30,7 +30,6 @@ final class StyleResolutionContext * @param Closure(string): string $cssComparableValue * @param Closure(string): array $parsedCssSelector * @param Closure(string): string $promotedClassName - * @param Closure(string, ?DOMElement): string $resolveCssVariablesInValue * @param Closure(string): string $resolvedAssetImageUrl * @param Closure(string): string $safeAnchor */ @@ -47,7 +46,6 @@ public function __construct( private readonly Closure $cssComparableValue, private readonly Closure $parsedCssSelector, private readonly Closure $promotedClassName, - private readonly Closure $resolveCssVariablesInValue, private readonly Closure $resolvedAssetImageUrl, private readonly Closure $safeAnchor ) { @@ -116,11 +114,6 @@ public function promotedClassName(string $className): string return ($this->promotedClassName)($className); } - public function resolveCssVariablesInValue(string $value, ?DOMElement $element = null): string - { - return ($this->resolveCssVariablesInValue)($value, $element); - } - public function resolvedAssetImageUrl(string $url): string { return ($this->resolvedAssetImageUrl)($url); diff --git a/php-transformer/src/HtmlToBlocks/Style/StyleResolver.php b/php-transformer/src/HtmlToBlocks/Style/StyleResolver.php index 9928326b..676c5f5d 100644 --- a/php-transformer/src/HtmlToBlocks/Style/StyleResolver.php +++ b/php-transformer/src/HtmlToBlocks/Style/StyleResolver.php @@ -295,7 +295,7 @@ private function resolvedPresentationAttributes( $declarations = $this->classOwnedBackgroundPaintDeclarations($element, $declarations); $mapped = $this->styleAttributeMapper()->map( $declarations, - fn (string $value): string => $this->context->resolveCssVariablesInValue($value, $element) + fn (string $value): string => $this->resolveCssVariablesInValue($value, $element) ); $forcedGeometryDeclarations = array() === $forcedGeometryProperties ? array() @@ -762,7 +762,7 @@ private function inlineAuthorOverrideDeclarations( ): array { $candidates = $this->styleAttributeMapper()->map( $inlineDeclarations, - fn (string $value): string => $this->context->resolveCssVariablesInValue($value, $element) + fn (string $value): string => $this->resolveCssVariablesInValue($value, $element) )['leftover'] ?? array(); if ( isset($inlineDeclarations['box-shadow']) ) { $candidates['box-shadow'] = $inlineDeclarations['box-shadow']; @@ -1884,7 +1884,7 @@ public function authoredInheritedPropertyWinner(DOMElement $element, string $pro return ''; } - return $this->context->resolveCssVariablesInValue($value); + return $this->resolveCssVariablesInValue($value); } return ''; @@ -2824,4 +2824,52 @@ private function authorClassTokens(DOMElement $element): string return implode(' ', array_filter($tokens, static fn (string $token): bool => '' !== $token && ! GeneratedGutenbergClassPolicy::isGeneratedClassName($token) && ! self::isTransformerMarkerClassName($token))); } + + /** + * Expand `var(--token)` references against source custom properties, with + * ancestor-declared properties layered over them when an element is given. + * + * Lives here rather than beside SVG materialization because it is CSS + * custom-property resolution and already depends on this resolver's own + * structural declarations. + */ + public function resolveCssVariablesInValue(string $value, ?DOMElement $element = null): string + { + if ( false === strpos($value, 'var(') ) { + return $value; + } + + $customProperties = $this->context->sourceStyles()->customProperties(); + if ( $element instanceof DOMElement ) { + $ancestors = array(); + for ( $current = $element; $current instanceof DOMElement; $current = $current->parentNode instanceof DOMElement ? $current->parentNode : null ) { + $ancestors[] = $current; + } + foreach ( array_reverse($ancestors) as $ancestor ) { + foreach ( $this->structuralPresentationDeclarations($ancestor) as $name => $propertyValue ) { + if ( str_starts_with($name, '--') ) { + $customProperties[$name] = $propertyValue; + } + } + } + } + + for ( $pass = 0; $pass < 5; ++$pass ) { + $expanded = preg_replace_callback('/var\(\s*(--[A-Za-z0-9_-]+)\s*(?:,\s*([^()]*))?\)/', static function (array $matches) use ($customProperties): string { + $name = (string) $matches[1]; + if ( isset($customProperties[$name]) && '' !== $customProperties[$name] ) { + return $customProperties[$name]; + } + + return isset($matches[2]) && '' !== trim((string) $matches[2]) ? trim((string) $matches[2]) : (string) $matches[0]; + }, $value); + + if ( ! is_string($expanded) || $expanded === $value ) { + break; + } + $value = $expanded; + } + + return trim($value); + } } diff --git a/php-transformer/src/HtmlToBlocks/Support/SvgMaterializationTrait.php b/php-transformer/src/HtmlToBlocks/Support/SvgMaterializationTrait.php index d8e03807..71e7727b 100644 --- a/php-transformer/src/HtmlToBlocks/Support/SvgMaterializationTrait.php +++ b/php-transformer/src/HtmlToBlocks/Support/SvgMaterializationTrait.php @@ -482,7 +482,7 @@ private function ensureSvgImageNamespace(string $html): string private function resolveMaterializedSvgColors(string $html, DOMElement $element): string { - $html = $this->resolveCssVariablesInValue($html); + $html = $this->styleResolver->resolveCssVariablesInValue($html); if ( false === stripos($html, 'currentColor') ) { return $html; } @@ -498,7 +498,7 @@ private function inheritedSvgColor(DOMElement $element): string continue; } - $color = $this->resolveCssVariablesInValue(trim((string) $declarations['color'])); + $color = $this->styleResolver->resolveCssVariablesInValue(trim((string) $declarations['color'])); if ( '' !== $color && ! preg_match('/\bcurrentColor\b|var\s*\(|[<>]/i', $color) ) { return $color; } @@ -507,45 +507,6 @@ private function inheritedSvgColor(DOMElement $element): string return '#000000'; } - private function resolveCssVariablesInValue(string $value, ?DOMElement $element = null): string - { - if ( false === strpos($value, 'var(') ) { - return $value; - } - - $customProperties = $this->sourceStyles()->customProperties(); - if ( $element instanceof DOMElement ) { - $ancestors = array(); - for ( $current = $element; $current instanceof DOMElement; $current = $current->parentNode instanceof DOMElement ? $current->parentNode : null ) { - $ancestors[] = $current; - } - foreach ( array_reverse($ancestors) as $ancestor ) { - foreach ( $this->styleResolver->structuralPresentationDeclarations($ancestor) as $name => $propertyValue ) { - if ( str_starts_with($name, '--') ) { - $customProperties[$name] = $propertyValue; - } - } - } - } - - for ( $pass = 0; $pass < 5; ++$pass ) { - $expanded = preg_replace_callback('/var\(\s*(--[A-Za-z0-9_-]+)\s*(?:,\s*([^()]*))?\)/', static function (array $matches) use ($customProperties): string { - $name = (string) $matches[1]; - if ( isset($customProperties[$name]) && '' !== $customProperties[$name] ) { - return $customProperties[$name]; - } - - return isset($matches[2]) && '' !== trim((string) $matches[2]) ? trim((string) $matches[2]) : (string) $matches[0]; - }, $value); - - if ( ! is_string($expanded) || $expanded === $value ) { - break; - } - $value = $expanded; - } - - return trim($value); - } /** * @return array