PluginProbe
Gutenberg / 16.2.0
Gutenberg v16.2.0
24.0.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 All 403 releases
gutenberg / lib / block-supports / layout.php

layout.php in Gutenberg 16.2.0, at lib/block-supports/layout.php

854 lines 28.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Layout block support flag.
4 *
5 * @package gutenberg
6 */
7
8 /**
9 * Returns layout definitions, keyed by layout type.
10 *
11 * Provides a common definition of slugs, classnames, base styles, and spacing styles for each layout type.
12 * When making changes or additions to layout definitions, the corresponding JavaScript definitions should
13 * also be updated.
14 *
15 * @return array[] Layout definitions.
16 */
17 function gutenberg_get_layout_definitions() {
18 $layout_definitions = array(
19 'default' => array(
20 'name' => 'default',
21 'slug' => 'flow',
22 'className' => 'is-layout-flow',
23 'baseStyles' => array(
24 array(
25 'selector' => ' > .alignleft',
26 'rules' => array(
27 'float' => 'left',
28 'margin-inline-start' => '0',
29 'margin-inline-end' => '2em',
30 ),
31 ),
32 array(
33 'selector' => ' > .alignright',
34 'rules' => array(
35 'float' => 'right',
36 'margin-inline-start' => '2em',
37 'margin-inline-end' => '0',
38 ),
39 ),
40 array(
41 'selector' => ' > .aligncenter',
42 'rules' => array(
43 'margin-left' => 'auto !important',
44 'margin-right' => 'auto !important',
45 ),
46 ),
47 ),
48 'spacingStyles' => array(
49 array(
50 'selector' => ' > :first-child:first-child',
51 'rules' => array(
52 'margin-block-start' => '0',
53 ),
54 ),
55 array(
56 'selector' => ' > :last-child:last-child',
57 'rules' => array(
58 'margin-block-end' => '0',
59 ),
60 ),
61 array(
62 'selector' => ' > *',
63 'rules' => array(
64 'margin-block-start' => null,
65 'margin-block-end' => '0',
66 ),
67 ),
68 ),
69 ),
70 'constrained' => array(
71 'name' => 'constrained',
72 'slug' => 'constrained',
73 'className' => 'is-layout-constrained',
74 'baseStyles' => array(
75 array(
76 'selector' => ' > .alignleft',
77 'rules' => array(
78 'float' => 'left',
79 'margin-inline-start' => '0',
80 'margin-inline-end' => '2em',
81 ),
82 ),
83 array(
84 'selector' => ' > .alignright',
85 'rules' => array(
86 'float' => 'right',
87 'margin-inline-start' => '2em',
88 'margin-inline-end' => '0',
89 ),
90 ),
91 array(
92 'selector' => ' > .aligncenter',
93 'rules' => array(
94 'margin-left' => 'auto !important',
95 'margin-right' => 'auto !important',
96 ),
97 ),
98 array(
99 'selector' => ' > :where(:not(.alignleft):not(.alignright):not(.alignfull))',
100 'rules' => array(
101 'max-width' => 'var(--wp--style--global--content-size)',
102 'margin-left' => 'auto !important',
103 'margin-right' => 'auto !important',
104 ),
105 ),
106 array(
107 'selector' => ' > .alignwide',
108 'rules' => array(
109 'max-width' => 'var(--wp--style--global--wide-size)',
110 ),
111 ),
112 ),
113 'spacingStyles' => array(
114 array(
115 'selector' => ' > :first-child:first-child',
116 'rules' => array(
117 'margin-block-start' => '0',
118 ),
119 ),
120 array(
121 'selector' => ' > :last-child:last-child',
122 'rules' => array(
123 'margin-block-end' => '0',
124 ),
125 ),
126 array(
127 'selector' => ' > *',
128 'rules' => array(
129 'margin-block-start' => null,
130 'margin-block-end' => '0',
131 ),
132 ),
133 ),
134 ),
135 'flex' => array(
136 'name' => 'flex',
137 'slug' => 'flex',
138 'className' => 'is-layout-flex',
139 'displayMode' => 'flex',
140 'baseStyles' => array(
141 array(
142 'selector' => '',
143 'rules' => array(
144 'flex-wrap' => 'wrap',
145 'align-items' => 'center',
146 ),
147 ),
148 array(
149 'selector' => ' > *',
150 'rules' => array(
151 'margin' => '0',
152 ),
153 ),
154 ),
155 'spacingStyles' => array(
156 array(
157 'selector' => '',
158 'rules' => array(
159 'gap' => null,
160 ),
161 ),
162 ),
163 ),
164 'grid' => array(
165 'name' => 'grid',
166 'slug' => 'grid',
167 'className' => 'is-layout-grid',
168 'displayMode' => 'grid',
169 'baseStyles' => array(
170 array(
171 'selector' => ' > *',
172 'rules' => array(
173 'margin' => '0',
174 ),
175 ),
176 ),
177 'spacingStyles' => array(
178 array(
179 'selector' => '',
180 'rules' => array(
181 'gap' => null,
182 ),
183 ),
184 ),
185 ),
186 );
187
188 return $layout_definitions;
189 }
190
191 /**
192 * Registers the layout block attribute for block types that support it.
193 *
194 * @param WP_Block_Type $block_type Block Type.
195 */
196 function gutenberg_register_layout_support( $block_type ) {
197 $support_layout = block_has_support( $block_type, array( 'layout' ), false ) || block_has_support( $block_type, array( '__experimentalLayout' ), false );
198 if ( $support_layout ) {
199 if ( ! $block_type->attributes ) {
200 $block_type->attributes = array();
201 }
202
203 if ( ! array_key_exists( 'layout', $block_type->attributes ) ) {
204 $block_type->attributes['layout'] = array(
205 'type' => 'object',
206 );
207 }
208 }
209 }
210
211 /**
212 * Generates the CSS corresponding to the provided layout.
213 *
214 * @param string $selector CSS selector.
215 * @param array $layout Layout object. The one that is passed has already checked
216 * the existence of default block layout.
217 * @param bool $has_block_gap_support Optional. Whether the theme has support for the block gap. Default false.
218 * @param string|string[]|null $gap_value Optional. The block gap value to apply. Default null.
219 * @param bool $should_skip_gap_serialization Optional. Whether to skip applying the user-defined value set in the editor. Default false.
220 * @param string $fallback_gap_value Optional. The block gap value to apply. Default '0.5em'.
221 * @param array|null $block_spacing Optional. Custom spacing set on the block. Default null.
222 * @return string CSS styles on success. Else, empty string.
223 */
224 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 ) {
225 $layout_type = isset( $layout['type'] ) ? $layout['type'] : 'default';
226 $layout_styles = array();
227
228 if ( 'default' === $layout_type ) {
229 if ( $has_block_gap_support ) {
230 if ( is_array( $gap_value ) ) {
231 $gap_value = isset( $gap_value['top'] ) ? $gap_value['top'] : null;
232 }
233 if ( null !== $gap_value && ! $should_skip_gap_serialization ) {
234 // Get spacing CSS variable from preset value if provided.
235 if ( is_string( $gap_value ) && str_contains( $gap_value, 'var:preset|spacing|' ) ) {
236 $index_to_splice = strrpos( $gap_value, '|' ) + 1;
237 $slug = _wp_to_kebab_case( substr( $gap_value, $index_to_splice ) );
238 $gap_value = "var(--wp--preset--spacing--$slug)";
239 }
240
241 array_push(
242 $layout_styles,
243 array(
244 'selector' => "$selector > *",
245 'declarations' => array(
246 'margin-block-start' => '0',
247 'margin-block-end' => '0',
248 ),
249 ),
250 array(
251 'selector' => "$selector$selector > * + *",
252 'declarations' => array(
253 'margin-block-start' => $gap_value,
254 'margin-block-end' => '0',
255 ),
256 )
257 );
258 }
259 }
260 } elseif ( 'constrained' === $layout_type ) {
261 $content_size = isset( $layout['contentSize'] ) ? $layout['contentSize'] : '';
262 $wide_size = isset( $layout['wideSize'] ) ? $layout['wideSize'] : '';
263 $justify_content = isset( $layout['justifyContent'] ) ? $layout['justifyContent'] : 'center';
264
265 $all_max_width_value = $content_size ? $content_size : $wide_size;
266 $wide_max_width_value = $wide_size ? $wide_size : $content_size;
267
268 // Make sure there is a single CSS rule, and all tags are stripped for security.
269 $all_max_width_value = safecss_filter_attr( explode( ';', $all_max_width_value )[0] );
270 $wide_max_width_value = safecss_filter_attr( explode( ';', $wide_max_width_value )[0] );
271
272 $margin_left = 'left' === $justify_content ? '0 !important' : 'auto !important';
273 $margin_right = 'right' === $justify_content ? '0 !important' : 'auto !important';
274
275 if ( $content_size || $wide_size ) {
276 array_push(
277 $layout_styles,
278 array(
279 'selector' => "$selector > :where(:not(.alignleft):not(.alignright):not(.alignfull))",
280 'declarations' => array(
281 'max-width' => $all_max_width_value,
282 'margin-left' => $margin_left,
283 'margin-right' => $margin_right,
284 ),
285 ),
286 array(
287 'selector' => "$selector > .alignwide",
288 'declarations' => array( 'max-width' => $wide_max_width_value ),
289 ),
290 array(
291 'selector' => "$selector .alignfull",
292 'declarations' => array( 'max-width' => 'none' ),
293 )
294 );
295
296 if ( isset( $block_spacing ) ) {
297 $block_spacing_values = gutenberg_style_engine_get_styles(
298 array(
299 'spacing' => $block_spacing,
300 )
301 );
302
303 /*
304 * Handle negative margins for alignfull children of blocks with custom padding set.
305 * They're added separately because padding might only be set on one side.
306 */
307 if ( isset( $block_spacing_values['declarations']['padding-right'] ) ) {
308 $padding_right = $block_spacing_values['declarations']['padding-right'];
309 $layout_styles[] = array(
310 'selector' => "$selector > .alignfull",
311 'declarations' => array( 'margin-right' => "calc($padding_right * -1)" ),
312 );
313 }
314 if ( isset( $block_spacing_values['declarations']['padding-left'] ) ) {
315 $padding_left = $block_spacing_values['declarations']['padding-left'];
316 $layout_styles[] = array(
317 'selector' => "$selector > .alignfull",
318 'declarations' => array( 'margin-left' => "calc($padding_left * -1)" ),
319 );
320 }
321 }
322 }
323
324 if ( 'left' === $justify_content ) {
325 $layout_styles[] = array(
326 'selector' => "$selector > :where(:not(.alignleft):not(.alignright):not(.alignfull))",
327 'declarations' => array( 'margin-left' => '0 !important' ),
328 );
329 }
330
331 if ( 'right' === $justify_content ) {
332 $layout_styles[] = array(
333 'selector' => "$selector > :where(:not(.alignleft):not(.alignright):not(.alignfull))",
334 'declarations' => array( 'margin-right' => '0 !important' ),
335 );
336 }
337
338 if ( $has_block_gap_support ) {
339 if ( is_array( $gap_value ) ) {
340 $gap_value = isset( $gap_value['top'] ) ? $gap_value['top'] : null;
341 }
342 if ( null !== $gap_value && ! $should_skip_gap_serialization ) {
343 // Get spacing CSS variable from preset value if provided.
344 if ( is_string( $gap_value ) && str_contains( $gap_value, 'var:preset|spacing|' ) ) {
345 $index_to_splice = strrpos( $gap_value, '|' ) + 1;
346 $slug = _wp_to_kebab_case( substr( $gap_value, $index_to_splice ) );
347 $gap_value = "var(--wp--preset--spacing--$slug)";
348 }
349
350 array_push(
351 $layout_styles,
352 array(
353 'selector' => "$selector > *",
354 'declarations' => array(
355 'margin-block-start' => '0',
356 'margin-block-end' => '0',
357 ),
358 ),
359 array(
360 'selector' => "$selector$selector > * + *",
361 'declarations' => array(
362 'margin-block-start' => $gap_value,
363 'margin-block-end' => '0',
364 ),
365 )
366 );
367 }
368 }
369 } elseif ( 'flex' === $layout_type ) {
370 $layout_orientation = isset( $layout['orientation'] ) ? $layout['orientation'] : 'horizontal';
371
372 $justify_content_options = array(
373 'left' => 'flex-start',
374 'right' => 'flex-end',
375 'center' => 'center',
376 );
377
378 $vertical_alignment_options = array(
379 'top' => 'flex-start',
380 'center' => 'center',
381 'bottom' => 'flex-end',
382 );
383
384 if ( 'horizontal' === $layout_orientation ) {
385 $justify_content_options += array( 'space-between' => 'space-between' );
386 $vertical_alignment_options += array( 'stretch' => 'stretch' );
387 } else {
388 $justify_content_options += array( 'stretch' => 'stretch' );
389 $vertical_alignment_options += array( 'space-between' => 'space-between' );
390 }
391
392 if ( ! empty( $layout['flexWrap'] ) && 'nowrap' === $layout['flexWrap'] ) {
393 $layout_styles[] = array(
394 'selector' => $selector,
395 'declarations' => array( 'flex-wrap' => 'nowrap' ),
396 );
397 }
398
399 if ( $has_block_gap_support && isset( $gap_value ) ) {
400 $combined_gap_value = '';
401 $gap_sides = is_array( $gap_value ) ? array( 'top', 'left' ) : array( 'top' );
402
403 foreach ( $gap_sides as $gap_side ) {
404 $process_value = is_string( $gap_value ) ? $gap_value : _wp_array_get( $gap_value, array( $gap_side ), $fallback_gap_value );
405 // Get spacing CSS variable from preset value if provided.
406 if ( is_string( $process_value ) && str_contains( $process_value, 'var:preset|spacing|' ) ) {
407 $index_to_splice = strrpos( $process_value, '|' ) + 1;
408 $slug = _wp_to_kebab_case( substr( $process_value, $index_to_splice ) );
409 $process_value = "var(--wp--preset--spacing--$slug)";
410 }
411 $combined_gap_value .= "$process_value ";
412 }
413 $gap_value = trim( $combined_gap_value );
414
415 if ( null !== $gap_value && ! $should_skip_gap_serialization ) {
416 $layout_styles[] = array(
417 'selector' => $selector,
418 'declarations' => array( 'gap' => $gap_value ),
419 );
420 }
421 }
422
423 if ( 'horizontal' === $layout_orientation ) {
424 /*
425 * Add this style only if is not empty for backwards compatibility,
426 * since we intend to convert blocks that had flex layout implemented
427 * by custom css.
428 */
429 if ( ! empty( $layout['justifyContent'] ) && array_key_exists( $layout['justifyContent'], $justify_content_options ) ) {
430 $layout_styles[] = array(
431 'selector' => $selector,
432 'declarations' => array( 'justify-content' => $justify_content_options[ $layout['justifyContent'] ] ),
433 );
434 }
435
436 if ( ! empty( $layout['verticalAlignment'] ) && array_key_exists( $layout['verticalAlignment'], $vertical_alignment_options ) ) {
437 $layout_styles[] = array(
438 'selector' => $selector,
439 'declarations' => array( 'align-items' => $vertical_alignment_options[ $layout['verticalAlignment'] ] ),
440 );
441 }
442 } else {
443 $layout_styles[] = array(
444 'selector' => $selector,
445 'declarations' => array( 'flex-direction' => 'column' ),
446 );
447 if ( ! empty( $layout['justifyContent'] ) && array_key_exists( $layout['justifyContent'], $justify_content_options ) ) {
448 $layout_styles[] = array(
449 'selector' => $selector,
450 'declarations' => array( 'align-items' => $justify_content_options[ $layout['justifyContent'] ] ),
451 );
452 } else {
453 $layout_styles[] = array(
454 'selector' => $selector,
455 'declarations' => array( 'align-items' => 'flex-start' ),
456 );
457 }
458 if ( ! empty( $layout['verticalAlignment'] ) && array_key_exists( $layout['verticalAlignment'], $vertical_alignment_options ) ) {
459 $layout_styles[] = array(
460 'selector' => $selector,
461 'declarations' => array( 'justify-content' => $vertical_alignment_options[ $layout['verticalAlignment'] ] ),
462 );
463 }
464 }
465 } elseif ( 'grid' === $layout_type ) {
466 if ( ! empty( $layout['columnCount'] ) ) {
467 $layout_styles[] = array(
468 'selector' => $selector,
469 'declarations' => array( 'grid-template-columns' => 'repeat(' . $layout['columnCount'] . ', minmax(0, 1fr))' ),
470 );
471 } else {
472 $minimum_column_width = ! empty( $layout['minimumColumnWidth'] ) ? $layout['minimumColumnWidth'] : '12rem';
473
474 $layout_styles[] = array(
475 'selector' => $selector,
476 'declarations' => array( 'grid-template-columns' => 'repeat(auto-fill, minmax(min(' . $minimum_column_width . ', 100%), 1fr))' ),
477 );
478 }
479
480 if ( $has_block_gap_support && isset( $gap_value ) ) {
481 $combined_gap_value = '';
482 $gap_sides = is_array( $gap_value ) ? array( 'top', 'left' ) : array( 'top' );
483
484 foreach ( $gap_sides as $gap_side ) {
485 $process_value = is_string( $gap_value ) ? $gap_value : _wp_array_get( $gap_value, array( $gap_side ), $fallback_gap_value );
486 // Get spacing CSS variable from preset value if provided.
487 if ( is_string( $process_value ) && str_contains( $process_value, 'var:preset|spacing|' ) ) {
488 $index_to_splice = strrpos( $process_value, '|' ) + 1;
489 $slug = _wp_to_kebab_case( substr( $process_value, $index_to_splice ) );
490 $process_value = "var(--wp--preset--spacing--$slug)";
491 }
492 $combined_gap_value .= "$process_value ";
493 }
494 $gap_value = trim( $combined_gap_value );
495
496 if ( null !== $gap_value && ! $should_skip_gap_serialization ) {
497 $layout_styles[] = array(
498 'selector' => $selector,
499 'declarations' => array( 'gap' => $gap_value ),
500 );
501 }
502 }
503 }
504
505 if ( ! empty( $layout_styles ) ) {
506 /*
507 * Add to the style engine store to enqueue and render layout styles.
508 * Return compiled layout styles to retain backwards compatibility.
509 * Since https://github.com/WordPress/gutenberg/pull/42452,
510 * wp_enqueue_block_support_styles is no longer called in this block supports file.
511 */
512 return gutenberg_style_engine_get_stylesheet_from_css_rules(
513 $layout_styles,
514 array(
515 'context' => 'block-supports',
516 'prettify' => false,
517 )
518 );
519 }
520
521 return '';
522 }
523
524 /**
525 * Renders the layout config to the block wrapper.
526 *
527 * @param string $block_content Rendered block content.
528 * @param array $block Block object.
529 * @return string Filtered block content.
530 */
531 function gutenberg_render_layout_support_flag( $block_content, $block ) {
532 $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] );
533 $support_layout = block_has_support( $block_type, array( 'layout' ), false ) || block_has_support( $block_type, array( '__experimentalLayout' ), false );
534 $has_child_layout = isset( $block['attrs']['style']['layout']['selfStretch'] );
535
536 if ( ! $support_layout
537 && ! $has_child_layout ) {
538 return $block_content;
539 }
540
541 $outer_class_names = array();
542
543 if ( $has_child_layout && ( 'fixed' === $block['attrs']['style']['layout']['selfStretch'] || 'fill' === $block['attrs']['style']['layout']['selfStretch'] ) ) {
544
545 $container_content_class = wp_unique_id( 'wp-container-content-' );
546
547 $child_layout_styles = array();
548
549 if ( 'fixed' === $block['attrs']['style']['layout']['selfStretch'] && isset( $block['attrs']['style']['layout']['flexSize'] ) ) {
550 $child_layout_styles[] = array(
551 'selector' => ".$container_content_class",
552 'declarations' => array(
553 'flex-basis' => $block['attrs']['style']['layout']['flexSize'],
554 'box-sizing' => 'border-box',
555 ),
556 );
557 } elseif ( 'fill' === $block['attrs']['style']['layout']['selfStretch'] ) {
558 $child_layout_styles[] = array(
559 'selector' => ".$container_content_class",
560 'declarations' => array(
561 'flex-grow' => '1',
562 ),
563 );
564 }
565
566 gutenberg_style_engine_get_stylesheet_from_css_rules(
567 $child_layout_styles,
568 array(
569 'context' => 'block-supports',
570 'prettify' => false,
571 )
572 );
573
574 $outer_class_names[] = $container_content_class;
575
576 }
577
578 // Return early if only child layout exists.
579 if ( ! $support_layout && ! empty( $outer_class_names ) ) {
580 $content = new WP_HTML_Tag_Processor( $block_content );
581 $content->next_tag();
582 $content->add_class( implode( ' ', $outer_class_names ) );
583 return (string) $content;
584 }
585
586 $global_settings = gutenberg_get_global_settings();
587 $fallback_layout = ! empty( _wp_array_get( $block_type->supports, array( 'layout', 'default' ), array() ) ) ? _wp_array_get( $block_type->supports, array( 'layout', 'default' ), array() ) : _wp_array_get( $block_type->supports, array( '__experimentalLayout', 'default' ), array() );
588 $used_layout = isset( $block['attrs']['layout'] ) ? $block['attrs']['layout'] : $fallback_layout;
589
590 $class_names = array();
591 $layout_definitions = gutenberg_get_layout_definitions();
592 $container_class = wp_unique_id( 'wp-container-' );
593 $layout_classname = '';
594
595 // Set the correct layout type for blocks using legacy content width.
596 if ( isset( $used_layout['inherit'] ) && $used_layout['inherit'] || isset( $used_layout['contentSize'] ) && $used_layout['contentSize'] ) {
597 $used_layout['type'] = 'constrained';
598 }
599
600 $root_padding_aware_alignments = _wp_array_get( $global_settings, array( 'useRootPaddingAwareAlignments' ), false );
601
602 if (
603 $root_padding_aware_alignments &&
604 isset( $used_layout['type'] ) &&
605 'constrained' === $used_layout['type']
606 ) {
607 $class_names[] = 'has-global-padding';
608 }
609
610 /*
611 * The following section was added to reintroduce a small set of layout classnames that were
612 * removed in the 5.9 release (https://github.com/WordPress/gutenberg/issues/38719). It is
613 * not intended to provide an extended set of classes to match all block layout attributes
614 * here.
615 */
616 if ( ! empty( $block['attrs']['layout']['orientation'] ) ) {
617 $class_names[] = 'is-' . sanitize_title( $block['attrs']['layout']['orientation'] );
618 }
619
620 if ( ! empty( $block['attrs']['layout']['justifyContent'] ) ) {
621 $class_names[] = 'is-content-justification-' . sanitize_title( $block['attrs']['layout']['justifyContent'] );
622 }
623
624 if ( ! empty( $block['attrs']['layout']['flexWrap'] ) && 'nowrap' === $block['attrs']['layout']['flexWrap'] ) {
625 $class_names[] = 'is-nowrap';
626 }
627
628 // Get classname for layout type.
629 if ( isset( $used_layout['type'] ) ) {
630 $layout_classname = _wp_array_get( $layout_definitions, array( $used_layout['type'], 'className' ), '' );
631 } else {
632 $layout_classname = _wp_array_get( $layout_definitions, array( 'default', 'className' ), '' );
633 }
634
635 if ( $layout_classname && is_string( $layout_classname ) ) {
636 $class_names[] = sanitize_title( $layout_classname );
637 }
638
639 /*
640 * Only generate Layout styles if the theme has not opted-out.
641 * Attribute-based Layout classnames are output in all cases.
642 */
643 if ( ! current_theme_supports( 'disable-layout-styles' ) ) {
644
645 $gap_value = _wp_array_get( $block, array( 'attrs', 'style', 'spacing', 'blockGap' ) );
646
647 /*
648 * Skip if gap value contains unsupported characters.
649 * Regex for CSS value borrowed from `safecss_filter_attr`, and used here
650 * to only match against the value, not the CSS attribute.
651 */
652 if ( is_array( $gap_value ) ) {
653 foreach ( $gap_value as $key => $value ) {
654 $gap_value[ $key ] = $value && preg_match( '%[\\\(&=}]|/\*%', $value ) ? null : $value;
655 }
656 } else {
657 $gap_value = $gap_value && preg_match( '%[\\\(&=}]|/\*%', $gap_value ) ? null : $gap_value;
658 }
659
660 $fallback_gap_value = _wp_array_get( $block_type->supports, array( 'spacing', 'blockGap', '__experimentalDefault' ), '0.5em' );
661 $block_spacing = _wp_array_get( $block, array( 'attrs', 'style', 'spacing' ), null );
662
663 /*
664 * If a block's block.json skips serialization for spacing or spacing.blockGap,
665 * don't apply the user-defined value to the styles.
666 */
667 $should_skip_gap_serialization = wp_should_skip_block_supports_serialization( $block_type, 'spacing', 'blockGap' );
668
669 $block_gap = _wp_array_get( $global_settings, array( 'spacing', 'blockGap' ), null );
670 $has_block_gap_support = isset( $block_gap );
671
672 $style = gutenberg_get_layout_style(
673 ".$container_class.$container_class",
674 $used_layout,
675 $has_block_gap_support,
676 $gap_value,
677 $should_skip_gap_serialization,
678 $fallback_gap_value,
679 $block_spacing
680 );
681
682 // Only add container class and enqueue block support styles if unique styles were generated.
683 if ( ! empty( $style ) ) {
684 $class_names[] = $container_class;
685 }
686 }
687
688 // Add combined layout and block classname for global styles to hook onto.
689 $block_name = explode( '/', $block['blockName'] );
690 $class_names[] = 'wp-block-' . end( $block_name ) . '-' . $layout_classname;
691
692 $content_with_outer_classnames = '';
693
694 if ( ! empty( $outer_class_names ) ) {
695 $content_with_outer_classnames = new WP_HTML_Tag_Processor( $block_content );
696 $content_with_outer_classnames->next_tag();
697 foreach ( $outer_class_names as $outer_class_name ) {
698 $content_with_outer_classnames->add_class( $outer_class_name );
699 }
700
701 $content_with_outer_classnames = (string) $content_with_outer_classnames;
702 }
703
704 /**
705 * The first chunk of innerContent contains the block markup up until the inner blocks start.
706 * We want to target the opening tag of the inner blocks wrapper, which is the last tag in that chunk.
707 */
708 $inner_content_classnames = '';
709
710 if ( isset( $block['innerContent'][0] ) && 'string' === gettype( $block['innerContent'][0] ) && count( $block['innerContent'] ) > 1 ) {
711 $tags = new WP_HTML_Tag_Processor( $block['innerContent'][0] );
712 $last_classnames = '';
713 while ( $tags->next_tag() ) {
714 $last_classnames = $tags->get_attribute( 'class' );
715 }
716
717 $inner_content_classnames = (string) $last_classnames;
718 }
719
720 $content = $content_with_outer_classnames ? new WP_HTML_Tag_Processor( $content_with_outer_classnames ) : new WP_HTML_Tag_Processor( $block_content );
721
722 if ( $inner_content_classnames ) {
723 $content->next_tag( array( 'class_name' => $inner_content_classnames ) );
724 foreach ( $class_names as $class_name ) {
725 $content->add_class( $class_name );
726 }
727 } else {
728 $content->next_tag();
729 foreach ( $class_names as $class_name ) {
730 $content->add_class( $class_name );
731 }
732 }
733
734 return (string) $content;
735 }
736
737 // Register the block support. (overrides core one).
738 WP_Block_Supports::get_instance()->register(
739 'layout',
740 array(
741 'register_attribute' => 'gutenberg_register_layout_support',
742 )
743 );
744
745 if ( function_exists( 'wp_render_layout_support_flag' ) ) {
746 remove_filter( 'render_block', 'wp_render_layout_support_flag' );
747 }
748 add_filter( 'render_block', 'gutenberg_render_layout_support_flag', 10, 2 );
749
750 /**
751 * For themes without theme.json file, make sure
752 * to restore the inner div for the group block
753 * to avoid breaking styles relying on that div.
754 *
755 * @param string $block_content Rendered block content.
756 * @param array $block Block object.
757 * @return string Filtered block content.
758 */
759 function gutenberg_restore_group_inner_container( $block_content, $block ) {
760 $tag_name = isset( $block['attrs']['tagName'] ) ? $block['attrs']['tagName'] : 'div';
761 $group_with_inner_container_regex = sprintf(
762 '/(^\s*<%1$s\b[^>]*wp-block-group(\s|")[^>]*>)(\s*<div\b[^>]*wp-block-group__inner-container(\s|")[^>]*>)((.|\S|\s)*)/U',
763 preg_quote( $tag_name, '/' )
764 );
765 if (
766 wp_theme_has_theme_json() ||
767 1 === preg_match( $group_with_inner_container_regex, $block_content ) ||
768 ( isset( $block['attrs']['layout']['type'] ) && ( 'flex' === $block['attrs']['layout']['type'] || 'grid' === $block['attrs']['layout']['type'] ) )
769 ) {
770 return $block_content;
771 }
772
773 $replace_regex = sprintf(
774 '/(^\s*<%1$s\b[^>]*wp-block-group[^>]*>)(.*)(<\/%1$s>\s*$)/ms',
775 preg_quote( $tag_name, '/' )
776 );
777 $updated_content = preg_replace_callback(
778 $replace_regex,
779 static function( $matches ) {
780 return $matches[1] . '<div class="wp-block-group__inner-container">' . $matches[2] . '</div>' . $matches[3];
781 },
782 $block_content
783 );
784 return $updated_content;
785 }
786
787 if ( function_exists( 'wp_restore_group_inner_container' ) ) {
788 remove_filter( 'render_block', 'wp_restore_group_inner_container', 10, 2 );
789 remove_filter( 'render_block_core/group', 'wp_restore_group_inner_container', 10, 2 );
790 }
791 add_filter( 'render_block_core/group', 'gutenberg_restore_group_inner_container', 10, 2 );
792
793
794 /**
795 * For themes without theme.json file, make sure
796 * to restore the outer div for the aligned image block
797 * to avoid breaking styles relying on that div.
798 *
799 * @param string $block_content Rendered block content.
800 * @param array $block Block object.
801 * @return string Filtered block content.
802 */
803 function gutenberg_restore_image_outer_container( $block_content, $block ) {
804 $image_with_align = "
805 /# 1) everything up to the class attribute contents
806 (
807 ^\s*
808 <figure\b
809 [^>]*
810 \bclass=
811 [\"']
812 )
813 # 2) the class attribute contents
814 (
815 [^\"']*
816 \bwp-block-image\b
817 [^\"']*
818 \b(?:alignleft|alignright|aligncenter)\b
819 [^\"']*
820 )
821 # 3) everything after the class attribute contents
822 (
823 [\"']
824 [^>]*
825 >
826 .*
827 <\/figure>
828 )/iUx";
829
830 if (
831 wp_theme_has_theme_json() ||
832 0 === preg_match( $image_with_align, $block_content, $matches )
833 ) {
834 return $block_content;
835 }
836
837 $wrapper_classnames = array( 'wp-block-image' );
838
839 // If the block has a classNames attribute these classnames need to be removed from the content and added back
840 // to the new wrapper div also.
841 if ( ! empty( $block['attrs']['className'] ) ) {
842 $wrapper_classnames = array_merge( $wrapper_classnames, explode( ' ', $block['attrs']['className'] ) );
843 }
844 $content_classnames = explode( ' ', $matches[2] );
845 $filtered_content_classnames = array_diff( $content_classnames, $wrapper_classnames );
846
847 return '<div class="' . implode( ' ', $wrapper_classnames ) . '">' . $matches[1] . implode( ' ', $filtered_content_classnames ) . $matches[3] . '</div>';
848 }
849
850 if ( function_exists( 'wp_restore_image_outer_container' ) ) {
851 remove_filter( 'render_block_core/image', 'wp_restore_image_outer_container', 10, 2 );
852 }
853 add_filter( 'render_block_core/image', 'gutenberg_restore_image_outer_container', 10, 2 );
854