diff --git a/src/wp-includes/block-patterns/query-standard-posts.php b/src/wp-includes/block-patterns/query-standard-posts.php
index 50fa37d637075..c05ace98db93c 100644
--- a/src/wp-includes/block-patterns/query-standard-posts.php
+++ b/src/wp-includes/block-patterns/query-standard-posts.php
@@ -9,7 +9,7 @@
'title' => _x( 'Standard', 'Block pattern title' ),
'blockTypes' => array( 'core/query' ),
'categories' => array( 'query' ),
- 'content' => '
+ 'content' => '
diff --git a/src/wp-includes/block-supports/border.php b/src/wp-includes/block-supports/border.php
index 08f3781ba6b3e..ca213cd5dc056 100644
--- a/src/wp-includes/block-supports/border.php
+++ b/src/wp-includes/block-supports/border.php
@@ -63,8 +63,23 @@ function wp_apply_border_support( $block_type, $block_attributes ) {
wp_has_border_feature_support( $block_type, 'radius' ) &&
isset( $block_attributes['style']['border']['radius'] )
) {
- $border_radius = (int) $block_attributes['style']['border']['radius'];
- $styles[] = sprintf( 'border-radius: %dpx;', $border_radius );
+ $border_radius = $block_attributes['style']['border']['radius'];
+
+ if ( is_array( $border_radius ) ) {
+ // We have individual border radius corner values.
+ foreach ( $border_radius as $key => $radius ) {
+ // Convert CamelCase corner name to kebab-case.
+ $corner = strtolower( preg_replace( '/(?attributes ) {
+ $block_type->attributes = array();
+ }
+
+ // Check for existing style attribute definition e.g. from block.json.
+ if ( array_key_exists( 'style', $block_type->attributes ) ) {
+ return;
+ }
+
+ $has_dimensions_support = block_has_support( $block_type, array( '__experimentalDimensions' ), false );
+ // Future block supports such as height & width will be added here.
+
+ if ( $has_dimensions_support ) {
+ $block_type->attributes['style'] = array(
+ 'type' => 'object',
+ );
+ }
+}
+
+/**
+ * Add CSS classes for block dimensions to the incoming attributes array.
+ * This will be applied to the block markup in the front-end.
+ *
+ * @since 5.9.0
+ * @access private
+ *
+ * @param WP_Block_Type $block_type Block Type.
+ * @param array $block_attributes Block attributes.
+ *
+ * @return array Block dimensions CSS classes and inline styles.
+ */
+function wp_apply_dimensions_support( $block_type, $block_attributes ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
+ if ( wp_skip_dimensions_serialization( $block_type ) ) {
+ return array();
+ }
+
+ $styles = array();
+
+ // Height support to be added in near future.
+ // Width support to be added in near future.
+
+ return empty( $styles ) ? array() : array( 'style' => implode( ' ', $styles ) );
+}
+
+/**
+ * Checks whether serialization of the current block's dimensions properties
+ * should occur.
+ *
+ * @since 5.9.0
+ * @access private
+ *
+ * @param WP_Block_type $block_type Block type.
+ *
+ * @return boolean Whether to serialize spacing support styles & classes.
+ */
+function wp_skip_dimensions_serialization( $block_type ) {
+ $dimensions_support = _wp_array_get( $block_type->supports, array( '__experimentalDimensions' ), false );
+ return is_array( $dimensions_support ) &&
+ array_key_exists( '__experimentalSkipSerialization', $dimensions_support ) &&
+ $dimensions_support['__experimentalSkipSerialization'];
+}
+
+// Register the block support.
+WP_Block_Supports::get_instance()->register(
+ 'dimensions',
+ array(
+ 'register_attribute' => 'wp_register_dimensions_support',
+ 'apply' => 'wp_apply_dimensions_support',
+ )
+);
diff --git a/src/wp-includes/block-supports/duotone.php b/src/wp-includes/block-supports/duotone.php
index c2f2d5256d676..974aba5133a0d 100644
--- a/src/wp-includes/block-supports/duotone.php
+++ b/src/wp-includes/block-supports/duotone.php
@@ -266,7 +266,7 @@ function wp_tinycolor_string_to_rgb( $color_str ) {
$hsla_regexp = '/^hsla' . $permissive_match4 . '$/';
if ( preg_match( $hsla_regexp, $color_str, $match ) ) {
- return wp_tinycolor_hsl_to_rgb(
+ $rgb = wp_tinycolor_hsl_to_rgb(
array(
'h' => $match[1],
's' => $match[2],
@@ -384,13 +384,16 @@ function wp_register_duotone_support( $block_type ) {
}
}
}
+
/**
* Renders the duotone filter SVG and returns the CSS filter property to
* reference the rendered SVG.
*
* @since 5.9.0
- *
+ * @access private
+
* @param array $preset Duotone preset value as seen in theme.json.
+ *
* @return string Duotone CSS filter property.
*/
function wp_render_duotone_filter_preset( $preset ) {
@@ -460,13 +463,11 @@ function wp_render_duotone_filter_preset( $preset ) {
}
add_action(
- /*
- * Safari doesn't render SVG filters defined in data URIs,
- * and SVG filters won't render in the head of a document,
- * so the next best place to put the SVG is in the footer.
- */
+ // Safari doesn't render SVG filters defined in data URIs,
+ // and SVG filters won't render in the head of a document,
+ // so the next best place to put the SVG is in the footer.
is_admin() ? 'admin_footer' : 'wp_footer',
- static function () use ( $svg ) {
+ function () use ( $svg ) {
echo $svg;
}
);
@@ -502,84 +503,38 @@ function wp_render_duotone_support( $block_content, $block ) {
return $block_content;
}
- $duotone_colors = $block['attrs']['style']['color']['duotone'];
-
- $duotone_values = array(
- 'r' => array(),
- 'g' => array(),
- 'b' => array(),
+ $filter_preset = array(
+ 'slug' => uniqid(),
+ 'colors' => $block['attrs']['style']['color']['duotone'],
);
- foreach ( $duotone_colors as $color_str ) {
- $color = wp_tinycolor_string_to_rgb( $color_str );
-
- $duotone_values['r'][] = $color['r'] / 255;
- $duotone_values['g'][] = $color['g'] / 255;
- $duotone_values['b'][] = $color['b'] / 255;
+ $filter_property = wp_render_duotone_filter_preset( $filter_preset );
+ $filter_id = 'wp-duotone-' . $filter_preset['slug'];
+
+ $scope = '.' . $filter_id;
+ $selectors = explode( ',', $duotone_support );
+ $scoped = array();
+ foreach ( $selectors as $sel ) {
+ $scoped[] = $scope . ' ' . trim( $sel );
}
+ $selector = implode( ', ', $scoped );
- $duotone_id = 'wp-duotone-filter-' . uniqid();
-
- $selectors = explode( ',', $duotone_support );
- $selectors_scoped = array_map(
- static function ( $selector ) use ( $duotone_id ) {
- return '.' . $duotone_id . ' ' . trim( $selector );
- },
- $selectors
- );
- $selectors_group = implode( ', ', $selectors_scoped );
-
- ob_start();
-
- ?>
+ // !important is needed because these styles render before global styles,
+ // and they should be overriding the duotone filters set by global styles.
+ $filter_style = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG
+ ? $selector . " {\n\tfilter: " . $filter_property . " !important;\n}\n"
+ : $selector . '{filter:' . $filter_property . ' !important;}';
-
-
-
-
-
-
- values=".299 .587 .114 0 0
- .299 .587 .114 0 0
- .299 .587 .114 0 0
- 0 0 0 1 0"
-
- />
-
-
-
-
-
-
-
-
-
- .$class_name a{" . $link_color_declaration . " !important;}\n";
+ $style = "\n";
// Like the layout hook this assumes the hook only applies to blocks with a single wrapper.
// Retrieve the opening tag of the first HTML element.
@@ -64,8 +68,19 @@ function wp_render_elements_support( $block_content, $block ) {
$content = substr_replace( $block_content, ' class="' . $class_name . '"', $first_element_offset + strlen( $first_element ) - 1, 0 );
}
- return $content . $style;
+ /*
+ * Ideally styles should be loaded in the head, but blocks may be parsed
+ * after that, so loading in the footer for now.
+ * See https://core.trac.wordpress.org/ticket/53494.
+ */
+ add_action(
+ 'wp_footer',
+ static function () use ( $style ) {
+ echo $style;
+ }
+ );
+ return $content;
}
add_filter( 'render_block', 'wp_render_elements_support', 10, 2 );
diff --git a/src/wp-includes/block-supports/layout.php b/src/wp-includes/block-supports/layout.php
index be1da09899da3..88eb2d50f9af5 100644
--- a/src/wp-includes/block-supports/layout.php
+++ b/src/wp-includes/block-supports/layout.php
@@ -29,6 +29,106 @@ function wp_register_layout_support( $block_type ) {
}
}
+/**
+ * Generates the CSS corresponding to the provided layout.
+ *
+ * @since 5.9.0
+ * @access private
+ *
+ * @param string $selector CSS selector.
+ * @param array $layout Layout object. The one that is passed has already checked the existance of default block layout.
+ * @param boolean $has_block_gap_support Whether the theme has support for the block gap.
+ *
+ * @return string CSS style.
+ */
+function wp_get_layout_style( $selector, $layout, $has_block_gap_support = false ) {
+ $layout_type = isset( $layout['type'] ) ? $layout['type'] : 'default';
+
+ $style = '';
+ if ( 'default' === $layout_type ) {
+ $content_size = isset( $layout['contentSize'] ) ? $layout['contentSize'] : null;
+ $wide_size = isset( $layout['wideSize'] ) ? $layout['wideSize'] : null;
+
+ $all_max_width_value = $content_size ? $content_size : $wide_size;
+ $wide_max_width_value = $wide_size ? $wide_size : $content_size;
+
+ // Make sure there is a single CSS rule, and all tags are stripped for security.
+ // TODO: Use `safecss_filter_attr` instead - once https://core.trac.wordpress.org/ticket/46197 is patched.
+ $all_max_width_value = wp_strip_all_tags( explode( ';', $all_max_width_value )[0] );
+ $wide_max_width_value = wp_strip_all_tags( explode( ';', $wide_max_width_value )[0] );
+
+ $style = '';
+ if ( $content_size || $wide_size ) {
+ $style = "$selector > * {";
+ $style .= 'max-width: ' . esc_html( $all_max_width_value ) . ';';
+ $style .= 'margin-left: auto !important;';
+ $style .= 'margin-right: auto !important;';
+ $style .= '}';
+
+ $style .= "$selector > .alignwide { max-width: " . esc_html( $wide_max_width_value ) . ';}';
+ $style .= "$selector .alignfull { max-width: none; }";
+ }
+
+ $style .= "$selector .alignleft { float: left; margin-right: 2em; }";
+ $style .= "$selector .alignright { float: right; margin-left: 2em; }";
+ if ( $has_block_gap_support ) {
+ $style .= "$selector > * { margin-top: 0; margin-bottom: 0; }";
+ $style .= "$selector > * + * { margin-top: var( --wp--style--block-gap ); margin-bottom: 0; }";
+ }
+ } elseif ( 'flex' === $layout_type ) {
+ $layout_orientation = isset( $layout['orientation'] ) ? $layout['orientation'] : 'horizontal';
+
+ $justify_content_options = array(
+ 'left' => 'flex-start',
+ 'right' => 'flex-end',
+ 'center' => 'center',
+ );
+
+ if ( 'horizontal' === $layout_orientation ) {
+ $justify_content_options += array( 'space-between' => 'space-between' );
+ }
+
+ $flex_wrap_options = array( 'wrap', 'nowrap' );
+ $flex_wrap = ! empty( $layout['flexWrap'] ) && in_array( $layout['flexWrap'], $flex_wrap_options, true ) ?
+ $layout['flexWrap'] :
+ 'wrap';
+
+ $style = "$selector {";
+ $style .= 'display: flex;';
+ if ( $has_block_gap_support ) {
+ $style .= 'gap: var( --wp--style--block-gap, 0.5em );';
+ } else {
+ $style .= 'gap: 0.5em;';
+ }
+ $style .= "flex-wrap: $flex_wrap;";
+ $style .= 'align-items: center;';
+ if ( 'horizontal' === $layout_orientation ) {
+ $style .= 'align-items: center;';
+ /**
+ * 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 ( ! empty( $layout['justifyContent'] ) && array_key_exists( $layout['justifyContent'], $justify_content_options ) ) {
+ $style .= "justify-content: {$justify_content_options[ $layout['justifyContent'] ]};";
+ // --justification-setting allows children to inherit the value regardless or row or column direction.
+ $style .= "--justification-setting: {$justify_content_options[ $layout['justifyContent'] ]};";
+ }
+ } else {
+ $style .= 'flex-direction: column;';
+ if ( ! empty( $layout['justifyContent'] ) && array_key_exists( $layout['justifyContent'], $justify_content_options ) ) {
+ $style .= "align-items: {$justify_content_options[ $layout['justifyContent'] ]};";
+ $style .= "--justification-setting: {$justify_content_options[ $layout['justifyContent'] ]};";
+ }
+ }
+ $style .= '}';
+
+ $style .= "$selector > * { margin: 0; }";
+ }
+
+ return $style;
+}
+
/**
* Renders the layout config to the block wrapper.
*
@@ -42,47 +142,25 @@ function wp_register_layout_support( $block_type ) {
function wp_render_layout_support_flag( $block_content, $block ) {
$block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] );
$support_layout = block_has_support( $block_type, array( '__experimentalLayout' ), false );
- if ( ! $support_layout || ! isset( $block['attrs']['layout'] ) ) {
+
+ if ( ! $support_layout ) {
return $block_content;
}
- $used_layout = $block['attrs']['layout'];
+ $block_gap = wp_get_global_settings( array( 'spacing', 'blockGap' ) );
+ $default_layout = wp_get_global_settings( array( 'layout' ) );
+ $has_block_gap_support = isset( $block_gap ) ? null !== $block_gap : false;
+ $default_block_layout = _wp_array_get( $block_type->supports, array( '__experimentalLayout', 'default' ), array() );
+ $used_layout = isset( $block['attrs']['layout'] ) ? $block['attrs']['layout'] : $default_block_layout;
if ( isset( $used_layout['inherit'] ) && $used_layout['inherit'] ) {
- $tree = WP_Theme_JSON_Resolver::get_merged_data();
- $default_layout = _wp_array_get( $tree->get_settings(), array( 'layout' ) );
if ( ! $default_layout ) {
return $block_content;
}
$used_layout = $default_layout;
}
- $id = uniqid();
- $content_size = isset( $used_layout['contentSize'] ) ? $used_layout['contentSize'] : null;
- $wide_size = isset( $used_layout['wideSize'] ) ? $used_layout['wideSize'] : null;
-
- $all_max_width_value = $content_size ? $content_size : $wide_size;
- $wide_max_width_value = $wide_size ? $wide_size : $content_size;
-
- // 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] );
-
- $style = '';
- if ( $content_size || $wide_size ) {
- $style = ".wp-container-$id > * {";
- $style .= 'max-width: ' . esc_html( $all_max_width_value ) . ';';
- $style .= 'margin-left: auto !important;';
- $style .= 'margin-right: auto !important;';
- $style .= '}';
-
- $style .= ".wp-container-$id > .alignwide { max-width: " . esc_html( $wide_max_width_value ) . ';}';
-
- $style .= ".wp-container-$id .alignfull { max-width: none; }";
- }
-
- $style .= ".wp-container-$id .alignleft { float: left; margin-right: 2em; }";
- $style .= ".wp-container-$id .alignright { float: right; margin-left: 2em; }";
-
+ $id = uniqid();
+ $style = wp_get_layout_style( ".wp-container-$id", $used_layout, $has_block_gap_support );
// This assumes the hook only applies to blocks with a single wrapper.
// I think this is a reasonable limitation for that particular hook.
$content = preg_replace(
@@ -92,7 +170,19 @@ function wp_render_layout_support_flag( $block_content, $block ) {
1
);
- return $content . '';
+ /*
+ * Ideally styles should be loaded in the head, but blocks may be parsed
+ * after that, so loading in the footer for now.
+ * See https://core.trac.wordpress.org/ticket/53494.
+ */
+ add_action(
+ 'wp_footer',
+ static function () use ( $style ) {
+ echo '';
+ }
+ );
+
+ return $content;
}
// Register the block support.
@@ -123,7 +213,8 @@ function wp_restore_group_inner_container( $block_content, $block ) {
if (
'core/group' !== $block['blockName'] ||
WP_Theme_JSON_Resolver::theme_has_support() ||
- 1 === preg_match( $group_with_inner_container_regex, $block_content )
+ 1 === preg_match( $group_with_inner_container_regex, $block_content ) ||
+ ( isset( $block['attrs']['layout']['type'] ) && 'default' !== $block['attrs']['layout']['type'] )
) {
return $block_content;
}
diff --git a/src/wp-includes/block-supports/spacing.php b/src/wp-includes/block-supports/spacing.php
index 69c1b77f20950..8d93d4e1e98c3 100644
--- a/src/wp-includes/block-supports/spacing.php
+++ b/src/wp-includes/block-supports/spacing.php
@@ -1,6 +1,9 @@
$value ) {
$styles[] = sprintf( 'padding-%s: %s;', $key, $value );
}
+ } elseif ( null !== $padding_value ) {
+ $styles[] = sprintf( 'padding: %s;', $padding_value );
}
}
if ( $has_margin_support ) {
$margin_value = _wp_array_get( $block_attributes, array( 'style', 'spacing', 'margin' ), null );
- if ( null !== $margin_value ) {
+ if ( is_array( $margin_value ) ) {
foreach ( $margin_value as $key => $value ) {
$styles[] = sprintf( 'margin-%s: %s;', $key, $value );
}
+ } elseif ( null !== $margin_value ) {
+ $styles[] = sprintf( 'margin: %s;', $margin_value );
}
}
@@ -68,20 +79,75 @@ function wp_apply_spacing_support( $block_type, $block_attributes ) {
}
/**
- * Checks whether the current block type supports the spacing feature requested.
+ * Checks whether serialization of the current block's spacing properties should
+ * occur.
*
- * @since 5.8.0
+ * @since 5.9.0
* @access private
*
- * @param WP_Block_Type $block_type Block type to check for support.
- * @param string $feature Name of the feature to check support for.
- * @param mixed $default Fallback value for feature support. Default false.
- * @return bool Whether the feature is supported.
+ * @param WP_Block_Type $block_type Block type.
+ *
+ * @return boolean Whether to serialize spacing support styles & classes.
*/
-function wp_has_spacing_feature_support( $block_type, $feature, $default = false ) {
- // Check if the specific feature has been opted into individually
- // via nested flag under `spacing`.
- return block_has_support( $block_type, array( 'spacing', $feature ), $default );
+function wp_skip_spacing_serialization( $block_type ) {
+ $spacing_support = _wp_array_get( $block_type->supports, array( 'spacing' ), false );
+
+ return is_array( $spacing_support ) &&
+ array_key_exists( '__experimentalSkipSerialization', $spacing_support ) &&
+ $spacing_support['__experimentalSkipSerialization'];
+}
+
+/**
+ * Renders the spacing gap support to the block wrapper, to ensure
+ * that the CSS variable is rendered in all environments.
+ *
+ * @since 5.9.0
+ * @access private
+ *
+ * @param string $block_content Rendered block content.
+ * @param array $block Block object.
+ * @return string Filtered block content.
+ */
+function wp_render_spacing_gap_support( $block_content, $block ) {
+ $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] );
+ $has_gap_support = block_has_support( $block_type, array( 'spacing', 'blockGap' ), false );
+ if ( ! $has_gap_support || ! isset( $block['attrs']['style']['spacing']['blockGap'] ) ) {
+ return $block_content;
+ }
+
+ $gap_value = $block['attrs']['style']['spacing']['blockGap'];
+
+ // Skip if gap value contains unsupported characters.
+ // Regex for CSS value borrowed from `safecss_filter_attr`, and used here
+ // because we only want to match against the value, not the CSS attribute.
+ if ( preg_match( '%[\\\(&=}]|/\*%', $gap_value ) ) {
+ return $block_content;
+ }
+
+ $style = sprintf(
+ '--wp--style--block-gap: %s',
+ esc_attr( $gap_value )
+ );
+
+ // Attempt to update an existing style attribute on the wrapper element.
+ $injected_style = preg_replace(
+ '/^([^>.]+?)(' . preg_quote( 'style="', '/' ) . ')(?=.+?>)/',
+ '$1$2' . $style . '; ',
+ $block_content,
+ 1
+ );
+
+ // If there is no existing style attribute, add one to the wrapper element.
+ if ( $injected_style === $block_content ) {
+ $injected_style = preg_replace(
+ '/<([a-zA-Z0-9]+)([ >])/',
+ '<$1 style="' . $style . '"$2',
+ $block_content,
+ 1
+ );
+ };
+
+ return $injected_style;
}
// Register the block support.
@@ -92,3 +158,5 @@ function wp_has_spacing_feature_support( $block_type, $feature, $default = false
'apply' => 'wp_apply_spacing_support',
)
);
+
+add_filter( 'render_block', 'wp_render_spacing_gap_support', 10, 2 );
diff --git a/src/wp-includes/block-supports/typography.php b/src/wp-includes/block-supports/typography.php
index 2d9f69e773f5d..82ee29af676df 100644
--- a/src/wp-includes/block-supports/typography.php
+++ b/src/wp-includes/block-supports/typography.php
@@ -28,6 +28,7 @@ function wp_register_typography_support( $block_type ) {
$has_font_size_support = _wp_array_get( $typography_supports, array( 'fontSize' ), false );
$has_font_style_support = _wp_array_get( $typography_supports, array( '__experimentalFontStyle' ), false );
$has_font_weight_support = _wp_array_get( $typography_supports, array( '__experimentalFontWeight' ), false );
+ $has_letter_spacing_support = _wp_array_get( $typography_supports, array( '__experimentalLetterSpacing' ), false );
$has_line_height_support = _wp_array_get( $typography_supports, array( 'lineHeight' ), false );
$has_text_decoration_support = _wp_array_get( $typography_supports, array( '__experimentalTextDecoration' ), false );
$has_text_transform_support = _wp_array_get( $typography_supports, array( '__experimentalTextTransform' ), false );
@@ -36,6 +37,7 @@ function wp_register_typography_support( $block_type ) {
|| $has_font_size_support
|| $has_font_style_support
|| $has_font_weight_support
+ || $has_letter_spacing_support
|| $has_line_height_support
|| $has_text_decoration_support
|| $has_text_transform_support;
@@ -93,6 +95,7 @@ function wp_apply_typography_support( $block_type, $block_attributes ) {
$has_font_size_support = _wp_array_get( $typography_supports, array( 'fontSize' ), false );
$has_font_style_support = _wp_array_get( $typography_supports, array( '__experimentalFontStyle' ), false );
$has_font_weight_support = _wp_array_get( $typography_supports, array( '__experimentalFontWeight' ), false );
+ $has_letter_spacing_support = _wp_array_get( $typography_supports, array( '__experimentalLetterSpacing' ), false );
$has_line_height_support = _wp_array_get( $typography_supports, array( 'lineHeight' ), false );
$has_text_decoration_support = _wp_array_get( $typography_supports, array( '__experimentalTextDecoration' ), false );
$has_text_transform_support = _wp_array_get( $typography_supports, array( '__experimentalTextTransform' ), false );
@@ -102,24 +105,28 @@ function wp_apply_typography_support( $block_type, $block_attributes ) {
$has_custom_font_size = isset( $block_attributes['style']['typography']['fontSize'] );
if ( $has_named_font_size ) {
- $classes[] = sprintf( 'has-%s-font-size', $block_attributes['fontSize'] );
+ $classes[] = sprintf( 'has-%s-font-size', _wp_to_kebab_case( $block_attributes['fontSize'] ) );
} elseif ( $has_custom_font_size ) {
$styles[] = sprintf( 'font-size: %s;', $block_attributes['style']['typography']['fontSize'] );
}
}
if ( $has_font_family_support ) {
- $has_font_family = isset( $block_attributes['style']['typography']['fontFamily'] );
- if ( $has_font_family ) {
- $font_family = $block_attributes['style']['typography']['fontFamily'];
- if ( strpos( $font_family, 'var:preset|font-family' ) !== false ) {
- // Get the name from the string and add proper styles.
- $index_to_splice = strrpos( $font_family, '|' ) + 1;
- $font_family_name = substr( $font_family, $index_to_splice );
- $styles[] = sprintf( 'font-family: var(--wp--preset--font-family--%s);', $font_family_name );
- } else {
- $styles[] = sprintf( 'font-family: %s;', $block_attributes['style']['typography']['fontFamily'] );
+ $has_named_font_family = array_key_exists( 'fontFamily', $block_attributes );
+ $has_custom_font_family = isset( $block_attributes['style']['typography']['fontFamily'] );
+
+ if ( $has_named_font_family ) {
+ $classes[] = sprintf( 'has-%s-font-family', _wp_to_kebab_case( $block_attributes['fontFamily'] ) );
+ } elseif ( $has_custom_font_family ) {
+ // Before using classes, the value was serialized as a CSS Custom Property.
+ // We don't need this code path when it lands in core.
+ $font_family_custom = $block_attributes['style']['typography']['fontFamily'];
+ if ( strpos( $font_family_custom, 'var:preset|font-family' ) !== false ) {
+ $index_to_splice = strrpos( $font_family_custom, '|' ) + 1;
+ $font_family_slug = _wp_to_kebab_case( substr( $font_family_custom, $index_to_splice ) );
+ $font_family_custom = sprintf( 'var(--wp--preset--font-family--%s)', $font_family_slug );
}
+ $styles[] = sprintf( 'font-family: %s;', $font_family_custom );
}
}
@@ -158,6 +165,13 @@ function wp_apply_typography_support( $block_type, $block_attributes ) {
}
}
+ if ( $has_letter_spacing_support ) {
+ $letter_spacing_style = wp_typography_get_css_variable_inline_style( $block_attributes, 'letterSpacing', 'letter-spacing' );
+ if ( $letter_spacing_style ) {
+ $styles[] = $letter_spacing_style;
+ }
+ }
+
if ( ! empty( $classes ) ) {
$attributes['class'] = implode( ' ', $classes );
}
diff --git a/src/wp-includes/blocks.php b/src/wp-includes/blocks.php
index f94a60d921502..f5b8b47e5ef20 100644
--- a/src/wp-includes/blocks.php
+++ b/src/wp-includes/blocks.php
@@ -1182,3 +1182,117 @@ function get_query_pagination_arrow( $block, $is_next ) {
}
return null;
}
+
+/**
+ * Enqueue a stylesheet for a specific block.
+ *
+ * If the theme has opted-in to separate-styles loading,
+ * then the stylesheet will be enqueued on-render,
+ * otherwise when the block inits.
+ *
+ * @param string $block_name The block-name, including namespace.
+ * @param array $args An array of arguments [handle,src,deps,ver,media].
+ *
+ * @return void
+ */
+function wp_enqueue_block_style( $block_name, $args ) {
+ $args = wp_parse_args(
+ $args,
+ array(
+ 'handle' => '',
+ 'src' => '',
+ 'deps' => array(),
+ 'ver' => false,
+ 'media' => 'all',
+ )
+ );
+
+ /**
+ * Callback function to register and enqueue styles.
+ *
+ * @param string $content When the callback is used for the render_block filter,
+ * the content needs to be returned so the function parameter
+ * is to ensure the content exists.
+ *
+ * @return string
+ */
+ $callback = static function( $content ) use ( $args ) {
+ // Register the stylesheet.
+ if ( ! empty( $args['src'] ) ) {
+ wp_register_style( $args['handle'], $args['src'], $args['deps'], $args['ver'], $args['media'] );
+ }
+
+ // Add `path` data if provided.
+ if ( isset( $args['path'] ) ) {
+ wp_style_add_data( $args['handle'], 'path', $args['path'] );
+
+ // Get the RTL file path.
+ $rtl_file_path = str_replace( '.css', '-rtl.css', $args['path'] );
+
+ // Add RTL stylesheet.
+ if ( file_exists( $rtl_file_path ) ) {
+ wp_style_add_data( $args['hanle'], 'rtl', 'replace' );
+
+ if ( is_rtl() ) {
+ wp_style_add_data( $args['handle'], 'path', $rtl_file_path );
+ }
+ }
+ }
+
+ // Enqueue the stylesheet.
+ wp_enqueue_style( $args['handle'] );
+
+ return $content;
+ };
+
+ $hook = did_action( 'wp_enqueue_scripts' ) ? 'wp_footer' : 'wp_enqueue_scripts';
+ if ( wp_should_load_separate_core_block_assets() ) {
+ $hook = "render_block_$block_name";
+ }
+
+ /*
+ * The filter's callback here is an anonymous function because
+ * using a named function in this case is not possible.
+ *
+ * The function cannot be unhooked, however, users are still able
+ * to dequeue the stylesheets registered/enqueued by the callback
+ * which is why in this case, using an anonymous function
+ * was deemed acceptable.
+ */
+ add_filter( $hook, $callback );
+
+ // Enqueue assets in the editor.
+ add_action( 'enqueue_block_assets', $callback );
+}
+
+/**
+ * Allow multiple block styles.
+ *
+ * @param array $metadata Metadata for registering a block type.
+ *
+ * @return array
+ */
+function _wp_multiple_block_styles( $metadata ) {
+ foreach ( array( 'style', 'editorStyle' ) as $key ) {
+ if ( ! empty( $metadata[ $key ] ) && is_array( $metadata[ $key ] ) ) {
+ $default_style = array_shift( $metadata[ $key ] );
+ foreach ( $metadata[ $key ] as $handle ) {
+ $args = array( 'handle' => $handle );
+ if ( 0 === strpos( $handle, 'file:' ) && isset( $metadata['file'] ) ) {
+ $style_path = remove_block_asset_path_prefix( $handle );
+ $args = array(
+ 'handle' => sanitize_key( "{$metadata['name']}-{$style_path}" ),
+ 'src' => plugins_url( $style_path, $metadata['file'] ),
+ );
+ }
+
+ wp_enqueue_block_style( $metadata['name'], $args );
+ }
+
+ // Only return the 1st item in the array.
+ $metadata[ $key ] = $default_style;
+ }
+ }
+ return $metadata;
+}
+add_filter( 'block_type_metadata', '_wp_multiple_block_styles' );
diff --git a/src/wp-includes/blocks/index.php b/src/wp-includes/blocks/index.php
index d3aa3f6d7dde4..3ff63f5738dbe 100644
--- a/src/wp-includes/blocks/index.php
+++ b/src/wp-includes/blocks/index.php
@@ -15,29 +15,38 @@
require ABSPATH . WPINC . '/blocks/latest-posts.php';
require ABSPATH . WPINC . '/blocks/legacy-widget.php';
require ABSPATH . WPINC . '/blocks/loginout.php';
+require ABSPATH . WPINC . '/blocks/navigation-area.php';
+require ABSPATH . WPINC . '/blocks/navigation-link.php';
+require ABSPATH . WPINC . '/blocks/navigation-submenu.php';
+require ABSPATH . WPINC . '/blocks/navigation.php';
require ABSPATH . WPINC . '/blocks/page-list.php';
+require ABSPATH . WPINC . '/blocks/pattern.php';
+require ABSPATH . WPINC . '/blocks/post-author.php';
+require ABSPATH . WPINC . '/blocks/post-comments.php';
require ABSPATH . WPINC . '/blocks/post-content.php';
require ABSPATH . WPINC . '/blocks/post-date.php';
require ABSPATH . WPINC . '/blocks/post-excerpt.php';
require ABSPATH . WPINC . '/blocks/post-featured-image.php';
+require ABSPATH . WPINC . '/blocks/post-navigation-link.php';
+require ABSPATH . WPINC . '/blocks/post-template.php';
require ABSPATH . WPINC . '/blocks/post-terms.php';
require ABSPATH . WPINC . '/blocks/post-title.php';
-require ABSPATH . WPINC . '/blocks/post-template.php';
-require ABSPATH . WPINC . '/blocks/query.php';
-require ABSPATH . WPINC . '/blocks/query-pagination.php';
require ABSPATH . WPINC . '/blocks/query-pagination-next.php';
require ABSPATH . WPINC . '/blocks/query-pagination-numbers.php';
require ABSPATH . WPINC . '/blocks/query-pagination-previous.php';
+require ABSPATH . WPINC . '/blocks/query-pagination.php';
require ABSPATH . WPINC . '/blocks/query-title.php';
+require ABSPATH . WPINC . '/blocks/query.php';
require ABSPATH . WPINC . '/blocks/rss.php';
require ABSPATH . WPINC . '/blocks/search.php';
require ABSPATH . WPINC . '/blocks/shortcode.php';
-require ABSPATH . WPINC . '/blocks/site-tagline.php';
require ABSPATH . WPINC . '/blocks/site-logo.php';
+require ABSPATH . WPINC . '/blocks/site-tagline.php';
require ABSPATH . WPINC . '/blocks/site-title.php';
require ABSPATH . WPINC . '/blocks/social-link.php';
require ABSPATH . WPINC . '/blocks/tag-cloud.php';
require ABSPATH . WPINC . '/blocks/template-part.php';
+require ABSPATH . WPINC . '/blocks/term-description.php';
/**
* Registers core block types using metadata files.
diff --git a/src/wp-includes/blocks/navigation-area.php b/src/wp-includes/blocks/navigation-area.php
new file mode 100644
index 0000000000000..76a2748361c47
--- /dev/null
+++ b/src/wp-includes/blocks/navigation-area.php
@@ -0,0 +1,21 @@
+ array(
+ 'navigationArea' => 'area',
+ ),
+ )
+ );
+}
+add_action( 'init', 'register_block_core_navigation_area' );
diff --git a/src/wp-includes/blocks/navigation-area/block.json b/src/wp-includes/blocks/navigation-area/block.json
new file mode 100644
index 0000000000000..95ad0a513e65b
--- /dev/null
+++ b/src/wp-includes/blocks/navigation-area/block.json
@@ -0,0 +1,27 @@
+{
+ "apiVersion": 2,
+ "name": "core/navigation-area",
+ "title": "Navigation Area",
+ "category": "theme",
+ "description": "Define a navigation area for your theme. The navigation block associated with this area will be automatically displayed.",
+ "keywords": [
+ "menu",
+ "navigation",
+ "links",
+ "location"
+ ],
+ "textdomain": "default",
+ "attributes": {
+ "area": {
+ "type": "string",
+ "default": "primary"
+ }
+ },
+ "providesContext": {
+ "navigationArea": "area"
+ },
+ "supports": {
+ "html": false,
+ "inserter": true
+ }
+}
diff --git a/src/wp-includes/blocks/navigation-link.php b/src/wp-includes/blocks/navigation-link.php
new file mode 100644
index 0000000000000..2f7620ad34f98
--- /dev/null
+++ b/src/wp-includes/blocks/navigation-link.php
@@ -0,0 +1,350 @@
+ array(),
+ 'inline_styles' => '',
+ );
+
+ $is_sub_menu = isset( $attributes['isTopLevelLink'] ) ? ( ! $attributes['isTopLevelLink'] ) : false;
+
+ // Text color.
+ $named_text_color = null;
+ $custom_text_color = null;
+
+ if ( $is_sub_menu && array_key_exists( 'customOverlayTextColor', $context ) ) {
+ $custom_text_color = $context['customOverlayTextColor'];
+ } elseif ( $is_sub_menu && array_key_exists( 'overlayTextColor', $context ) ) {
+ $named_text_color = $context['overlayTextColor'];
+ } elseif ( array_key_exists( 'customTextColor', $context ) ) {
+ $custom_text_color = $context['customTextColor'];
+ } elseif ( array_key_exists( 'textColor', $context ) ) {
+ $named_text_color = $context['textColor'];
+ } elseif ( isset( $context['style']['color']['text'] ) ) {
+ $custom_text_color = $context['style']['color']['text'];
+ }
+
+ // If has text color.
+ if ( ! is_null( $named_text_color ) ) {
+ // Add the color class.
+ array_push( $colors['css_classes'], 'has-text-color', sprintf( 'has-%s-color', $named_text_color ) );
+ } elseif ( ! is_null( $custom_text_color ) ) {
+ // Add the custom color inline style.
+ $colors['css_classes'][] = 'has-text-color';
+ $colors['inline_styles'] .= sprintf( 'color: %s;', $custom_text_color );
+ }
+
+ // Background color.
+ $named_background_color = null;
+ $custom_background_color = null;
+
+ if ( $is_sub_menu && array_key_exists( 'customOverlayBackgroundColor', $context ) ) {
+ $custom_background_color = $context['customOverlayBackgroundColor'];
+ } elseif ( $is_sub_menu && array_key_exists( 'overlayBackgroundColor', $context ) ) {
+ $named_background_color = $context['overlayBackgroundColor'];
+ } elseif ( array_key_exists( 'customBackgroundColor', $context ) ) {
+ $custom_background_color = $context['customBackgroundColor'];
+ } elseif ( array_key_exists( 'backgroundColor', $context ) ) {
+ $named_background_color = $context['backgroundColor'];
+ } elseif ( isset( $context['style']['color']['background'] ) ) {
+ $custom_background_color = $context['style']['color']['background'];
+ }
+
+ // If has background color.
+ if ( ! is_null( $named_background_color ) ) {
+ // Add the background-color class.
+ array_push( $colors['css_classes'], 'has-background', sprintf( 'has-%s-background-color', $named_background_color ) );
+ } elseif ( ! is_null( $custom_background_color ) ) {
+ // Add the custom background-color inline style.
+ $colors['css_classes'][] = 'has-background';
+ $colors['inline_styles'] .= sprintf( 'background-color: %s;', $custom_background_color );
+ }
+
+ return $colors;
+}
+
+/**
+ * Build an array with CSS classes and inline styles defining the font sizes
+ * which will be applied to the navigation markup in the front-end.
+ *
+ * @param array $context Navigation block context.
+ * @return array Font size CSS classes and inline styles.
+ */
+function block_core_navigation_link_build_css_font_sizes( $context ) {
+ // CSS classes.
+ $font_sizes = array(
+ 'css_classes' => array(),
+ 'inline_styles' => '',
+ );
+
+ $has_named_font_size = array_key_exists( 'fontSize', $context );
+ $has_custom_font_size = isset( $context['style']['typography']['fontSize'] );
+
+ if ( $has_named_font_size ) {
+ // Add the font size class.
+ $font_sizes['css_classes'][] = sprintf( 'has-%s-font-size', $context['fontSize'] );
+ } elseif ( $has_custom_font_size ) {
+ // Add the custom font size inline style.
+ $font_sizes['inline_styles'] = sprintf( 'font-size: %spx;', $context['style']['typography']['fontSize'] );
+ }
+
+ return $font_sizes;
+}
+
+/**
+ * Returns the top-level submenu SVG chevron icon.
+ *
+ * @return string
+ */
+function block_core_navigation_link_render_submenu_icon() {
+ return '
';
+}
+
+/**
+ * Renders the `core/navigation-link` block.
+ *
+ * @param array $attributes The block attributes.
+ * @param array $content The saved content.
+ * @param array $block The parsed block.
+ *
+ * @return string Returns the post content with the legacy widget added.
+ */
+function render_block_core_navigation_link( $attributes, $content, $block ) {
+ $navigation_link_has_id = isset( $attributes['id'] ) && is_numeric( $attributes['id'] );
+ $is_post_type = isset( $attributes['kind'] ) && 'post-type' === $attributes['kind'];
+ $is_post_type = $is_post_type || isset( $attributes['type'] ) && ( 'post' === $attributes['type'] || 'page' === $attributes['type'] );
+
+ // Don't render the block's subtree if it is a draft or if the ID does not exist.
+ if ( $is_post_type && $navigation_link_has_id ) {
+ $post = get_post( $attributes['id'] );
+ if ( ! $post || 'publish' !== $post->post_status ) {
+ return '';
+ }
+ }
+
+ // Don't render the block's subtree if it has no label.
+ if ( empty( $attributes['label'] ) ) {
+ return '';
+ }
+
+ $colors = block_core_navigation_link_build_css_colors( $block->context, $attributes );
+ $font_sizes = block_core_navigation_link_build_css_font_sizes( $block->context );
+ $classes = array_merge(
+ $colors['css_classes'],
+ $font_sizes['css_classes']
+ );
+ $style_attribute = ( $colors['inline_styles'] . $font_sizes['inline_styles'] );
+
+ $css_classes = trim( implode( ' ', $classes ) );
+ $has_submenu = count( $block->inner_blocks ) > 0;
+ $is_active = ! empty( $attributes['id'] ) && ( get_the_ID() === $attributes['id'] );
+
+ $wrapper_attributes = get_block_wrapper_attributes(
+ array(
+ 'class' => $css_classes . ' wp-block-navigation-item' . ( $has_submenu ? ' has-child' : '' ) .
+ ( $is_active ? ' current-menu-item' : '' ),
+ 'style' => $style_attribute,
+ )
+ );
+ $html = '
' .
+ '';
+
+ if ( isset( $attributes['label'] ) ) {
+ $html .= wp_kses(
+ $attributes['label'],
+ array(
+ 'code' => array(),
+ 'em' => array(),
+ 'img' => array(
+ 'scale' => array(),
+ 'class' => array(),
+ 'style' => array(),
+ 'src' => array(),
+ 'alt' => array(),
+ ),
+ 's' => array(),
+ 'span' => array(
+ 'style' => array(),
+ ),
+ 'strong' => array(),
+ )
+ );
+ }
+
+ $html .= '';
+
+ if ( isset( $block->context['showSubmenuIcon'] ) && $block->context['showSubmenuIcon'] && $has_submenu ) {
+ // The submenu icon can be hidden by a CSS rule on the Navigation Block.
+ $html .= '';
+ }
+
+ $html .= ' ';
+ // End anchor tag content.
+
+ if ( $has_submenu ) {
+ $inner_blocks_html = '';
+ foreach ( $block->inner_blocks as $inner_block ) {
+ $inner_blocks_html .= $inner_block->render();
+ }
+
+ $html .= sprintf(
+ '',
+ $inner_blocks_html
+ );
+ }
+
+ $html .= ' ';
+
+ return $html;
+}
+
+/**
+ * Returns a navigation link variation
+ *
+ * @param WP_Taxonomy|WP_Post_Type $entity post type or taxonomy entity.
+ * @param string $kind string of value 'taxonomy' or 'post-type'.
+ *
+ * @return array
+ */
+function build_variation_for_navigation_link( $entity, $kind ) {
+ $title = '';
+ $description = '';
+
+ if ( property_exists( $entity->labels, 'item_link' ) ) {
+ $title = $entity->labels->item_link;
+ }
+ if ( property_exists( $entity->labels, 'item_link_description' ) ) {
+ $description = $entity->labels->item_link_description;
+ }
+
+ $variation = array(
+ 'name' => $entity->name,
+ 'title' => $title,
+ 'description' => $description,
+ 'attributes' => array(
+ 'type' => $entity->name,
+ 'kind' => $kind,
+ ),
+ );
+
+ // Tweak some value for the variations.
+ $variation_overrides = array(
+ 'post_tag' => array(
+ 'name' => 'tag',
+ 'attributes' => array(
+ 'type' => 'tag',
+ 'kind' => $kind,
+ ),
+ ),
+ 'post_format' => array(
+ // The item_link and item_link_description for post formats is the
+ // same as for tags, so need to be overridden.
+ 'title' => __( 'Post Format Link' ),
+ 'description' => __( 'A link to a post format' ),
+ 'attributes' => array(
+ 'type' => 'post_format',
+ 'kind' => $kind,
+ ),
+ ),
+ );
+
+ if ( array_key_exists( $entity->name, $variation_overrides ) ) {
+ $variation = array_merge(
+ $variation,
+ $variation_overrides[ $entity->name ]
+ );
+ }
+
+ return $variation;
+}
+
+/**
+ * Register the navigation link block.
+ *
+ * @uses render_block_core_navigation()
+ * @throws WP_Error An WP_Error exception parsing the block definition.
+ */
+function register_block_core_navigation_link() {
+ $post_types = get_post_types( array( 'show_in_nav_menus' => true ), 'objects' );
+ $taxonomies = get_taxonomies( array( 'show_in_nav_menus' => true ), 'objects' );
+
+ // Use two separate arrays as a way to order the variations in the UI.
+ // Known variations (like Post Link and Page Link) are added to the
+ // `built_ins` array. Variations for custom post types and taxonomies are
+ // added to the `variations` array and will always appear after `built-ins.
+ $built_ins = array();
+ $variations = array();
+
+ if ( $post_types ) {
+ foreach ( $post_types as $post_type ) {
+ $variation = build_variation_for_navigation_link( $post_type, 'post-type' );
+ if ( $post_type->_builtin ) {
+ $built_ins[] = $variation;
+ } else {
+ $variations[] = $variation;
+ }
+ }
+ }
+ if ( $taxonomies ) {
+ foreach ( $taxonomies as $taxonomy ) {
+ $variation = build_variation_for_navigation_link( $taxonomy, 'taxonomy' );
+ if ( $taxonomy->_builtin ) {
+ $built_ins[] = $variation;
+ } else {
+ $variations[] = $variation;
+ }
+ }
+ }
+
+ register_block_type_from_metadata(
+ __DIR__ . '/navigation-link',
+ array(
+ 'render_callback' => 'render_block_core_navigation_link',
+ 'variations' => array_merge( $built_ins, $variations ),
+ )
+ );
+}
+add_action( 'init', 'register_block_core_navigation_link' );
diff --git a/src/wp-includes/blocks/navigation-link/block.json b/src/wp-includes/blocks/navigation-link/block.json
new file mode 100644
index 0000000000000..eac8af0cdec69
--- /dev/null
+++ b/src/wp-includes/blocks/navigation-link/block.json
@@ -0,0 +1,65 @@
+{
+ "apiVersion": 2,
+ "name": "core/navigation-link",
+ "title": "Custom Link",
+ "category": "design",
+ "parent": [
+ "core/navigation"
+ ],
+ "description": "Add a page, link, or another item to your navigation.",
+ "textdomain": "default",
+ "attributes": {
+ "label": {
+ "type": "string"
+ },
+ "type": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "rel": {
+ "type": "string"
+ },
+ "id": {
+ "type": "number"
+ },
+ "opensInNewTab": {
+ "type": "boolean",
+ "default": false
+ },
+ "url": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "kind": {
+ "type": "string"
+ },
+ "isTopLevelLink": {
+ "type": "boolean"
+ }
+ },
+ "usesContext": [
+ "textColor",
+ "customTextColor",
+ "backgroundColor",
+ "customBackgroundColor",
+ "overlayTextColor",
+ "customOverlayTextColor",
+ "overlayBackgroundColor",
+ "customOverlayBackgroundColor",
+ "fontSize",
+ "customFontSize",
+ "showSubmenuIcon",
+ "style"
+ ],
+ "supports": {
+ "reusable": false,
+ "html": false,
+ "__experimentalSlashInserter": true
+ },
+ "editorStyle": "wp-block-navigation-link-editor",
+ "style": "wp-block-navigation-link"
+}
diff --git a/src/wp-includes/blocks/navigation-submenu.php b/src/wp-includes/blocks/navigation-submenu.php
new file mode 100644
index 0000000000000..cdb26b93b1b50
--- /dev/null
+++ b/src/wp-includes/blocks/navigation-submenu.php
@@ -0,0 +1,300 @@
+ array(),
+ 'inline_styles' => '',
+ );
+
+ $is_sub_menu = isset( $attributes['isTopLevelLink'] ) ? ( ! $attributes['isTopLevelLink'] ) : false;
+
+ // Text color.
+ $named_text_color = null;
+ $custom_text_color = null;
+
+ if ( $is_sub_menu && array_key_exists( 'customOverlayTextColor', $context ) ) {
+ $custom_text_color = $context['customOverlayTextColor'];
+ } elseif ( $is_sub_menu && array_key_exists( 'overlayTextColor', $context ) ) {
+ $named_text_color = $context['overlayTextColor'];
+ } elseif ( array_key_exists( 'customTextColor', $context ) ) {
+ $custom_text_color = $context['customTextColor'];
+ } elseif ( array_key_exists( 'textColor', $context ) ) {
+ $named_text_color = $context['textColor'];
+ } elseif ( isset( $context['style']['color']['text'] ) ) {
+ $custom_text_color = $context['style']['color']['text'];
+ }
+
+ // If has text color.
+ if ( ! is_null( $named_text_color ) ) {
+ // Add the color class.
+ array_push( $colors['css_classes'], 'has-text-color', sprintf( 'has-%s-color', $named_text_color ) );
+ } elseif ( ! is_null( $custom_text_color ) ) {
+ // Add the custom color inline style.
+ $colors['css_classes'][] = 'has-text-color';
+ $colors['inline_styles'] .= sprintf( 'color: %s;', $custom_text_color );
+ }
+
+ // Background color.
+ $named_background_color = null;
+ $custom_background_color = null;
+
+ if ( $is_sub_menu && array_key_exists( 'customOverlayBackgroundColor', $context ) ) {
+ $custom_background_color = $context['customOverlayBackgroundColor'];
+ } elseif ( $is_sub_menu && array_key_exists( 'overlayBackgroundColor', $context ) ) {
+ $named_background_color = $context['overlayBackgroundColor'];
+ } elseif ( array_key_exists( 'customBackgroundColor', $context ) ) {
+ $custom_background_color = $context['customBackgroundColor'];
+ } elseif ( array_key_exists( 'backgroundColor', $context ) ) {
+ $named_background_color = $context['backgroundColor'];
+ } elseif ( isset( $context['style']['color']['background'] ) ) {
+ $custom_background_color = $context['style']['color']['background'];
+ }
+
+ // If has background color.
+ if ( ! is_null( $named_background_color ) ) {
+ // Add the background-color class.
+ array_push( $colors['css_classes'], 'has-background', sprintf( 'has-%s-background-color', $named_background_color ) );
+ } elseif ( ! is_null( $custom_background_color ) ) {
+ // Add the custom background-color inline style.
+ $colors['css_classes'][] = 'has-background';
+ $colors['inline_styles'] .= sprintf( 'background-color: %s;', $custom_background_color );
+ }
+
+ return $colors;
+}
+
+/**
+ * Build an array with CSS classes and inline styles defining the font sizes
+ * which will be applied to the navigation markup in the front-end.
+ *
+ * @param array $context Navigation block context.
+ * @return array Font size CSS classes and inline styles.
+ */
+function block_core_navigation_submenu_build_css_font_sizes( $context ) {
+ // CSS classes.
+ $font_sizes = array(
+ 'css_classes' => array(),
+ 'inline_styles' => '',
+ );
+
+ $has_named_font_size = array_key_exists( 'fontSize', $context );
+ $has_custom_font_size = isset( $context['style']['typography']['fontSize'] );
+
+ if ( $has_named_font_size ) {
+ // Add the font size class.
+ $font_sizes['css_classes'][] = sprintf( 'has-%s-font-size', $context['fontSize'] );
+ } elseif ( $has_custom_font_size ) {
+ // Add the custom font size inline style.
+ $font_sizes['inline_styles'] = sprintf( 'font-size: %spx;', $context['style']['typography']['fontSize'] );
+ }
+
+ return $font_sizes;
+}
+
+/**
+ * Returns the top-level submenu SVG chevron icon.
+ *
+ * @return string
+ */
+function block_core_navigation_submenu_render_submenu_icon() {
+ return '
';
+}
+
+/**
+ * Renders the `core/navigation-submenu` block.
+ *
+ * @param array $attributes The block attributes.
+ * @param string $content The saved content.
+ * @param object $block The parsed block.
+ *
+ * @return string Returns the post content with the legacy widget added.
+ */
+function render_block_core_navigation_submenu( $attributes, $content, $block ) {
+
+ $navigation_link_has_id = isset( $attributes['id'] ) && is_numeric( $attributes['id'] );
+ $is_post_type = isset( $attributes['kind'] ) && 'post-type' === $attributes['kind'];
+ $is_post_type = $is_post_type || isset( $attributes['type'] ) && ( 'post' === $attributes['type'] || 'page' === $attributes['type'] );
+
+ // Don't render the block's subtree if it is a draft.
+ if ( $is_post_type && $navigation_link_has_id && 'publish' !== get_post_status( $attributes['id'] ) ) {
+ return '';
+ }
+
+ // Don't render the block's subtree if it has no label.
+ if ( empty( $attributes['label'] ) ) {
+ return '';
+ }
+
+ $colors = block_core_navigation_submenu_build_css_colors( $block->context, $attributes );
+ $font_sizes = block_core_navigation_submenu_build_css_font_sizes( $block->context );
+ $classes = array_merge(
+ $colors['css_classes'],
+ $font_sizes['css_classes']
+ );
+ $style_attribute = ( $colors['inline_styles'] . $font_sizes['inline_styles'] );
+
+ $css_classes = trim( implode( ' ', $classes ) );
+ $has_submenu = count( $block->inner_blocks ) > 0;
+ $is_active = ! empty( $attributes['id'] ) && ( get_the_ID() === $attributes['id'] );
+
+ $class_name = ! empty( $attributes['className'] ) ? implode( ' ', (array) $attributes['className'] ) : false;
+
+ if ( false !== $class_name ) {
+ $css_classes .= ' ' . $class_name;
+ }
+
+ $show_submenu_indicators = isset( $block->context['showSubmenuIcon'] ) && $block->context['showSubmenuIcon'];
+ $open_on_click = isset( $block->context['openSubmenusOnClick'] ) && $block->context['openSubmenusOnClick'];
+ $open_on_hover_and_click = isset( $block->context['openSubmenusOnClick'] ) && ! $block->context['openSubmenusOnClick'] &&
+ $show_submenu_indicators;
+
+ $wrapper_attributes = get_block_wrapper_attributes(
+ array(
+ 'class' => $css_classes . ' wp-block-navigation-item' . ( $has_submenu ? ' has-child' : '' ) .
+ ( $open_on_click ? ' open-on-click' : '' ) . ( $open_on_hover_and_click ? ' open-on-hover-click' : '' ) .
+ ( $is_active ? ' current-menu-item' : '' ),
+ 'style' => $style_attribute,
+ )
+ );
+ $html = '
';
+
+ // If Submenus open on hover, we render an anchor tag with attributes.
+ // If submenu icons are set to show, we also render a submenu button, so the submenu can be opened on click.
+ if ( ! $open_on_click ) {
+ $item_url = isset( $attributes['url'] ) ? esc_url( $attributes['url'] ) : '';
+ // Start appending HTML attributes to anchor tag.
+ $html .= ' array(),
+ 'em' => array(),
+ 'img' => array(
+ 'scale' => array(),
+ 'class' => array(),
+ 'style' => array(),
+ 'src' => array(),
+ 'alt' => array(),
+ ),
+ 's' => array(),
+ 'span' => array(
+ 'style' => array(),
+ ),
+ 'strong' => array(),
+ )
+ );
+ }
+
+ $html .= ' ';
+ // End anchor tag content.
+
+ if ( $show_submenu_indicators ) {
+ // The submenu icon is rendered in a button here
+ // so that there's a clickable elment to open the submenu.
+ $html .= '';
+ }
+ } else {
+ // If menus open on click, we render the parent as a button.
+ $html .= '';
+
+ }
+
+ if ( $has_submenu ) {
+ $inner_blocks_html = '';
+ foreach ( $block->inner_blocks as $inner_block ) {
+ $inner_blocks_html .= $inner_block->render();
+ }
+
+ $html .= sprintf(
+ '',
+ $inner_blocks_html
+ );
+ }
+
+ $html .= ' ';
+
+ return $html;
+}
+
+/**
+ * Register the navigation submenu block.
+ *
+ * @uses render_block_core_navigation_submenu()
+ * @throws WP_Error An WP_Error exception parsing the block definition.
+ */
+function register_block_core_navigation_submenu() {
+ register_block_type_from_metadata(
+ __DIR__ . '/navigation-submenu',
+ array(
+ 'render_callback' => 'render_block_core_navigation_submenu',
+ )
+ );
+}
+add_action( 'init', 'register_block_core_navigation_submenu' );
diff --git a/src/wp-includes/blocks/navigation-submenu/block.json b/src/wp-includes/blocks/navigation-submenu/block.json
new file mode 100644
index 0000000000000..db0791485a8cb
--- /dev/null
+++ b/src/wp-includes/blocks/navigation-submenu/block.json
@@ -0,0 +1,65 @@
+{
+ "apiVersion": 2,
+ "name": "core/navigation-submenu",
+ "title": "Submenu",
+ "category": "design",
+ "parent": [
+ "core/navigation"
+ ],
+ "description": "Add a submenu to your navigation.",
+ "textdomain": "default",
+ "attributes": {
+ "label": {
+ "type": "string"
+ },
+ "type": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "rel": {
+ "type": "string"
+ },
+ "id": {
+ "type": "number"
+ },
+ "opensInNewTab": {
+ "type": "boolean",
+ "default": false
+ },
+ "url": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "kind": {
+ "type": "string"
+ },
+ "isTopLevelItem": {
+ "type": "boolean"
+ }
+ },
+ "usesContext": [
+ "textColor",
+ "customTextColor",
+ "backgroundColor",
+ "customBackgroundColor",
+ "overlayTextColor",
+ "customOverlayTextColor",
+ "overlayBackgroundColor",
+ "customOverlayBackgroundColor",
+ "fontSize",
+ "customFontSize",
+ "showSubmenuIcon",
+ "openSubmenusOnClick",
+ "style"
+ ],
+ "supports": {
+ "reusable": false,
+ "html": false
+ },
+ "editorStyle": "wp-block-navigation-submenu-editor",
+ "style": "wp-block-navigation-submenu"
+}
diff --git a/src/wp-includes/blocks/navigation.php b/src/wp-includes/blocks/navigation.php
new file mode 100644
index 0000000000000..6f52c41c71d7d
--- /dev/null
+++ b/src/wp-includes/blocks/navigation.php
@@ -0,0 +1,429 @@
+ array(),
+ 'inline_styles' => '',
+ );
+
+ // Text color.
+ $has_named_text_color = array_key_exists( 'textColor', $attributes );
+ $has_custom_text_color = array_key_exists( 'customTextColor', $attributes );
+
+ // If has text color.
+ if ( $has_custom_text_color || $has_named_text_color ) {
+ // Add has-text-color class.
+ $colors['css_classes'][] = 'has-text-color';
+ }
+
+ if ( $has_named_text_color ) {
+ // Add the color class.
+ $colors['css_classes'][] = sprintf( 'has-%s-color', $attributes['textColor'] );
+ } elseif ( $has_custom_text_color ) {
+ // Add the custom color inline style.
+ $colors['inline_styles'] .= sprintf( 'color: %s;', $attributes['customTextColor'] );
+ }
+
+ // Background color.
+ $has_named_background_color = array_key_exists( 'backgroundColor', $attributes );
+ $has_custom_background_color = array_key_exists( 'customBackgroundColor', $attributes );
+
+ // If has background color.
+ if ( $has_custom_background_color || $has_named_background_color ) {
+ // Add has-background class.
+ $colors['css_classes'][] = 'has-background';
+ }
+
+ if ( $has_named_background_color ) {
+ // Add the background-color class.
+ $colors['css_classes'][] = sprintf( 'has-%s-background-color', $attributes['backgroundColor'] );
+ } elseif ( $has_custom_background_color ) {
+ // Add the custom background-color inline style.
+ $colors['inline_styles'] .= sprintf( 'background-color: %s;', $attributes['customBackgroundColor'] );
+ }
+
+ return $colors;
+}
+
+/**
+ * Build an array with CSS classes and inline styles defining the font sizes
+ * which will be applied to the navigation markup in the front-end.
+ *
+ * @param array $attributes Navigation block attributes.
+ * @return array Font size CSS classes and inline styles.
+ */
+function block_core_navigation_build_css_font_sizes( $attributes ) {
+ // CSS classes.
+ $font_sizes = array(
+ 'css_classes' => array(),
+ 'inline_styles' => '',
+ );
+
+ $has_named_font_size = array_key_exists( 'fontSize', $attributes );
+ $has_custom_font_size = array_key_exists( 'customFontSize', $attributes );
+
+ if ( $has_named_font_size ) {
+ // Add the font size class.
+ $font_sizes['css_classes'][] = sprintf( 'has-%s-font-size', $attributes['fontSize'] );
+ } elseif ( $has_custom_font_size ) {
+ // Add the custom font size inline style.
+ $font_sizes['inline_styles'] = sprintf( 'font-size: %spx;', $attributes['customFontSize'] );
+ }
+
+ return $font_sizes;
+}
+
+/**
+ * Returns the menu items for a WordPress menu location.
+ *
+ * @param string $location The menu location.
+ * @return array Menu items for the location.
+ */
+function gutenberg_get_menu_items_at_location( $location ) {
+ if ( empty( $location ) ) {
+ return;
+ }
+
+ // Build menu data. The following approximates the code in
+ // `wp_nav_menu()` and `gutenberg_output_block_nav_menu`.
+
+ // Find the location in the list of locations, returning early if the
+ // location can't be found.
+ $locations = get_nav_menu_locations();
+ if ( ! isset( $locations[ $location ] ) ) {
+ return;
+ }
+
+ // Get the menu from the location, returning early if there is no
+ // menu or there was an error.
+ $menu = wp_get_nav_menu_object( $locations[ $location ] );
+ if ( ! $menu || is_wp_error( $menu ) ) {
+ return;
+ }
+
+ $menu_items = wp_get_nav_menu_items( $menu->term_id, array( 'update_post_term_cache' => false ) );
+ _wp_menu_item_classes_by_context( $menu_items );
+
+ return $menu_items;
+}
+
+/**
+ * Sorts a standard array of menu items into a nested structure keyed by the
+ * id of the parent menu.
+ *
+ * @param array $menu_items Menu items to sort.
+ * @return array An array keyed by the id of the parent menu where each element
+ * is an array of menu items that belong to that parent.
+ */
+function gutenberg_sort_menu_items_by_parent_id( $menu_items ) {
+ $sorted_menu_items = array();
+ foreach ( (array) $menu_items as $menu_item ) {
+ $sorted_menu_items[ $menu_item->menu_order ] = $menu_item;
+ }
+ unset( $menu_items, $menu_item );
+
+ $menu_items_by_parent_id = array();
+ foreach ( $sorted_menu_items as $menu_item ) {
+ $menu_items_by_parent_id[ $menu_item->menu_item_parent ][] = $menu_item;
+ }
+
+ return $menu_items_by_parent_id;
+}
+
+/**
+ * Turns menu item data into a nested array of parsed blocks
+ *
+ * @param array $menu_items An array of menu items that represent
+ * an individual level of a menu.
+ * @param array $menu_items_by_parent_id An array keyed by the id of the
+ * parent menu where each element is an
+ * array of menu items that belong to
+ * that parent.
+ * @return array An array of parsed block data.
+ */
+function gutenberg_parse_blocks_from_menu_items( $menu_items, $menu_items_by_parent_id ) {
+ if ( empty( $menu_items ) ) {
+ return array();
+ }
+
+ $blocks = array();
+
+ foreach ( $menu_items as $menu_item ) {
+ $class_name = ! empty( $menu_item->classes ) ? implode( ' ', (array) $menu_item->classes ) : null;
+ $id = ( null !== $menu_item->object_id && 'custom' !== $menu_item->object ) ? $menu_item->object_id : null;
+ $opens_in_new_tab = null !== $menu_item->target && '_blank' === $menu_item->target;
+ $rel = ( null !== $menu_item->xfn && '' !== $menu_item->xfn ) ? $menu_item->xfn : null;
+ $kind = null !== $menu_item->type ? str_replace( '_', '-', $menu_item->type ) : 'custom';
+
+ $block = array(
+ 'blockName' => 'core/navigation-link',
+ 'attrs' => array(
+ 'className' => $class_name,
+ 'description' => $menu_item->description,
+ 'id' => $id,
+ 'kind' => $kind,
+ 'label' => $menu_item->title,
+ 'opensInNewTab' => $opens_in_new_tab,
+ 'rel' => $rel,
+ 'title' => $menu_item->attr_title,
+ 'type' => $menu_item->object,
+ 'url' => $menu_item->url,
+ ),
+ );
+
+ $block['innerBlocks'] = gutenberg_parse_blocks_from_menu_items(
+ isset( $menu_items_by_parent_id[ $menu_item->ID ] )
+ ? $menu_items_by_parent_id[ $menu_item->ID ]
+ : array(),
+ $menu_items_by_parent_id
+ );
+
+ $blocks[] = $block;
+ }
+
+ return $blocks;
+}
+
+/**
+ * Returns the top-level submenu SVG chevron icon.
+ *
+ * @return string
+ */
+function block_core_navigation_render_submenu_icon() {
+ return '
';
+}
+
+/**
+ * Renders the `core/navigation` block on server.
+ *
+ * @param array $attributes The block attributes.
+ * @param array $content The saved content.
+ * @param array $block The parsed block.
+ *
+ * @return string Returns the post content with the legacy widget added.
+ */
+function render_block_core_navigation( $attributes, $content, $block ) {
+ /**
+ * Deprecated:
+ * The rgbTextColor and rgbBackgroundColor attributes
+ * have been deprecated in favor of
+ * customTextColor and customBackgroundColor ones.
+ * Move the values from old attrs to the new ones.
+ */
+ if ( isset( $attributes['rgbTextColor'] ) && empty( $attributes['textColor'] ) ) {
+ $attributes['customTextColor'] = $attributes['rgbTextColor'];
+ }
+
+ if ( isset( $attributes['rgbBackgroundColor'] ) && empty( $attributes['backgroundColor'] ) ) {
+ $attributes['customBackgroundColor'] = $attributes['rgbBackgroundColor'];
+ }
+
+ unset( $attributes['rgbTextColor'], $attributes['rgbBackgroundColor'] );
+
+ /**
+ * This is for backwards compatibility after `isResponsive` attribute has been removed.
+ */
+ $has_old_responsive_attribute = ! empty( $attributes['isResponsive'] ) && $attributes['isResponsive'];
+ $is_responsive_menu = isset( $attributes['overlayMenu'] ) && 'never' !== $attributes['overlayMenu'] || $has_old_responsive_attribute;
+ $should_load_view_script = ! wp_script_is( 'wp-block-navigation-view' ) && ( $is_responsive_menu || $attributes['openSubmenusOnClick'] || $attributes['showSubmenuIcon'] );
+ if ( $should_load_view_script ) {
+ wp_enqueue_script( 'wp-block-navigation-view' );
+ }
+
+ $inner_blocks = $block->inner_blocks;
+
+ // If `__unstableLocation` is defined, create inner blocks from the classic menu assigned to that location.
+ if ( empty( $inner_blocks ) && array_key_exists( '__unstableLocation', $attributes ) ) {
+ $menu_items = gutenberg_get_menu_items_at_location( $attributes['__unstableLocation'] );
+ if ( empty( $menu_items ) ) {
+ return '';
+ }
+
+ $menu_items_by_parent_id = gutenberg_sort_menu_items_by_parent_id( $menu_items );
+ $parsed_blocks = gutenberg_parse_blocks_from_menu_items( $menu_items_by_parent_id[0], $menu_items_by_parent_id );
+ $inner_blocks = new WP_Block_List( $parsed_blocks, $attributes );
+ }
+
+ if ( ! empty( $block->context['navigationArea'] ) ) {
+ $area = $block->context['navigationArea'];
+ $mapping = get_option( 'fse_navigation_areas', array() );
+ if ( ! empty( $mapping[ $area ] ) ) {
+ $attributes['navigationMenuId'] = $mapping[ $area ];
+ }
+ }
+
+ // Load inner blocks from the navigation post.
+ if ( array_key_exists( 'navigationMenuId', $attributes ) ) {
+ $navigation_post = get_post( $attributes['navigationMenuId'] );
+ if ( ! isset( $navigation_post ) ) {
+ return '';
+ }
+
+ $parsed_blocks = parse_blocks( $navigation_post->post_content );
+
+ // 'parse_blocks' includes a null block with '\n\n' as the content when
+ // it encounters whitespace. This code strips it.
+ $compacted_blocks = array_filter(
+ $parsed_blocks,
+ function( $block ) {
+ return isset( $block['blockName'] );
+ }
+ );
+
+ // TODO - this uses the full navigation block attributes for the
+ // context which could be refined.
+ $inner_blocks = new WP_Block_List( $compacted_blocks, $attributes );
+ }
+
+ if ( empty( $inner_blocks ) ) {
+ return '';
+ }
+ $colors = block_core_navigation_build_css_colors( $attributes );
+ $font_sizes = block_core_navigation_build_css_font_sizes( $attributes );
+ $classes = array_merge(
+ $colors['css_classes'],
+ $font_sizes['css_classes'],
+ $is_responsive_menu ? array( 'is-responsive' ) : array()
+ );
+
+ $inner_blocks_html = '';
+ $is_list_open = false;
+ foreach ( $inner_blocks as $inner_block ) {
+ if ( ( 'core/navigation-link' === $inner_block->name || 'core/home-link' === $inner_block->name || 'core/site-title' === $inner_block->name || 'core/site-logo' === $inner_block->name || 'core/navigation-submenu' === $inner_block->name ) && ! $is_list_open ) {
+ $is_list_open = true;
+ $inner_blocks_html .= '
';
+ }
+ if ( 'core/navigation-link' !== $inner_block->name && 'core/home-link' !== $inner_block->name && 'core/site-title' !== $inner_block->name && 'core/site-logo' !== $inner_block->name && 'core/navigation-submenu' !== $inner_block->name && $is_list_open ) {
+ $is_list_open = false;
+ $inner_blocks_html .= ' ';
+ }
+ if ( 'core/site-title' === $inner_block->name || 'core/site-logo' === $inner_block->name ) {
+ $inner_blocks_html .= '
' . $inner_block->render() . ' ';
+ } else {
+ $inner_blocks_html .= $inner_block->render();
+ }
+ }
+
+ if ( $is_list_open ) {
+ $inner_blocks_html .= '';
+ }
+
+ $block_styles = isset( $attributes['styles'] ) ? $attributes['styles'] : '';
+
+ $wrapper_attributes = get_block_wrapper_attributes(
+ array(
+ 'class' => implode( ' ', $classes ),
+ 'style' => $block_styles . $colors['inline_styles'] . $font_sizes['inline_styles'],
+ )
+ );
+
+ $modal_unique_id = uniqid();
+
+ // Determine whether or not navigation elements should be wrapped in the markup required to make it responsive,
+ // return early if they don't.
+ if ( ! $is_responsive_menu ) {
+ return sprintf(
+ '
%2$s ',
+ $wrapper_attributes,
+ $inner_blocks_html
+ );
+ }
+
+ $is_hidden_by_default = isset( $attributes['overlayMenu'] ) && 'always' === $attributes['overlayMenu'];
+
+ $responsive_container_classes = array(
+ 'wp-block-navigation__responsive-container',
+ $is_hidden_by_default ? 'hidden-by-default' : '',
+ );
+ $open_button_classes = array(
+ 'wp-block-navigation__responsive-container-open',
+ $is_hidden_by_default ? 'always-shown' : '',
+ );
+
+ $responsive_container_markup = sprintf(
+ '
+
',
+ $modal_unique_id,
+ $inner_blocks_html,
+ __( 'Open menu' ), // Open button label.
+ __( 'Close menu' ), // Close button label.
+ implode( ' ', $responsive_container_classes ),
+ implode( ' ', $open_button_classes )
+ );
+
+ return sprintf(
+ '
%2$s ',
+ $wrapper_attributes,
+ $responsive_container_markup
+ );
+}
+
+/**
+ * Register the navigation block.
+ *
+ * @uses render_block_core_navigation()
+ * @throws WP_Error An WP_Error exception parsing the block definition.
+ */
+function register_block_core_navigation() {
+ register_block_type_from_metadata(
+ __DIR__ . '/navigation',
+ array(
+ 'render_callback' => 'render_block_core_navigation',
+ )
+ );
+}
+
+add_action( 'init', 'register_block_core_navigation' );
+
+/**
+ * Filter that changes the parsed attribute values of navigation blocks contain typographic presets to contain the values directly.
+ *
+ * @param array $parsed_block The block being rendered.
+ * @return array The block being rendered without typographic presets.
+ */
+function block_core_navigation_typographic_presets_backcompatibility( $parsed_block ) {
+ if ( 'core/navigation' === $parsed_block['blockName'] ) {
+ $attribute_to_prefix_map = array(
+ 'fontStyle' => 'var:preset|font-style|',
+ 'fontWeight' => 'var:preset|font-weight|',
+ 'textDecoration' => 'var:preset|text-decoration|',
+ 'textTransform' => 'var:preset|text-transform|',
+ );
+ foreach ( $attribute_to_prefix_map as $style_attribute => $prefix ) {
+ if ( ! empty( $parsed_block['attrs']['style']['typography'][ $style_attribute ] ) ) {
+ $prefix_len = strlen( $prefix );
+ $attribute_value = &$parsed_block['attrs']['style']['typography'][ $style_attribute ];
+ if ( 0 === strncmp( $attribute_value, $prefix, $prefix_len ) ) {
+ $attribute_value = substr( $attribute_value, $prefix_len );
+ }
+ if ( 'textDecoration' === $style_attribute && 'strikethrough' === $attribute_value ) {
+ $attribute_value = 'line-through';
+ }
+ }
+ }
+ }
+ return $parsed_block;
+}
+
+add_filter( 'render_block_data', 'block_core_navigation_typographic_presets_backcompatibility' );
diff --git a/src/wp-includes/blocks/navigation/block.json b/src/wp-includes/blocks/navigation/block.json
new file mode 100644
index 0000000000000..931010815030e
--- /dev/null
+++ b/src/wp-includes/blocks/navigation/block.json
@@ -0,0 +1,124 @@
+{
+ "apiVersion": 2,
+ "name": "core/navigation",
+ "title": "Navigation",
+ "category": "theme",
+ "description": "A collection of blocks that allow visitors to get around your site.",
+ "keywords": [
+ "menu",
+ "navigation",
+ "links"
+ ],
+ "textdomain": "default",
+ "attributes": {
+ "navigationMenuId": {
+ "type": "number"
+ },
+ "textColor": {
+ "type": "string"
+ },
+ "customTextColor": {
+ "type": "string"
+ },
+ "rgbTextColor": {
+ "type": "string"
+ },
+ "backgroundColor": {
+ "type": "string"
+ },
+ "customBackgroundColor": {
+ "type": "string"
+ },
+ "rgbBackgroundColor": {
+ "type": "string"
+ },
+ "showSubmenuIcon": {
+ "type": "boolean",
+ "default": true
+ },
+ "openSubmenusOnClick": {
+ "type": "boolean",
+ "default": false
+ },
+ "overlayMenu": {
+ "type": "string",
+ "default": "mobile"
+ },
+ "__unstableLocation": {
+ "type": "string"
+ },
+ "overlayBackgroundColor": {
+ "type": "string"
+ },
+ "customOverlayBackgroundColor": {
+ "type": "string"
+ },
+ "overlayTextColor": {
+ "type": "string"
+ },
+ "customOverlayTextColor": {
+ "type": "string"
+ }
+ },
+ "usesContext": [ "navigationArea" ],
+ "providesContext": {
+ "textColor": "textColor",
+ "customTextColor": "customTextColor",
+ "backgroundColor": "backgroundColor",
+ "customBackgroundColor": "customBackgroundColor",
+ "overlayTextColor": "overlayTextColor",
+ "customOverlayTextColor": "customOverlayTextColor",
+ "overlayBackgroundColor": "overlayBackgroundColor",
+ "customOverlayBackgroundColor": "customOverlayBackgroundColor",
+ "fontSize": "fontSize",
+ "customFontSize": "customFontSize",
+ "showSubmenuIcon": "showSubmenuIcon",
+ "openSubmenusOnClick": "openSubmenusOnClick",
+ "style": "style",
+ "orientation": "orientation"
+ },
+ "supports": {
+ "align": [
+ "wide",
+ "full"
+ ],
+ "anchor": true,
+ "html": false,
+ "inserter": true,
+ "typography": {
+ "fontSize": true,
+ "lineHeight": true,
+ "__experimentalFontStyle": true,
+ "__experimentalFontWeight": true,
+ "__experimentalTextTransform": true,
+ "__experimentalFontFamily": true,
+ "__experimentalTextDecoration": true,
+ "__experimentalDefaultControls": {
+ "fontSize": true
+ }
+ },
+ "spacing": {
+ "blockGap": true,
+ "units": [
+ "px",
+ "em",
+ "rem",
+ "vh",
+ "vw"
+ ],
+ "__experimentalDefaultControls": {
+ "blockGap": true
+ }
+ },
+ "__experimentalLayout": {
+ "allowSwitching": false,
+ "allowInheriting": false,
+ "default": {
+ "type": "flex"
+ }
+ }
+ },
+ "viewScript": "file:./view.min.js",
+ "editorStyle": "wp-block-navigation-editor",
+ "style": "wp-block-navigation"
+}
diff --git a/src/wp-includes/blocks/navigation/view.asset.php b/src/wp-includes/blocks/navigation/view.asset.php
new file mode 100644
index 0000000000000..d28c9db95b5b3
--- /dev/null
+++ b/src/wp-includes/blocks/navigation/view.asset.php
@@ -0,0 +1 @@
+ array(), 'version' => '86538493346805d860c94eb70dd1323d');
\ No newline at end of file
diff --git a/src/wp-includes/blocks/navigation/view.min.asset.php b/src/wp-includes/blocks/navigation/view.min.asset.php
new file mode 100644
index 0000000000000..444bff68672db
--- /dev/null
+++ b/src/wp-includes/blocks/navigation/view.min.asset.php
@@ -0,0 +1 @@
+ array(), 'version' => '7b2c5174a07c417dc3db6f1d9a9c3f78');
\ No newline at end of file
diff --git a/src/wp-includes/blocks/pattern.php b/src/wp-includes/blocks/pattern.php
new file mode 100644
index 0000000000000..32a08601ca808
--- /dev/null
+++ b/src/wp-includes/blocks/pattern.php
@@ -0,0 +1,44 @@
+ 'render_block_core_pattern',
+ )
+ );
+}
+
+/**
+ * Renders the `core/pattern` block on the server.
+ *
+ * @param array $attributes Block attributes.
+ *
+ * @return string Returns the output of the pattern.
+ */
+function render_block_core_pattern( $attributes ) {
+ if ( empty( $attributes['slug'] ) ) {
+ return '';
+ }
+
+ $slug = $attributes['slug'];
+ $registry = WP_Block_Patterns_Registry::get_instance();
+ if ( ! $registry->is_registered( $slug ) ) {
+ return '';
+ }
+
+ $pattern = $registry->get_registered( $slug );
+ return do_blocks( $pattern['content'] );
+}
+
+add_action( 'init', 'register_block_core_pattern' );
diff --git a/src/wp-includes/blocks/pattern/block.json b/src/wp-includes/blocks/pattern/block.json
new file mode 100644
index 0000000000000..78423e8d49123
--- /dev/null
+++ b/src/wp-includes/blocks/pattern/block.json
@@ -0,0 +1,17 @@
+{
+ "apiVersion": 2,
+ "name": "core/pattern",
+ "title": "Pattern",
+ "category": "design",
+ "description": "Show a block pattern.",
+ "supports": {
+ "html": false,
+ "inserter": false
+ },
+ "textdomain": "default",
+ "attributes": {
+ "slug": {
+ "type": "string"
+ }
+ }
+}
diff --git a/src/wp-includes/blocks/post-author.php b/src/wp-includes/blocks/post-author.php
new file mode 100644
index 0000000000000..e31be65f70994
--- /dev/null
+++ b/src/wp-includes/blocks/post-author.php
@@ -0,0 +1,61 @@
+context['postId'] ) ) {
+ return '';
+ }
+
+ $author_id = get_post_field( 'post_author', $block->context['postId'] );
+ if ( empty( $author_id ) ) {
+ return '';
+ }
+
+ $avatar = ! empty( $attributes['avatarSize'] ) ? get_avatar(
+ $author_id,
+ $attributes['avatarSize']
+ ) : null;
+
+ $byline = ! empty( $attributes['byline'] ) ? $attributes['byline'] : false;
+ $classes = array_merge(
+ isset( $attributes['className'] ) ? array( $attributes['className'] ) : array(),
+ isset( $attributes['itemsJustification'] ) ? array( 'items-justified-' . $attributes['itemsJustification'] ) : array(),
+ isset( $attributes['textAlign'] ) ? array( 'has-text-align-' . $attributes['textAlign'] ) : array()
+ );
+
+ $wrapper_attributes = get_block_wrapper_attributes( array( 'class' => implode( ' ', $classes ) ) );
+
+ return sprintf( '
', $wrapper_attributes ) .
+ ( ! empty( $attributes['showAvatar'] ) ? '
' . $avatar . '
' : '' ) .
+ '
' .
+ ( ! empty( $byline ) ? '
' . $byline . '
' : '' ) .
+ '
' . get_the_author_meta( 'display_name', $author_id ) . '
' .
+ ( ! empty( $attributes['showBio'] ) ? '
' . get_the_author_meta( 'user_description', $author_id ) . '
' : '' ) .
+ '
' .
+ '
';
+}
+
+/**
+ * Registers the `core/post-author` block on the server.
+ */
+function register_block_core_post_author() {
+ register_block_type_from_metadata(
+ __DIR__ . '/post-author',
+ array(
+ 'render_callback' => 'render_block_core_post_author',
+ )
+ );
+}
+add_action( 'init', 'register_block_core_post_author' );
diff --git a/src/wp-includes/blocks/post-author/block.json b/src/wp-includes/blocks/post-author/block.json
new file mode 100644
index 0000000000000..d46c8df69da29
--- /dev/null
+++ b/src/wp-includes/blocks/post-author/block.json
@@ -0,0 +1,53 @@
+{
+ "apiVersion": 2,
+ "name": "core/post-author",
+ "title": "Post Author",
+ "category": "theme",
+ "description": "Add the author of this post.",
+ "textdomain": "default",
+ "attributes": {
+ "textAlign": {
+ "type": "string"
+ },
+ "avatarSize": {
+ "type": "number",
+ "default": 48
+ },
+ "showAvatar": {
+ "type": "boolean",
+ "default": true
+ },
+ "showBio": {
+ "type": "boolean"
+ },
+ "byline": {
+ "type": "string"
+ }
+ },
+ "usesContext": [ "postType", "postId", "queryId" ],
+ "supports": {
+ "html": false,
+ "spacing": {
+ "margin": true,
+ "padding": true
+ },
+ "typography": {
+ "fontSize": true,
+ "lineHeight": true,
+ "__experimentalFontStyle": true,
+ "__experimentalFontWeight": true,
+ "__experimentalLetterSpacing": true,
+ "__experimentalTextTransform": true,
+ "__experimentalDefaultControls": {
+ "fontSize": true
+ }
+ },
+ "color": {
+ "gradients": true,
+ "link": true,
+ "__experimentalDuotone": ".wp-block-post-author__avatar img"
+ }
+ },
+ "editorStyle": "wp-block-post-author-editor",
+ "style": "wp-block-post-author"
+}
diff --git a/src/wp-includes/blocks/post-comments.php b/src/wp-includes/blocks/post-comments.php
new file mode 100644
index 0000000000000..42f9a8d60d94e
--- /dev/null
+++ b/src/wp-includes/blocks/post-comments.php
@@ -0,0 +1,68 @@
+context['postId'];
+ if ( ! isset( $post_id ) ) {
+ return '';
+ }
+
+ $comment_args = array(
+ 'post_id' => $post_id,
+ 'count' => true,
+ );
+ // Return early if there are no comments and comments are closed.
+ if ( ! comments_open( $post_id ) && get_comments( $comment_args ) === 0 ) {
+ return '';
+ }
+
+ $post_before = $post;
+ $post = get_post( $post_id );
+ setup_postdata( $post );
+
+ ob_start();
+ // There's a deprecation warning generated by WP Core.
+ // Ideally this deprecation is removed from Core.
+ // In the meantime, this removes it from the output.
+ add_filter( 'deprecated_file_trigger_error', '__return_false' );
+ comments_template();
+ remove_filter( 'deprecated_file_trigger_error', '__return_false' );
+ $post = $post_before;
+
+ $classes = '';
+ if ( isset( $attributes['textAlign'] ) ) {
+ $classes .= 'has-text-align-' . $attributes['textAlign'];
+ }
+
+ $wrapper_attributes = get_block_wrapper_attributes( array( 'class' => $classes ) );
+ $output = ob_get_clean();
+
+ return sprintf( '
%2$s
', $wrapper_attributes, $output );
+}
+
+/**
+ * Registers the `core/post-comments` block on the server.
+ */
+function register_block_core_post_comments() {
+ register_block_type_from_metadata(
+ __DIR__ . '/post-comments',
+ array(
+ 'render_callback' => 'render_block_core_post_comments',
+ )
+ );
+}
+add_action( 'init', 'register_block_core_post_comments' );
diff --git a/src/wp-includes/blocks/post-comments/block.json b/src/wp-includes/blocks/post-comments/block.json
new file mode 100644
index 0000000000000..2aab1d09f9902
--- /dev/null
+++ b/src/wp-includes/blocks/post-comments/block.json
@@ -0,0 +1,34 @@
+{
+ "apiVersion": 2,
+ "name": "core/post-comments",
+ "title": "Post Comments",
+ "category": "theme",
+ "description": "Display a post's comments.",
+ "textdomain": "default",
+ "attributes": {
+ "textAlign": {
+ "type": "string"
+ }
+ },
+ "usesContext": [ "postId", "postType" ],
+ "supports": {
+ "html": false,
+ "align": [ "wide", "full" ],
+ "typography": {
+ "fontSize": true,
+ "lineHeight": true,
+ "__experimentalFontStyle": true,
+ "__experimentalFontWeight": true,
+ "__experimentalLetterSpacing": true,
+ "__experimentalTextTransform": true,
+ "__experimentalDefaultControls": {
+ "fontSize": true
+ }
+ },
+ "color": {
+ "gradients": true,
+ "link": true
+ }
+ },
+ "style": "wp-block-post-comments"
+}
diff --git a/src/wp-includes/blocks/post-navigation-link.php b/src/wp-includes/blocks/post-navigation-link.php
new file mode 100644
index 0000000000000..9fedf38d6fa1c
--- /dev/null
+++ b/src/wp-includes/blocks/post-navigation-link.php
@@ -0,0 +1,96 @@
+ $classes ) );
+ // Set default values.
+ $format = '%link';
+ $link = 'next' === $navigation_type ? _x( 'Next', 'label for next post link' ) : _x( 'Previous', 'label for previous post link' );
+ $label = '';
+
+ // If a custom label is provided, make this a link.
+ // `$label` is used to prepend the provided label, if we want to show the page title as well.
+ if ( isset( $attributes['label'] ) && ! empty( $attributes['label'] ) ) {
+ $label = "{$attributes['label']}";
+ $link = $label;
+ }
+
+ // If we want to also show the page title, make the page title a link and prepend the label.
+ if ( isset( $attributes['showTitle'] ) && $attributes['showTitle'] ) {
+ /*
+ * If the label link option is not enabled but there is a custom label,
+ * display the custom label as text before the linked title.
+ */
+ if ( ! $attributes['linkLabel'] ) {
+ if ( $label ) {
+ $format = '
' . $label . ' %link';
+ }
+ $link = '%title';
+ } elseif ( isset( $attributes['linkLabel'] ) && $attributes['linkLabel'] ) {
+ // If the label link option is enabled and there is a custom label, display it before the title.
+ if ( $label ) {
+ $link = '
' . $label . ' %title';
+ } else {
+ /*
+ * If the label link option is enabled and there is no custom label,
+ * add a colon between the label and the post title.
+ */
+ $label = 'next' === $navigation_type ? _x( 'Next:', 'label before the title of the next post' ) : _x( 'Previous:', 'label before the title of the previous post' );
+ $link = sprintf(
+ '%1$s %2$s ',
+ $label,
+ '%title'
+ );
+ }
+ }
+ }
+
+ // The dynamic portion of the function name, `$navigation_type`,
+ // refers to the type of adjacency, 'next' or 'previous'.
+ $get_link_function = "get_{$navigation_type}_post_link";
+ $content = $get_link_function( $format, $link );
+ return sprintf(
+ '%2$s
',
+ $wrapper_attributes,
+ $content
+ );
+}
+
+/**
+ * Registers the `core/post-navigation-link` block on the server.
+ */
+function register_block_core_post_navigation_link() {
+ register_block_type_from_metadata(
+ __DIR__ . '/post-navigation-link',
+ array(
+ 'render_callback' => 'render_block_core_post_navigation_link',
+ )
+ );
+}
+add_action( 'init', 'register_block_core_post_navigation_link' );
diff --git a/src/wp-includes/blocks/post-navigation-link/block.json b/src/wp-includes/blocks/post-navigation-link/block.json
new file mode 100644
index 0000000000000..ec983b6b57a6f
--- /dev/null
+++ b/src/wp-includes/blocks/post-navigation-link/block.json
@@ -0,0 +1,43 @@
+{
+ "apiVersion": 2,
+ "name": "core/post-navigation-link",
+ "title": "Post Navigation Link",
+ "category": "theme",
+ "description": "Displays the next or previous post link that is adjacent to the current post.",
+ "textdomain": "default",
+ "attributes": {
+ "textAlign": {
+ "type": "string"
+ },
+ "type": {
+ "type": "string",
+ "default": "next"
+ },
+ "label": {
+ "type": "string"
+ },
+ "showTitle": {
+ "type": "boolean",
+ "default": false
+ },
+ "linkLabel": {
+ "type": "boolean",
+ "default": false
+ }
+ },
+ "supports": {
+ "reusable": false,
+ "html": false,
+ "typography": {
+ "fontSize": true,
+ "lineHeight": true,
+ "__experimentalFontStyle": true,
+ "__experimentalFontWeight": true,
+ "__experimentalLetterSpacing": true,
+ "__experimentalTextTransform": true,
+ "__experimentalDefaultControls": {
+ "fontSize": true
+ }
+ }
+ }
+}
diff --git a/src/wp-includes/blocks/term-description.php b/src/wp-includes/blocks/term-description.php
new file mode 100644
index 0000000000000..4f1cd7e518d11
--- /dev/null
+++ b/src/wp-includes/blocks/term-description.php
@@ -0,0 +1,45 @@
+ 'has-text-align-' . $attributes['textAlign'] )
+ : array();
+ $wrapper_attributes = get_block_wrapper_attributes( $extra_attributes );
+
+ return '' . $term_description . '
';
+}
+
+/**
+ * Registers the `core/term-description` block on the server.
+ */
+function register_block_core_term_description() {
+ register_block_type_from_metadata(
+ __DIR__ . '/term-description',
+ array(
+ 'render_callback' => 'render_block_core_term_description',
+ )
+ );
+}
+add_action( 'init', 'register_block_core_term_description' );
diff --git a/src/wp-includes/blocks/term-description/block.json b/src/wp-includes/blocks/term-description/block.json
new file mode 100644
index 0000000000000..608ae1a242349
--- /dev/null
+++ b/src/wp-includes/blocks/term-description/block.json
@@ -0,0 +1,28 @@
+{
+ "apiVersion": 2,
+ "name": "core/term-description",
+ "title": "Term Description",
+ "category": "theme",
+ "description": "Display the description of categories, tags and custom taxonomies when viewing an archive.",
+ "textdomain": "default",
+ "attributes": {
+ "textAlign": {
+ "type": "string"
+ }
+ },
+ "supports": {
+ "align": [ "wide", "full" ],
+ "html": false,
+ "color": {
+ "link": true
+ },
+ "typography": {
+ "fontSize": true,
+ "lineHeight": true,
+ "__experimentalDefaultControls": {
+ "fontSize": true
+ }
+ }
+ },
+ "editorStyle": "wp-block-term-description-editor"
+}
diff --git a/src/wp-includes/blocks/widget-group.php b/src/wp-includes/blocks/widget-group.php
new file mode 100644
index 0000000000000..6cf6442346a30
--- /dev/null
+++ b/src/wp-includes/blocks/widget-group.php
@@ -0,0 +1,78 @@
+';
+ $after_title = '';
+ }
+
+ $html = '';
+
+ if ( ! empty( $attributes['title'] ) ) {
+ $html .= $before_title . $attributes['title'] . $after_title;
+ }
+
+ $html .= '';
+ foreach ( $block->inner_blocks as $inner_block ) {
+ $html .= $inner_block->render();
+ }
+ $html .= '
';
+
+ return $html;
+}
+
+/**
+ * Registers the 'core/widget-group' block.
+ */
+function register_block_core_widget_group() {
+ register_block_type_from_metadata(
+ __DIR__ . '/widget-group',
+ array(
+ 'render_callback' => 'render_block_core_widget_group',
+ )
+ );
+}
+
+add_action( 'init', 'register_block_core_widget_group' );
+
+/**
+ * Make a note of the sidebar being rendered before WordPress starts rendering
+ * it. This lets us get to the current sidebar in
+ * render_block_core_widget_group().
+ *
+ * @param int|string $index Index, name, or ID of the dynamic sidebar.
+ */
+function note_sidebar_being_rendered( $index ) {
+ global $_sidebar_being_rendered;
+ $_sidebar_being_rendered = $index;
+}
+add_action( 'dynamic_sidebar_before', 'note_sidebar_being_rendered' );
+
+/**
+ * Clear whatever we set in note_sidebar_being_rendered() after WordPress
+ * finishes rendering a sidebar.
+ */
+function discard_sidebar_being_rendered() {
+ global $_sidebar_being_rendered;
+ unset( $_sidebar_being_rendered );
+}
+add_action( 'dynamic_sidebar_after', 'discard_sidebar_being_rendered' );
diff --git a/src/wp-includes/blocks/widget-group/block.json b/src/wp-includes/blocks/widget-group/block.json
new file mode 100644
index 0000000000000..ec48d90eda5ca
--- /dev/null
+++ b/src/wp-includes/blocks/widget-group/block.json
@@ -0,0 +1,18 @@
+{
+ "apiVersion": 2,
+ "name": "core/widget-group",
+ "category": "widgets",
+ "attributes": {
+ "title": {
+ "type": "string"
+ }
+ },
+ "supports": {
+ "html": false,
+ "inserter": true,
+ "customClassName": true,
+ "reusable": false
+ },
+ "editorStyle": "wp-block-widget-group-editor",
+ "style": "wp-block-widget-group"
+}
diff --git a/src/wp-includes/default-filters.php b/src/wp-includes/default-filters.php
index 5783d5cd0f33d..d67a432637732 100644
--- a/src/wp-includes/default-filters.php
+++ b/src/wp-includes/default-filters.php
@@ -581,6 +581,7 @@
add_action( 'admin_footer-post.php', 'wp_add_iframed_editor_assets_html' );
add_action( 'admin_footer-post-new.php', 'wp_add_iframed_editor_assets_html' );
+add_action( 'admin_footer-widgets.php', 'wp_add_iframed_editor_assets_html' );
// Taxonomy.
add_action( 'init', 'create_initial_taxonomies', 0 ); // Highest priority.
diff --git a/src/wp-includes/kses.php b/src/wp-includes/kses.php
index 90169c54dd0c3..32df2c64060b0 100644
--- a/src/wp-includes/kses.php
+++ b/src/wp-includes/kses.php
@@ -2287,6 +2287,8 @@ function safecss_filter_attr( $css, $deprecated = '' ) {
'border-right-width',
'border-bottom',
'border-bottom-color',
+ 'border-bottom-left-radius',
+ 'border-bottom-right-radius',
'border-bottom-style',
'border-bottom-width',
'border-bottom-right-radius',
@@ -2297,6 +2299,8 @@ function safecss_filter_attr( $css, $deprecated = '' ) {
'border-left-width',
'border-top',
'border-top-color',
+ 'border-top-left-radius',
+ 'border-top-right-radius',
'border-top-style',
'border-top-width',
'border-top-left-radius',
diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php
index 8716e29961397..96ed177773e41 100644
--- a/src/wp-includes/post.php
+++ b/src/wp-includes/post.php
@@ -450,6 +450,51 @@ function create_initial_post_types() {
)
);
+ register_post_type(
+ 'wp_navigation',
+ array(
+ 'labels' => array(
+ 'name' => __( 'Navigation Menus', 'gutenberg' ),
+ 'singular_name' => __( 'Navigation Menu', 'gutenberg' ),
+ 'menu_name' => _x( 'Navigation Menus', 'Admin Menu text', 'gutenberg' ),
+ 'add_new' => _x( 'Add New', 'Navigation Menu', 'gutenberg' ),
+ 'add_new_item' => __( 'Add New Navigation Menu', 'gutenberg' ),
+ 'new_item' => __( 'New Navigation Menu', 'gutenberg' ),
+ 'edit_item' => __( 'Edit Navigation Menu', 'gutenberg' ),
+ 'view_item' => __( 'View Navigation Menu', 'gutenberg' ),
+ 'all_items' => __( 'All Navigation Menus', 'gutenberg' ),
+ 'search_items' => __( 'Search Navigation Menus', 'gutenberg' ),
+ 'parent_item_colon' => __( 'Parent Navigation Menu:', 'gutenberg' ),
+ 'not_found' => __( 'No Navigation Menu found.', 'gutenberg' ),
+ 'not_found_in_trash' => __( 'No Navigation Menu found in Trash.', 'gutenberg' ),
+ 'archives' => __( 'Navigation Menu archives', 'gutenberg' ),
+ 'insert_into_item' => __( 'Insert into Navigation Menu', 'gutenberg' ),
+ 'uploaded_to_this_item' => __( 'Uploaded to this Navigation Menu', 'gutenberg' ),
+ // Some of these are a bit weird, what are they for?
+ 'filter_items_list' => __( 'Filter Navigation Menu list', 'gutenberg' ),
+ 'items_list_navigation' => __( 'Navigation Menus list navigation', 'gutenberg' ),
+ 'items_list' => __( 'Navigation Menus list', 'gutenberg' ),
+ ),
+ 'description' => __( 'Navigation menus.', 'gutenberg' ),
+ 'public' => false,
+ '_builtin' => true, /* internal use only. don't use this when registering your own post type. */
+ 'has_archive' => false,
+ 'show_ui' => false,
+ 'show_in_menu' => 'themes.php',
+ 'show_in_admin_bar' => false,
+ 'show_in_rest' => true,
+ 'rewrite' => false,
+ 'map_meta_cap' => true,
+ 'rest_base' => 'navigation',
+ 'rest_controller_class' => 'WP_REST_Posts_Controller',
+ 'supports' => array(
+ 'title',
+ 'editor',
+ 'revisions',
+ ),
+ )
+ );
+
register_post_status(
'publish',
array(
diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php
index e3e5d935d7a1a..a299580be1d2a 100644
--- a/src/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php
+++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php
@@ -311,6 +311,12 @@ public function get_items( $request ) {
$prepared_args['has_published_posts'] = get_post_types( array( 'show_in_rest' => true ), 'names' );
}
+ if ( ! empty( $request['has_published_posts'] ) ) {
+ $prepared_args['has_published_posts'] = ( true === $request['has_published_posts'] )
+ ? get_post_types( array( 'show_in_rest' => true ), 'names' )
+ : (array) $request['has_published_posts'];
+ }
+
if ( ! empty( $prepared_args['search'] ) ) {
$prepared_args['search'] = '*' . $prepared_args['search'] . '*';
}
@@ -1580,6 +1586,15 @@ public function get_collection_params() {
),
);
+ $query_params['has_published_posts'] = array(
+ 'description' => __( 'Limit result set to users who have published posts.' ),
+ 'type' => array( 'boolean', 'array' ),
+ 'items' => array(
+ 'type' => 'string',
+ 'enum' => get_post_types( array( 'show_in_rest' => true ), 'names' ),
+ ),
+ );
+
/**
* Filters REST API collection parameters for the users controller.
*
diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-widget-types-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-widget-types-controller.php
index 82c3a5255ee84..2fd591c719b10 100644
--- a/src/wp-includes/rest-api/endpoints/class-wp-rest-widget-types-controller.php
+++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-widget-types-controller.php
@@ -99,6 +99,29 @@ public function register_routes() {
),
)
);
+
+ register_rest_route(
+ $this->namespace,
+ '/' . $this->rest_base . '/(?P[a-zA-Z0-9_-]+)/render',
+ array(
+ array(
+ 'methods' => WP_REST_Server::CREATABLE,
+ 'permission_callback' => array( $this, 'get_item_permissions_check' ),
+ 'callback' => array( $this, 'render' ),
+ 'args' => array(
+ 'id' => array(
+ 'description' => __( 'The widget type id.', 'default' ),
+ 'type' => 'string',
+ 'required' => true,
+ ),
+ 'instance' => array(
+ 'description' => __( 'Current instance settings of the widget.', 'default' ),
+ 'type' => 'object',
+ ),
+ ),
+ ),
+ )
+ );
}
/**
@@ -559,6 +582,78 @@ private function get_widget_form( $widget_object, $instance ) {
return ob_get_clean();
}
+ /**
+ * Renders a single Legacy Widget and wraps it in a JSON-encodable array.
+ *
+ * @since 5.9.0
+ *
+ * @param WP_REST_Request $request Full details about the request.
+ *
+ * @return array An array with rendered Legacy Widget HTML.
+ */
+ public function render( $request ) {
+ return array(
+ 'preview' => $this->render_legacy_widget_preview_iframe(
+ $request['id'],
+ isset( $request['instance'] ) ? $request['instance'] : null
+ ),
+ );
+ }
+
+ /**
+ * Renders a page containing a preview of the requested Legacy Widget block.
+ *
+ * @since 5.9.0
+ *
+ * @param string $id_base The id base of the requested widget.
+ * @param array $instance The widget instance attributes.
+ *
+ * @return string Rendered Legacy Widget block preview.
+ */
+ private function render_legacy_widget_preview_iframe( $id_base, $instance ) {
+ if ( ! defined( 'IFRAME_REQUEST' ) ) {
+ define( 'IFRAME_REQUEST', true );
+ }
+
+ ob_start();
+ ?>
+
+ >
+
+
+
+
+
+
+
+ >
+
+
+ get_registered( 'core/legacy-widget' );
+ echo $block->render(
+ array(
+ 'idBase' => $id_base,
+ 'instance' => $instance,
+ )
+ );
+ ?>
+
+
+
+
+
+ '16.13.1',
- 'react-dom' => '16.13.1',
+ 'react' => '17.0.1',
+ 'react-dom' => '17.0.1',
'regenerator-runtime' => '0.13.7',
'moment' => '2.29.1',
'lodash' => '4.17.19',
@@ -1598,6 +1598,11 @@ function wp_default_styles( $styles ) {
'wp-block-library',
'wp-reusable-blocks',
),
+ 'edit-site' => array(
+ 'wp-components',
+ 'wp-block-editor',
+ 'wp-edit-blocks',
+ ),
);
foreach ( $package_styles as $package => $dependencies ) {
@@ -1655,6 +1660,7 @@ function wp_default_styles( $styles ) {
'wp-components',
'wp-customize-widgets',
'wp-edit-post',
+ 'wp-edit-site',
'wp-edit-widgets',
'wp-editor',
'wp-format-library',
@@ -2265,7 +2271,19 @@ function wp_common_block_scripts_and_styles() {
wp_enqueue_style( 'wp-block-library' );
if ( current_theme_supports( 'wp-block-styles' ) ) {
- wp_enqueue_style( 'wp-block-library-theme' );
+ if ( wp_should_load_separate_core_block_assets() ) {
+ $suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? 'css' : 'min.css';
+ $files = glob( __DIR__ . "/blocks/**/theme.$suffix" );
+ foreach ( $files as $path ) {
+ $block_name = basename( dirname( $path ) );
+ if ( is_rtl() && file_exists( __DIR__ . "/blocks/$block_name/theme-rtl.$suffix" ) ) {
+ $path = __DIR__ . "/blocks/$block_name/theme-rtl.$suffix";
+ }
+ wp_add_inline_style( "wp-block-{$block_name}", file_get_contents( $path ) );
+ }
+ } else {
+ wp_enqueue_style( 'wp-block-library-theme' );
+ }
}
/**
@@ -2773,6 +2791,8 @@ function _wp_normalize_relative_css_links( $css, $stylesheet_url ) {
* @since 5.8.0
*/
function wp_add_iframed_editor_assets_html() {
+ global $pagenow;
+
if ( ! wp_should_load_block_editor_scripts_and_styles() ) {
return;
}
@@ -2785,6 +2805,11 @@ function wp_add_iframed_editor_assets_html() {
'wp-edit-blocks',
);
+ if ( 'widgets.php' === $pagenow || 'customize.php' === $pagenow ) {
+ $style_handles[] = 'wp-widgets';
+ $style_handles[] = 'wp-edit-widgets';
+ }
+
$block_registry = WP_Block_Type_Registry::get_instance();
foreach ( $block_registry->get_all_registered() as $block_type ) {
@@ -2806,7 +2831,8 @@ function wp_add_iframed_editor_assets_html() {
ob_start();
- wp_styles()->done = array();
+ // We do not need reset styles for the iframed editor.
+ wp_styles()->done = array( 'wp-reset-editor-styles' );
wp_styles()->do_items( $style_handles );
wp_styles()->done = $done;
diff --git a/src/wp-includes/template-canvas.php b/src/wp-includes/template-canvas.php
index 2ce5b12ca6596..2972c94bd16fc 100644
--- a/src/wp-includes/template-canvas.php
+++ b/src/wp-includes/template-canvas.php
@@ -10,8 +10,7 @@
* This needs to run before so that blocks can add scripts and styles in wp_head().
*/
$template_html = get_the_block_template_html();
-?>
-
+?>
>
diff --git a/src/wp-includes/theme.php b/src/wp-includes/theme.php
index 7d17540142a8f..d0471792e19ab 100644
--- a/src/wp-includes/theme.php
+++ b/src/wp-includes/theme.php
@@ -4075,3 +4075,14 @@ function create_initial_theme_features() {
)
);
}
+
+/**
+ * Returns whether the current theme is a block-based theme or not.
+ *
+ * @since 5.9.0
+ *
+ * @return boolean Whether the current theme is a block-based theme or not.
+ */
+function wp_is_block_template_theme() {
+ return is_readable( get_theme_file_path( '/block-templates/index.html' ) );
+}
diff --git a/src/wp-settings.php b/src/wp-settings.php
index 9f027ee645f07..978b451415073 100644
--- a/src/wp-settings.php
+++ b/src/wp-settings.php
@@ -317,6 +317,7 @@
require ABSPATH . WPINC . '/block-supports/border.php';
require ABSPATH . WPINC . '/block-supports/colors.php';
require ABSPATH . WPINC . '/block-supports/custom-classname.php';
+require ABSPATH . WPINC . '/block-supports/dimensions.php';
require ABSPATH . WPINC . '/block-supports/duotone.php';
require ABSPATH . WPINC . '/block-supports/elements.php';
require ABSPATH . WPINC . '/block-supports/generated-classname.php';
diff --git a/tests/phpunit/includes/functions.php b/tests/phpunit/includes/functions.php
index 02d6ba343b1ff..1a100a3432168 100644
--- a/tests/phpunit/includes/functions.php
+++ b/tests/phpunit/includes/functions.php
@@ -307,20 +307,27 @@ function _unhook_block_registration() {
remove_action( 'init', 'register_block_core_calendar' );
remove_action( 'init', 'register_block_core_categories' );
remove_action( 'init', 'register_block_core_file' );
- remove_action( 'init', 'register_block_core_loginout' );
remove_action( 'init', 'register_block_core_latest_comments' );
remove_action( 'init', 'register_block_core_latest_posts' );
+ remove_action( 'init', 'register_block_core_legacy_widget' );
+ remove_action( 'init', 'register_block_core_loginout' );
+ remove_action( 'init', 'register_block_core_navigation' );
+ remove_action( 'init', 'register_block_core_navigation_area' );
+ remove_action( 'init', 'register_block_core_navigation_link' );
+ remove_action( 'init', 'register_block_core_navigation_submenu' );
remove_action( 'init', 'register_block_core_page_list' );
+ remove_action( 'init', 'register_block_core_pattern' );
remove_action( 'init', 'register_block_core_post_author' );
+ remove_action( 'init', 'register_block_core_post_comments' );
remove_action( 'init', 'register_block_core_post_content' );
remove_action( 'init', 'register_block_core_post_date' );
remove_action( 'init', 'register_block_core_post_excerpt' );
remove_action( 'init', 'register_block_core_post_featured_image' );
+ remove_action( 'init', 'register_block_core_post_navigation_link' );
+ remove_action( 'init', 'register_block_core_post_template' );
remove_action( 'init', 'register_block_core_post_terms' );
remove_action( 'init', 'register_block_core_post_title' );
remove_action( 'init', 'register_block_core_query' );
- remove_action( 'init', 'register_block_core_post_template' );
- remove_action( 'init', 'gutenberg_register_legacy_query_loop_block' );
remove_action( 'init', 'register_block_core_query_pagination' );
remove_action( 'init', 'register_block_core_query_pagination_next' );
remove_action( 'init', 'register_block_core_query_pagination_numbers' );
@@ -329,14 +336,14 @@ function _unhook_block_registration() {
remove_action( 'init', 'register_block_core_rss' );
remove_action( 'init', 'register_block_core_search' );
remove_action( 'init', 'register_block_core_shortcode' );
- remove_action( 'init', 'register_block_core_site_tagline' );
remove_action( 'init', 'register_block_core_site_logo' );
+ remove_action( 'init', 'register_block_core_site_tagline' );
remove_action( 'init', 'register_block_core_site_title' );
remove_action( 'init', 'register_block_core_social_link' );
remove_action( 'init', 'register_block_core_social_link' );
remove_action( 'init', 'register_block_core_tag_cloud' );
- remove_action( 'init', 'register_core_block_types_from_metadata' );
- remove_action( 'init', 'register_block_core_legacy_widget' );
remove_action( 'init', 'register_block_core_template_part' );
+ remove_action( 'init', 'register_block_core_term_description' );
+ remove_action( 'init', 'register_core_block_types_from_metadata' );
}
tests_add_filter( 'init', '_unhook_block_registration', 1000 );
diff --git a/tests/phpunit/tests/block-supports/colors.php b/tests/phpunit/tests/block-supports/colors.php
new file mode 100644
index 0000000000000..dbc32d6d276e3
--- /dev/null
+++ b/tests/phpunit/tests/block-supports/colors.php
@@ -0,0 +1,47 @@
+ 2,
+ 'attributes' => array(
+ 'textColor' => array(
+ 'type' => 'string',
+ ),
+ 'backgroundColor' => array(
+ 'type' => 'string',
+ ),
+ 'gradient' => array(
+ 'type' => 'string',
+ ),
+ ),
+ 'supports' => array(
+ 'color' => array(
+ 'text' => true,
+ 'background' => true,
+ 'gradients' => true,
+ ),
+ ),
+ )
+ );
+ $registry = WP_Block_Type_Registry::get_instance();
+ $block_type = $registry->get_registered( 'test/color-slug-with-numbers' );
+
+ $block_atts = array(
+ 'textColor' => 'fg1',
+ 'backgroundColor' => 'bg2',
+ 'gradient' => 'gr3',
+ );
+
+ $actual = wp_apply_colors_support( $block_type, $block_atts );
+ $expected = array( 'class' => 'has-text-color has-fg-1-color has-background has-bg-2-background-color has-background has-gr-3-gradient-background' );
+
+ $this->assertSame( $expected, $actual );
+ unregister_block_type( 'test/color-slug-with-numbers' );
+ }
+}
diff --git a/tests/phpunit/tests/block-supports/elements.php b/tests/phpunit/tests/block-supports/elements.php
new file mode 100644
index 0000000000000..6a56c083e6c09
--- /dev/null
+++ b/tests/phpunit/tests/block-supports/elements.php
@@ -0,0 +1,104 @@
+Hello WordPress !',
+ array(
+ 'blockName' => 'core/paragraph',
+ 'attrs' => array(
+ 'style' => array(
+ 'elements' => array(
+ 'link' => array(
+ 'color' => array(
+ 'text' => 'var:preset|color|subtle-background',
+ ),
+ ),
+ ),
+ ),
+ ),
+ )
+ )
+ );
+ $this->assertSame(
+ $result,
+ 'Hello WordPress !
'
+ );
+ }
+
+ /**
+ * Test wp_render_elements_support() with a paragraph containing a class.
+ */
+ public function test_class_paragraph_link_color() {
+ $result = self::make_unique_id_one(
+ wp_render_elements_support(
+ 'Hello WordPress !
',
+ array(
+ 'blockName' => 'core/paragraph',
+ 'attrs' => array(
+ 'style' => array(
+ 'elements' => array(
+ 'link' => array(
+ 'color' => array(
+ 'text' => 'red',
+ ),
+ ),
+ ),
+ ),
+ 'backgroundColor' => 'dark-gray',
+ ),
+ )
+ )
+ );
+ $this->assertSame(
+ $result,
+ 'Hello WordPress !
'
+ );
+ }
+
+ /**
+ * Test wp_render_elements_support() with a paragraph containing a anchor.
+ */
+ public function test_anchor_paragraph_link_color() {
+ $result = self::make_unique_id_one(
+ wp_render_elements_support(
+ 'Hello WordPress !
',
+ array(
+ 'blockName' => 'core/paragraph',
+ 'attrs' => array(
+ 'style' => array(
+ 'elements' => array(
+ 'link' => array(
+ 'color' => array(
+ 'text' => '#fff000',
+ ),
+ ),
+ ),
+ ),
+ ),
+ )
+ )
+ );
+ $this->assertSame(
+ $result,
+ 'Hello WordPress !
'
+ );
+ }
+}
diff --git a/tests/phpunit/tests/block-supports/spacing.php b/tests/phpunit/tests/block-supports/spacing.php
new file mode 100644
index 0000000000000..7c965214c0e7b
--- /dev/null
+++ b/tests/phpunit/tests/block-supports/spacing.php
@@ -0,0 +1,138 @@
+Test ';
+ private $test_gap_block_value = array();
+ private $test_gap_block_args = array();
+
+ function set_up() {
+ parent::set_up();
+
+ $this->test_gap_block_value = array(
+ 'blockName' => 'test/test-block',
+ 'attrs' => array(
+ 'style' => array(
+ 'spacing' => array(
+ 'blockGap' => '3em',
+ ),
+ ),
+ ),
+ );
+
+ $this->test_gap_block_args = array(
+ 'api_version' => 2,
+ 'supports' => array(
+ 'spacing' => array(
+ 'blockGap' => true,
+ ),
+ ),
+ );
+ }
+
+ function tear_down() {
+ unregister_block_type( 'test/test-block' );
+
+ parent::tear_down();
+ }
+
+ function test_spacing_gap_block_support_renders_block_inline_style() {
+ register_block_type( 'test/test-block', $this->test_gap_block_args );
+ $render_output = wp_render_spacing_gap_support(
+ $this->sample_block_content,
+ $this->test_gap_block_value
+ );
+
+ $this->assertSame(
+ '
',
+ $render_output
+ );
+ }
+
+ function test_spacing_gap_block_support_renders_block_inline_style_with_inner_tag() {
+ register_block_type( 'test/test-block', $this->test_gap_block_args );
+ $render_output = wp_render_spacing_gap_support(
+ '
',
+ $render_output
+ );
+ }
+
+ function test_spacing_gap_block_support_renders_block_inline_style_with_no_other_attributes() {
+ register_block_type( 'test/test-block', $this->test_gap_block_args );
+ $render_output = wp_render_spacing_gap_support(
+ '
',
+ $render_output
+ );
+ }
+
+ function test_spacing_gap_block_support_renders_appended_block_inline_style() {
+ register_block_type( 'test/test-block', $this->test_gap_block_args );
+ $render_output = wp_render_spacing_gap_support(
+ '
',
+ $render_output
+ );
+ }
+
+ function test_spacing_gap_block_support_does_not_render_style_when_support_is_false() {
+ $this->test_gap_block_args['supports']['spacing']['blockGap'] = false;
+
+ register_block_type( 'test/test-block', $this->test_gap_block_args );
+ $render_output = wp_render_spacing_gap_support(
+ $this->sample_block_content,
+ $this->test_gap_block_value
+ );
+
+ $this->assertEquals(
+ $this->sample_block_content,
+ $render_output
+ );
+ }
+
+ function test_spacing_gap_block_support_does_not_render_style_when_gap_is_null() {
+ $this->test_gap_block_value['attrs']['style']['spacing']['blockGap'] = null;
+ $this->test_gap_block_args['supports']['spacing']['blockGap'] = true;
+
+ register_block_type( 'test/test-block', $this->test_gap_block_args );
+ $render_output = wp_render_spacing_gap_support(
+ $this->sample_block_content,
+ $this->test_gap_block_value
+ );
+
+ $this->assertEquals(
+ $this->sample_block_content,
+ $render_output
+ );
+ }
+
+ function test_spacing_gap_block_support_does_not_render_style_when_gap_is_illegal_value() {
+ $this->test_gap_block_value['attrs']['style']['spacing']['blockGap'] = '" javascript="alert("hello");';
+ $this->test_gap_block_args['supports']['spacing']['blockGap'] = true;
+
+ register_block_type( 'test/test-block', $this->test_gap_block_args );
+ $render_output = wp_render_spacing_gap_support(
+ $this->sample_block_content,
+ $this->test_gap_block_value
+ );
+
+ $this->assertEquals(
+ $this->sample_block_content,
+ $render_output
+ );
+ }
+}
diff --git a/tests/phpunit/tests/block-supports/typography.php b/tests/phpunit/tests/block-supports/typography.php
new file mode 100644
index 0000000000000..6bda93d92f9c0
--- /dev/null
+++ b/tests/phpunit/tests/block-supports/typography.php
@@ -0,0 +1,123 @@
+ 2,
+ 'attributes' => array(
+ 'fontSize' => array(
+ 'type' => 'string',
+ ),
+ ),
+ 'supports' => array(
+ 'typography' => array(
+ 'fontSize' => true,
+ ),
+ ),
+ )
+ );
+ $registry = WP_Block_Type_Registry::get_instance();
+ $block_type = $registry->get_registered( 'test/font-size-slug-with-numbers' );
+
+ $block_atts = array( 'fontSize' => 'h1' );
+
+ $actual = wp_apply_typography_support( $block_type, $block_atts );
+ $expected = array( 'class' => 'has-h-1-font-size' );
+
+ $this->assertSame( $expected, $actual );
+ unregister_block_type( 'test/font-size-slug-with-numbers' );
+ }
+
+ function test_font_family_with_legacy_inline_styles_using_a_value() {
+ $block_name = 'test/font-family-with-inline-styles-using-value';
+ register_block_type(
+ $block_name,
+ array(
+ 'api_version' => 2,
+ 'attributes' => array(
+ 'style' => array(
+ 'type' => 'object',
+ ),
+ ),
+ 'supports' => array(
+ 'typography' => array(
+ '__experimentalFontFamily' => true,
+ ),
+ ),
+ )
+ );
+ $registry = WP_Block_Type_Registry::get_instance();
+ $block_type = $registry->get_registered( $block_name );
+ $block_atts = array( 'style' => array( 'typography' => array( 'fontFamily' => 'serif' ) ) );
+
+ $actual = wp_apply_typography_support( $block_type, $block_atts );
+ $expected = array( 'style' => 'font-family: serif;' );
+
+ $this->assertSame( $expected, $actual );
+ unregister_block_type( $block_name );
+ }
+
+ function test_font_family_with_legacy_inline_styles_using_a_css_var() {
+ $block_name = 'test/font-family-with-inline-styles-using-css-var';
+ register_block_type(
+ $block_name,
+ array(
+ 'api_version' => 2,
+ 'attributes' => array(
+ 'style' => array(
+ 'type' => 'object',
+ ),
+ ),
+ 'supports' => array(
+ 'typography' => array(
+ '__experimentalFontFamily' => true,
+ ),
+ ),
+ )
+ );
+ $registry = WP_Block_Type_Registry::get_instance();
+ $block_type = $registry->get_registered( $block_name );
+ $block_atts = array( 'style' => array( 'typography' => array( 'fontFamily' => 'var:preset|font-family|h1' ) ) );
+
+ $actual = wp_apply_typography_support( $block_type, $block_atts );
+ $expected = array( 'style' => 'font-family: var(--wp--preset--font-family--h-1);' );
+
+ $this->assertSame( $expected, $actual );
+ unregister_block_type( $block_name );
+ }
+
+ function test_font_family_with_class() {
+ $block_name = 'test/font-family-with-class';
+ register_block_type(
+ $block_name,
+ array(
+ 'api_version' => 2,
+ 'attributes' => array(
+ 'style' => array(
+ 'type' => 'object',
+ ),
+ ),
+ 'supports' => array(
+ 'typography' => array(
+ '__experimentalFontFamily' => true,
+ ),
+ ),
+ )
+ );
+ $registry = WP_Block_Type_Registry::get_instance();
+ $block_type = $registry->get_registered( $block_name );
+ $block_atts = array( 'fontFamily' => 'h1' );
+
+ $actual = wp_apply_typography_support( $block_type, $block_atts );
+ $expected = array( 'class' => 'has-h-1-font-family' );
+
+ $this->assertSame( $expected, $actual );
+ unregister_block_type( $block_name );
+ }
+
+}
diff --git a/tests/phpunit/tests/block-template-utils.php b/tests/phpunit/tests/block-template-utils.php
index 714885319aadb..9d32e36731271 100644
--- a/tests/phpunit/tests/block-template-utils.php
+++ b/tests/phpunit/tests/block-template-utils.php
@@ -75,7 +75,7 @@ public static function wpTearDownAfterClass() {
wp_delete_post( self::$post->ID );
}
- public function test_build_template_result_from_post() {
+ public function test_build_block_template_result_from_post() {
$template = _build_block_template_result_from_post(
self::$post,
'wp_template'
diff --git a/tests/phpunit/tests/rest-api/rest-schema-setup.php b/tests/phpunit/tests/rest-api/rest-schema-setup.php
index a81b5b21e2358..4390bc2f9fccd 100644
--- a/tests/phpunit/tests/rest-api/rest-schema-setup.php
+++ b/tests/phpunit/tests/rest-api/rest-schema-setup.php
@@ -154,8 +154,15 @@ public function test_expected_routes_in_schema() {
'/wp/v2/widget-types',
'/wp/v2/widget-types/(?P