@@ -38,20 +38,12 @@ class WP_Block_Metadata_Registry {
3838 private static $ last_matched_collection = null ;
3939
4040 /**
41- * Stores the WordPress 'wp-includes' directory path .
41+ * Stores the default allowed collection root paths .
4242 *
43- * @since 6.7.0
44- * @var string|null
45- */
46- private static $ wpinc_dir = null ;
47-
48- /**
49- * Stores the normalized WordPress plugin directory path.
50- *
51- * @since 6.7.0
52- * @var string|null
43+ * @since 6.7.2
44+ * @var string[]|null
5345 */
54- private static $ plugin_dir = null ;
46+ private static $ default_collection_roots = null ;
5547
5648 /**
5749 * Registers a block metadata collection.
@@ -92,29 +84,50 @@ class WP_Block_Metadata_Registry {
9284 public static function register_collection ( $ path , $ manifest ) {
9385 $ path = wp_normalize_path ( rtrim ( $ path , '/ ' ) );
9486
95- $ wpinc_dir = self ::get_wpinc_dir ();
96- $ plugin_dir = self ::get_plugin_dir ();
87+ $ collection_roots = self ::get_default_collection_roots ();
9788
98- // Check if the path is valid:
99- if ( str_starts_with ( $ path , $ plugin_dir ) ) {
100- // For plugins, ensure the path is within a specific plugin directory and not the base plugin directory.
101- $ relative_path = substr ( $ path , strlen ( $ plugin_dir ) + 1 );
102- $ plugin_name = strtok ( $ relative_path , '/ ' );
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 );
103111
104- if ( empty ( $ plugin_name ) || $ plugin_name === $ relative_path ) {
105- _doing_it_wrong (
106- __METHOD__ ,
107- __ ( 'Block metadata collections can only be registered for a specific plugin. The provided path is neither a core path nor a valid plugin path. ' ),
108- '6.7.0 '
109- );
110- return false ;
111- }
112- } elseif ( ! str_starts_with ( $ path , $ wpinc_dir ) ) {
113- // If it's neither a plugin directory path nor within 'wp-includes', the path is invalid.
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 ) ) {
114123 _doing_it_wrong (
115124 __METHOD__ ,
116- __ ( 'Block metadata collections can only be registered for a specific plugin. The provided path is neither a core path nor a valid plugin path. ' ),
117- '6.7.0 '
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 '
118131 );
119132 return false ;
120133 }
@@ -244,30 +257,58 @@ private static function default_identifier_callback( $path ) {
244257 }
245258
246259 /**
247- * Gets the WordPress 'wp-includes' directory path.
260+ * Checks whether the given block metadata collection path is valid against the list of collection roots .
248261 *
249- * @since 6.7.0
262+ * @since 6.7.2
250263 *
251- * @return string The WordPress 'wp-includes' directory path.
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.
252267 */
253- private static function get_wpinc_dir () {
254- if ( ! isset ( self ::$ wpinc_dir ) ) {
255- self ::$ wpinc_dir = wp_normalize_path ( ABSPATH . WPINC );
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+ }
256279 }
257- return self ::$ wpinc_dir ;
280+
281+ return true ;
258282 }
259283
260284 /**
261- * Gets the normalized WordPress plugin directory path .
285+ * Gets the default collection root directory paths .
262286 *
263- * @since 6.7.0
287+ * @since 6.7.2
264288 *
265- * @return string The normalized WordPress plugin directory path .
289+ * @return string[] List of directory paths within which metadata collections are allowed .
266290 */
267- private static function get_plugin_dir () {
268- if ( ! isset ( self ::$ plugin_dir ) ) {
269- self ::$ plugin_dir = wp_normalize_path ( WP_PLUGIN_DIR ) ;
291+ private static function get_default_collection_roots () {
292+ if ( isset ( self ::$ default_collection_roots ) ) {
293+ return self ::$ default_collection_roots ;
270294 }
271- return self ::$ plugin_dir ;
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 ;
272313 }
273314}
0 commit comments