Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 27 additions & 12 deletions php-transformer/src/HtmlToBlocks/Style/StyleResolutionTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,25 @@ private function inlineLayoutCarrierProperties(): array
);
}

/**
* @return list<string>
*/
private function inlineListMarkerCarrierProperties(): array
{
return array(
'list-style',
'list-style-type',
'list-style-position',
'list-style-image',
);
}

/**
* @return list<string>
*/
private function inlineGeometryProperties(): array
{
return array_merge($this->inlineLayoutCarrierProperties(), array(
return array_merge($this->inlineLayoutCarrierProperties(), $this->inlineListMarkerCarrierProperties(), array(
'width',
'height',
'min-width',
Expand Down Expand Up @@ -375,7 +388,7 @@ private function inlineGeometryClassName(
}
$rawValue = trim((string) ($declarations[$property] ?? ($forcedDeclarations[$property] ?? '')));
$value = trim(preg_replace('/\s*!\s*important\s*$/i', '', $rawValue) ?? $rawValue);
if ( in_array($property, array( 'background', 'background-image' ), true) ) {
if ( in_array($property, array( 'background', 'background-image', 'list-style', 'list-style-image' ), true) ) {
$value = CssUrlRewriter::rewrite($value, fn (string $url): string => $this->resolvedAssetImageUrl($url));
}
if ('' !== $value && ! preg_match('~[{}<>;]|/\*~', $value)) {
Expand All @@ -400,16 +413,18 @@ private function inlineGeometryClassName(
// (forced/custom-property fallbacks) sort last.
$sourceOrder = array_flip(array_keys($declarations));
uksort($geometry, static fn (string $a, string $b): int => (($sourceOrder[$a] ?? PHP_INT_MAX) <=> ($sourceOrder[$b] ?? PHP_INT_MAX)) ?: strcmp($a, $b));
$layoutDeclarations = array();
$normalPriorityDeclarations = array();
$importantDeclarations = array();
$forcedPropertyLookup = array_fill_keys($forcedProperties, true);
$inlineLayoutPropertyLookup = array_fill_keys($this->inlineLayoutCarrierProperties(), true);
$inlineListMarkerPropertyLookup = array_fill_keys($this->inlineListMarkerCarrierProperties(), true);
foreach ($geometry as $property => $value) {
if ( isset($inlineLayoutPropertyLookup[$property]) && ! isset($forcedPropertyLookup[$property]) ) {
// Preserve source inline layout over a later plain author class
// without preventing an authored !important declaration from
// retaining its normal cascade priority.
$layoutDeclarations[] = $property . ':' . $value;
if ( isset($inlineListMarkerPropertyLookup[$property])
|| ( isset($inlineLayoutPropertyLookup[$property]) && ! isset($forcedPropertyLookup[$property]) )
) {
// Preserve source inline layout and list markers over a later
// plain author class without introducing !important.
$normalPriorityDeclarations[] = $property . ':' . $value;
continue;
}

Expand All @@ -418,11 +433,11 @@ private function inlineGeometryClassName(
// rules retain their normal cascade priority through specificity.
$importantDeclarations[] = $property . ':' . $value . ' !important';
}
$signature = implode(';', array_merge($layoutDeclarations, $importantDeclarations));
$signature = implode(';', array_merge($normalPriorityDeclarations, $importantDeclarations));
$className = ($this->geometryCarrierClassAllocator ??= new GeometryCarrierClassAllocator())->allocate($this->geometryStructuralPath($element) . "\n" . $signature);
$rules = array();
if ( array() !== $layoutDeclarations ) {
$rules[] = ':root .' . $className . '{' . implode(';', $layoutDeclarations) . '}';
if ( array() !== $normalPriorityDeclarations ) {
$rules[] = ':root .' . $className . '{' . implode(';', $normalPriorityDeclarations) . '}';
}
if ( array() !== $importantDeclarations ) {
$rules[] = '.' . $className . '{' . implode(';', $importantDeclarations) . '}';
Expand Down Expand Up @@ -1758,7 +1773,7 @@ private function cssDeclarations(string $style): array
[$name, $value] = array_map('trim', explode(':', $declaration, 2));
$name = strtolower($name);
$value = preg_replace('/\s+/', ' ', $value) ?? $value;
$allowsImageUrl = in_array($name, array( 'background', 'background-image' ), true) && ! preg_match('/(?:expression\s*\(|javascript\s*:)/i', $value);
$allowsImageUrl = in_array($name, array( 'background', 'background-image', 'list-style', 'list-style-image' ), true) && ! preg_match('/(?:expression\s*\(|javascript\s*:)/i', $value);
if ( '' !== $name && '' !== $value && ( $allowsImageUrl || ! preg_match('/(?:expression\s*\(|javascript\s*:|url\s*\()/i', $value) ) ) {
$declarations[$name] = $value;
}
Expand Down
32 changes: 32 additions & 0 deletions php-transformer/tests/unit/block-style-support-conversion.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,38 @@
$assert(! str_contains($nativeGridMarkup, 'gap:1.2rem'), '16c: native Group grid markup omits inline gap that Gutenberg save does not reproduce', $nativeGridMarkup);
$assert(str_contains($nativeGridCss, 'gap:1.2rem !important'), '16d: inline native Group grid gap moves to the generated geometry carrier', $nativeGridCss);

$unorderedListSource = '<ul style="list-style:none"><li>Alpha</li></ul>';
$unorderedListResult = ( new HtmlTransformer() )->transform($unorderedListSource)->toArray();
$unorderedListAttrs = $unorderedListResult['blocks'][0]['attrs'] ?? array();
$unorderedListCss = implode("\n", array_map(static fn (array $asset): string => (string) ($asset['content'] ?? ''), is_array($unorderedListResult['assets'] ?? null) ? $unorderedListResult['assets'] : array()));
$assert(str_contains($unorderedListSource, 'list-style:none'), 'L1 precondition: unordered list fixture authors list-style:none', $unorderedListSource);
$assert(str_contains((string) ($unorderedListAttrs['className'] ?? ''), 'be-inline-geometry-') && str_contains($unorderedListCss, 'list-style:none') && ! str_contains($unorderedListCss, 'list-style:none !important'), 'L1: unordered list carries authored list-style:none without !important', $unorderedListCss);

$orderedListResult = ( new HtmlTransformer() )->transform('<ol style="list-style:none"><li>First</li></ol>')->toArray();
$orderedListAttrs = $orderedListResult['blocks'][0]['attrs'] ?? array();
$orderedListCss = implode("\n", array_map(static fn (array $asset): string => (string) ($asset['content'] ?? ''), is_array($orderedListResult['assets'] ?? null) ? $orderedListResult['assets'] : array()));
$assert(true === ($orderedListAttrs['ordered'] ?? false) && str_contains((string) ($orderedListAttrs['className'] ?? ''), 'be-inline-geometry-') && str_contains($orderedListCss, 'list-style:none') && ! str_contains($orderedListCss, 'list-style:none !important'), 'L2: ordered list carries authored list-style:none without !important', $orderedListCss);

$plainListResult = ( new HtmlTransformer() )->transform('<ul><li>Marker remains</li></ul>')->toArray();
$plainListMarkup = (string) ($plainListResult['serialized_blocks'] ?? '');
$assert(
'<!-- wp:list --><ul class="wp-block-list"><!-- wp:list-item {"content":"Marker remains"} --><li>Marker remains</li><!-- /wp:list-item --></ul><!-- /wp:list -->' === $plainListMarkup
&& array() === ($plainListResult['assets'] ?? array()),
'L3: a list without authored list-style keeps canonical marker-rendering output and gains no carrier',
$plainListMarkup
);

$listLonghandsResult = ( new HtmlTransformer() )->transform('<ul style="list-style-type:square;list-style-position:inside;list-style-image:url(https://example.com/marker.svg)"><li>Detailed marker</li></ul>')->toArray();
$listLonghandsCss = implode("\n", array_map(static fn (array $asset): string => (string) ($asset['content'] ?? ''), is_array($listLonghandsResult['assets'] ?? null) ? $listLonghandsResult['assets'] : array()));
$assert(
str_contains($listLonghandsCss, 'list-style-type:square')
&& str_contains($listLonghandsCss, 'list-style-position:inside')
&& str_contains($listLonghandsCss, 'list-style-image:url(https://example.com/marker.svg)')
&& ! str_contains($listLonghandsCss, '!important'),
'list-style longhands ride the generated carrier without !important',
$listLonghandsCss
);

$cardHtml = '<section class="pricing-shell" style="max-width:1120px;margin:0 auto;padding:5rem 2rem"><article class="pricing-card" style="max-width:360px;padding:2rem;background:#fff"><h2>Team</h2><p>Scale every launch.</p></article></section>';
$cardResult = ( new HtmlTransformer() )->transform($cardHtml, array())->toArray();
$cardShell = $cardResult['blocks'][0] ?? array();
Expand Down
Loading