PluginProbe
Gutenberg / 14.5.2
Gutenberg v14.5.2
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
← All changes | lib/block-supports/layout.php +175 -1108 23.4.0 → 14.5.2 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 = 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 }
@@ -237,196 +25,8 @@
237 25 }
238 26 }
239 27
240 28 /**
241 - * Returns the child-layout-only subset of a layout object.
242 - *
243 - * Child layout keys are the ones controlling how the block lays itself out
244 - * inside its parent's grid or flex container.
245 - *
246 - * @param mixed $layout Layout object.
247 - * @return array Child layout values, or an empty array.
248 - */
249 -function gutenberg_get_layout_child_values( $layout ) {
250 - if ( ! is_array( $layout ) ) {
251 - return array();
252 - }
253 -
254 - return array_intersect_key(
255 - $layout,
256 - array_flip(
257 - array( 'selfStretch', 'flexSize', 'columnStart', 'columnSpan', 'rowStart', 'rowSpan' )
258 - )
259 - );
260 -}
261 -
262 -/**
263 - * Returns the container-layout subset of a layout object (everything except child layout keys).
264 - *
265 - * @param mixed $layout Layout object.
266 - * @return array Container layout values, or an empty array.
267 - */
268 -function gutenberg_get_layout_container_values( $layout ) {
269 - if ( ! is_array( $layout ) ) {
270 - return array();
271 - }
272 -
273 - return array_diff_key(
274 - $layout,
275 - array_flip(
276 - array( 'selfStretch', 'flexSize', 'columnStart', 'columnSpan', 'rowStart', 'rowSpan' )
277 - )
278 - );
279 -}
280 -
281 -/**
282 - * Sanitizes a block gap value before layout style generation.
283 - *
284 - * Regex for CSS value borrowed from `safecss_filter_attr`, used here to only match
285 - * against the value, not the CSS attribute.
286 - *
287 - * @param string|array|null $gap_value Block gap value.
288 - * @return string|array|null Sanitized block gap value.
289 - */
290 -function gutenberg_sanitize_block_gap_value( $gap_value ) {
291 - if ( is_array( $gap_value ) ) {
292 - foreach ( $gap_value as $key => $value ) {
293 - $gap_value[ $key ] = $value && preg_match( '%[\\\(&=}]|/\*%', $value ) ? null : $value;
294 - }
295 - return $gap_value;
296 - }
297 -
298 - return $gap_value && preg_match( '%[\\\(&=}]|/\*%', $gap_value ) ? null : $gap_value;
299 -}
300 -
301 -/**
302 - * Returns child layout styles for a block affected by its parent's layout.
303 - *
304 - * @param string $selector CSS selector.
305 - * @param array $child_layout Child layout values.
306 - * @param array $parent_layout Parent layout values.
307 - * @param array|null $viewport_overrides Optional. Child viewport layout overrides to emit.
308 - * @return array Child layout style rules.
309 - */
310 -function gutenberg_get_child_layout_style_rules( $selector, $child_layout, $parent_layout = array(), $viewport_overrides = null ) {
311 - $base_child_layout = is_array( $child_layout ) ? $child_layout : array();
312 - $viewport_overrides = is_array( $viewport_overrides ) ? $viewport_overrides : null;
313 - $child_layout = null === $viewport_overrides ? $base_child_layout : array_replace( $base_child_layout, $viewport_overrides );
314 - $child_layout_declarations = array();
315 - $child_layout_styles = array();
316 - $has_viewport_property_override = static function ( $property ) use ( $viewport_overrides ) {
317 - return array_key_exists( $property, $viewport_overrides );
318 - };
319 -
320 - $self_stretch = $child_layout['selfStretch'] ?? null;
321 -
322 - if ( null === $viewport_overrides || $has_viewport_property_override( 'selfStretch' ) || $has_viewport_property_override( 'flexSize' ) ) {
323 - if ( 'fixed' === $self_stretch && isset( $child_layout['flexSize'] ) ) {
324 - $child_layout_declarations['flex-basis'] = $child_layout['flexSize'];
325 - $child_layout_declarations['box-sizing'] = 'border-box';
326 - } elseif ( 'fill' === $self_stretch ) {
327 - $child_layout_declarations['flex-grow'] = '1';
328 - }
329 - }
330 -
331 - $column_start = $child_layout['columnStart'] ?? null;
332 - $column_span = $child_layout['columnSpan'] ?? null;
333 - if ( null === $viewport_overrides || $has_viewport_property_override( 'columnStart' ) || $has_viewport_property_override( 'columnSpan' ) ) {
334 - if ( $column_start && $column_span ) {
335 - $child_layout_declarations['grid-column'] = "$column_start / span $column_span";
336 - } elseif ( $column_start ) {
337 - $child_layout_declarations['grid-column'] = "$column_start";
338 - } elseif ( $column_span ) {
339 - $child_layout_declarations['grid-column'] = "span $column_span";
340 - }
341 - }
342 -
343 - $row_start = $child_layout['rowStart'] ?? null;
344 - $row_span = $child_layout['rowSpan'] ?? null;
345 - if ( null === $viewport_overrides || $has_viewport_property_override( 'rowStart' ) || $has_viewport_property_override( 'rowSpan' ) ) {
346 - if ( $row_start && $row_span ) {
347 - $child_layout_declarations['grid-row'] = "$row_start / span $row_span";
348 - } elseif ( $row_start ) {
349 - $child_layout_declarations['grid-row'] = "$row_start";
350 - } elseif ( $row_span ) {
351 - $child_layout_declarations['grid-row'] = "span $row_span";
352 - }
353 - }
354 -
355 - if ( ! empty( $child_layout_declarations ) ) {
356 - $child_layout_styles[] = array(
357 - 'selector' => $selector,
358 - 'declarations' => $child_layout_declarations,
359 - );
360 - }
361 -
362 - $minimum_column_width = $parent_layout['minimumColumnWidth'] ?? null;
363 - $column_count = $parent_layout['columnCount'] ?? null;
364 -
365 - /*
366 - * If columnSpan or columnStart is set, and the parent grid is responsive, i.e. if it has a minimumColumnWidth set,
367 - * the columnSpan should be removed once the grid is smaller than the span, and columnStart should be removed
368 - * once the grid has less columns than the start.
369 - * If there's a minimumColumnWidth, the grid is responsive. But if the minimumColumnWidth value wasn't changed, it won't be set.
370 - * In that case, if columnCount doesn't exist, we can assume that the grid is responsive.
371 - */
372 - if ( null === $viewport_overrides && ( $column_span || $column_start ) && ( $minimum_column_width || ! $column_count ) ) {
373 - $column_span_number = floatval( $column_span );
374 - $column_start_number = floatval( $column_start );
375 - $parent_column_width = $minimum_column_width ? $minimum_column_width : '12rem';
376 - $parent_column_value = floatval( $parent_column_width );
377 - $parent_column_unit = explode( $parent_column_value, $parent_column_width );
378 -
379 - $num_cols_to_break_at = 2;
380 - if ( $column_span_number && $column_start_number ) {
381 - $num_cols_to_break_at = $column_start_number + $column_span_number - 1;
382 - } elseif ( $column_span_number ) {
383 - $num_cols_to_break_at = $column_span_number;
384 - } else {
385 - $num_cols_to_break_at = $column_start_number;
386 - }
387 -
388 - /*
389 - * If there is no unit, the width has somehow been mangled so we reset both unit and value
390 - * to defaults.
391 - * Additionally, the unit should be one of px, rem or em, so that also needs to be checked.
392 - */
393 - if ( count( $parent_column_unit ) <= 1 ) {
394 - $parent_column_unit = 'rem';
395 - $parent_column_value = 12;
396 - } else {
397 - $parent_column_unit = $parent_column_unit[1];
398 -
399 - if ( ! in_array( $parent_column_unit, array( 'px', 'rem', 'em' ), true ) ) {
400 - $parent_column_unit = 'rem';
401 - }
402 - }
403 -
404 - /*
405 - * A default gap value is used for this computation because custom gap values may not be
406 - * viable to use in the computation of the container query value.
407 - */
408 - $default_gap_value = 'px' === $parent_column_unit ? 24 : 1.5;
409 - $container_query_value = $num_cols_to_break_at * $parent_column_value + ( $num_cols_to_break_at - 1 ) * $default_gap_value;
410 - $minimum_container_query_value = $parent_column_value * 2 + $default_gap_value - 1;
411 - $container_query_value = max( $container_query_value, $minimum_container_query_value ) . $parent_column_unit;
412 - // If a span is set we want to preserve it as long as possible, otherwise we just reset the value.
413 - $grid_column_value = $column_span && $column_span > 1 ? '1/-1' : 'auto';
414 -
415 - $child_layout_styles[] = array(
416 - 'rules_group' => "@container (max-width: $container_query_value )",
417 - 'selector' => $selector,
418 - 'declarations' => array(
419 - 'grid-column' => $grid_column_value,
420 - 'grid-row' => 'auto',
421 - ),
422 - );
423 - }
424 -
425 - return $child_layout_styles;
426 -}
427 -
428 -/**
429 29 * Generates the CSS corresponding to the provided layout.
430 30 *
431 31 * @param string $selector CSS selector.
432 32 * @param array $layout Layout object. The one that is passed has already checked
@@ -433,30 +33,20 @@
433 33 * the existence of default block layout.
434 34 * @param bool $has_block_gap_support Optional. Whether the theme has support for the block gap. Default false.
435 35 * @param string|string[]|null $gap_value Optional. The block gap value to apply. Default null.
436 36 * @param bool $should_skip_gap_serialization Optional. Whether to skip applying the user-defined value set in the editor. Default false.
437 - * @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'.
37 + * @param string $fallback_gap_value Optional. The block gap value to apply. Default '0.5em'.
438 38 * @param array|null $block_spacing Optional. Custom spacing set on the block. Default null.
439 - * @param array $options Optional. Extra options for internal callers. Default empty array.
440 - * @return string CSS styles, or empty string.
39 + * @return string CSS styles on success. Else, empty string.
441 40 */
442 -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, $options = array() ) {
443 - $base_layout = is_array( $layout ) ? $layout : array();
444 - $viewport_overrides = $options['viewport_overrides'] ?? null;
445 - $layout_for_styles = null === $viewport_overrides ? $base_layout : array_replace( $base_layout, $viewport_overrides );
446 - $layout_type = $base_layout['type'] ?? 'default';
447 - $rules_group = $options['rules_group'] ?? null;
448 - $has_block_gap_override = ! empty( $options['has_block_gap_override'] );
449 - $should_output_block_gap = null === $viewport_overrides || $has_block_gap_override;
450 - $has_viewport_property_override = static function ( $property ) use ( $viewport_overrides ) {
451 - return array_key_exists( $property, $viewport_overrides );
452 - };
453 - $layout_styles = array();
41 +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 ) {
42 + $layout_type = isset( $layout['type'] ) ? $layout['type'] : 'default';
43 + $layout_styles = array();
454 44
455 45 if ( 'default' === $layout_type ) {
456 - if ( $has_block_gap_support && $should_output_block_gap ) {
46 + if ( $has_block_gap_support ) {
457 47 if ( is_array( $gap_value ) ) {
458 - $gap_value = $gap_value['top'] ?? null;
48 + $gap_value = isset( $gap_value['top'] ) ? $gap_value['top'] : null;
459 49 }
460 50 if ( null !== $gap_value && ! $should_skip_gap_serialization ) {
461 51 // Get spacing CSS variable from preset value if provided.
462 52 if ( is_string( $gap_value ) && str_contains( $gap_value, 'var:preset|spacing|' ) ) {
@@ -474,9 +64,9 @@
474 64 'margin-block-end' => '0',
475 65 ),
476 66 ),
477 67 array(
478 - 'selector' => "$selector > * + *",
68 + 'selector' => "$selector$selector > * + *",
479 69 'declarations' => array(
480 70 'margin-block-start' => $gap_value,
481 71 'margin-block-end' => '0',
482 72 ),
@@ -484,39 +74,33 @@
484 74 );
485 75 }
486 76 }
487 77 } elseif ( 'constrained' === $layout_type ) {
488 - $content_size = $layout_for_styles['contentSize'] ?? '';
489 - $wide_size = $layout_for_styles['wideSize'] ?? '';
490 - $justify_content = $layout_for_styles['justifyContent'] ?? 'center';
78 + $content_size = isset( $layout['contentSize'] ) ? $layout['contentSize'] : '';
79 + $wide_size = isset( $layout['wideSize'] ) ? $layout['wideSize'] : '';
80 + $justify_content = isset( $layout['justifyContent'] ) ? $layout['justifyContent'] : 'center';
491 81
492 82 $all_max_width_value = $content_size ? $content_size : $wide_size;
493 83 $wide_max_width_value = $wide_size ? $wide_size : $content_size;
494 84
495 85 // Make sure there is a single CSS rule, and all tags are stripped for security.
496 - $all_max_width_value = safecss_filter_attr( explode( ';', $all_max_width_value )[0] );
497 - $wide_max_width_value = safecss_filter_attr( explode( ';', $wide_max_width_value )[0] );
86 + // TODO: Use `safecss_filter_attr` instead - once https://core.trac.wordpress.org/ticket/46197 is patched.
87 + $all_max_width_value = wp_strip_all_tags( explode( ';', $all_max_width_value )[0] );
88 + $wide_max_width_value = wp_strip_all_tags( explode( ';', $wide_max_width_value )[0] );
498 89
499 90 $margin_left = 'left' === $justify_content ? '0 !important' : 'auto !important';
500 91 $margin_right = 'right' === $justify_content ? '0 !important' : 'auto !important';
501 92
502 - $has_justify_content_override = null !== $viewport_overrides && $has_viewport_property_override( 'justifyContent' );
503 - $should_output_constrained_sizes = null === $viewport_overrides || $has_viewport_property_override( 'contentSize' ) || $has_viewport_property_override( 'wideSize' );
504 - if ( $should_output_constrained_sizes && ( $content_size || $wide_size ) ) {
505 - $content_size_declarations = array(
506 - 'max-width' => $all_max_width_value,
507 - );
508 -
509 - if ( null === $viewport_overrides || $has_justify_content_override ) {
510 - $content_size_declarations['margin-left'] = $margin_left;
511 - $content_size_declarations['margin-right'] = $margin_right;
512 - }
513 -
93 + if ( $content_size || $wide_size ) {
514 94 array_push(
515 95 $layout_styles,
516 96 array(
517 97 'selector' => "$selector > :where(:not(.alignleft):not(.alignright):not(.alignfull))",
518 - 'declarations' => $content_size_declarations,
98 + 'declarations' => array(
99 + 'max-width' => $all_max_width_value,
100 + 'margin-left' => $margin_left,
101 + 'margin-right' => $margin_right,
102 + ),
519 103 ),
520 104 array(
521 105 'selector' => "$selector > .alignwide",
522 106 'declarations' => array( 'max-width' => $wide_max_width_value ),
@@ -525,72 +109,54 @@
525 109 'selector' => "$selector .alignfull",
526 110 'declarations' => array( 'max-width' => 'none' ),
527 111 )
528 112 );
529 - }
530 113
531 - if ( null === $viewport_overrides && isset( $block_spacing ) ) {
532 - $block_spacing_values = gutenberg_style_engine_get_styles(
533 - array(
534 - 'spacing' => $block_spacing,
535 - )
536 - );
114 + if ( isset( $block_spacing ) ) {
115 + $block_spacing_values = gutenberg_style_engine_get_styles(
116 + array(
117 + 'spacing' => $block_spacing,
118 + )
119 + );
537 120
538 - /*
539 - * Handle negative margins for alignfull children of blocks with custom padding set.
540 - * They're added separately because padding might only be set on one side.
541 - */
542 - if ( isset( $block_spacing_values['declarations']['padding-right'] ) ) {
543 - $padding_right = $block_spacing_values['declarations']['padding-right'];
544 - // Add unit if 0.
545 - if ( '0' === $padding_right ) {
546 - $padding_right = '0px';
121 + /*
122 + * Handle negative margins for alignfull children of blocks with custom padding set.
123 + * They're added separately because padding might only be set on one side.
124 + */
125 + if ( isset( $block_spacing_values['declarations']['padding-right'] ) ) {
126 + $padding_right = $block_spacing_values['declarations']['padding-right'];
127 + $layout_styles[] = array(
128 + 'selector' => "$selector > .alignfull",
129 + 'declarations' => array( 'margin-right' => "calc($padding_right * -1)" ),
130 + );
547 131 }
548 - $layout_styles[] = array(
549 - 'selector' => "$selector > .alignfull",
550 - 'declarations' => array( 'margin-right' => "calc($padding_right * -1)" ),
551 - );
552 - }
553 - if ( isset( $block_spacing_values['declarations']['padding-left'] ) ) {
554 - $padding_left = $block_spacing_values['declarations']['padding-left'];
555 - // Add unit if 0.
556 - if ( '0' === $padding_left ) {
557 - $padding_left = '0px';
132 + if ( isset( $block_spacing_values['declarations']['padding-left'] ) ) {
133 + $padding_left = $block_spacing_values['declarations']['padding-left'];
134 + $layout_styles[] = array(
135 + 'selector' => "$selector > .alignfull",
136 + 'declarations' => array( 'margin-left' => "calc($padding_left * -1)" ),
137 + );
558 138 }
559 - $layout_styles[] = array(
560 - 'selector' => "$selector > .alignfull",
561 - 'declarations' => array( 'margin-left' => "calc($padding_left * -1)" ),
562 - );
563 139 }
564 140 }
565 141
566 - if ( $has_justify_content_override && ! $should_output_constrained_sizes ) {
142 + if ( 'left' === $justify_content ) {
567 143 $layout_styles[] = array(
568 144 'selector' => "$selector > :where(:not(.alignleft):not(.alignright):not(.alignfull))",
569 - 'declarations' => array(
570 - 'margin-left' => $margin_left,
571 - 'margin-right' => $margin_right,
572 - ),
145 + 'declarations' => array( 'margin-left' => '0 !important' ),
573 146 );
574 - } elseif ( null === $viewport_overrides ) {
575 - if ( 'left' === $justify_content ) {
576 - $layout_styles[] = array(
577 - 'selector' => "$selector > :where(:not(.alignleft):not(.alignright):not(.alignfull))",
578 - 'declarations' => array( 'margin-left' => '0 !important' ),
579 - );
580 - }
147 + }
581 148
582 - if ( 'right' === $justify_content ) {
583 - $layout_styles[] = array(
584 - 'selector' => "$selector > :where(:not(.alignleft):not(.alignright):not(.alignfull))",
585 - 'declarations' => array( 'margin-right' => '0 !important' ),
586 - );
587 - }
149 + if ( 'right' === $justify_content ) {
150 + $layout_styles[] = array(
151 + 'selector' => "$selector > :where(:not(.alignleft):not(.alignright):not(.alignfull))",
152 + 'declarations' => array( 'margin-right' => '0 !important' ),
153 + );
588 154 }
589 155
590 - if ( $has_block_gap_support && $should_output_block_gap ) {
156 + if ( $has_block_gap_support ) {
591 157 if ( is_array( $gap_value ) ) {
592 - $gap_value = $gap_value['top'] ?? null;
158 + $gap_value = isset( $gap_value['top'] ) ? $gap_value['top'] : null;
593 159 }
594 160 if ( null !== $gap_value && ! $should_skip_gap_serialization ) {
595 161 // Get spacing CSS variable from preset value if provided.
596 162 if ( is_string( $gap_value ) && str_contains( $gap_value, 'var:preset|spacing|' ) ) {
@@ -608,9 +174,9 @@
608 174 'margin-block-end' => '0',
609 175 ),
610 176 ),
611 177 array(
612 - 'selector' => "$selector > * + *",
178 + 'selector' => "$selector$selector > * + *",
613 179 'declarations' => array(
614 180 'margin-block-start' => $gap_value,
615 181 'margin-block-end' => '0',
616 182 ),
@@ -618,9 +184,9 @@
618 184 );
619 185 }
620 186 }
621 187 } elseif ( 'flex' === $layout_type ) {
622 - $layout_orientation = $layout_for_styles['orientation'] ?? 'horizontal';
188 + $layout_orientation = isset( $layout['orientation'] ) ? $layout['orientation'] : 'horizontal';
623 189
624 190 $justify_content_options = array(
625 191 'left' => 'flex-start',
626 192 'right' => 'flex-end',
@@ -633,21 +199,12 @@
633 199 'bottom' => 'flex-end',
634 200 );
635 201
636 202 if ( 'horizontal' === $layout_orientation ) {
637 - $justify_content_options += array( 'space-between' => 'space-between' );
638 - $vertical_alignment_options += array( 'stretch' => 'stretch' );
639 - } else {
640 - $justify_content_options += array( 'stretch' => 'stretch' );
641 - $vertical_alignment_options += array( 'space-between' => 'space-between' );
203 + $justify_content_options += array( 'space-between' => 'space-between' );
642 204 }
643 205
644 - $should_output_flex_wrap = null === $viewport_overrides || $has_viewport_property_override( 'flexWrap' );
645 - $should_output_flex_orientation = null === $viewport_overrides || $has_viewport_property_override( 'orientation' );
646 - $should_output_flex_justification = null === $viewport_overrides || $has_viewport_property_override( 'justifyContent' ) || $has_viewport_property_override( 'orientation' );
647 - $should_output_flex_alignment = null === $viewport_overrides || $has_viewport_property_override( 'verticalAlignment' ) || $has_viewport_property_override( 'orientation' );
648 -
649 - if ( $should_output_flex_wrap && ! empty( $layout_for_styles['flexWrap'] ) && 'nowrap' === $layout_for_styles['flexWrap'] ) {
206 + if ( ! empty( $layout['flexWrap'] ) && 'nowrap' === $layout['flexWrap'] ) {
650 207 $layout_styles[] = array(
651 208 'selector' => $selector,
652 209 'declarations' => array( 'flex-wrap' => 'nowrap' ),
653 210 );
@@ -652,22 +209,14 @@
652 209 'declarations' => array( 'flex-wrap' => 'nowrap' ),
653 210 );
654 211 }
655 212
656 - if ( $has_block_gap_support && $should_output_block_gap && isset( $gap_value ) ) {
213 + if ( $has_block_gap_support && isset( $gap_value ) ) {
657 214 $combined_gap_value = '';
658 215 $gap_sides = is_array( $gap_value ) ? array( 'top', 'left' ) : array( 'top' );
659 216
660 217 foreach ( $gap_sides as $gap_side ) {
661 - $process_value = $gap_value;
662 - if ( is_array( $gap_value ) ) {
663 - if ( is_array( $fallback_gap_value ) ) {
664 - $fallback_value = $fallback_gap_value[ $gap_side ] ?? reset( $fallback_gap_value );
665 - } else {
666 - $fallback_value = $fallback_gap_value;
667 - }
668 - $process_value = $gap_value[ $gap_side ] ?? $fallback_value;
669 - }
218 + $process_value = is_string( $gap_value ) ? $gap_value : _wp_array_get( $gap_value, array( $gap_side ), $fallback_gap_value );
670 219 // Get spacing CSS variable from preset value if provided.
671 220 if ( is_string( $process_value ) && str_contains( $process_value, 'var:preset|spacing|' ) ) {
672 221 $index_to_splice = strrpos( $process_value, '|' ) + 1;
673 222 $slug = _wp_to_kebab_case( substr( $process_value, $index_to_splice ) );
@@ -690,142 +239,41 @@
690 239 * Add this style only if is not empty for backwards compatibility,
691 240 * since we intend to convert blocks that had flex layout implemented
692 241 * by custom css.
693 242 */
694 - if ( $should_output_flex_justification && ! empty( $layout_for_styles['justifyContent'] ) && array_key_exists( $layout_for_styles['justifyContent'], $justify_content_options ) ) {
243 + if ( ! empty( $layout['justifyContent'] ) && array_key_exists( $layout['justifyContent'], $justify_content_options ) ) {
695 244 $layout_styles[] = array(
696 245 'selector' => $selector,
697 - 'declarations' => array( 'justify-content' => $justify_content_options[ $layout_for_styles['justifyContent'] ] ),
246 + 'declarations' => array( 'justify-content' => $justify_content_options[ $layout['justifyContent'] ] ),
698 247 );
699 248 }
700 249
701 - if ( $should_output_flex_alignment && ! empty( $layout_for_styles['verticalAlignment'] ) && array_key_exists( $layout_for_styles['verticalAlignment'], $vertical_alignment_options ) ) {
250 + if ( ! empty( $layout['verticalAlignment'] ) && array_key_exists( $layout['verticalAlignment'], $vertical_alignment_options ) ) {
702 251 $layout_styles[] = array(
703 252 'selector' => $selector,
704 - 'declarations' => array( 'align-items' => $vertical_alignment_options[ $layout_for_styles['verticalAlignment'] ] ),
253 + 'declarations' => array( 'align-items' => $vertical_alignment_options[ $layout['verticalAlignment'] ] ),
705 254 );
706 255 }
707 256 } else {
708 - if ( $should_output_flex_orientation ) {
257 + $layout_styles[] = array(
258 + 'selector' => $selector,
259 + 'declarations' => array( 'flex-direction' => 'column' ),
260 + );
261 + if ( ! empty( $layout['justifyContent'] ) && array_key_exists( $layout['justifyContent'], $justify_content_options ) ) {
709 262 $layout_styles[] = array(
710 263 'selector' => $selector,
711 - 'declarations' => array( 'flex-direction' => 'column' ),
264 + 'declarations' => array( 'align-items' => $justify_content_options[ $layout['justifyContent'] ] ),
712 265 );
713 - }
714 - if ( $should_output_flex_justification && ! empty( $layout_for_styles['justifyContent'] ) && array_key_exists( $layout_for_styles['justifyContent'], $justify_content_options ) ) {
266 + } else {
715 267 $layout_styles[] = array(
716 268 'selector' => $selector,
717 - 'declarations' => array( 'align-items' => $justify_content_options[ $layout_for_styles['justifyContent'] ] ),
718 - );
719 - } elseif ( $should_output_flex_justification ) {
720 - $layout_styles[] = array(
721 - 'selector' => $selector,
722 269 'declarations' => array( 'align-items' => 'flex-start' ),
723 270 );
724 271 }
725 - if ( $should_output_flex_alignment && ! empty( $layout_for_styles['verticalAlignment'] ) && array_key_exists( $layout_for_styles['verticalAlignment'], $vertical_alignment_options ) ) {
726 - $layout_styles[] = array(
727 - 'selector' => $selector,
728 - 'declarations' => array( 'justify-content' => $vertical_alignment_options[ $layout_for_styles['verticalAlignment'] ] ),
729 - );
730 - }
731 272 }
732 - } elseif ( 'grid' === $layout_type ) {
733 - /*
734 - * If the gap value is an array, we use the "left" value because it represents the vertical gap, which
735 - * is the relevant one for computation of responsive grid columns.
736 - */
737 - if ( is_array( $fallback_gap_value ) ) {
738 - $responsive_gap_value = $fallback_gap_value['left'] ?? reset( $fallback_gap_value );
739 - } else {
740 - $responsive_gap_value = $fallback_gap_value;
741 - }
742 -
743 - if ( $has_block_gap_support && isset( $gap_value ) ) {
744 - $combined_gap_value = '';
745 - $gap_sides = is_array( $gap_value ) ? array( 'top', 'left' ) : array( 'top' );
746 -
747 - foreach ( $gap_sides as $gap_side ) {
748 - $process_value = $gap_value;
749 - if ( is_array( $gap_value ) ) {
750 - if ( is_array( $fallback_gap_value ) ) {
751 - $fallback_value = $fallback_gap_value[ $gap_side ] ?? reset( $fallback_gap_value );
752 - } else {
753 - $fallback_value = $fallback_gap_value;
754 - }
755 - $process_value = $gap_value[ $gap_side ] ?? $fallback_value;
756 - }
757 - // Get spacing CSS variable from preset value if provided.
758 - if ( is_string( $process_value ) && str_contains( $process_value, 'var:preset|spacing|' ) ) {
759 - $index_to_splice = strrpos( $process_value, '|' ) + 1;
760 - $slug = _wp_to_kebab_case( substr( $process_value, $index_to_splice ) );
761 - $process_value = "var(--wp--preset--spacing--$slug)";
762 - }
763 - $combined_gap_value .= "$process_value ";
764 - }
765 - $gap_value = trim( $combined_gap_value );
766 - $responsive_gap_value = $gap_value;
767 - }
768 -
769 - // Ensure 0 values have a unit so they work in calc().
770 - if ( '0' === $responsive_gap_value || 0 === $responsive_gap_value ) {
771 - $responsive_gap_value = '0px';
772 - }
773 -
774 - $should_output_grid_columns = null === $viewport_overrides || $has_viewport_property_override( 'minimumColumnWidth' ) || $has_viewport_property_override( 'columnCount' );
775 - $uses_gap_in_grid_columns = ! empty( $layout_for_styles['columnCount'] ) && ! empty( $layout_for_styles['minimumColumnWidth'] );
776 - if ( $has_block_gap_override && $uses_gap_in_grid_columns ) {
777 - $should_output_grid_columns = true;
778 - }
779 -
780 - $should_output_grid_rows = ( null === $viewport_overrides || $has_viewport_property_override( 'rowCount' ) ) && ! empty( $layout_for_styles['columnCount'] ) && ! empty( $layout_for_styles['rowCount'] );
781 - $grid_declarations = array();
782 -
783 - if ( $should_output_grid_columns && ! empty( $layout_for_styles['columnCount'] ) && ! empty( $layout_for_styles['minimumColumnWidth'] ) ) {
784 - $max_value = 'max(min(' . $layout_for_styles['minimumColumnWidth'] . ', 100%), (100% - (' . $responsive_gap_value . ' * (' . $layout_for_styles['columnCount'] . ' - 1))) /' . $layout_for_styles['columnCount'] . ')';
785 - $grid_declarations['grid-template-columns'] = 'repeat(auto-fill, minmax(' . $max_value . ', 1fr))';
786 - } elseif ( $should_output_grid_columns && ! empty( $layout_for_styles['columnCount'] ) ) {
787 - $grid_declarations['grid-template-columns'] = 'repeat(' . $layout_for_styles['columnCount'] . ', minmax(0, 1fr))';
788 - } elseif ( $should_output_grid_columns ) {
789 - $minimum_column_width = ! empty( $layout_for_styles['minimumColumnWidth'] ) ? $layout_for_styles['minimumColumnWidth'] : '12rem';
790 - $grid_declarations['grid-template-columns'] = 'repeat(auto-fill, minmax(min(' . $minimum_column_width . ', 100%), 1fr))';
791 - }
792 -
793 - if ( ! empty( $grid_declarations ) ) {
794 - $base_has_container_type = empty( $base_layout['columnCount'] ) || ( ! empty( $base_layout['columnCount'] ) && ! empty( $base_layout['minimumColumnWidth'] ) );
795 - if ( empty( $layout_for_styles['columnCount'] ) || ! empty( $layout_for_styles['minimumColumnWidth'] ) ) {
796 - if ( null === $viewport_overrides || ! $base_has_container_type ) {
797 - $grid_declarations['container-type'] = 'inline-size';
798 - }
799 - }
800 - $layout_styles[] = array(
801 - 'selector' => $selector,
802 - 'declarations' => $grid_declarations,
803 - );
804 - }
805 -
806 - if ( $should_output_grid_rows ) {
807 - $layout_styles[] = array(
808 - 'selector' => $selector,
809 - 'declarations' => array( 'grid-template-rows' => 'repeat(' . $layout_for_styles['rowCount'] . ', minmax(1rem, auto))' ),
810 - );
811 - }
812 -
813 - if ( $has_block_gap_support && $should_output_block_gap && null !== $gap_value && ! $should_skip_gap_serialization ) {
814 - $layout_styles[] = array(
815 - 'selector' => $selector,
816 - 'declarations' => array( 'gap' => $gap_value ),
817 - );
818 - }
819 273 }
820 274
821 275 if ( ! empty( $layout_styles ) ) {
822 - if ( ! empty( $rules_group ) ) {
823 - foreach ( $layout_styles as $index => $layout_style ) {
824 - $layout_styles[ $index ]['rules_group'] = $rules_group;
825 - }
826 - }
827 -
828 276 /*
829 277 * Add to the style engine store to enqueue and render layout styles.
830 278 * Return compiled layout styles to retain backwards compatibility.
831 279 * Since https://github.com/WordPress/gutenberg/pull/42452,
@@ -843,43 +291,22 @@
843 291 return '';
844 292 }
845 293
846 294 /**
847 - * Generates an incremental ID that is independent per each different prefix.
295 + * Gets classname from last tag in a string of HTML.
848 296 *
849 - * It is similar to `wp_unique_id`, but each prefix has it's own internal ID
850 - * counter to make each prefix independent from each other. The ID starts at 1
851 - * and increments on each call. The returned value is not universally unique,
852 - * but it is unique across the life of the PHP process and it's stable per
853 - * prefix.
854 - *
855 - * @param string $prefix Prefix for the returned ID.
856 - * @return string Incremental ID per prefix.
297 + * @param string $html markup to be processed.
298 + * @return string String of inner wrapper classnames.
857 299 */
858 -function gutenberg_incremental_id_per_prefix( $prefix = '' ) {
859 - static $id_counters = array();
860 - if ( ! array_key_exists( $prefix, $id_counters ) ) {
861 - $id_counters[ $prefix ] = 0;
300 +function gutenberg_get_classnames_from_last_tag( $html ) {
301 + $tags = new WP_HTML_Tag_Processor( $html );
302 + $last_classnames = '';
303 +
304 + while ( $tags->next_tag() ) {
305 + $last_classnames = $tags->get_attribute( 'class' );
862 306 }
863 - return $prefix . (string) ++$id_counters[ $prefix ];
864 -}
865 307
866 -/**
867 - * Generates a unique ID based on the structure and values of a given array.
868 - *
869 - * This function serializes the array into a JSON string and generates a hash
870 - * that serves as a unique identifier. Optionally, a prefix can be added to
871 - * the generated ID for context or categorization.
872 - *
873 - * @param array $data The input array to generate an ID from.
874 - * @param string $prefix Optional. A prefix to prepend to the generated ID. Default ''.
875 - *
876 - * @return string The generated unique ID for the array.
877 - */
878 -function gutenberg_unique_id_from_values( array $data, string $prefix = '' ): string {
879 - $serialized = wp_json_encode( $data );
880 - $hash = substr( md5( $serialized ), 0, 8 );
881 - return $prefix . $hash;
308 + return (string) $last_classnames;
882 309 }
883 310
884 311 /**
885 312 * Renders the layout config to the block wrapper.
@@ -888,138 +315,31 @@
888 315 * @param array $block Block object.
889 316 * @return string Filtered block content.
890 317 */
891 318 function gutenberg_render_layout_support_flag( $block_content, $block ) {
892 - static $global_styles = null;
319 + $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] );
320 + $support_layout = block_has_support( $block_type, array( '__experimentalLayout' ), false );
893 321
894 - $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] );
895 - $block_supports_layout = block_has_support( $block_type, array( 'layout' ), false ) || block_has_support( $block_type, array( '__experimentalLayout' ), false );
896 - $style_attr = $block['attrs']['style'] ?? array();
897 - // If there is any value in style -> layout, the block has a child layout.
898 - $child_layout = $style_attr['layout'] ?? null;
899 -
900 - // Collect responsive viewport child layout overrides so that a block with
901 - // only responsive child layout (no base child layout) is still processed.
902 - $viewport_child_layouts = array();
903 - foreach ( WP_Theme_JSON_Gutenberg::RESPONSIVE_BREAKPOINTS as $breakpoint => $media_query ) {
904 - $viewport_child = gutenberg_get_layout_child_values( $style_attr[ $breakpoint ]['layout'] ?? null );
905 - if ( ! empty( $viewport_child ) ) {
906 - $viewport_child_layouts[ $breakpoint ] = array(
907 - 'media_query' => $media_query,
908 - 'child_layout' => $viewport_child,
909 - );
910 - }
911 - }
912 -
913 - if ( ! $block_supports_layout && ! $child_layout && empty( $viewport_child_layouts ) ) {
322 + if ( ! $support_layout ) {
914 323 return $block_content;
915 324 }
916 325
917 - $outer_class_names = array();
326 + $block_gap = gutenberg_get_global_settings( array( 'spacing', 'blockGap' ) );
327 + $global_layout_settings = gutenberg_get_global_settings( array( 'layout' ) );
328 + $has_block_gap_support = isset( $block_gap ) ? null !== $block_gap : false;
329 + $default_block_layout = _wp_array_get( $block_type->supports, array( '__experimentalLayout', 'default' ), array() );
330 + $used_layout = isset( $block['attrs']['layout'] ) ? $block['attrs']['layout'] : $default_block_layout;
918 331
919 - // Child layout specific logic.
920 - if ( $child_layout || ! empty( $viewport_child_layouts ) ) {
921 - $base_child_layout = gutenberg_get_layout_child_values( $child_layout );
922 - $parent_layout = $block['parentLayout'] ?? array();
923 -
924 - /*
925 - * Generates a unique class for child block layout styles.
926 - *
927 - * To ensure consistent class generation across different page renders,
928 - * only properties that affect layout styling are used. These properties
929 - * come from `$block['attrs']['style']['layout']`, viewport overrides in
930 - * `$block['attrs']['style'][$breakpoint]['layout']`, and
931 - * `$block['parentLayout']`.
932 - *
933 - * As long as these properties coincide, the generated class will be the same.
934 - */
935 - $container_content_hash_input = array(
936 - 'layout' => $base_child_layout,
937 - 'parentLayout' => array_intersect_key(
938 - $parent_layout,
939 - array_flip( array( 'minimumColumnWidth', 'columnCount' ) )
940 - ),
941 - );
942 - foreach ( $viewport_child_layouts as $breakpoint => $viewport_data ) {
943 - $container_content_hash_input[ $breakpoint ] = $viewport_data['child_layout'];
332 + if ( isset( $used_layout['inherit'] ) && $used_layout['inherit'] ) {
333 + if ( ! $global_layout_settings ) {
334 + return $block_content;
944 335 }
945 - $container_content_class = gutenberg_unique_id_from_values(
946 - $container_content_hash_input,
947 - 'wp-container-content-'
948 - );
949 -
950 - $child_layout_styles = gutenberg_get_child_layout_style_rules(
951 - ".$container_content_class",
952 - $base_child_layout,
953 - $parent_layout
954 - );
955 -
956 - // Emit responsive child layout CSS using the same container-content class
957 - // so that base and responsive child layout share the exact same selector.
958 - foreach ( $viewport_child_layouts as $viewport_data ) {
959 - $viewport_child_styles = gutenberg_get_child_layout_style_rules(
960 - ".$container_content_class",
961 - $base_child_layout,
962 - $parent_layout,
963 - $viewport_data['child_layout']
964 - );
965 - foreach ( $viewport_child_styles as $index => $rule ) {
966 - $viewport_child_styles[ $index ]['rules_group'] = $viewport_data['media_query'];
967 - }
968 -
969 - $child_layout_styles = array_merge( $child_layout_styles, $viewport_child_styles );
970 - }
971 -
972 - /*
973 - * Add to the style engine store to enqueue and render layout styles.
974 - * Return styles here just to check if any exist.
975 - */
976 - $child_css = gutenberg_style_engine_get_stylesheet_from_css_rules(
977 - $child_layout_styles,
978 - array(
979 - 'context' => 'block-supports',
980 - 'prettify' => false,
981 - )
982 - );
983 -
984 - if ( $child_css ) {
985 - $outer_class_names[] = $container_content_class;
986 - }
987 336 }
988 337
989 - // Prep the processor for modifying the block output.
990 - $processor = new WP_HTML_Tag_Processor( $block_content );
991 -
992 - // Having no tags implies there are no tags onto which to add class names.
993 - if ( ! $processor->next_tag() ) {
994 - return $block_content;
995 - }
996 -
997 - /*
998 - * A block may not support layout but still be affected by a parent block's layout.
999 - *
1000 - * In these cases add the appropriate class names and then return early; there's
1001 - * no need to investigate on this block whether additional layout constraints apply.
1002 - */
1003 - if ( ! $block_supports_layout && ! empty( $outer_class_names ) ) {
1004 - foreach ( $outer_class_names as $class_name ) {
1005 - $processor->add_class( $class_name );
1006 - }
1007 - return $processor->get_updated_html();
1008 - } elseif ( ! $block_supports_layout ) {
1009 - // Ensure layout classnames are not injected if there is no layout support.
1010 - return $block_content;
1011 - }
1012 -
1013 - $global_settings = gutenberg_get_global_settings();
1014 - $fallback_layout = $block_type->supports['layout']['default'] ?? array();
1015 - if ( empty( $fallback_layout ) ) {
1016 - $fallback_layout = $block_type->supports['__experimentalLayout']['default'] ?? array();
1017 - }
1018 - $used_layout = $block['attrs']['layout'] ?? $fallback_layout;
1019 -
1020 338 $class_names = array();
1021 - $layout_definitions = gutenberg_get_layout_definitions();
339 + $layout_definitions = _wp_array_get( $global_layout_settings, array( 'definitions' ), array() );
340 + $container_class = wp_unique_id( 'wp-container-' );
341 + $layout_classname = '';
1022 342
1023 343 // Set the correct layout type for blocks using legacy content width.
1024 344 if ( isset( $used_layout['inherit'] ) && $used_layout['inherit'] || isset( $used_layout['contentSize'] ) && $used_layout['contentSize'] ) {
1025 345 $used_layout['type'] = 'constrained';
@@ -1024,11 +344,13 @@
1024 344 if ( isset( $used_layout['inherit'] ) && $used_layout['inherit'] || isset( $used_layout['contentSize'] ) && $used_layout['contentSize'] ) {
1025 345 $used_layout['type'] = 'constrained';
1026 346 }
1027 347
1028 - $root_padding_aware_alignments = $global_settings['useRootPaddingAwareAlignments'] ?? false;
1029 -
1030 - if ( $root_padding_aware_alignments && isset( $used_layout['type'] ) && 'constrained' === $used_layout['type'] ) {
348 + if (
349 + gutenberg_get_global_settings( array( 'useRootPaddingAwareAlignments' ) ) &&
350 + isset( $used_layout['type'] ) &&
351 + 'constrained' === $used_layout['type']
352 + ) {
1031 353 $class_names[] = 'has-global-padding';
1032 354 }
1033 355
1034 356 /*
@@ -1050,11 +372,11 @@
1050 372 }
1051 373
1052 374 // Get classname for layout type.
1053 375 if ( isset( $used_layout['type'] ) ) {
1054 - $layout_classname = $layout_definitions[ $used_layout['type'] ]['className'] ?? '';
376 + $layout_classname = _wp_array_get( $layout_definitions, array( $used_layout['type'], 'className' ), '' );
1055 377 } else {
1056 - $layout_classname = $layout_definitions['default']['className'] ?? '';
378 + $layout_classname = _wp_array_get( $layout_definitions, array( 'default', 'className' ), '' );
1057 379 }
1058 380
1059 381 if ( $layout_classname && is_string( $layout_classname ) ) {
1060 382 $class_names[] = sanitize_title( $layout_classname );
@@ -1065,93 +387,34 @@
1065 387 * Attribute-based Layout classnames are output in all cases.
1066 388 */
1067 389 if ( ! current_theme_supports( 'disable-layout-styles' ) ) {
1068 390
1069 - $gap_value = gutenberg_sanitize_block_gap_value( $block['attrs']['style']['spacing']['blockGap'] ?? null );
391 + $gap_value = _wp_array_get( $block, array( 'attrs', 'style', 'spacing', 'blockGap' ) );
1070 392
1071 - $fallback_gap_value = $block_type->supports['spacing']['blockGap']['__experimentalDefault'] ?? '0.5em';
1072 - $block_spacing = $block['attrs']['style']['spacing'] ?? null;
1073 -
1074 393 /*
1075 - * If a block's block.json skips serialization for spacing or spacing.blockGap,
1076 - * don't apply the user-defined value to the styles.
394 + * Skip if gap value contains unsupported characters.
395 + * Regex for CSS value borrowed from `safecss_filter_attr`, and used here
396 + * to only match against the value, not the CSS attribute.
1077 397 */
1078 - $should_skip_gap_serialization = wp_should_skip_block_supports_serialization( $block_type, 'spacing', 'blockGap' );
1079 -
1080 - $block_gap = $global_settings['spacing']['blockGap'] ?? null;
1081 - $has_block_gap_support = isset( $block_gap );
1082 -
1083 - // Get default blockGap value from global styles for use in layouts like grid.
1084 - // Check style variation first, then block-specific styles, then fall back to root styles.
1085 - $block_name = $block['blockName'] ?? '';
1086 - if ( null === $global_styles ) {
1087 - $global_styles = gutenberg_get_global_styles();
1088 - }
1089 -
1090 - // Check if the block has an active style variation with a blockGap value.
1091 - // Only check the registry if the className contains a variation class to avoid unnecessary lookups.
1092 - $variation_block_gap_value = null;
1093 - $block_class_name = $block['attrs']['className'] ?? '';
1094 - if ( $block_class_name && str_contains( $block_class_name, 'is-style-' ) && $block_name ) {
1095 - $styles_registry = WP_Block_Styles_Registry::get_instance();
1096 - $registered_styles = $styles_registry->get_registered_styles_for_block( $block_name );
1097 - $variation_name = gutenberg_get_block_style_variation_name_from_registered_style( $block_class_name, $registered_styles );
1098 - if ( $variation_name ) {
1099 - $variation_block_gap_value = $global_styles['blocks'][ $block_name ]['variations'][ $variation_name ]['spacing']['blockGap'] ?? null;
398 + if ( is_array( $gap_value ) ) {
399 + foreach ( $gap_value as $key => $value ) {
400 + $gap_value[ $key ] = $value && preg_match( '%[\\\(&=}]|/\*%', $value ) ? null : $value;
1100 401 }
402 + } else {
403 + $gap_value = $gap_value && preg_match( '%[\\\(&=}]|/\*%', $gap_value ) ? null : $gap_value;
1101 404 }
1102 405
1103 - $global_block_gap_value = $variation_block_gap_value ?? $global_styles['blocks'][ $block_name ]['spacing']['blockGap'] ?? $global_styles['spacing']['blockGap'] ?? null;
406 + $fallback_gap_value = _wp_array_get( $block_type->supports, array( 'spacing', 'blockGap', '__experimentalDefault' ), '0.5em' );
407 + $block_spacing = _wp_array_get( $block, array( 'attrs', 'style', 'spacing' ), null );
1104 408
1105 - if ( null !== $global_block_gap_value ) {
1106 - $fallback_gap_value = $global_block_gap_value;
1107 - }
1108 -
1109 409 /*
1110 - * We generate a unique ID based on all the data required to obtain the
1111 - * corresponding layout style. This way, the CSS class names keep the same
1112 - * even for different blocks with the same layout definition. We need this to
1113 - * make the CSS class names stable across paginations for features like the
1114 - * enhanced pagination of the Query block.
410 + * If a block's block.json skips serialization for spacing or spacing.blockGap,
411 + * don't apply the user-defined value to the styles.
1115 412 */
1116 - $container_class_hash_input = array(
1117 - $used_layout,
1118 - $has_block_gap_support,
1119 - $gap_value,
1120 - $should_skip_gap_serialization,
1121 - $fallback_gap_value,
1122 - $block_spacing,
1123 - );
413 + $should_skip_gap_serialization = gutenberg_should_skip_block_supports_serialization( $block_type, 'spacing', 'blockGap' );
1124 414
1125 - foreach ( array_keys( WP_Theme_JSON_Gutenberg::RESPONSIVE_BREAKPOINTS ) as $breakpoint ) {
1126 - $viewport_style = $style_attr[ $breakpoint ] ?? null;
1127 - if ( ! is_array( $viewport_style ) ) {
1128 - continue;
1129 - }
1130 -
1131 - $viewport_container_layout = gutenberg_get_layout_container_values( $viewport_style['layout'] ?? null );
1132 - if ( ! empty( $viewport_container_layout ) ) {
1133 - $container_class_hash_input[] = array(
1134 - 'breakpoint' => $breakpoint,
1135 - 'layout' => $viewport_container_layout,
1136 - );
1137 - }
1138 -
1139 - if ( isset( $viewport_style['spacing']['blockGap'] ) ) {
1140 - $container_class_hash_input[] = array(
1141 - 'breakpoint' => $breakpoint,
1142 - 'blockGap' => gutenberg_sanitize_block_gap_value( $viewport_style['spacing']['blockGap'] ),
1143 - );
1144 - }
1145 - }
1146 -
1147 - $container_class = gutenberg_unique_id_from_values(
1148 - $container_class_hash_input,
1149 - 'wp-container-' . sanitize_title( $block['blockName'] ) . '-is-layout-'
1150 - );
1151 -
1152 415 $style = gutenberg_get_layout_style(
1153 - ".$container_class",
416 + ".$container_class.$container_class",
1154 417 $used_layout,
1155 418 $has_block_gap_support,
1156 419 $gap_value,
1157 420 $should_skip_gap_serialization,
@@ -1162,195 +425,30 @@
1162 425 // Only add container class and enqueue block support styles if unique styles were generated.
1163 426 if ( ! empty( $style ) ) {
1164 427 $class_names[] = $container_class;
1165 428 }
1166 -
1167 - /*
1168 - * Emit responsive container layout styles using the same $container_class
1169 - * selector as the base layout so they target the inner block wrapper.
1170 - */
1171 - foreach ( WP_Theme_JSON_Gutenberg::RESPONSIVE_BREAKPOINTS as $breakpoint => $media_query ) {
1172 - $viewport_style = $style_attr[ $breakpoint ] ?? null;
1173 - if ( ! is_array( $viewport_style ) ) {
1174 - continue;
1175 - }
1176 -
1177 - $viewport_container_layout = gutenberg_get_layout_container_values( $viewport_style['layout'] ?? null );
1178 - $has_viewport_layout = ! empty( $viewport_container_layout );
1179 - $has_viewport_block_gap = isset( $viewport_style['spacing']['blockGap'] );
1180 -
1181 - if ( ! $has_viewport_layout && ! $has_viewport_block_gap ) {
1182 - continue;
1183 - }
1184 -
1185 - $viewport_gap_value = $has_viewport_block_gap
1186 - ? gutenberg_sanitize_block_gap_value( $viewport_style['spacing']['blockGap'] )
1187 - : $gap_value;
1188 - $viewport_block_spacing = is_array( $viewport_style['spacing'] ?? null )
1189 - ? array_replace( is_array( $block_spacing ) ? $block_spacing : array(), $viewport_style['spacing'] )
1190 - : $block_spacing;
1191 -
1192 - $viewport_styles = gutenberg_get_layout_style(
1193 - ".$container_class",
1194 - $used_layout,
1195 - $has_block_gap_support,
1196 - $viewport_gap_value,
1197 - $should_skip_gap_serialization,
1198 - $fallback_gap_value,
1199 - $viewport_block_spacing,
1200 - array(
1201 - 'rules_group' => $media_query,
1202 - 'viewport_overrides' => $viewport_container_layout,
1203 - 'has_block_gap_override' => $has_viewport_block_gap,
1204 - )
1205 - );
1206 -
1207 - if ( ! empty( $viewport_styles ) && ! in_array( $container_class, $class_names, true ) ) {
1208 - $class_names[] = $container_class;
1209 - }
1210 - }
1211 429 }
1212 430
1213 - // Add combined layout and block classname for global styles to hook onto.
1214 - $split_block_name = explode( '/', $block['blockName'] );
1215 - $full_block_name = 'core' === $split_block_name[0] ? end( $split_block_name ) : implode( '-', $split_block_name );
1216 - $class_names[] = 'wp-block-' . $full_block_name . '-' . $layout_classname;
431 + /**
432 + * The first chunk of innerContent contains the block markup up until the inner blocks start.
433 + * We want to target the opening tag of the inner blocks wrapper, which is the last tag in that chunk.
434 + */
435 + $inner_content_classnames = isset( $block['innerContent'][0] ) && 'string' === gettype( $block['innerContent'][0] ) ? gutenberg_get_classnames_from_last_tag( $block['innerContent'][0] ) : '';
1217 436
1218 - // Add classes to the outermost HTML tag if necessary.
1219 - if ( ! empty( $outer_class_names ) ) {
1220 - foreach ( $outer_class_names as $outer_class_name ) {
1221 - $processor->add_class( $outer_class_name );
437 + $content = new WP_HTML_Tag_Processor( $block_content );
438 + if ( $inner_content_classnames ) {
439 + $content->next_tag( array( 'class_name' => $inner_content_classnames ) );
440 + foreach ( $class_names as $class_name ) {
441 + $content->add_class( $class_name );
1222 442 }
443 + } else {
444 + $content->next_tag();
445 + $content->add_class( implode( ' ', $class_names ) );
1223 446 }
1224 447
1225 - /*
1226 - * Attempts to refer to the inner-block wrapping element by its class attribute.
1227 - *
1228 - * When examining a block's inner content, if a block has inner blocks, then
1229 - * the first content item will likely be a text (HTML) chunk immediately
1230 - * preceding the inner blocks. The last HTML tag in that chunk would then be
1231 - * an opening tag for an element that wraps the inner blocks.
1232 - *
1233 - * There's no reliable way to associate this wrapper in $block_content because
1234 - * it may have changed during the rendering pipeline (as inner contents is
1235 - * provided before rendering) and through previous filters. In many cases,
1236 - * however, the `class` attribute will be a good-enough identifier, so this
1237 - * code finds the last tag in that chunk and stores the `class` attribute
1238 - * so that it can be used later when working through the rendered block output
1239 - * to identify the wrapping element and add the remaining class names to it.
1240 - *
1241 - * It's also possible that no inner block wrapper even exists. If that's the
1242 - * case this code could apply the class names to an invalid element.
1243 - *
1244 - * Example:
1245 - *
1246 - * $block['innerBlocks'] = array( $list_item );
1247 - * $block['innerContent'] = array( '<ul class="list-wrapper is-unordered">', null, '</ul>' );
1248 - *
1249 - * // After rendering, the initial contents may have been modified by other renderers or filters.
1250 - * $block_content = <<<HTML
1251 - * <figure>
1252 - * <ul class="annotated-list list-wrapper is-unordered">
1253 - * <li>Code</li>
1254 - * </ul><figcaption>It's a list!</figcaption>
1255 - * </figure>
1256 - * HTML;
1257 - *
1258 - * Although it is possible that the original block-wrapper classes are changed in $block_content
1259 - * from how they appear in $block['innerContent'], it's likely that the original class attributes
1260 - * are still present in the wrapper as they are in this example. Frequently, additional classes
1261 - * will also be present; rarely should classes be removed.
1262 - *
1263 - * @todo Find a better way to match the first inner block. If it's possible to identify where the
1264 - * first inner block starts, then it will be possible to find the last tag before it starts
1265 - * and then that tag, if an opening tag, can be solidly identified as a wrapping element.
1266 - * Can some unique value or class or ID be added to the inner blocks when they process
1267 - * so that they can be extracted here safely without guessing? Can the block rendering function
1268 - * return information about where the rendered inner blocks start?
1269 - *
1270 - * @var string|null
1271 - */
1272 - $inner_block_wrapper_classes = null;
1273 - $first_chunk = $block['innerContent'][0] ?? null;
1274 - if ( is_string( $first_chunk ) && count( $block['innerContent'] ) > 1 ) {
1275 - $first_chunk_processor = new WP_HTML_Tag_Processor( $first_chunk );
1276 - /*
1277 - * Use a stack to track open elements as tags are visited. Void elements
1278 - * (those without a matching closing tag) are excluded so they don't
1279 - * accumulate on the stack. At the end of the chunk, every element still
1280 - * on the stack is unclosed — meaning its closing tag lives in a later
1281 - * innerContent entry alongside the inner blocks, which makes it the
1282 - * inner-block container. Elements that open and close within this chunk
1283 - * are siblings that precede the inner blocks and should be ignored.
1284 - * The last unclosed element with a class attribute is the best candidate
1285 - * for the inner-block wrapper.
1286 - */
1287 - $tag_stack = array();
1288 - while ( $first_chunk_processor->next_tag( array( 'tag_closers' => 'visit' ) ) ) {
1289 - if ( $first_chunk_processor->is_tag_closer() ) {
1290 - array_pop( $tag_stack );
1291 - } elseif ( ! WP_HTML_Processor::is_void( $first_chunk_processor->get_tag() ) ) {
1292 - $tag_stack[] = $first_chunk_processor->get_attribute( 'class' );
1293 - }
1294 - }
1295 - foreach ( array_reverse( $tag_stack ) as $class_attribute ) {
1296 - if ( is_string( $class_attribute ) && ! empty( $class_attribute ) ) {
1297 - $inner_block_wrapper_classes = $class_attribute;
1298 - break;
1299 - }
1300 - }
1301 - }
1302 -
1303 - /*
1304 - * If necessary, advance to what is likely to be an inner block wrapper tag.
1305 - *
1306 - * This advances until it finds the first tag containing the original class
1307 - * attribute from above. If none is found it will scan to the end of the block
1308 - * and fail to add any class names.
1309 - *
1310 - * If there is no block wrapper it won't advance at all, in which case the
1311 - * class names will be added to the first and outermost tag of the block.
1312 - * For cases where this outermost tag is the only tag surrounding inner
1313 - * blocks then the outer wrapper and inner wrapper are the same.
1314 - */
1315 - do {
1316 - if ( ! $inner_block_wrapper_classes ) {
1317 - break;
1318 - }
1319 -
1320 - $class_attribute = $processor->get_attribute( 'class' );
1321 - if ( is_string( $class_attribute ) && str_contains( $class_attribute, $inner_block_wrapper_classes ) ) {
1322 - break;
1323 - }
1324 - } while ( $processor->next_tag() );
1325 -
1326 - // Add the remaining class names.
1327 - foreach ( $class_names as $class_name ) {
1328 - $processor->add_class( $class_name );
1329 - }
1330 -
1331 - return $processor->get_updated_html();
448 + return (string) $content;
1332 449 }
1333 450
1334 -/*
1335 - * Add a `render_block_data` filter to fetch the parent block layout data.
1336 - */
1337 -add_filter(
1338 - 'render_block_data',
1339 - function ( $parsed_block, $source_block, $parent_block ) {
1340 - /*
1341 - * Check if the parent block exists and if it has a layout attribute.
1342 - * If it does, add the parent layout to the parsed block.
1343 - */
1344 - if ( $parent_block && isset( $parent_block->parsed_block['attrs']['layout'] ) ) {
1345 - $parsed_block['parentLayout'] = $parent_block->parsed_block['attrs']['layout'];
1346 - }
1347 - return $parsed_block;
1348 - },
1349 - 10,
1350 - 3
1351 -);
1352 -
1353 451 // Register the block support. (overrides core one).
1354 452 WP_Block_Supports::get_instance()->register(
1355 453 'layout',
1356 454 array(
@@ -1372,9 +470,9 @@
1372 470 * @param array $block Block object.
1373 471 * @return string Filtered block content.
1374 472 */
1375 473 function gutenberg_restore_group_inner_container( $block_content, $block ) {
1376 - $tag_name = $block['attrs']['tagName'] ?? 'div';
474 + $tag_name = isset( $block['attrs']['tagName'] ) ? $block['attrs']['tagName'] : 'div';
1377 475 $group_with_inner_container_regex = sprintf(
1378 476 '/(^\s*<%1$s\b[^>]*wp-block-group(\s|")[^>]*>)(\s*<div\b[^>]*wp-block-group__inner-container(\s|")[^>]*>)((.|\S|\s)*)/U',
1379 477 preg_quote( $tag_name, '/' )
1380 478 );
@@ -1380,62 +478,34 @@
1380 478 );
1381 479 if (
1382 480 wp_theme_has_theme_json() ||
1383 481 1 === preg_match( $group_with_inner_container_regex, $block_content ) ||
1384 - ( isset( $block['attrs']['layout']['type'] ) && ( 'flex' === $block['attrs']['layout']['type'] || 'grid' === $block['attrs']['layout']['type'] ) )
482 + ( isset( $block['attrs']['layout']['type'] ) && 'flex' === $block['attrs']['layout']['type'] )
1385 483 ) {
1386 484 return $block_content;
1387 485 }
1388 486
1389 - /*
1390 - * This filter runs after the layout classnames have been added to the block, so they
1391 - * have to be removed from the outer wrapper and then added to the inner.
1392 - */
1393 - $layout_classes = array();
1394 - $processor = new WP_HTML_Tag_Processor( $block_content );
1395 -
1396 - if ( $processor->next_tag( array( 'class_name' => 'wp-block-group' ) ) ) {
1397 - foreach ( $processor->class_list() as $class_name ) {
1398 - if ( str_contains( $class_name, 'layout' ) ) {
1399 - array_push( $layout_classes, $class_name );
1400 - $processor->remove_class( $class_name );
1401 - }
1402 - }
1403 - }
1404 -
1405 - $content_without_layout_classes = $processor->get_updated_html();
1406 - $replace_regex = sprintf(
487 + $replace_regex = sprintf(
1407 488 '/(^\s*<%1$s\b[^>]*wp-block-group[^>]*>)(.*)(<\/%1$s>\s*$)/ms',
1408 489 preg_quote( $tag_name, '/' )
1409 490 );
1410 - $updated_content = preg_replace_callback(
491 + $updated_content = preg_replace_callback(
1411 492 $replace_regex,
1412 - static function ( $matches ) {
493 + function( $matches ) {
1413 494 return $matches[1] . '<div class="wp-block-group__inner-container">' . $matches[2] . '</div>' . $matches[3];
1414 495 },
1415 - $content_without_layout_classes
496 + $block_content
1416 497 );
1417 -
1418 - // Add layout classes to inner wrapper.
1419 - if ( ! empty( $layout_classes ) ) {
1420 - $processor = new WP_HTML_Tag_Processor( $updated_content );
1421 - if ( $processor->next_tag( array( 'class_name' => 'wp-block-group__inner-container' ) ) ) {
1422 - foreach ( $layout_classes as $class_name ) {
1423 - $processor->add_class( $class_name );
1424 - }
1425 - }
1426 - $updated_content = $processor->get_updated_html();
1427 - }
1428 -
1429 498 return $updated_content;
1430 499 }
1431 500
1432 501 if ( function_exists( 'wp_restore_group_inner_container' ) ) {
1433 - remove_filter( 'render_block', 'wp_restore_group_inner_container', 10 );
1434 - remove_filter( 'render_block_core/group', 'wp_restore_group_inner_container', 10 );
502 + remove_filter( 'render_block', 'wp_restore_group_inner_container', 10, 2 );
503 + remove_filter( 'render_block_core/group', 'wp_restore_group_inner_container', 10, 2 );
1435 504 }
1436 505 add_filter( 'render_block_core/group', 'gutenberg_restore_group_inner_container', 10, 2 );
1437 506
507 +
1438 508 /**
1439 509 * For themes without theme.json file, make sure
1440 510 * to restore the outer div for the aligned image block
1441 511 * to avoid breaking styles relying on that div.
@@ -1444,57 +514,54 @@
1444 514 * @param array $block Block object.
1445 515 * @return string Filtered block content.
1446 516 */
1447 517 function gutenberg_restore_image_outer_container( $block_content, $block ) {
1448 - if ( wp_theme_has_theme_json() ) {
1449 - return $block_content;
1450 - }
518 + $image_with_align = "
519 +/# 1) everything up to the class attribute contents
520 +(
521 + ^\s*
522 + <figure\b
523 + [^>]*
524 + \bclass=
525 + [\"']
526 +)
527 +# 2) the class attribute contents
528 +(
529 + [^\"']*
530 + \bwp-block-image\b
531 + [^\"']*
532 + \b(?:alignleft|alignright|aligncenter)\b
533 + [^\"']*
534 +)
535 +# 3) everything after the class attribute contents
536 +(
537 + [\"']
538 + [^>]*
539 + >
540 + .*
541 + <\/figure>
542 +)/iUx";
1451 543
1452 - $figure_processor = new WP_HTML_Tag_Processor( $block_content );
1453 544 if (
1454 - ! $figure_processor->next_tag( 'FIGURE' ) ||
1455 - ! $figure_processor->has_class( 'wp-block-image' ) ||
1456 - ! (
1457 - $figure_processor->has_class( 'alignleft' ) ||
1458 - $figure_processor->has_class( 'aligncenter' ) ||
1459 - $figure_processor->has_class( 'alignright' )
1460 - )
545 + wp_theme_has_theme_json() ||
546 + 0 === preg_match( $image_with_align, $block_content, $matches )
1461 547 ) {
1462 548 return $block_content;
1463 549 }
1464 550
1465 - /*
1466 - * The next section of code wraps the existing figure in a new DIV element.
1467 - * While doing it, it needs to transfer the layout and the additional CSS
1468 - * class names from the original figure upward to the wrapper.
1469 - *
1470 - * Example:
1471 - *
1472 - * // From this…
1473 - * <!-- wp:image {"className":"hires"} -->
1474 - * <figure class="wp-block-image wide hires">…
1475 - *
1476 - * // To this…
1477 - * <div class="wp-block-image hires"><figure class="wide">…
1478 - */
1479 - $wrapper_processor = new WP_HTML_Tag_Processor( '<div>' );
1480 - $wrapper_processor->next_token();
1481 - $wrapper_processor->set_attribute(
1482 - 'class',
1483 - is_string( $block['attrs']['className'] ?? null )
1484 - ? "wp-block-image {$block['attrs']['className']}"
1485 - : 'wp-block-image'
1486 - );
551 + $wrapper_classnames = array( 'wp-block-image' );
1487 552
1488 - // And remove them from the existing content; it has been transferred upward.
1489 - $figure_processor->remove_class( 'wp-block-image' );
1490 - foreach ( $wrapper_processor->class_list() as $class_name ) {
1491 - $figure_processor->remove_class( $class_name );
553 + // If the block has a classNames attribute these classnames need to be removed from the content and added back
554 + // to the new wrapper div also.
555 + if ( ! empty( $block['attrs']['className'] ) ) {
556 + $wrapper_classnames = array_merge( $wrapper_classnames, explode( ' ', $block['attrs']['className'] ) );
1492 557 }
558 + $content_classnames = explode( ' ', $matches[2] );
559 + $filtered_content_classnames = array_diff( $content_classnames, $wrapper_classnames );
1493 560
1494 - return "{$wrapper_processor->get_updated_html()}{$figure_processor->get_updated_html()}</div>";
561 + return '<div class="' . implode( ' ', $wrapper_classnames ) . '">' . $matches[1] . implode( ' ', $filtered_content_classnames ) . $matches[3] . '</div>';
1495 562 }
1496 563
1497 564 if ( function_exists( 'wp_restore_image_outer_container' ) ) {
1498 - remove_filter( 'render_block_core/image', 'wp_restore_image_outer_container', 10 );
565 + remove_filter( 'render_block_core/image', 'wp_restore_image_outer_container', 10, 2 );
1499 566 }
1500 567 add_filter( 'render_block_core/image', 'gutenberg_restore_image_outer_container', 10, 2 );