diff --git a/src/wp-includes/class-wp-theme-json.php b/src/wp-includes/class-wp-theme-json.php index 2908fb94d7e69..15633dba2932d 100644 --- a/src/wp-includes/class-wp-theme-json.php +++ b/src/wp-includes/class-wp-theme-json.php @@ -1006,6 +1006,27 @@ public function get_stylesheet( $types = array( 'variables', 'styles', 'presets' return $stylesheet; } + /** + * Processes the CSS, to apply nesting. + * + * @param string $css The CSS to process. + * @param string $selector The selector to nest. + * + * @return string The processed CSS. + */ + public function process_blocks_custom_css( $css, $selector ) { + $processed_css = ''; + + // Split CSS nested rules. + $parts = explode( '&', $css ); + foreach ( $parts as $part ) { + $processed_css .= ( ! str_contains( $part, '{' ) ) + ? trim( $selector ) . '{' . trim( $part ) . '}' // If the part doesn't contain braces, it applies to the root level. + : trim( $selector . $part ); // Prepend the selector, which effectively replaces the "&" character. + } + return $processed_css; + } + /** * Returns the global styles custom css. * diff --git a/tests/phpunit/tests/theme/wpThemeJson.php b/tests/phpunit/tests/theme/wpThemeJson.php index efa709ef756dc..1b68745244338 100644 --- a/tests/phpunit/tests/theme/wpThemeJson.php +++ b/tests/phpunit/tests/theme/wpThemeJson.php @@ -4624,4 +4624,55 @@ public function data_custom_css_for_user_caps() { ), ); } + + /** + * @dataProvider data_process_blocks_custom_css + * + * @param array $input An array containing the selector and css to test. + * @param string $expected Expected results. + */ + public function test_process_blocks_custom_css( $input, $expected ) { + $theme_json = new WP_Theme_JSON_Gutenberg( + array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'styles' => array(), + ) + ); + + $this->assertEquals( $expected, $theme_json->process_blocks_custom_css( $input['css'], $input['selector'] ) ); + } + + /** + * Data provider. + * + * @return array[] + */ + public function data_process_blocks_custom_css() { + return array( + // Simple CSS without any child selectors. + 'no child selectors' => array( + 'input' => array( + 'selector' => '.foo', + 'css' => 'color: red; margin: auto;', + ), + 'expected' => '.foo{color: red; margin: auto;}', + ), + // CSS with child selectors. + 'with children' => array( + 'input' => array( + 'selector' => '.foo', + 'css' => 'color: red; margin: auto; & .bar{color: blue;}', + ), + 'expected' => '.foo{color: red; margin: auto;}.foo .bar{color: blue;}', + ), + // CSS with child selectors and pseudo elements. + 'with children and pseudo elements' => array( + 'input' => array( + 'selector' => '.foo', + 'css' => 'color: red; margin: auto; & .bar{color: blue;} &::before{color: green;}', + ), + 'expected' => '.foo{color: red; margin: auto;}.foo .bar{color: blue;}.foo::before{color: green;}', + ), + ); + } }