PluginProbe
Gutenberg / 23.5.3
Gutenberg v23.5.3
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.3, at lib/block-supports/block-style-variations.php

328 lines 10.4 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 $block_class_name = $block['attrs']['className'];
234 if ( ! is_string( $block_class_name ) ) {
235 return $block_content;
236 }
237
238 /*
239 * Matches a class prefixed by `is-style`, followed by the
240 * variation slug, then `--`, and finally an instance number.
241 */
242 preg_match( '/\bis-style-(\S+?--\d+)\b/', $block_class_name, $matches );
243
244 if ( empty( $matches ) ) {
245 return $block_content;
246 }
247
248 $tags = new WP_HTML_Tag_Processor( $block_content );
249
250 if ( $tags->next_tag() ) {
251 /*
252 * Ensure the variation instance class name set in the
253 * `render_block_data` filter is applied in markup.
254 * See `gutenberg_render_block_style_variation_support_styles`.
255 */
256 $tags->add_class( $matches[0] );
257 }
258
259 return $tags->get_updated_html();
260 }
261
262 /**
263 * Enqueues styles for block style variations.
264 *
265 * @since 6.6.0
266 */
267 function gutenberg_enqueue_block_style_variation_styles() {
268 wp_enqueue_style( 'block-style-variation-styles' );
269 }
270
271 // Register the block support.
272 WP_Block_Supports::get_instance()->register( 'block-style-variation', array() );
273
274 // Remove core filters and action.
275 if ( function_exists( 'wp_render_block_style_variation_support_styles' ) ) {
276 remove_filter( 'render_block_data', 'wp_render_block_style_variation_support_styles' );
277 }
278 if ( function_exists( 'wp_render_block_style_variation_class_name' ) ) {
279 remove_filter( 'render_block', 'wp_render_block_style_variation_class_name' );
280 }
281 if ( function_exists( 'wp_enqueue_block_style_variation_styles' ) ) {
282 remove_action( 'wp_enqueue_scripts', 'wp_enqueue_block_style_variation_styles' );
283 }
284
285 // Add Gutenberg filters and action.
286 add_filter( 'render_block_data', 'gutenberg_render_block_style_variation_support_styles' );
287 add_filter( 'render_block', 'gutenberg_render_block_style_variation_class_name', 10, 2 );
288 add_action( 'wp_enqueue_scripts', 'gutenberg_enqueue_block_style_variation_styles', 1 );
289
290 /**
291 * Registers block style variations read in from theme.json partials.
292 *
293 * @access private
294 *
295 * @param array $variations Shared block style variations.
296 */
297 function gutenberg_register_block_style_variations_from_theme_json_partials( $variations ) {
298 if ( empty( $variations ) ) {
299 return;
300 }
301
302 $registry = WP_Block_Styles_Registry::get_instance();
303
304 foreach ( $variations as $variation ) {
305 if ( empty( $variation['blockTypes'] ) || empty( $variation['styles'] ) ) {
306 continue;
307 }
308
309 $variation_name = $variation['slug'] ?? _wp_to_kebab_case( $variation['title'] );
310 $variation_label = $variation['title'] ?? $variation_name;
311
312 foreach ( $variation['blockTypes'] as $block_type ) {
313 $registered_styles = $registry->get_registered_styles_for_block( $block_type );
314
315 // Register block style variation if it hasn't already been registered.
316 if ( ! array_key_exists( $variation_name, $registered_styles ) ) {
317 register_block_style(
318 $block_type,
319 array(
320 'name' => $variation_name,
321 'label' => $variation_label,
322 )
323 );
324 }
325 }
326 }
327 }
328