public/edition/wp-includes/class-wp-block.php line 586

Open in your IDE?
  1. <?php
  2. /**
  3.  * Blocks API: WP_Block class
  4.  *
  5.  * @package WordPress
  6.  * @since 5.5.0
  7.  */
  8. /**
  9.  * Class representing a parsed instance of a block.
  10.  *
  11.  * @since 5.5.0
  12.  * @property array $attributes
  13.  */
  14. #[AllowDynamicProperties]
  15. class WP_Block {
  16.     /**
  17.      * Original parsed array representation of block.
  18.      *
  19.      * @since 5.5.0
  20.      * @var array
  21.      */
  22.     public $parsed_block;
  23.     /**
  24.      * Name of block.
  25.      *
  26.      * @example "core/paragraph"
  27.      *
  28.      * @since 5.5.0
  29.      * @var string
  30.      */
  31.     public $name;
  32.     /**
  33.      * Block type associated with the instance.
  34.      *
  35.      * @since 5.5.0
  36.      * @var WP_Block_Type
  37.      */
  38.     public $block_type;
  39.     /**
  40.      * Block context values.
  41.      *
  42.      * @since 5.5.0
  43.      * @var array
  44.      */
  45.     public $context = array();
  46.     /**
  47.      * All available context of the current hierarchy.
  48.      *
  49.      * @since 5.5.0
  50.      * @var array
  51.      * @access protected
  52.      */
  53.     protected $available_context = array();
  54.     /**
  55.      * Block type registry.
  56.      *
  57.      * @since 5.9.0
  58.      * @var WP_Block_Type_Registry
  59.      * @access protected
  60.      */
  61.     protected $registry;
  62.     /**
  63.      * List of inner blocks (of this same class)
  64.      *
  65.      * @since 5.5.0
  66.      * @var WP_Block_List
  67.      */
  68.     public $inner_blocks = array();
  69.     /**
  70.      * Resultant HTML from inside block comment delimiters after removing inner
  71.      * blocks.
  72.      *
  73.      * @example "...Just <!-- wp:test /--> testing..." -> "Just testing..."
  74.      *
  75.      * @since 5.5.0
  76.      * @var string
  77.      */
  78.     public $inner_html '';
  79.     /**
  80.      * List of string fragments and null markers where inner blocks were found
  81.      *
  82.      * @example array(
  83.      *   'inner_html'    => 'BeforeInnerAfter',
  84.      *   'inner_blocks'  => array( block, block ),
  85.      *   'inner_content' => array( 'Before', null, 'Inner', null, 'After' ),
  86.      * )
  87.      *
  88.      * @since 5.5.0
  89.      * @var array
  90.      */
  91.     public $inner_content = array();
  92.     /**
  93.      * Constructor.
  94.      *
  95.      * Populates object properties from the provided block instance argument.
  96.      *
  97.      * The given array of context values will not necessarily be available on
  98.      * the instance itself, but is treated as the full set of values provided by
  99.      * the block's ancestry. This is assigned to the private `available_context`
  100.      * property. Only values which are configured to consumed by the block via
  101.      * its registered type will be assigned to the block's `context` property.
  102.      *
  103.      * @since 5.5.0
  104.      *
  105.      * @param array                  $block             {
  106.      *     An associative array of a single parsed block object. See WP_Block_Parser_Block.
  107.      *
  108.      *     @type string   $blockName    Name of block.
  109.      *     @type array    $attrs        Attributes from block comment delimiters.
  110.      *     @type array    $innerBlocks  List of inner blocks. An array of arrays that
  111.      *                                  have the same structure as this one.
  112.      *     @type string   $innerHTML    HTML from inside block comment delimiters.
  113.      *     @type array    $innerContent List of string fragments and null markers where inner blocks were found.
  114.      * }
  115.      * @param array                  $available_context Optional array of ancestry context values.
  116.      * @param WP_Block_Type_Registry $registry          Optional block type registry.
  117.      */
  118.     public function __construct$block$available_context = array(), $registry null ) {
  119.         $this->parsed_block $block;
  120.         $this->name         $block['blockName'];
  121.         if ( is_null$registry ) ) {
  122.             $registry WP_Block_Type_Registry::get_instance();
  123.         }
  124.         $this->registry $registry;
  125.         $this->block_type $registry->get_registered$this->name );
  126.         $this->available_context $available_context;
  127.         $this->refresh_context_dependents();
  128.     }
  129.     /**
  130.      * Updates the context for the current block and its inner blocks.
  131.      *
  132.      * The method updates the context of inner blocks, if any, by passing down
  133.      * any context values the block provides (`provides_context`).
  134.      *
  135.      * If the block has inner blocks, the method recursively processes them by creating new instances of `WP_Block`
  136.      * for each inner block and updating their context based on the block's `provides_context` property.
  137.      *
  138.      * @since 6.8.0
  139.      */
  140.     public function refresh_context_dependents() {
  141.         /*
  142.          * Merging the `$context` property here is not ideal, but for now needs to happen because of backward compatibility.
  143.          * Ideally, the `$context` property itself would not be filterable directly and only the `$available_context` would be filterable.
  144.          * However, this needs to be separately explored whether it's possible without breakage.
  145.          */
  146.         $this->available_context array_merge$this->available_context$this->context );
  147.         if ( ! empty( $this->block_type->uses_context ) ) {
  148.             foreach ( $this->block_type->uses_context as $context_name ) {
  149.                 if ( array_key_exists$context_name$this->available_context ) ) {
  150.                     $this->context$context_name ] = $this->available_context$context_name ];
  151.                 }
  152.             }
  153.         }
  154.         $this->refresh_parsed_block_dependents();
  155.     }
  156.     /**
  157.      * Updates the parsed block content for the current block and its inner blocks.
  158.      *
  159.      * This method sets the `inner_html` and `inner_content` properties of the block based on the parsed
  160.      * block content provided during initialization. It ensures that the block instance reflects the
  161.      * most up-to-date content for both the inner HTML and any string fragments around inner blocks.
  162.      *
  163.      * If the block has inner blocks, this method initializes a new `WP_Block_List` for them, ensuring the
  164.      * correct content and context are updated for each nested block.
  165.      *
  166.      * @since 6.8.0
  167.      */
  168.     public function refresh_parsed_block_dependents() {
  169.         if ( ! empty( $this->parsed_block['innerBlocks'] ) ) {
  170.             $child_context $this->available_context;
  171.             if ( ! empty( $this->block_type->provides_context ) ) {
  172.                 foreach ( $this->block_type->provides_context as $context_name => $attribute_name ) {
  173.                     if ( array_key_exists$attribute_name$this->attributes ) ) {
  174.                         $child_context$context_name ] = $this->attributes$attribute_name ];
  175.                     }
  176.                 }
  177.             }
  178.             $this->inner_blocks = new WP_Block_List$this->parsed_block['innerBlocks'], $child_context$this->registry );
  179.         }
  180.         if ( ! empty( $this->parsed_block['innerHTML'] ) ) {
  181.             $this->inner_html $this->parsed_block['innerHTML'];
  182.         }
  183.         if ( ! empty( $this->parsed_block['innerContent'] ) ) {
  184.             $this->inner_content $this->parsed_block['innerContent'];
  185.         }
  186.     }
  187.     /**
  188.      * Returns a value from an inaccessible property.
  189.      *
  190.      * This is used to lazily initialize the `attributes` property of a block,
  191.      * such that it is only prepared with default attributes at the time that
  192.      * the property is accessed. For all other inaccessible properties, a `null`
  193.      * value is returned.
  194.      *
  195.      * @since 5.5.0
  196.      *
  197.      * @param string $name Property name.
  198.      * @return array|null Prepared attributes, or null.
  199.      */
  200.     public function __get$name ) {
  201.         if ( 'attributes' === $name ) {
  202.             $this->attributes = isset( $this->parsed_block['attrs'] ) ?
  203.                 $this->parsed_block['attrs'] :
  204.                 array();
  205.             if ( ! is_null$this->block_type ) ) {
  206.                 $this->attributes $this->block_type->prepare_attributes_for_render$this->attributes );
  207.             }
  208.             return $this->attributes;
  209.         }
  210.         return null;
  211.     }
  212.     /**
  213.      * Processes the block bindings and updates the block attributes with the values from the sources.
  214.      *
  215.      * A block might contain bindings in its attributes. Bindings are mappings
  216.      * between an attribute of the block and a source. A "source" is a function
  217.      * registered with `register_block_bindings_source()` that defines how to
  218.      * retrieve a value from outside the block, e.g. from post meta.
  219.      *
  220.      * This function will process those bindings and update the block's attributes
  221.      * with the values coming from the bindings.
  222.      *
  223.      * ### Example
  224.      *
  225.      * The "bindings" property for an Image block might look like this:
  226.      *
  227.      * ```json
  228.      * {
  229.      *   "metadata": {
  230.      *     "bindings": {
  231.      *       "title": {
  232.      *         "source": "core/post-meta",
  233.      *         "args": { "key": "text_custom_field" }
  234.      *       },
  235.      *       "url": {
  236.      *         "source": "core/post-meta",
  237.      *         "args": { "key": "url_custom_field" }
  238.      *       }
  239.      *     }
  240.      *   }
  241.      * }
  242.      * ```
  243.      *
  244.      * The above example will replace the `title` and `url` attributes of the Image
  245.      * block with the values of the `text_custom_field` and `url_custom_field` post meta.
  246.      *
  247.      * @since 6.5.0
  248.      * @since 6.6.0 Handle the `__default` attribute for pattern overrides.
  249.      * @since 6.7.0 Return any updated bindings metadata in the computed attributes.
  250.      *
  251.      * @return array The computed block attributes for the provided block bindings.
  252.      */
  253.     private function process_block_bindings() {
  254.         $parsed_block               $this->parsed_block;
  255.         $computed_attributes        = array();
  256.         $supported_block_attributes = array(
  257.             'core/paragraph' => array( 'content' ),
  258.             'core/heading'   => array( 'content' ),
  259.             'core/image'     => array( 'id''url''title''alt' ),
  260.             'core/button'    => array( 'url''text''linkTarget''rel' ),
  261.         );
  262.         // If the block doesn't have the bindings property, isn't one of the supported
  263.         // block types, or the bindings property is not an array, return the block content.
  264.         if (
  265.             ! isset( $supported_block_attributes$this->name ] ) ||
  266.             empty( $parsed_block['attrs']['metadata']['bindings'] ) ||
  267.             ! is_array$parsed_block['attrs']['metadata']['bindings'] )
  268.         ) {
  269.             return $computed_attributes;
  270.         }
  271.         $bindings $parsed_block['attrs']['metadata']['bindings'];
  272.         /*
  273.          * If the default binding is set for pattern overrides, replace it
  274.          * with a pattern override binding for all supported attributes.
  275.          */
  276.         if (
  277.             isset( $bindings['__default']['source'] ) &&
  278.             'core/pattern-overrides' === $bindings['__default']['source']
  279.         ) {
  280.             $updated_bindings = array();
  281.             /*
  282.              * Build a binding array of all supported attributes.
  283.              * Note that this also omits the `__default` attribute from the
  284.              * resulting array.
  285.              */
  286.             foreach ( $supported_block_attributes$parsed_block['blockName'] ] as $attribute_name ) {
  287.                 // Retain any non-pattern override bindings that might be present.
  288.                 $updated_bindings$attribute_name ] = isset( $bindings$attribute_name ] )
  289.                     ? $bindings$attribute_name ]
  290.                     : array( 'source' => 'core/pattern-overrides' );
  291.             }
  292.             $bindings $updated_bindings;
  293.             /*
  294.              * Update the bindings metadata of the computed attributes.
  295.              * This ensures the block receives the expanded __default binding metadata when it renders.
  296.              */
  297.             $computed_attributes['metadata'] = array_merge(
  298.                 $parsed_block['attrs']['metadata'],
  299.                 array( 'bindings' => $bindings )
  300.             );
  301.         }
  302.         foreach ( $bindings as $attribute_name => $block_binding ) {
  303.             // If the attribute is not in the supported list, process next attribute.
  304.             if ( ! in_array$attribute_name$supported_block_attributes$this->name ], true ) ) {
  305.                 continue;
  306.             }
  307.             // If no source is provided, or that source is not registered, process next attribute.
  308.             if ( ! isset( $block_binding['source'] ) || ! is_string$block_binding['source'] ) ) {
  309.                 continue;
  310.             }
  311.             $block_binding_source get_block_bindings_source$block_binding['source'] );
  312.             if ( null === $block_binding_source ) {
  313.                 continue;
  314.             }
  315.             // Adds the necessary context defined by the source.
  316.             if ( ! empty( $block_binding_source->uses_context ) ) {
  317.                 foreach ( $block_binding_source->uses_context as $context_name ) {
  318.                     if ( array_key_exists$context_name$this->available_context ) ) {
  319.                         $this->context$context_name ] = $this->available_context$context_name ];
  320.                     }
  321.                 }
  322.             }
  323.             $source_args  = ! empty( $block_binding['args'] ) && is_array$block_binding['args'] ) ? $block_binding['args'] : array();
  324.             $source_value $block_binding_source->get_value$source_args$this$attribute_name );
  325.             // If the value is not null, process the HTML based on the block and the attribute.
  326.             if ( ! is_null$source_value ) ) {
  327.                 $computed_attributes$attribute_name ] = $source_value;
  328.             }
  329.         }
  330.         return $computed_attributes;
  331.     }
  332.     /**
  333.      * Depending on the block attribute name, replace its value in the HTML based on the value provided.
  334.      *
  335.      * @since 6.5.0
  336.      *
  337.      * @param string $block_content  Block content.
  338.      * @param string $attribute_name The attribute name to replace.
  339.      * @param mixed  $source_value   The value used to replace in the HTML.
  340.      * @return string The modified block content.
  341.      */
  342.     private function replace_htmlstring $block_contentstring $attribute_name$source_value ) {
  343.         $block_type $this->block_type;
  344.         if ( ! isset( $block_type->attributes$attribute_name ]['source'] ) ) {
  345.             return $block_content;
  346.         }
  347.         // Depending on the attribute source, the processing will be different.
  348.         switch ( $block_type->attributes$attribute_name ]['source'] ) {
  349.             case 'html':
  350.             case 'rich-text':
  351.                 $block_reader = new WP_HTML_Tag_Processor$block_content );
  352.                 // TODO: Support for CSS selectors whenever they are ready in the HTML API.
  353.                 // In the meantime, support comma-separated selectors by exploding them into an array.
  354.                 $selectors explode','$block_type->attributes$attribute_name ]['selector'] );
  355.                 // Add a bookmark to the first tag to be able to iterate over the selectors.
  356.                 $block_reader->next_tag();
  357.                 $block_reader->set_bookmark'iterate-selectors' );
  358.                 // TODO: This shouldn't be needed when the `set_inner_html` function is ready.
  359.                 // Store the parent tag and its attributes to be able to restore them later in the button.
  360.                 // The button block has a wrapper while the paragraph and heading blocks don't.
  361.                 if ( 'core/button' === $this->name ) {
  362.                     $button_wrapper                 $block_reader->get_tag();
  363.                     $button_wrapper_attribute_names $block_reader->get_attribute_names_with_prefix'' );
  364.                     $button_wrapper_attrs           = array();
  365.                     foreach ( $button_wrapper_attribute_names as $name ) {
  366.                         $button_wrapper_attrs$name ] = $block_reader->get_attribute$name );
  367.                     }
  368.                 }
  369.                 foreach ( $selectors as $selector ) {
  370.                     // If the parent tag, or any of its children, matches the selector, replace the HTML.
  371.                     if ( strcasecmp$block_reader->get_tag(), $selector ) === || $block_reader->next_tag(
  372.                         array(
  373.                             'tag_name' => $selector,
  374.                         )
  375.                     ) ) {
  376.                         $block_reader->release_bookmark'iterate-selectors' );
  377.                         // TODO: Use `set_inner_html` method whenever it's ready in the HTML API.
  378.                         // Until then, it is hardcoded for the paragraph, heading, and button blocks.
  379.                         // Store the tag and its attributes to be able to restore them later.
  380.                         $selector_attribute_names $block_reader->get_attribute_names_with_prefix'' );
  381.                         $selector_attrs           = array();
  382.                         foreach ( $selector_attribute_names as $name ) {
  383.                             $selector_attrs$name ] = $block_reader->get_attribute$name );
  384.                         }
  385.                         $selector_markup "<$selector>" wp_kses_post$source_value ) . "</$selector>";
  386.                         $amended_content = new WP_HTML_Tag_Processor$selector_markup );
  387.                         $amended_content->next_tag();
  388.                         foreach ( $selector_attrs as $attribute_key => $attribute_value ) {
  389.                             $amended_content->set_attribute$attribute_key$attribute_value );
  390.                         }
  391.                         if ( 'core/paragraph' === $this->name || 'core/heading' === $this->name ) {
  392.                             return $amended_content->get_updated_html();
  393.                         }
  394.                         if ( 'core/button' === $this->name ) {
  395.                             $button_markup  "<$button_wrapper>{$amended_content->get_updated_html()}</$button_wrapper>";
  396.                             $amended_button = new WP_HTML_Tag_Processor$button_markup );
  397.                             $amended_button->next_tag();
  398.                             foreach ( $button_wrapper_attrs as $attribute_key => $attribute_value ) {
  399.                                 $amended_button->set_attribute$attribute_key$attribute_value );
  400.                             }
  401.                             return $amended_button->get_updated_html();
  402.                         }
  403.                     } else {
  404.                         $block_reader->seek'iterate-selectors' );
  405.                     }
  406.                 }
  407.                 $block_reader->release_bookmark'iterate-selectors' );
  408.                 return $block_content;
  409.             case 'attribute':
  410.                 $amended_content = new WP_HTML_Tag_Processor$block_content );
  411.                 if ( ! $amended_content->next_tag(
  412.                     array(
  413.                         // TODO: build the query from CSS selector.
  414.                         'tag_name' => $block_type->attributes$attribute_name ]['selector'],
  415.                     )
  416.                 ) ) {
  417.                     return $block_content;
  418.                 }
  419.                 $amended_content->set_attribute$block_type->attributes$attribute_name ]['attribute'], $source_value );
  420.                 return $amended_content->get_updated_html();
  421.             default:
  422.                 return $block_content;
  423.         }
  424.     }
  425.     /**
  426.      * Generates the render output for the block.
  427.      *
  428.      * @since 5.5.0
  429.      * @since 6.5.0 Added block bindings processing.
  430.      *
  431.      * @global WP_Post $post Global post object.
  432.      *
  433.      * @param array $options {
  434.      *     Optional options object.
  435.      *
  436.      *     @type bool $dynamic Defaults to 'true'. Optionally set to false to avoid using the block's render_callback.
  437.      * }
  438.      * @return string Rendered block output.
  439.      */
  440.     public function render$options = array() ) {
  441.         global $post;
  442.         /*
  443.          * There can be only one root interactive block at a time because the rendered HTML of that block contains
  444.          * the rendered HTML of all its inner blocks, including any interactive block.
  445.          */
  446.         static $root_interactive_block null;
  447.         /**
  448.          * Filters whether Interactivity API should process directives.
  449.          *
  450.          * @since 6.6.0
  451.          *
  452.          * @param bool $enabled Whether the directives processing is enabled.
  453.          */
  454.         $interactivity_process_directives_enabled apply_filters'interactivity_process_directives'true );
  455.         if (
  456.             $interactivity_process_directives_enabled && null === $root_interactive_block && (
  457.                 ( isset( $this->block_type->supports['interactivity'] ) && true === $this->block_type->supports['interactivity'] ) ||
  458.                 ! empty( $this->block_type->supports['interactivity']['interactive'] )
  459.             )
  460.         ) {
  461.             $root_interactive_block $this;
  462.         }
  463.         $options wp_parse_args(
  464.             $options,
  465.             array(
  466.                 'dynamic' => true,
  467.             )
  468.         );
  469.         // Process the block bindings and get attributes updated with the values from the sources.
  470.         $computed_attributes $this->process_block_bindings();
  471.         if ( ! empty( $computed_attributes ) ) {
  472.             // Merge the computed attributes with the original attributes.
  473.             $this->attributes array_merge$this->attributes$computed_attributes );
  474.         }
  475.         $is_dynamic    $options['dynamic'] && $this->name && null !== $this->block_type && $this->block_type->is_dynamic();
  476.         $block_content '';
  477.         if ( ! $options['dynamic'] || empty( $this->block_type->skip_inner_blocks ) ) {
  478.             $index 0;
  479.             foreach ( $this->inner_content as $chunk ) {
  480.                 if ( is_string$chunk ) ) {
  481.                     $block_content .= $chunk;
  482.                 } else {
  483.                     $inner_block  $this->inner_blocks$index ];
  484.                     $parent_block $this;
  485.                     /** This filter is documented in wp-includes/blocks.php */
  486.                     $pre_render apply_filters'pre_render_block'null$inner_block->parsed_block$parent_block );
  487.                     if ( ! is_null$pre_render ) ) {
  488.                         $block_content .= $pre_render;
  489.                     } else {
  490.                         $source_block        $inner_block->parsed_block;
  491.                         $inner_block_context $inner_block->context;
  492.                         /** This filter is documented in wp-includes/blocks.php */
  493.                         $inner_block->parsed_block apply_filters'render_block_data'$inner_block->parsed_block$source_block$parent_block );
  494.                         /** This filter is documented in wp-includes/blocks.php */
  495.                         $inner_block->context apply_filters'render_block_context'$inner_block->context$inner_block->parsed_block$parent_block );
  496.                         /*
  497.                          * The `refresh_context_dependents()` method already calls `refresh_parsed_block_dependents()`.
  498.                          * Therefore the second condition is irrelevant if the first one is satisfied.
  499.                          */
  500.                         if ( $inner_block->context !== $inner_block_context ) {
  501.                             $inner_block->refresh_context_dependents();
  502.                         } elseif ( $inner_block->parsed_block !== $source_block ) {
  503.                             $inner_block->refresh_parsed_block_dependents();
  504.                         }
  505.                         $block_content .= $inner_block->render();
  506.                     }
  507.                     ++$index;
  508.                 }
  509.             }
  510.         }
  511.         if ( ! empty( $computed_attributes ) && ! empty( $block_content ) ) {
  512.             foreach ( $computed_attributes as $attribute_name => $source_value ) {
  513.                 $block_content $this->replace_html$block_content$attribute_name$source_value );
  514.             }
  515.         }
  516.         if ( $is_dynamic ) {
  517.             $global_post $post;
  518.             $parent      WP_Block_Supports::$block_to_render;
  519.             WP_Block_Supports::$block_to_render $this->parsed_block;
  520.             $block_content = (string) call_user_func$this->block_type->render_callback$this->attributes$block_content$this );
  521.             WP_Block_Supports::$block_to_render $parent;
  522.             $post $global_post;
  523.         }
  524.         if ( ( ! empty( $this->block_type->script_handles ) ) ) {
  525.             foreach ( $this->block_type->script_handles as $script_handle ) {
  526.                 wp_enqueue_script$script_handle );
  527.             }
  528.         }
  529.         if ( ! empty( $this->block_type->view_script_handles ) ) {
  530.             foreach ( $this->block_type->view_script_handles as $view_script_handle ) {
  531.                 wp_enqueue_script$view_script_handle );
  532.             }
  533.         }
  534.         if ( ! empty( $this->block_type->view_script_module_ids ) ) {
  535.             foreach ( $this->block_type->view_script_module_ids as $view_script_module_id ) {
  536.                 wp_enqueue_script_module$view_script_module_id );
  537.             }
  538.         }
  539.         /*
  540.          * For Core blocks, these styles are only enqueued if `wp_should_load_separate_core_block_assets()` returns
  541.          * true. Otherwise these `wp_enqueue_style()` calls will not have any effect, as the Core blocks are relying on
  542.          * the combined 'wp-block-library' stylesheet instead, which is unconditionally enqueued.
  543.          */
  544.         if ( ( ! empty( $this->block_type->style_handles ) ) ) {
  545.             foreach ( $this->block_type->style_handles as $style_handle ) {
  546.                 wp_enqueue_style$style_handle );
  547.             }
  548.         }
  549.         if ( ( ! empty( $this->block_type->view_style_handles ) ) ) {
  550.             foreach ( $this->block_type->view_style_handles as $view_style_handle ) {
  551.                 wp_enqueue_style$view_style_handle );
  552.             }
  553.         }
  554.         /**
  555.          * Filters the content of a single block.
  556.          *
  557.          * @since 5.0.0
  558.          * @since 5.9.0 The `$instance` parameter was added.
  559.          *
  560.          * @param string   $block_content The block content.
  561.          * @param array    $block         The full block, including name and attributes.
  562.          * @param WP_Block $instance      The block instance.
  563.          */
  564.         $block_content apply_filters'render_block'$block_content$this->parsed_block$this );
  565.         /**
  566.          * Filters the content of a single block.
  567.          *
  568.          * The dynamic portion of the hook name, `$name`, refers to
  569.          * the block name, e.g. "core/paragraph".
  570.          *
  571.          * @since 5.7.0
  572.          * @since 5.9.0 The `$instance` parameter was added.
  573.          *
  574.          * @param string   $block_content The block content.
  575.          * @param array    $block         The full block, including name and attributes.
  576.          * @param WP_Block $instance      The block instance.
  577.          */
  578.         $block_content apply_filters"render_block_{$this->name}"$block_content$this->parsed_block$this );
  579.         if ( $root_interactive_block === $this ) {
  580.             // The root interactive block has finished rendering. Time to process directives.
  581.             $block_content          wp_interactivity_process_directives$block_content );
  582.             $root_interactive_block null;
  583.         }
  584.         return $block_content;
  585.     }
  586. }