From 1b194eea0654edd7ebcc8cfa7c3ad0460bf6c8ee Mon Sep 17 00:00:00 2001 From: Chris Huber Date: Mon, 6 Jul 2026 19:46:08 -0400 Subject: [PATCH] Fix text color support class validity --- .../src/HtmlToBlocks/BlockFactory.php | 51 +++++++++++++++++++ .../Style/StyleAttributeMapper.php | 9 +++- .../unit/block-style-support-conversion.php | 29 ++++++++--- 3 files changed, 79 insertions(+), 10 deletions(-) diff --git a/php-transformer/src/HtmlToBlocks/BlockFactory.php b/php-transformer/src/HtmlToBlocks/BlockFactory.php index 3f160525..269425a8 100644 --- a/php-transformer/src/HtmlToBlocks/BlockFactory.php +++ b/php-transformer/src/HtmlToBlocks/BlockFactory.php @@ -62,6 +62,8 @@ public function create(string $name, array $attrs = array(), array $innerBlocks */ private function normalizeAttrsForBlock(string $name, array $attrs): array { + $attrs = $this->normalizeClassNameForBlockSupports($attrs); + if ( in_array($name, array( 'core/group', 'core/columns' ), true) ) { unset($attrs['style']['dimensions']['maxWidth']); if ( empty($attrs['style']['dimensions']) ) { @@ -85,6 +87,55 @@ private function normalizeAttrsForBlock(string $name, array $attrs): array return $attrs; } + /** + * @param array $attrs + * @return array + */ + private function normalizeClassNameForBlockSupports(array $attrs): array + { + if ( empty($attrs['className']) || ! is_string($attrs['className']) ) { + return $attrs; + } + + $generated = array(); + $textColor = $this->safeSlug((string) ($attrs['textColor'] ?? '')); + if ( '' !== $textColor ) { + $generated[] = 'has-' . $textColor . '-color'; + $generated[] = 'has-text-color'; + } + + $backgroundColor = $this->safeSlug((string) ($attrs['backgroundColor'] ?? '')); + if ( '' !== $backgroundColor ) { + $generated[] = 'has-' . $backgroundColor . '-background-color'; + $generated[] = 'has-background'; + } + + $style = is_array($attrs['style'] ?? null) ? $attrs['style'] : array(); + $color = is_array($style['color'] ?? null) ? $style['color'] : array(); + if ( '' !== trim((string) ($color['text'] ?? '')) ) { + $generated[] = 'has-text-color'; + } + if ( '' !== trim((string) ($color['background'] ?? '')) || '' !== trim((string) ($color['gradient'] ?? '')) ) { + $generated[] = 'has-background'; + } + + if ( array() === $generated ) { + return $attrs; + } + + $generated = array_values(array_unique($generated)); + $classes = preg_split('/\s+/', trim($attrs['className'])) ?: array(); + $classes = array_values(array_filter($classes, static fn (string $className): bool => '' !== $className && ! in_array($className, $generated, true))); + + if ( array() === $classes ) { + unset($attrs['className']); + } else { + $attrs['className'] = implode(' ', array_values(array_unique($classes))); + } + + return $attrs; + } + /** * @param array $attrs * @return array diff --git a/php-transformer/src/HtmlToBlocks/Style/StyleAttributeMapper.php b/php-transformer/src/HtmlToBlocks/Style/StyleAttributeMapper.php index d812d0d1..da4f4613 100644 --- a/php-transformer/src/HtmlToBlocks/Style/StyleAttributeMapper.php +++ b/php-transformer/src/HtmlToBlocks/Style/StyleAttributeMapper.php @@ -291,15 +291,20 @@ private function presetColorSlug(string $value): string { $value = trim($value); if ( preg_match('/^var:preset\|color\|([a-z0-9_-]+)$/i', $value, $match) ) { - return strtolower($match[1]); + return $this->safePresetColorSlug(strtolower($match[1])); } if ( preg_match('/^var\(\s*--wp--preset--color--([a-z0-9_-]+)\s*\)$/i', $value, $match) ) { - return strtolower($match[1]); + return $this->safePresetColorSlug(strtolower($match[1])); } return ''; } + private function safePresetColorSlug(string $slug): string + { + return 'text' === $slug ? '' : $slug; + } + /** * @param array $declarations * @param array $consumed diff --git a/php-transformer/tests/unit/block-style-support-conversion.php b/php-transformer/tests/unit/block-style-support-conversion.php index 76d10ad9..470f58f4 100644 --- a/php-transformer/tests/unit/block-style-support-conversion.php +++ b/php-transformer/tests/unit/block-style-support-conversion.php @@ -101,9 +101,22 @@ $paragraphColorHtml = '

Steeped daily.

'; $paragraphColorResult = ( new HtmlTransformer() )->transform($paragraphColorHtml, array())->toArray(); $paragraphColorMarkup = (string) ($paragraphColorResult['serialized_blocks'] ?? ''); +$paragraphColorAttrs = $paragraphColorResult['blocks'][0]['attrs'] ?? array(); $assert(str_contains($paragraphColorMarkup, '

Steeped daily.

'), '32: paragraph preset text color emits one has-text-color token plus source class', $paragraphColorMarkup); $assert(! str_contains($paragraphColorMarkup, 'has-text-color has-text-color'), '33: paragraph color support never duplicates has-text-color', $paragraphColorMarkup); +$assert('hero-tagline' === ($paragraphColorAttrs['className'] ?? ''), '34: paragraph className excludes generated color support classes', json_encode($paragraphColorAttrs)); + +$textPresetHtml = '

I am a cognitive neuroscientist.

'; +$textPresetResult = ( new HtmlTransformer() )->transform($textPresetHtml, array())->toArray(); +$textPresetBlock = $textPresetResult['blocks'][0] ?? array(); +$textPresetAttrs = is_array($textPresetBlock['attrs'] ?? null) ? $textPresetBlock['attrs'] : array(); +$textPresetInnerHtml = (string) ($textPresetBlock['innerHTML'] ?? ''); + +$assert(! isset($textPresetAttrs['textColor']), '35: preset text slug stays custom color to avoid duplicate has-text-color classes', json_encode($textPresetAttrs)); +$assert('var(--wp--preset--color--text)' === ($textPresetAttrs['style']['color']['text'] ?? ''), '36: preset text color value remains visually preserved', json_encode($textPresetAttrs['style'] ?? array())); +$assert('masthead__bio' === ($textPresetAttrs['className'] ?? ''), '37: source paragraph class remains preserved without generated color class leakage', json_encode($textPresetAttrs)); +$assert(! str_contains($textPresetInnerHtml, 'has-text-color has-text-color'), '38: rendered paragraph does not carry duplicate generated text color classes', $textPresetInnerHtml); $paintCss = '.pricing-card{background:radial-gradient(circle at 20% 10%,rgba(255,255,255,.9),rgba(255,255,255,0) 38%),linear-gradient(180deg,#fff,#f5efe4);background-position:center top;background-size:120% 80%,100% 100%;background-repeat:no-repeat;box-shadow:0 28px 80px rgba(20,12,4,.18);padding:2rem;border-radius:24px}'; $paintHtml = '

Roast Club

Fresh coffee every week.

'; @@ -111,19 +124,19 @@ $paintBlock = $paintResult['blocks'][0] ?? array(); $paintAttrs = is_array($paintBlock['attrs'] ?? null) ? $paintBlock['attrs'] : array(); -$assert('pricing-card' === ($paintAttrs['className'] ?? ''), '34: high-value card wrapper keeps source class for class-owned paint CSS', json_encode($paintAttrs)); -$assert(! isset($paintAttrs['style']['box-shadow']), '35: class-owned box-shadow is not stored as an unsupported block style attr', json_encode($paintAttrs['style'] ?? array())); -$assert(! isset($paintAttrs['style']['background-position']) && ! isset($paintAttrs['style']['background-size']), '36: background layer controls stay out of block style attrs', json_encode($paintAttrs['style'] ?? array())); +$assert('pricing-card' === ($paintAttrs['className'] ?? ''), '39: high-value card wrapper keeps source class for class-owned paint CSS', json_encode($paintAttrs)); +$assert(! isset($paintAttrs['style']['box-shadow']), '40: class-owned box-shadow is not stored as an unsupported block style attr', json_encode($paintAttrs['style'] ?? array())); +$assert(! isset($paintAttrs['style']['background-position']) && ! isset($paintAttrs['style']['background-size']), '41: background layer controls stay out of block style attrs', json_encode($paintAttrs['style'] ?? array())); $rulesMethod = new ReflectionMethod(HtmlTransformer::class, 'staticStyleRules'); $paintRules = $rulesMethod->invoke(new HtmlTransformer(), '', $paintCss); $paintDeclarations = $paintRules[0]['declarations'] ?? array(); -$assert(($paintDeclarations['background'] ?? '') === 'radial-gradient(circle at 20% 10%,rgba(255,255,255,.9),rgba(255,255,255,0) 38%),linear-gradient(180deg,#fff,#f5efe4)', '37: radial and layered backgrounds survive safe CSS resolution', json_encode($paintDeclarations)); -$assert(($paintDeclarations['background-position'] ?? '') === 'center top', '38: background-position survives safe CSS resolution', json_encode($paintDeclarations)); -$assert(($paintDeclarations['background-size'] ?? '') === '120% 80%,100% 100%', '39: background-size survives safe CSS resolution', json_encode($paintDeclarations)); -$assert(($paintDeclarations['background-repeat'] ?? '') === 'no-repeat', '40: background-repeat survives safe CSS resolution', json_encode($paintDeclarations)); -$assert(($paintDeclarations['box-shadow'] ?? '') === '0 28px 80px rgba(20,12,4,.18)', '41: box-shadow survives safe CSS resolution', json_encode($paintDeclarations)); +$assert(($paintDeclarations['background'] ?? '') === 'radial-gradient(circle at 20% 10%,rgba(255,255,255,.9),rgba(255,255,255,0) 38%),linear-gradient(180deg,#fff,#f5efe4)', '42: radial and layered backgrounds survive safe CSS resolution', json_encode($paintDeclarations)); +$assert(($paintDeclarations['background-position'] ?? '') === 'center top', '43: background-position survives safe CSS resolution', json_encode($paintDeclarations)); +$assert(($paintDeclarations['background-size'] ?? '') === '120% 80%,100% 100%', '44: background-size survives safe CSS resolution', json_encode($paintDeclarations)); +$assert(($paintDeclarations['background-repeat'] ?? '') === 'no-repeat', '45: background-repeat survives safe CSS resolution', json_encode($paintDeclarations)); +$assert(($paintDeclarations['box-shadow'] ?? '') === '0 28px 80px rgba(20,12,4,.18)', '46: box-shadow survives safe CSS resolution', json_encode($paintDeclarations)); if ( $failures > 0 ) { fwrite(STDERR, "Block style support conversion tests: {$failures} failed, {$passes} passed\n");