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