PluginProbe
Gutenberg / 23.5.2
Gutenberg v23.5.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.5.2, at lib/block-supports/block-style-variations.php

323 lines 10.3 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 // Turn off filter that excludes block nodes. They are needed here for the variation's inner block types.
171 if ( ! is_admin() ) {
172 remove_filter( 'wp_theme_json_get_style_nodes', 'wp_filter_out_block_nodes' );
173 }
174
175 // Temporarily prevent variation instance from being sanitized while processing theme.json.
176 $styles_registry = WP_Block_Styles_Registry::get_instance();
177 $styles_registry->register( $parsed_block['blockName'], array( 'name' => $variation_instance ) );
178
179 $variation_theme_json = new WP_Theme_JSON_Gutenberg( $config, 'blocks' );
180 $variation_styles = $variation_theme_json->get_stylesheet(
181 array( 'styles' ),
182 array( 'custom' ),
183 array(
184 'include_block_style_variations' => true,
185 'skip_root_layout_styles' => true,
186 'scope' => ".$class_name",
187 )
188 );
189
190 // Clean up temporary block style now instance styles have been processed.
191 $styles_registry->unregister( $parsed_block['blockName'], $variation_instance );
192
193 // Restore filter that excludes block nodes.
194 if ( ! is_admin() ) {
195 add_filter( 'wp_theme_json_get_style_nodes', 'wp_filter_out_block_nodes' );
196 }
197
198 if ( empty( $variation_styles ) ) {
199 return $parsed_block;
200 }
201
202 wp_register_style( 'block-style-variation-styles', false, array( 'wp-block-library', 'global-styles' ) );
203 wp_add_inline_style( 'block-style-variation-styles', $variation_styles );
204
205 /*
206 * Add variation instance class name to block's className string so it can
207 * be enforced in the block markup via render_block filter.
208 */
209 _wp_array_set( $parsed_block, array( 'attrs', 'className' ), $updated_class_name );
210
211 return $parsed_block;
212 }
213
214 /**
215 * Ensure the variation block support class name generated and added to
216 * block attributes in the `render_block_data` filter gets applied to the
217 * block's markup.
218 *
219 * @since 6.6.0
220 *
221 * @see gutenberg_render_block_style_variation_support_styles
222 *
223 * @param string $block_content Rendered block content.
224 * @param array $block Block object.
225 *
226 * @return string Filtered block content.
227 */
228 function gutenberg_render_block_style_variation_class_name( $block_content, $block ) {
229 if ( ! $block_content || empty( $block['attrs']['className'] ) ) {
230 return $block_content;
231 }
232
233 /*
234 * Matches a class prefixed by `is-style`, followed by the
235 * variation slug, then `--`, and finally an instance number.
236 */
237 preg_match( '/\bis-style-(\S+?--\d+)\b/', $block['attrs']['className'], $matches );
238
239 if ( empty( $matches ) ) {
240 return $block_content;
241 }
242
243 $tags = new WP_HTML_Tag_Processor( $block_content );
244
245 if ( $tags->next_tag() ) {
246 /*
247 * Ensure the variation instance class name set in the
248 * `render_block_data` filter is applied in markup.
249 * See `gutenberg_render_block_style_variation_support_styles`.
250 */
251 $tags->add_class( $matches[0] );
252 }
253
254 return $tags->get_updated_html();
255 }
256
257 /**
258 * Enqueues styles for block style variations.
259 *
260 * @since 6.6.0
261 */
262 function gutenberg_enqueue_block_style_variation_styles() {
263 wp_enqueue_style( 'block-style-variation-styles' );
264 }
265
266 // Register the block support.
267 WP_Block_Supports::get_instance()->register( 'block-style-variation', array() );
268
269 // Remove core filters and action.
270 if ( function_exists( 'wp_render_block_style_variation_support_styles' ) ) {
271 remove_filter( 'render_block_data', 'wp_render_block_style_variation_support_styles' );
272 }
273 if ( function_exists( 'wp_render_block_style_variation_class_name' ) ) {
274 remove_filter( 'render_block', 'wp_render_block_style_variation_class_name' );
275 }
276 if ( function_exists( 'wp_enqueue_block_style_variation_styles' ) ) {
277 remove_action( 'wp_enqueue_scripts', 'wp_enqueue_block_style_variation_styles' );
278 }
279
280 // Add Gutenberg filters and action.
281 add_filter( 'render_block_data', 'gutenberg_render_block_style_variation_support_styles' );
282 add_filter( 'render_block', 'gutenberg_render_block_style_variation_class_name', 10, 2 );
283 add_action( 'wp_enqueue_scripts', 'gutenberg_enqueue_block_style_variation_styles', 1 );
284
285 /**
286 * Registers block style variations read in from theme.json partials.
287 *
288 * @access private
289 *
290 * @param array $variations Shared block style variations.
291 */
292 function gutenberg_register_block_style_variations_from_theme_json_partials( $variations ) {
293 if ( empty( $variations ) ) {
294 return;
295 }
296
297 $registry = WP_Block_Styles_Registry::get_instance();
298
299 foreach ( $variations as $variation ) {
300 if ( empty( $variation['blockTypes'] ) || empty( $variation['styles'] ) ) {
301 continue;
302 }
303
304 $variation_name = $variation['slug'] ?? _wp_to_kebab_case( $variation['title'] );
305 $variation_label = $variation['title'] ?? $variation_name;
306
307 foreach ( $variation['blockTypes'] as $block_type ) {
308 $registered_styles = $registry->get_registered_styles_for_block( $block_type );
309
310 // Register block style variation if it hasn't already been registered.
311 if ( ! array_key_exists( $variation_name, $registered_styles ) ) {
312 register_block_style(
313 $block_type,
314 array(
315 'name' => $variation_name,
316 'label' => $variation_label,
317 )
318 );
319 }
320 }
321 }
322 }
323