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