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