PluginProbe
Gutenberg / 12.1.0
Gutenberg v12.1.0
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
← All changes | lib/block-supports/layout.php +102 -1120 23.0.112.1.0 View file →
@@ -5,226 +5,14 @@
5 5 * @package gutenberg
6 6 */
7 7
8 8 /**
9 - * Get the first style variation name from a className string that matches a registered style.
10 - *
11 - * @param string $class_name CSS class string for a block.
12 - * @param array<string, array<string, mixed>> $registered_styles Currently registered block styles.
13 - * @return string|null The name of the first registered variation, or null if none found.
14 - */
15 -function gutenberg_get_block_style_variation_name_from_registered_style( string $class_name, array $registered_styles = array() ): ?string {
16 - if ( ! $class_name ) {
17 - return null;
18 - }
19 -
20 - $registered_names = array_filter( array_column( $registered_styles, 'name' ) );
21 -
22 - $prefix = 'is-style-';
23 - $length = strlen( $prefix );
24 -
25 - foreach ( explode( ' ', $class_name ) as $class ) {
26 - if ( str_starts_with( $class, $prefix ) ) {
27 - $variation = substr( $class, $length );
28 - if ( 'default' !== $variation && in_array( $variation, $registered_names, true ) ) {
29 - return $variation;
30 - }
31 - }
32 - }
33 -
34 - return null;
35 -}
36 -
37 -/**
38 - * Returns layout definitions, keyed by layout type.
39 - *
40 - * Provides a common definition of slugs, classnames, base styles, and spacing styles for each layout type.
41 - * When making changes or additions to layout definitions, the corresponding JavaScript definitions should
42 - * also be updated.
43 - *
44 - * @return array[] Layout definitions.
45 - */
46 -function gutenberg_get_layout_definitions() {
47 - $layout_definitions = array(
48 - 'default' => array(
49 - 'name' => 'default',
50 - 'slug' => 'flow',
51 - 'className' => 'is-layout-flow',
52 - 'baseStyles' => array(
53 - array(
54 - 'selector' => ' > .alignleft',
55 - 'rules' => array(
56 - 'float' => 'left',
57 - 'margin-inline-start' => '0',
58 - 'margin-inline-end' => '2em',
59 - ),
60 - ),
61 - array(
62 - 'selector' => ' > .alignright',
63 - 'rules' => array(
64 - 'float' => 'right',
65 - 'margin-inline-start' => '2em',
66 - 'margin-inline-end' => '0',
67 - ),
68 - ),
69 - array(
70 - 'selector' => ' > .aligncenter',
71 - 'rules' => array(
72 - 'margin-left' => 'auto !important',
73 - 'margin-right' => 'auto !important',
74 - ),
75 - ),
76 - ),
77 - 'spacingStyles' => array(
78 - array(
79 - 'selector' => ' > :first-child',
80 - 'rules' => array(
81 - 'margin-block-start' => '0',
82 - ),
83 - ),
84 - array(
85 - 'selector' => ' > :last-child',
86 - 'rules' => array(
87 - 'margin-block-end' => '0',
88 - ),
89 - ),
90 - array(
91 - 'selector' => ' > *',
92 - 'rules' => array(
93 - 'margin-block-start' => null,
94 - 'margin-block-end' => '0',
95 - ),
96 - ),
97 - ),
98 - ),
99 - 'constrained' => array(
100 - 'name' => 'constrained',
101 - 'slug' => 'constrained',
102 - 'className' => 'is-layout-constrained',
103 - 'baseStyles' => array(
104 - array(
105 - 'selector' => ' > .alignleft',
106 - 'rules' => array(
107 - 'float' => 'left',
108 - 'margin-inline-start' => '0',
109 - 'margin-inline-end' => '2em',
110 - ),
111 - ),
112 - array(
113 - 'selector' => ' > .alignright',
114 - 'rules' => array(
115 - 'float' => 'right',
116 - 'margin-inline-start' => '2em',
117 - 'margin-inline-end' => '0',
118 - ),
119 - ),
120 - array(
121 - 'selector' => ' > .aligncenter',
122 - 'rules' => array(
123 - 'margin-left' => 'auto !important',
124 - 'margin-right' => 'auto !important',
125 - ),
126 - ),
127 - array(
128 - 'selector' => ' > :where(:not(.alignleft):not(.alignright):not(.alignfull))',
129 - 'rules' => array(
130 - 'max-width' => 'var(--wp--style--global--content-size)',
131 - 'margin-left' => 'auto !important',
132 - 'margin-right' => 'auto !important',
133 - ),
134 - ),
135 - array(
136 - 'selector' => ' > .alignwide',
137 - 'rules' => array(
138 - 'max-width' => 'var(--wp--style--global--wide-size)',
139 - ),
140 - ),
141 - ),
142 - 'spacingStyles' => array(
143 - array(
144 - 'selector' => ' > :first-child',
145 - 'rules' => array(
146 - 'margin-block-start' => '0',
147 - ),
148 - ),
149 - array(
150 - 'selector' => ' > :last-child',
151 - 'rules' => array(
152 - 'margin-block-end' => '0',
153 - ),
154 - ),
155 - array(
156 - 'selector' => ' > *',
157 - 'rules' => array(
158 - 'margin-block-start' => null,
159 - 'margin-block-end' => '0',
160 - ),
161 - ),
162 - ),
163 - ),
164 - 'flex' => array(
165 - 'name' => 'flex',
166 - 'slug' => 'flex',
167 - 'className' => 'is-layout-flex',
168 - 'displayMode' => 'flex',
169 - 'baseStyles' => array(
170 - array(
171 - 'selector' => '',
172 - 'rules' => array(
173 - 'flex-wrap' => 'wrap',
174 - 'align-items' => 'center',
175 - ),
176 - ),
177 - array(
178 - 'selector' => ' > :is(*, div)', // :is(*, div) instead of just * increases the specificity by 001.
179 - 'rules' => array(
180 - 'margin' => '0',
181 - ),
182 - ),
183 - ),
184 - 'spacingStyles' => array(
185 - array(
186 - 'selector' => '',
187 - 'rules' => array(
188 - 'gap' => null,
189 - ),
190 - ),
191 - ),
192 - ),
193 - 'grid' => array(
194 - 'name' => 'grid',
195 - 'slug' => 'grid',
196 - 'className' => 'is-layout-grid',
197 - 'displayMode' => 'grid',
198 - 'baseStyles' => array(
199 - array(
200 - 'selector' => ' > :is(*, div)', // :is(*, div) instead of just * increases the specificity by 001.
201 - 'rules' => array(
202 - 'margin' => '0',
203 - ),
204 - ),
205 - ),
206 - 'spacingStyles' => array(
207 - array(
208 - 'selector' => '',
209 - 'rules' => array(
210 - 'gap' => null,
211 - ),
212 - ),
213 - ),
214 - ),
215 - );
216 -
217 - return $layout_definitions;
218 -}
219 -
220 -/**
221 9 * Registers the layout block attribute for block types that support it.
222 10 *
223 11 * @param WP_Block_Type $block_type Block Type.
224 12 */
225 13 function gutenberg_register_layout_support( $block_type ) {
226 - $support_layout = block_has_support( $block_type, array( 'layout' ), false ) || block_has_support( $block_type, array( '__experimentalLayout' ), false );
14 + $support_layout = gutenberg_block_has_support( $block_type, array( '__experimentalLayout' ), false );
227 15 if ( $support_layout ) {
228 16 if ( ! $block_type->attributes ) {
229 17 $block_type->attributes = array();
230 18 }
@@ -239,173 +27,50 @@
239 27
240 28 /**
241 29 * Generates the CSS corresponding to the provided layout.
242 30 *
243 - * @param string $selector CSS selector.
244 - * @param array $layout Layout object. The one that is passed has already checked
245 - * the existence of default block layout.
246 - * @param bool $has_block_gap_support Optional. Whether the theme has support for the block gap. Default false.
247 - * @param string|string[]|null $gap_value Optional. The block gap value to apply. Default null.
248 - * @param bool $should_skip_gap_serialization Optional. Whether to skip applying the user-defined value set in the editor. Default false.
249 - * @param string|array $fallback_gap_value Optional. The block gap value to apply. If it's an array expected properties are "top" and/or "left". Default '0.5em'.
250 - * @param array|null $block_spacing Optional. Custom spacing set on the block. Default null.
251 - * @return string CSS styles on success. Else, empty string.
31 + * @param string $selector CSS selector.
32 + * @param array $layout Layout object. The one that is passed has already checked the existance of default block layout.
33 + * @param boolean $has_block_gap_support Whether the theme has support for the block gap.
34 + *
35 + * @return string CSS style.
252 36 */
253 -function gutenberg_get_layout_style( $selector, $layout, $has_block_gap_support = false, $gap_value = null, $should_skip_gap_serialization = false, $fallback_gap_value = '0.5em', $block_spacing = null ) {
254 - $layout_type = $layout['type'] ?? 'default';
255 - $layout_styles = array();
37 +function gutenberg_get_layout_style( $selector, $layout, $has_block_gap_support = false ) {
38 + $layout_type = isset( $layout['type'] ) ? $layout['type'] : 'default';
256 39
40 + $style = '';
257 41 if ( 'default' === $layout_type ) {
258 - if ( $has_block_gap_support ) {
259 - if ( is_array( $gap_value ) ) {
260 - $gap_value = $gap_value['top'] ?? null;
261 - }
262 - if ( null !== $gap_value && ! $should_skip_gap_serialization ) {
263 - // Get spacing CSS variable from preset value if provided.
264 - if ( is_string( $gap_value ) && str_contains( $gap_value, 'var:preset|spacing|' ) ) {
265 - $index_to_splice = strrpos( $gap_value, '|' ) + 1;
266 - $slug = _wp_to_kebab_case( substr( $gap_value, $index_to_splice ) );
267 - $gap_value = "var(--wp--preset--spacing--$slug)";
268 - }
42 + $content_size = isset( $layout['contentSize'] ) ? $layout['contentSize'] : null;
43 + $wide_size = isset( $layout['wideSize'] ) ? $layout['wideSize'] : null;
269 44
270 - array_push(
271 - $layout_styles,
272 - array(
273 - 'selector' => "$selector > *",
274 - 'declarations' => array(
275 - 'margin-block-start' => '0',
276 - 'margin-block-end' => '0',
277 - ),
278 - ),
279 - array(
280 - 'selector' => "$selector > * + *",
281 - 'declarations' => array(
282 - 'margin-block-start' => $gap_value,
283 - 'margin-block-end' => '0',
284 - ),
285 - )
286 - );
287 - }
288 - }
289 - } elseif ( 'constrained' === $layout_type ) {
290 - $content_size = $layout['contentSize'] ?? '';
291 - $wide_size = $layout['wideSize'] ?? '';
292 - $justify_content = $layout['justifyContent'] ?? 'center';
293 -
294 45 $all_max_width_value = $content_size ? $content_size : $wide_size;
295 46 $wide_max_width_value = $wide_size ? $wide_size : $content_size;
296 47
297 48 // Make sure there is a single CSS rule, and all tags are stripped for security.
298 - $all_max_width_value = safecss_filter_attr( explode( ';', $all_max_width_value )[0] );
299 - $wide_max_width_value = safecss_filter_attr( explode( ';', $wide_max_width_value )[0] );
49 + // TODO: Use `safecss_filter_attr` instead - once https://core.trac.wordpress.org/ticket/46197 is patched.
50 + $all_max_width_value = wp_strip_all_tags( explode( ';', $all_max_width_value )[0] );
51 + $wide_max_width_value = wp_strip_all_tags( explode( ';', $wide_max_width_value )[0] );
300 52
301 - $margin_left = 'left' === $justify_content ? '0 !important' : 'auto !important';
302 - $margin_right = 'right' === $justify_content ? '0 !important' : 'auto !important';
303 -
53 + $style = '';
304 54 if ( $content_size || $wide_size ) {
305 - array_push(
306 - $layout_styles,
307 - array(
308 - 'selector' => "$selector > :where(:not(.alignleft):not(.alignright):not(.alignfull))",
309 - 'declarations' => array(
310 - 'max-width' => $all_max_width_value,
311 - 'margin-left' => $margin_left,
312 - 'margin-right' => $margin_right,
313 - ),
314 - ),
315 - array(
316 - 'selector' => "$selector > .alignwide",
317 - 'declarations' => array( 'max-width' => $wide_max_width_value ),
318 - ),
319 - array(
320 - 'selector' => "$selector .alignfull",
321 - 'declarations' => array( 'max-width' => 'none' ),
322 - )
323 - );
324 - }
55 + $style = "$selector > * {";
56 + $style .= 'max-width: ' . esc_html( $all_max_width_value ) . ';';
57 + $style .= 'margin-left: auto !important;';
58 + $style .= 'margin-right: auto !important;';
59 + $style .= '}';
325 60
326 - if ( isset( $block_spacing ) ) {
327 - $block_spacing_values = gutenberg_style_engine_get_styles(
328 - array(
329 - 'spacing' => $block_spacing,
330 - )
331 - );
332 -
333 - /*
334 - * Handle negative margins for alignfull children of blocks with custom padding set.
335 - * They're added separately because padding might only be set on one side.
336 - */
337 - if ( isset( $block_spacing_values['declarations']['padding-right'] ) ) {
338 - $padding_right = $block_spacing_values['declarations']['padding-right'];
339 - // Add unit if 0.
340 - if ( '0' === $padding_right ) {
341 - $padding_right = '0px';
342 - }
343 - $layout_styles[] = array(
344 - 'selector' => "$selector > .alignfull",
345 - 'declarations' => array( 'margin-right' => "calc($padding_right * -1)" ),
346 - );
347 - }
348 - if ( isset( $block_spacing_values['declarations']['padding-left'] ) ) {
349 - $padding_left = $block_spacing_values['declarations']['padding-left'];
350 - // Add unit if 0.
351 - if ( '0' === $padding_left ) {
352 - $padding_left = '0px';
353 - }
354 - $layout_styles[] = array(
355 - 'selector' => "$selector > .alignfull",
356 - 'declarations' => array( 'margin-left' => "calc($padding_left * -1)" ),
357 - );
358 - }
61 + $style .= "$selector > .alignwide { max-width: " . esc_html( $wide_max_width_value ) . ';}';
62 + $style .= "$selector .alignfull { max-width: none; }";
359 63 }
360 64
361 - if ( 'left' === $justify_content ) {
362 - $layout_styles[] = array(
363 - 'selector' => "$selector > :where(:not(.alignleft):not(.alignright):not(.alignfull))",
364 - 'declarations' => array( 'margin-left' => '0 !important' ),
365 - );
366 - }
367 -
368 - if ( 'right' === $justify_content ) {
369 - $layout_styles[] = array(
370 - 'selector' => "$selector > :where(:not(.alignleft):not(.alignright):not(.alignfull))",
371 - 'declarations' => array( 'margin-right' => '0 !important' ),
372 - );
373 - }
374 -
65 + $style .= "$selector .alignleft { float: left; margin-right: 2em; }";
66 + $style .= "$selector .alignright { float: right; margin-left: 2em; }";
375 67 if ( $has_block_gap_support ) {
376 - if ( is_array( $gap_value ) ) {
377 - $gap_value = $gap_value['top'] ?? null;
378 - }
379 - if ( null !== $gap_value && ! $should_skip_gap_serialization ) {
380 - // Get spacing CSS variable from preset value if provided.
381 - if ( is_string( $gap_value ) && str_contains( $gap_value, 'var:preset|spacing|' ) ) {
382 - $index_to_splice = strrpos( $gap_value, '|' ) + 1;
383 - $slug = _wp_to_kebab_case( substr( $gap_value, $index_to_splice ) );
384 - $gap_value = "var(--wp--preset--spacing--$slug)";
385 - }
386 -
387 - array_push(
388 - $layout_styles,
389 - array(
390 - 'selector' => "$selector > *",
391 - 'declarations' => array(
392 - 'margin-block-start' => '0',
393 - 'margin-block-end' => '0',
394 - ),
395 - ),
396 - array(
397 - 'selector' => "$selector > * + *",
398 - 'declarations' => array(
399 - 'margin-block-start' => $gap_value,
400 - 'margin-block-end' => '0',
401 - ),
402 - )
403 - );
404 - }
68 + $style .= "$selector > * { margin-top: 0; margin-bottom: 0; }";
69 + $style .= "$selector > * + * { margin-top: var( --wp--style--block-gap ); margin-bottom: 0; }";
405 70 }
406 71 } elseif ( 'flex' === $layout_type ) {
407 - $layout_orientation = $layout['orientation'] ?? 'horizontal';
72 + $layout_orientation = isset( $layout['orientation'] ) ? $layout['orientation'] : 'horizontal';
408 73
409 74 $justify_content_options = array(
410 75 'left' => 'flex-start',
411 76 'right' => 'flex-end',
@@ -411,249 +76,65 @@
411 76 'right' => 'flex-end',
412 77 'center' => 'center',
413 78 );
414 79
415 - $vertical_alignment_options = array(
416 - 'top' => 'flex-start',
417 - 'center' => 'center',
418 - 'bottom' => 'flex-end',
419 - );
420 -
421 80 if ( 'horizontal' === $layout_orientation ) {
422 - $justify_content_options += array( 'space-between' => 'space-between' );
423 - $vertical_alignment_options += array( 'stretch' => 'stretch' );
424 - } else {
425 - $justify_content_options += array( 'stretch' => 'stretch' );
426 - $vertical_alignment_options += array( 'space-between' => 'space-between' );
81 + $justify_content_options += array( 'space-between' => 'space-between' );
427 82 }
428 83
429 - if ( ! empty( $layout['flexWrap'] ) && 'nowrap' === $layout['flexWrap'] ) {
430 - $layout_styles[] = array(
431 - 'selector' => $selector,
432 - 'declarations' => array( 'flex-wrap' => 'nowrap' ),
433 - );
434 - }
84 + $flex_wrap_options = array( 'wrap', 'nowrap' );
85 + $flex_wrap = ! empty( $layout['flexWrap'] ) && in_array( $layout['flexWrap'], $flex_wrap_options, true ) ?
86 + $layout['flexWrap'] :
87 + 'wrap';
435 88
436 - if ( $has_block_gap_support && isset( $gap_value ) ) {
437 - $combined_gap_value = '';
438 - $gap_sides = is_array( $gap_value ) ? array( 'top', 'left' ) : array( 'top' );
439 -
440 - foreach ( $gap_sides as $gap_side ) {
441 - $process_value = $gap_value;
442 - if ( is_array( $gap_value ) ) {
443 - if ( is_array( $fallback_gap_value ) ) {
444 - $fallback_value = $fallback_gap_value[ $gap_side ] ?? reset( $fallback_gap_value );
445 - } else {
446 - $fallback_value = $fallback_gap_value;
447 - }
448 - $process_value = $gap_value[ $gap_side ] ?? $fallback_value;
449 - }
450 - // Get spacing CSS variable from preset value if provided.
451 - if ( is_string( $process_value ) && str_contains( $process_value, 'var:preset|spacing|' ) ) {
452 - $index_to_splice = strrpos( $process_value, '|' ) + 1;
453 - $slug = _wp_to_kebab_case( substr( $process_value, $index_to_splice ) );
454 - $process_value = "var(--wp--preset--spacing--$slug)";
455 - }
456 - $combined_gap_value .= "$process_value ";
457 - }
458 - $gap_value = trim( $combined_gap_value );
459 -
460 - if ( null !== $gap_value && ! $should_skip_gap_serialization ) {
461 - $layout_styles[] = array(
462 - 'selector' => $selector,
463 - 'declarations' => array( 'gap' => $gap_value ),
464 - );
465 - }
89 + $style = "$selector {";
90 + $style .= 'display: flex;';
91 + if ( $has_block_gap_support ) {
92 + $style .= 'gap: var( --wp--style--block-gap, 0.5em );';
93 + } else {
94 + $style .= 'gap: 0.5em;';
466 95 }
467 -
96 + $style .= "flex-wrap: $flex_wrap;";
468 97 if ( 'horizontal' === $layout_orientation ) {
469 - /*
98 + $style .= 'align-items: center;';
99 + /**
470 100 * Add this style only if is not empty for backwards compatibility,
471 101 * since we intend to convert blocks that had flex layout implemented
472 102 * by custom css.
473 103 */
474 104 if ( ! empty( $layout['justifyContent'] ) && array_key_exists( $layout['justifyContent'], $justify_content_options ) ) {
475 - $layout_styles[] = array(
476 - 'selector' => $selector,
477 - 'declarations' => array( 'justify-content' => $justify_content_options[ $layout['justifyContent'] ] ),
478 - );
105 + $style .= "justify-content: {$justify_content_options[ $layout['justifyContent'] ]};";
106 + if ( ! empty( $layout['setCascadingProperties'] ) && $layout['setCascadingProperties'] ) {
107 + // --layout-justification-setting allows children to inherit the value regardless or row or column direction.
108 + $style .= "--layout-justification-setting: {$justify_content_options[ $layout['justifyContent'] ]};";
109 + $style .= '--layout-direction: row;';
110 + $style .= "--layout-wrap: $flex_wrap;";
111 + $style .= "--layout-justify: {$justify_content_options[ $layout['justifyContent'] ]};";
112 + $style .= '--layout-align: center;';
113 + }
479 114 }
480 -
481 - if ( ! empty( $layout['verticalAlignment'] ) && array_key_exists( $layout['verticalAlignment'], $vertical_alignment_options ) ) {
482 - $layout_styles[] = array(
483 - 'selector' => $selector,
484 - 'declarations' => array( 'align-items' => $vertical_alignment_options[ $layout['verticalAlignment'] ] ),
485 - );
486 - }
487 115 } else {
488 - $layout_styles[] = array(
489 - 'selector' => $selector,
490 - 'declarations' => array( 'flex-direction' => 'column' ),
491 - );
116 + $style .= 'flex-direction: column;';
492 117 if ( ! empty( $layout['justifyContent'] ) && array_key_exists( $layout['justifyContent'], $justify_content_options ) ) {
493 - $layout_styles[] = array(
494 - 'selector' => $selector,
495 - 'declarations' => array( 'align-items' => $justify_content_options[ $layout['justifyContent'] ] ),
496 - );
497 - } else {
498 - $layout_styles[] = array(
499 - 'selector' => $selector,
500 - 'declarations' => array( 'align-items' => 'flex-start' ),
501 - );
502 - }
503 - if ( ! empty( $layout['verticalAlignment'] ) && array_key_exists( $layout['verticalAlignment'], $vertical_alignment_options ) ) {
504 - $layout_styles[] = array(
505 - 'selector' => $selector,
506 - 'declarations' => array( 'justify-content' => $vertical_alignment_options[ $layout['verticalAlignment'] ] ),
507 - );
508 - }
509 - }
510 - } elseif ( 'grid' === $layout_type ) {
511 - /*
512 - * If the gap value is an array, we use the "left" value because it represents the vertical gap, which
513 - * is the relevant one for computation of responsive grid columns.
514 - */
515 - if ( is_array( $fallback_gap_value ) ) {
516 - $responsive_gap_value = $fallback_gap_value['left'] ?? reset( $fallback_gap_value );
517 - } else {
518 - $responsive_gap_value = $fallback_gap_value;
519 - }
520 -
521 - if ( $has_block_gap_support && isset( $gap_value ) ) {
522 - $combined_gap_value = '';
523 - $gap_sides = is_array( $gap_value ) ? array( 'top', 'left' ) : array( 'top' );
524 -
525 - foreach ( $gap_sides as $gap_side ) {
526 - $process_value = $gap_value;
527 - if ( is_array( $gap_value ) ) {
528 - if ( is_array( $fallback_gap_value ) ) {
529 - $fallback_value = $fallback_gap_value[ $gap_side ] ?? reset( $fallback_gap_value );
530 - } else {
531 - $fallback_value = $fallback_gap_value;
532 - }
533 - $process_value = $gap_value[ $gap_side ] ?? $fallback_value;
118 + $style .= "align-items: {$justify_content_options[ $layout['justifyContent'] ]};";
119 + if ( ! empty( $layout['setCascadingProperties'] ) && $layout['setCascadingProperties'] ) {
120 + // --layout-justification-setting allows children to inherit the value regardless or row or column direction.
121 + $style .= "--layout-justification-setting: {$justify_content_options[ $layout['justifyContent'] ]};";
122 + $style .= '--layout-direction: column;';
123 + $style .= '--layout-justify: initial;';
124 + $style .= "--layout-align: {$justify_content_options[ $layout['justifyContent'] ]};";
534 125 }
535 - // Get spacing CSS variable from preset value if provided.
536 - if ( is_string( $process_value ) && str_contains( $process_value, 'var:preset|spacing|' ) ) {
537 - $index_to_splice = strrpos( $process_value, '|' ) + 1;
538 - $slug = _wp_to_kebab_case( substr( $process_value, $index_to_splice ) );
539 - $process_value = "var(--wp--preset--spacing--$slug)";
540 - }
541 - $combined_gap_value .= "$process_value ";
542 126 }
543 - $gap_value = trim( $combined_gap_value );
544 - $responsive_gap_value = $gap_value;
545 127 }
128 + $style .= '}';
546 129
547 - // Ensure 0 values have a unit so they work in calc().
548 - if ( '0' === $responsive_gap_value || 0 === $responsive_gap_value ) {
549 - $responsive_gap_value = '0px';
550 - }
551 -
552 - if ( ! empty( $layout['columnCount'] ) && ! empty( $layout['minimumColumnWidth'] ) ) {
553 - $max_value = 'max(min(' . $layout['minimumColumnWidth'] . ', 100%), (100% - (' . $responsive_gap_value . ' * (' . $layout['columnCount'] . ' - 1))) /' . $layout['columnCount'] . ')';
554 - $layout_styles[] = array(
555 - 'selector' => $selector,
556 - 'declarations' => array(
557 - 'grid-template-columns' => 'repeat(auto-fill, minmax(' . $max_value . ', 1fr))',
558 - 'container-type' => 'inline-size',
559 - ),
560 - );
561 - if ( ! empty( $layout['rowCount'] ) ) {
562 - $layout_styles[] = array(
563 - 'selector' => $selector,
564 - 'declarations' => array( 'grid-template-rows' => 'repeat(' . $layout['rowCount'] . ', minmax(1rem, auto))' ),
565 - );
566 - }
567 - } elseif ( ! empty( $layout['columnCount'] ) ) {
568 - $layout_styles[] = array(
569 - 'selector' => $selector,
570 - 'declarations' => array( 'grid-template-columns' => 'repeat(' . $layout['columnCount'] . ', minmax(0, 1fr))' ),
571 - );
572 - if ( ! empty( $layout['rowCount'] ) ) {
573 - $layout_styles[] = array(
574 - 'selector' => $selector,
575 - 'declarations' => array( 'grid-template-rows' => 'repeat(' . $layout['rowCount'] . ', minmax(1rem, auto))' ),
576 - );
577 - }
578 - } else {
579 - $minimum_column_width = ! empty( $layout['minimumColumnWidth'] ) ? $layout['minimumColumnWidth'] : '12rem';
580 -
581 - $layout_styles[] = array(
582 - 'selector' => $selector,
583 - 'declarations' => array(
584 - 'grid-template-columns' => 'repeat(auto-fill, minmax(min(' . $minimum_column_width . ', 100%), 1fr))',
585 - 'container-type' => 'inline-size',
586 - ),
587 - );
588 - }
589 -
590 - if ( $has_block_gap_support && null !== $gap_value && ! $should_skip_gap_serialization ) {
591 - $layout_styles[] = array(
592 - 'selector' => $selector,
593 - 'declarations' => array( 'gap' => $gap_value ),
594 - );
595 - }
130 + $style .= "$selector > * { margin: 0; }";
596 131 }
597 132
598 - if ( ! empty( $layout_styles ) ) {
599 - /*
600 - * Add to the style engine store to enqueue and render layout styles.
601 - * Return compiled layout styles to retain backwards compatibility.
602 - * Since https://github.com/WordPress/gutenberg/pull/42452,
603 - * wp_enqueue_block_support_styles is no longer called in this block supports file.
604 - */
605 - return gutenberg_style_engine_get_stylesheet_from_css_rules(
606 - $layout_styles,
607 - array(
608 - 'context' => 'block-supports',
609 - 'prettify' => false,
610 - )
611 - );
612 - }
613 -
614 - return '';
133 + return $style;
615 134 }
616 135
617 136 /**
618 - * Generates an incremental ID that is independent per each different prefix.
619 - *
620 - * It is similar to `wp_unique_id`, but each prefix has it's own internal ID
621 - * counter to make each prefix independent from each other. The ID starts at 1
622 - * and increments on each call. The returned value is not universally unique,
623 - * but it is unique across the life of the PHP process and it's stable per
624 - * prefix.
625 - *
626 - * @param string $prefix Prefix for the returned ID.
627 - * @return string Incremental ID per prefix.
628 - */
629 -function gutenberg_incremental_id_per_prefix( $prefix = '' ) {
630 - static $id_counters = array();
631 - if ( ! array_key_exists( $prefix, $id_counters ) ) {
632 - $id_counters[ $prefix ] = 0;
633 - }
634 - return $prefix . (string) ++$id_counters[ $prefix ];
635 -}
636 -
637 -/**
638 - * Generates a unique ID based on the structure and values of a given array.
639 - *
640 - * This function serializes the array into a JSON string and generates a hash
641 - * that serves as a unique identifier. Optionally, a prefix can be added to
642 - * the generated ID for context or categorization.
643 - *
644 - * @param array $data The input array to generate an ID from.
645 - * @param string $prefix Optional. A prefix to prepend to the generated ID. Default ''.
646 - *
647 - * @return string The generated unique ID for the array.
648 - */
649 -function gutenberg_unique_id_from_values( array $data, string $prefix = '' ): string {
650 - $serialized = wp_json_encode( $data );
651 - $hash = substr( md5( $serialized ), 0, 8 );
652 - return $prefix . $hash;
653 -}
654 -
655 -/**
656 137 * Renders the layout config to the block wrapper.
657 138 *
658 139 * @param string $block_content Rendered block content.
659 140 * @param array $block Block object.
@@ -659,456 +140,51 @@
659 140 * @param array $block Block object.
660 141 * @return string Filtered block content.
661 142 */
662 143 function gutenberg_render_layout_support_flag( $block_content, $block ) {
663 - static $global_styles = null;
144 + $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] );
145 + $support_layout = gutenberg_block_has_support( $block_type, array( '__experimentalLayout' ), false );
664 146
665 - $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] );
666 - $block_supports_layout = block_has_support( $block_type, array( 'layout' ), false ) || block_has_support( $block_type, array( '__experimentalLayout' ), false );
667 - // If there is any value in style -> layout, the block has a child layout.
668 - $child_layout = $block['attrs']['style']['layout'] ?? null;
669 -
670 - if ( ! $block_supports_layout && ! $child_layout ) {
147 + if ( ! $support_layout ) {
671 148 return $block_content;
672 149 }
673 150
674 - $outer_class_names = array();
675 -
676 - // Child layout specific logic.
677 - if ( $child_layout ) {
678 - /*
679 - * Generates a unique class for child block layout styles.
680 - *
681 - * To ensure consistent class generation across different page renders,
682 - * only properties that affect layout styling are used. These properties
683 - * come from `$block['attrs']['style']['layout']` and `$block['parentLayout']`.
684 - *
685 - * As long as these properties coincide, the generated class will be the same.
686 - */
687 - $container_content_class = gutenberg_unique_id_from_values(
688 - array(
689 - 'layout' => array_intersect_key(
690 - $block['attrs']['style']['layout'] ?? array(),
691 - array_flip(
692 - array( 'selfStretch', 'flexSize', 'columnStart', 'columnSpan', 'rowStart', 'rowSpan' )
693 - )
694 - ),
695 - 'parentLayout' => array_intersect_key(
696 - $block['parentLayout'] ?? array(),
697 - array_flip(
698 - array( 'minimumColumnWidth', 'columnCount' )
699 - )
700 - ),
701 - ),
702 - 'wp-container-content-'
703 - );
704 -
705 - $child_layout_declarations = array();
706 - $child_layout_styles = array();
707 -
708 - $self_stretch = $block['attrs']['style']['layout']['selfStretch'] ?? null;
709 -
710 - if ( 'fixed' === $self_stretch && isset( $block['attrs']['style']['layout']['flexSize'] ) ) {
711 - $child_layout_declarations['flex-basis'] = $block['attrs']['style']['layout']['flexSize'];
712 - $child_layout_declarations['box-sizing'] = 'border-box';
713 - } elseif ( 'fill' === $self_stretch ) {
714 - $child_layout_declarations['flex-grow'] = '1';
151 + $block_gap = wp_get_global_settings( array( 'spacing', 'blockGap' ) );
152 + $default_layout = wp_get_global_settings( array( 'layout' ) );
153 + $has_block_gap_support = isset( $block_gap ) ? null !== $block_gap : false;
154 + $default_block_layout = _wp_array_get( $block_type->supports, array( '__experimentalLayout', 'default' ), array() );
155 + $used_layout = isset( $block['attrs']['layout'] ) ? $block['attrs']['layout'] : $default_block_layout;
156 + if ( isset( $used_layout['inherit'] ) && $used_layout['inherit'] ) {
157 + if ( ! $default_layout ) {
158 + return $block_content;
715 159 }
716 -
717 - $column_start = $block['attrs']['style']['layout']['columnStart'] ?? null;
718 - $column_span = $block['attrs']['style']['layout']['columnSpan'] ?? null;
719 - if ( $column_start && $column_span ) {
720 - $child_layout_declarations['grid-column'] = "$column_start / span $column_span";
721 - } elseif ( $column_start ) {
722 - $child_layout_declarations['grid-column'] = "$column_start";
723 - } elseif ( $column_span ) {
724 - $child_layout_declarations['grid-column'] = "span $column_span";
725 - }
726 -
727 - $row_start = $block['attrs']['style']['layout']['rowStart'] ?? null;
728 - $row_span = $block['attrs']['style']['layout']['rowSpan'] ?? null;
729 - if ( $row_start && $row_span ) {
730 - $child_layout_declarations['grid-row'] = "$row_start / span $row_span";
731 - } elseif ( $row_start ) {
732 - $child_layout_declarations['grid-row'] = "$row_start";
733 - } elseif ( $row_span ) {
734 - $child_layout_declarations['grid-row'] = "span $row_span";
735 - }
736 -
737 - $child_layout_styles[] = array(
738 - 'selector' => ".$container_content_class",
739 - 'declarations' => $child_layout_declarations,
740 - );
741 -
742 - $minimum_column_width = $block['parentLayout']['minimumColumnWidth'] ?? null;
743 - $column_count = $block['parentLayout']['columnCount'] ?? null;
744 -
745 - /*
746 - * If columnSpan or columnStart is set, and the parent grid is responsive, i.e. if it has a minimumColumnWidth set,
747 - * the columnSpan should be removed once the grid is smaller than the span, and columnStart should be removed
748 - * once the grid has less columns than the start.
749 - * If there's a minimumColumnWidth, the grid is responsive. But if the minimumColumnWidth value wasn't changed, it won't be set.
750 - * In that case, if columnCount doesn't exist, we can assume that the grid is responsive.
751 - */
752 - if ( ( $column_span || $column_start ) && ( $minimum_column_width || ! $column_count ) ) {
753 - $column_span_number = floatval( $column_span );
754 - $column_start_number = floatval( $column_start );
755 - $parent_column_width = $minimum_column_width ? $minimum_column_width : '12rem';
756 - $parent_column_value = floatval( $parent_column_width );
757 - $parent_column_unit = explode( $parent_column_value, $parent_column_width );
758 -
759 - $num_cols_to_break_at = 2;
760 - if ( $column_span_number && $column_start_number ) {
761 - $num_cols_to_break_at = $column_start_number + $column_span_number - 1;
762 - } elseif ( $column_span_number ) {
763 - $num_cols_to_break_at = $column_span_number;
764 - } else {
765 - $num_cols_to_break_at = $column_start_number;
766 - }
767 -
768 - /*
769 - * If there is no unit, the width has somehow been mangled so we reset both unit and value
770 - * to defaults.
771 - * Additionally, the unit should be one of px, rem or em, so that also needs to be checked.
772 - */
773 - if ( count( $parent_column_unit ) <= 1 ) {
774 - $parent_column_unit = 'rem';
775 - $parent_column_value = 12;
776 - } else {
777 - $parent_column_unit = $parent_column_unit[1];
778 -
779 - if ( ! in_array( $parent_column_unit, array( 'px', 'rem', 'em' ), true ) ) {
780 - $parent_column_unit = 'rem';
781 - }
782 - }
783 -
784 - /*
785 - * A default gap value is used for this computation because custom gap values may not be
786 - * viable to use in the computation of the container query value.
787 - */
788 - $default_gap_value = 'px' === $parent_column_unit ? 24 : 1.5;
789 - $container_query_value = $num_cols_to_break_at * $parent_column_value + ( $num_cols_to_break_at - 1 ) * $default_gap_value;
790 - $minimum_container_query_value = $parent_column_value * 2 + $default_gap_value - 1;
791 - $container_query_value = max( $container_query_value, $minimum_container_query_value ) . $parent_column_unit;
792 - // If a span is set we want to preserve it as long as possible, otherwise we just reset the value.
793 - $grid_column_value = $column_span && $column_span > 1 ? '1/-1' : 'auto';
794 -
795 - $child_layout_styles[] = array(
796 - 'rules_group' => "@container (max-width: $container_query_value )",
797 - 'selector' => ".$container_content_class",
798 - 'declarations' => array(
799 - 'grid-column' => $grid_column_value,
800 - 'grid-row' => 'auto',
801 - ),
802 - );
803 - }
804 -
805 - /*
806 - * Add to the style engine store to enqueue and render layout styles.
807 - * Return styles here just to check if any exist.
808 - */
809 - $child_css = gutenberg_style_engine_get_stylesheet_from_css_rules(
810 - $child_layout_styles,
811 - array(
812 - 'context' => 'block-supports',
813 - 'prettify' => false,
814 - )
815 - );
816 -
817 - if ( $child_css ) {
818 - $outer_class_names[] = $container_content_class;
819 - }
160 + $used_layout = $default_layout;
820 161 }
821 162
822 - // Prep the processor for modifying the block output.
823 - $processor = new WP_HTML_Tag_Processor( $block_content );
163 + $id = uniqid();
164 + $style = gutenberg_get_layout_style( ".wp-container-$id", $used_layout, $has_block_gap_support );
165 + // This assumes the hook only applies to blocks with a single wrapper.
166 + // I think this is a reasonable limitation for that particular hook.
167 + $content = preg_replace(
168 + '/' . preg_quote( 'class="', '/' ) . '/',
169 + 'class="wp-container-' . $id . ' ',
170 + $block_content,
171 + 1
172 + );
824 173
825 - // Having no tags implies there are no tags onto which to add class names.
826 - if ( ! $processor->next_tag() ) {
827 - return $block_content;
828 - }
829 -
830 - /*
831 - * A block may not support layout but still be affected by a parent block's layout.
832 - *
833 - * In these cases add the appropriate class names and then return early; there's
834 - * no need to investigate on this block whether additional layout constraints apply.
835 - */
836 - if ( ! $block_supports_layout && ! empty( $outer_class_names ) ) {
837 - foreach ( $outer_class_names as $class_name ) {
838 - $processor->add_class( $class_name );
174 + // Ideally styles should be loaded in the head, but blocks may be parsed
175 + // after that, so loading in the footer for now.
176 + // See https://core.trac.wordpress.org/ticket/53494.
177 + add_action(
178 + 'wp_footer',
179 + function () use ( $style ) {
180 + echo '<style>' . $style . '</style>';
839 181 }
840 - return $processor->get_updated_html();
841 - } elseif ( ! $block_supports_layout ) {
842 - // Ensure layout classnames are not injected if there is no layout support.
843 - return $block_content;
844 - }
182 + );
845 183
846 - $global_settings = gutenberg_get_global_settings();
847 - $fallback_layout = $block_type->supports['layout']['default'] ?? array();
848 - if ( empty( $fallback_layout ) ) {
849 - $fallback_layout = $block_type->supports['__experimentalLayout']['default'] ?? array();
850 - }
851 - $used_layout = $block['attrs']['layout'] ?? $fallback_layout;
852 -
853 - $class_names = array();
854 - $layout_definitions = gutenberg_get_layout_definitions();
855 -
856 - // Set the correct layout type for blocks using legacy content width.
857 - if ( isset( $used_layout['inherit'] ) && $used_layout['inherit'] || isset( $used_layout['contentSize'] ) && $used_layout['contentSize'] ) {
858 - $used_layout['type'] = 'constrained';
859 - }
860 -
861 - $root_padding_aware_alignments = $global_settings['useRootPaddingAwareAlignments'] ?? false;
862 -
863 - if ( $root_padding_aware_alignments && isset( $used_layout['type'] ) && 'constrained' === $used_layout['type'] ) {
864 - $class_names[] = 'has-global-padding';
865 - }
866 -
867 - /*
868 - * The following section was added to reintroduce a small set of layout classnames that were
869 - * removed in the 5.9 release (https://github.com/WordPress/gutenberg/issues/38719). It is
870 - * not intended to provide an extended set of classes to match all block layout attributes
871 - * here.
872 - */
873 - if ( ! empty( $block['attrs']['layout']['orientation'] ) ) {
874 - $class_names[] = 'is-' . sanitize_title( $block['attrs']['layout']['orientation'] );
875 - }
876 -
877 - if ( ! empty( $block['attrs']['layout']['justifyContent'] ) ) {
878 - $class_names[] = 'is-content-justification-' . sanitize_title( $block['attrs']['layout']['justifyContent'] );
879 - }
880 -
881 - if ( ! empty( $block['attrs']['layout']['flexWrap'] ) && 'nowrap' === $block['attrs']['layout']['flexWrap'] ) {
882 - $class_names[] = 'is-nowrap';
883 - }
884 -
885 - // Get classname for layout type.
886 - if ( isset( $used_layout['type'] ) ) {
887 - $layout_classname = $layout_definitions[ $used_layout['type'] ]['className'] ?? '';
888 - } else {
889 - $layout_classname = $layout_definitions['default']['className'] ?? '';
890 - }
891 -
892 - if ( $layout_classname && is_string( $layout_classname ) ) {
893 - $class_names[] = sanitize_title( $layout_classname );
894 - }
895 -
896 - /*
897 - * Only generate Layout styles if the theme has not opted-out.
898 - * Attribute-based Layout classnames are output in all cases.
899 - */
900 - if ( ! current_theme_supports( 'disable-layout-styles' ) ) {
901 -
902 - $gap_value = $block['attrs']['style']['spacing']['blockGap'] ?? null;
903 -
904 - /*
905 - * Skip if gap value contains unsupported characters.
906 - * Regex for CSS value borrowed from `safecss_filter_attr`, and used here
907 - * to only match against the value, not the CSS attribute.
908 - */
909 - if ( is_array( $gap_value ) ) {
910 - foreach ( $gap_value as $key => $value ) {
911 - $gap_value[ $key ] = $value && preg_match( '%[\\\(&=}]|/\*%', $value ) ? null : $value;
912 - }
913 - } else {
914 - $gap_value = $gap_value && preg_match( '%[\\\(&=}]|/\*%', $gap_value ) ? null : $gap_value;
915 - }
916 -
917 - $fallback_gap_value = $block_type->supports['spacing']['blockGap']['__experimentalDefault'] ?? '0.5em';
918 - $block_spacing = $block['attrs']['style']['spacing'] ?? null;
919 -
920 - /*
921 - * If a block's block.json skips serialization for spacing or spacing.blockGap,
922 - * don't apply the user-defined value to the styles.
923 - */
924 - $should_skip_gap_serialization = wp_should_skip_block_supports_serialization( $block_type, 'spacing', 'blockGap' );
925 -
926 - $block_gap = $global_settings['spacing']['blockGap'] ?? null;
927 - $has_block_gap_support = isset( $block_gap );
928 -
929 - // Get default blockGap value from global styles for use in layouts like grid.
930 - // Check style variation first, then block-specific styles, then fall back to root styles.
931 - $block_name = $block['blockName'] ?? '';
932 - if ( null === $global_styles ) {
933 - $global_styles = gutenberg_get_global_styles();
934 - }
935 -
936 - // Check if the block has an active style variation with a blockGap value.
937 - // Only check the registry if the className contains a variation class to avoid unnecessary lookups.
938 - $variation_block_gap_value = null;
939 - $block_class_name = $block['attrs']['className'] ?? '';
940 - if ( $block_class_name && str_contains( $block_class_name, 'is-style-' ) && $block_name ) {
941 - $styles_registry = WP_Block_Styles_Registry::get_instance();
942 - $registered_styles = $styles_registry->get_registered_styles_for_block( $block_name );
943 - $variation_name = gutenberg_get_block_style_variation_name_from_registered_style( $block_class_name, $registered_styles );
944 - if ( $variation_name ) {
945 - $variation_block_gap_value = $global_styles['blocks'][ $block_name ]['variations'][ $variation_name ]['spacing']['blockGap'] ?? null;
946 - }
947 - }
948 -
949 - $global_block_gap_value = $variation_block_gap_value ?? $global_styles['blocks'][ $block_name ]['spacing']['blockGap'] ?? $global_styles['spacing']['blockGap'] ?? null;
950 -
951 - if ( null !== $global_block_gap_value ) {
952 - $fallback_gap_value = $global_block_gap_value;
953 - }
954 -
955 - /*
956 - * We generate a unique ID based on all the data required to obtain the
957 - * corresponding layout style. This way, the CSS class names keep the same
958 - * even for different blocks with the same layout definition. We need this to
959 - * make the CSS class names stable across paginations for features like the
960 - * enhanced pagination of the Query block.
961 - */
962 - $container_class = gutenberg_unique_id_from_values(
963 - array(
964 - $used_layout,
965 - $has_block_gap_support,
966 - $gap_value,
967 - $should_skip_gap_serialization,
968 - $fallback_gap_value,
969 - $block_spacing,
970 - ),
971 - 'wp-container-' . sanitize_title( $block['blockName'] ) . '-is-layout-'
972 - );
973 -
974 - $style = gutenberg_get_layout_style(
975 - ".$container_class",
976 - $used_layout,
977 - $has_block_gap_support,
978 - $gap_value,
979 - $should_skip_gap_serialization,
980 - $fallback_gap_value,
981 - $block_spacing
982 - );
983 -
984 - // Only add container class and enqueue block support styles if unique styles were generated.
985 - if ( ! empty( $style ) ) {
986 - $class_names[] = $container_class;
987 - }
988 - }
989 -
990 - // Add combined layout and block classname for global styles to hook onto.
991 - $split_block_name = explode( '/', $block['blockName'] );
992 - $full_block_name = 'core' === $split_block_name[0] ? end( $split_block_name ) : implode( '-', $split_block_name );
993 - $class_names[] = 'wp-block-' . $full_block_name . '-' . $layout_classname;
994 -
995 - // Add classes to the outermost HTML tag if necessary.
996 - if ( ! empty( $outer_class_names ) ) {
997 - foreach ( $outer_class_names as $outer_class_name ) {
998 - $processor->add_class( $outer_class_name );
999 - }
1000 - }
1001 -
1002 - /*
1003 - * Attempts to refer to the inner-block wrapping element by its class attribute.
1004 - *
1005 - * When examining a block's inner content, if a block has inner blocks, then
1006 - * the first content item will likely be a text (HTML) chunk immediately
1007 - * preceding the inner blocks. The last HTML tag in that chunk would then be
1008 - * an opening tag for an element that wraps the inner blocks.
1009 - *
1010 - * There's no reliable way to associate this wrapper in $block_content because
1011 - * it may have changed during the rendering pipeline (as inner contents is
1012 - * provided before rendering) and through previous filters. In many cases,
1013 - * however, the `class` attribute will be a good-enough identifier, so this
1014 - * code finds the last tag in that chunk and stores the `class` attribute
1015 - * so that it can be used later when working through the rendered block output
1016 - * to identify the wrapping element and add the remaining class names to it.
1017 - *
1018 - * It's also possible that no inner block wrapper even exists. If that's the
1019 - * case this code could apply the class names to an invalid element.
1020 - *
1021 - * Example:
1022 - *
1023 - * $block['innerBlocks'] = array( $list_item );
1024 - * $block['innerContent'] = array( '<ul class="list-wrapper is-unordered">', null, '</ul>' );
1025 - *
1026 - * // After rendering, the initial contents may have been modified by other renderers or filters.
1027 - * $block_content = <<<HTML
1028 - * <figure>
1029 - * <ul class="annotated-list list-wrapper is-unordered">
1030 - * <li>Code</li>
1031 - * </ul><figcaption>It's a list!</figcaption>
1032 - * </figure>
1033 - * HTML;
1034 - *
1035 - * Although it is possible that the original block-wrapper classes are changed in $block_content
1036 - * from how they appear in $block['innerContent'], it's likely that the original class attributes
1037 - * are still present in the wrapper as they are in this example. Frequently, additional classes
1038 - * will also be present; rarely should classes be removed.
1039 - *
1040 - * @todo Find a better way to match the first inner block. If it's possible to identify where the
1041 - * first inner block starts, then it will be possible to find the last tag before it starts
1042 - * and then that tag, if an opening tag, can be solidly identified as a wrapping element.
1043 - * Can some unique value or class or ID be added to the inner blocks when they process
1044 - * so that they can be extracted here safely without guessing? Can the block rendering function
1045 - * return information about where the rendered inner blocks start?
1046 - *
1047 - * @var string|null
1048 - */
1049 - $inner_block_wrapper_classes = null;
1050 - $first_chunk = $block['innerContent'][0] ?? null;
1051 - if ( is_string( $first_chunk ) && count( $block['innerContent'] ) > 1 ) {
1052 - $first_chunk_processor = new WP_HTML_Tag_Processor( $first_chunk );
1053 - while ( $first_chunk_processor->next_tag() ) {
1054 - $class_attribute = $first_chunk_processor->get_attribute( 'class' );
1055 - if ( is_string( $class_attribute ) && ! empty( $class_attribute ) ) {
1056 - $inner_block_wrapper_classes = $class_attribute;
1057 - }
1058 - }
1059 - }
1060 -
1061 - /*
1062 - * If necessary, advance to what is likely to be an inner block wrapper tag.
1063 - *
1064 - * This advances until it finds the first tag containing the original class
1065 - * attribute from above. If none is found it will scan to the end of the block
1066 - * and fail to add any class names.
1067 - *
1068 - * If there is no block wrapper it won't advance at all, in which case the
1069 - * class names will be added to the first and outermost tag of the block.
1070 - * For cases where this outermost tag is the only tag surrounding inner
1071 - * blocks then the outer wrapper and inner wrapper are the same.
1072 - */
1073 - do {
1074 - if ( ! $inner_block_wrapper_classes ) {
1075 - break;
1076 - }
1077 -
1078 - $class_attribute = $processor->get_attribute( 'class' );
1079 - if ( is_string( $class_attribute ) && str_contains( $class_attribute, $inner_block_wrapper_classes ) ) {
1080 - break;
1081 - }
1082 - } while ( $processor->next_tag() );
1083 -
1084 - // Add the remaining class names.
1085 - foreach ( $class_names as $class_name ) {
1086 - $processor->add_class( $class_name );
1087 - }
1088 -
1089 - return $processor->get_updated_html();
184 + return $content;
1090 185 }
1091 186
1092 -/*
1093 - * Add a `render_block_data` filter to fetch the parent block layout data.
1094 - */
1095 -add_filter(
1096 - 'render_block_data',
1097 - function ( $parsed_block, $source_block, $parent_block ) {
1098 - /*
1099 - * Check if the parent block exists and if it has a layout attribute.
1100 - * If it does, add the parent layout to the parsed block.
1101 - */
1102 - if ( $parent_block && isset( $parent_block->parsed_block['attrs']['layout'] ) ) {
1103 - $parsed_block['parentLayout'] = $parent_block->parsed_block['attrs']['layout'];
1104 - }
1105 - return $parsed_block;
1106 - },
1107 - 10,
1108 - 3
1109 -);
1110 -
1111 187 // Register the block support. (overrides core one).
1112 188 WP_Block_Supports::get_instance()->register(
1113 189 'layout',
1114 190 array(
@@ -1114,9 +190,8 @@
1114 190 array(
1115 191 'register_attribute' => 'gutenberg_register_layout_support',
1116 192 )
1117 193 );
1118 -
1119 194 if ( function_exists( 'wp_render_layout_support_flag' ) ) {
1120 195 remove_filter( 'render_block', 'wp_render_layout_support_flag' );
1121 196 }
1122 197 add_filter( 'render_block', 'gutenberg_render_layout_support_flag', 10, 2 );
@@ -1130,129 +205,36 @@
1130 205 * @param array $block Block object.
1131 206 * @return string Filtered block content.
1132 207 */
1133 208 function gutenberg_restore_group_inner_container( $block_content, $block ) {
1134 - $tag_name = $block['attrs']['tagName'] ?? 'div';
209 + $tag_name = isset( $block['attrs']['tagName'] ) ? $block['attrs']['tagName'] : 'div';
1135 210 $group_with_inner_container_regex = sprintf(
1136 211 '/(^\s*<%1$s\b[^>]*wp-block-group(\s|")[^>]*>)(\s*<div\b[^>]*wp-block-group__inner-container(\s|")[^>]*>)((.|\S|\s)*)/U',
1137 212 preg_quote( $tag_name, '/' )
1138 213 );
1139 214 if (
1140 - wp_theme_has_theme_json() ||
215 + 'core/group' !== $block['blockName'] ||
216 + WP_Theme_JSON_Resolver_Gutenberg::theme_has_support() ||
1141 217 1 === preg_match( $group_with_inner_container_regex, $block_content ) ||
1142 - ( isset( $block['attrs']['layout']['type'] ) && ( 'flex' === $block['attrs']['layout']['type'] || 'grid' === $block['attrs']['layout']['type'] ) )
218 + ( isset( $block['attrs']['layout']['type'] ) && 'default' !== $block['attrs']['layout']['type'] )
1143 219 ) {
1144 220 return $block_content;
1145 221 }
1146 222
1147 - /*
1148 - * This filter runs after the layout classnames have been added to the block, so they
1149 - * have to be removed from the outer wrapper and then added to the inner.
1150 - */
1151 - $layout_classes = array();
1152 - $processor = new WP_HTML_Tag_Processor( $block_content );
1153 -
1154 - if ( $processor->next_tag( array( 'class_name' => 'wp-block-group' ) ) ) {
1155 - foreach ( $processor->class_list() as $class_name ) {
1156 - if ( str_contains( $class_name, 'layout' ) ) {
1157 - array_push( $layout_classes, $class_name );
1158 - $processor->remove_class( $class_name );
1159 - }
1160 - }
1161 - }
1162 -
1163 - $content_without_layout_classes = $processor->get_updated_html();
1164 - $replace_regex = sprintf(
223 + $replace_regex = sprintf(
1165 224 '/(^\s*<%1$s\b[^>]*wp-block-group[^>]*>)(.*)(<\/%1$s>\s*$)/ms',
1166 225 preg_quote( $tag_name, '/' )
1167 226 );
1168 - $updated_content = preg_replace_callback(
227 + $updated_content = preg_replace_callback(
1169 228 $replace_regex,
1170 - static function ( $matches ) {
229 + function( $matches ) {
1171 230 return $matches[1] . '<div class="wp-block-group__inner-container">' . $matches[2] . '</div>' . $matches[3];
1172 231 },
1173 - $content_without_layout_classes
232 + $block_content
1174 233 );
1175 -
1176 - // Add layout classes to inner wrapper.
1177 - if ( ! empty( $layout_classes ) ) {
1178 - $processor = new WP_HTML_Tag_Processor( $updated_content );
1179 - if ( $processor->next_tag( array( 'class_name' => 'wp-block-group__inner-container' ) ) ) {
1180 - foreach ( $layout_classes as $class_name ) {
1181 - $processor->add_class( $class_name );
1182 - }
1183 - }
1184 - $updated_content = $processor->get_updated_html();
1185 - }
1186 -
1187 234 return $updated_content;
1188 235 }
1189 236
1190 237 if ( function_exists( 'wp_restore_group_inner_container' ) ) {
1191 - remove_filter( 'render_block', 'wp_restore_group_inner_container', 10 );
1192 - remove_filter( 'render_block_core/group', 'wp_restore_group_inner_container', 10 );
238 + remove_filter( 'render_block', 'wp_restore_group_inner_container', 10, 2 );
1193 239 }
1194 -add_filter( 'render_block_core/group', 'gutenberg_restore_group_inner_container', 10, 2 );
1195 -
1196 -/**
1197 - * For themes without theme.json file, make sure
1198 - * to restore the outer div for the aligned image block
1199 - * to avoid breaking styles relying on that div.
1200 - *
1201 - * @param string $block_content Rendered block content.
1202 - * @param array $block Block object.
1203 - * @return string Filtered block content.
1204 - */
1205 -function gutenberg_restore_image_outer_container( $block_content, $block ) {
1206 - if ( wp_theme_has_theme_json() ) {
1207 - return $block_content;
1208 - }
1209 -
1210 - $figure_processor = new WP_HTML_Tag_Processor( $block_content );
1211 - if (
1212 - ! $figure_processor->next_tag( 'FIGURE' ) ||
1213 - ! $figure_processor->has_class( 'wp-block-image' ) ||
1214 - ! (
1215 - $figure_processor->has_class( 'alignleft' ) ||
1216 - $figure_processor->has_class( 'aligncenter' ) ||
1217 - $figure_processor->has_class( 'alignright' )
1218 - )
1219 - ) {
1220 - return $block_content;
1221 - }
1222 -
1223 - /*
1224 - * The next section of code wraps the existing figure in a new DIV element.
1225 - * While doing it, it needs to transfer the layout and the additional CSS
1226 - * class names from the original figure upward to the wrapper.
1227 - *
1228 - * Example:
1229 - *
1230 - * // From this…
1231 - * <!-- wp:image {"className":"hires"} -->
1232 - * <figure class="wp-block-image wide hires">…
1233 - *
1234 - * // To this…
1235 - * <div class="wp-block-image hires"><figure class="wide">…
1236 - */
1237 - $wrapper_processor = new WP_HTML_Tag_Processor( '<div>' );
1238 - $wrapper_processor->next_token();
1239 - $wrapper_processor->set_attribute(
1240 - 'class',
1241 - is_string( $block['attrs']['className'] ?? null )
1242 - ? "wp-block-image {$block['attrs']['className']}"
1243 - : 'wp-block-image'
1244 - );
1245 -
1246 - // And remove them from the existing content; it has been transferred upward.
1247 - $figure_processor->remove_class( 'wp-block-image' );
1248 - foreach ( $wrapper_processor->class_list() as $class_name ) {
1249 - $figure_processor->remove_class( $class_name );
1250 - }
1251 -
1252 - return "{$wrapper_processor->get_updated_html()}{$figure_processor->get_updated_html()}</div>";
1253 -}
1254 -
1255 -if ( function_exists( 'wp_restore_image_outer_container' ) ) {
1256 - remove_filter( 'render_block_core/image', 'wp_restore_image_outer_container', 10 );
1257 -}
1258 -add_filter( 'render_block_core/image', 'gutenberg_restore_image_outer_container', 10, 2 );
240 +add_filter( 'render_block', 'gutenberg_restore_group_inner_container', 10, 2 );