* For the remaining types (presets, styles), we do consider origins: * * - themes without theme.json: only the classes for the presets defined by core * - themes with theme.json: the presets and styles classes, both from core and the theme */ $styles_rest = ''; if ( ! empty( $types ) ) { /* * Only use the default, theme, and custom origins. Why? * Because styles for `blocks` origin are added at a later phase * (i.e. in the render cycle). Here, only the ones in use are rendered. * @see wp_add_global_styles_for_blocks */ $origins = array( 'default', 'theme', 'custom' ); if ( ! $supports_theme_json ) { $origins = array( 'default' ); } $styles_rest = $tree->get_stylesheet( $types, $origins ); } $stylesheet = $styles_variables . $styles_rest; if ( $can_use_cached ) { wp_cache_set( $cache_key, $stylesheet, $cache_group ); } return $stylesheet; } /** * Gets the global styles custom CSS from theme.json. * * @since 6.2.0 * * @return string The global styles custom CSS. */ function wp_get_global_styles_custom_css() { if ( ! wp_theme_has_theme_json() ) { return ''; } /* * Ignore cache when the development mode is set to 'theme', so it doesn't interfere with the theme * developer's workflow. */ $can_use_cached = ! wp_is_development_mode( 'theme' ); /* * By using the 'theme_json' group, this data is marked to be non-persistent across requests. * @see `wp_cache_add_non_persistent_groups()`. * * The rationale for this is to make sure derived data from theme.json * is always fresh from the potential modifications done via hooks * that can use dynamic data (modify the stylesheet depending on some option, * settings depending on user permissions, etc.). * See some of the existing hooks to modify theme.json behavior: * @see https://make.wordpress.org/core/2022/10/10/filters-for-theme-json-data/ * * A different alternative considered was to invalidate the cache upon certain * events such as options add/update/delete, user meta, etc. * It was judged not enough, hence this approach. * @see https://github.com/WordPress/gutenberg/pull/45372 */ $cache_key = 'wp_get_global_styles_custom_css'; $cache_group = 'theme_json'; if ( $can_use_cached ) { $cached = wp_cache_get( $cache_key, $cache_group ); if ( $cached ) { return $cached; } } $tree = WP_Theme_JSON_Resolver::get_merged_data(); $stylesheet = $tree->get_custom_css(); if ( $can_use_cached ) { wp_cache_set( $cache_key, $stylesheet, $cache_group ); } return $stylesheet; } /** * Adds global style rules to the inline style for each block. * * @since 6.1.0 */ function wp_add_global_styles_for_blocks() { $tree = WP_Theme_JSON_Resolver::get_merged_data(); $block_nodes = $tree->get_styles_block_nodes(); foreach ( $block_nodes as $metadata ) { $block_css = $tree->get_styles_for_block( $metadata ); if ( ! wp_should_load_separate_core_block_assets() ) { wp_add_inline_style( 'global-styles', $block_css ); continue; } $stylesheet_handle = 'global-styles'; if ( isset( $metadata['name'] ) ) { /* * These block styles are added on block_render. * This hooks inline CSS to them so that they are loaded conditionally * based on whether or not the block is used on the page. */ if ( str_starts_with( $metadata['name'], 'core/' ) ) { $block_name = str_replace( 'core/', '', $metadata['name'] ); $stylesheet_handle = 'wp-block-' . $block_name; } wp_add_inline_style( $stylesheet_handle, $block_css ); } // The likes of block element styles from theme.json do not have $metadata['name'] set. if ( ! isset( $metadata['name'] ) && ! empty( $metadata['path'] ) ) { $block_name = wp_get_block_name_from_theme_json_path( $metadata['path'] ); if ( $block_name ) { if ( str_starts_with( $block_name, 'core/' ) ) { $block_name = str_replace( 'core/', '', $block_name ); $stylesheet_handle = 'wp-block-' . $block_name; } wp_add_inline_style( $stylesheet_handle, $block_css ); } } } } /** * Gets the block name from a given theme.json path. * * @since 6.3.0 * @access private * * @param array $path An array of keys describing the path to a property in theme.json. * @return string Identified block name, or empty string if none found. */ function wp_get_block_name_from_theme_json_path( $path ) { // Block name is expected to be the third item after 'styles' and 'blocks'. if ( count( $path ) >= 3 && 'styles' === $path[0] && 'blocks' === $path[1] && str_contains( $path[2], '/' ) ) { return $path[2]; } /* * As fallback and for backward compatibility, allow any core block to be * at any position. */ $result = array_values( array_filter( $path, static function ( $item ) { if ( str_contains( $item, 'core/' ) ) { return true; } return false; } ) ); if ( isset( $result[0] ) ) { return $result[0]; } return ''; } /** * Checks whether a theme or its parent has a theme.json file. * * @since 6.2.0 * * @return bool Returns true if theme or its parent has a theme.json file, false otherwise. */ function wp_theme_has_theme_json() { static $theme_has_support = array(); $stylesheet = get_stylesheet(); if ( isset( $theme_has_support[ $stylesheet ] ) && /* * Ignore static cache when the development mode is set to 'theme', to avoid interfering with * the theme developer's workflow. */ ! wp_is_development_mode( 'theme' ) ) { return $theme_has_support[ $stylesheet ]; } $stylesheet_directory = get_stylesheet_directory(); $template_directory = get_template_directory(); // This is the same as get_theme_file_path(), which isn't available in load-styles.php context if ( $stylesheet_directory !== $template_directory && file_exists( $stylesheet_directory . '/theme.json' ) ) { $path = $stylesheet_directory . '/theme.json'; } else { $path = $template_directory . '/theme.json'; } /** This filter is documented in wp-includes/link-template.php */ $path = apply_filters( 'theme_file_path', $path, 'theme.json' ); $theme_has_support[ $stylesheet ] = file_exists( $path ); return $theme_has_support[ $stylesheet ]; } /** * Cleans the caches under the theme_json group. * * @since 6.2.0 */ function wp_clean_theme_json_cache() { wp_cache_delete( 'wp_get_global_stylesheet', 'theme_json' ); wp_cache_delete( 'wp_get_global_styles_svg_filters', 'theme_json' ); wp_cache_delete( 'wp_get_global_settings_custom', 'theme_json' ); wp_cache_delete( 'wp_get_global_settings_theme', 'theme_json' ); wp_cache_delete( 'wp_get_global_styles_custom_css', 'theme_json' ); wp_cache_delete( 'wp_get_theme_data_template_parts', 'theme_json' ); WP_Theme_JSON_Resolver::clean_cached_data(); } /** * Returns the current theme's wanted patterns (slugs) to be * registered from Pattern Directory. * * @since 6.3.0 * * @return string[] */ function wp_get_theme_directory_pattern_slugs() { return WP_Theme_JSON_Resolver::get_theme_data( array(), array( 'with_supports' => false ) )->get_patterns(); } /** * Returns the metadata for the custom templates defined by the theme via theme.json. * * @since 6.4.0 * * @return array Associative array of `$template_name => $template_data` pairs, * with `$template_data` having "title" and "postTypes" fields. */ function wp_get_theme_data_custom_templates() { return WP_Theme_JSON_Resolver::get_theme_data( array(), array( 'with_supports' => false ) )->get_custom_templates(); } /** * Returns the metadata for the template parts defined by the theme. * * @since 6.4.0 * * @return array Associative array of `$part_name => $part_data` pairs, * with `$part_data` having "title" and "area" fields. */ function wp_get_theme_data_template_parts() { $cache_group = 'theme_json'; $cache_key = 'wp_get_theme_data_template_parts'; $can_use_cached = ! wp_is_development_mode( 'theme' ); $metadata = false; if ( $can_use_cached ) { $metadata = wp_cache_get( $cache_key, $cache_group ); if ( false !== $metadata ) { return $metadata; } } if ( false === $metadata ) { $metadata = WP_Theme_JSON_Resolver::get_theme_data( array(), array( 'with_supports' => false ) )->get_template_parts(); if ( $can_use_cached ) { wp_cache_set( $cache_key, $metadata, $cache_group ); } } return $metadata; } /** * Determines the CSS selector for the block type and property provided, * returning it if available. * * @since 6.3.0 * * @param WP_Block_Type $block_type The block's type. * @param string|array $target The desired selector's target, `root` or array path. * @param boolean $fallback Whether to fall back to broader selector. * * @return string|null CSS selector or `null` if no selector available. */ function wp_get_block_css_selector( $block_type, $target = 'root', $fallback = false ) { if ( empty( $target ) ) { return null; } $has_selectors = ! empty( $block_type->selectors ); // Root Selector. // Calculated before returning as it can be used as fallback for // feature selectors later on. $root_selector = null; if ( $has_selectors && isset( $block_type->selectors['root'] ) ) { // Use the selectors API if available. $root_selector = $block_type->selectors['root']; } elseif ( isset( $block_type->supports['__experimentalSelector'] ) && is_string( $block_type->supports['__experimentalSelector'] ) ) { // Use the old experimental selector supports property if set. $root_selector = $block_type->supports['__experimentalSelector']; } else { // If no root selector found, generate default block class selector. $block_name = str_replace( '/', '-', str_replace( 'core/', '', $block_type->name ) ); $root_selector = ".wp-block-{$block_name}"; } // Return selector if it's the root target we are looking for. if ( 'root' === $target ) { return $root_selector; } // If target is not `root` we have a feature or subfeature as the target. // If the target is a string convert to an array. if ( is_string( $target ) ) { $target = explode( '.', $target ); } // Feature Selectors ( May fallback to root selector ). if ( 1 === count( $target ) ) { $fallback_selector = $fallback ? $root_selector : null; // Prefer the selectors API if available. if ( $has_selectors ) { // Look for selector under `feature.root`. $path = array( current( $target ), 'root' ); $feature_selector = _wp_array_get( $block_type->selectors, $path, null ); if ( $feature_selector ) { return $feature_selector; } // Check if feature selector is set via shorthand. $feature_selector = _wp_array_get( $block_type->selectors, $target, null ); return is_string( $feature_selector ) ? $feature_selector : $fallback_selector; } // Try getting old experimental supports selector value. $path = array( current( $target ), '__experimentalSelector' ); $feature_selector = _wp_array_get( $block_type->supports, $path, null ); // Nothing to work with, provide fallback or null. if ( null === $feature_selector ) { return $fallback_selector; } // Scope the feature selector by the block's root selector. return WP_Theme_JSON::scope_selector( $root_selector, $feature_selector ); } // Subfeature selector // This may fallback either to parent feature or root selector. $subfeature_selector = null; // Use selectors API if available. if ( $has_selectors ) { $subfeature_selector = _wp_array_get( $block_type->selectors, $target, null ); } // Only return if we have a subfeature selector. if ( $subfeature_selector ) { return $subfeature_selector; } // To this point we don't have a subfeature selector. If a fallback // has been requested, remove subfeature from target path and return // results of a call for the parent feature's selector. if ( $fallback ) { return wp_get_block_css_selector( $block_type, $target[0], $fallback ); } return null; }