diff --git a/src/wp-includes/class-wp-theme-json.php b/src/wp-includes/class-wp-theme-json.php index 39e3581c18765..05c5d5bdd0f2a 100644 --- a/src/wp-includes/class-wp-theme-json.php +++ b/src/wp-includes/class-wp-theme-json.php @@ -1168,7 +1168,7 @@ public function get_stylesheet( $types = array( 'variables', 'styles', 'presets' $node['selector'] = static::scope_selector( $options['scope'], $node['selector'] ); } foreach ( $style_nodes as &$node ) { - $node['selector'] = static::scope_selector( $options['scope'], $node['selector'] ); + $node = static::scope_style_node_selectors( $options['scope'], $node ); } unset( $node ); } @@ -1792,6 +1792,39 @@ public static function scope_selector( $scope, $selector ) { return $result; } + /** + * Scopes the selectors for a given style node. + * + * This includes the primary selector, i.e. `$node['selector']`, as well as any custom + * selectors for features and subfeatures, e.g. `$node['selectors']['border']` etc. + * + * @since 6.6.0 + * + * @param string $scope Selector to scope to. + * @param array $node Style node with selectors to scope. + * @return array Node with updated selectors. + */ + protected static function scope_style_node_selectors( $scope, $node ) { + $node['selector'] = static::scope_selector( $scope, $node['selector'] ); + + if ( empty( $node['selectors'] ) ) { + return $node; + } + + foreach ( $node['selectors'] as $feature => $selector ) { + if ( is_string( $selector ) ) { + $node['selectors'][ $feature ] = static::scope_selector( $scope, $selector ); + } + if ( is_array( $selector ) ) { + foreach ( $selector as $subfeature => $subfeature_selector ) { + $node['selectors'][ $feature ][ $subfeature ] = static::scope_selector( $scope, $subfeature_selector ); + } + } + } + + return $node; + } + /** * Gets preset values keyed by slugs based on settings and metadata. * diff --git a/tests/phpunit/tests/theme/wpThemeJson.php b/tests/phpunit/tests/theme/wpThemeJson.php index 8a64925cdcd7e..73281b2e08953 100644 --- a/tests/phpunit/tests/theme/wpThemeJson.php +++ b/tests/phpunit/tests/theme/wpThemeJson.php @@ -5422,4 +5422,51 @@ public function data_get_block_style_variation_selector() { ), ); } + + /** + * Tests the correct scoping of selectors for a style node. + * + * @ticket 61119 + */ + public function test_scope_style_node_selectors() { + $theme_json = new ReflectionClass( 'WP_Theme_JSON' ); + + $func = $theme_json->getMethod( 'scope_style_node_selectors' ); + $func->setAccessible( true ); + + $node = array( + 'name' => 'core/image', + 'path' => array( 'styles', 'blocks', 'core/image' ), + 'selector' => '.wp-block-image', + 'selectors' => array( + 'root' => '.wp-block-image', + 'border' => '.wp-block-image img, .wp-block-image .wp-block-image__crop-area, .wp-block-image .components-placeholder', + 'typography' => array( + 'textDecoration' => '.wp-block-image caption', + ), + 'filter' => array( + 'duotone' => '.wp-block-image img, .wp-block-image .components-placeholder', + ), + ), + ); + + $actual = $func->invoke( null, '.custom-scope', $node ); + $expected = array( + 'name' => 'core/image', + 'path' => array( 'styles', 'blocks', 'core/image' ), + 'selector' => '.custom-scope .wp-block-image', + 'selectors' => array( + 'root' => '.custom-scope .wp-block-image', + 'border' => '.custom-scope .wp-block-image img, .custom-scope .wp-block-image .wp-block-image__crop-area, .custom-scope .wp-block-image .components-placeholder', + 'typography' => array( + 'textDecoration' => '.custom-scope .wp-block-image caption', + ), + 'filter' => array( + 'duotone' => '.custom-scope .wp-block-image img, .custom-scope .wp-block-image .components-placeholder', + ), + ), + ); + + $this->assertEquals( $expected, $actual ); + } }