??????????????
PK ! +(5
layout.phpnu [ > $registered_styles Currently registered block styles.
* @return string|null The name of the first registered variation, or null if none found.
*/
function wp_get_block_style_variation_name_from_registered_style( string $class_name, array $registered_styles = array() ): ?string {
if ( ! $class_name ) {
return null;
}
$registered_names = array_filter( array_column( $registered_styles, 'name' ) );
$prefix = 'is-style-';
$length = strlen( $prefix );
foreach ( explode( ' ', $class_name ) as $class ) {
if ( str_starts_with( $class, $prefix ) ) {
$variation = substr( $class, $length );
if ( 'default' !== $variation && in_array( $variation, $registered_names, true ) ) {
return $variation;
}
}
}
return null;
}
/**
* Returns the child-layout-only subset of a layout object.
*
* @since 7.1.0
*
* @param mixed $layout Layout object.
* @return array Child layout values, or an empty array.
*/
function wp_get_layout_child_values( $layout ) {
if ( ! is_array( $layout ) ) {
return array();
}
return array_intersect_key(
$layout,
array_flip( array( 'selfStretch', 'flexSize', 'columnStart', 'columnSpan', 'rowStart', 'rowSpan' ) )
);
}
/**
* Returns the container-layout subset of a layout object.
*
* @since 7.1.0
*
* @param mixed $layout Layout object.
* @return array Container layout values, or an empty array.
*/
function wp_get_layout_container_values( $layout ) {
if ( ! is_array( $layout ) ) {
return array();
}
return array_diff_key(
$layout,
array_flip( array( 'selfStretch', 'flexSize', 'columnStart', 'columnSpan', 'rowStart', 'rowSpan' ) )
);
}
/**
* Sanitizes a block gap value before layout style generation.
*
* @since 7.1.0
*
* @param string|array|null $gap_value Block gap value.
* @return string|array|null Sanitized block gap value.
*/
function wp_sanitize_block_gap_value( $gap_value ) {
if ( is_array( $gap_value ) ) {
foreach ( $gap_value as $key => $value ) {
$gap_value[ $key ] = ! is_scalar( $value ) || ( $value && preg_match( '%[\\\(&=}]|/\*%', (string) $value ) ) ? null : $value;
}
return $gap_value;
}
return $gap_value && preg_match( '%[\\\(&=}]|/\*%', $gap_value ) ? null : $gap_value;
}
/**
* Returns child layout styles for a block affected by its parent's layout.
*
* @since 7.1.0
*
* @param string $selector CSS selector.
* @param array $child_layout Child layout values.
* @param array $parent_layout Parent layout values.
* @param array|null $viewport_overrides Optional. Child viewport layout overrides to emit.
* @return array Child layout style rules.
*/
function wp_get_child_layout_style_rules( $selector, $child_layout, $parent_layout = array(), $viewport_overrides = null ) {
$base_child_layout = is_array( $child_layout ) ? $child_layout : array();
$viewport_overrides = is_array( $viewport_overrides ) ? $viewport_overrides : null;
$child_layout = null === $viewport_overrides ? $base_child_layout : array_replace( $base_child_layout, $viewport_overrides );
$child_layout_declarations = array();
$child_layout_styles = array();
$has_viewport_property_override = static function ( $property ) use ( $viewport_overrides ) {
return array_key_exists( $property, $viewport_overrides );
};
$self_stretch = $child_layout['selfStretch'] ?? null;
$base_self_stretch = $base_child_layout['selfStretch'] ?? null;
/*
* These are the serialized `selfStretch` values. `max` used to be called
* "Fixed" in the UI, but was renamed and replaced by `fixedNoShrink`.
*/
$flex_child_layout_values = array(
'fit' => 'fit',
'grow' => 'fill',
'max' => 'fixed',
'fixed' => 'fixedNoShrink',
);
$flex_size_values = array(
$flex_child_layout_values['max'],
$flex_child_layout_values['fixed'],
);
if ( null === $viewport_overrides || $has_viewport_property_override( 'selfStretch' ) || $has_viewport_property_override( 'flexSize' ) ) {
if (
null !== $viewport_overrides &&
( $flex_child_layout_values['fit'] === $self_stretch || $flex_child_layout_values['grow'] === $self_stretch ) &&
in_array( $base_self_stretch, $flex_size_values, true ) &&
isset( $base_child_layout['flexSize'] )
) {
$child_layout_declarations['flex-basis'] = 'unset';
if ( $flex_child_layout_values['fixed'] === $base_self_stretch ) {
$child_layout_declarations['flex-shrink'] = 'unset';
}
}
if ( in_array( $self_stretch, $flex_size_values, true ) && isset( $child_layout['flexSize'] ) ) {
$child_layout_declarations['flex-basis'] = $child_layout['flexSize'];
if ( $flex_child_layout_values['fixed'] === $self_stretch ) {
$child_layout_declarations['flex-shrink'] = '0';
} elseif ( null !== $viewport_overrides && $flex_child_layout_values['fixed'] === $base_self_stretch ) {
$child_layout_declarations['flex-shrink'] = 'unset';
}
$child_layout_declarations['box-sizing'] = 'border-box';
} elseif ( $flex_child_layout_values['grow'] === $self_stretch ) {
$child_layout_declarations['flex-grow'] = '1';
}
}
$column_start = $child_layout['columnStart'] ?? null;
$column_span = $child_layout['columnSpan'] ?? null;
if ( null === $viewport_overrides || $has_viewport_property_override( 'columnStart' ) || $has_viewport_property_override( 'columnSpan' ) ) {
if ( $column_start && $column_span ) {
$child_layout_declarations['grid-column'] = "$column_start / span $column_span";
} elseif ( $column_start ) {
$child_layout_declarations['grid-column'] = "$column_start";
} elseif ( $column_span ) {
$child_layout_declarations['grid-column'] = "span $column_span";
}
}
$row_start = $child_layout['rowStart'] ?? null;
$row_span = $child_layout['rowSpan'] ?? null;
if ( null === $viewport_overrides || $has_viewport_property_override( 'rowStart' ) || $has_viewport_property_override( 'rowSpan' ) ) {
if ( $row_start && $row_span ) {
$child_layout_declarations['grid-row'] = "$row_start / span $row_span";
} elseif ( $row_start ) {
$child_layout_declarations['grid-row'] = "$row_start";
} elseif ( $row_span ) {
$child_layout_declarations['grid-row'] = "span $row_span";
}
}
if ( ! empty( $child_layout_declarations ) ) {
$child_layout_styles[] = array(
'selector' => $selector,
'declarations' => $child_layout_declarations,
);
}
$minimum_column_width = $parent_layout['minimumColumnWidth'] ?? null;
$column_count = $parent_layout['columnCount'] ?? null;
/*
* If columnSpan or columnStart is set, and the parent grid is responsive, i.e. if it has a minimumColumnWidth set,
* the columnSpan should be removed once the grid is smaller than the span, and columnStart should be removed
* once the grid has less columns than the start.
* If there's a minimumColumnWidth, the grid is responsive. But if the minimumColumnWidth value wasn't changed, it won't be set.
* In that case, if columnCount doesn't exist, we can assume that the grid is responsive.
*/
if ( null === $viewport_overrides && ( $column_span || $column_start ) && ( $minimum_column_width || ! $column_count ) ) {
$column_span_number = floatval( $column_span );
$column_start_number = floatval( $column_start );
$parent_column_width = $minimum_column_width ? $minimum_column_width : '12rem';
$parent_column_value = floatval( $parent_column_width );
$parent_column_unit = explode( $parent_column_value, $parent_column_width );
$num_cols_to_break_at = 2;
if ( $column_span_number && $column_start_number ) {
$num_cols_to_break_at = $column_start_number + $column_span_number - 1;
} elseif ( $column_span_number ) {
$num_cols_to_break_at = $column_span_number;
} else {
$num_cols_to_break_at = $column_start_number;
}
/*
* If there is no unit, the width has somehow been mangled so we reset both unit and value
* to defaults.
* Additionally, the unit should be one of px, rem or em, so that also needs to be checked.
*/
if ( count( $parent_column_unit ) <= 1 ) {
$parent_column_unit = 'rem';
$parent_column_value = 12;
} else {
$parent_column_unit = $parent_column_unit[1];
if ( ! in_array( $parent_column_unit, array( 'px', 'rem', 'em' ), true ) ) {
$parent_column_unit = 'rem';
}
}
/*
* A default gap value is used for this computation because custom gap values may not be
* viable to use in the computation of the container query value.
*/
$default_gap_value = 'px' === $parent_column_unit ? 24 : 1.5;
$container_query_value = $num_cols_to_break_at * $parent_column_value + ( $num_cols_to_break_at - 1 ) * $default_gap_value;
$minimum_container_query_value = $parent_column_value * 2 + $default_gap_value - 1;
$container_query_value = max( $container_query_value, $minimum_container_query_value ) . $parent_column_unit;
// If a span is set we want to preserve it as long as possible, otherwise we just reset the value.
$grid_column_value = $column_span && $column_span > 1 ? '1/-1' : 'auto';
$child_layout_styles[] = array(
'rules_group' => "@container (max-width: $container_query_value )",
'selector' => $selector,
'declarations' => array(
'grid-column' => $grid_column_value,
'grid-row' => 'auto',
),
);
}
return $child_layout_styles;
}
/**
* Returns layout definitions, keyed by layout type.
*
* Provides a common definition of slugs, classnames, base styles, and spacing styles for each layout type.
* When making changes or additions to layout definitions, the corresponding JavaScript definitions should
* also be updated.
*
* @since 6.3.0
* @since 6.6.0 Updated specificity for compatibility with 0-1-0 global styles specificity.
* @access private
*
* @return array[] Layout definitions.
*/
function wp_get_layout_definitions() {
$layout_definitions = array(
'default' => array(
'name' => 'default',
'slug' => 'flow',
'className' => 'is-layout-flow',
'baseStyles' => array(
array(
'selector' => ' > .alignleft',
'rules' => array(
'float' => 'left',
'margin-inline-start' => '0',
'margin-inline-end' => '2em',
),
),
array(
'selector' => ' > .alignright',
'rules' => array(
'float' => 'right',
'margin-inline-start' => '2em',
'margin-inline-end' => '0',
),
),
array(
'selector' => ' > .aligncenter',
'rules' => array(
'margin-left' => 'auto !important',
'margin-right' => 'auto !important',
),
),
),
'spacingStyles' => array(
array(
'selector' => ' > :first-child',
'rules' => array(
'margin-block-start' => '0',
),
),
array(
'selector' => ' > :last-child',
'rules' => array(
'margin-block-end' => '0',
),
),
array(
'selector' => ' > *',
'rules' => array(
'margin-block-start' => null,
'margin-block-end' => '0',
),
),
),
),
'constrained' => array(
'name' => 'constrained',
'slug' => 'constrained',
'className' => 'is-layout-constrained',
'baseStyles' => array(
array(
'selector' => ' > .alignleft',
'rules' => array(
'float' => 'left',
'margin-inline-start' => '0',
'margin-inline-end' => '2em',
),
),
array(
'selector' => ' > .alignright',
'rules' => array(
'float' => 'right',
'margin-inline-start' => '2em',
'margin-inline-end' => '0',
),
),
array(
'selector' => ' > .aligncenter',
'rules' => array(
'margin-left' => 'auto !important',
'margin-right' => 'auto !important',
),
),
array(
'selector' => ' > :where(:not(.alignleft):not(.alignright):not(.alignfull))',
'rules' => array(
'max-width' => 'var(--wp--style--global--content-size)',
'margin-left' => 'auto !important',
'margin-right' => 'auto !important',
),
),
array(
'selector' => ' > .alignwide',
'rules' => array(
'max-width' => 'var(--wp--style--global--wide-size)',
),
),
),
'spacingStyles' => array(
array(
'selector' => ' > :first-child',
'rules' => array(
'margin-block-start' => '0',
),
),
array(
'selector' => ' > :last-child',
'rules' => array(
'margin-block-end' => '0',
),
),
array(
'selector' => ' > *',
'rules' => array(
'margin-block-start' => null,
'margin-block-end' => '0',
),
),
),
),
'flex' => array(
'name' => 'flex',
'slug' => 'flex',
'className' => 'is-layout-flex',
'displayMode' => 'flex',
'baseStyles' => array(
array(
'selector' => '',
'rules' => array(
'flex-wrap' => 'wrap',
'align-items' => 'center',
),
),
array(
'selector' => ' > :is(*, div)', // :is(*, div) instead of just * increases the specificity by 001.
'rules' => array(
'margin' => '0',
),
),
),
'spacingStyles' => array(
array(
'selector' => '',
'rules' => array(
'gap' => null,
),
),
),
),
'grid' => array(
'name' => 'grid',
'slug' => 'grid',
'className' => 'is-layout-grid',
'displayMode' => 'grid',
'baseStyles' => array(
array(
'selector' => ' > :is(*, div)', // :is(*, div) instead of just * increases the specificity by 001.
'rules' => array(
'margin' => '0',
),
),
),
'spacingStyles' => array(
array(
'selector' => '',
'rules' => array(
'gap' => null,
),
),
),
),
);
return $layout_definitions;
}
/**
* Registers the layout block attribute for block types that support it.
*
* @since 5.8.0
* @since 6.3.0 Check for layout support via the `layout` key with fallback to `__experimentalLayout`.
* @access private
*
* @param WP_Block_Type $block_type Block Type.
*/
function wp_register_layout_support( $block_type ) {
$support_layout = block_has_support( $block_type, 'layout', false ) || block_has_support( $block_type, '__experimentalLayout', false );
if ( $support_layout ) {
if ( ! $block_type->attributes ) {
$block_type->attributes = array();
}
if ( ! array_key_exists( 'layout', $block_type->attributes ) ) {
$block_type->attributes['layout'] = array(
'type' => 'object',
);
}
}
}
/**
* Generates the CSS corresponding to the provided layout.
*
* @since 5.9.0
* @since 6.1.0 Added `$block_spacing` param, use style engine to enqueue styles.
* @since 6.3.0 Added grid layout type.
* @since 6.6.0 Removed duplicated selector from layout styles.
* Enabled negative margins for alignfull children of blocks with custom padding.
* @since 7.1.0 Added options array with options to process responsive styles.
* @access private
*
* @param string $selector CSS selector.
* @param array $layout Layout object. The one that is passed has already checked
* the existence of default block layout.
* @param bool $has_block_gap_support Optional. Whether the theme has support for the block gap. Default false.
* @param string|string[]|null $gap_value Optional. The block gap value to apply. Default null.
* @param bool $should_skip_gap_serialization Optional. Whether to skip applying the user-defined value set in the editor. Default false.
* @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'.
* @param array|null $block_spacing Optional. Custom spacing set on the block. Default null.
* @param array $options {
* Optional. Extra options for internal callers. Default empty array.
*
* @type array $viewport_overrides An array of layout property overrides for the sake of style generation,
* keyed by property name.
* @type string|null $rules_group Optional group name for the rules. Default null.
* @type bool $has_block_gap_override Whether the block gap has been overridden. Default false.
* }
* @return string CSS styles on success. Else, empty string.
*/
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, $options = array() ) {
$base_layout = is_array( $layout ) ? $layout : array();
$viewport_overrides = $options['viewport_overrides'] ?? null;
$layout_for_styles = null === $viewport_overrides ? $base_layout : array_replace( $base_layout, $viewport_overrides );
$layout_type = $base_layout['type'] ?? 'default';
$rules_group = $options['rules_group'] ?? null;
$has_block_gap_override = ! empty( $options['has_block_gap_override'] );
$should_output_block_gap = null === $viewport_overrides || $has_block_gap_override;
/*
* Viewport styles only store changed fields. If a field is present with null,
* the user cleared a value inherited from the default viewport, so check
* whether the key exists rather than whether the value is truthy.
*/
$has_viewport_property_override = static function ( $property ) use ( $viewport_overrides ) {
return array_key_exists( $property, $viewport_overrides );
};
$layout_styles = array();
if ( 'default' === $layout_type ) {
if ( $has_block_gap_support && $should_output_block_gap ) {
if ( is_array( $gap_value ) ) {
$gap_value = $gap_value['top'] ?? null;
}
if ( null !== $gap_value && ! $should_skip_gap_serialization ) {
// Get spacing CSS variable from preset value if provided.
if ( is_string( $gap_value ) && str_contains( $gap_value, 'var:preset|spacing|' ) ) {
$index_to_splice = strrpos( $gap_value, '|' ) + 1;
$slug = _wp_to_kebab_case( substr( $gap_value, $index_to_splice ) );
$gap_value = "var(--wp--preset--spacing--$slug)";
}
array_push(
$layout_styles,
array(
'selector' => "$selector > *",
'declarations' => array(
'margin-block-start' => '0',
'margin-block-end' => '0',
),
),
array(
'selector' => "$selector > * + *",
'declarations' => array(
'margin-block-start' => $gap_value,
'margin-block-end' => '0',
),
)
);
}
}
} elseif ( 'constrained' === $layout_type ) {
$content_size = $layout_for_styles['contentSize'] ?? '';
$wide_size = $layout_for_styles['wideSize'] ?? '';
$justify_content = $layout_for_styles['justifyContent'] ?? 'center';
// Check if viewport-specific ("override") values exist. Null values are valid and mean the user cleared a value inherited from the default viewport.
$has_justify_content_override = null !== $viewport_overrides && $has_viewport_property_override( 'justifyContent' );
$has_content_size_override = null !== $viewport_overrides && $has_viewport_property_override( 'contentSize' );
$has_wide_size_override = null !== $viewport_overrides && $has_viewport_property_override( 'wideSize' );
/*
* 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
* value for contentSize or wideSize. If a viewport clears a custom constrained size, reset to the global layout variable.
*/
$should_output_constrained_sizes = null === $viewport_overrides || $has_content_size_override || $has_wide_size_override;
$is_resetting_constrained_sizes = null !== $viewport_overrides &&
(
( $has_content_size_override && ! $content_size ) ||
( $has_wide_size_override && ! $wide_size )
);
// If a viewport clears a custom constrained size, reset to the global layout variable.
$all_max_width_value = $content_size
? $content_size
: ( $wide_size && ! $has_content_size_override ? $wide_size : 'var(--wp--style--global--content-size, none)' );
$wide_max_width_value = $wide_size
? $wide_size
: ( $content_size && ! $has_wide_size_override ? $content_size : 'var(--wp--style--global--wide-size, none)' );
// Make sure there is a single CSS rule, and all tags are stripped for security.
$all_max_width_value = safecss_filter_attr( explode( ';', $all_max_width_value )[0] );
$wide_max_width_value = safecss_filter_attr( explode( ';', $wide_max_width_value )[0] );
$margin_left = 'left' === $justify_content ? '0 !important' : 'auto !important';
$margin_right = 'right' === $justify_content ? '0 !important' : 'auto !important';
if ( $should_output_constrained_sizes && ( $content_size || $wide_size || $is_resetting_constrained_sizes ) ) {
$content_size_declarations = array(
'max-width' => $all_max_width_value,
);
if ( null === $viewport_overrides || $has_justify_content_override ) {
$content_size_declarations['margin-left'] = $margin_left;
$content_size_declarations['margin-right'] = $margin_right;
}
array_push(
$layout_styles,
array(
'selector' => "$selector > :where(:not(.alignleft):not(.alignright):not(.alignfull))",
'declarations' => $content_size_declarations,
),
array(
'selector' => "$selector > .alignwide",
'declarations' => array( 'max-width' => $wide_max_width_value ),
),
array(
'selector' => "$selector .alignfull",
'declarations' => array( 'max-width' => 'none' ),
)
);
}
if ( null === $viewport_overrides && isset( $block_spacing ) ) {
$block_spacing_values = wp_style_engine_get_styles(
array(
'spacing' => $block_spacing,
)
);
/*
* Handle negative margins for alignfull children of blocks with custom padding set.
* They're added separately because padding might only be set on one side.
*/
if ( isset( $block_spacing_values['declarations']['padding-right'] ) ) {
$padding_right = $block_spacing_values['declarations']['padding-right'];
// Add unit if 0.
if ( '0' === $padding_right ) {
$padding_right = '0px';
}
$layout_styles[] = array(
'selector' => "$selector > .alignfull",
'declarations' => array( 'margin-right' => "calc($padding_right * -1)" ),
);
}
if ( isset( $block_spacing_values['declarations']['padding-left'] ) ) {
$padding_left = $block_spacing_values['declarations']['padding-left'];
// Add unit if 0.
if ( '0' === $padding_left ) {
$padding_left = '0px';
}
$layout_styles[] = array(
'selector' => "$selector > .alignfull",
'declarations' => array( 'margin-left' => "calc($padding_left * -1)" ),
);
}
}
if ( $has_justify_content_override && ! $should_output_constrained_sizes ) {
$layout_styles[] = array(
'selector' => "$selector > :where(:not(.alignleft):not(.alignright):not(.alignfull))",
'declarations' => array(
'margin-left' => $margin_left,
'margin-right' => $margin_right,
),
);
} elseif ( null === $viewport_overrides ) {
if ( 'left' === $justify_content ) {
$layout_styles[] = array(
'selector' => "$selector > :where(:not(.alignleft):not(.alignright):not(.alignfull))",
'declarations' => array( 'margin-left' => '0 !important' ),
);
}
if ( 'right' === $justify_content ) {
$layout_styles[] = array(
'selector' => "$selector > :where(:not(.alignleft):not(.alignright):not(.alignfull))",
'declarations' => array( 'margin-right' => '0 !important' ),
);
}
}
if ( $has_block_gap_support && $should_output_block_gap ) {
if ( is_array( $gap_value ) ) {
$gap_value = $gap_value['top'] ?? null;
}
if ( null !== $gap_value && ! $should_skip_gap_serialization ) {
// Get spacing CSS variable from preset value if provided.
if ( is_string( $gap_value ) && str_contains( $gap_value, 'var:preset|spacing|' ) ) {
$index_to_splice = strrpos( $gap_value, '|' ) + 1;
$slug = _wp_to_kebab_case( substr( $gap_value, $index_to_splice ) );
$gap_value = "var(--wp--preset--spacing--$slug)";
}
array_push(
$layout_styles,
array(
'selector' => "$selector > *",
'declarations' => array(
'margin-block-start' => '0',
'margin-block-end' => '0',
),
),
array(
'selector' => "$selector > * + *",
'declarations' => array(
'margin-block-start' => $gap_value,
'margin-block-end' => '0',
),
)
);
}
}
} elseif ( 'flex' === $layout_type ) {
$layout_orientation = $layout_for_styles['orientation'] ?? 'horizontal';
$justify_content_options = array(
'left' => 'flex-start',
'right' => 'flex-end',
'center' => 'center',
);
$vertical_alignment_options = array(
'top' => 'flex-start',
'center' => 'center',
'bottom' => 'flex-end',
);
if ( 'horizontal' === $layout_orientation ) {
$justify_content_options += array( 'space-between' => 'space-between' );
$vertical_alignment_options += array( 'stretch' => 'stretch' );
} else {
$justify_content_options += array( 'stretch' => 'stretch' );
$vertical_alignment_options += array( 'space-between' => 'space-between' );
}
/*
* 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
* value for any of the flex properties.
*/
$should_output_flex_wrap = null === $viewport_overrides || $has_viewport_property_override( 'flexWrap' );
$should_output_flex_orientation = null === $viewport_overrides || $has_viewport_property_override( 'orientation' );
$should_output_flex_justification = null === $viewport_overrides || $has_viewport_property_override( 'justifyContent' ) || $has_viewport_property_override( 'orientation' );
$should_output_flex_alignment = null === $viewport_overrides || $has_viewport_property_override( 'verticalAlignment' ) || $has_viewport_property_override( 'orientation' );
if ( $should_output_flex_wrap && ! empty( $layout_for_styles['flexWrap'] ) && 'nowrap' === $layout_for_styles['flexWrap'] ) {
$layout_styles[] = array(
'selector' => $selector,
'declarations' => array( 'flex-wrap' => 'nowrap' ),
);
}
if ( $has_block_gap_support && $should_output_block_gap && isset( $gap_value ) ) {
$combined_gap_value = '';
$gap_sides = is_array( $gap_value ) ? array( 'top', 'left' ) : array( 'top' );
foreach ( $gap_sides as $gap_side ) {
$process_value = $gap_value;
if ( is_array( $gap_value ) ) {
if ( is_array( $fallback_gap_value ) ) {
$fallback_value = $fallback_gap_value[ $gap_side ] ?? reset( $fallback_gap_value );
} else {
$fallback_value = $fallback_gap_value;
}
$process_value = $gap_value[ $gap_side ] ?? $fallback_value;
}
// Get spacing CSS variable from preset value if provided.
if ( is_string( $process_value ) && str_contains( $process_value, 'var:preset|spacing|' ) ) {
$index_to_splice = strrpos( $process_value, '|' ) + 1;
$slug = _wp_to_kebab_case( substr( $process_value, $index_to_splice ) );
$process_value = "var(--wp--preset--spacing--$slug)";
}
$combined_gap_value .= "$process_value ";
}
$gap_value = trim( $combined_gap_value );
if ( null !== $gap_value && ! $should_skip_gap_serialization ) {
$layout_styles[] = array(
'selector' => $selector,
'declarations' => array( 'gap' => $gap_value ),
);
}
}
if ( 'horizontal' === $layout_orientation ) {
/*
* Add this style only if is not empty for backwards compatibility,
* since we intend to convert blocks that had flex layout implemented
* by custom css.
*/
if ( $should_output_flex_justification && ! empty( $layout_for_styles['justifyContent'] ) && array_key_exists( $layout_for_styles['justifyContent'], $justify_content_options ) ) {
$layout_styles[] = array(
'selector' => $selector,
'declarations' => array( 'justify-content' => $justify_content_options[ $layout_for_styles['justifyContent'] ] ),
);
}
if ( $should_output_flex_alignment && ! empty( $layout_for_styles['verticalAlignment'] ) && array_key_exists( $layout_for_styles['verticalAlignment'], $vertical_alignment_options ) ) {
$layout_styles[] = array(
'selector' => $selector,
'declarations' => array( 'align-items' => $vertical_alignment_options[ $layout_for_styles['verticalAlignment'] ] ),
);
}
} else {
if ( $should_output_flex_orientation ) {
$layout_styles[] = array(
'selector' => $selector,
'declarations' => array( 'flex-direction' => 'column' ),
);
}
if ( $should_output_flex_justification && ! empty( $layout_for_styles['justifyContent'] ) && array_key_exists( $layout_for_styles['justifyContent'], $justify_content_options ) ) {
$layout_styles[] = array(
'selector' => $selector,
'declarations' => array( 'align-items' => $justify_content_options[ $layout_for_styles['justifyContent'] ] ),
);
} elseif ( $should_output_flex_justification ) {
$layout_styles[] = array(
'selector' => $selector,
'declarations' => array( 'align-items' => 'flex-start' ),
);
}
if ( $should_output_flex_alignment && ! empty( $layout_for_styles['verticalAlignment'] ) && array_key_exists( $layout_for_styles['verticalAlignment'], $vertical_alignment_options ) ) {
$layout_styles[] = array(
'selector' => $selector,
'declarations' => array( 'justify-content' => $vertical_alignment_options[ $layout_for_styles['verticalAlignment'] ] ),
);
}
}
} elseif ( 'grid' === $layout_type ) {
/*
* If the gap value is an array, we use the "left" value because it represents the vertical gap, which
* is the relevant one for computation of responsive grid columns.
*/
if ( is_array( $fallback_gap_value ) ) {
$responsive_gap_value = $fallback_gap_value['left'] ?? reset( $fallback_gap_value );
} else {
$responsive_gap_value = $fallback_gap_value;
}
if ( $has_block_gap_support && isset( $gap_value ) ) {
$combined_gap_value = '';
$gap_sides = is_array( $gap_value ) ? array( 'top', 'left' ) : array( 'top' );
foreach ( $gap_sides as $gap_side ) {
$process_value = $gap_value;
if ( is_array( $gap_value ) ) {
if ( is_array( $fallback_gap_value ) ) {
$fallback_value = $fallback_gap_value[ $gap_side ] ?? reset( $fallback_gap_value );
} else {
$fallback_value = $fallback_gap_value;
}
$process_value = $gap_value[ $gap_side ] ?? $fallback_value;
}
// Get spacing CSS variable from preset value if provided.
if ( is_string( $process_value ) && str_contains( $process_value, 'var:preset|spacing|' ) ) {
$index_to_splice = strrpos( $process_value, '|' ) + 1;
$slug = _wp_to_kebab_case( substr( $process_value, $index_to_splice ) );
$process_value = "var(--wp--preset--spacing--$slug)";
}
$combined_gap_value .= "$process_value ";
}
$gap_value = trim( $combined_gap_value );
$responsive_gap_value = $gap_value;
}
// Ensure 0 values have a unit so they work in calc().
if ( '0' === $responsive_gap_value || 0 === $responsive_gap_value ) {
$responsive_gap_value = '0px';
}
/*
* 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
* value for any of the grid properties.
*/
$should_output_grid_columns = null === $viewport_overrides || $has_viewport_property_override( 'minimumColumnWidth' ) || $has_viewport_property_override( 'columnCount' ) || $has_viewport_property_override( 'autoFit' );
$uses_gap_in_grid_columns = ! empty( $layout_for_styles['columnCount'] ) && ! empty( $layout_for_styles['minimumColumnWidth'] );
if ( $has_block_gap_override && $uses_gap_in_grid_columns ) {
$should_output_grid_columns = true;
}
$should_output_grid_rows = ( null === $viewport_overrides || $has_viewport_property_override( 'rowCount' ) ) && ! empty( $layout_for_styles['columnCount'] ) && ! empty( $layout_for_styles['rowCount'] );
$grid_declarations = array();
/*
* When enabled, columns stretch to fill the available space using
* `auto-fit`; otherwise empty tracks are preserved with `auto-fill`.
*/
$auto_placement = ! empty( $layout_for_styles['autoFit'] ) ? 'auto-fit' : 'auto-fill';
if ( $should_output_grid_columns && ! empty( $layout_for_styles['columnCount'] ) && ! empty( $layout_for_styles['minimumColumnWidth'] ) ) {
$max_value = 'max(min(' . $layout_for_styles['minimumColumnWidth'] . ', 100%), (100% - (' . $responsive_gap_value . ' * (' . $layout_for_styles['columnCount'] . ' - 1))) /' . $layout_for_styles['columnCount'] . ')';
$grid_declarations['grid-template-columns'] = 'repeat(' . $auto_placement . ', minmax(' . $max_value . ', 1fr))';
} elseif ( $should_output_grid_columns && ! empty( $layout_for_styles['columnCount'] ) ) {
$grid_declarations['grid-template-columns'] = 'repeat(' . $layout_for_styles['columnCount'] . ', minmax(0, 1fr))';
} elseif ( $should_output_grid_columns ) {
$minimum_column_width = ! empty( $layout_for_styles['minimumColumnWidth'] ) ? $layout_for_styles['minimumColumnWidth'] : '12rem';
$grid_declarations['grid-template-columns'] = 'repeat(' . $auto_placement . ', minmax(min(' . $minimum_column_width . ', 100%), 1fr))';
}
if ( ! empty( $grid_declarations ) ) {
$base_has_container_type = empty( $base_layout['columnCount'] ) || ( ! empty( $base_layout['columnCount'] ) && ! empty( $base_layout['minimumColumnWidth'] ) );
if ( empty( $layout_for_styles['columnCount'] ) || ! empty( $layout_for_styles['minimumColumnWidth'] ) ) {
if ( null === $viewport_overrides || ! $base_has_container_type ) {
$grid_declarations['container-type'] = 'inline-size';
}
}
$layout_styles[] = array(
'selector' => $selector,
'declarations' => $grid_declarations,
);
}
if ( $should_output_grid_rows ) {
$layout_styles[] = array(
'selector' => $selector,
'declarations' => array( 'grid-template-rows' => 'repeat(' . $layout_for_styles['rowCount'] . ', minmax(1rem, auto))' ),
);
}
if ( $has_block_gap_support && $should_output_block_gap && null !== $gap_value && ! $should_skip_gap_serialization ) {
$layout_styles[] = array(
'selector' => $selector,
'declarations' => array( 'gap' => $gap_value ),
);
}
}
if ( ! empty( $layout_styles ) ) {
if ( ! empty( $rules_group ) ) {
foreach ( $layout_styles as $index => $layout_style ) {
$layout_styles[ $index ]['rules_group'] = $rules_group;
}
}
/*
* Add to the style engine store to enqueue and render layout styles.
* Return compiled layout styles to retain backwards compatibility.
* Since https://github.com/WordPress/gutenberg/pull/42452,
* wp_enqueue_block_support_styles is no longer called in this block supports file.
*/
return wp_style_engine_get_stylesheet_from_css_rules(
$layout_styles,
array(
'context' => 'block-supports',
'prettify' => false,
)
);
}
return '';
}
/**
* Renders the layout config to the block wrapper.
*
* @since 5.8.0
* @since 6.3.0 Adds compound class to layout wrapper for global spacing styles.
* @since 6.3.0 Check for layout support via the `layout` key with fallback to `__experimentalLayout`.
* @since 6.6.0 Removed duplicate container class from layout styles.
* @access private
*
* @param string $block_content Rendered block content.
* @param array $block Block object.
* @return string Filtered block content.
*/
function wp_render_layout_support_flag( $block_content, $block ) {
static $global_styles = null;
$block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] );
$block_supports_layout = block_has_support( $block_type, 'layout', false ) || block_has_support( $block_type, '__experimentalLayout', false );
$style_attr = $block['attrs']['style'] ?? array();
/*
* A block with no layout support and no style attribute at all cannot
* produce layout output, so return before resolving global settings.
*
* Resolving settings is not read-only: on a cold cache it queries the
* user's `wp_global_styles` post, which fires `the_posts`. A callback on
* that hook that renders blocks re-enters this filter, and the content it
* renders at that point is the global styles post itself, which parses to a
* single block with no name and no attributes. Without this return that
* block resolves settings again and the recursion has no base case.
*/
if ( ! $block_supports_layout && empty( $style_attr ) ) {
return $block_content;
}
$global_settings = wp_get_global_settings();
$viewport_settings = $global_settings['viewport'] ?? null;
$responsive_media_queries = WP_Theme_JSON::get_viewport_media_queries( $viewport_settings );
$child_layout = $style_attr['layout'] ?? null;
/*
* Collect responsive viewport child layout overrides so that a block with
* only responsive child layout (no base child layout) is still processed.
*/
$viewport_child_layouts = array();
foreach ( $responsive_media_queries as $breakpoint => $media_query ) {
$viewport_child = wp_get_layout_child_values( $style_attr[ $breakpoint ]['layout'] ?? null );
if ( ! empty( $viewport_child ) ) {
$viewport_child_layouts[ $breakpoint ] = array(
'media_query' => $media_query,
'child_layout' => $viewport_child,
);
}
}
if ( ! $block_supports_layout && ! $child_layout && empty( $viewport_child_layouts ) ) {
return $block_content;
}
$outer_class_names = array();
// Child layout specific logic.
if ( $child_layout || ! empty( $viewport_child_layouts ) ) {
$base_child_layout = wp_get_layout_child_values( $child_layout );
$parent_layout = $block['parentLayout'] ?? array();
/*
* Generates a unique class for child block layout styles.
*
* To ensure consistent class generation across different page renders,
* only properties that affect layout styling are used. These properties
* come from `$block['attrs']['style']['layout']`, viewport overrides in
* `$block['attrs']['style'][$breakpoint]['layout']`, and `$block['parentLayout']`.
*
* As long as these properties coincide, the generated class will be the same.
*/
$container_content_hash_input = array(
'layout' => $base_child_layout,
'parentLayout' => array_intersect_key(
$parent_layout,
array_flip( array( 'minimumColumnWidth', 'columnCount' ) )
),
);
foreach ( $viewport_child_layouts as $breakpoint => $viewport_data ) {
$container_content_hash_input[ $breakpoint ] = $viewport_data['child_layout'];
}
$container_content_class = wp_unique_id_from_values(
$container_content_hash_input,
'wp-container-content-'
);
$child_layout_styles = wp_get_child_layout_style_rules( ".$container_content_class", $base_child_layout, $parent_layout );
/*
* Emit responsive child layout CSS using the same container-content class
* so that base and responsive child layout share the exact same selector.
*/
foreach ( $viewport_child_layouts as $viewport_data ) {
$viewport_child_styles = wp_get_child_layout_style_rules(
".$container_content_class",
$base_child_layout,
$parent_layout,
$viewport_data['child_layout']
);
foreach ( $viewport_child_styles as $index => $rule ) {
$viewport_child_styles[ $index ]['rules_group'] = $viewport_data['media_query'];
}
$child_layout_styles = array_merge( $child_layout_styles, $viewport_child_styles );
}
/*
* Add to the style engine store to enqueue and render layout styles.
* Return styles here just to check if any exist.
*/
$child_css = wp_style_engine_get_stylesheet_from_css_rules(
$child_layout_styles,
array(
'context' => 'block-supports',
'prettify' => false,
)
);
if ( $child_css ) {
$outer_class_names[] = $container_content_class;
}
}
// Prep the processor for modifying the block output.
$processor = new WP_HTML_Tag_Processor( $block_content );
// Having no tags implies there are no tags onto which to add class names.
if ( ! $processor->next_tag() ) {
return $block_content;
}
/*
* A block may not support layout but still be affected by a parent block's layout.
*
* In these cases add the appropriate class names and then return early; there's
* no need to investigate on this block whether additional layout constraints apply.
*/
if ( ! $block_supports_layout && ! empty( $outer_class_names ) ) {
foreach ( $outer_class_names as $class_name ) {
$processor->add_class( $class_name );
}
return $processor->get_updated_html();
} elseif ( ! $block_supports_layout ) {
// Ensure layout classnames are not injected if there is no layout support.
return $block_content;
}
$fallback_layout = $block_type->supports['layout']['default'] ?? array();
if ( empty( $fallback_layout ) ) {
$fallback_layout = $block_type->supports['__experimentalLayout']['default'] ?? array();
}
$used_layout = $block['attrs']['layout'] ?? $fallback_layout;
$class_names = array();
$layout_definitions = wp_get_layout_definitions();
// Set the correct layout type for blocks using legacy content width.
if ( isset( $used_layout['inherit'] ) && $used_layout['inherit'] || isset( $used_layout['contentSize'] ) && $used_layout['contentSize'] ) {
$used_layout['type'] = 'constrained';
}
$root_padding_aware_alignments = $global_settings['useRootPaddingAwareAlignments'] ?? false;
if (
$root_padding_aware_alignments &&
isset( $used_layout['type'] ) &&
'constrained' === $used_layout['type']
) {
$class_names[] = 'has-global-padding';
}
/*
* The following section was added to reintroduce a small set of layout classnames that were
* removed in the 5.9 release (https://github.com/WordPress/gutenberg/issues/38719). It is
* not intended to provide an extended set of classes to match all block layout attributes
* here.
*/
if ( ! empty( $block['attrs']['layout']['orientation'] ) ) {
$class_names[] = 'is-' . sanitize_title( $block['attrs']['layout']['orientation'] );
}
if ( ! empty( $block['attrs']['layout']['justifyContent'] ) ) {
$class_names[] = 'is-content-justification-' . sanitize_title( $block['attrs']['layout']['justifyContent'] );
}
if ( ! empty( $block['attrs']['layout']['flexWrap'] ) && 'nowrap' === $block['attrs']['layout']['flexWrap'] ) {
$class_names[] = 'is-nowrap';
}
// Get classname for layout type.
if ( isset( $used_layout['type'] ) ) {
$layout_classname = $layout_definitions[ $used_layout['type'] ]['className'] ?? '';
} else {
$layout_classname = $layout_definitions['default']['className'] ?? '';
}
if ( $layout_classname && is_string( $layout_classname ) ) {
$class_names[] = sanitize_title( $layout_classname );
}
/*
* Only generate Layout styles if the theme has not opted-out.
* Attribute-based Layout classnames are output in all cases.
*/
if ( ! current_theme_supports( 'disable-layout-styles' ) ) {
$gap_value = wp_sanitize_block_gap_value( $style_attr['spacing']['blockGap'] ?? null );
$fallback_gap_value = $block_type->supports['spacing']['blockGap']['__experimentalDefault'] ?? '0.5em';
$block_spacing = $style_attr['spacing'] ?? null;
/*
* If a block's block.json skips serialization for spacing or spacing.blockGap,
* don't apply the user-defined value to the styles.
*/
$should_skip_gap_serialization = wp_should_skip_block_supports_serialization( $block_type, 'spacing', 'blockGap' );
$block_gap = $global_settings['spacing']['blockGap'] ?? null;
$has_block_gap_support = isset( $block_gap );
// Get default blockGap value from global styles for use in layouts like grid.
// Check style variation first, then block-specific styles, then fall back to root styles.
$block_name = $block['blockName'] ?? '';
if ( null === $global_styles ) {
$global_styles = wp_get_global_styles();
}
// Check if the block has an active style variation with a blockGap value.
// Only check the registry if the className contains a variation class to avoid unnecessary lookups.
$variation_block_gap_value = null;
$block_class_name = is_string( $block['attrs']['className'] ?? null )
? $block['attrs']['className']
: '';
if ( $block_class_name && str_contains( $block_class_name, 'is-style-' ) && $block_name ) {
$styles_registry = WP_Block_Styles_Registry::get_instance();
$registered_styles = $styles_registry->get_registered_styles_for_block( $block_name );
$variation_name = wp_get_block_style_variation_name_from_registered_style( $block_class_name, $registered_styles );
if ( $variation_name ) {
$variation_block_gap_value = $global_styles['blocks'][ $block_name ]['variations'][ $variation_name ]['spacing']['blockGap'] ?? null;
}
}
$global_block_gap_value = $variation_block_gap_value ?? $global_styles['blocks'][ $block_name ]['spacing']['blockGap'] ?? $global_styles['spacing']['blockGap'] ?? null;
if ( null !== $global_block_gap_value ) {
$fallback_gap_value = $global_block_gap_value;
}
$container_class_hash_input = array(
$used_layout,
$has_block_gap_support,
$gap_value,
$should_skip_gap_serialization,
$fallback_gap_value,
$block_spacing,
);
foreach ( array_keys( $responsive_media_queries ) as $breakpoint ) {
$viewport_style = $style_attr[ $breakpoint ] ?? null;
if ( ! is_array( $viewport_style ) ) {
continue;
}
$viewport_container_layout = wp_get_layout_container_values( $viewport_style['layout'] ?? null );
if ( ! empty( $viewport_container_layout ) ) {
$container_class_hash_input[] = array(
'breakpoint' => $breakpoint,
'layout' => $viewport_container_layout,
);
}
if ( isset( $viewport_style['spacing']['blockGap'] ) ) {
$container_class_hash_input[] = array(
'breakpoint' => $breakpoint,
'blockGap' => wp_sanitize_block_gap_value( $viewport_style['spacing']['blockGap'] ),
);
}
}
/*
* Generates a unique ID based on all the data required to obtain the
* corresponding layout style. Keeps the CSS class names the same
* even for different blocks on different places, as long as they have
* the same layout definition. Makes the CSS class names stable across
* paginations for features like the enhanced pagination of the Query block.
*/
$container_class = wp_unique_id_from_values(
$container_class_hash_input,
'wp-container-' . sanitize_title( $block['blockName'] ) . '-is-layout-'
);
$style = wp_get_layout_style(
".$container_class",
$used_layout,
$has_block_gap_support,
$gap_value,
$should_skip_gap_serialization,
$fallback_gap_value,
$block_spacing
);
/*
* Emit responsive container layout styles using the same $container_class
* selector as the base layout so they target the inner block wrapper.
*/
foreach ( $responsive_media_queries as $breakpoint => $media_query ) {
$viewport_style = $style_attr[ $breakpoint ] ?? null;
if ( ! is_array( $viewport_style ) ) {
continue;
}
$viewport_container_layout = wp_get_layout_container_values( $viewport_style['layout'] ?? null );
$has_viewport_layout = ! empty( $viewport_container_layout );
$has_viewport_block_gap = isset( $viewport_style['spacing']['blockGap'] );
if ( ! $has_viewport_layout && ! $has_viewport_block_gap ) {
continue;
}
$viewport_gap_value = $has_viewport_block_gap
? wp_sanitize_block_gap_value( $viewport_style['spacing']['blockGap'] )
: $gap_value;
$viewport_block_spacing = is_array( $viewport_style['spacing'] ?? null )
? array_replace( is_array( $block_spacing ) ? $block_spacing : array(), $viewport_style['spacing'] )
: $block_spacing;
$viewport_styles = wp_get_layout_style(
".$container_class",
$used_layout,
$has_block_gap_support,
$viewport_gap_value,
$should_skip_gap_serialization,
$fallback_gap_value,
$viewport_block_spacing,
array(
'rules_group' => $media_query,
'viewport_overrides' => $viewport_container_layout,
'has_block_gap_override' => $has_viewport_block_gap,
)
);
if ( ! empty( $viewport_styles ) && ! in_array( $container_class, $class_names, true ) ) {
$class_names[] = $container_class;
}
}
// Only add container class and enqueue block support styles if unique styles were generated.
if ( ! empty( $style ) ) {
$class_names[] = $container_class;
}
}
// Add combined layout and block classname for global styles to hook onto.
$split_block_name = explode( '/', $block['blockName'] );
$full_block_name = 'core' === $split_block_name[0] ? end( $split_block_name ) : implode( '-', $split_block_name );
$class_names[] = 'wp-block-' . $full_block_name . '-' . $layout_classname;
// Add classes to the outermost HTML tag if necessary.
if ( ! empty( $outer_class_names ) ) {
foreach ( $outer_class_names as $outer_class_name ) {
$processor->add_class( $outer_class_name );
}
}
/**
* Attempts to refer to the inner-block wrapping element by its class attribute.
*
* When examining a block's inner content, if a block has inner blocks, then
* the first content item will likely be a text (HTML) chunk immediately
* preceding the inner blocks. The last HTML tag in that chunk would then be
* an opening tag for an element that wraps the inner blocks.
*
* There's no reliable way to associate this wrapper in $block_content because
* it may have changed during the rendering pipeline (as inner contents is
* provided before rendering) and through previous filters. In many cases,
* however, the `class` attribute will be a good-enough identifier, so this
* code finds the last tag in that chunk and stores the `class` attribute
* so that it can be used later when working through the rendered block output
* to identify the wrapping element and add the remaining class names to it.
*
* It's also possible that no inner block wrapper even exists. If that's the
* case this code could apply the class names to an invalid element.
*
* Example:
*
* $block['innerBlocks'] = array( $list_item );
* $block['innerContent'] = array( '
', null, '
' );
*
* // After rendering, the initial contents may have been modified by other renderers or filters.
* $block_content = <<
*
*
Code
*
It's a list!
*
* HTML;
*
* Although it is possible that the original block-wrapper classes are changed in $block_content
* from how they appear in $block['innerContent'], it's likely that the original class attributes
* are still present in the wrapper as they are in this example. Frequently, additional classes
* will also be present; rarely should classes be removed.
*
* @todo Find a better way to match the first inner block. If it's possible to identify where the
* first inner block starts, then it will be possible to find the last tag before it starts
* and then that tag, if an opening tag, can be solidly identified as a wrapping element.
* Can some unique value or class or ID be added to the inner blocks when they process
* so that they can be extracted here safely without guessing? Can the block rendering function
* return information about where the rendered inner blocks start?
*
* @var string|null
*/
$inner_block_wrapper_classes = null;
$first_chunk = $block['innerContent'][0] ?? null;
if ( is_string( $first_chunk ) && count( $block['innerContent'] ) > 1 ) {
$first_chunk_processor = new WP_HTML_Tag_Processor( $first_chunk );
/*
* Use a stack to track open elements as tags are visited. Void elements
* (those without a matching closing tag) are excluded so they don't
* accumulate on the stack. At the end of the chunk, every element still
* on the stack is unclosed — meaning its closing tag lives in a later
* innerContent entry alongside the inner blocks, which makes it the
* inner-block container. Elements that open and close within this chunk
* are siblings that precede the inner blocks and should be ignored.
* The last unclosed element with a class attribute is the best candidate
* for the inner-block wrapper.
*/
$tag_stack = array();
while ( $first_chunk_processor->next_tag( array( 'tag_closers' => 'visit' ) ) ) {
if ( $first_chunk_processor->is_tag_closer() ) {
array_pop( $tag_stack );
} elseif ( ! WP_HTML_Processor::is_void( $first_chunk_processor->get_tag() ) ) {
$tag_stack[] = $first_chunk_processor->get_attribute( 'class' );
}
}
foreach ( array_reverse( $tag_stack ) as $class_attribute ) {
if ( is_string( $class_attribute ) && ! empty( $class_attribute ) ) {
$inner_block_wrapper_classes = $class_attribute;
break;
}
}
}
/*
* If necessary, advance to what is likely to be an inner block wrapper tag.
*
* This advances until it finds the first tag containing the original class
* attribute from above. If none is found it will scan to the end of the block
* and fail to add any class names.
*
* If there is no block wrapper it won't advance at all, in which case the
* class names will be added to the first and outermost tag of the block.
* For cases where this outermost tag is the only tag surrounding inner
* blocks then the outer wrapper and inner wrapper are the same.
*/
do {
if ( ! $inner_block_wrapper_classes ) {
break;
}
$class_attribute = $processor->get_attribute( 'class' );
if ( is_string( $class_attribute ) && str_contains( $class_attribute, $inner_block_wrapper_classes ) ) {
break;
}
} while ( $processor->next_tag() );
// Add the remaining class names.
foreach ( $class_names as $class_name ) {
$processor->add_class( $class_name );
}
return $processor->get_updated_html();
}
/**
* Check if the parent block exists and if it has a layout attribute.
* If it does, add the parent layout to the parsed block
*
* @since 6.6.0
* @access private
*
* @param array $parsed_block The parsed block.
* @param array $source_block The source block.
* @param WP_Block $parent_block The parent block.
* @return array The parsed block with parent layout attribute if it exists.
*/
function wp_add_parent_layout_to_parsed_block( $parsed_block, $source_block, $parent_block ) {
if ( $parent_block && isset( $parent_block->parsed_block['attrs']['layout'] ) ) {
$parsed_block['parentLayout'] = $parent_block->parsed_block['attrs']['layout'];
}
return $parsed_block;
}
add_filter( 'render_block_data', 'wp_add_parent_layout_to_parsed_block', 10, 3 );
// Register the block support.
WP_Block_Supports::get_instance()->register(
'layout',
array(
'register_attribute' => 'wp_register_layout_support',
)
);
add_filter( 'render_block', 'wp_render_layout_support_flag', 10, 2 );
/**
* For themes without theme.json file, make sure
* to restore the inner div for the group block
* to avoid breaking styles relying on that div.
*
* @since 5.8.0
* @since 6.6.1 Removed inner container from Grid variations.
* @access private
*
* @param string $block_content Rendered block content.
* @param array $block Block object.
* @return string Filtered block content.
*/
function wp_restore_group_inner_container( $block_content, $block ) {
$tag_name = $block['attrs']['tagName'] ?? 'div';
$group_with_inner_container_regex = sprintf(
'/(^\s*<%1$s\b[^>]*wp-block-group(\s|")[^>]*>)(\s*
]*wp-block-group__inner-container(\s|")[^>]*>)((.|\S|\s)*)/U',
preg_quote( $tag_name, '/' )
);
if (
wp_theme_has_theme_json() ||
1 === preg_match( $group_with_inner_container_regex, $block_content ) ||
( isset( $block['attrs']['layout']['type'] ) && ( 'flex' === $block['attrs']['layout']['type'] || 'grid' === $block['attrs']['layout']['type'] ) )
) {
return $block_content;
}
/*
* This filter runs after the layout classnames have been added to the block, so they
* have to be removed from the outer wrapper and then added to the inner.
*/
$layout_classes = array();
$processor = new WP_HTML_Tag_Processor( $block_content );
if ( $processor->next_tag( array( 'class_name' => 'wp-block-group' ) ) ) {
foreach ( $processor->class_list() as $class_name ) {
if ( str_contains( $class_name, 'is-layout-' ) ) {
$layout_classes[] = $class_name;
$processor->remove_class( $class_name );
}
}
}
$content_without_layout_classes = $processor->get_updated_html();
$replace_regex = sprintf(
'/(^\s*<%1$s\b[^>]*wp-block-group[^>]*>)(.*)(<\/%1$s>\s*$)/ms',
preg_quote( $tag_name, '/' )
);
$updated_content = preg_replace_callback(
$replace_regex,
static function ( $matches ) {
return $matches[1] . '
' . $matches[2] . '
' . $matches[3];
},
$content_without_layout_classes
);
// Add layout classes to inner wrapper.
if ( ! empty( $layout_classes ) ) {
$processor = new WP_HTML_Tag_Processor( $updated_content );
if ( $processor->next_tag( array( 'class_name' => 'wp-block-group__inner-container' ) ) ) {
foreach ( $layout_classes as $class_name ) {
$processor->add_class( $class_name );
}
}
$updated_content = $processor->get_updated_html();
}
return $updated_content;
}
add_filter( 'render_block_core/group', 'wp_restore_group_inner_container', 10, 2 );
/**
* For themes without theme.json file, make sure
* to restore the outer div for the aligned image block
* to avoid breaking styles relying on that div.
*
* @since 6.0.0
* @access private
*
* @param string $block_content Rendered block content.
* @param array $block Block object.
* @return string Filtered block content.
*/
function wp_restore_image_outer_container( $block_content, $block ) {
if ( wp_theme_has_theme_json() ) {
return $block_content;
}
$figure_processor = new WP_HTML_Tag_Processor( $block_content );
if (
! $figure_processor->next_tag( 'FIGURE' ) ||
! $figure_processor->has_class( 'wp-block-image' ) ||
! (
$figure_processor->has_class( 'alignleft' ) ||
$figure_processor->has_class( 'aligncenter' ) ||
$figure_processor->has_class( 'alignright' )
)
) {
return $block_content;
}
/*
* The next section of code wraps the existing figure in a new DIV element.
* While doing it, it needs to transfer the layout and the additional CSS
* class names from the original figure upward to the wrapper.
*
* Example:
*
* // From this…
*
* …
*
* // To this…
*
…
*/
$wrapper_processor = new WP_HTML_Tag_Processor( '
' );
$wrapper_processor->next_token();
$wrapper_processor->set_attribute(
'class',
is_string( $block['attrs']['className'] ?? null )
? "wp-block-image {$block['attrs']['className']}"
: 'wp-block-image'
);
// And remove them from the existing content; it has been transferred upward.
$figure_processor->remove_class( 'wp-block-image' );
foreach ( $wrapper_processor->class_list() as $class_name ) {
$figure_processor->remove_class( $class_name );
}
return "{$wrapper_processor->get_updated_html()}{$figure_processor->get_updated_html()}
get_error_code(), $valid_error_codes, true ) ) {
$signup = $result->get_error_data();
?>
';
if ( '' === $signup->domain . $signup->path ) {
printf(
/* translators: 1: Login URL, 2: Username, 3: User email address, 4: Lost password URL. */
__( 'Your account has been activated. You may now log in to the site using your chosen username of “%2$s”. Please check your email inbox at %3$s for your password and login instructions. If you do not receive an email, please check your junk or spam folder. If you still do not receive an email within an hour, you can reset your password.' ),
esc_url( network_site_url( $blog_details->path . 'wp-login.php', 'login' ) ),
esc_html( $signup->user_login ),
esc_html( $signup->user_email ),
esc_url( wp_lostpassword_url() )
);
} else {
printf(
/* translators: 1: Site URL, 2: Username, 3: User email address, 4: Lost password URL. */
__( 'Your site at %1$s is active. You may now log in to your site using your chosen username of “%2$s”. Please check your email inbox at %3$s for your password and login instructions. If you do not receive an email, please check your junk or spam folder. If you still do not receive an email within an hour, you can reset your password.' ),
sprintf( '%1$s', esc_url( $signup->domain . $blog_details->path ) ),
esc_html( $signup->user_login ),
esc_html( $signup->user_email ),
esc_url( wp_lostpassword_url() )
);
}
echo '';
} elseif ( null === $result || is_wp_error( $result ) ) {
?>
get_error_message() ); ?>
user_login ); ?>
View your site or Log in' ), esc_url( $url ), esc_url( $login_url ) );
?>
Log in or go back to the homepage.' ),
esc_url( network_site_url( $blog_details->path . 'wp-login.php', 'login' ) ),
esc_url( network_home_url( $blog_details->path ) )
);
?>
supports, $path, false );
if ( is_array( $skip_serialization ) ) {
return in_array( $feature, $skip_serialization, true );
}
return $skip_serialization;
}
PK ! } auto-register.phpnu [ $args Array of arguments for registering a block type.
* @return array Modified block type arguments.
*/
function wp_mark_auto_generate_control_attributes( array $args ): array {
if ( empty( $args['attributes'] ) || ! is_array( $args['attributes'] ) ) {
return $args;
}
$has_auto_register = ! empty( $args['supports']['autoRegister'] );
if ( ! $has_auto_register ) {
return $args;
}
foreach ( $args['attributes'] as $attr_key => $attr_schema ) {
// Skip HTML-derived attributes (edited inline, not via inspector).
if ( ! empty( $attr_schema['source'] ) ) {
continue;
}
// Skip internal attributes (not user-configurable).
if ( isset( $attr_schema['role'] ) && 'local' === $attr_schema['role'] ) {
continue;
}
// Skip unsupported types (only 'string', 'number', 'integer', 'boolean' are supported).
$type = $attr_schema['type'] ?? null;
if ( ! in_array( $type, array( 'string', 'number', 'integer', 'boolean' ), true ) ) {
continue;
}
$args['attributes'][ $attr_key ]['autoGenerateControl'] = true;
}
return $args;
}
// Priority 5 to mark original attributes before other filters (priority 10+) might add their own.
add_filter( 'register_block_type_args', 'wp_mark_auto_generate_control_attributes', 5 );
PK ! #o o typography.phpnu [ supports['typography'] ?? false;
if ( ! $typography_supports ) {
return;
}
$has_font_family_support = $typography_supports['__experimentalFontFamily'] ?? false;
$has_font_size_support = $typography_supports['fontSize'] ?? false;
$has_font_style_support = $typography_supports['__experimentalFontStyle'] ?? false;
$has_font_weight_support = $typography_supports['__experimentalFontWeight'] ?? false;
$has_letter_spacing_support = $typography_supports['__experimentalLetterSpacing'] ?? false;
$has_line_height_support = $typography_supports['lineHeight'] ?? false;
$has_text_align_support = $typography_supports['textAlign'] ?? false;
$has_text_columns_support = $typography_supports['textColumns'] ?? false;
$has_text_decoration_support = $typography_supports['__experimentalTextDecoration'] ?? false;
$has_text_transform_support = $typography_supports['__experimentalTextTransform'] ?? false;
$has_text_indent_support = $typography_supports['textIndent'] ?? false;
$has_writing_mode_support = $typography_supports['__experimentalWritingMode'] ?? false;
$has_typography_support = $has_font_family_support
|| $has_font_size_support
|| $has_font_style_support
|| $has_font_weight_support
|| $has_letter_spacing_support
|| $has_line_height_support
|| $has_text_align_support
|| $has_text_columns_support
|| $has_text_decoration_support
|| $has_text_transform_support
|| $has_text_indent_support
|| $has_writing_mode_support;
if ( ! $block_type->attributes ) {
$block_type->attributes = array();
}
if ( $has_typography_support && ! array_key_exists( 'style', $block_type->attributes ) ) {
$block_type->attributes['style'] = array(
'type' => 'object',
);
}
if ( $has_font_size_support && ! array_key_exists( 'fontSize', $block_type->attributes ) ) {
$block_type->attributes['fontSize'] = array(
'type' => 'string',
);
}
if ( $has_font_family_support && ! array_key_exists( 'fontFamily', $block_type->attributes ) ) {
$block_type->attributes['fontFamily'] = array(
'type' => 'string',
);
}
}
/**
* Adds CSS classes and inline styles for typography features such as font sizes
* to the incoming attributes array. This will be applied to the block markup in
* the front-end.
*
* @since 5.6.0
* @since 6.1.0 Used the style engine to generate CSS and classnames.
* @since 6.3.0 Added support for text-columns.
* @since 7.0.0 Added support for text-indent.
* @access private
*
* @param WP_Block_Type $block_type Block type.
* @param array $block_attributes Block attributes.
* @return array Typography CSS classes and inline styles.
*/
function wp_apply_typography_support( $block_type, $block_attributes ) {
if ( ! ( $block_type instanceof WP_Block_Type ) ) {
return array();
}
$typography_supports = $block_type->supports['typography'] ?? false;
if ( ! $typography_supports ) {
return array();
}
if ( wp_should_skip_block_supports_serialization( $block_type, 'typography' ) ) {
return array();
}
$has_font_family_support = $typography_supports['__experimentalFontFamily'] ?? false;
$has_font_size_support = $typography_supports['fontSize'] ?? false;
$has_font_style_support = $typography_supports['__experimentalFontStyle'] ?? false;
$has_font_weight_support = $typography_supports['__experimentalFontWeight'] ?? false;
$has_letter_spacing_support = $typography_supports['__experimentalLetterSpacing'] ?? false;
$has_line_height_support = $typography_supports['lineHeight'] ?? false;
$has_text_align_support = $typography_supports['textAlign'] ?? false;
$has_text_columns_support = $typography_supports['textColumns'] ?? false;
$has_text_decoration_support = $typography_supports['__experimentalTextDecoration'] ?? false;
$has_text_transform_support = $typography_supports['__experimentalTextTransform'] ?? false;
$has_text_indent_support = $typography_supports['textIndent'] ?? false;
$has_writing_mode_support = $typography_supports['__experimentalWritingMode'] ?? false;
// Whether to skip individual block support features.
$should_skip_font_size = wp_should_skip_block_supports_serialization( $block_type, 'typography', 'fontSize' );
$should_skip_font_family = wp_should_skip_block_supports_serialization( $block_type, 'typography', 'fontFamily' );
$should_skip_font_style = wp_should_skip_block_supports_serialization( $block_type, 'typography', 'fontStyle' );
$should_skip_font_weight = wp_should_skip_block_supports_serialization( $block_type, 'typography', 'fontWeight' );
$should_skip_line_height = wp_should_skip_block_supports_serialization( $block_type, 'typography', 'lineHeight' );
$should_skip_text_align = wp_should_skip_block_supports_serialization( $block_type, 'typography', 'textAlign' );
$should_skip_text_columns = wp_should_skip_block_supports_serialization( $block_type, 'typography', 'textColumns' );
$should_skip_text_decoration = wp_should_skip_block_supports_serialization( $block_type, 'typography', 'textDecoration' );
$should_skip_text_transform = wp_should_skip_block_supports_serialization( $block_type, 'typography', 'textTransform' );
$should_skip_letter_spacing = wp_should_skip_block_supports_serialization( $block_type, 'typography', 'letterSpacing' );
$should_skip_text_indent = wp_should_skip_block_supports_serialization( $block_type, 'typography', 'textIndent' );
$should_skip_writing_mode = wp_should_skip_block_supports_serialization( $block_type, 'typography', 'writingMode' );
$typography_block_styles = array();
if ( $has_font_size_support && ! $should_skip_font_size ) {
$preset_font_size = array_key_exists( 'fontSize', $block_attributes )
? "var:preset|font-size|{$block_attributes['fontSize']}"
: null;
$custom_font_size = $block_attributes['style']['typography']['fontSize'] ?? null;
$typography_block_styles['fontSize'] = $preset_font_size ? $preset_font_size : wp_get_typography_font_size_value(
array(
'size' => $custom_font_size,
)
);
}
if ( $has_font_family_support && ! $should_skip_font_family ) {
$preset_font_family = array_key_exists( 'fontFamily', $block_attributes )
? "var:preset|font-family|{$block_attributes['fontFamily']}"
: null;
$custom_font_family = isset( $block_attributes['style']['typography']['fontFamily'] )
? wp_typography_get_preset_inline_style_value( $block_attributes['style']['typography']['fontFamily'], 'font-family' )
: null;
$typography_block_styles['fontFamily'] = $preset_font_family ? $preset_font_family : $custom_font_family;
}
if (
$has_font_style_support &&
! $should_skip_font_style &&
isset( $block_attributes['style']['typography']['fontStyle'] )
) {
$typography_block_styles['fontStyle'] = wp_typography_get_preset_inline_style_value(
$block_attributes['style']['typography']['fontStyle'],
'font-style'
);
}
if (
$has_font_weight_support &&
! $should_skip_font_weight &&
isset( $block_attributes['style']['typography']['fontWeight'] )
) {
$typography_block_styles['fontWeight'] = wp_typography_get_preset_inline_style_value(
$block_attributes['style']['typography']['fontWeight'],
'font-weight'
);
}
if ( $has_line_height_support && ! $should_skip_line_height ) {
$typography_block_styles['lineHeight'] = $block_attributes['style']['typography']['lineHeight'] ?? null;
}
if ( $has_text_align_support && ! $should_skip_text_align ) {
$typography_block_styles['textAlign'] = $block_attributes['style']['typography']['textAlign'] ?? null;
}
if ( $has_text_columns_support && ! $should_skip_text_columns && isset( $block_attributes['style']['typography']['textColumns'] ) ) {
$typography_block_styles['textColumns'] = $block_attributes['style']['typography']['textColumns'] ?? null;
}
if (
$has_text_decoration_support &&
! $should_skip_text_decoration &&
isset( $block_attributes['style']['typography']['textDecoration'] )
) {
$typography_block_styles['textDecoration'] = wp_typography_get_preset_inline_style_value(
$block_attributes['style']['typography']['textDecoration'],
'text-decoration'
);
}
if (
$has_text_transform_support &&
! $should_skip_text_transform &&
isset( $block_attributes['style']['typography']['textTransform'] )
) {
$typography_block_styles['textTransform'] = wp_typography_get_preset_inline_style_value(
$block_attributes['style']['typography']['textTransform'],
'text-transform'
);
}
if (
$has_letter_spacing_support &&
! $should_skip_letter_spacing &&
isset( $block_attributes['style']['typography']['letterSpacing'] )
) {
$typography_block_styles['letterSpacing'] = wp_typography_get_preset_inline_style_value(
$block_attributes['style']['typography']['letterSpacing'],
'letter-spacing'
);
}
if ( $has_writing_mode_support &&
! $should_skip_writing_mode &&
isset( $block_attributes['style']['typography']['writingMode'] )
) {
$typography_block_styles['writingMode'] = $block_attributes['style']['typography']['writingMode'] ?? null;
}
if ( $has_text_indent_support && ! $should_skip_text_indent && isset( $block_attributes['style']['typography']['textIndent'] ) ) {
$typography_block_styles['textIndent'] = $block_attributes['style']['typography']['textIndent'] ?? null;
}
$attributes = array();
$classnames = array();
$styles = wp_style_engine_get_styles(
array( 'typography' => $typography_block_styles ),
array( 'convert_vars_to_classnames' => true )
);
if ( ! empty( $styles['classnames'] ) ) {
$classnames[] = $styles['classnames'];
}
if ( $has_text_align_support && ! $should_skip_text_align && isset( $block_attributes['style']['typography']['textAlign'] ) ) {
$classnames[] = 'has-text-align-' . $block_attributes['style']['typography']['textAlign'];
}
if ( ! empty( $classnames ) ) {
$attributes['class'] = implode( ' ', $classnames );
}
if ( ! empty( $styles['css'] ) ) {
$attributes['style'] = $styles['css'];
}
return $attributes;
}
/**
* Generates an inline style value for a typography feature e.g. text decoration,
* text transform, and font style.
*
* Note: This function is for backwards compatibility.
* * It is necessary to parse older blocks whose typography styles contain presets.
* * It mostly replaces the deprecated `wp_typography_get_css_variable_inline_style()`,
* but skips compiling a CSS declaration as the style engine takes over this role.
* @link https://github.com/wordpress/gutenberg/pull/27555
*
* @since 6.1.0
*
* @param string $style_value A raw style value for a single typography feature from a block's style attribute.
* @param string $css_property Slug for the CSS property the inline style sets.
* @return string A CSS inline style value.
*/
function wp_typography_get_preset_inline_style_value( $style_value, $css_property ) {
// If the style value is not a preset CSS variable go no further.
if ( empty( $style_value ) || ! str_contains( $style_value, "var:preset|{$css_property}|" ) ) {
return $style_value;
}
/*
* For backwards compatibility.
* Presets were removed in WordPress/gutenberg#27555.
* A preset CSS variable is the style.
* Gets the style value from the string and return CSS style.
*/
$index_to_splice = strrpos( $style_value, '|' ) + 1;
$slug = _wp_to_kebab_case( substr( $style_value, $index_to_splice ) );
// Return the actual CSS inline style value,
// e.g. `var(--wp--preset--text-decoration--underline);`.
return sprintf( 'var(--wp--preset--%s--%s);', $css_property, $slug );
}
/**
* Renders typography styles/content to the block wrapper.
*
* @since 6.1.0
*
* @param string $block_content Rendered block content.
* @param array $block Block object.
* @return string Filtered block content.
*/
function wp_render_typography_support( $block_content, $block ) {
if ( ! empty( $block['attrs']['fitText'] ) && $block['attrs']['fitText'] && ! is_admin() ) {
wp_enqueue_script_module( '@wordpress/block-editor/utils/fit-text-frontend' );
// Add Interactivity API directives for fit text to work with client-side navigation.
if ( ! empty( $block_content ) ) {
$processor = new WP_HTML_Tag_Processor( $block_content );
if ( $processor->next_tag() ) {
if ( ! $processor->get_attribute( 'data-wp-interactive' ) ) {
$processor->set_attribute( 'data-wp-interactive', true );
}
$processor->set_attribute( 'data-wp-context---core-fit-text', 'core/fit-text::{"fontSize":""}' );
$processor->set_attribute( 'data-wp-init---core-fit-text', 'core/fit-text::callbacks.init' );
$processor->set_attribute( 'data-wp-style--font-size', 'core/fit-text::context.fontSize' );
$block_content = $processor->get_updated_html();
}
}
// fitText supersedes any other typography features
return $block_content;
}
if ( ! isset( $block['attrs']['style']['typography']['fontSize'] ) ) {
return $block_content;
}
$custom_font_size = $block['attrs']['style']['typography']['fontSize'];
$fluid_font_size = wp_get_typography_font_size_value( array( 'size' => $custom_font_size ) );
/*
* Checks that $fluid_font_size does not match $custom_font_size,
* which means it's been mutated by the fluid font size functions.
*/
if ( ! empty( $fluid_font_size ) && $fluid_font_size !== $custom_font_size ) {
// Replaces the first instance of `font-size:$custom_font_size` with `font-size:$fluid_font_size`.
return preg_replace( '/font-size\s*:\s*' . preg_quote( $custom_font_size, '/' ) . '\s*;?/', 'font-size:' . esc_attr( $fluid_font_size ) . ';', $block_content, 1 );
}
return $block_content;
}
/**
* Checks a string for a unit and value and returns an array
* consisting of `'value'` and `'unit'`, e.g. array( '42', 'rem' ).
*
* @since 6.1.0
*
* @param string|int|float $raw_value Raw size value from theme.json.
* @param array $options {
* Optional. An associative array of options. Default is empty array.
*
* @type string $coerce_to Coerce the value to rem or px. Default `'rem'`.
* @type int $root_size_value Value of root font size for rem|em <-> px conversion. Default `16`.
* @type string[] $acceptable_units An array of font size units. Default `array( 'rem', 'px', 'em' )`;
* }
* @return array|null An array consisting of `'value'` and `'unit'` properties on success.
* `null` on failure.
* @phpstan-param array{
* coerce_to?: string,
* root_size_value?: positive-int,
* acceptable_units?: non-empty-array,
* } $options
* @phpstan-return array{ value: float, unit: non-empty-string }|null
*/
function wp_get_typography_value_and_unit( $raw_value, $options = array() ): ?array {
if ( ! is_string( $raw_value ) && ! is_int( $raw_value ) && ! is_float( $raw_value ) ) {
_doing_it_wrong(
__FUNCTION__,
__( 'Raw size value must be a string, integer, or float.' ),
'6.1.0'
);
return null;
}
if ( empty( $raw_value ) ) {
return null;
}
// Converts numbers to pixel values by default.
if ( is_numeric( $raw_value ) ) {
$raw_value = $raw_value . 'px';
}
$defaults = array(
'coerce_to' => '',
'root_size_value' => 16,
'acceptable_units' => array( 'rem', 'px', 'em' ),
);
/**
* @var array{
* coerce_to: string,
* root_size_value: positive-int,
* acceptable_units: non-empty-array,
* } $options
*/
$options = wp_parse_args( $options, $defaults );
// Bails out if the raw value can't be parsed.
if ( ! preg_match( '/^(\d*\.?\d+)([a-zA-Z]+|%)$/', $raw_value, $matches ) ) {
return null;
}
$value = (float) $matches[1];
$unit = $matches[2];
if ( ! in_array( $unit, $options['acceptable_units'], true ) ) {
return null;
}
/*
* Default browser font size. Later, possibly could inject some JS to
* compute this `getComputedStyle( document.querySelector( "html" ) ).fontSize`.
*/
if ( 'px' === $options['coerce_to'] && ( 'em' === $unit || 'rem' === $unit ) ) {
$value = $value * $options['root_size_value'];
$unit = $options['coerce_to'];
}
if ( 'px' === $unit && ( 'em' === $options['coerce_to'] || 'rem' === $options['coerce_to'] ) ) {
$value = $value / $options['root_size_value'];
$unit = $options['coerce_to'];
}
/*
* No calculation is required if swapping between em and rem yet,
* since we assume a root size value. Later we might like to differentiate between
* :root font size (rem) and parent element font size (em) relativity.
*/
if ( ( 'em' === $options['coerce_to'] || 'rem' === $options['coerce_to'] ) && ( 'em' === $unit || 'rem' === $unit ) ) {
$unit = $options['coerce_to'];
}
return array(
'value' => round( $value, 3 ),
'unit' => $unit,
);
}
/**
* Internal implementation of CSS clamp() based on available min/max viewport
* width and min/max font sizes.
*
* @since 6.1.0
* @since 6.3.0 Checks for unsupported min/max viewport values that cause invalid clamp values.
* @since 6.5.0 Returns early when min and max viewport subtraction is zero to avoid division by zero.
* @access private
*
* @param array $args {
* Optional. An associative array of values to calculate a fluid formula
* for font size. Default is empty array.
*
* @type string $maximum_viewport_width Maximum size up to which type will have fluidity.
* @type string $minimum_viewport_width Minimum viewport size from which type will have fluidity.
* @type string $maximum_font_size Maximum font size for any clamp() calculation.
* @type string $minimum_font_size Minimum font size for any clamp() calculation.
* @type int $scale_factor A scale factor to determine how fast a font scales within boundaries.
* }
* @return string|null A font-size value using clamp() on success, otherwise null.
*/
function wp_get_computed_fluid_typography_value( $args = array() ) {
$maximum_viewport_width_raw = $args['maximum_viewport_width'] ?? null;
$minimum_viewport_width_raw = $args['minimum_viewport_width'] ?? null;
$maximum_font_size_raw = $args['maximum_font_size'] ?? null;
$minimum_font_size_raw = $args['minimum_font_size'] ?? null;
$scale_factor = $args['scale_factor'] ?? null;
// Normalizes the minimum font size in order to use the value for calculations.
$minimum_font_size = wp_get_typography_value_and_unit( $minimum_font_size_raw );
/*
* We get a 'preferred' unit to keep units consistent when calculating,
* otherwise the result will not be accurate.
*/
$font_size_unit = $minimum_font_size['unit'] ?? 'rem';
// Normalizes the maximum font size in order to use the value for calculations.
$maximum_font_size = wp_get_typography_value_and_unit(
$maximum_font_size_raw,
array(
'coerce_to' => $font_size_unit,
)
);
// Checks for mandatory min and max sizes, and protects against unsupported units.
if ( ! $maximum_font_size || ! $minimum_font_size ) {
return null;
}
// Uses rem for accessible fluid target font scaling.
$minimum_font_size_rem = wp_get_typography_value_and_unit(
$minimum_font_size_raw,
array(
'coerce_to' => 'rem',
)
);
// Viewport widths defined for fluid typography. Normalize units.
$maximum_viewport_width = wp_get_typography_value_and_unit(
$maximum_viewport_width_raw,
array(
'coerce_to' => $font_size_unit,
)
);
$minimum_viewport_width = wp_get_typography_value_and_unit(
$minimum_viewport_width_raw,
array(
'coerce_to' => $font_size_unit,
)
);
// Protects against unsupported units in min and max viewport widths.
if ( ! $minimum_viewport_width || ! $maximum_viewport_width ) {
return null;
}
// Calculates the linear factor denominator. If it's 0, we cannot calculate a fluid value.
$linear_factor_denominator = $maximum_viewport_width['value'] - $minimum_viewport_width['value'];
if ( empty( $linear_factor_denominator ) ) {
return null;
}
/*
* Build CSS rule.
* Borrowed from https://websemantics.uk/tools/responsive-font-calculator/.
*/
$view_port_width_offset = round( $minimum_viewport_width['value'] / 100, 3 ) . $font_size_unit;
$linear_factor = 100 * ( ( $maximum_font_size['value'] - $minimum_font_size['value'] ) / ( $linear_factor_denominator ) );
$linear_factor_scaled = round( $linear_factor * $scale_factor, 3 );
$linear_factor_scaled = empty( $linear_factor_scaled ) ? 1 : $linear_factor_scaled;
$fluid_target_font_size = implode( '', $minimum_font_size_rem ) . " + ((1vw - $view_port_width_offset) * $linear_factor_scaled)";
return "clamp($minimum_font_size_raw, $fluid_target_font_size, $maximum_font_size_raw)";
}
/**
* Returns a font-size value based on a given font-size preset.
* Takes into account fluid typography parameters and attempts to return a CSS
* formula depending on available, valid values.
*
* @since 6.1.0
* @since 6.1.1 Adjusted rules for min and max font sizes.
* @since 6.2.0 Added 'settings.typography.fluid.minFontSize' support.
* @since 6.3.0 Using layout.wideSize as max viewport width, and logarithmic scale factor to calculate minimum font scale.
* @since 6.4.0 Added configurable min and max viewport width values to the typography.fluid theme.json schema.
* @since 6.6.0 Deprecated bool argument $should_use_fluid_typography.
* @since 6.7.0 Font size presets can enable fluid typography individually, even if it’s disabled globally.
*
* @param array $preset {
* Required. fontSizes preset value as seen in theme.json.
*
* @type string $name Name of the font size preset.
* @type string $slug Kebab-case, unique identifier for the font size preset.
* @type string|int|float $size CSS font-size value, including units if applicable.
* }
* @param bool|array $settings Optional Theme JSON settings array that overrides any global theme settings.
* Default is false.
* @return string|null Font-size value or null if a size is not passed in $preset.
*/
function wp_get_typography_font_size_value( $preset, $settings = array() ) {
if ( ! isset( $preset['size'] ) ) {
return null;
}
/*
* Catches falsy values and 0/'0'. Fluid calculations cannot be performed on `0`.
* Also returns early when a preset font size explicitly disables fluid typography with `false`.
*/
$fluid_font_size_settings = $preset['fluid'] ?? null;
if ( false === $fluid_font_size_settings || empty( $preset['size'] ) ) {
return $preset['size'];
}
/*
* As a boolean (deprecated since 6.6), $settings acts as an override to switch fluid typography "on" (`true`) or "off" (`false`).
*/
if ( is_bool( $settings ) ) {
_deprecated_argument( __FUNCTION__, '6.6.0', __( '`boolean` type for second argument `$settings` is deprecated. Use `array()` instead.' ) );
$settings = array(
'typography' => array(
'fluid' => $settings,
),
);
}
// Fallback to global settings as default.
$global_settings = wp_get_global_settings();
$settings = wp_parse_args(
$settings,
$global_settings
);
$typography_settings = $settings['typography'] ?? array();
/*
* Return early when fluid typography is disabled in the settings, and there
* are no local settings to enable it for the individual preset.
*
* If this condition isn't met, either the settings or individual preset settings
* have enabled fluid typography.
*/
if ( empty( $typography_settings['fluid'] ) && empty( $fluid_font_size_settings ) ) {
return $preset['size'];
}
$fluid_settings = $typography_settings['fluid'] ?? array();
$layout_settings = $settings['layout'] ?? array();
// Defaults.
$default_maximum_viewport_width = '1600px';
$default_minimum_viewport_width = '320px';
$default_minimum_font_size_factor_max = 0.75;
$default_minimum_font_size_factor_min = 0.25;
$default_scale_factor = 1;
$default_minimum_font_size_limit = '14px';
// Defaults overrides.
$minimum_viewport_width = $fluid_settings['minViewportWidth'] ?? $default_minimum_viewport_width;
$maximum_viewport_width = isset( $layout_settings['wideSize'] ) && ! empty( wp_get_typography_value_and_unit( $layout_settings['wideSize'] ) ) ? $layout_settings['wideSize'] : $default_maximum_viewport_width;
if ( isset( $fluid_settings['maxViewportWidth'] ) ) {
$maximum_viewport_width = $fluid_settings['maxViewportWidth'];
}
$has_min_font_size = isset( $fluid_settings['minFontSize'] ) && ! empty( wp_get_typography_value_and_unit( $fluid_settings['minFontSize'] ) );
$minimum_font_size_limit = $has_min_font_size ? $fluid_settings['minFontSize'] : $default_minimum_font_size_limit;
// Try to grab explicit min and max fluid font sizes.
$minimum_font_size_raw = $fluid_font_size_settings['min'] ?? null;
$maximum_font_size_raw = $fluid_font_size_settings['max'] ?? null;
// Font sizes.
$preferred_size = wp_get_typography_value_and_unit( $preset['size'] );
// Protects against unsupported units.
if ( empty( $preferred_size['unit'] ) ) {
return $preset['size'];
}
/*
* Normalizes the minimum font size limit according to the incoming unit,
* in order to perform comparative checks.
*/
$minimum_font_size_limit = wp_get_typography_value_and_unit(
$minimum_font_size_limit,
array(
'coerce_to' => $preferred_size['unit'],
)
);
// Don't enforce minimum font size if a font size has explicitly set a min and max value.
if ( ! empty( $minimum_font_size_limit ) && ( ! $minimum_font_size_raw && ! $maximum_font_size_raw ) ) {
/*
* If a minimum size was not passed to this function
* and the user-defined font size is lower than $minimum_font_size_limit,
* do not calculate a fluid value.
*/
if ( $preferred_size['value'] <= $minimum_font_size_limit['value'] ) {
return $preset['size'];
}
}
// If no fluid max font size is available use the incoming value.
if ( ! $maximum_font_size_raw ) {
$maximum_font_size_raw = $preferred_size['value'] . $preferred_size['unit'];
}
/*
* If no minimumFontSize is provided, create one using
* the given font size multiplied by the min font size scale factor.
*/
if ( ! $minimum_font_size_raw ) {
$preferred_font_size_in_px = 'px' === $preferred_size['unit'] ? $preferred_size['value'] : $preferred_size['value'] * 16;
/*
* The scale factor is a multiplier that affects how quickly the curve will move towards the minimum,
* that is, how quickly the size factor reaches 0 given increasing font size values.
* For a - b * log2(), lower values of b will make the curve move towards the minimum faster.
* The scale factor is constrained between min and max values.
*/
$minimum_font_size_factor = clamp( 1 - 0.075 * log( $preferred_font_size_in_px, 2 ), $default_minimum_font_size_factor_min, $default_minimum_font_size_factor_max );
$calculated_minimum_font_size = round( $preferred_size['value'] * $minimum_font_size_factor, 3 );
// Only use calculated min font size if it's > $minimum_font_size_limit value.
if ( ! empty( $minimum_font_size_limit ) && $calculated_minimum_font_size <= $minimum_font_size_limit['value'] ) {
$minimum_font_size_raw = $minimum_font_size_limit['value'] . $minimum_font_size_limit['unit'];
} else {
$minimum_font_size_raw = $calculated_minimum_font_size . $preferred_size['unit'];
}
}
$fluid_font_size_value = wp_get_computed_fluid_typography_value(
array(
'minimum_viewport_width' => $minimum_viewport_width,
'maximum_viewport_width' => $maximum_viewport_width,
'minimum_font_size' => $minimum_font_size_raw,
'maximum_font_size' => $maximum_font_size_raw,
'scale_factor' => $default_scale_factor,
)
);
if ( ! empty( $fluid_font_size_value ) ) {
return $fluid_font_size_value;
}
return $preset['size'];
}
// Register the block support.
WP_Block_Supports::get_instance()->register(
'typography',
array(
'register_attribute' => 'wp_register_typography_support',
'apply' => 'wp_apply_typography_support',
)
);
PK ! gvU vU
states.phpnu [ $nested_value ) {
$value[ $key ] = wp_normalize_state_preset_vars( $nested_value );
}
return $value;
}
if ( ! is_string( $value ) || ! str_starts_with( $value, 'var:preset|' ) ) {
return $value;
}
$unwrapped_name = str_replace( '|', '--', substr( $value, strlen( 'var:' ) ) );
return "var(--wp--$unwrapped_name)";
}
/**
* Normalizes a state style object before generating CSS declarations.
*
* @since 7.1.0
*
* @param array $style State style object.
* @return array Normalized state style object.
*/
function wp_normalize_state_style_for_css_output( $style ) {
// Layout is processed separately by wp_render_layout_support_flag(), so we remove it before declaration generation.
unset( $style['layout'] );
$style = wp_normalize_state_preset_vars( $style );
return $style;
}
/**
* Adds fallback border-style declarations for visible border declarations.
*
* CSS does not render border color or width unless a border style is also set.
* State styles are emitted as stylesheet rules rather than inline styles, so
* they cannot rely on the block-library inline-style attribute fallback rules.
*
* @since 7.1.0
*
* @param array $declarations CSS declarations generated by the style engine.
* @return array CSS declarations with fallback border styles applied where needed.
*/
function wp_get_state_declarations_with_fallback_border_styles( $declarations ) {
if ( ! is_array( $declarations ) ) {
return $declarations;
}
$has_border_style = isset( $declarations['border-style'] ) && '' !== $declarations['border-style'];
$has_border_color = isset( $declarations['border-color'] ) && '' !== $declarations['border-color'];
$has_border_width = isset( $declarations['border-width'] ) && '' !== $declarations['border-width'];
if ( ! $has_border_style && ( $has_border_color || $has_border_width ) ) {
$declarations['border-style'] = 'solid';
}
$sides = array( 'top', 'right', 'bottom', 'left' );
foreach ( $sides as $side ) {
$side_style_property = "border-$side-style";
$side_color_property = "border-$side-color";
$side_width_property = "border-$side-width";
$has_side_style = isset( $declarations[ $side_style_property ] ) && '' !== $declarations[ $side_style_property ];
$has_side_color = isset( $declarations[ $side_color_property ] ) && '' !== $declarations[ $side_color_property ];
$has_side_width = isset( $declarations[ $side_width_property ] ) && '' !== $declarations[ $side_width_property ];
if ( ! $has_border_style && ! $has_side_style && ( $has_side_color || $has_side_width ) ) {
$declarations[ $side_style_property ] = 'solid';
}
}
return $declarations;
}
/**
* Adds background reset declarations to prevent gradient/solid color conflicts.
*
* When a state sets a solid background-color, any gradient applied to the
* default state (via `background` shorthand or `background-image`) must be
* explicitly cleared. Without this, the gradient image layer remains visible
* on top of the solid hover color even when `!important` is used, because
* `background-color` and `background-image` are separate CSS properties.
*
* @since 7.1.0
*
* @param array $declarations CSS declarations generated by the style engine.
* @return array CSS declarations with background resets applied where needed.
*/
function wp_get_state_declarations_with_background_resets( $declarations ) {
if ( ! is_array( $declarations ) ) {
return $declarations;
}
$has_background_color = isset( $declarations['background-color'] ) && '' !== $declarations['background-color'];
$has_background = isset( $declarations['background'] ) && '' !== $declarations['background'];
$has_background_image = isset( $declarations['background-image'] ) && '' !== $declarations['background-image'];
/*
* When the state sets a solid background-color but no gradient of its own,
* emit `background-image: unset` to clear any gradient (whether stored as
* the `background` shorthand or as `background-image`) that was applied to
* the default / normal state via an inline style attribute. The declaration
* is marked important when the state rule is registered with the style engine.
*/
if ( $has_background_color && ! $has_background && ! $has_background_image ) {
$declarations['background-image'] = 'unset';
}
return $declarations;
}
/**
* Adds fallback dimension styles for aspectRatio and height block-support values.
*
* @since 7.1.0
*
* @param array $state_style State style object.
* @return array State style object with fallback dimension styles applied where needed.
*/
function wp_get_state_style_with_fallback_dimension_styles( $state_style ) {
if ( ! is_array( $state_style ) ) {
return $state_style;
}
$dimensions = isset( $state_style['dimensions'] ) && is_array( $state_style['dimensions'] )
? $state_style['dimensions']
: array();
if ( empty( $dimensions ) ) {
return $state_style;
}
if ( wp_is_explicit_aspect_ratio_value( $dimensions['aspectRatio'] ?? null ) ) {
return array_replace_recursive(
$state_style,
array(
'dimensions' => array(
'minHeight' => 'unset',
'height' => 'unset',
),
)
);
}
$has_min_height = isset( $dimensions['minHeight'] ) && ( is_string( $dimensions['minHeight'] ) || is_numeric( $dimensions['minHeight'] ) ) && '' !== trim( (string) $dimensions['minHeight'] );
$has_height = isset( $dimensions['height'] ) && ( is_string( $dimensions['height'] ) || is_numeric( $dimensions['height'] ) ) && '' !== trim( (string) $dimensions['height'] );
if ( $has_min_height || $has_height ) {
return array_replace_recursive(
$state_style,
array(
'dimensions' => array(
'aspectRatio' => 'unset',
),
)
);
}
return $state_style;
}
/**
* Adds a style fragment to a selector-keyed state style group.
*
* @since 7.1.0
*
* @param array $groups Selector-keyed style groups.
* @param string|null $selector Block or feature selector.
* @param array $style Style fragment.
*/
function wp_add_state_style_group( &$groups, $selector, $style ) {
$key = is_string( $selector ) ? $selector : '';
if ( ! isset( $groups[ $key ] ) ) {
$groups[ $key ] = array(
'selector' => $selector,
'style' => array(),
);
}
$groups[ $key ]['style'] = array_replace_recursive( $groups[ $key ]['style'], $style );
}
/**
* Splits a state style object into groups based on block feature selectors.
*
* @since 7.1.0
*
* @param array $state_style State style object.
* @param array $block_selectors Block selectors metadata.
* @return array[] Selector/style groups.
*/
function wp_get_state_style_groups( $state_style, $block_selectors ) {
$groups = array();
foreach ( $state_style as $feature => $feature_styles ) {
$feature_selectors = $block_selectors[ $feature ] ?? null;
if ( is_string( $feature_selectors ) ) {
wp_add_state_style_group(
$groups,
$feature_selectors,
array( $feature => $feature_styles )
);
continue;
}
if ( is_array( $feature_selectors ) && is_array( $feature_styles ) ) {
$remaining_styles = $feature_styles;
foreach ( $feature_selectors as $subfeature => $subfeature_selector ) {
if (
'root' === $subfeature ||
! is_string( $subfeature_selector ) ||
! array_key_exists( $subfeature, $feature_styles )
) {
continue;
}
wp_add_state_style_group(
$groups,
$subfeature_selector,
array(
$feature => array(
$subfeature => $feature_styles[ $subfeature ],
),
)
);
unset( $remaining_styles[ $subfeature ] );
}
if ( array() !== $remaining_styles ) {
wp_add_state_style_group(
$groups,
$feature_selectors['root'] ?? ( $block_selectors['root'] ?? null ),
array( $feature => $remaining_styles )
);
}
continue;
}
wp_add_state_style_group(
$groups,
$block_selectors['root'] ?? null,
array( $feature => $feature_styles )
);
}
return array_values( $groups );
}
/**
* Returns a style object with nested state keys removed.
*
* @since 7.1.0
*
* @param array $state_style State style object.
* @param array $nested_keys Keys to remove from the root style object.
* @return array Root-only style object.
*/
function wp_get_root_state_style( $state_style, $nested_keys ) {
if ( ! is_array( $state_style ) ) {
return $state_style;
}
$root_style = $state_style;
foreach ( $nested_keys as $key ) {
unset( $root_style[ $key ] );
}
return $root_style;
}
/**
* Generates all element selectors for a block root selector.
*
* @since 7.1.0
*
* @param string $root_selector The block root CSS selector.
* @return string[] Element selectors keyed by element name.
*/
function wp_get_block_state_element_selectors( $root_selector ) {
if ( ! is_string( $root_selector ) || '' === trim( $root_selector ) ) {
return array();
}
$block_selectors = wp_split_selector_list( $root_selector );
$element_selectors = array();
foreach ( WP_Theme_JSON::ELEMENTS as $element_name => $element_selector ) {
$selectors = array();
foreach ( $block_selectors as $block_selector ) {
$block_selector = trim( $block_selector );
if ( '' === $block_selector ) {
continue;
}
if ( $block_selector === $element_selector ) {
$selectors = array( $element_selector );
break;
}
$selector_prefix = "$block_selector ";
if ( ! str_contains( $element_selector, ',' ) ) {
$selectors[] = $selector_prefix . $element_selector;
continue;
}
$prepended_selectors = array();
foreach ( wp_split_selector_list( $element_selector ) as $selector ) {
$prepended_selectors[] = $selector_prefix . $selector;
}
$selectors[] = implode( ',', $prepended_selectors );
}
if ( ! empty( $selectors ) ) {
$element_selectors[ $element_name ] = implode( ',', $selectors );
}
}
return $element_selectors;
}
/**
* Adds a compiled state style rule to a rule list.
*
* @since 7.1.0
*
* @param array $css_rules Style rules.
* @param string $state Pseudo-state selector.
* @param string|null $selector Block, feature, or element selector.
* @param array $style Style object.
* @param string|null $rules_group Optional CSS grouping rule, e.g. a media query.
*/
function wp_add_block_state_style_rule( &$css_rules, $state, $selector, $style, $rules_group = null ) {
if ( empty( $style ) || ! is_array( $style ) ) {
return;
}
$compiled = wp_style_engine_get_styles(
wp_normalize_state_style_for_css_output( $style )
);
$declarations = $compiled['declarations'] ?? array();
$text_align = $style['typography']['textAlign'] ?? null;
// Base text alignment is class-based, so state styles need a declaration.
if ( is_string( $text_align ) && '' !== trim( $text_align ) ) {
$declarations['text-align'] = $text_align;
}
if ( empty( $declarations ) ) {
return;
}
$css_rules[] = array(
'state' => $state,
'selector' => $selector,
'declarations' => $declarations,
);
if ( ! empty( $rules_group ) ) {
$css_rules[ count( $css_rules ) - 1 ]['rules_group'] = $rules_group;
}
}
/**
* Builds compiled state style rules, preserving the selector each rule targets.
*
* @since 7.1.0
*
* @param array $state_styles Map of state to style array.
* @param WP_Block_Type $block_type Block type.
* @param string|null $rules_group Optional CSS grouping rule, e.g. a media query.
* @return array[] State style rules.
*/
function wp_get_block_state_style_rules( $state_styles, $block_type, $rules_group = null ) {
$css_rules = array();
$block_selectors = isset( $block_type->selectors ) && is_array( $block_type->selectors )
? $block_type->selectors
: array();
foreach ( $state_styles as $state => $state_style ) {
if ( empty( $state_style ) || ! is_array( $state_style ) ) {
continue;
}
foreach ( wp_get_state_style_groups( $state_style, $block_selectors ) as $group ) {
wp_add_block_state_style_rule(
$css_rules,
$state,
$group['selector'],
$group['style'],
$rules_group
);
}
}
return $css_rules;
}
/**
* Returns a unique class for a set of state style rules.
*
* @since 7.1.0
*
* @param string $block_name Block name.
* @param array $css_rules State style rules.
* @return string Unique class name.
*/
function wp_get_block_state_unique_class( $block_name, $css_rules ) {
return 'wp-states-' . substr(
md5(
wp_json_encode(
array(
'blockName' => $block_name,
'rules' => $css_rules,
)
)
),
0,
8
);
}
/**
* Splits a selector list by top-level commas.
*
* @since 7.1.0
*
* @param string $selector CSS selector list.
* @return string[] Selectors.
*/
function wp_split_selector_list( $selector ) {
if ( ! str_contains( $selector, ',' ) ) {
return array( $selector );
}
$selectors = array();
$current_selector = '';
$parentheses_depth = 0;
$selector_length = strlen( $selector );
for ( $i = 0; $i < $selector_length; $i++ ) {
$char = $selector[ $i ];
if ( '(' === $char ) {
++$parentheses_depth;
} elseif ( ')' === $char && $parentheses_depth > 0 ) {
--$parentheses_depth;
} elseif ( ',' === $char && 0 === $parentheses_depth ) {
$selectors[] = $current_selector;
$current_selector = '';
continue;
}
$current_selector .= $char;
}
$selectors[] = $current_selector;
return $selectors;
}
/**
* Builds a scoped selector from a block selector and optional pseudo-state.
*
* @since 7.1.0
*
* @param string $base_selector Block-instance scoping selector.
* @param string|null $block_selector Block or feature selector from metadata.
* @param string $state Pseudo-state selector.
* @return string Scoped selector.
*/
function wp_build_state_selector( $base_selector, $block_selector, $state ) {
if ( ! is_string( $block_selector ) || '' === trim( $block_selector ) ) {
return $base_selector . $state;
}
$selectors = wp_split_selector_list( $block_selector );
$scoped_selectors = array();
foreach ( $selectors as $selector ) {
$selector = trim( $selector );
if ( '' === $selector ) {
continue;
}
/*
* Replace only the leading block selector part (e.g. class name,
* attribute selector, ID, or tag name) with the block instance selector.
* Preserve anything after that prefix, including modifier classes on the
* same element and combinators without spaces.
*/
if ( preg_match( '/^([.#]?[-_a-zA-Z0-9]+|\[[^\]]+\])/', $selector, $matches ) ) {
$scoped_selectors[] = $base_selector . substr( $selector, strlen( $matches[0] ) ) . $state;
continue;
}
$scoped_selectors[] = $base_selector . $state;
}
return empty( $scoped_selectors )
? $base_selector . $state
: implode( ', ', $scoped_selectors );
}
/**
* Renders per-instance state styles on the frontend.
*
* @since 7.1.0
*
* @param string $block_content The block's rendered HTML.
* @param array $block The block data including blockName and attrs.
* @return string Modified block content with injected state styles.
*/
function wp_render_block_states_support( $block_content, $block ) {
if ( empty( $block['blockName'] ) || empty( $block_content ) ) {
return $block_content;
}
$block_name = $block['blockName'];
$block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block_name );
if ( ! $block_type ) {
return $block_content;
}
$supported_pseudo_states = WP_Theme_JSON::VALID_BLOCK_PSEUDO_SELECTORS[ $block_name ] ?? array();
$style = $block['attrs']['style'] ?? array();
$css_rules = array();
$viewport_settings = wp_get_global_settings( array( 'viewport' ) );
$responsive_media_queries = WP_Theme_JSON::get_viewport_media_queries( $viewport_settings );
foreach ( $supported_pseudo_states as $pseudo_state ) {
if ( empty( $style[ $pseudo_state ] ) || ! is_array( $style[ $pseudo_state ] ) ) {
continue;
}
$css_rules = array_merge(
$css_rules,
wp_get_block_state_style_rules(
array( $pseudo_state => $style[ $pseudo_state ] ),
$block_type
)
);
}
foreach ( $responsive_media_queries as $breakpoint => $media_query ) {
if ( empty( $style[ $breakpoint ] ) || ! is_array( $style[ $breakpoint ] ) ) {
continue;
}
$root_state_style = wp_get_root_state_style(
$style[ $breakpoint ],
array_merge( array( 'elements' ), $supported_pseudo_states )
);
if ( ! empty( $root_state_style ) ) {
$css_rules = array_merge(
$css_rules,
wp_get_block_state_style_rules(
array( '' => $root_state_style ),
$block_type,
$media_query
)
);
}
if (
! empty( $style[ $breakpoint ]['elements'] ) &&
is_array( $style[ $breakpoint ]['elements'] )
) {
$element_selectors = wp_get_block_state_element_selectors(
wp_get_block_css_selector( $block_type )
);
foreach ( $style[ $breakpoint ]['elements'] as $element_name => $element_style ) {
if (
empty( $element_style ) ||
! is_array( $element_style ) ||
empty( $element_selectors[ $element_name ] )
) {
continue;
}
$element_pseudo_states = WP_Theme_JSON::VALID_ELEMENT_PSEUDO_SELECTORS[ $element_name ]
?? array();
$root_element_style = wp_get_root_state_style(
$element_style,
$element_pseudo_states
);
wp_add_block_state_style_rule(
$css_rules,
'',
$element_selectors[ $element_name ],
$root_element_style,
$media_query
);
foreach ( $element_pseudo_states as $pseudo_state ) {
if (
empty( $element_style[ $pseudo_state ] ) ||
! is_array( $element_style[ $pseudo_state ] )
) {
continue;
}
wp_add_block_state_style_rule(
$css_rules,
$pseudo_state,
$element_selectors[ $element_name ],
$element_style[ $pseudo_state ],
$media_query
);
}
}
}
foreach ( $supported_pseudo_states as $pseudo_state ) {
if ( empty( $style[ $breakpoint ][ $pseudo_state ] ) || ! is_array( $style[ $breakpoint ][ $pseudo_state ] ) ) {
continue;
}
$css_rules = array_merge(
$css_rules,
wp_get_block_state_style_rules(
array( $pseudo_state => $style[ $breakpoint ][ $pseudo_state ] ),
$block_type,
$media_query
)
);
}
}
if ( empty( $css_rules ) ) {
return $block_content;
}
$unique_class = wp_get_block_state_unique_class( $block_name, $css_rules );
/*
* Register each state's CSS rules with the block-supports style engine store.
* The store deduplicates rules by selector — two block instances with identical
* state styles share the same hash class and therefore the same selector,
* so only one CSS rule is emitted. The store is flushed to the page by
* wp_enqueue_stored_styles() rather than injected inline here.
*
* State declarations need !important to apply reliably over inline styles and
* preset utility classes such as .has-accent-3-background-color.
*
* Layout-driven state styles (responsive layout, blockGap, child layout) are
* handled by wp_render_layout_support_flag() so they share a selector with
* the base layout and target the correct (inner) wrapper element.
*/
$style_rules = array();
foreach ( $css_rules as $rule ) {
$declarations = $rule['declarations'];
$important_declaration_values = wp_get_state_declarations_with_background_resets( $declarations );
$important_declarations = new WP_Style_Engine_CSS_Declarations();
foreach ( $important_declaration_values as $property => $value ) {
$important_declarations->add_declaration(
$property,
$value,
array(
'important' => true,
)
);
}
$selector = wp_build_state_selector(
".$unique_class",
$rule['selector'],
$rule['state']
);
$important_style_rule = array(
'selector' => $selector,
'declarations' => $important_declarations,
);
if ( ! empty( $rule['rules_group'] ) ) {
$important_style_rule['rules_group'] = $rule['rules_group'];
}
$style_rules[] = $important_style_rule;
$fallback_declarations = wp_get_state_declarations_with_fallback_border_styles( $declarations );
foreach ( array_keys( $declarations ) as $property ) {
unset( $fallback_declarations[ $property ] );
}
if ( empty( $fallback_declarations ) ) {
continue;
}
$fallback_style_rule = array(
'selector' => $selector,
'declarations' => $fallback_declarations,
);
if ( ! empty( $rule['rules_group'] ) ) {
$fallback_style_rule['rules_group'] = $rule['rules_group'];
}
$style_rules[] = $fallback_style_rule;
}
wp_style_engine_get_stylesheet_from_css_rules(
$style_rules,
array(
'context' => 'block-supports',
'prettify' => false,
)
);
$processor = new WP_HTML_Tag_Processor( $block_content );
if ( $processor->next_tag() ) {
$processor->add_class( $unique_class );
}
return $processor->get_updated_html();
}
add_filter( 'render_block', 'wp_render_block_states_support', 10, 2 );
PK !