<!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>[59132] trunk: Editor: Allow registering PHP manifest file for block metadata collections for enhanced performance.</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/59132">59132</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/59132","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>2024-09-30 17:05:37 +0000 (Mon, 30 Sep 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'>Editor: Allow registering PHP manifest file for block metadata collections for enhanced performance.

Typically, when registering a new block type, its metadata is read from the provided `block.json` file. The more block types are registered on a site, the more costly becomes this process, as it involves filesystem reads and parsing JSON.

WordPress Core's built-in blocks have in the past worked around that by having a auto-generated PHP manifest file that includes the already parsed JSON data for all blocks. This changeset effectively allows plugins to do the same, by introducing a new API function `wp_register_block_metadata_collection()`. The WordPress Core block manifest is now handled using this API as well, rather than custom logic baked into `register_block_type_from_metadata()`.

The `wp_register_block_metadata_collection()` function requires two parameters:
* `$path`: The base path in which block files for the collection reside.
* `$manifest`: The path to the manifest file for the collection.

Every `block.json` file that is supposed to be part of the collection must reside within the provided `$path`, within its own block-specific directory matching the block name (without the block namespace). For example, for a collection `$path` of `/wp-content/plugins/test-plugin` and a block `test-plugin/testimonial`, the block file could be `/wp-content/plugins/test-plugins/blocks/testimonial/block.json`.

