: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
$custom_block_css = isset( $this->theme_json['styles']['blocks'][ $name ]['css'] )
? $this->theme_json['styles']['blocks'][ $name ]['css']
if ( $custom_block_css ) {
$selector = static::$blocks_metadata[ $name ]['selector'];
$stylesheet .= $this->process_blocks_custom_css( $custom_block_css, $selector );
* Returns the page templates of the active theme.
public function get_custom_templates() {
$custom_templates = array();
if ( ! isset( $this->theme_json['customTemplates'] ) || ! is_array( $this->theme_json['customTemplates'] ) ) {
return $custom_templates;
foreach ( $this->theme_json['customTemplates'] as $item ) {
if ( isset( $item['name'] ) ) {
$custom_templates[ $item['name'] ] = array(
'title' => isset( $item['title'] ) ? $item['title'] : '',
'postTypes' => isset( $item['postTypes'] ) ? $item['postTypes'] : array( 'page' ),
return $custom_templates;
* Returns the template part data of active theme.
public function get_template_parts() {
$template_parts = array();
if ( ! isset( $this->theme_json['templateParts'] ) || ! is_array( $this->theme_json['templateParts'] ) ) {
foreach ( $this->theme_json['templateParts'] as $item ) {
if ( isset( $item['name'] ) ) {
$template_parts[ $item['name'] ] = array(
'title' => isset( $item['title'] ) ? $item['title'] : '',
'area' => isset( $item['area'] ) ? $item['area'] : '',
* Converts each style section into a list of rulesets
* containing the block styles to be appended to the stylesheet.
* See glossary at https://developer.mozilla.org/en-US/docs/Web/CSS/Syntax
* For each section this creates a new ruleset such as:
* style-property-one: value;
* @since 5.8.0 As `get_block_styles()`.
* @since 5.9.0 Renamed from `get_block_styles()` to `get_block_classes()`
* and no longer returns preset classes.
* Removed the `$setting_nodes` parameter.
* @since 6.1.0 Moved most internal logic to `get_styles_for_block()`.
* @param array $style_nodes Nodes with styles.
* @return string The new stylesheet.
protected function get_block_classes( $style_nodes ) {
foreach ( $style_nodes as $metadata ) {
if ( null === $metadata['selector'] ) {
$block_rules .= static::get_styles_for_block( $metadata );
* Gets the CSS layout rules for a particular block from theme.json layout definitions.
* @since 6.3.0 Reduced specificity for layout margin rules.
* @since 6.5.1 Only output rules referencing content and wide sizes when values exist.
* @since 6.5.3 Add types parameter to check if only base layout styles are needed.
* @since 6.6.0 Updated layout style specificity to be compatible with overall 0-1-0 specificity in global styles.
* @param array $block_metadata Metadata about the block to get styles for.
* @param array $types Optional. Types of styles to output. If empty, all styles will be output.
* @return string Layout styles for the block.
protected function get_layout_styles( $block_metadata, $types = array() ) {
// Skip outputting layout styles if explicitly disabled.
if ( current_theme_supports( 'disable-layout-styles' ) ) {
if ( isset( $block_metadata['name'] ) ) {
$block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block_metadata['name'] );
if ( ! block_has_support( $block_type, 'layout', false ) && ! block_has_support( $block_type, '__experimentalLayout', false ) ) {
$selector = isset( $block_metadata['selector'] ) ? $block_metadata['selector'] : '';
$has_block_gap_support = isset( $this->theme_json['settings']['spacing']['blockGap'] );
$has_fallback_gap_support = ! $has_block_gap_support; // This setting isn't useful yet: it exists as a placeholder for a future explicit fallback gap styles support.
$node = _wp_array_get( $this->theme_json, $block_metadata['path'], array() );
$layout_definitions = wp_get_layout_definitions();
$layout_selector_pattern = '/^[a-zA-Z0-9\-\.\,\ *+>:\(\)]*$/'; // Allow alphanumeric classnames, spaces, wildcard, sibling, child combinator and pseudo class selectors.
* Gap styles will only be output if the theme has block gap support, or supports a fallback gap.
* Default layout gap styles will be skipped for themes that do not explicitly opt-in to blockGap with a `true` or `false` value.
if ( $has_block_gap_support || $has_fallback_gap_support ) {
// Use a fallback gap value if block gap support is not available.
if ( ! $has_block_gap_support ) {
$block_gap_value = static::ROOT_BLOCK_SELECTOR === $selector ? '0.5em' : null;
if ( ! empty( $block_type ) ) {
$block_gap_value = isset( $block_type->supports['spacing']['blockGap']['__experimentalDefault'] )
? $block_type->supports['spacing']['blockGap']['__experimentalDefault']
$block_gap_value = static::get_property_value( $node, array( 'spacing', 'blockGap' ) );
// Support split row / column values and concatenate to a shorthand value.
if ( is_array( $block_gap_value ) ) {
if ( isset( $block_gap_value['top'] ) && isset( $block_gap_value['left'] ) ) {
$gap_row = static::get_property_value( $node, array( 'spacing', 'blockGap', 'top' ) );
$gap_column = static::get_property_value( $node, array( 'spacing', 'blockGap', 'left' ) );
$block_gap_value = $gap_row === $gap_column ? $gap_row : $gap_row . ' ' . $gap_column;
// Skip outputting gap value if not all sides are provided.
// If the block should have custom gap, add the gap styles.
if ( null !== $block_gap_value && false !== $block_gap_value && '' !== $block_gap_value ) {
foreach ( $layout_definitions as $layout_definition_key => $layout_definition ) {
// Allow outputting fallback gap styles for flex and grid layout types when block gap support isn't available.
if ( ! $has_block_gap_support && 'flex' !== $layout_definition_key && 'grid' !== $layout_definition_key ) {
$class_name = isset( $layout_definition['className'] ) ? $layout_definition['className'] : false;
$spacing_rules = isset( $layout_definition['spacingStyles'] ) ? $layout_definition['spacingStyles'] : array();
! empty( $class_name ) &&
! empty( $spacing_rules )
foreach ( $spacing_rules as $spacing_rule ) {
isset( $spacing_rule['selector'] ) &&
preg_match( $layout_selector_pattern, $spacing_rule['selector'] ) &&
! empty( $spacing_rule['rules'] )
// Iterate over each of the styling rules and substitute non-string values such as `null` with the real `blockGap` value.
foreach ( $spacing_rule['rules'] as $css_property => $css_value ) {
$current_css_value = is_string( $css_value ) ? $css_value : $block_gap_value;
if ( static::is_safe_css_declaration( $css_property, $current_css_value ) ) {
'value' => $current_css_value,
if ( ! $has_block_gap_support ) {
// For fallback gap styles, use lower specificity, to ensure styles do not unintentionally override theme styles.
$format = static::ROOT_BLOCK_SELECTOR === $selector ? ':where(.%2$s%3$s)' : ':where(%1$s.%2$s%3$s)';
$layout_selector = sprintf(
$spacing_rule['selector']
$format = static::ROOT_BLOCK_SELECTOR === $selector ? ':root :where(.%2$s)%3$s' : ':root :where(%1$s-%2$s)%3$s';
$layout_selector = sprintf(
$spacing_rule['selector']
$block_rules .= static::to_ruleset( $layout_selector, $declarations );
static::ROOT_BLOCK_SELECTOR === $selector
$valid_display_modes = array( 'block', 'flex', 'grid' );
foreach ( $layout_definitions as $layout_definition ) {
$class_name = isset( $layout_definition['className'] ) ? $layout_definition['className'] : false;
$base_style_rules = isset( $layout_definition['baseStyles'] ) ? $layout_definition['baseStyles'] : array();
! empty( $class_name ) &&
is_array( $base_style_rules )
// Output display mode. This requires special handling as `display` is not exposed in `safe_style_css_filter`.
! empty( $layout_definition['displayMode'] ) &&
is_string( $layout_definition['displayMode'] ) &&
in_array( $layout_definition['displayMode'], $valid_display_modes, true )
$layout_selector = sprintf(
$block_rules .= static::to_ruleset(
'value' => $layout_definition['displayMode'],
foreach ( $base_style_rules as $base_style_rule ) {
// Skip outputting base styles for flow and constrained layout types if theme doesn't support theme.json. The 'base-layout-styles' type flags this.
if ( in_array( 'base-layout-styles', $types, true ) && ( 'default' === $layout_definition['name'] || 'constrained' === $layout_definition['name'] ) ) {
isset( $base_style_rule['selector'] ) &&
preg_match( $layout_selector_pattern, $base_style_rule['selector'] ) &&
! empty( $base_style_rule['rules'] )
foreach ( $base_style_rule['rules'] as $css_property => $css_value ) {
// Skip rules that reference content size or wide size if they are not defined in the theme.json.
is_string( $css_value ) &&
( str_contains( $css_value, '--global--content-size' ) || str_contains( $css_value, '--global--wide-size' ) ) &&
! isset( $this->theme_json['settings']['layout']['contentSize'] ) &&
! isset( $this->theme_json['settings']['layout']['wideSize'] )
if ( static::is_safe_css_declaration( $css_property, $css_value ) ) {
$layout_selector = sprintf(
$base_style_rule['selector']
$block_rules .= static::to_ruleset( $layout_selector, $declarations );
* Creates new rulesets as classes for each preset value such as:
* .has-value-background-color {
* background-color: value;
* .has-value-gradient-background {
* p.has-value-gradient-background {
* @param array $setting_nodes Nodes with settings.
* @param string[] $origins List of origins to process presets from.
* @return string The new stylesheet.
protected function get_preset_classes( $setting_nodes, $origins ) {
foreach ( $setting_nodes as $metadata ) {
if ( null === $metadata['selector'] ) {
$selector = $metadata['selector'];
$node = _wp_array_get( $this->theme_json, $metadata['path'], array() );
$preset_rules .= static::compute_preset_classes( $node, $selector, $origins );
* Converts each styles section into a list of rulesets
* to be appended to the stylesheet.
* These rulesets contain all the css variables (custom variables and preset variables).
* See glossary at https://developer.mozilla.org/en-US/docs/Web/CSS/Syntax
* For each section this creates a new ruleset such as:
* --wp--preset--category--slug: value;
* --wp--custom--variable: value;
* @since 5.9.0 Added the `$origins` parameter.
* @param array $nodes Nodes with settings.
* @param string[] $origins List of origins to process.
* @return string The new stylesheet.
protected function get_css_variables( $nodes, $origins ) {
foreach ( $nodes as $metadata ) {
if ( null === $metadata['selector'] ) {
$selector = $metadata['selector'];
$node = _wp_array_get( $this->theme_json, $metadata['path'], array() );
$declarations = static::compute_preset_vars( $node, $origins );
$theme_vars_declarations = static::compute_theme_vars( $node );
foreach ( $theme_vars_declarations as $theme_vars_declaration ) {
$declarations[] = $theme_vars_declaration;
$stylesheet .= static::to_ruleset( $selector, $declarations );
* Given a selector and a declaration list,
* creates the corresponding ruleset.
* @param string $selector CSS selector.
* @param array $declarations List of declarations.
* @return string The resulting CSS ruleset.
protected static function to_ruleset( $selector, $declarations ) {
if ( empty( $declarations ) ) {
$declaration_block = array_reduce(
static function ( $carry, $element ) {
return $carry .= $element['name'] . ': ' . $element['value'] . ';'; },
return $selector . '{' . $declaration_block . '}';
* Given a settings array, returns the generated rulesets
* for the preset classes.
* @since 5.9.0 Added the `$origins` parameter.
* @since 6.6.0 Added check for root CSS properties selector.
* @param array $settings Settings to process.
* @param string $selector Selector wrapping the classes.
* @param string[] $origins List of origins to process.
* @return string The result of processing the presets.
protected static function compute_preset_classes( $settings, $selector, $origins ) {
if ( static::ROOT_BLOCK_SELECTOR === $selector || static::ROOT_CSS_PROPERTIES_SELECTOR === $selector ) {
* Classes at the global level do not need any CSS prefixed,
* and we don't want to increase its specificity.
foreach ( static::PRESETS_METADATA as $preset_metadata ) {
if ( empty( $preset_metadata['classes'] ) ) {
$slugs = static::get_settings_slugs( $settings, $preset_metadata, $origins );
foreach ( $preset_metadata['classes'] as $class => $property ) {
foreach ( $slugs as $slug ) {
$css_var = static::replace_slug_in_string( $preset_metadata['css_vars'], $slug );
$class_name = static::replace_slug_in_string( $class, $slug );
// $selector is often empty, so we can save ourselves the `append_to_selector()` call then.
$new_selector = '' === $selector ? $class_name : static::append_to_selector( $selector, $class_name );
$stylesheet .= static::to_ruleset(
'value' => 'var(' . $css_var . ') !important',
* Function that scopes a selector with another one. This works a bit like
* SCSS nesting except the `&` operator isn't supported.
* $selector = '> .x, .y';
* $merged = scope_selector( $scope, $selector );
* // $merged is '.a > .x, .a .y, .b .c > .x, .b .c .y'
* @since 6.6.0 Added early return if missing scope or selector.
* @param string $scope Selector to scope to.
* @param string $selector Original selector.
* @return string Scoped selector.
public static function scope_selector( $scope, $selector ) {
if ( ! $scope || ! $selector ) {
$scopes = explode( ',', $scope );
$selectors = explode( ',', $selector );
$selectors_scoped = array();
foreach ( $scopes as $outer ) {
foreach ( $selectors as $inner ) {
if ( ! empty( $outer ) && ! empty( $inner ) ) {
$selectors_scoped[] = $outer . ' ' . $inner;
} elseif ( empty( $outer ) ) {
$selectors_scoped[] = $inner;