PluginProbe
Gutenberg / 23.6.2
Gutenberg v23.6.2
23.9.1 23.9.0 23.8.0 23.7.2 23.7.1 23.7.0 23.6.1 23.6.2 23.6.0 23.5.3 23.5.2 23.5.1 23.5.0 23.4.0 23.3.2 23.3.1 23.3.0 23.2.0 23.2.1 23.2.2 23.1.1 23.1.0 23.0.1 12.6.0 7.4.0 All 402 releases
gutenberg / lib / block-supports / block-style-variations.php

block-style-variations.php in Gutenberg 23.6.2, at lib/block-supports/block-style-variations.php

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