diff --git a/php-transformer/composer.json b/php-transformer/composer.json index 0051bd6d9..e7204676e 100644 --- a/php-transformer/composer.json +++ b/php-transformer/composer.json @@ -457,6 +457,7 @@ "php tests/unit/color-scheme-variant.php", "php tests/unit/color-scheme-projection.php", "php tests/unit/background-image-extractor.php", + "php tests/unit/repeating-background-layer.php", "php tests/unit/empty-layout-background-carrier.php", "php tests/unit/empty-span-spacer-selector-carrier.php", "php tests/unit/empty-definite-grid-cells.php", diff --git a/php-transformer/src/HtmlToBlocks/Elements/FlowContainerElementConverter.php b/php-transformer/src/HtmlToBlocks/Elements/FlowContainerElementConverter.php index c41161ae8..fb9537e2b 100644 --- a/php-transformer/src/HtmlToBlocks/Elements/FlowContainerElementConverter.php +++ b/php-transformer/src/HtmlToBlocks/Elements/FlowContainerElementConverter.php @@ -309,6 +309,10 @@ private function lower(DOMElement $element, string $tagName, array &$fallbacks): if ( array() === $children && ! $this->context->hasDirectMediaChild($element) ) { $backgroundImage = $this->context->backgroundImageBlock($element); if ( null !== $backgroundImage ) { + // A background group already owns the source element's box. + if ( 'core/group' === ($backgroundImage['blockName'] ?? '') ) { + return ConversionOutcome::handled($backgroundImage); + } $children[] = $backgroundImage; } } diff --git a/php-transformer/src/HtmlToBlocks/HtmlCompilation.php b/php-transformer/src/HtmlToBlocks/HtmlCompilation.php index 072d220e7..5bc10306e 100644 --- a/php-transformer/src/HtmlToBlocks/HtmlCompilation.php +++ b/php-transformer/src/HtmlToBlocks/HtmlCompilation.php @@ -137,6 +137,7 @@ use Automattic\BlocksEngine\PhpTransformer\HtmlToBlocks\Patterns\DetailsPattern; use Automattic\BlocksEngine\PhpTransformer\HtmlToBlocks\Patterns\ColumnsPatternContext; use Automattic\BlocksEngine\PhpTransformer\HtmlToBlocks\Patterns\CommerceStructureRecognizer; +use Automattic\BlocksEngine\PhpTransformer\HtmlToBlocks\Patterns\CoverStyleResolver; use Automattic\BlocksEngine\PhpTransformer\HtmlToBlocks\Patterns\GalleryPattern; use Automattic\BlocksEngine\PhpTransformer\HtmlToBlocks\Patterns\GalleryPatternContext; use Automattic\BlocksEngine\PhpTransformer\HtmlToBlocks\Patterns\LogoPatternContext; @@ -9143,6 +9144,19 @@ private function backgroundImageBlockFromElement(DOMElement $element): ?array return null; } + if ( (new CoverStyleResolver())->hasRepeatingBackground($this->styleResolver->mergedPresentationStyle($element)) ) { + $attrs = $this->styleResolver->presentationAttributes($element); + $attrs['style']['background'] = array_filter(array( + 'backgroundImage' => array( 'url' => $this->resolvedAssetImageUrl($url) ), + 'backgroundRepeat' => $declarations['background-repeat'] ?? 'repeat', + // Core defaults a missing size to cover, not CSS's initial auto. + 'backgroundSize' => $declarations['background-size'] ?? 'auto', + 'backgroundPosition' => $declarations['background-position'] ?? '', + ), static fn ($value): bool => '' !== $value); + + return $this->createBlock('core/group', $attrs, array(), $element); + } + $width = trim((string) ($declarations['width'] ?? '')); $height = trim((string) ($declarations['height'] ?? '')); $scale = strtolower(trim((string) ($declarations['background-size'] ?? ''))); diff --git a/php-transformer/tests/unit/repeating-background-layer.php b/php-transformer/tests/unit/repeating-background-layer.php new file mode 100644 index 000000000..915b1d46c --- /dev/null +++ b/php-transformer/tests/unit/repeating-background-layer.php @@ -0,0 +1,58 @@ +Tile' + . '

Answer every call

'; +}; + +foreach ( array( + array( 'background-repeat:repeat', 'repeat', 'auto', null ), + array( 'background-repeat:repeat;background-size:20px 30px;background-position:left top', 'repeat', '20px 30px', 'left top' ), + array( 'background-repeat:repeat-x;background-size:contain', 'repeat-x', 'contain', null ), + array( 'background: url(tile.png) repeat', 'repeat', 'auto', null ), +) as [$declarations, $repeat, $size, $position] ) { + $result = (new HtmlTransformer())->transform($fixture($declarations))->toArray(); + $markup = (string) ($result['serialized_blocks'] ?? ''); + $assert(! str_contains($markup, '/', $markup, $matches); + $layers = array(); + foreach ( $matches[1] as $json ) { + $attrs = json_decode($json, true, 512, JSON_THROW_ON_ERROR); + if ( isset($attrs['style']['background']['backgroundImage']['url']) ) { + $layers[] = $attrs; + } + } + $assert(1 === count($layers), $declarations . ': exactly one group owns the background.'); + $attrs = $layers[0] ?? array(); + $background = $attrs['style']['background'] ?? array(); + $assert('tile.png' === ($background['backgroundImage']['url'] ?? null), 'The layer keeps the asset URL.'); + $assert($repeat === ($background['backgroundRepeat'] ?? null), 'The layer keeps the repeat.'); + $assert($size === ($background['backgroundSize'] ?? null), 'The layer uses authored size or explicit CSS initial auto.'); + if ( null !== $position ) { + $assert($position === ($background['backgroundPosition'] ?? null), 'The layer keeps the authored position.'); + } + $assert(str_contains((string) ($attrs['className'] ?? ''), 't'), 'The background group itself carries source presentation.'); + $assert(! preg_match('/\s*]*class="[^"]*\bt\b[^>]*>\s*