wp/wp-includes/global-styles-and-settings.php
changeset 21 48c4eec2b7e6
parent 19 3d72ae0968f4
child 22 8c2e4d02f4ef
equal deleted inserted replaced
20:7b1b88e27a20 21:48c4eec2b7e6
     4  *
     4  *
     5  * @package WordPress
     5  * @package WordPress
     6  */
     6  */
     7 
     7 
     8 /**
     8 /**
     9  * Function to get the settings resulting of merging core, theme, and user data.
     9  * Gets the settings resulting of merging core, theme, and user data.
    10  *
    10  *
    11  * @since 5.9.0
    11  * @since 5.9.0
    12  *
    12  *
    13  * @param array $path    Path to the specific setting to retrieve. Optional.
    13  * @param array $path    Path to the specific setting to retrieve. Optional.
    14  *                       If empty, will return all settings.
    14  *                       If empty, will return all settings.
    19  *                              If empty, it'll return the settings for the global context.
    19  *                              If empty, it'll return the settings for the global context.
    20  *     @type string $origin     Which origin to take data from.
    20  *     @type string $origin     Which origin to take data from.
    21  *                              Valid values are 'all' (core, theme, and user) or 'base' (core and theme).
    21  *                              Valid values are 'all' (core, theme, and user) or 'base' (core and theme).
    22  *                              If empty or unknown, 'all' is used.
    22  *                              If empty or unknown, 'all' is used.
    23  * }
    23  * }
    24  *
    24  * @return mixed The settings array or individual setting value to retrieve.
    25  * @return array The settings to retrieve.
       
    26  */
    25  */
    27 function wp_get_global_settings( $path = array(), $context = array() ) {
    26 function wp_get_global_settings( $path = array(), $context = array() ) {
    28 	if ( ! empty( $context['block_name'] ) ) {
    27 	if ( ! empty( $context['block_name'] ) ) {
    29 		$path = array_merge( array( 'blocks', $context['block_name'] ), $path );
    28 		$new_path = array( 'blocks', $context['block_name'] );
    30 	}
    29 		foreach ( $path as $subpath ) {
    31 
    30 			$new_path[] = $subpath;
       
    31 		}
       
    32 		$path = $new_path;
       
    33 	}
       
    34 
       
    35 	/*
       
    36 	 * This is the default value when no origin is provided or when it is 'all'.
       
    37 	 *
       
    38 	 * The $origin is used as part of the cache key. Changes here need to account
       
    39 	 * for clearing the cache appropriately.
       
    40 	 */
    32 	$origin = 'custom';
    41 	$origin = 'custom';
    33 	if ( isset( $context['origin'] ) && 'base' === $context['origin'] ) {
    42 	if (
       
    43 		! wp_theme_has_theme_json() ||
       
    44 		( isset( $context['origin'] ) && 'base' === $context['origin'] )
       
    45 	) {
    34 		$origin = 'theme';
    46 		$origin = 'theme';
    35 	}
    47 	}
    36 
    48 
    37 	$settings = WP_Theme_JSON_Resolver::get_merged_data( $origin )->get_settings();
    49 	/*
       
    50 	 * By using the 'theme_json' group, this data is marked to be non-persistent across requests.
       
    51 	 * See `wp_cache_add_non_persistent_groups` in src/wp-includes/load.php and other places.
       
    52 	 *
       
    53 	 * The rationale for this is to make sure derived data from theme.json
       
    54 	 * is always fresh from the potential modifications done via hooks
       
    55 	 * that can use dynamic data (modify the stylesheet depending on some option,
       
    56 	 * settings depending on user permissions, etc.).
       
    57 	 * See some of the existing hooks to modify theme.json behavior:
       
    58 	 * https://make.wordpress.org/core/2022/10/10/filters-for-theme-json-data/
       
    59 	 *
       
    60 	 * A different alternative considered was to invalidate the cache upon certain
       
    61 	 * events such as options add/update/delete, user meta, etc.
       
    62 	 * It was judged not enough, hence this approach.
       
    63 	 * See https://github.com/WordPress/gutenberg/pull/45372
       
    64 	 */
       
    65 	$cache_group = 'theme_json';
       
    66 	$cache_key   = 'wp_get_global_settings_' . $origin;
       
    67 
       
    68 	/*
       
    69 	 * Ignore cache when the development mode is set to 'theme', so it doesn't interfere with the theme
       
    70 	 * developer's workflow.
       
    71 	 */
       
    72 	$can_use_cached = ! wp_is_development_mode( 'theme' );
       
    73 
       
    74 	$settings = false;
       
    75 	if ( $can_use_cached ) {
       
    76 		$settings = wp_cache_get( $cache_key, $cache_group );
       
    77 	}
       
    78 
       
    79 	if ( false === $settings ) {
       
    80 		$settings = WP_Theme_JSON_Resolver::get_merged_data( $origin )->get_settings();
       
    81 		if ( $can_use_cached ) {
       
    82 			wp_cache_set( $cache_key, $settings, $cache_group );
       
    83 		}
       
    84 	}
    38 
    85 
    39 	return _wp_array_get( $settings, $path, $settings );
    86 	return _wp_array_get( $settings, $path, $settings );
    40 }
    87 }
    41 
    88 
    42 /**
    89 /**
    43  * Function to get the styles resulting of merging core, theme, and user data.
    90  * Gets the styles resulting of merging core, theme, and user data.
    44  *
    91  *
    45  * @since 5.9.0
    92  * @since 5.9.0
       
    93  * @since 6.3.0 the internal link format "var:preset|color|secondary" is resolved
       
    94  *              to "var(--wp--preset--font-size--small)" so consumers don't have to.
       
    95  * @since 6.3.0 `transforms` is now usable in the `context` parameter. In case [`transforms`]['resolve_variables']
       
    96  *              is defined, variables are resolved to their value in the styles.
    46  *
    97  *
    47  * @param array $path    Path to the specific style to retrieve. Optional.
    98  * @param array $path    Path to the specific style to retrieve. Optional.
    48  *                       If empty, will return all styles.
    99  *                       If empty, will return all styles.
    49  * @param array $context {
   100  * @param array $context {
    50  *     Metadata to know where to retrieve the $path from. Optional.
   101  *     Metadata to know where to retrieve the $path from. Optional.
    52  *     @type string $block_name Which block to retrieve the styles from.
   103  *     @type string $block_name Which block to retrieve the styles from.
    53  *                              If empty, it'll return the styles for the global context.
   104  *                              If empty, it'll return the styles for the global context.
    54  *     @type string $origin     Which origin to take data from.
   105  *     @type string $origin     Which origin to take data from.
    55  *                              Valid values are 'all' (core, theme, and user) or 'base' (core and theme).
   106  *                              Valid values are 'all' (core, theme, and user) or 'base' (core and theme).
    56  *                              If empty or unknown, 'all' is used.
   107  *                              If empty or unknown, 'all' is used.
       
   108  *     @type array $transforms Which transformation(s) to apply.
       
   109  *                              Valid value is array( 'resolve-variables' ).
       
   110  *                              If defined, variables are resolved to their value in the styles.
    57  * }
   111  * }
    58  *
   112  * @return mixed The styles array or individual style value to retrieve.
    59  * @return array The styles to retrieve.
       
    60  */
   113  */
    61 function wp_get_global_styles( $path = array(), $context = array() ) {
   114 function wp_get_global_styles( $path = array(), $context = array() ) {
    62 	if ( ! empty( $context['block_name'] ) ) {
   115 	if ( ! empty( $context['block_name'] ) ) {
    63 		$path = array_merge( array( 'blocks', $context['block_name'] ), $path );
   116 		$path = array_merge( array( 'blocks', $context['block_name'] ), $path );
    64 	}
   117 	}
    66 	$origin = 'custom';
   119 	$origin = 'custom';
    67 	if ( isset( $context['origin'] ) && 'base' === $context['origin'] ) {
   120 	if ( isset( $context['origin'] ) && 'base' === $context['origin'] ) {
    68 		$origin = 'theme';
   121 		$origin = 'theme';
    69 	}
   122 	}
    70 
   123 
    71 	$styles = WP_Theme_JSON_Resolver::get_merged_data( $origin )->get_raw_data()['styles'];
   124 	$resolve_variables = isset( $context['transforms'] )
    72 
   125 	&& is_array( $context['transforms'] )
       
   126 	&& in_array( 'resolve-variables', $context['transforms'], true );
       
   127 
       
   128 	$merged_data = WP_Theme_JSON_Resolver::get_merged_data( $origin );
       
   129 	if ( $resolve_variables ) {
       
   130 		$merged_data = WP_Theme_JSON::resolve_variables( $merged_data );
       
   131 	}
       
   132 	$styles = $merged_data->get_raw_data()['styles'];
    73 	return _wp_array_get( $styles, $path, $styles );
   133 	return _wp_array_get( $styles, $path, $styles );
    74 }
   134 }
    75 
   135 
       
   136 
    76 /**
   137 /**
    77  * Returns the stylesheet resulting of merging core, theme, and user data.
   138  * Returns the stylesheet resulting of merging core, theme, and user data.
    78  *
   139  *
    79  * @since 5.9.0
   140  * @since 5.9.0
    80  *
   141  * @since 6.1.0 Added 'base-layout-styles' support.
    81  * @param array $types Types of styles to load. Optional.
   142  * @since 6.6.0 Resolves relative paths in theme.json styles to theme absolute paths.
    82  *                     It accepts 'variables', 'styles', 'presets' as values.
   143  *
    83  *                     If empty, it'll load all for themes with theme.json support
   144  * @param array $types Optional. Types of styles to load.
    84  *                     and only [ 'variables', 'presets' ] for themes without theme.json support.
   145  *                     It accepts as values 'variables', 'presets', 'styles', 'base-layout-styles'.
    85  *
   146  *                     If empty, it'll load the following:
       
   147  *                     - for themes without theme.json: 'variables', 'presets', 'base-layout-styles'.
       
   148  *                     - for themes with theme.json: 'variables', 'presets', 'styles'.
    86  * @return string Stylesheet.
   149  * @return string Stylesheet.
    87  */
   150  */
    88 function wp_get_global_stylesheet( $types = array() ) {
   151 function wp_get_global_stylesheet( $types = array() ) {
    89 	// Return cached value if it can be used and exists.
   152 	/*
    90 	// It's cached by theme to make sure that theme switching clears the cache.
   153 	 * Ignore cache when the development mode is set to 'theme', so it doesn't interfere with the theme
    91 	$can_use_cached = (
   154 	 * developer's workflow.
    92 		( empty( $types ) ) &&
   155 	 */
    93 		( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) &&
   156 	$can_use_cached = empty( $types ) && ! wp_is_development_mode( 'theme' );
    94 		( ! defined( 'SCRIPT_DEBUG' ) || ! SCRIPT_DEBUG ) &&
   157 
    95 		( ! defined( 'REST_REQUEST' ) || ! REST_REQUEST ) &&
   158 	/*
    96 		! is_admin()
   159 	 * By using the 'theme_json' group, this data is marked to be non-persistent across requests.
    97 	);
   160 	 * @see `wp_cache_add_non_persistent_groups()`.
    98 	$transient_name = 'global_styles_' . get_stylesheet();
   161 	 *
       
   162 	 * The rationale for this is to make sure derived data from theme.json
       
   163 	 * is always fresh from the potential modifications done via hooks
       
   164 	 * that can use dynamic data (modify the stylesheet depending on some option,
       
   165 	 * settings depending on user permissions, etc.).
       
   166 	 * See some of the existing hooks to modify theme.json behavior:
       
   167 	 * @see https://make.wordpress.org/core/2022/10/10/filters-for-theme-json-data/
       
   168 	 *
       
   169 	 * A different alternative considered was to invalidate the cache upon certain
       
   170 	 * events such as options add/update/delete, user meta, etc.
       
   171 	 * It was judged not enough, hence this approach.
       
   172 	 * @see https://github.com/WordPress/gutenberg/pull/45372
       
   173 	 */
       
   174 	$cache_group = 'theme_json';
       
   175 	$cache_key   = 'wp_get_global_stylesheet';
    99 	if ( $can_use_cached ) {
   176 	if ( $can_use_cached ) {
   100 		$cached = get_transient( $transient_name );
   177 		$cached = wp_cache_get( $cache_key, $cache_group );
   101 		if ( $cached ) {
   178 		if ( $cached ) {
   102 			return $cached;
   179 			return $cached;
   103 		}
   180 		}
   104 	}
   181 	}
   105 
   182 
   106 	$tree = WP_Theme_JSON_Resolver::get_merged_data();
   183 	$tree                = WP_Theme_JSON_Resolver::resolve_theme_file_uris( WP_Theme_JSON_Resolver::get_merged_data() );
   107 
   184 	$supports_theme_json = wp_theme_has_theme_json();
   108 	$supports_theme_json = WP_Theme_JSON_Resolver::theme_has_support();
   185 
   109 	if ( empty( $types ) && ! $supports_theme_json ) {
   186 	if ( empty( $types ) && ! $supports_theme_json ) {
   110 		$types = array( 'variables', 'presets' );
   187 		$types = array( 'variables', 'presets', 'base-layout-styles' );
   111 	} elseif ( empty( $types ) ) {
   188 	} elseif ( empty( $types ) ) {
   112 		$types = array( 'variables', 'styles', 'presets' );
   189 		$types = array( 'variables', 'styles', 'presets' );
   113 	}
   190 	}
   114 
   191 
   115 	/*
   192 	/*
   116 	 * If variables are part of the stylesheet,
   193 	 * If variables are part of the stylesheet, then add them.
   117 	 * we add them for all origins (default, theme, user).
       
   118 	 * This is so themes without a theme.json still work as before 5.9:
   194 	 * This is so themes without a theme.json still work as before 5.9:
   119 	 * they can override the default presets.
   195 	 * they can override the default presets.
   120 	 * See https://core.trac.wordpress.org/ticket/54782
   196 	 * See https://core.trac.wordpress.org/ticket/54782
   121 	 */
   197 	 */
   122 	$styles_variables = '';
   198 	$styles_variables = '';
   123 	if ( in_array( 'variables', $types, true ) ) {
   199 	if ( in_array( 'variables', $types, true ) ) {
   124 		$styles_variables = $tree->get_stylesheet( array( 'variables' ) );
   200 		/*
       
   201 		 * Only use the default, theme, and custom origins. Why?
       
   202 		 * Because styles for `blocks` origin are added at a later phase
       
   203 		 * (i.e. in the render cycle). Here, only the ones in use are rendered.
       
   204 		 * @see wp_add_global_styles_for_blocks
       
   205 		 */
       
   206 		$origins          = array( 'default', 'theme', 'custom' );
       
   207 		$styles_variables = $tree->get_stylesheet( array( 'variables' ), $origins );
   125 		$types            = array_diff( $types, array( 'variables' ) );
   208 		$types            = array_diff( $types, array( 'variables' ) );
   126 	}
   209 	}
   127 
   210 
   128 	/*
   211 	/*
   129 	 * For the remaining types (presets, styles), we do consider origins:
   212 	 * For the remaining types (presets, styles), we do consider origins:
   131 	 * - themes without theme.json: only the classes for the presets defined by core
   214 	 * - themes without theme.json: only the classes for the presets defined by core
   132 	 * - themes with theme.json: the presets and styles classes, both from core and the theme
   215 	 * - themes with theme.json: the presets and styles classes, both from core and the theme
   133 	 */
   216 	 */
   134 	$styles_rest = '';
   217 	$styles_rest = '';
   135 	if ( ! empty( $types ) ) {
   218 	if ( ! empty( $types ) ) {
       
   219 		/*
       
   220 		 * Only use the default, theme, and custom origins. Why?
       
   221 		 * Because styles for `blocks` origin are added at a later phase
       
   222 		 * (i.e. in the render cycle). Here, only the ones in use are rendered.
       
   223 		 * @see wp_add_global_styles_for_blocks
       
   224 		 */
   136 		$origins = array( 'default', 'theme', 'custom' );
   225 		$origins = array( 'default', 'theme', 'custom' );
   137 		if ( ! $supports_theme_json ) {
   226 		/*
       
   227 		 * If the theme doesn't have theme.json but supports both appearance tools and color palette,
       
   228 		 * the 'theme' origin should be included so color palette presets are also output.
       
   229 		 */
       
   230 		if ( ! $supports_theme_json && ( current_theme_supports( 'appearance-tools' ) || current_theme_supports( 'border' ) ) && current_theme_supports( 'editor-color-palette' ) ) {
       
   231 			$origins = array( 'default', 'theme' );
       
   232 		} elseif ( ! $supports_theme_json ) {
   138 			$origins = array( 'default' );
   233 			$origins = array( 'default' );
   139 		}
   234 		}
   140 		$styles_rest = $tree->get_stylesheet( $types, $origins );
   235 		$styles_rest = $tree->get_stylesheet( $types, $origins );
   141 	}
   236 	}
   142 
   237 
   143 	$stylesheet = $styles_variables . $styles_rest;
   238 	$stylesheet = $styles_variables . $styles_rest;
   144 
       
   145 	if ( $can_use_cached ) {
   239 	if ( $can_use_cached ) {
   146 		// Cache for a minute.
   240 		wp_cache_set( $cache_key, $stylesheet, $cache_group );
   147 		// This cache doesn't need to be any longer, we only want to avoid spikes on high-traffic sites.
       
   148 		set_transient( $transient_name, $stylesheet, MINUTE_IN_SECONDS );
       
   149 	}
   241 	}
   150 
   242 
   151 	return $stylesheet;
   243 	return $stylesheet;
   152 }
   244 }
   153 
   245 
   154 /**
   246 /**
   155  * Returns a string containing the SVGs to be referenced as filters (duotone).
   247  * Gets the global styles custom CSS from theme.json.
   156  *
   248  *
   157  * @since 5.9.1
   249  * @since 6.2.0
   158  *
   250  *
   159  * @return string
   251  * @return string The global styles custom CSS.
   160  */
   252  */
   161 function wp_get_global_styles_svg_filters() {
   253 function wp_get_global_styles_custom_css() {
   162 	// Return cached value if it can be used and exists.
   254 	if ( ! wp_theme_has_theme_json() ) {
   163 	// It's cached by theme to make sure that theme switching clears the cache.
   255 		return '';
   164 	$can_use_cached = (
   256 	}
   165 		( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) &&
   257 	/*
   166 		( ! defined( 'SCRIPT_DEBUG' ) || ! SCRIPT_DEBUG ) &&
   258 	 * Ignore cache when the development mode is set to 'theme', so it doesn't interfere with the theme
   167 		( ! defined( 'REST_REQUEST' ) || ! REST_REQUEST ) &&
   259 	 * developer's workflow.
   168 		! is_admin()
   260 	 */
   169 	);
   261 	$can_use_cached = ! wp_is_development_mode( 'theme' );
   170 	$transient_name = 'global_styles_svg_filters_' . get_stylesheet();
   262 
       
   263 	/*
       
   264 	 * By using the 'theme_json' group, this data is marked to be non-persistent across requests.
       
   265 	 * @see `wp_cache_add_non_persistent_groups()`.
       
   266 	 *
       
   267 	 * The rationale for this is to make sure derived data from theme.json
       
   268 	 * is always fresh from the potential modifications done via hooks
       
   269 	 * that can use dynamic data (modify the stylesheet depending on some option,
       
   270 	 * settings depending on user permissions, etc.).
       
   271 	 * See some of the existing hooks to modify theme.json behavior:
       
   272 	 * @see https://make.wordpress.org/core/2022/10/10/filters-for-theme-json-data/
       
   273 	 *
       
   274 	 * A different alternative considered was to invalidate the cache upon certain
       
   275 	 * events such as options add/update/delete, user meta, etc.
       
   276 	 * It was judged not enough, hence this approach.
       
   277 	 * @see https://github.com/WordPress/gutenberg/pull/45372
       
   278 	 */
       
   279 	$cache_key   = 'wp_get_global_styles_custom_css';
       
   280 	$cache_group = 'theme_json';
   171 	if ( $can_use_cached ) {
   281 	if ( $can_use_cached ) {
   172 		$cached = get_transient( $transient_name );
   282 		$cached = wp_cache_get( $cache_key, $cache_group );
   173 		if ( $cached ) {
   283 		if ( $cached ) {
   174 			return $cached;
   284 			return $cached;
   175 		}
   285 		}
   176 	}
   286 	}
   177 
   287 
   178 	$supports_theme_json = WP_Theme_JSON_Resolver::theme_has_support();
   288 	$tree       = WP_Theme_JSON_Resolver::get_merged_data();
   179 
   289 	$stylesheet = $tree->get_custom_css();
   180 	$origins = array( 'default', 'theme', 'custom' );
       
   181 	if ( ! $supports_theme_json ) {
       
   182 		$origins = array( 'default' );
       
   183 	}
       
   184 
       
   185 	$tree = WP_Theme_JSON_Resolver::get_merged_data();
       
   186 	$svgs = $tree->get_svg_filters( $origins );
       
   187 
   290 
   188 	if ( $can_use_cached ) {
   291 	if ( $can_use_cached ) {
   189 		// Cache for a minute, same as wp_get_global_stylesheet.
   292 		wp_cache_set( $cache_key, $stylesheet, $cache_group );
   190 		set_transient( $transient_name, $svgs, MINUTE_IN_SECONDS );
   293 	}
   191 	}
   294 
   192 
   295 	return $stylesheet;
   193 	return $svgs;
   296 }
   194 }
   297 
       
   298 /**
       
   299  * Adds global style rules to the inline style for each block.
       
   300  *
       
   301  * @since 6.1.0
       
   302  *
       
   303  * @global WP_Styles $wp_styles
       
   304  */
       
   305 function wp_add_global_styles_for_blocks() {
       
   306 	global $wp_styles;
       
   307 
       
   308 	$tree        = WP_Theme_JSON_Resolver::get_merged_data();
       
   309 	$block_nodes = $tree->get_styles_block_nodes();
       
   310 	foreach ( $block_nodes as $metadata ) {
       
   311 		$block_css = $tree->get_styles_for_block( $metadata );
       
   312 
       
   313 		if ( ! wp_should_load_separate_core_block_assets() ) {
       
   314 			wp_add_inline_style( 'global-styles', $block_css );
       
   315 			continue;
       
   316 		}
       
   317 
       
   318 		$stylesheet_handle = 'global-styles';
       
   319 
       
   320 		/*
       
   321 		 * When `wp_should_load_separate_core_block_assets()` is true, block styles are
       
   322 		 * enqueued for each block on the page in class WP_Block's render function.
       
   323 		 * This means there will be a handle in the styles queue for each of those blocks.
       
   324 		 * Block-specific global styles should be attached to the global-styles handle, but
       
   325 		 * only for blocks on the page, thus we check if the block's handle is in the queue
       
   326 		 * before adding the inline style.
       
   327 		 * This conditional loading only applies to core blocks.
       
   328 		 */
       
   329 		if ( isset( $metadata['name'] ) ) {
       
   330 			if ( str_starts_with( $metadata['name'], 'core/' ) ) {
       
   331 				$block_name   = str_replace( 'core/', '', $metadata['name'] );
       
   332 				$block_handle = 'wp-block-' . $block_name;
       
   333 				if ( in_array( $block_handle, $wp_styles->queue, true ) ) {
       
   334 					wp_add_inline_style( $stylesheet_handle, $block_css );
       
   335 				}
       
   336 			} else {
       
   337 				wp_add_inline_style( $stylesheet_handle, $block_css );
       
   338 			}
       
   339 		}
       
   340 
       
   341 		// The likes of block element styles from theme.json do not have  $metadata['name'] set.
       
   342 		if ( ! isset( $metadata['name'] ) && ! empty( $metadata['path'] ) ) {
       
   343 			$block_name = wp_get_block_name_from_theme_json_path( $metadata['path'] );
       
   344 			if ( $block_name ) {
       
   345 				if ( str_starts_with( $block_name, 'core/' ) ) {
       
   346 					$block_name   = str_replace( 'core/', '', $block_name );
       
   347 					$block_handle = 'wp-block-' . $block_name;
       
   348 					if ( in_array( $block_handle, $wp_styles->queue, true ) ) {
       
   349 						wp_add_inline_style( $stylesheet_handle, $block_css );
       
   350 					}
       
   351 				} else {
       
   352 					wp_add_inline_style( $stylesheet_handle, $block_css );
       
   353 				}
       
   354 			}
       
   355 		}
       
   356 	}
       
   357 }
       
   358 
       
   359 /**
       
   360  * Gets the block name from a given theme.json path.
       
   361  *
       
   362  * @since 6.3.0
       
   363  * @access private
       
   364  *
       
   365  * @param array $path An array of keys describing the path to a property in theme.json.
       
   366  * @return string Identified block name, or empty string if none found.
       
   367  */
       
   368 function wp_get_block_name_from_theme_json_path( $path ) {
       
   369 	// Block name is expected to be the third item after 'styles' and 'blocks'.
       
   370 	if (
       
   371 		count( $path ) >= 3
       
   372 		&& 'styles' === $path[0]
       
   373 		&& 'blocks' === $path[1]
       
   374 		&& str_contains( $path[2], '/' )
       
   375 	) {
       
   376 		return $path[2];
       
   377 	}
       
   378 
       
   379 	/*
       
   380 	 * As fallback and for backward compatibility, allow any core block to be
       
   381 	 * at any position.
       
   382 	 */
       
   383 	$result = array_values(
       
   384 		array_filter(
       
   385 			$path,
       
   386 			static function ( $item ) {
       
   387 				if ( str_contains( $item, 'core/' ) ) {
       
   388 					return true;
       
   389 				}
       
   390 				return false;
       
   391 			}
       
   392 		)
       
   393 	);
       
   394 	if ( isset( $result[0] ) ) {
       
   395 		return $result[0];
       
   396 	}
       
   397 	return '';
       
   398 }
       
   399 
       
   400 /**
       
   401  * Checks whether a theme or its parent has a theme.json file.
       
   402  *
       
   403  * @since 6.2.0
       
   404  *
       
   405  * @return bool Returns true if theme or its parent has a theme.json file, false otherwise.
       
   406  */
       
   407 function wp_theme_has_theme_json() {
       
   408 	static $theme_has_support = array();
       
   409 
       
   410 	$stylesheet = get_stylesheet();
       
   411 
       
   412 	if (
       
   413 		isset( $theme_has_support[ $stylesheet ] ) &&
       
   414 		/*
       
   415 		 * Ignore static cache when the development mode is set to 'theme', to avoid interfering with
       
   416 		 * the theme developer's workflow.
       
   417 		 */
       
   418 		! wp_is_development_mode( 'theme' )
       
   419 	) {
       
   420 		return $theme_has_support[ $stylesheet ];
       
   421 	}
       
   422 
       
   423 	$stylesheet_directory = get_stylesheet_directory();
       
   424 	$template_directory   = get_template_directory();
       
   425 
       
   426 	// This is the same as get_theme_file_path(), which isn't available in load-styles.php context
       
   427 	if ( $stylesheet_directory !== $template_directory && file_exists( $stylesheet_directory . '/theme.json' ) ) {
       
   428 		$path = $stylesheet_directory . '/theme.json';
       
   429 	} else {
       
   430 		$path = $template_directory . '/theme.json';
       
   431 	}
       
   432 
       
   433 	/** This filter is documented in wp-includes/link-template.php */
       
   434 	$path = apply_filters( 'theme_file_path', $path, 'theme.json' );
       
   435 
       
   436 	$theme_has_support[ $stylesheet ] = file_exists( $path );
       
   437 
       
   438 	return $theme_has_support[ $stylesheet ];
       
   439 }
       
   440 
       
   441 /**
       
   442  * Cleans the caches under the theme_json group.
       
   443  *
       
   444  * @since 6.2.0
       
   445  */
       
   446 function wp_clean_theme_json_cache() {
       
   447 	wp_cache_delete( 'wp_get_global_stylesheet', 'theme_json' );
       
   448 	wp_cache_delete( 'wp_get_global_styles_svg_filters', 'theme_json' );
       
   449 	wp_cache_delete( 'wp_get_global_settings_custom', 'theme_json' );
       
   450 	wp_cache_delete( 'wp_get_global_settings_theme', 'theme_json' );
       
   451 	wp_cache_delete( 'wp_get_global_styles_custom_css', 'theme_json' );
       
   452 	wp_cache_delete( 'wp_get_theme_data_template_parts', 'theme_json' );
       
   453 	WP_Theme_JSON_Resolver::clean_cached_data();
       
   454 }
       
   455 
       
   456 /**
       
   457  * Returns the current theme's wanted patterns (slugs) to be
       
   458  * registered from Pattern Directory.
       
   459  *
       
   460  * @since 6.3.0
       
   461  *
       
   462  * @return string[]
       
   463  */
       
   464 function wp_get_theme_directory_pattern_slugs() {
       
   465 	return WP_Theme_JSON_Resolver::get_theme_data( array(), array( 'with_supports' => false ) )->get_patterns();
       
   466 }
       
   467 
       
   468 /**
       
   469  * Returns the metadata for the custom templates defined by the theme via theme.json.
       
   470  *
       
   471  * @since 6.4.0
       
   472  *
       
   473  * @return array Associative array of `$template_name => $template_data` pairs,
       
   474  *               with `$template_data` having "title" and "postTypes" fields.
       
   475  */
       
   476 function wp_get_theme_data_custom_templates() {
       
   477 	return WP_Theme_JSON_Resolver::get_theme_data( array(), array( 'with_supports' => false ) )->get_custom_templates();
       
   478 }
       
   479 
       
   480 /**
       
   481  * Returns the metadata for the template parts defined by the theme.
       
   482  *
       
   483  * @since 6.4.0
       
   484  *
       
   485  * @return array Associative array of `$part_name => $part_data` pairs,
       
   486  *               with `$part_data` having "title" and "area" fields.
       
   487  */
       
   488 function wp_get_theme_data_template_parts() {
       
   489 	$cache_group    = 'theme_json';
       
   490 	$cache_key      = 'wp_get_theme_data_template_parts';
       
   491 	$can_use_cached = ! wp_is_development_mode( 'theme' );
       
   492 
       
   493 	$metadata = false;
       
   494 	if ( $can_use_cached ) {
       
   495 		$metadata = wp_cache_get( $cache_key, $cache_group );
       
   496 		if ( false !== $metadata ) {
       
   497 			return $metadata;
       
   498 		}
       
   499 	}
       
   500 
       
   501 	if ( false === $metadata ) {
       
   502 		$metadata = WP_Theme_JSON_Resolver::get_theme_data( array(), array( 'with_supports' => false ) )->get_template_parts();
       
   503 		if ( $can_use_cached ) {
       
   504 			wp_cache_set( $cache_key, $metadata, $cache_group );
       
   505 		}
       
   506 	}
       
   507 
       
   508 	return $metadata;
       
   509 }
       
   510 
       
   511 /**
       
   512  * Determines the CSS selector for the block type and property provided,
       
   513  * returning it if available.
       
   514  *
       
   515  * @since 6.3.0
       
   516  *
       
   517  * @param WP_Block_Type $block_type The block's type.
       
   518  * @param string|array  $target     The desired selector's target, `root` or array path.
       
   519  * @param boolean       $fallback   Whether to fall back to broader selector.
       
   520  *
       
   521  * @return string|null CSS selector or `null` if no selector available.
       
   522  */
       
   523 function wp_get_block_css_selector( $block_type, $target = 'root', $fallback = false ) {
       
   524 	if ( empty( $target ) ) {
       
   525 		return null;
       
   526 	}
       
   527 
       
   528 	$has_selectors = ! empty( $block_type->selectors );
       
   529 
       
   530 	// Root Selector.
       
   531 
       
   532 	// Calculated before returning as it can be used as fallback for
       
   533 	// feature selectors later on.
       
   534 	$root_selector = null;
       
   535 
       
   536 	if ( $has_selectors && isset( $block_type->selectors['root'] ) ) {
       
   537 		// Use the selectors API if available.
       
   538 		$root_selector = $block_type->selectors['root'];
       
   539 	} elseif ( isset( $block_type->supports['__experimentalSelector'] ) && is_string( $block_type->supports['__experimentalSelector'] ) ) {
       
   540 		// Use the old experimental selector supports property if set.
       
   541 		$root_selector = $block_type->supports['__experimentalSelector'];
       
   542 	} else {
       
   543 		// If no root selector found, generate default block class selector.
       
   544 		$block_name    = str_replace( '/', '-', str_replace( 'core/', '', $block_type->name ) );
       
   545 		$root_selector = ".wp-block-{$block_name}";
       
   546 	}
       
   547 
       
   548 	// Return selector if it's the root target we are looking for.
       
   549 	if ( 'root' === $target ) {
       
   550 		return $root_selector;
       
   551 	}
       
   552 
       
   553 	// If target is not `root` we have a feature or subfeature as the target.
       
   554 	// If the target is a string convert to an array.
       
   555 	if ( is_string( $target ) ) {
       
   556 		$target = explode( '.', $target );
       
   557 	}
       
   558 
       
   559 	// Feature Selectors ( May fallback to root selector ).
       
   560 	if ( 1 === count( $target ) ) {
       
   561 		$fallback_selector = $fallback ? $root_selector : null;
       
   562 
       
   563 		// Prefer the selectors API if available.
       
   564 		if ( $has_selectors ) {
       
   565 			// Look for selector under `feature.root`.
       
   566 			$path             = array( current( $target ), 'root' );
       
   567 			$feature_selector = _wp_array_get( $block_type->selectors, $path, null );
       
   568 
       
   569 			if ( $feature_selector ) {
       
   570 				return $feature_selector;
       
   571 			}
       
   572 
       
   573 			// Check if feature selector is set via shorthand.
       
   574 			$feature_selector = _wp_array_get( $block_type->selectors, $target, null );
       
   575 
       
   576 			return is_string( $feature_selector ) ? $feature_selector : $fallback_selector;
       
   577 		}
       
   578 
       
   579 		// Try getting old experimental supports selector value.
       
   580 		$path             = array( current( $target ), '__experimentalSelector' );
       
   581 		$feature_selector = _wp_array_get( $block_type->supports, $path, null );
       
   582 
       
   583 		// Nothing to work with, provide fallback or null.
       
   584 		if ( null === $feature_selector ) {
       
   585 			return $fallback_selector;
       
   586 		}
       
   587 
       
   588 		// Scope the feature selector by the block's root selector.
       
   589 		return WP_Theme_JSON::scope_selector( $root_selector, $feature_selector );
       
   590 	}
       
   591 
       
   592 	// Subfeature selector
       
   593 	// This may fallback either to parent feature or root selector.
       
   594 	$subfeature_selector = null;
       
   595 
       
   596 	// Use selectors API if available.
       
   597 	if ( $has_selectors ) {
       
   598 		$subfeature_selector = _wp_array_get( $block_type->selectors, $target, null );
       
   599 	}
       
   600 
       
   601 	// Only return if we have a subfeature selector.
       
   602 	if ( $subfeature_selector ) {
       
   603 		return $subfeature_selector;
       
   604 	}
       
   605 
       
   606 	// To this point we don't have a subfeature selector. If a fallback
       
   607 	// has been requested, remove subfeature from target path and return
       
   608 	// results of a call for the parent feature's selector.
       
   609 	if ( $fallback ) {
       
   610 		return wp_get_block_css_selector( $block_type, $target[0], $fallback );
       
   611 	}
       
   612 
       
   613 	return null;
       
   614 }