<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[58186] trunk: Block Hooks API: Insert metadata at the same time as hooked blocks.</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { white-space: pre-line; overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="https://core.trac.wordpress.org/changeset/58186">58186</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"https://core.trac.wordpress.org/changeset/58186","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>Bernhard Reiter</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2024-05-23 18:33:11 +0000 (Thu, 23 May 2024)</dd>
</dl>

<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>Block Hooks API: Insert metadata at the same time as hooked blocks.

The Block Hooks UI relies on the `ignoredHookedBlocks` metadata when determining whether to show the toggle in the Site Editor. The problem is that for uncustomized templates we don't add this metadata.

Currently the visitor functions have a default callback of `insert_hooked_blocks` and we only add the metadata when we're writing to the database via an available filter in the template controller.

This changeset creates a new callback which both inserts the hooked blocks and adds the `ignoredHookedBlocks` metadata to the anchor block, and uses this new callback explicitly in the visitor functions that are run upon reading from the database.

We continue to set the `ignoredHookedBlocks` metadata when writing to the database, i.e. this operation happens twice. Although not ideal, this is necessary to cover the following scenarios:

* When the user adds an anchor block within the editor, we still need to add the `ignoredHookedBlocks` meta to it to prevent hooked blocks hooking on to it unexpectedly on the frontend. This is required to keep parity between the frontend and editor.
* When a user writes template data to the database directly through the API (instead of the editor), we need to again ensure we're not inserting hooked blocks unexpectedly.

It is worth noting that with this change, the first hooked block to insert relative to its anchor block will be accepted. Any additional blocks of the same type (e.g. a second `core/loginout` block) trying to hook onto the same anchor block will be ignored, irrespective of the position.

Props tomjcafferkey, bernhard-reiter, gziolo.
Fixes <a href="https://core.trac.wordpress.org/ticket/59574">#59574</a>.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunksrcwpincludesblocktemplateutilsphp">trunk/src/wp-includes/block-template-utils.php</a></li>
<li><a href="#trunksrcwpincludesblocksphp">trunk/src/wp-includes/blocks.php</a></li>
<li><a href="#trunksrcwpincludesclasswpblockpatternsregistryphp">trunk/src/wp-includes/class-wp-block-patterns-registry.php</a></li>
<li><a href="#trunktestsphpunittestsblocksgetHookedBlocksphp">trunk/tests/phpunit/tests/blocks/getHookedBlocks.php</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#trunktestsphpunittestsblocksinsertHookedBlocksAndSetIgnoredHookedBlocksMetadataphp">trunk/tests/phpunit/tests/blocks/insertHookedBlocksAndSetIgnoredHookedBlocksMetadata.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunksrcwpincludesblocktemplateutilsphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/block-template-utils.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/block-template-utils.php    2024-05-23 16:12:31 UTC (rev 58185)
+++ trunk/src/wp-includes/block-template-utils.php      2024-05-23 18:33:11 UTC (rev 58186)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -598,8 +598,8 @@
</span><span class="cx" style="display: block; padding: 0 10px">        $after_block_visitor  = null;
</span><span class="cx" style="display: block; padding: 0 10px">        $hooked_blocks        = get_hooked_blocks();
</span><span class="cx" style="display: block; padding: 0 10px">        if ( ! empty( $hooked_blocks ) || has_filter( 'hooked_block_types' ) ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $before_block_visitor = make_before_block_visitor( $hooked_blocks, $template );
-               $after_block_visitor  = make_after_block_visitor( $hooked_blocks, $template );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $before_block_visitor = make_before_block_visitor( $hooked_blocks, $template, 'insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata' );
+               $after_block_visitor  = make_after_block_visitor( $hooked_blocks, $template, 'insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata' );
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px">        $blocks            = parse_blocks( $template->content );
</span><span class="cx" style="display: block; padding: 0 10px">        $template->content = traverse_and_serialize_blocks( $blocks, $before_block_visitor, $after_block_visitor );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -984,8 +984,8 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        $hooked_blocks = get_hooked_blocks();
</span><span class="cx" style="display: block; padding: 0 10px">        if ( ! empty( $hooked_blocks ) || has_filter( 'hooked_block_types' ) ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $before_block_visitor = make_before_block_visitor( $hooked_blocks, $template );
-               $after_block_visitor  = make_after_block_visitor( $hooked_blocks, $template );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $before_block_visitor = make_before_block_visitor( $hooked_blocks, $template, 'insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata' );
+               $after_block_visitor  = make_after_block_visitor( $hooked_blocks, $template, 'insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata' );
</ins><span class="cx" style="display: block; padding: 0 10px">                 $blocks               = parse_blocks( $template->content );
</span><span class="cx" style="display: block; padding: 0 10px">                $template->content    = traverse_and_serialize_blocks( $blocks, $before_block_visitor, $after_block_visitor );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span></span></pre></div>
<a id="trunksrcwpincludesblocksphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/blocks.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/blocks.php  2024-05-23 16:12:31 UTC (rev 58185)
+++ trunk/src/wp-includes/blocks.php    2024-05-23 18:33:11 UTC (rev 58186)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -858,11 +858,11 @@
</span><span class="cx" style="display: block; padding: 0 10px">  * @since 6.5.0
</span><span class="cx" style="display: block; padding: 0 10px">  * @access private
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * @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.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @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.
</ins><span class="cx" style="display: block; padding: 0 10px">  * @return string
</span><span class="cx" style="display: block; padding: 0 10px">  */
</span><span class="cx" style="display: block; padding: 0 10px"> function insert_hooked_blocks( &$parsed_anchor_block, $relative_position, $hooked_blocks, $context ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -949,12 +949,12 @@
</span><span class="cx" style="display: block; padding: 0 10px">  * @since 6.5.0
</span><span class="cx" style="display: block; padding: 0 10px">  * @access private
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * @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.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @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.
</ins><span class="cx" style="display: block; padding: 0 10px">  */
</span><span class="cx" style="display: block; padding: 0 10px"> function set_ignored_hooked_blocks_metadata( &$parsed_anchor_block, $relative_position, $hooked_blocks, $context ) {
</span><span class="cx" style="display: block; padding: 0 10px">        $anchor_block_type  = $parsed_anchor_block['blockName'];
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1003,6 +1003,29 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * 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;
+}
+
+/**
</ins><span class="cx" style="display: block; padding: 0 10px">  * Returns a function that injects the theme attribute into, and hooked blocks before, a given block.
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * The returned function can be used as `$pre_callback` argument to `traverse_and_serialize_block(s)`,
</span></span></pre></div>
<a id="trunksrcwpincludesclasswpblockpatternsregistryphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/class-wp-block-patterns-registry.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/class-wp-block-patterns-registry.php        2024-05-23 16:12:31 UTC (rev 58185)
+++ trunk/src/wp-includes/class-wp-block-patterns-registry.php  2024-05-23 18:33:11 UTC (rev 58186)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -174,8 +174,8 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $before_block_visitor = '_inject_theme_attribute_in_template_part_block';
</span><span class="cx" style="display: block; padding: 0 10px">                $after_block_visitor  = null;
</span><span class="cx" style="display: block; padding: 0 10px">                if ( ! empty( $hooked_blocks ) || has_filter( 'hooked_block_types' ) ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        $before_block_visitor = make_before_block_visitor( $hooked_blocks, $pattern );
-                       $after_block_visitor  = make_after_block_visitor( $hooked_blocks, $pattern );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 $before_block_visitor = make_before_block_visitor( $hooked_blocks, $pattern, 'insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata' );
+                       $after_block_visitor  = make_after_block_visitor( $hooked_blocks, $pattern, 'insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata' );
</ins><span class="cx" style="display: block; padding: 0 10px">                 }
</span><span class="cx" style="display: block; padding: 0 10px">                $blocks  = parse_blocks( $content );
</span><span class="cx" style="display: block; padding: 0 10px">                $content = traverse_and_serialize_blocks( $blocks, $before_block_visitor, $after_block_visitor );
</span></span></pre></div>
<a id="trunktestsphpunittestsblocksgetHookedBlocksphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/blocks/getHookedBlocks.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/blocks/getHookedBlocks.php      2024-05-23 16:12:31 UTC (rev 58185)
+++ trunk/tests/phpunit/tests/blocks/getHookedBlocks.php        2024-05-23 18:33:11 UTC (rev 58186)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -151,7 +151,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        $template->content
</span><span class="cx" style="display: block; padding: 0 10px">                );
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertStringContainsString(
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        '<!-- wp:post-content {"layout":{"type":"constrained"}} /-->'
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 '<!-- wp:post-content {"layout":{"type":"constrained"},"metadata":{"ignoredHookedBlocks":["tests/hooked-after"]}} /-->'
</ins><span class="cx" style="display: block; padding: 0 10px">                         . '<!-- wp:tests/hooked-after /-->',
</span><span class="cx" style="display: block; padding: 0 10px">                        $template->content
</span><span class="cx" style="display: block; padding: 0 10px">                );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -180,7 +180,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertStringContainsString(
</span><span class="cx" style="display: block; padding: 0 10px">                        '<!-- wp:tests/hooked-before /-->'
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        . '<!-- wp:navigation {"layout":{"type":"flex","setCascadingProperties":true,"justifyContent":"right"}} /-->',
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 . '<!-- wp:navigation {"layout":{"type":"flex","setCascadingProperties":true,"justifyContent":"right"},"metadata":{"ignoredHookedBlocks":["tests/hooked-before"]}} /-->',
</ins><span class="cx" style="display: block; padding: 0 10px">                         $template->content
</span><span class="cx" style="display: block; padding: 0 10px">                );
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertStringNotContainsString(
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -221,7 +221,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        $pattern['content']
</span><span class="cx" style="display: block; padding: 0 10px">                );
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertStringContainsString(
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        '<!-- wp:comments -->'
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 '<!-- wp:comments {"metadata":{"ignoredHookedBlocks":["tests/hooked-first-child"]}} -->'
</ins><span class="cx" style="display: block; padding: 0 10px">                         . '<div class="wp-block-comments">'
</span><span class="cx" style="display: block; padding: 0 10px">                        . '<!-- wp:tests/hooked-first-child /-->',
</span><span class="cx" style="display: block; padding: 0 10px">                        str_replace( array( "\n", "\t" ), '', $pattern['content'] )
</span></span></pre></div>
<a id="trunktestsphpunittestsblocksinsertHookedBlocksAndSetIgnoredHookedBlocksMetadataphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/phpunit/tests/blocks/insertHookedBlocksAndSetIgnoredHookedBlocksMetadata.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/blocks/insertHookedBlocksAndSetIgnoredHookedBlocksMetadata.php                          (rev 0)
+++ trunk/tests/phpunit/tests/blocks/insertHookedBlocksAndSetIgnoredHookedBlocksMetadata.php    2024-05-23 18:33:11 UTC (rev 58186)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,246 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * Tests for the insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata function.
+ *
+ * @package WordPress
+ * @subpackage Blocks
+ *
+ * @since 6.6.0
+ *
+ * @group blocks
+ * @group block-hooks
+ * @covers ::insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata
+ */
+class Tests_Blocks_InsertHookedBlocksAndSetIgnoredHookedBlocksMetadata extends WP_UnitTestCase {
+       const ANCHOR_BLOCK_TYPE       = 'tests/anchor-block';
+       const HOOKED_BLOCK_TYPE       = 'tests/hooked-block';
+       const OTHER_HOOKED_BLOCK_TYPE = 'tests/other-hooked-block';
+
+       const HOOKED_BLOCKS = array(
+               self::ANCHOR_BLOCK_TYPE => array(
+                       'after'  => array( self::HOOKED_BLOCK_TYPE ),
+                       'before' => array( self::OTHER_HOOKED_BLOCK_TYPE ),
+               ),
+       );
+
+       /**
+        * @ticket 59574
+        */
+       private static function create_block_template_object() {
+               $template              = new WP_Block_Template();
+               $template->type        = 'wp_template';
+               $template->theme       = 'test-theme';
+               $template->slug        = 'single';
+               $template->id          = $template->theme . '//' . $template->slug;
+               $template->title       = 'Single';
+               $template->content     = '<!-- wp:tests/anchor-block /-->';
+               $template->description = 'Description of my template';
+
+               return $template;
+       }
+
+       /**
+        * @ticket 59574
+        */
+       public function test_insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata_returns_correct_markup_and_sets_metadata() {
+               $anchor_block = array(
+                       'blockName' => self::ANCHOR_BLOCK_TYPE,
+               );
+
+               $actual = insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata( $anchor_block, 'after', self::HOOKED_BLOCKS, array() );
+               $this->assertSame(
+                       '<!-- wp:' . self::HOOKED_BLOCK_TYPE . ' /-->',
+                       $actual,
+                       "Markup for hooked block wasn't generated correctly."
+               );
+               $this->assertSame(
+                       array( 'tests/hooked-block' ),
+                       $anchor_block['attrs']['metadata']['ignoredHookedBlocks'],
+                       "Block wasn't added to ignoredHookedBlocks metadata."
+               );
+       }
+
+       /**
+        * @ticket 59574
+        */
+       public function test_insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata_if_block_is_ignored() {
+               $anchor_block = array(
+                       'blockName' => 'tests/anchor-block',
+                       'attrs'     => array(
+                               'metadata' => array(
+                                       'ignoredHookedBlocks' => array( self::HOOKED_BLOCK_TYPE ),
+                               ),
+                       ),
+               );
+
+               $actual = insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata( $anchor_block, 'after', self::HOOKED_BLOCKS, array() );
+               $this->assertSame(
+                       '',
+                       $actual,
+                       "No markup should've been generated for ignored hooked block."
+               );
+               $this->assertSame(
+                       array( 'tests/hooked-block' ),
+                       $anchor_block['attrs']['metadata']['ignoredHookedBlocks'],
+                       "ignoredHookedBlocks metadata shouldn't have been modified."
+               );
+       }
+
+       /**
+        * @ticket 59574
+        */
+       public function test_insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata_if_other_block_is_ignored() {
+               $anchor_block = array(
+                       'blockName' => 'tests/anchor-block',
+                       'attrs'     => array(
+                               'metadata' => array(
+                                       'ignoredHookedBlocks' => array( 'tests/other-ignored-block' ),
+                               ),
+                       ),
+               );
+
+               $hooked_blocks = array(
+                       'tests/anchor-block' => array(
+                               'after' => array( 'tests/hooked-block' ),
+                       ),
+               );
+
+               $actual = insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata( $anchor_block, 'after', $hooked_blocks, array() );
+               $this->assertSame(
+                       '<!-- wp:' . self::HOOKED_BLOCK_TYPE . ' /-->',
+                       $actual,
+                       "Markup for newly hooked block should've been generated."
+               );
+               $this->assertSame(
+                       array( 'tests/other-ignored-block', 'tests/hooked-block' ),
+                       $anchor_block['attrs']['metadata']['ignoredHookedBlocks']
+               );
+       }
+
+       /**
+        * @ticket 59574
+        */
+       public function test_insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata_filter_can_suppress_hooked_block() {
+               $anchor_block = array(
+                       'blockName'    => self::ANCHOR_BLOCK_TYPE,
+                       'attrs'        => array(
+                               'layout' => array(
+                                       'type' => 'flex',
+                               ),
+                       ),
+                       'innerContent' => array(),
+               );
+
+               $filter = function ( $parsed_hooked_block, $hooked_block_type, $relative_position, $parsed_anchor_block ) {
+                       // Is the hooked block adjacent to the anchor block?
+                       if ( 'before' !== $relative_position && 'after' !== $relative_position ) {
+                               return $parsed_hooked_block;
+                       }
+
+                       if (
+                               isset( $parsed_anchor_block['attrs']['layout']['type'] ) &&
+                               'flex' === $parsed_anchor_block['attrs']['layout']['type']
+                       ) {
+                               return null;
+                       }
+
+                       return $parsed_hooked_block;
+               };
+               add_filter( 'hooked_block_' . self::HOOKED_BLOCK_TYPE, $filter, 10, 4 );
+               $actual = insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata( $anchor_block, 'after', self::HOOKED_BLOCKS, array() );
+               remove_filter( 'hooked_block_' . self::HOOKED_BLOCK_TYPE, $filter );
+
+               $this->assertSame( '', $actual, "No markup should've been generated for hooked block suppressed by filter." );
+               $this->assertSame(
+                       array(),
+                       $anchor_block['attrs']['metadata']['ignoredHookedBlocks'],
+                       "No block should've been added to ignoredHookedBlocks metadata."
+               );
+       }
+
+       /**
+        * @ticket 59574
+        */
+       public function test_insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata_added_by_context_aware_filter() {
+               $anchor_block = array(
+                       'blockName' => 'tests/anchor-block',
+                       'attrs'     => array(),
+               );
+
+               $filter = function ( $hooked_block_types, $relative_position, $anchor_block_type, $context ) {
+                       if (
+                               ! $context instanceof WP_Block_Template ||
+                               ! property_exists( $context, 'slug' ) ||
+                               'single' !== $context->slug
+                       ) {
+                               return $hooked_block_types;
+                       }
+
+                       if ( 'tests/anchor-block' === $anchor_block_type && 'after' === $relative_position ) {
+                               $hooked_block_types[] = 'tests/hooked-block-added-by-filter';
+                       }
+
+                       return $hooked_block_types;
+               };
+
+               $template = self::create_block_template_object();
+
+               add_filter( 'hooked_block_types', $filter, 10, 4 );
+               $actual = insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata( $anchor_block, 'after', array(), $template );
+               remove_filter( 'hooked_block_types', $filter, 10 );
+
+               $this->assertSame(
+                       '<!-- wp:tests/hooked-block-added-by-filter /-->',
+                       $actual,
+                       "Markup for hooked block added by filter wasn't generated correctly."
+               );
+               $this->assertSame(
+                       array( 'tests/hooked-block-added-by-filter' ),
+                       $anchor_block['attrs']['metadata']['ignoredHookedBlocks'],
+                       "Block added by filter wasn't added to ignoredHookedBlocks metadata."
+               );
+       }
+
+       /**
+        * @ticket 59574
+        */
+       public function test_insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata_for_block_suppressed_by_filter() {
+               $anchor_block = array(
+                       'blockName' => 'tests/anchor-block',
+                       'attrs'     => array(),
+               );
+
+               $hooked_blocks = array(
+                       'tests/anchor-block' => array(
+                               'after' => array( 'tests/hooked-block', 'tests/hooked-block-suppressed-by-filter' ),
+                       ),
+               );
+
+               $filter = function ( $parsed_hooked_block, $hooked_block_type, $relative_position, $parsed_anchor_block ) {
+                       if (
+                               'tests/hooked-block-suppressed-by-filter' === $hooked_block_type &&
+                               'after' === $relative_position &&
+                               'tests/anchor-block' === $parsed_anchor_block['blockName']
+                       ) {
+                               return null;
+                       }
+
+                       return $parsed_hooked_block;
+               };
+
+               add_filter( 'hooked_block', $filter, 10, 4 );
+               $actual = insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata( $anchor_block, 'after', $hooked_blocks, null );
+               remove_filter( 'hooked_block', $filter );
+
+               $this->assertSame(
+                       '<!-- wp:tests/hooked-block /-->',
+                       $actual,
+                       "Markup for hooked block wasn't generated correctly."
+               );
+               $this->assertSame(
+                       array( 'tests/hooked-block' ),
+                       $anchor_block['attrs']['metadata']['ignoredHookedBlocks'],
+                       "ignoredHookedBlocks metadata wasn't set correctly."
+               );
+       }
+}
</ins></span></pre>
</div>
</div>

</body>
</html>