It is recommended that plugins use the new API function for enhanced performance, especially if they register several block types. However, the use of the function is entirely optional. Not using it will not result in any difference in user-facing behavior.

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

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunksrcwpincludesblocksindexphp">trunk/src/wp-includes/blocks/index.php</a></li>
<li><a href="#trunksrcwpincludesblocksphp">trunk/src/wp-includes/blocks.php</a></li>
<li><a href="#trunksrcwpsettingsphp">trunk/src/wp-settings.php</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#trunksrcwpincludesclasswpblockmetadataregistryphp">trunk/src/wp-includes/class-wp-block-metadata-registry.php</a></li>
<li><a href="#trunktestsphpunittestsblocksregisterBlockTypeFromMetadataWithRegistryphp">trunk/tests/phpunit/tests/blocks/registerBlockTypeFromMetadataWithRegistry.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="trunksrcwpincludesblocksindexphp"></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/index.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/blocks/index.php    2024-09-30 17:05:24 UTC (rev 59131)
+++ trunk/src/wp-includes/blocks/index.php      2024-09-30 17:05:37 UTC (rev 59132)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -160,3 +160,20 @@
</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"> add_action( 'init', 'register_core_block_types_from_metadata' );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+/**
+ * Registers the core block metadata collection.
+ *
+ * This function is hooked into the 'init' action with a priority of 9,
+ * ensuring that the core block metadata is registered before the regular
+ * block initialization that happens at priority 10.
+ *
+ * @since 6.7.0
+ */
+function wp_register_core_block_metadata_collection() {
+       wp_register_block_metadata_collection(
+               BLOCKS_PATH,
+               BLOCKS_PATH . 'blocks-json.php'
+       );
+}
+add_action( 'init', 'wp_register_core_block_metadata_collection', 9 );
</ins></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-09-30 17:05:24 UTC (rev 59131)
+++ trunk/src/wp-includes/blocks.php    2024-09-30 17:05:37 UTC (rev 59132)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -376,6 +376,22 @@
</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 a block metadata collection.
+ *
+ * This function allows core and third-party plugins to register their block metadata
+ * collections in a centralized location. Registering collections can improve performance
+ * by avoiding multiple reads from the filesystem and parsing JSON.
+ *
+ * @since 6.7.0
+ *
+ * @param string $path     The base path in which block files for the collection reside.
+ * @param string $manifest The path to the manifest file for the collection.
+ */
+function wp_register_block_metadata_collection( $path, $manifest ) {
+       WP_Block_Metadata_Registry::register_collection( $path, $manifest );
+}
+
+/**
</ins><span class="cx" style="display: block; padding: 0 10px">  * Registers a block type from the metadata stored in the `block.json` file.
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @since 5.5.0
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -402,34 +418,21 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * instead of reading a JSON file per-block, and then decoding from JSON to PHP.
</span><span class="cx" style="display: block; padding: 0 10px">         * Using a static variable ensures that the metadata is only read once per request.
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        static $core_blocks_meta;
-       if ( ! $core_blocks_meta ) {
-               $core_blocks_meta = require ABSPATH . WPINC . '/blocks/blocks-json.php';
-       }
</del><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        $metadata_file = ( ! str_ends_with( $file_or_folder, 'block.json' ) ) ?
</span><span class="cx" style="display: block; padding: 0 10px">                trailingslashit( $file_or_folder ) . 'block.json' :
</span><span class="cx" style="display: block; padding: 0 10px">                $file_or_folder;
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        $is_core_block = str_starts_with( $file_or_folder, ABSPATH . WPINC );
-       // If the block is not a core block, the metadata file must exist.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $is_core_block        = str_starts_with( $file_or_folder, ABSPATH . WPINC );
</ins><span class="cx" style="display: block; padding: 0 10px">         $metadata_file_exists = $is_core_block || file_exists( $metadata_file );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        if ( ! $metadata_file_exists && empty( $args['name'] ) ) {
-               return false;
-       }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $registry_metadata    = WP_Block_Metadata_Registry::get_metadata( $file_or_folder );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        // Try to get metadata from the static cache for core blocks.
-       $metadata = array();
-       if ( $is_core_block ) {
-               $core_block_name = str_replace( ABSPATH . WPINC . '/blocks/', '', $file_or_folder );
-               if ( ! empty( $core_blocks_meta[ $core_block_name ] ) ) {
-                       $metadata = $core_blocks_meta[ $core_block_name ];
-               }
-       }
-
-       // If metadata is not found in the static cache, read it from the file.
-       if ( $metadata_file_exists && empty( $metadata ) ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( $registry_metadata ) {
+               $metadata = $registry_metadata;
+       } elseif ( $metadata_file_exists ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                 $metadata = wp_json_file_decode( $metadata_file, array( 'associative' => true ) );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        } else {
+               $metadata = array();
</ins><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">        if ( ! is_array( $metadata ) || ( empty( $metadata['name'] ) && empty( $args['name'] ) ) ) {
</span></span></pre></div>
<a id="trunksrcwpincludesclasswpblockmetadataregistryphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: 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                                (rev 0)
+++ trunk/src/wp-includes/class-wp-block-metadata-registry.php  2024-09-30 17:05:37 UTC (rev 59132)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,273 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * Block Metadata Registry
+ *
+ * @package WordPress
+ * @subpackage Blocks
+ * @since 6.7.0
+ */
+
+/**
+ * Class used for managing block metadata collections.
+ *
+ * The WP_Block_Metadata_Registry allows plugins to register metadata for large
+ * collections of blocks (e.g., 50-100+) using a single PHP file. This approach
+ * reduces the need to read and decode multiple `block.json` files, enhancing
+ * performance through opcode caching.
+ *
+ * @since 6.7.0
+ */
+class WP_Block_Metadata_Registry {
+
+       /**
+        * Container for storing block metadata collections.
+        *
+        * Each entry maps a base path to its corresponding metadata and callback.
+        *
+        * @since 6.7.0
+        * @var array<string, array<string, mixed>>
+        */
+       private static $collections = array();
+
+       /**
+        * Caches the last matched collection path for performance optimization.
+        *
+        * @since 6.7.0
+        * @var string|null
+        */
+       private static $last_matched_collection = null;
+
+       /**
+        * Stores the WordPress 'wp-includes' directory path.
+        *
+        * @since 6.7.0
+        * @var string|null
+        */
+       private static $wpinc_dir = null;
+
+       /**
+        * Stores the normalized WordPress plugin directory path.
+        *
+        * @since 6.7.0
+        * @var string|null
+        */
+       private static $plugin_dir = null;
+
+       /**
+        * Registers a block metadata collection.
+        *
+        * This method allows registering a collection of block metadata from a single
+        * manifest file, improving performance for large sets of blocks.
+        *
+        * The manifest file should be a PHP file that returns an associative array, where
+        * the keys are the block identifiers (without their namespace) and the values are
+        * the corresponding block metadata arrays. The block identifiers must match the
+        * parent directory name for the respective `block.json` file.
+        *
+        * Example manifest file structure:
+        * ```
+        * return array(
+        *     'example-block' => array(
+        *         'title' => 'Example Block',
+        *         'category' => 'widgets',
+        *         'icon' => 'smiley',
+        *         // ... other block metadata
+        *     ),
+        *     'another-block' => array(
+        *         'title' => 'Another Block',
+        *         'category' => 'formatting',
+        *         'icon' => 'star-filled',
+        *         // ... other block metadata
+        *     ),
+        *     // ... more block metadata entries
+        * );
+        * ```
+        *
+        * @since 6.7.0
+        *
+        * @param string $path     The absolute base path for the collection ( e.g., WP_PLUGIN_DIR . '/my-plugin/blocks/' ).
+        * @param string $manifest The absolute path to the manifest file containing the metadata collection.
+        * @return bool True if the collection was registered successfully, false otherwise.
+        */
+       public static function register_collection( $path, $manifest ) {
+               $path = wp_normalize_path( rtrim( $path, '/' ) );
+
+               $wpinc_dir  = self::get_wpinc_dir();
+               $plugin_dir = self::get_plugin_dir();
+
+               // Check if the path is valid:
+               if ( str_starts_with( $path, $plugin_dir ) ) {
+                       // For plugins, ensure the path is within a specific plugin directory and not the base plugin directory.
+                       $relative_path = substr( $path, strlen( $plugin_dir ) + 1 );
+                       $plugin_name   = strtok( $relative_path, '/' );
+
+                       if ( empty( $plugin_name ) || $plugin_name === $relative_path ) {
+                               _doing_it_wrong(
+                                       __METHOD__,
+                                       __( 'Block metadata collections can only be registered for a specific plugin. The provided path is neither a core path nor a valid plugin path.' ),
+                                       '6.7.0'
+                               );
+                               return false;
+                       }
+               } elseif ( ! str_starts_with( $path, $wpinc_dir ) ) {
+                       // If it's neither a plugin directory path nor within 'wp-includes', the path is invalid.
+                       _doing_it_wrong(
+                               __METHOD__,
+                               __( 'Block metadata collections can only be registered for a specific plugin. The provided path is neither a core path nor a valid plugin path.' ),
+                               '6.7.0'
+                       );
+                       return false;
+               }
+
+               if ( ! file_exists( $manifest ) ) {
+                       _doing_it_wrong(
+                               __METHOD__,
+                               __( 'The specified manifest file does not exist.' ),
+                               '6.7.0'
+                       );
+                       return false;
+               }
+
+               self::$collections[ $path ] = array(
+                       'manifest' => $manifest,
+                       'metadata' => null,
+               );
+
+               return true;
+       }
+
+       /**
+        * Retrieves block metadata for a given block within a specific collection.
+        *
+        * This method uses the registered collections to efficiently lookup
+        * block metadata without reading individual `block.json` files.
+        *
+        * @since 6.7.0
+        *
+        * @param string $file_or_folder The path to the file or folder containing the block.
+        * @return array|null The block metadata for the block, or null if not found.
+        */
+       public static function get_metadata( $file_or_folder ) {
+               $path = self::find_collection_path( $file_or_folder );
+               if ( ! $path ) {
+                       return null;
+               }
+
+               $collection = &self::$collections[ $path ];
+
+               if ( null === $collection['metadata'] ) {
+                       // Load the manifest file if not already loaded
+                       $collection['metadata'] = require $collection['manifest'];
+               }
+
+               // Get the block name from the path.
+               $block_name = self::default_identifier_callback( $file_or_folder );
+
+               return isset( $collection['metadata'][ $block_name ] ) ? $collection['metadata'][ $block_name ] : null;
+       }
+
+       /**
+        * Finds the collection path for a given file or folder.
+        *
+        * @since 6.7.0
+        *
+        * @param string $file_or_folder The path to the file or folder.
+        * @return string|null The collection path if found, or null if not found.
+        */
+       private static function find_collection_path( $file_or_folder ) {
+               if ( empty( $file_or_folder ) ) {
+                       return null;
+               }
+
+               // Check the last matched collection first, since block registration usually happens in batches per plugin or theme.
+               $path = wp_normalize_path( rtrim( $file_or_folder, '/' ) );
+               if ( self::$last_matched_collection && str_starts_with( $path, self::$last_matched_collection ) ) {
+                       return self::$last_matched_collection;
+               }
+
+               $collection_paths = array_keys( self::$collections );
+               foreach ( $collection_paths as $collection_path ) {
+                       if ( str_starts_with( $path, $collection_path ) ) {
+                               self::$last_matched_collection = $collection_path;
+                               return $collection_path;
+                       }
+               }
+               return null;
+       }
+
+       /**
+        * Checks if metadata exists for a given block name in a specific collection.
+        *
+        * @since 6.7.0
+        *
+        * @param string $file_or_folder The path to the file or folder containing the block metadata.
+        * @return bool True if metadata exists for the block, false otherwise.
+        */
+       public static function has_metadata( $file_or_folder ) {
+               return null !== self::get_metadata( $file_or_folder );
+       }
+
+       /**
+        * Default identifier function to determine the block identifier from a given path.
+        *
+        * This function extracts the block identifier from the path:
+        * - For 'block.json' files, it uses the parent directory name.
+        * - For directories, it uses the directory name itself.
+        * - For empty paths, it returns an empty string.
+        *
+        * For example:
+        * - Path: '/wp-content/plugins/my-plugin/blocks/example/block.json'
+        *   Identifier: 'example'
+        * - Path: '/wp-content/plugins/my-plugin/blocks/another-block'
+        *   Identifier: 'another-block'
+        *
+        * This default behavior matches the standard WordPress block structure.
+        *
+        * @since 6.7.0
+        *
+        * @param string $path The file or folder path to determine the block identifier from.
+        * @return string The block identifier, or an empty string if the path is empty.
+        */
+       private static function default_identifier_callback( $path ) {
+               // Ensure $path is not empty to prevent unexpected behavior.
+               if ( empty( $path ) ) {
+                       return '';
+               }
+
+               if ( str_ends_with( $path, 'block.json' ) ) {
+                       // Return the parent directory name if it's a block.json file.
+                       return basename( dirname( $path ) );
+               }
+
+               // Otherwise, assume it's a directory and return its name.
+               return basename( $path );
+       }
+
+       /**
+        * Gets the WordPress 'wp-includes' directory path.
+        *
+        * @since 6.7.0
+        *
+        * @return string The WordPress 'wp-includes' directory path.
+        */
+       private static function get_wpinc_dir() {
+               if ( ! isset( self::$wpinc_dir ) ) {
+                       self::$wpinc_dir = wp_normalize_path( ABSPATH . WPINC );
+               }
+               return self::$wpinc_dir;
+       }
+
+       /**
+        * Gets the normalized WordPress plugin directory path.
+        *
+        * @since 6.7.0
+        *
+        * @return string The normalized WordPress plugin directory path.
+        */
+       private static function get_plugin_dir() {
+               if ( ! isset( self::$plugin_dir ) ) {
+                       self::$plugin_dir = wp_normalize_path( WP_PLUGIN_DIR );
+               }
+               return self::$plugin_dir;
+       }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/src/wp-includes/class-wp-block-metadata-registry.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunksrcwpsettingsphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-settings.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-settings.php 2024-09-30 17:05:24 UTC (rev 59131)
+++ trunk/src/wp-settings.php   2024-09-30 17:05:37 UTC (rev 59132)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -355,6 +355,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> require ABSPATH . WPINC . '/class-wp-block-type-registry.php';
</span><span class="cx" style="display: block; padding: 0 10px"> require ABSPATH . WPINC . '/class-wp-block.php';
</span><span class="cx" style="display: block; padding: 0 10px"> require ABSPATH . WPINC . '/class-wp-block-list.php';
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+require ABSPATH . WPINC . '/class-wp-block-metadata-registry.php';
</ins><span class="cx" style="display: block; padding: 0 10px"> require ABSPATH . WPINC . '/class-wp-block-parser-block.php';
</span><span class="cx" style="display: block; padding: 0 10px"> require ABSPATH . WPINC . '/class-wp-block-parser-frame.php';
</span><span class="cx" style="display: block; padding: 0 10px"> require ABSPATH . WPINC . '/class-wp-block-parser.php';
</span></span></pre></div>
<a id="trunktestsphpunittestsblocksregisterBlockTypeFromMetadataWithRegistryphp"></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/registerBlockTypeFromMetadataWithRegistry.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/blocks/registerBlockTypeFromMetadataWithRegistry.php                            (rev 0)
+++ trunk/tests/phpunit/tests/blocks/registerBlockTypeFromMetadataWithRegistry.php      2024-09-30 17:05:37 UTC (rev 59132)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,102 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * Tests for WP_Block_Metadata_Registry integration with register_block_type_from_metadata().
+ *
+ * @group blocks
+ */
+class Tests_Blocks_RegisterBlockTypeFromMetadataWithRegistry extends WP_UnitTestCase {
+       private $temp_manifest_file;
+
+       public function set_up() {
+               parent::set_up();
+               $this->temp_manifest_file = wp_tempnam( 'block-metadata-manifest' );
+       }
+
+       public function tear_down() {
+               $this->unregister_test_blocks();
+               unlink( $this->temp_manifest_file );
+               parent::tear_down();
+       }
+
+       public function test_register_block_type_from_metadata_with_registry() {
+               $plugin_path     = WP_PLUGIN_DIR . '/test-plugin';
+               $block_json_path = $plugin_path . '/blocks/test-block/block.json';
+
+               // Create a manifest file with metadata for our test block
+               $manifest_data = array(
+                       'test-block' => array(
+                               'name'        => 'test-suite/test-block',
+                               'title'       => 'Custom Test Block',
+                               'category'    => 'widgets',
+                               'icon'        => 'smiley',
+                               'description' => 'A test block registered via WP_Block_Metadata_Registry',
+                               'supports'    => array( 'html' => false ),
+                               'textdomain'  => 'test-plugin',
+                       ),
+               );
+               file_put_contents( $this->temp_manifest_file, '<?php return ' . var_export( $manifest_data, true ) . ';' );
+
+               // Register the collection
+               WP_Block_Metadata_Registry::register_collection( $plugin_path . '/blocks', $this->temp_manifest_file );
+
+               // Attempt to register the block
+               $registered_block = register_block_type_from_metadata( $block_json_path );
+
+               // Assert that the block was registered successfully
+               $this->assertInstanceOf( 'WP_Block_Type', $registered_block );
+               $this->assertEquals( 'test-suite/test-block', $registered_block->name );
+               $this->assertEquals( 'Custom Test Block', $registered_block->title );
+               $this->assertEquals( 'widgets', $registered_block->category );
+               $this->assertEquals( 'smiley', $registered_block->icon );
+               $this->assertEquals( 'A test block registered via WP_Block_Metadata_Registry', $registered_block->description );
+               $this->assertEquals( array( 'html' => false ), $registered_block->supports );
+       }
+
+       public function test_register_block_type_from_metadata_with_registry_and_override() {
+               $plugin_path     = WP_PLUGIN_DIR . '/test-plugin-2';
+               $block_json_path = $plugin_path . '/blocks/test-block/block.json';
+
+               // Create a manifest file with metadata for our test block
+               $manifest_data = array(
+                       'test-block' => array(
+                               'name'        => 'test-suite/test-block',
+                               'title'       => 'Custom Test Block',
+                               'category'    => 'widgets',
+                               'icon'        => 'smiley',
+                               'description' => 'A test block registered via WP_Block_Metadata_Registry',
+                               'supports'    => array( 'html' => false ),
+                       ),
+               );
+               file_put_contents( $this->temp_manifest_file, '<?php return ' . var_export( $manifest_data, true ) . ';' );
+
+               // Register the collection
+               WP_Block_Metadata_Registry::register_collection( $plugin_path . '/blocks', $this->temp_manifest_file );
+
+               // Attempt to register the block with some overrides
+               $registered_block = register_block_type_from_metadata(
+                       $block_json_path,
+                       array(
+                               'title'    => 'Overridden Title',
+                               'supports' => array( 'html' => true ),
+                       )
+               );
+
+               // Assert that the block was registered successfully with overrides
+               $this->assertInstanceOf( 'WP_Block_Type', $registered_block );
+               $this->assertEquals( 'test-suite/test-block', $registered_block->name );
+               $this->assertEquals( 'Overridden Title', $registered_block->title );
+               $this->assertEquals( 'widgets', $registered_block->category );
+               $this->assertEquals( 'smiley', $registered_block->icon );
+               $this->assertEquals( 'A test block registered via WP_Block_Metadata_Registry', $registered_block->description );
+               $this->assertEquals( array( 'html' => true ), $registered_block->supports );
+       }
+
+       private function unregister_test_blocks() {
+               $registry   = WP_Block_Type_Registry::get_instance();
+               $block_name = 'test-suite/test-block';
+
+               if ( $registry->is_registered( $block_name ) ) {
+                       $registry->unregister( $block_name );
+               }
+       }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/tests/phpunit/tests/blocks/registerBlockTypeFromMetadataWithRegistry.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunktestsphpunittestsblockswpBlockMetadataRegistryphp"></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/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                              (rev 0)
+++ trunk/tests/phpunit/tests/blocks/wpBlockMetadataRegistry.php        2024-09-30 17:05:37 UTC (rev 59132)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,91 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+/**
+ * Tests for WP_Block_Metadata_Registry class.
+ *
+ * @group blocks
+ */
+class Tests_Blocks_WpBlockMetadataRegistry extends WP_UnitTestCase {
+
+       private $temp_manifest_file;
+
+       public function set_up() {
+               parent::set_up();
+               $this->temp_manifest_file = wp_tempnam( 'block-metadata-manifest' );
+       }
+
+       public function tear_down() {
+               unlink( $this->temp_manifest_file );
+               parent::tear_down();
+       }
+
+       public function test_register_collection_and_get_metadata() {
+               $path          = WP_PLUGIN_DIR . '/test/path';
+               $manifest_data = array(
+                       'test-block' => array(
+                               'name'  => 'test-block',
+                               'title' => 'Test Block',
+                       ),
+               );
+
+               file_put_contents( $this->temp_manifest_file, '<?php return ' . var_export( $manifest_data, true ) . ';' );
+
+               WP_Block_Metadata_Registry::register_collection( $path, $this->temp_manifest_file );
+
+               $retrieved_metadata = WP_Block_Metadata_Registry::get_metadata( $path . '/test-block' );
+               $this->assertEquals( $manifest_data['test-block'], $retrieved_metadata );
+       }
+
+       public function test_get_nonexistent_metadata() {
+               $path               = WP_PLUGIN_DIR . '/nonexistent/path';
+               $retrieved_metadata = WP_Block_Metadata_Registry::get_metadata( $path . '/nonexistent-block' );
+               $this->assertNull( $retrieved_metadata );
+       }
+
+       public function test_has_metadata() {
+                       $path          = WP_PLUGIN_DIR . '/another/test/path';
+                       $manifest_data = array(
+                               'existing-block' => array(
+                                       'name'  => 'existing-block',
+                                       'title' => 'Existing Block',
+                               ),
+                       );
+
+                       file_put_contents( $this->temp_manifest_file, '<?php return ' . var_export( $manifest_data, true ) . ';' );
+
+                       WP_Block_Metadata_Registry::register_collection( $path, $this->temp_manifest_file );
+
+                       $this->assertTrue( WP_Block_Metadata_Registry::has_metadata( $path . '/existing-block' ) );
+                       $this->assertFalse( WP_Block_Metadata_Registry::has_metadata( $path . '/non-existing-block' ) );
+       }
+
+       public function test_register_collection_with_core_path() {
+               $core_path = ABSPATH . WPINC . '/blocks';
+               $result    = WP_Block_Metadata_Registry::register_collection( $core_path, $this->temp_manifest_file );
+               $this->assertTrue( $result, 'Core path should be registered successfully' );
+       }
+
+       public function test_register_collection_with_valid_plugin_path() {
+               $plugin_path = WP_PLUGIN_DIR . '/my-plugin/blocks';
+               $result      = WP_Block_Metadata_Registry::register_collection( $plugin_path, $this->temp_manifest_file );
+               $this->assertTrue( $result, 'Valid plugin path should be registered successfully' );
+       }
+
+       public function test_register_collection_with_invalid_plugin_path() {
+               $invalid_plugin_path = WP_PLUGIN_DIR;
+
+               $this->setExpectedIncorrectUsage( 'WP_Block_Metadata_Registry::register_collection' );
+
+               $result = WP_Block_Metadata_Registry::register_collection( $invalid_plugin_path, $this->temp_manifest_file );
+               $this->assertFalse( $result, 'Invalid plugin path should not be registered' );
+       }
+
+       public function test_register_collection_with_non_existent_path() {
+               $non_existent_path = '/path/that/does/not/exist';
+
+               $this->setExpectedIncorrectUsage( 'WP_Block_Metadata_Registry::register_collection' );
+
+               $result = WP_Block_Metadata_Registry::register_collection( $non_existent_path, $this->temp_manifest_file );
+               $this->assertFalse( $result, 'Non-existent path should not be registered' );
+       }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/tests/phpunit/tests/blocks/wpBlockMetadataRegistry.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span></div>

</body>
</html>