diff --git a/php-transformer/src/HtmlToBlocks/BlockFactory.php b/php-transformer/src/HtmlToBlocks/BlockFactory.php index 0d57dd23..ae1ed02b 100644 --- a/php-transformer/src/HtmlToBlocks/BlockFactory.php +++ b/php-transformer/src/HtmlToBlocks/BlockFactory.php @@ -64,9 +64,9 @@ private function normalizeAttrsForBlock(string $name, array $attrs): array { $attrs = $this->normalizeClassNameAttr($attrs); - // RichText block save() does not reproduce dimensions.maxWidth. Inline + // These core save functions do not reproduce dimensions.maxWidth. Inline // max-width is retained by the generated geometry carrier stylesheet. - if ( in_array($name, array( 'core/group', 'core/columns', 'core/list-item', 'core/paragraph' ), true) ) { + if ( in_array($name, array( 'core/group', 'core/columns', 'core/list-item', 'core/paragraph', 'core/separator' ), true) ) { unset($attrs['style']['dimensions']['maxWidth']); if ( empty($attrs['style']['dimensions']) ) { unset($attrs['style']['dimensions']); @@ -76,6 +76,19 @@ private function normalizeAttrsForBlock(string $name, array $attrs): array } } + if ( 'core/separator' === $name ) { + unset($attrs['style']['spacing']['margin']['left'], $attrs['style']['spacing']['margin']['right']); + if ( empty($attrs['style']['spacing']['margin']) ) { + unset($attrs['style']['spacing']['margin']); + } + if ( empty($attrs['style']['spacing']) ) { + unset($attrs['style']['spacing']); + } + if ( empty($attrs['style']) ) { + unset($attrs['style']); + } + } + if ( in_array($name, array( 'core/buttons', 'core/column', 'core/columns', 'core/group', 'core/heading', 'core/list', 'core/list-item', 'core/paragraph' ), true) ) { unset($attrs['style']['spacing']['blockGap']); if ( empty($attrs['style']['spacing']) ) { diff --git a/php-transformer/src/HtmlToBlocks/HtmlTransformer.php b/php-transformer/src/HtmlToBlocks/HtmlTransformer.php index 8b98a28a..e967ae8e 100644 --- a/php-transformer/src/HtmlToBlocks/HtmlTransformer.php +++ b/php-transformer/src/HtmlToBlocks/HtmlTransformer.php @@ -2094,7 +2094,7 @@ private function convertElement(DOMElement $element, array &$fallbacks, bool $ca } if ( 'hr' === $tagName ) { - return $this->createBlock('core/separator', $this->presentationAttributes($element), array(), $element); + return $this->createBlock('core/separator', $this->presentationAttributes($element, array(), array( 'margin-left', 'margin-right' )), array(), $element); } if ( 'br' === $tagName ) { diff --git a/php-transformer/src/HtmlToBlocks/Style/StyleResolutionTrait.php b/php-transformer/src/HtmlToBlocks/Style/StyleResolutionTrait.php index c245bccc..8b04041e 100644 --- a/php-transformer/src/HtmlToBlocks/Style/StyleResolutionTrait.php +++ b/php-transformer/src/HtmlToBlocks/Style/StyleResolutionTrait.php @@ -108,9 +108,9 @@ private function highValueStyleBoundaryPolicy(): HighValueStyleBoundaryPolicy * * @return array */ - private function presentationAttributes(DOMElement $element, array $excludedGeometryProperties = array()): array + private function presentationAttributes(DOMElement $element, array $excludedGeometryProperties = array(), array $forcedGeometryProperties = array()): array { - $cacheKey = $this->presentationCacheKey($element) . ':' . implode(',', $excludedGeometryProperties); + $cacheKey = $this->presentationCacheKey($element) . ':' . implode(',', $excludedGeometryProperties) . ':' . implode(',', $forcedGeometryProperties); if ( isset($this->presentationAttributesCache[$cacheKey]) ) { return $this->presentationAttributesCache[$cacheKey]; } @@ -120,12 +120,15 @@ private function presentationAttributes(DOMElement $element, array $excludedGeom $this->presentationDeclarations($element) ); $mapped = $this->styleAttributeMapper()->map($declarations); + $forcedGeometryDeclarations = array() === $forcedGeometryProperties + ? array() + : $this->cssDeclarations((string) ($this->styleAttributeMapper()->serialize($mapped['style'] ?? array())['style'] ?? '')); $attrs = array_filter(array_merge($mapped['attrs'] ?? array(), array( 'anchor' => $this->safeAnchor($this->attr($element, 'id')), 'className' => $this->mergePresentationClassNames( $this->promotedClassName($this->attr($element, 'class')), - $this->inlineGeometryClassName($element, $excludedGeometryProperties) + $this->inlineGeometryClassName($element, $excludedGeometryProperties, $forcedGeometryProperties, $forcedGeometryDeclarations) ), 'inlineGeometryStyle' => $this->inlineGeometryStyle($element, $excludedGeometryProperties), 'style' => $mapped['style'], @@ -230,15 +233,15 @@ private function hasConditionalStyleFamily(DOMElement $element, string $family): * inline geometry in a generated stylesheet; class-owned declarations are * already retained by author stylesheet materialization. */ - private function inlineGeometryClassName(DOMElement $element, array $excludedProperties = array()): string + private function inlineGeometryClassName(DOMElement $element, array $excludedProperties = array(), array $forcedProperties = array(), array $forcedDeclarations = array()): string { $declarations = $this->cssDeclarations($this->attr($element, 'style')); $geometry = array(); - foreach ($this->inlineGeometryProperties() as $property) { + foreach (array_values(array_unique(array_merge($this->inlineGeometryProperties(), $forcedProperties))) as $property) { if (in_array($property, $excludedProperties, true)) { continue; } - $rawValue = trim((string) ($declarations[$property] ?? '')); + $rawValue = trim((string) ($declarations[$property] ?? ($forcedDeclarations[$property] ?? ''))); if (1 === preg_match('/\s*!important\s*$/i', $rawValue)) { continue; } diff --git a/php-transformer/tests/contract/run.php b/php-transformer/tests/contract/run.php index 0ef48f8d..4d67d2bf 100644 --- a/php-transformer/tests/contract/run.php +++ b/php-transformer/tests/contract/run.php @@ -738,6 +738,13 @@ public function match(DOMElement $element, PatternContext $context): ?array $assert(1 === substr_count($separatorMarkup, 'has-css-opacity'), 'separator serialization emits has-css-opacity exactly once'); $assert(! str_contains($separatorMarkup, 'wp-block-separator divider wp-block-separator'), 'separator serialization does not duplicate generated classes'); +$boundedSeparator = ( new HtmlTransformer() )->transform('

')->toArray(); +$boundedSeparatorAttrs = $boundedSeparator['blocks'][0]['attrs'] ?? array(); +$boundedSeparatorMarkup = (string) ($boundedSeparator['serialized_blocks'] ?? ''); +$boundedSeparatorCss = implode("\n", array_map(static fn (array $asset): string => (string) ($asset['content'] ?? ''), $boundedSeparator['assets'] ?? array())); +$assert(array('top' => '0', 'bottom' => '0') === ($boundedSeparatorAttrs['style']['spacing']['margin'] ?? null) && ! isset($boundedSeparatorAttrs['style']['dimensions']), 'separator keeps only spacing supported by its canonical core block attributes'); +$assert(! str_contains($boundedSeparatorMarkup, 'margin-left:auto') && ! str_contains($boundedSeparatorMarkup, 'max-width:var(--max)') && str_contains($boundedSeparatorCss, 'margin-left:auto !important') && str_contains($boundedSeparatorCss, 'margin-right:auto !important') && str_contains($boundedSeparatorCss, 'max-width:var(--max) !important'), 'separator moves unsupported horizontal and width geometry to its generated carrier stylesheet'); + $customStateFindings = ( new CanonicalSaveShapeValidator() )->findings(array(array( 'blockName' => 'core/group', 'attrs' => array(),