run:R W Run
1.67 KB
2026-03-11 16:18:52
R W Run
1.57 KB
2026-03-11 16:18:52
R W Run
4.05 KB
2026-03-11 16:18:52
R W Run
9.2 KB
2026-03-11 16:18:52
R W Run
854 By
2026-03-11 16:18:52
R W Run
6.27 KB
2026-03-11 16:18:52
R W Run
5.81 KB
2026-03-11 16:18:52
R W Run
1.64 KB
2026-03-11 16:18:52
R W Run
5.28 KB
2026-03-11 16:18:52
R W Run
2.67 KB
2026-03-11 16:18:52
R W Run
8.46 KB
2026-03-11 16:18:52
R W Run
1.7 KB
2026-03-11 16:18:52
R W Run
39 KB
2026-03-11 16:18:52
R W Run
4.24 KB
2026-03-11 16:18:52
R W Run
4.52 KB
2026-03-11 16:18:52
R W Run
2.04 KB
2026-03-11 16:18:52
R W Run
2.81 KB
2026-03-11 16:18:52
R W Run
29.08 KB
2026-03-11 16:18:52
R W Run
1011 By
2026-03-11 16:18:52
R W Run
error_log
📄block-style-variations.php
1<?php
2/**
3 * Block support to enable per-section styling of block types via
4 * block style variations.
5 *
6 * @package WordPress
7 * @since 6.6.0
8 */
9
10/**
11 * Determines the block style variation names within a CSS class string.
12 *
13 * @since 6.6.0
14 *
15 * @param string $class_string CSS class string to look for a variation in.
16 *
17 * @return array|null The block style variation name if found.
18 */
19function wp_get_block_style_variation_name_from_class( $class_string ) {
20 if ( ! is_string( $class_string ) ) {
21 return null;
22 }
23
24 preg_match_all( '/\bis-style-(?!default)(\S+)\b/', $class_string, $matches );
25 return $matches[1] ?? null;
26}
27
28/**
29 * Recursively resolves any `ref` values within a block style variation's data.
30 *
31 * @since 6.6.0
32 * @access private
33 *
34 * @param array $variation_data Reference to the variation data being processed.
35 * @param array $theme_json Theme.json data to retrieve referenced values from.
36 */
37function wp_resolve_block_style_variation_ref_values( &$variation_data, $theme_json ) {
38 foreach ( $variation_data as $key => &$value ) {
39 // Only need to potentially process arrays.
40 if ( is_array( $value ) ) {
41 // If ref value is set, attempt to find its matching value and update it.
42 if ( array_key_exists( 'ref', $value ) ) {
43 // Clean up any invalid ref value.
44 if ( empty( $value['ref'] ) || ! is_string( $value['ref'] ) ) {
45 unset( $variation_data[ $key ] );
46 }
47
48 $value_path = explode( '.', $value['ref'] ?? '' );
49 $ref_value = _wp_array_get( $theme_json, $value_path );
50
51 // Only update the current value if the referenced path matched a value.
52 if ( null === $ref_value ) {
53 unset( $variation_data[ $key ] );
54 } else {
55 $value = $ref_value;
56 }
57 } else {
58 // Recursively look for ref instances.
59 wp_resolve_block_style_variation_ref_values( $value, $theme_json );
60 }
61 }
62 }
63}
64/**
65 * Renders the block style variation's styles.
66 *
67 * In the case of nested blocks with variations applied, we want the parent
68 * variation's styles to be rendered before their descendants. This solves the
69 * issue of a block type being styled in both the parent and descendant: we want
70 * the descendant style to take priority, and this is done by loading it after,
71 * in the DOM order. This is why the variation stylesheet generation is in a
72 * different filter.
73 *
74 * @since 6.6.0
75 * @access private
76 *
77 * @param array $parsed_block The parsed block.
78 *
79 * @return array The parsed block with block style variation classname added.
80 */
81function wp_render_block_style_variation_support_styles( $parsed_block ) {
82 $classes = $parsed_block['attrs']['className'] ?? null;
83 $variations = wp_get_block_style_variation_name_from_class( $classes );
84
85 if ( ! $variations ) {
86 return $parsed_block;
87 }
88
89 $tree = WP_Theme_JSON_Resolver::get_merged_data();
90 $theme_json = $tree->get_raw_data();
91
92 // Only the first block style variation with data is supported.
93 $variation_data = array();
94 foreach ( $variations as $variation ) {
95 $variation_data = $theme_json['styles']['blocks'][ $parsed_block['blockName'] ]['variations'][ $variation ] ?? array();
96
97 if ( ! empty( $variation_data ) ) {
98 break;
99 }
100 }
101
102 if ( empty( $variation_data ) ) {
103 return $parsed_block;
104 }
105
106 /*
107 * Recursively resolve any ref values with the appropriate value within the
108 * theme_json data.
109 */
110 wp_resolve_block_style_variation_ref_values( $variation_data, $theme_json );
111
112 $variation_instance = wp_unique_id( $variation . '--' );
113 $class_name = "is-style-$variation_instance";
114 $updated_class_name = $parsed_block['attrs']['className'] . " $class_name";
115
116 /*
117 * Even though block style variations are effectively theme.json partials,
118 * they can't be processed completely as though they are.
119 *
120 * Block styles support custom selectors to direct specific types of styles
121 * to inner elements. For example, borders on Image block's get applied to
122 * the inner `img` element rather than the wrapping `figure`.
123 *
124 * The following relocates the "root" block style variation styles to
125 * under an appropriate blocks property to leverage the preexisting style
126 * generation for simple block style variations. This way they get the
127 * custom selectors they need.
128 *
129 * The inner elements and block styles for the variation itself are
130 * still included at the top level but scoped by the variation's selector
131 * when the stylesheet is generated.
132 */
133 $elements_data = $variation_data['elements'] ?? array();
134 $blocks_data = $variation_data['blocks'] ?? array();
135 unset( $variation_data['elements'] );
136 unset( $variation_data['blocks'] );
137
138 _wp_array_set(
139 $blocks_data,
140 array( $parsed_block['blockName'], 'variations', $variation_instance ),
141 $variation_data
142 );
143
144 $config = array(
145 'version' => WP_Theme_JSON::LATEST_SCHEMA,
146 'styles' => array(
147 'elements' => $elements_data,
148 'blocks' => $blocks_data,
149 ),
150 );
151
152 // Turn off filter that excludes block nodes. They are needed here for the variation's inner block types.
153 if ( ! is_admin() ) {
154 remove_filter( 'wp_theme_json_get_style_nodes', 'wp_filter_out_block_nodes' );
155 }
156
157 // Temporarily prevent variation instance from being sanitized while processing theme.json.
158 $styles_registry = WP_Block_Styles_Registry::get_instance();
159 $styles_registry->register( $parsed_block['blockName'], array( 'name' => $variation_instance ) );
160
161 $variation_theme_json = new WP_Theme_JSON( $config, 'blocks' );
162 $variation_styles = $variation_theme_json->get_stylesheet(
163 array( 'styles' ),
164 array( 'custom' ),
165 array(
166 'include_block_style_variations' => true,
167 'skip_root_layout_styles' => true,
168 'scope' => ".$class_name",
169 )
170 );
171
172 // Clean up temporary block style now instance styles have been processed.
173 $styles_registry->unregister( $parsed_block['blockName'], $variation_instance );
174
175 // Restore filter that excludes block nodes.
176 if ( ! is_admin() ) {
177 add_filter( 'wp_theme_json_get_style_nodes', 'wp_filter_out_block_nodes' );
178 }
179
180 if ( empty( $variation_styles ) ) {
181 return $parsed_block;
182 }
183
184 wp_register_style( 'block-style-variation-styles', false, array( 'wp-block-library', 'global-styles' ) );
185 wp_add_inline_style( 'block-style-variation-styles', $variation_styles );
186
187 /*
188 * Add variation instance class name to block's className string so it can
189 * be enforced in the block markup via render_block filter.
190 */
191 _wp_array_set( $parsed_block, array( 'attrs', 'className' ), $updated_class_name );
192
193 return $parsed_block;
194}
195
196/**
197 * Ensures the variation block support class name generated and added to
198 * block attributes in the `render_block_data` filter gets applied to the
199 * block's markup.
200 *
201 * @since 6.6.0
202 * @access private
203 *
204 * @see wp_render_block_style_variation_support_styles
205 *
206 * @param string $block_content Rendered block content.
207 * @param array $block Block object.
208 *
209 * @return string Filtered block content.
210 */
211function wp_render_block_style_variation_class_name( $block_content, $block ) {
212 if ( ! $block_content || empty( $block['attrs']['className'] ) ) {
213 return $block_content;
214 }
215
216 /*
217 * Matches a class prefixed by `is-style`, followed by the
218 * variation slug, then `--`, and finally an instance number.
219 */
220 preg_match( '/\bis-style-(\S+?--\d+)\b/', $block['attrs']['className'], $matches );
221
222 if ( empty( $matches ) ) {
223 return $block_content;
224 }
225
226 $tags = new WP_HTML_Tag_Processor( $block_content );
227
228 if ( $tags->next_tag() ) {
229 /*
230 * Ensure the variation instance class name set in the
231 * `render_block_data` filter is applied in markup.
232 * See `wp_render_block_style_variation_support_styles`.
233 */
234 $tags->add_class( $matches[0] );
235 }
236
237 return $tags->get_updated_html();
238}
239
240/**
241 * Enqueues styles for block style variations.
242 *
243 * @since 6.6.0
244 * @access private
245 */
246function wp_enqueue_block_style_variation_styles() {
247 wp_enqueue_style( 'block-style-variation-styles' );
248}
249
250// Register the block support.
251WP_Block_Supports::get_instance()->register( 'block-style-variation', array() );
252
253add_filter( 'render_block_data', 'wp_render_block_style_variation_support_styles', 10, 2 );
254add_filter( 'render_block', 'wp_render_block_style_variation_class_name', 10, 2 );
255add_action( 'wp_enqueue_scripts', 'wp_enqueue_block_style_variation_styles', 1 );
256
257/**
258 * Registers block style variations read in from theme.json partials.
259 *
260 * @since 6.6.0
261 * @access private
262 *
263 * @param array $variations Shared block style variations.
264 */
265function wp_register_block_style_variations_from_theme_json_partials( $variations ) {
266 if ( empty( $variations ) ) {
267 return;
268 }
269
270 $registry = WP_Block_Styles_Registry::get_instance();
271
272 foreach ( $variations as $variation ) {
273 if ( empty( $variation['blockTypes'] ) || empty( $variation['styles'] ) ) {
274 continue;
275 }
276
277 $variation_name = $variation['slug'] ?? _wp_to_kebab_case( $variation['title'] );
278 $variation_label = $variation['title'] ?? $variation_name;
279
280 foreach ( $variation['blockTypes'] as $block_type ) {
281 $registered_styles = $registry->get_registered_styles_for_block( $block_type );
282
283 // Register block style variation if it hasn't already been registered.
284 if ( ! array_key_exists( $variation_name, $registered_styles ) ) {
285 register_block_style(
286 $block_type,
287 array(
288 'name' => $variation_name,
289 'label' => $variation_label,
290 )
291 );
292 }
293 }
294 }
295}
296