diff options
Diffstat (limited to 'wp-includes/blocks.php')
-rw-r--r-- | wp-includes/blocks.php | 404 |
1 files changed, 374 insertions, 30 deletions
diff --git a/wp-includes/blocks.php b/wp-includes/blocks.php index 5b452c6..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. * @@ -1483,7 +1761,6 @@ function filter_block_kses_value( $value, $allowed_html, $allowed_protocols = ar 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 ] ); } @@ -1502,11 +1779,11 @@ function filter_block_kses_value( $value, $allowed_html, $allowed_protocols = ar * * @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. + * @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 ) { @@ -1666,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 ) { @@ -1680,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 ); @@ -1696,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 ); @@ -1723,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 ); @@ -1739,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 ) { /** @@ -1818,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 ) { |