<!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>[59874] trunk: Editor: Allow registering block type collections with a single function call.</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/59874">59874</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/59874","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>flixos90</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2025-02-26 19:38:08 +0000 (Wed, 26 Feb 2025)</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'>Editor: Allow registering block type collections with a single function call.

<a href="https://core.trac.wordpress.org/changeset/59132">[59132]</a> introduced the `wp_register_block_metadata_collection()` function and underlying `WP_Block_Metadata_Registry` class to allow central registration of a block metadata PHP manifest file in favor of parsing individual JSON files. While this improves performance, it only increases the amount of APIs and code that plugin developers need to use to register their block types properly.

This changeset introduces a new function `wp_register_block_types_from_metadata_collection()` that improves the developer experience of registering block types from a single source, by handling it in only a single function call.

Developers that already use a generated block metadata PHP manifest file (e.g. via the `wp-scripts build-blocks-manifest` tool) can now call `wp_register_block_types_from_metadata_collection()` with that file to automatically register all block types from that block metadata collection. Individual calls to `register_block_type()` or `register_block_type_from_metadata()` are no longer necessary when the new function is used.

Props flixos90, gziolo, joemcgill, mreishus, mukesh27, swissspidy.
Fixes <a href="https://core.trac.wordpress.org/ticket/62267">#62267</a>.
See <a href="https://core.trac.wordpress.org/ticket/62002">#62002</a>.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunksrcwpincludesblocksphp">trunk/src/wp-includes/blocks.php</a></li>
<li><a href="#trunksrcwpincludesclasswpblockmetadataregistryphp">trunk/src/wp-includes/class-wp-block-metadata-registry.php</a></li>
<li><a href="#trunktestsphpunittestsblockswpBlockMetadataRegistryphp">trunk/tests/phpunit/tests/blocks/wpBlockMetadataRegistry.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<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  2025-02-26 18:47:26 UTC (rev 59873)
+++ trunk/src/wp-includes/blocks.php    2025-02-26 19:38:08 UTC (rev 59874)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -376,6 +376,32 @@
</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">+ * Registers all block types from a block metadata collection.
+ *
+ * This can either reference a previously registered metadata collection or, if the `$manifest` parameter is provided,
+ * register the metadata collection directly within the same function call.
+ *
+ * @since 6.8.0
+ * @see wp_register_block_metadata_collection()
+ * @see register_block_type_from_metadata()
+ *
+ * @param string $path     The absolute base path for the collection ( e.g., WP_PLUGIN_DIR . '/my-plugin/blocks/' ).
+ * @param string $manifest Optional. The absolute path to the manifest file containing the metadata collection, in
+ *                         order to register the collection. If this parameter is not provided, the `$path` parameter
+ *                         must reference a previously registered block metadata collection.
+ */
+function wp_register_block_types_from_metadata_collection( $path, $manifest = '' ) {
+       if ( $manifest ) {
+               wp_register_block_metadata_collection( $path, $manifest );
+       }
+
+       $block_metadata_files = WP_Block_Metadata_Registry::get_collection_block_metadata_files( $path );
+       foreach ( $block_metadata_files as $block_metadata_file ) {
+               register_block_type_from_metadata( $block_metadata_file );
+       }
+}
+
+/**
</ins><span class="cx" style="display: block; padding: 0 10px">  * Registers a block metadata collection.
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * This function allows core and third-party plugins to register their block metadata
</span></span></pre></div>
<a id="trunksrcwpincludesclasswpblockmetadataregistryphp"></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-metadata-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-metadata-registry.php        2025-02-26 18:47:26 UTC (rev 59873)
+++ trunk/src/wp-includes/class-wp-block-metadata-registry.php  2025-02-26 19:38:08 UTC (rev 59874)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -180,6 +180,47 @@
</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">+         * Gets the list of absolute paths to all block metadata files that are part of the given collection.
+        *
+        * For instance, if a block metadata collection is registered with path `WP_PLUGIN_DIR . '/my-plugin/blocks/'`,
+        * and the manifest file includes metadata for two blocks `'block-a'` and `'block-b'`, the result of this method
+        * will be an array containing:
+        * * `WP_PLUGIN_DIR . '/my-plugin/blocks/block-a/block.json'`
+        * * `WP_PLUGIN_DIR . '/my-plugin/blocks/block-b/block.json'`
+        *
+        * @since 6.8.0
+        *
+        * @param string $path The absolute base path for a previously registered collection.
+        * @return string[] List of block metadata file paths, or an empty array if the given `$path` is invalid.
+        */
+       public static function get_collection_block_metadata_files( $path ) {
+               $path = wp_normalize_path( rtrim( $path, '/' ) );
+
+               if ( ! isset( self::$collections[ $path ] ) ) {
+                       _doing_it_wrong(
+                               __METHOD__,
+                               __( 'No registered block metadata collection was found for the provided path.' ),
+                               '6.8.0'
+                       );
+                       return array();
+               }
+
+               $collection = &self::$collections[ $path ];
+
+               if ( null === $collection['metadata'] ) {
+                       // Load the manifest file if not already loaded.
+                       $collection['metadata'] = require $collection['manifest'];
+               }
+
+               return array_map(
+                       static function ( $block_name ) use ( $path ) {
+                               return "{$path}/{$block_name}/block.json";
+                       },
+                       array_keys( $collection['metadata'] )
+               );
+       }
+
+       /**
</ins><span class="cx" style="display: block; padding: 0 10px">          * Finds the collection path for a given file or folder.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @since 6.7.0
</span></span></pre></div>
<a id="trunktestsphpunittestsblockswpBlockMetadataRegistryphp"></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/wpBlockMetadataRegistry.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/blocks/wpBlockMetadataRegistry.php      2025-02-26 18:47:26 UTC (rev 59873)
+++ trunk/tests/phpunit/tests/blocks/wpBlockMetadataRegistry.php        2025-02-26 19:38:08 UTC (rev 59874)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -4,6 +4,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">  * Tests for WP_Block_Metadata_Registry class.
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @group blocks
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @coversDefaultClass WP_Block_Metadata_Registry
</ins><span class="cx" style="display: block; padding: 0 10px">  */
</span><span class="cx" style="display: block; padding: 0 10px"> class Tests_Blocks_WpBlockMetadataRegistry extends WP_UnitTestCase {
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -188,4 +189,35 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $result = WP_Block_Metadata_Registry::register_collection( '/var/arbitrary/path', $non_existent_manifest );
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertFalse( $result, 'Non-existent manifest should not be registered' );
</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 that the `get_collection_block_metadata_files()` method returns the expected list of block metadata files.
+        *
+        * @ticket 62267
+        * @covers ::get_collection_block_metadata_files
+        */
+       public function test_get_collection_block_metadata_files() {
+               $path          = WP_PLUGIN_DIR . '/test-plugin/data/block-types';
+               $manifest_data = array(
+                       'a-block'       => array(
+                               'name'  => 'a-block',
+                               'title' => 'A Block',
+                       ),
+                       'another-block' => array(
+                               'name'  => 'another-block',
+                               'title' => 'Another Block',
+                       ),
+               );
+
+               file_put_contents( $this->temp_manifest_file, '<?php return ' . var_export( $manifest_data, true ) . ';' );
+
+               $this->assertTrue( WP_Block_Metadata_Registry::register_collection( $path, $this->temp_manifest_file ) );
+               $this->assertSame(
+                       array(
+                               WP_PLUGIN_DIR . '/test-plugin/data/block-types/a-block/block.json',
+                               WP_PLUGIN_DIR . '/test-plugin/data/block-types/another-block/block.json',
+                       ),
+                       WP_Block_Metadata_Registry::get_collection_block_metadata_files( $path )
+               );
+       }
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre>
</div>
</div>

</body>
</html>