summaryrefslogtreecommitdiffstats
path: root/wp-includes/blocks.php
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--wp-includes/blocks.php428
1 files changed, 400 insertions, 28 deletions
diff --git a/wp-includes/blocks.php b/wp-includes/blocks.php
index 854992d..26e5e0c 100644
--- a/wp-includes/blocks.php
+++ b/wp-includes/blocks.php
@@ -858,11 +858,11 @@ function get_hooked_blocks() {
* @since 6.5.0
* @access private
*
- * @param array $parsed_anchor_block The anchor block, in parsed block array format.
- * @param string $relative_position The relative position of the hooked blocks.
- * Can be one of 'before', 'after', 'first_child', or 'last_child'.
- * @param array $hooked_blocks An array of hooked block types, grouped by anchor block and relative position.
- * @param WP_Block_Template|array $context The block template, template part, or pattern that the anchor block belongs to.
+ * @param array $parsed_anchor_block The anchor block, in parsed block array format.
+ * @param string $relative_position The relative position of the hooked blocks.
+ * Can be one of 'before', 'after', 'first_child', or 'last_child'.
+ * @param array $hooked_blocks An array of hooked block types, grouped by anchor block and relative position.
+ * @param WP_Block_Template|WP_Post|array $context The block template, template part, or pattern that the anchor block belongs to.
* @return string
*/
function insert_hooked_blocks( &$parsed_anchor_block, $relative_position, $hooked_blocks, $context ) {
@@ -949,12 +949,12 @@ function insert_hooked_blocks( &$parsed_anchor_block, $relative_position, $hooke
* @since 6.5.0
* @access private
*
- * @param array $parsed_anchor_block The anchor block, in parsed block array format.
- * @param string $relative_position The relative position of the hooked blocks.
- * Can be one of 'before', 'after', 'first_child', or 'last_child'.
- * @param array $hooked_blocks An array of hooked block types, grouped by anchor block and relative position.
- * @param WP_Block_Template|array $context The block template, template part, or pattern that the anchor block belongs to.
- * @return string An empty string.
+ * @param array $parsed_anchor_block The anchor block, in parsed block array format.
+ * @param string $relative_position The relative position of the hooked blocks.
+ * Can be one of 'before', 'after', 'first_child', or 'last_child'.
+ * @param array $hooked_blocks An array of hooked block types, grouped by anchor block and relative position.
+ * @param WP_Block_Template|WP_Post|array $context The block template, template part, or pattern that the anchor block belongs to.
+ * @return string Empty string.
*/
function set_ignored_hooked_blocks_metadata( &$parsed_anchor_block, $relative_position, $hooked_blocks, $context ) {
$anchor_block_type = $parsed_anchor_block['blockName'];
@@ -1003,6 +1003,183 @@ function set_ignored_hooked_blocks_metadata( &$parsed_anchor_block, $relative_po
}
/**
+ * Runs the hooked blocks algorithm on the given content.
+ *
+ * @since 6.6.0
+ * @access private
+ *
+ * @param string $content Serialized content.
+ * @param WP_Block_Template|WP_Post|array $context A block template, template part, `wp_navigation` post object,
+ * or pattern that the blocks belong to.
+ * @param callable $callback A function that will be called for each block to generate
+ * the markup for a given list of blocks that are hooked to it.
+ * Default: 'insert_hooked_blocks'.
+ * @return string The serialized markup.
+ */
+function apply_block_hooks_to_content( $content, $context, $callback = 'insert_hooked_blocks' ) {
+ $hooked_blocks = get_hooked_blocks();
+ if ( empty( $hooked_blocks ) && ! has_filter( 'hooked_block_types' ) ) {
+ return $content;
+ }
+
+ $blocks = parse_blocks( $content );
+
+ $before_block_visitor = make_before_block_visitor( $hooked_blocks, $context, $callback );
+ $after_block_visitor = make_after_block_visitor( $hooked_blocks, $context, $callback );
+
+ return traverse_and_serialize_blocks( $blocks, $before_block_visitor, $after_block_visitor );
+}
+
+/**
+ * Accepts the serialized markup of a block and its inner blocks, and returns serialized markup of the inner blocks.
+ *
+ * @since 6.6.0
+ * @access private
+ *
+ * @param string $serialized_block The serialized markup of a block and its inner blocks.
+ * @return string The serialized markup of the inner blocks.
+ */
+function remove_serialized_parent_block( $serialized_block ) {
+ $start = strpos( $serialized_block, '-->' ) + strlen( '-->' );
+ $end = strrpos( $serialized_block, '<!--' );
+ return substr( $serialized_block, $start, $end - $start );
+}
+
+/**
+ * Updates the wp_postmeta with the list of ignored hooked blocks where the inner blocks are stored as post content.
+ * Currently only supports `wp_navigation` post types.
+ *
+ * @since 6.6.0
+ * @access private
+ *
+ * @param stdClass $post Post object.
+ * @return stdClass The updated post object.
+ */
+function update_ignored_hooked_blocks_postmeta( $post ) {
+ /*
+ * In this scenario the user has likely tried to create a navigation via the REST API.
+ * In which case we won't have a post ID to work with and store meta against.
+ */
+ if ( empty( $post->ID ) ) {
+ return $post;
+ }
+
+ /*
+ * Skip meta generation when consumers intentionally update specific Navigation fields
+ * and omit the content update.
+ */
+ if ( ! isset( $post->post_content ) ) {
+ return $post;
+ }
+
+ /*
+ * Skip meta generation when the post content is not a navigation block.
+ */
+ if ( ! isset( $post->post_type ) || 'wp_navigation' !== $post->post_type ) {
+ return $post;
+ }
+
+ $attributes = array();
+
+ $ignored_hooked_blocks = get_post_meta( $post->ID, '_wp_ignored_hooked_blocks', true );
+ if ( ! empty( $ignored_hooked_blocks ) ) {
+ $ignored_hooked_blocks = json_decode( $ignored_hooked_blocks, true );
+ $attributes['metadata'] = array(
+ 'ignoredHookedBlocks' => $ignored_hooked_blocks,
+ );
+ }
+
+ $markup = get_comment_delimited_block_content(
+ 'core/navigation',
+ $attributes,
+ $post->post_content
+ );
+
+ $serialized_block = apply_block_hooks_to_content( $markup, get_post( $post->ID ), 'set_ignored_hooked_blocks_metadata' );
+ $root_block = parse_blocks( $serialized_block )[0];
+
+ $ignored_hooked_blocks = isset( $root_block['attrs']['metadata']['ignoredHookedBlocks'] )
+ ? $root_block['attrs']['metadata']['ignoredHookedBlocks']
+ : array();
+
+ if ( ! empty( $ignored_hooked_blocks ) ) {
+ $existing_ignored_hooked_blocks = get_post_meta( $post->ID, '_wp_ignored_hooked_blocks', true );
+ if ( ! empty( $existing_ignored_hooked_blocks ) ) {
+ $existing_ignored_hooked_blocks = json_decode( $existing_ignored_hooked_blocks, true );
+ $ignored_hooked_blocks = array_unique( array_merge( $ignored_hooked_blocks, $existing_ignored_hooked_blocks ) );
+ }
+ update_post_meta( $post->ID, '_wp_ignored_hooked_blocks', json_encode( $ignored_hooked_blocks ) );
+ }
+
+ $post->post_content = remove_serialized_parent_block( $serialized_block );
+ return $post;
+}
+
+/**
+ * Returns the markup for blocks hooked to the given anchor block in a specific relative position and then
+ * adds a list of hooked block types to an anchor block's ignored hooked block types.
+ *
+ * This function is meant for internal use only.
+ *
+ * @since 6.6.0
+ * @access private
+ *
+ * @param array $parsed_anchor_block The anchor block, in parsed block array format.
+ * @param string $relative_position The relative position of the hooked blocks.
+ * Can be one of 'before', 'after', 'first_child', or 'last_child'.
+ * @param array $hooked_blocks An array of hooked block types, grouped by anchor block and relative position.
+ * @param WP_Block_Template|WP_Post|array $context The block template, template part, or pattern that the anchor block belongs to.
+ * @return string
+ */
+function insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata( &$parsed_anchor_block, $relative_position, $hooked_blocks, $context ) {
+ $markup = insert_hooked_blocks( $parsed_anchor_block, $relative_position, $hooked_blocks, $context );
+ $markup .= set_ignored_hooked_blocks_metadata( $parsed_anchor_block, $relative_position, $hooked_blocks, $context );
+
+ return $markup;
+}
+
+/**
+ * Hooks into the REST API response for the core/navigation block and adds the first and last inner blocks.
+ *
+ * @since 6.6.0
+ *
+ * @param WP_REST_Response $response The response object.
+ * @param WP_Post $post Post object.
+ * @return WP_REST_Response The response object.
+ */
+function insert_hooked_blocks_into_rest_response( $response, $post ) {
+ if ( ! isset( $response->data['content']['raw'] ) || ! isset( $response->data['content']['rendered'] ) ) {
+ return $response;
+ }
+
+ $attributes = array();
+ $ignored_hooked_blocks = get_post_meta( $post->ID, '_wp_ignored_hooked_blocks', true );
+ if ( ! empty( $ignored_hooked_blocks ) ) {
+ $ignored_hooked_blocks = json_decode( $ignored_hooked_blocks, true );
+ $attributes['metadata'] = array(
+ 'ignoredHookedBlocks' => $ignored_hooked_blocks,
+ );
+ }
+ $content = get_comment_delimited_block_content(
+ 'core/navigation',
+ $attributes,
+ $response->data['content']['raw']
+ );
+
+ $content = apply_block_hooks_to_content( $content, $post );
+
+ // Remove mock Navigation block wrapper.
+ $content = remove_serialized_parent_block( $content );
+
+ $response->data['content']['raw'] = $content;
+
+ /** This filter is documented in wp-includes/post-template.php */
+ $response->data['content']['rendered'] = apply_filters( 'the_content', $content );
+
+ return $response;
+}
+
+/**
* Returns a function that injects the theme attribute into, and hooked blocks before, a given block.
*
* The returned function can be used as `$pre_callback` argument to `traverse_and_serialize_block(s)`,
@@ -1201,7 +1378,17 @@ function get_comment_delimited_block_content( $block_name, $block_attributes, $b
*
* @since 5.3.1
*
- * @param array $block A representative array of a single parsed block object. See WP_Block_Parser_Block.
+ * @param array $block {
+ * A representative array of a single parsed block object. See WP_Block_Parser_Block.
+ *
+ * @type string $blockName Name of block.
+ * @type array $attrs Attributes from block comment delimiters.
+ * @type array[] $innerBlocks List of inner blocks. An array of arrays that
+ * have the same structure as this one.
+ * @type string $innerHTML HTML from inside block comment delimiters.
+ * @type array $innerContent List of string fragments and null markers where
+ * inner blocks were found.
+ * }
* @return string String of rendered HTML.
*/
function serialize_block( $block ) {
@@ -1229,7 +1416,21 @@ function serialize_block( $block ) {
*
* @since 5.3.1
*
- * @param array[] $blocks An array of representative arrays of parsed block objects. See serialize_block().
+ * @param array[] $blocks {
+ * Array of block structures.
+ *
+ * @type array ...$0 {
+ * A representative array of a single parsed block object. See WP_Block_Parser_Block.
+ *
+ * @type string $blockName Name of block.
+ * @type array $attrs Attributes from block comment delimiters.
+ * @type array[] $innerBlocks List of inner blocks. An array of arrays that
+ * have the same structure as this one.
+ * @type string $innerHTML HTML from inside block comment delimiters.
+ * @type array $innerContent List of string fragments and null markers where
+ * inner blocks were found.
+ * }
+ * }
* @return string String of rendered HTML.
*/
function serialize_blocks( $blocks ) {
@@ -1321,6 +1522,83 @@ function traverse_and_serialize_block( $block, $pre_callback = null, $post_callb
}
/**
+ * Replaces patterns in a block tree with their content.
+ *
+ * @since 6.6.0
+ *
+ * @param array $blocks An array blocks.
+ *
+ * @return array An array of blocks with patterns replaced by their content.
+ */
+function resolve_pattern_blocks( $blocks ) {
+ static $inner_content;
+ // Keep track of seen references to avoid infinite loops.
+ static $seen_refs = array();
+ $i = 0;
+ while ( $i < count( $blocks ) ) {
+ if ( 'core/pattern' === $blocks[ $i ]['blockName'] ) {
+ $attrs = $blocks[ $i ]['attrs'];
+
+ if ( empty( $attrs['slug'] ) ) {
+ ++$i;
+ continue;
+ }
+
+ $slug = $attrs['slug'];
+
+ if ( isset( $seen_refs[ $slug ] ) ) {
+ // Skip recursive patterns.
+ array_splice( $blocks, $i, 1 );
+ continue;
+ }
+
+ $registry = WP_Block_Patterns_Registry::get_instance();
+ $pattern = $registry->get_registered( $slug );
+
+ // Skip unknown patterns.
+ if ( ! $pattern ) {
+ ++$i;
+ continue;
+ }
+
+ $blocks_to_insert = parse_blocks( $pattern['content'] );
+ $seen_refs[ $slug ] = true;
+ $prev_inner_content = $inner_content;
+ $inner_content = null;
+ $blocks_to_insert = resolve_pattern_blocks( $blocks_to_insert );
+ $inner_content = $prev_inner_content;
+ unset( $seen_refs[ $slug ] );
+ array_splice( $blocks, $i, 1, $blocks_to_insert );
+
+ // If we have inner content, we need to insert nulls in the
+ // inner content array, otherwise serialize_blocks will skip
+ // blocks.
+ if ( $inner_content ) {
+ $null_indices = array_keys( $inner_content, null, true );
+ $content_index = $null_indices[ $i ];
+ $nulls = array_fill( 0, count( $blocks_to_insert ), null );
+ array_splice( $inner_content, $content_index, 1, $nulls );
+ }
+
+ // Skip inserted blocks.
+ $i += count( $blocks_to_insert );
+ } else {
+ if ( ! empty( $blocks[ $i ]['innerBlocks'] ) ) {
+ $prev_inner_content = $inner_content;
+ $inner_content = $blocks[ $i ]['innerContent'];
+ $blocks[ $i ]['innerBlocks'] = resolve_pattern_blocks(
+ $blocks[ $i ]['innerBlocks']
+ );
+ $blocks[ $i ]['innerContent'] = $inner_content;
+ $inner_content = $prev_inner_content;
+ }
+ ++$i;
+ }
+ }
+ return $blocks;
+}
+
+/**
* Given an array of parsed block trees, applies callbacks before and after serializing them and
* returns their concatenated output.
*
@@ -1447,7 +1725,7 @@ function _filter_block_content_callback( $matches ) {
* @return array The filtered and sanitized block object result.
*/
function filter_block_kses( $block, $allowed_html, $allowed_protocols = array() ) {
- $block['attrs'] = filter_block_kses_value( $block['attrs'], $allowed_html, $allowed_protocols );
+ $block['attrs'] = filter_block_kses_value( $block['attrs'], $allowed_html, $allowed_protocols, $block );
if ( is_array( $block['innerBlocks'] ) ) {
foreach ( $block['innerBlocks'] as $i => $inner_block ) {
@@ -1463,6 +1741,7 @@ function filter_block_kses( $block, $allowed_html, $allowed_protocols = array()
* non-allowable HTML.
*
* @since 5.3.1
+ * @since 6.5.5 Added the `$block_context` parameter.
*
* @param string[]|string $value The attribute value to filter.
* @param array[]|string $allowed_html An array of allowed HTML elements and attributes,
@@ -1470,14 +1749,18 @@ function filter_block_kses( $block, $allowed_html, $allowed_protocols = array()
* for the list of accepted context names.
* @param string[] $allowed_protocols Optional. Array of allowed URL protocols.
* Defaults to the result of wp_allowed_protocols().
+ * @param array $block_context Optional. The block the attribute belongs to, in parsed block array format.
* @return string[]|string The filtered and sanitized result.
*/
-function filter_block_kses_value( $value, $allowed_html, $allowed_protocols = array() ) {
+function filter_block_kses_value( $value, $allowed_html, $allowed_protocols = array(), $block_context = null ) {
if ( is_array( $value ) ) {
foreach ( $value as $key => $inner_value ) {
- $filtered_key = filter_block_kses_value( $key, $allowed_html, $allowed_protocols );
- $filtered_value = filter_block_kses_value( $inner_value, $allowed_html, $allowed_protocols );
+ $filtered_key = filter_block_kses_value( $key, $allowed_html, $allowed_protocols, $block_context );
+ $filtered_value = filter_block_kses_value( $inner_value, $allowed_html, $allowed_protocols, $block_context );
+ if ( isset( $block_context['blockName'] ) && 'core/template-part' === $block_context['blockName'] ) {
+ $filtered_value = filter_block_core_template_part_attributes( $filtered_value, $filtered_key, $allowed_html );
+ }
if ( $filtered_key !== $key ) {
unset( $value[ $key ] );
}
@@ -1492,6 +1775,28 @@ function filter_block_kses_value( $value, $allowed_html, $allowed_protocols = ar
}
/**
+ * Sanitizes the value of the Template Part block's `tagName` attribute.
+ *
+ * @since 6.5.5
+ *
+ * @param string $attribute_value The attribute value to filter.
+ * @param string $attribute_name The attribute name.
+ * @param array[]|string $allowed_html An array of allowed HTML elements and attributes,
+ * or a context name such as 'post'. See wp_kses_allowed_html()
+ * for the list of accepted context names.
+ * @return string The sanitized attribute value.
+ */
+function filter_block_core_template_part_attributes( $attribute_value, $attribute_name, $allowed_html ) {
+ if ( empty( $attribute_value ) || 'tagName' !== $attribute_name ) {
+ return $attribute_value;
+ }
+ if ( ! is_array( $allowed_html ) ) {
+ $allowed_html = wp_kses_allowed_html( $allowed_html );
+ }
+ return isset( $allowed_html[ $attribute_value ] ) ? $attribute_value : '';
+}
+
+/**
* Parses blocks out of a content string, and renders those appropriate for the excerpt.
*
* As the excerpt should be a small string of text relevant to the full post content,
@@ -1638,7 +1943,17 @@ function _excerpt_render_inner_blocks( $parsed_block, $allowed_blocks ) {
*
* @global WP_Post $post The post to edit.
*
- * @param array $parsed_block A single parsed block object.
+ * @param array $parsed_block {
+ * A representative array of the block being rendered. See WP_Block_Parser_Block.
+ *
+ * @type string $blockName Name of block.
+ * @type array $attrs Attributes from block comment delimiters.
+ * @type array[] $innerBlocks List of inner blocks. An array of arrays that
+ * have the same structure as this one.
+ * @type string $innerHTML HTML from inside block comment delimiters.
+ * @type array $innerContent List of string fragments and null markers where
+ * inner blocks were found.
+ * }
* @return string String of rendered HTML.
*/
function render_block( $parsed_block ) {
@@ -1652,7 +1967,17 @@ function render_block( $parsed_block ) {
* @since 5.9.0 The `$parent_block` parameter was added.
*
* @param string|null $pre_render The pre-rendered content. Default null.
- * @param array $parsed_block The block being rendered.
+ * @param array $parsed_block {
+ * A representative array of the block being rendered. See WP_Block_Parser_Block.
+ *
+ * @type string $blockName Name of block.
+ * @type array $attrs Attributes from block comment delimiters.
+ * @type array[] $innerBlocks List of inner blocks. An array of arrays that
+ * have the same structure as this one.
+ * @type string $innerHTML HTML from inside block comment delimiters.
+ * @type array $innerContent List of string fragments and null markers where
+ * inner blocks were found.
+ * }
* @param WP_Block|null $parent_block If this is a nested block, a reference to the parent block.
*/
$pre_render = apply_filters( 'pre_render_block', null, $parsed_block, $parent_block );
@@ -1668,8 +1993,29 @@ function render_block( $parsed_block ) {
* @since 5.1.0
* @since 5.9.0 The `$parent_block` parameter was added.
*
- * @param array $parsed_block The block being rendered.
- * @param array $source_block An un-modified copy of $parsed_block, as it appeared in the source content.
+ * @param array $parsed_block {
+ * A representative array of the block being rendered. See WP_Block_Parser_Block.
+ *
+ * @type string $blockName Name of block.
+ * @type array $attrs Attributes from block comment delimiters.
+ * @type array[] $innerBlocks List of inner blocks. An array of arrays that
+ * have the same structure as this one.
+ * @type string $innerHTML HTML from inside block comment delimiters.
+ * @type array $innerContent List of string fragments and null markers where
+ * inner blocks were found.
+ * }
+ * @param array $source_block {
+ * An un-modified copy of `$parsed_block`, as it appeared in the source content.
+ * See WP_Block_Parser_Block.
+ *
+ * @type string $blockName Name of block.
+ * @type array $attrs Attributes from block comment delimiters.
+ * @type array[] $innerBlocks List of inner blocks. An array of arrays that
+ * have the same structure as this one.
+ * @type string $innerHTML HTML from inside block comment delimiters.
+ * @type array $innerContent List of string fragments and null markers where
+ * inner blocks were found.
+ * }
* @param WP_Block|null $parent_block If this is a nested block, a reference to the parent block.
*/
$parsed_block = apply_filters( 'render_block_data', $parsed_block, $source_block, $parent_block );
@@ -1695,7 +2041,17 @@ function render_block( $parsed_block ) {
* @since 5.9.0 The `$parent_block` parameter was added.
*
* @param array $context Default context.
- * @param array $parsed_block Block being rendered, filtered by `render_block_data`.
+ * @param array $parsed_block {
+ * A representative array of the block being rendered. See WP_Block_Parser_Block.
+ *
+ * @type string $blockName Name of block.
+ * @type array $attrs Attributes from block comment delimiters.
+ * @type array[] $innerBlocks List of inner blocks. An array of arrays that
+ * have the same structure as this one.
+ * @type string $innerHTML HTML from inside block comment delimiters.
+ * @type array $innerContent List of string fragments and null markers where
+ * inner blocks were found.
+ * }
* @param WP_Block|null $parent_block If this is a nested block, a reference to the parent block.
*/
$context = apply_filters( 'render_block_context', $context, $parsed_block, $parent_block );
@@ -1711,7 +2067,21 @@ function render_block( $parsed_block ) {
* @since 5.0.0
*
* @param string $content Post content.
- * @return array[] Array of parsed block objects.
+ * @return array[] {
+ * Array of block structures.
+ *
+ * @type array ...$0 {
+ * A representative array of a single parsed block object. See WP_Block_Parser_Block.
+ *
+ * @type string $blockName Name of block.
+ * @type array $attrs Attributes from block comment delimiters.
+ * @type array[] $innerBlocks List of inner blocks. An array of arrays that
+ * have the same structure as this one.
+ * @type string $innerHTML HTML from inside block comment delimiters.
+ * @type array $innerContent List of string fragments and null markers where
+ * inner blocks were found.
+ * }
+ * }
*/
function parse_blocks( $content ) {
/**
@@ -1790,14 +2160,16 @@ function block_version( $content ) {
* Registers a new block style.
*
* @since 5.3.0
+ * @since 6.6.0 Added support for registering styles for multiple block types.
*
* @link https://developer.wordpress.org/block-editor/reference-guides/block-api/block-styles/
*
- * @param string $block_name Block type name including namespace.
- * @param array $style_properties Array containing the properties of the style name, label,
- * style_handle (name of the stylesheet to be enqueued),
- * inline_style (string containing the CSS to be added).
- * See WP_Block_Styles_Registry::register().
+ * @param string|string[] $block_name Block type name including namespace or array of namespaced block type names.
+ * @param array $style_properties Array containing the properties of the style name, label,
+ * style_handle (name of the stylesheet to be enqueued),
+ * inline_style (string containing the CSS to be added),
+ * style_data (theme.json-like array to generate CSS from).
+ * See WP_Block_Styles_Registry::register().
* @return bool True if the block style was registered with success and false otherwise.
*/
function register_block_style( $block_name, $style_properties ) {