<!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>[58234] trunk: Interactivity API: Move directive processing to `WP_Block` class</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/58234">58234</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/58234","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>gziolo</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2024-05-29 11:55:27 +0000 (Wed, 29 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'>Interactivity API: Move directive processing to `WP_Block` class

Integrates the directives processing into the WP_Block class. It removes the overhead of running additional hooks when rendering blocks and simplifies the way we detect whether the directive processing should run on an interactive region of the produced final HTML for the blocks.

Introduces `interactivity_process_directives` filter to offer a way to opt out from directives processing. It's needed in Gutenberg: https://github.com/WordPress/gutenberg/pull/62095.

Props gziolo, cbravobernal.
Fixes <a href="https://core.trac.wordpress.org/ticket/61185">#61185</a>.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunksrcwpincludesclasswpblockphp">trunk/src/wp-includes/class-wp-block.php</a></li>
<li><a href="#trunksrcwpincludesdeprecatedphp">trunk/src/wp-includes/deprecated.php</a></li>
<li><a href="#trunksrcwpincludesinteractivityapiinteractivityapiphp">trunk/src/wp-includes/interactivity-api/interactivity-api.php</a></li>
<li><a href="#trunktestsphpunittestsinteractivityapiwpInteractivityAPIFunctionsphp">trunk/tests/phpunit/tests/interactivity-api/wpInteractivityAPIFunctions.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunksrcwpincludesclasswpblockphp"></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.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/class-wp-block.php  2024-05-29 11:40:16 UTC (rev 58233)
+++ trunk/src/wp-includes/class-wp-block.php    2024-05-29 11:55:27 UTC (rev 58234)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -408,6 +408,29 @@
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public function render( $options = array() ) {
</span><span class="cx" style="display: block; padding: 0 10px">                global $post;
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+               /*
+                * There can be only one root interactive block at a time because the rendered HTML of that block contains
+                * the rendered HTML of all its inner blocks, including any interactive block.
+                */
+               static $root_interactive_block  = null;
+               /**
+                * Filters whether Interactivity API should process directives.
+                *
+                * @since 6.6.0
+                *
+                * @param bool $enabled Whether the directives processing is enabled.
+                */
+               $interactivity_process_directives_enabled = apply_filters( 'interactivity_process_directives', true );
+               if (
+                       $interactivity_process_directives_enabled && null === $root_interactive_block && (
+                               ( isset( $this->block_type->supports['interactivity'] ) && true === $this->block_type->supports['interactivity'] ) ||
+                               ! empty( $this->block_type->supports['interactivity']['interactive'] )
+                       )
+               ) {
+                       $root_interactive_block = $this;
+               }
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 $options = wp_parse_args(
</span><span class="cx" style="display: block; padding: 0 10px">                        $options,
</span><span class="cx" style="display: block; padding: 0 10px">                        array(
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -533,6 +556,12 @@
</span><span class="cx" style="display: block; padding: 0 10px">                 */
</span><span class="cx" style="display: block; padding: 0 10px">                $block_content = apply_filters( "render_block_{$this->name}", $block_content, $this->parsed_block, $this );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                if ( $root_interactive_block === $this ) {
+                       // The root interactive block has finished rendering. Time to process directives.
+                       $block_content          = wp_interactivity_process_directives( $block_content );
+                       $root_interactive_block = null;
+               }
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 return $block_content;
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre></div>
<a id="trunksrcwpincludesdeprecatedphp"></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/deprecated.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/deprecated.php      2024-05-29 11:40:16 UTC (rev 58233)
+++ trunk/src/wp-includes/deprecated.php        2024-05-29 11:55:27 UTC (rev 58234)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -6318,3 +6318,22 @@
</span><span class="cx" style="display: block; padding: 0 10px">        _deprecated_function( __FUNCTION__, '6.6.0', 'wp_render_elements_class_name' );
</span><span class="cx" style="display: block; padding: 0 10px">        return $block_content;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+/**
+ * Processes the directives on the rendered HTML of the interactive blocks.
+ *
+ * This processes only one root interactive block at a time because the
+ * rendered HTML of that block contains the rendered HTML of all its inner
+ * blocks, including any interactive block. It does so by ignoring all the
+ * interactive inner blocks until the root interactive block is processed.
+ *
+ * @since 6.5.0
+ * @deprecated 6.6.0
+ *
+ * @param array $parsed_block The parsed block.
+ * @return array The same parsed block.
+ */
+function wp_interactivity_process_directives_of_interactive_blocks( array $parsed_block ): array {
+       _deprecated_function( __FUNCTION__, '6.6.0' );
+       return $parsed_block;
+}
</ins></span></pre></div>
<a id="trunksrcwpincludesinteractivityapiinteractivityapiphp"></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/interactivity-api/interactivity-api.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/interactivity-api/interactivity-api.php     2024-05-29 11:40:16 UTC (rev 58233)
+++ trunk/src/wp-includes/interactivity-api/interactivity-api.php       2024-05-29 11:55:27 UTC (rev 58234)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -8,71 +8,6 @@
</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><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * Processes the directives on the rendered HTML of the interactive blocks.
- *
- * This processes only one root interactive block at a time because the
- * rendered HTML of that block contains the rendered HTML of all its inner
- * blocks, including any interactive block. It does so by ignoring all the
- * interactive inner blocks until the root interactive block is processed.
- *
- * @since 6.5.0
- *
- * @param array $parsed_block The parsed block.
- * @return array The same parsed block.
- */
-function wp_interactivity_process_directives_of_interactive_blocks( array $parsed_block ): array {
-       static $root_interactive_block = null;
-
-       /*
-        * Checks whether a root interactive block is already annotated for
-        * processing, and if it is, it ignores the subsequent ones.
-        */
-       if ( null === $root_interactive_block ) {
-               $block_name = $parsed_block['blockName'];
-               $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block_name );
-
-               if (
-                       isset( $block_name ) &&
-                       ( ( isset( $block_type->supports['interactivity'] ) && true === $block_type->supports['interactivity'] ) ||
-                       ( isset( $block_type->supports['interactivity']['interactive'] ) && true === $block_type->supports['interactivity']['interactive'] ) )
-               ) {
-                       // Annotates the root interactive block for processing.
-                       $root_interactive_block = array( $block_name, $parsed_block );
-
-                       /*
-                        * Adds a filter to process the root interactive block once it has
-                        * finished rendering.
-                        */
-                       $process_interactive_blocks = static function ( string $content, array $parsed_block ) use ( &$root_interactive_block, &$process_interactive_blocks ): string {
-                               // Checks whether the current block is the root interactive block.
-                               list($root_block_name, $root_parsed_block) = $root_interactive_block;
-                               if ( $root_block_name === $parsed_block['blockName'] && $parsed_block === $root_parsed_block ) {
-                                       // The root interactive blocks has finished rendering, process it.
-                                       $content = wp_interactivity_process_directives( $content );
-                                       // Removes the filter and reset the root interactive block.
-                                       remove_filter( 'render_block_' . $parsed_block['blockName'], $process_interactive_blocks );
-                                       $root_interactive_block = null;
-                               }
-                               return $content;
-                       };
-
-                       /*
-                        * Uses a priority of 100 to ensure that other filters can add additional
-                        * directives before the processing starts.
-                        */
-                       add_filter( 'render_block_' . $block_name, $process_interactive_blocks, 100, 2 );
-               }
-       }
-
-       return $parsed_block;
-}
-/*
- * Uses a priority of 100 to ensure that other filters can add additional attributes to
- * $parsed_block before the processing starts.
- */
-add_filter( 'render_block_data', 'wp_interactivity_process_directives_of_interactive_blocks', 100, 1 );
-
-/**
</del><span class="cx" style="display: block; padding: 0 10px">  * Retrieves the main WP_Interactivity_API instance.
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * It provides access to the WP_Interactivity_API instance, creating one if it
</span></span></pre></div>
<a id="trunktestsphpunittestsinteractivityapiwpInteractivityAPIFunctionsphp"></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/interactivity-api/wpInteractivityAPIFunctions.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/interactivity-api/wpInteractivityAPIFunctions.php       2024-05-29 11:40:16 UTC (rev 58233)
+++ trunk/tests/phpunit/tests/interactivity-api/wpInteractivityAPIFunctions.php 2024-05-29 11:55:27 UTC (rev 58234)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -525,4 +525,40 @@
</span><span class="cx" style="display: block; padding: 0 10px">                unregister_block_type( 'test/custom-directive-block' );
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertNull( $input_value );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+       /**
+        * Tests interactivity_process_directives filter.
+        *
+        * @ticket 61185
+        *
+        * @covers wp_interactivity_process_directives
+        */
+       public function test_not_processing_directives_filter() {
+               wp_interactivity_state(
+                       'dont-process',
+                       array(
+                               'text' => 'text',
+                       )
+               );
+               register_block_type(
+                       'test/custom-directive-block',
+                       array(
+                               'render_callback' => function () {
+                                       return '<div data-wp-interactive="dont-process"><input data-wp-bind--value="state.text" /></div>';
+                               },
+                               'supports'        => array(
+                                       'interactivity' => true,
+                               ),
+                       )
+               );
+               $post_content = '<!-- wp:test/custom-directive-block /-->';
+               add_filter( 'interactivity_process_directives', '__return_false' );
+               $processed_content = do_blocks( $post_content );
+               $processor         = new WP_HTML_Tag_Processor( $processed_content );
+               $processor->next_tag( array( 'tag_name' => 'input' ) );
+               $input_value = $processor->get_attribute( 'value' );
+               remove_filter( 'interactivity_process_directives', '__return_false' );
+               unregister_block_type( 'test/custom-directive-block' );
+               $this->assertNull( $input_value );
+       }
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre>
</div>
</div>

</body>
</html>