[ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Block Metadata Registry 4 * 5 * @package WordPress 6 * @subpackage Blocks 7 * @since 6.7.0 8 */ 9 10 /** 11 * Class used for managing block metadata collections. 12 * 13 * The WP_Block_Metadata_Registry allows plugins to register metadata for large 14 * collections of blocks (e.g., 50-100+) using a single PHP file. This approach 15 * reduces the need to read and decode multiple `block.json` files, enhancing 16 * performance through opcode caching. 17 * 18 * @since 6.7.0 19 */ 20 class WP_Block_Metadata_Registry { 21 22 /** 23 * Container for storing block metadata collections. 24 * 25 * Each entry maps a base path to its corresponding metadata and callback. 26 * 27 * @since 6.7.0 28 * @var array<string, array<string, mixed>> 29 */ 30 private static $collections = array(); 31 32 /** 33 * Caches the last matched collection path for performance optimization. 34 * 35 * @since 6.7.0 36 * @var string|null 37 */ 38 private static $last_matched_collection = null; 39 40 /** 41 * Stores the default allowed collection root paths. 42 * 43 * @since 6.7.2 44 * @var string[]|null 45 */ 46 private static $default_collection_roots = null; 47 48 /** 49 * Registers a block metadata collection. 50 * 51 * This method allows registering a collection of block metadata from a single 52 * manifest file, improving performance for large sets of blocks. 53 * 54 * The manifest file should be a PHP file that returns an associative array, where 55 * the keys are the block identifiers (without their namespace) and the values are 56 * the corresponding block metadata arrays. The block identifiers must match the 57 * parent directory name for the respective `block.json` file. 58 * 59 * Example manifest file structure: 60 * ``` 61 * return array( 62 * 'example-block' => array( 63 * 'title' => 'Example Block', 64 * 'category' => 'widgets', 65 * 'icon' => 'smiley', 66 * // ... other block metadata 67 * ), 68 * 'another-block' => array( 69 * 'title' => 'Another Block', 70 * 'category' => 'formatting', 71 * 'icon' => 'star-filled', 72 * // ... other block metadata 73 * ), 74 * // ... more block metadata entries 75 * ); 76 * ``` 77 * 78 * @since 6.7.0 79 * 80 * @param string $path The absolute base path for the collection ( e.g., WP_PLUGIN_DIR . '/my-plugin/blocks/' ). 81 * @param string $manifest The absolute path to the manifest file containing the metadata collection. 82 * @return bool True if the collection was registered successfully, false otherwise. 83 */ 84 public static function register_collection( $path, $manifest ) { 85 $path = wp_normalize_path( rtrim( $path, '/' ) ); 86 87 $collection_roots = self::get_default_collection_roots(); 88 89 /** 90 * Filters the root directory paths for block metadata collections. 91 * 92 * Any block metadata collection that is registered must not use any of these paths, or any parent directory 93 * path of them. Most commonly, block metadata collections should reside within one of these paths, though in 94 * some scenarios they may also reside in entirely different directories (e.g. in case of symlinked plugins). 95 * 96 * Example: 97 * * It is allowed to register a collection with path `WP_PLUGIN_DIR . '/my-plugin'`. 98 * * It is not allowed to register a collection with path `WP_PLUGIN_DIR`. 99 * * It is not allowed to register a collection with path `dirname( WP_PLUGIN_DIR )`. 100 * 101 * The default list encompasses the `wp-includes` directory, as well as the root directories for plugins, 102 * must-use plugins, and themes. This filter can be used to expand the list, e.g. to custom directories that 103 * contain symlinked plugins, so that these root directories cannot be used themselves for a block metadata 104 * collection either. 105 * 106 * @since 6.7.2 107 * 108 * @param string[] $collection_roots List of allowed metadata collection root paths. 109 */ 110 $collection_roots = apply_filters( 'wp_allowed_block_metadata_collection_roots', $collection_roots ); 111 112 $collection_roots = array_unique( 113 array_map( 114 static function ( $allowed_root ) { 115 return rtrim( $allowed_root, '/' ); 116 }, 117 $collection_roots 118 ) 119 ); 120 121 // Check if the path is valid: 122 if ( ! self::is_valid_collection_path( $path, $collection_roots ) ) { 123 _doing_it_wrong( 124 __METHOD__, 125 sprintf( 126 /* translators: %s: list of allowed collection roots */ 127 __( 'Block metadata collections cannot be registered as one of the following directories or their parent directories: %s' ), 128 esc_html( implode( wp_get_list_item_separator(), $collection_roots ) ) 129 ), 130 '6.7.2' 131 ); 132 return false; 133 } 134 135 if ( ! file_exists( $manifest ) ) { 136 _doing_it_wrong( 137 __METHOD__, 138 __( 'The specified manifest file does not exist.' ), 139 '6.7.0' 140 ); 141 return false; 142 } 143 144 self::$collections[ $path ] = array( 145 'manifest' => $manifest, 146 'metadata' => null, 147 ); 148 149 return true; 150 } 151 152 /** 153 * Retrieves block metadata for a given block within a specific collection. 154 * 155 * This method uses the registered collections to efficiently lookup 156 * block metadata without reading individual `block.json` files. 157 * 158 * @since 6.7.0 159 * 160 * @param string $file_or_folder The path to the file or folder containing the block. 161 * @return array|null The block metadata for the block, or null if not found. 162 */ 163 public static function get_metadata( $file_or_folder ) { 164 $path = self::find_collection_path( $file_or_folder ); 165 if ( ! $path ) { 166 return null; 167 } 168 169 $collection = &self::$collections[ $path ]; 170 171 if ( null === $collection['metadata'] ) { 172 // Load the manifest file if not already loaded 173 $collection['metadata'] = require $collection['manifest']; 174 } 175 176 // Get the block name from the path. 177 $block_name = self::default_identifier_callback( $file_or_folder ); 178 179 return isset( $collection['metadata'][ $block_name ] ) ? $collection['metadata'][ $block_name ] : null; 180 } 181 182 /** 183 * Finds the collection path for a given file or folder. 184 * 185 * @since 6.7.0 186 * 187 * @param string $file_or_folder The path to the file or folder. 188 * @return string|null The collection path if found, or null if not found. 189 */ 190 private static function find_collection_path( $file_or_folder ) { 191 if ( empty( $file_or_folder ) ) { 192 return null; 193 } 194 195 // Check the last matched collection first, since block registration usually happens in batches per plugin or theme. 196 $path = wp_normalize_path( rtrim( $file_or_folder, '/' ) ); 197 if ( self::$last_matched_collection && str_starts_with( $path, self::$last_matched_collection ) ) { 198 return self::$last_matched_collection; 199 } 200 201 $collection_paths = array_keys( self::$collections ); 202 foreach ( $collection_paths as $collection_path ) { 203 if ( str_starts_with( $path, $collection_path ) ) { 204 self::$last_matched_collection = $collection_path; 205 return $collection_path; 206 } 207 } 208 return null; 209 } 210 211 /** 212 * Checks if metadata exists for a given block name in a specific collection. 213 * 214 * @since 6.7.0 215 * 216 * @param string $file_or_folder The path to the file or folder containing the block metadata. 217 * @return bool True if metadata exists for the block, false otherwise. 218 */ 219 public static function has_metadata( $file_or_folder ) { 220 return null !== self::get_metadata( $file_or_folder ); 221 } 222 223 /** 224 * Default identifier function to determine the block identifier from a given path. 225 * 226 * This function extracts the block identifier from the path: 227 * - For 'block.json' files, it uses the parent directory name. 228 * - For directories, it uses the directory name itself. 229 * - For empty paths, it returns an empty string. 230 * 231 * For example: 232 * - Path: '/wp-content/plugins/my-plugin/blocks/example/block.json' 233 * Identifier: 'example' 234 * - Path: '/wp-content/plugins/my-plugin/blocks/another-block' 235 * Identifier: 'another-block' 236 * 237 * This default behavior matches the standard WordPress block structure. 238 * 239 * @since 6.7.0 240 * 241 * @param string $path The file or folder path to determine the block identifier from. 242 * @return string The block identifier, or an empty string if the path is empty. 243 */ 244 private static function default_identifier_callback( $path ) { 245 // Ensure $path is not empty to prevent unexpected behavior. 246 if ( empty( $path ) ) { 247 return ''; 248 } 249 250 if ( str_ends_with( $path, 'block.json' ) ) { 251 // Return the parent directory name if it's a block.json file. 252 return basename( dirname( $path ) ); 253 } 254 255 // Otherwise, assume it's a directory and return its name. 256 return basename( $path ); 257 } 258 259 /** 260 * Checks whether the given block metadata collection path is valid against the list of collection roots. 261 * 262 * @since 6.7.2 263 * 264 * @param string $path Block metadata collection path, without trailing slash. 265 * @param string[] $collection_roots List of collection root paths, without trailing slashes. 266 * @return bool True if the path is allowed, false otherwise. 267 */ 268 private static function is_valid_collection_path( $path, $collection_roots ) { 269 foreach ( $collection_roots as $allowed_root ) { 270 // If the path matches any root exactly, it is invalid. 271 if ( $allowed_root === $path ) { 272 return false; 273 } 274 275 // If the path is a parent path of any of the roots, it is invalid. 276 if ( str_starts_with( $allowed_root, $path ) ) { 277 return false; 278 } 279 } 280 281 return true; 282 } 283 284 /** 285 * Gets the default collection root directory paths. 286 * 287 * @since 6.7.2 288 * 289 * @return string[] List of directory paths within which metadata collections are allowed. 290 */ 291 private static function get_default_collection_roots() { 292 if ( isset( self::$default_collection_roots ) ) { 293 return self::$default_collection_roots; 294 } 295 296 $collection_roots = array( 297 wp_normalize_path( ABSPATH . WPINC ), 298 wp_normalize_path( WP_CONTENT_DIR ), 299 wp_normalize_path( WPMU_PLUGIN_DIR ), 300 wp_normalize_path( WP_PLUGIN_DIR ), 301 ); 302 303 $theme_roots = get_theme_roots(); 304 if ( ! is_array( $theme_roots ) ) { 305 $theme_roots = array( $theme_roots ); 306 } 307 foreach ( $theme_roots as $theme_root ) { 308 $collection_roots[] = trailingslashit( wp_normalize_path( WP_CONTENT_DIR ) ) . ltrim( wp_normalize_path( $theme_root ), '/' ); 309 } 310 311 self::$default_collection_roots = array_unique( $collection_roots ); 312 return self::$default_collection_roots; 313 } 314 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated : Fri Feb 21 08:20:01 2025 | Cross-referenced by PHPXref |