: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
array( &$inner_block, &$block, $next )
$block_content .= traverse_and_serialize_block( $inner_block, $pre_callback, $post_callback );
$block_content .= isset( $post_markup ) ? $post_markup : '';
if ( ! is_array( $block['attrs'] ) ) {
$block['attrs'] = array();
return get_comment_delimited_block_content(
* Replaces patterns in a block tree with their content.
* @param array $blocks An array blocks.
* @return array An array of blocks with patterns replaced by their content.
function resolve_pattern_blocks( $blocks ) {
// Keep track of seen references to avoid infinite loops.
static $seen_refs = array();
while ( $i < count( $blocks ) ) {
if ( 'core/pattern' === $blocks[ $i ]['blockName'] ) {
$attrs = $blocks[ $i ]['attrs'];
if ( empty( $attrs['slug'] ) ) {
if ( isset( $seen_refs[ $slug ] ) ) {
// Skip recursive patterns.
array_splice( $blocks, $i, 1 );
$registry = WP_Block_Patterns_Registry::get_instance();
$pattern = $registry->get_registered( $slug );
// Skip unknown patterns.
$blocks_to_insert = parse_blocks( $pattern['content'] );
$seen_refs[ $slug ] = true;
$prev_inner_content = $inner_content;
$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
$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 );
$i += count( $blocks_to_insert );
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;
* Given an array of parsed block trees, applies callbacks before and after serializing them and
* returns their concatenated output.
* Recursively traverses the blocks and their inner blocks and applies the two callbacks provided as
* arguments, the first one before serializing a block, and the second one after serializing.
* If either callback returns a string value, it will be prepended and appended to the serialized
* block markup, respectively.
* The callbacks will receive a reference to the current block as their first argument, so that they
* can also modify it, and the current block's parent block as second argument. Finally, the
* `$pre_callback` receives the previous block, whereas the `$post_callback` receives
* the next block as third argument.
* Serialized blocks are returned including comment delimiters, and with all attributes serialized.
* This function should be used when there is a need to modify the saved blocks, or to inject markup
* into the return value. Prefer `serialize_blocks` when preparing blocks to be saved to post content.
* This function is meant for internal use only.
* @see serialize_blocks()
* @param array[] $blocks An array of parsed blocks. See WP_Block_Parser_Block.
* @param callable $pre_callback Callback to run on each block in the tree before it is traversed and serialized.
* It is called with the following arguments: &$block, $parent_block, $previous_block.
* Its string return value will be prepended to the serialized block markup.
* @param callable $post_callback Callback to run on each block in the tree after it is traversed and serialized.
* It is called with the following arguments: &$block, $parent_block, $next_block.
* Its string return value will be appended to the serialized block markup.
* @return string Serialized block markup.
function traverse_and_serialize_blocks( $blocks, $pre_callback = null, $post_callback = null ) {
$parent_block = null; // At the top level, there is no parent block to pass to the callbacks; yet the callbacks expect a reference.
foreach ( $blocks as $index => $block ) {
if ( is_callable( $pre_callback ) ) {
$result .= call_user_func_array(
array( &$block, &$parent_block, $prev )
if ( is_callable( $post_callback ) ) {
$next = count( $blocks ) - 1 === $index
$post_markup = call_user_func_array(
array( &$block, &$parent_block, $next )
$result .= traverse_and_serialize_block( $block, $pre_callback, $post_callback );
$result .= isset( $post_markup ) ? $post_markup : '';
* Filters and sanitizes block content to remove non-allowable HTML
* from parsed block attribute values.
* @param string $text Text that may contain block content.
* @param array[]|string $allowed_html Optional. 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. Default 'post'.
* @param string[] $allowed_protocols Optional. Array of allowed URL protocols.
* Defaults to the result of wp_allowed_protocols().
* @return string The filtered and sanitized content result.
function filter_block_content( $text, $allowed_html = 'post', $allowed_protocols = array() ) {
if ( str_contains( $text, '<!--' ) && str_contains( $text, '--->' ) ) {
$text = preg_replace_callback( '%<!--(.*?)--->%', '_filter_block_content_callback', $text );
$blocks = parse_blocks( $text );
foreach ( $blocks as $block ) {
$block = filter_block_kses( $block, $allowed_html, $allowed_protocols );
$result .= serialize_block( $block );
* Callback used for regular expression replacement in filter_block_content().
* @param array $matches Array of preg_replace_callback matches.
* @return string Replacement string.
function _filter_block_content_callback( $matches ) {
return '<!--' . rtrim( $matches[1], '-' ) . '-->';
* Filters and sanitizes a parsed block to remove non-allowable HTML
* from block attribute values.
* @param WP_Block_Parser_Block $block The parsed block object.
* @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[] $allowed_protocols Optional. Array of allowed URL protocols.
* Defaults to the result of wp_allowed_protocols().
* @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 );
if ( is_array( $block['innerBlocks'] ) ) {
foreach ( $block['innerBlocks'] as $i => $inner_block ) {
$block['innerBlocks'][ $i ] = filter_block_kses( $inner_block, $allowed_html, $allowed_protocols );
* Filters and sanitizes a parsed block attribute value to remove
* @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,
* or a context name such as 'post'. See wp_kses_allowed_html()
* 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(), $block_context = null ) {
if ( is_array( $value ) ) {
foreach ( $value as $key => $inner_value ) {
$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 ) {
$value[ $filtered_key ] = $filtered_value;
} elseif ( is_string( $value ) ) {
return wp_kses( $value, $allowed_html, $allowed_protocols );
* Sanitizes the value of the Template Part block's `tagName` attribute.
* @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 ) {
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,
* this function renders the blocks that are most likely to contain such text.
* @param string $content The content to parse.
* @return string The parsed and filtered content.
function excerpt_remove_blocks( $content ) {
if ( ! has_blocks( $content ) ) {
$allowed_inner_blocks = array(
// Classic blocks have their blockName set to null.
$allowed_wrapper_blocks = array(
* Filters the list of blocks that can be used as wrapper blocks, allowing
* excerpts to be generated from the `innerBlocks` of these wrappers.
* @param string[] $allowed_wrapper_blocks The list of names of allowed wrapper blocks.
$allowed_wrapper_blocks = apply_filters( 'excerpt_allowed_wrapper_blocks', $allowed_wrapper_blocks );
$allowed_blocks = array_merge( $allowed_inner_blocks, $allowed_wrapper_blocks );
* Filters the list of blocks that can contribute to the excerpt.
* If a dynamic block is added to this list, it must not generate another
* excerpt, as this will cause an infinite loop to occur.
* @param string[] $allowed_blocks The list of names of allowed blocks.
$allowed_blocks = apply_filters( 'excerpt_allowed_blocks', $allowed_blocks );
$blocks = parse_blocks( $content );
foreach ( $blocks as $block ) {
if ( in_array( $block['blockName'], $allowed_blocks, true ) ) {
if ( ! empty( $block['innerBlocks'] ) ) {
if ( in_array( $block['blockName'], $allowed_wrapper_blocks, true ) ) {
$output .= _excerpt_render_inner_blocks( $block, $allowed_blocks );
// Skip the block if it has disallowed or nested inner blocks.
foreach ( $block['innerBlocks'] as $inner_block ) {
! in_array( $inner_block['blockName'], $allowed_inner_blocks, true ) ||
! empty( $inner_block['innerBlocks'] )
$output .= render_block( $block );
* Parses footnotes markup out of a content string,
* and renders those appropriate for the excerpt.
* @param string $content The content to parse.
* @return string The parsed and filtered content.
function excerpt_remove_footnotes( $content ) {
if ( ! str_contains( $content, 'data-fn=' ) ) {
'_<sup data-fn="[^"]+" class="[^"]+">\s*<a href="[^"]+" id="[^"]+">\d+</a>\s*</sup>_',
* Renders inner blocks from the allowed wrapper blocks
* for generating an excerpt.
* @param array $parsed_block The parsed block.
* @param array $allowed_blocks The list of allowed inner blocks.
* @return string The rendered inner blocks.
function _excerpt_render_inner_blocks( $parsed_block, $allowed_blocks ) {
foreach ( $parsed_block['innerBlocks'] as $inner_block ) {
if ( ! in_array( $inner_block['blockName'], $allowed_blocks, true ) ) {
if ( empty( $inner_block['innerBlocks'] ) ) {
$output .= render_block( $inner_block );
$output .= _excerpt_render_inner_blocks( $inner_block, $allowed_blocks );
* Renders a single block into a HTML string.
* @global WP_Post $post The post to edit.
* @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 ) {
* Allows render_block() to be short-circuited, by returning a non-null value.
* @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 {
* 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 );
if ( ! is_null( $pre_render ) ) {
$source_block = $parsed_block;
* Filters the block being rendered in render_block(), before it's processed.
* @since 5.9.0 The `$parent_block` parameter was added.
* @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.