wp/wp-includes/class-wp-theme-json-resolver.php
changeset 21 48c4eec2b7e6
parent 19 3d72ae0968f4
child 22 8c2e4d02f4ef
equal deleted inserted replaced
20:7b1b88e27a20 21:48c4eec2b7e6
    11  * Class that abstracts the processing of the different data sources
    11  * Class that abstracts the processing of the different data sources
    12  * for site-level config and offers an API to work with them.
    12  * for site-level config and offers an API to work with them.
    13  *
    13  *
    14  * This class is for internal core usage and is not supposed to be used by extenders (plugins and/or themes).
    14  * This class is for internal core usage and is not supposed to be used by extenders (plugins and/or themes).
    15  * This is a low-level API that may need to do breaking changes. Please,
    15  * This is a low-level API that may need to do breaking changes. Please,
    16  * use get_global_settings, get_global_styles, and get_global_stylesheet instead.
    16  * use get_global_settings(), get_global_styles(), and get_global_stylesheet() instead.
    17  *
    17  *
    18  * @access private
    18  * @access private
    19  */
    19  */
       
    20 #[AllowDynamicProperties]
    20 class WP_Theme_JSON_Resolver {
    21 class WP_Theme_JSON_Resolver {
       
    22 
       
    23 	/**
       
    24 	 * Container for keep track of registered blocks.
       
    25 	 *
       
    26 	 * @since 6.1.0
       
    27 	 * @var array
       
    28 	 */
       
    29 	protected static $blocks_cache = array(
       
    30 		'core'   => array(),
       
    31 		'blocks' => array(),
       
    32 		'theme'  => array(),
       
    33 		'user'   => array(),
       
    34 	);
    21 
    35 
    22 	/**
    36 	/**
    23 	 * Container for data coming from core.
    37 	 * Container for data coming from core.
    24 	 *
    38 	 *
    25 	 * @since 5.8.0
    39 	 * @since 5.8.0
    26 	 * @var WP_Theme_JSON
    40 	 * @var WP_Theme_JSON
    27 	 */
    41 	 */
    28 	protected static $core = null;
    42 	protected static $core = null;
    29 
    43 
    30 	/**
    44 	/**
       
    45 	 * Container for data coming from the blocks.
       
    46 	 *
       
    47 	 * @since 6.1.0
       
    48 	 * @var WP_Theme_JSON
       
    49 	 */
       
    50 	protected static $blocks = null;
       
    51 
       
    52 	/**
    31 	 * Container for data coming from the theme.
    53 	 * Container for data coming from the theme.
    32 	 *
    54 	 *
    33 	 * @since 5.8.0
    55 	 * @since 5.8.0
    34 	 * @var WP_Theme_JSON
    56 	 * @var WP_Theme_JSON
    35 	 */
    57 	 */
    36 	protected static $theme = null;
    58 	protected static $theme = null;
    37 
       
    38 	/**
       
    39 	 * Whether or not the theme supports theme.json.
       
    40 	 *
       
    41 	 * @since 5.8.0
       
    42 	 * @var bool
       
    43 	 */
       
    44 	protected static $theme_has_support = null;
       
    45 
    59 
    46 	/**
    60 	/**
    47 	 * Container for data coming from the user.
    61 	 * Container for data coming from the user.
    48 	 *
    62 	 *
    49 	 * @since 5.9.0
    63 	 * @since 5.9.0
    68 	 * @var array
    82 	 * @var array
    69 	 */
    83 	 */
    70 	protected static $i18n_schema = null;
    84 	protected static $i18n_schema = null;
    71 
    85 
    72 	/**
    86 	/**
       
    87 	 * `theme.json` file cache.
       
    88 	 *
       
    89 	 * @since 6.1.0
       
    90 	 * @var array
       
    91 	 */
       
    92 	protected static $theme_json_file_cache = array();
       
    93 
       
    94 	/**
    73 	 * Processes a file that adheres to the theme.json schema
    95 	 * Processes a file that adheres to the theme.json schema
    74 	 * and returns an array with its contents, or a void array if none found.
    96 	 * and returns an array with its contents, or a void array if none found.
    75 	 *
    97 	 *
    76 	 * @since 5.8.0
    98 	 * @since 5.8.0
       
    99 	 * @since 6.1.0 Added caching.
    77 	 *
   100 	 *
    78 	 * @param string $file_path Path to file. Empty if no file.
   101 	 * @param string $file_path Path to file. Empty if no file.
    79 	 * @return array Contents that adhere to the theme.json schema.
   102 	 * @return array Contents that adhere to the theme.json schema.
    80 	 */
   103 	 */
    81 	protected static function read_json_file( $file_path ) {
   104 	protected static function read_json_file( $file_path ) {
    82 		$config = array();
       
    83 		if ( $file_path ) {
   105 		if ( $file_path ) {
       
   106 			if ( array_key_exists( $file_path, static::$theme_json_file_cache ) ) {
       
   107 				return static::$theme_json_file_cache[ $file_path ];
       
   108 			}
       
   109 
    84 			$decoded_file = wp_json_file_decode( $file_path, array( 'associative' => true ) );
   110 			$decoded_file = wp_json_file_decode( $file_path, array( 'associative' => true ) );
    85 			if ( is_array( $decoded_file ) ) {
   111 			if ( is_array( $decoded_file ) ) {
    86 				$config = $decoded_file;
   112 				static::$theme_json_file_cache[ $file_path ] = $decoded_file;
    87 			}
   113 				return static::$theme_json_file_cache[ $file_path ];
    88 		}
   114 			}
    89 		return $config;
   115 		}
       
   116 
       
   117 		return array();
    90 	}
   118 	}
    91 
   119 
    92 	/**
   120 	/**
    93 	 * Returns a data structure used in theme.json translation.
   121 	 * Returns a data structure used in theme.json translation.
    94 	 *
   122 	 *
   128 	 * @since 5.8.0
   156 	 * @since 5.8.0
   129 	 *
   157 	 *
   130 	 * @return WP_Theme_JSON Entity that holds core data.
   158 	 * @return WP_Theme_JSON Entity that holds core data.
   131 	 */
   159 	 */
   132 	public static function get_core_data() {
   160 	public static function get_core_data() {
   133 		if ( null !== static::$core ) {
   161 		if ( null !== static::$core && static::has_same_registered_blocks( 'core' ) ) {
   134 			return static::$core;
   162 			return static::$core;
   135 		}
   163 		}
   136 
   164 
   137 		$config       = static::read_json_file( __DIR__ . '/theme.json' );
   165 		$config = static::read_json_file( __DIR__ . '/theme.json' );
   138 		$config       = static::translate( $config );
   166 		$config = static::translate( $config );
   139 		static::$core = new WP_Theme_JSON( $config, 'default' );
   167 
       
   168 		/**
       
   169 		 * Filters the default data provided by WordPress for global styles & settings.
       
   170 		 *
       
   171 		 * @since 6.1.0
       
   172 		 *
       
   173 		 * @param WP_Theme_JSON_Data $theme_json Class to access and update the underlying data.
       
   174 		 */
       
   175 		$theme_json = apply_filters( 'wp_theme_json_data_default', new WP_Theme_JSON_Data( $config, 'default' ) );
       
   176 
       
   177 		/*
       
   178 		 * Backward compatibility for extenders returning a WP_Theme_JSON_Data
       
   179 		 * compatible class that is not a WP_Theme_JSON_Data object.
       
   180 		 */
       
   181 		if ( $theme_json instanceof WP_Theme_JSON_Data ) {
       
   182 			static::$core = $theme_json->get_theme_json();
       
   183 		} else {
       
   184 			$config       = $theme_json->get_data();
       
   185 			static::$core = new WP_Theme_JSON( $config, 'default' );
       
   186 		}
   140 
   187 
   141 		return static::$core;
   188 		return static::$core;
       
   189 	}
       
   190 
       
   191 	/**
       
   192 	 * Checks whether the registered blocks were already processed for this origin.
       
   193 	 *
       
   194 	 * @since 6.1.0
       
   195 	 *
       
   196 	 * @param string $origin Data source for which to cache the blocks.
       
   197 	 *                       Valid values are 'core', 'blocks', 'theme', and 'user'.
       
   198 	 * @return bool True on success, false otherwise.
       
   199 	 */
       
   200 	protected static function has_same_registered_blocks( $origin ) {
       
   201 		// Bail out if the origin is invalid.
       
   202 		if ( ! isset( static::$blocks_cache[ $origin ] ) ) {
       
   203 			return false;
       
   204 		}
       
   205 
       
   206 		$registry = WP_Block_Type_Registry::get_instance();
       
   207 		$blocks   = $registry->get_all_registered();
       
   208 
       
   209 		// Is there metadata for all currently registered blocks?
       
   210 		$block_diff = array_diff_key( $blocks, static::$blocks_cache[ $origin ] );
       
   211 		if ( empty( $block_diff ) ) {
       
   212 			return true;
       
   213 		}
       
   214 
       
   215 		foreach ( $blocks as $block_name => $block_type ) {
       
   216 			static::$blocks_cache[ $origin ][ $block_name ] = true;
       
   217 		}
       
   218 
       
   219 		return false;
   142 	}
   220 	}
   143 
   221 
   144 	/**
   222 	/**
   145 	 * Returns the theme's data.
   223 	 * Returns the theme's data.
   146 	 *
   224 	 *
   150 	 * the theme.json takes precedence.
   228 	 * the theme.json takes precedence.
   151 	 *
   229 	 *
   152 	 * @since 5.8.0
   230 	 * @since 5.8.0
   153 	 * @since 5.9.0 Theme supports have been inlined and the `$theme_support_data` argument removed.
   231 	 * @since 5.9.0 Theme supports have been inlined and the `$theme_support_data` argument removed.
   154 	 * @since 6.0.0 Added an `$options` parameter to allow the theme data to be returned without theme supports.
   232 	 * @since 6.0.0 Added an `$options` parameter to allow the theme data to be returned without theme supports.
       
   233 	 * @since 6.6.0 Add support for 'default-font-sizes' and 'default-spacing-sizes' theme supports.
       
   234 	 *              Added registration and merging of block style variations from partial theme.json files and the block styles registry.
   155 	 *
   235 	 *
   156 	 * @param array $deprecated Deprecated. Not used.
   236 	 * @param array $deprecated Deprecated. Not used.
   157 	 * @param array $options {
   237 	 * @param array $options {
   158 	 *     Options arguments.
   238 	 *     Options arguments.
   159 	 *
   239 	 *
   166 			_deprecated_argument( __METHOD__, '5.9.0' );
   246 			_deprecated_argument( __METHOD__, '5.9.0' );
   167 		}
   247 		}
   168 
   248 
   169 		$options = wp_parse_args( $options, array( 'with_supports' => true ) );
   249 		$options = wp_parse_args( $options, array( 'with_supports' => true ) );
   170 
   250 
   171 		if ( null === static::$theme ) {
   251 		if ( null === static::$theme || ! static::has_same_registered_blocks( 'theme' ) ) {
   172 			$theme_json_data = static::read_json_file( static::get_file_path_from_theme( 'theme.json' ) );
   252 			$wp_theme        = wp_get_theme();
   173 			$theme_json_data = static::translate( $theme_json_data, wp_get_theme()->get( 'TextDomain' ) );
   253 			$theme_json_file = $wp_theme->get_file_path( 'theme.json' );
   174 			static::$theme   = new WP_Theme_JSON( $theme_json_data );
   254 			if ( is_readable( $theme_json_file ) ) {
   175 
   255 				$theme_json_data = static::read_json_file( $theme_json_file );
   176 			if ( wp_get_theme()->parent() ) {
   256 				$theme_json_data = static::translate( $theme_json_data, $wp_theme->get( 'TextDomain' ) );
       
   257 			} else {
       
   258 				$theme_json_data = array( 'version' => WP_Theme_JSON::LATEST_SCHEMA );
       
   259 			}
       
   260 
       
   261 			/*
       
   262 			 * Register variations defined by theme partials (theme.json files in the styles directory).
       
   263 			 * This is required so the variations pass sanitization of theme.json data.
       
   264 			 */
       
   265 			$variations = static::get_style_variations( 'block' );
       
   266 			wp_register_block_style_variations_from_theme_json_partials( $variations );
       
   267 
       
   268 			/*
       
   269 			 * Source variations from the block registry and block style variation files. Then, merge them into the existing theme.json data.
       
   270 			 *
       
   271 			 * In case the same style properties are defined in several sources, this is how we should resolve the values,
       
   272 			 * from higher to lower priority:
       
   273 			 *
       
   274 			 * - styles.blocks.blockType.variations from theme.json
       
   275 			 * - styles.variations from theme.json
       
   276 			 * - variations from block style variation files
       
   277 			 * - variations from block styles registry
       
   278 			 *
       
   279 			 * See test_add_registered_block_styles_to_theme_data and test_unwraps_block_style_variations.
       
   280 			 *
       
   281 			 */
       
   282 			$theme_json_data = static::inject_variations_from_block_style_variation_files( $theme_json_data, $variations );
       
   283 			$theme_json_data = static::inject_variations_from_block_styles_registry( $theme_json_data );
       
   284 
       
   285 			/**
       
   286 			 * Filters the data provided by the theme for global styles and settings.
       
   287 			 *
       
   288 			 * @since 6.1.0
       
   289 			 *
       
   290 			 * @param WP_Theme_JSON_Data $theme_json Class to access and update the underlying data.
       
   291 			 */
       
   292 			$theme_json = apply_filters( 'wp_theme_json_data_theme', new WP_Theme_JSON_Data( $theme_json_data, 'theme' ) );
       
   293 
       
   294 			/*
       
   295 			 * Backward compatibility for extenders returning a WP_Theme_JSON_Data
       
   296 			 * compatible class that is not a WP_Theme_JSON_Data object.
       
   297 			 */
       
   298 			if ( $theme_json instanceof WP_Theme_JSON_Data ) {
       
   299 				static::$theme = $theme_json->get_theme_json();
       
   300 			} else {
       
   301 				$config        = $theme_json->get_data();
       
   302 				static::$theme = new WP_Theme_JSON( $config );
       
   303 			}
       
   304 
       
   305 			if ( $wp_theme->parent() ) {
   177 				// Get parent theme.json.
   306 				// Get parent theme.json.
   178 				$parent_theme_json_data = static::read_json_file( static::get_file_path_from_theme( 'theme.json', true ) );
   307 				$parent_theme_json_file = $wp_theme->parent()->get_file_path( 'theme.json' );
   179 				$parent_theme_json_data = static::translate( $parent_theme_json_data, wp_get_theme()->parent()->get( 'TextDomain' ) );
   308 				if ( $theme_json_file !== $parent_theme_json_file && is_readable( $parent_theme_json_file ) ) {
   180 				$parent_theme           = new WP_Theme_JSON( $parent_theme_json_data );
   309 					$parent_theme_json_data = static::read_json_file( $parent_theme_json_file );
   181 
   310 					$parent_theme_json_data = static::translate( $parent_theme_json_data, $wp_theme->parent()->get( 'TextDomain' ) );
   182 				// Merge the child theme.json into the parent theme.json.
   311 					$parent_theme           = new WP_Theme_JSON( $parent_theme_json_data );
   183 				// The child theme takes precedence over the parent.
   312 
   184 				$parent_theme->merge( static::$theme );
   313 					/*
   185 				static::$theme = $parent_theme;
   314 					 * Merge the child theme.json into the parent theme.json.
       
   315 					 * The child theme takes precedence over the parent.
       
   316 					 */
       
   317 					$parent_theme->merge( static::$theme );
       
   318 					static::$theme = $parent_theme;
       
   319 				}
   186 			}
   320 			}
   187 		}
   321 		}
   188 
   322 
   189 		if ( ! $options['with_supports'] ) {
   323 		if ( ! $options['with_supports'] ) {
   190 			return static::$theme;
   324 			return static::$theme;
   194 		 * We want the presets and settings declared in theme.json
   328 		 * We want the presets and settings declared in theme.json
   195 		 * to override the ones declared via theme supports.
   329 		 * to override the ones declared via theme supports.
   196 		 * So we take theme supports, transform it to theme.json shape
   330 		 * So we take theme supports, transform it to theme.json shape
   197 		 * and merge the static::$theme upon that.
   331 		 * and merge the static::$theme upon that.
   198 		 */
   332 		 */
   199 		$theme_support_data = WP_Theme_JSON::get_from_editor_settings( get_default_block_editor_settings() );
   333 		$theme_support_data = WP_Theme_JSON::get_from_editor_settings( get_classic_theme_supports_block_editor_settings() );
   200 		if ( ! static::theme_has_support() ) {
   334 		if ( ! wp_theme_has_theme_json() ) {
   201 			if ( ! isset( $theme_support_data['settings']['color'] ) ) {
   335 			/*
   202 				$theme_support_data['settings']['color'] = array();
   336 			 * Unlike block themes, classic themes without a theme.json disable
   203 			}
   337 			 * default presets when custom preset theme support is added. This
   204 
   338 			 * behavior can be overridden by using the corresponding default
   205 			$default_palette = false;
   339 			 * preset theme support.
   206 			if ( current_theme_supports( 'default-color-palette' ) ) {
   340 			 */
   207 				$default_palette = true;
   341 			$theme_support_data['settings']['color']['defaultPalette']        =
   208 			}
   342 				! isset( $theme_support_data['settings']['color']['palette'] ) ||
   209 			if ( ! isset( $theme_support_data['settings']['color']['palette'] ) ) {
   343 				current_theme_supports( 'default-color-palette' );
   210 				// If the theme does not have any palette, we still want to show the core one.
   344 			$theme_support_data['settings']['color']['defaultGradients']      =
   211 				$default_palette = true;
   345 				! isset( $theme_support_data['settings']['color']['gradients'] ) ||
   212 			}
   346 				current_theme_supports( 'default-gradient-presets' );
   213 			$theme_support_data['settings']['color']['defaultPalette'] = $default_palette;
   347 			$theme_support_data['settings']['typography']['defaultFontSizes'] =
   214 
   348 				! isset( $theme_support_data['settings']['typography']['fontSizes'] ) ||
   215 			$default_gradients = false;
   349 				current_theme_supports( 'default-font-sizes' );
   216 			if ( current_theme_supports( 'default-gradient-presets' ) ) {
   350 			$theme_support_data['settings']['spacing']['defaultSpacingSizes'] =
   217 				$default_gradients = true;
   351 				! isset( $theme_support_data['settings']['spacing']['spacingSizes'] ) ||
   218 			}
   352 				current_theme_supports( 'default-spacing-sizes' );
   219 			if ( ! isset( $theme_support_data['settings']['color']['gradients'] ) ) {
   353 
   220 				// If the theme does not have any gradients, we still want to show the core ones.
   354 			/*
   221 				$default_gradients = true;
   355 			 * Shadow presets are explicitly disabled for classic themes until a
   222 			}
   356 			 * decision is made for whether the default presets should match the
   223 			$theme_support_data['settings']['color']['defaultGradients'] = $default_gradients;
   357 			 * other presets or if they should be disabled by default in classic
   224 
   358 			 * themes. See https://github.com/WordPress/gutenberg/issues/59989.
   225 			// Classic themes without a theme.json don't support global duotone.
   359 			 */
   226 			$theme_support_data['settings']['color']['defaultDuotone'] = false;
   360 			$theme_support_data['settings']['shadow']['defaultPresets'] = false;
       
   361 
       
   362 			// Allow themes to enable link color setting via theme_support.
       
   363 			if ( current_theme_supports( 'link-color' ) ) {
       
   364 				$theme_support_data['settings']['color']['link'] = true;
       
   365 			}
       
   366 
       
   367 			// Allow themes to enable all border settings via theme_support.
       
   368 			if ( current_theme_supports( 'border' ) ) {
       
   369 				$theme_support_data['settings']['border']['color']  = true;
       
   370 				$theme_support_data['settings']['border']['radius'] = true;
       
   371 				$theme_support_data['settings']['border']['style']  = true;
       
   372 				$theme_support_data['settings']['border']['width']  = true;
       
   373 			}
       
   374 
       
   375 			// Allow themes to enable appearance tools via theme_support.
       
   376 			if ( current_theme_supports( 'appearance-tools' ) ) {
       
   377 				$theme_support_data['settings']['appearanceTools'] = true;
       
   378 			}
   227 		}
   379 		}
   228 		$with_theme_supports = new WP_Theme_JSON( $theme_support_data );
   380 		$with_theme_supports = new WP_Theme_JSON( $theme_support_data );
   229 		$with_theme_supports->merge( static::$theme );
   381 		$with_theme_supports->merge( static::$theme );
   230 
       
   231 		return $with_theme_supports;
   382 		return $with_theme_supports;
   232 	}
   383 	}
   233 
   384 
   234 	/**
   385 	/**
       
   386 	 * Gets the styles for blocks from the block.json file.
       
   387 	 *
       
   388 	 * @since 6.1.0
       
   389 	 *
       
   390 	 * @return WP_Theme_JSON
       
   391 	 */
       
   392 	public static function get_block_data() {
       
   393 		$registry = WP_Block_Type_Registry::get_instance();
       
   394 		$blocks   = $registry->get_all_registered();
       
   395 
       
   396 		if ( null !== static::$blocks && static::has_same_registered_blocks( 'blocks' ) ) {
       
   397 			return static::$blocks;
       
   398 		}
       
   399 
       
   400 		$config = array( 'version' => WP_Theme_JSON::LATEST_SCHEMA );
       
   401 		foreach ( $blocks as $block_name => $block_type ) {
       
   402 			if ( isset( $block_type->supports['__experimentalStyle'] ) ) {
       
   403 				$config['styles']['blocks'][ $block_name ] = static::remove_json_comments( $block_type->supports['__experimentalStyle'] );
       
   404 			}
       
   405 
       
   406 			if (
       
   407 				isset( $block_type->supports['spacing']['blockGap']['__experimentalDefault'] ) &&
       
   408 				! isset( $config['styles']['blocks'][ $block_name ]['spacing']['blockGap'] )
       
   409 			) {
       
   410 				/*
       
   411 				 * Ensure an empty placeholder value exists for the block, if it provides a default blockGap value.
       
   412 				 * The real blockGap value to be used will be determined when the styles are rendered for output.
       
   413 				 */
       
   414 				$config['styles']['blocks'][ $block_name ]['spacing']['blockGap'] = null;
       
   415 			}
       
   416 		}
       
   417 
       
   418 		/**
       
   419 		 * Filters the data provided by the blocks for global styles & settings.
       
   420 		 *
       
   421 		 * @since 6.1.0
       
   422 		 *
       
   423 		 * @param WP_Theme_JSON_Data $theme_json Class to access and update the underlying data.
       
   424 		 */
       
   425 		$theme_json = apply_filters( 'wp_theme_json_data_blocks', new WP_Theme_JSON_Data( $config, 'blocks' ) );
       
   426 
       
   427 		/*
       
   428 		 * Backward compatibility for extenders returning a WP_Theme_JSON_Data
       
   429 		 * compatible class that is not a WP_Theme_JSON_Data object.
       
   430 		 */
       
   431 		if ( $theme_json instanceof WP_Theme_JSON_Data ) {
       
   432 			static::$blocks = $theme_json->get_theme_json();
       
   433 		} else {
       
   434 			$config         = $theme_json->get_data();
       
   435 			static::$blocks = new WP_Theme_JSON( $config, 'blocks' );
       
   436 		}
       
   437 
       
   438 		return static::$blocks;
       
   439 	}
       
   440 
       
   441 	/**
       
   442 	 * When given an array, this will remove any keys with the name `//`.
       
   443 	 *
       
   444 	 * @since 6.1.0
       
   445 	 *
       
   446 	 * @param array $input_array The array to filter.
       
   447 	 * @return array The filtered array.
       
   448 	 */
       
   449 	private static function remove_json_comments( $input_array ) {
       
   450 		unset( $input_array['//'] );
       
   451 		foreach ( $input_array as $k => $v ) {
       
   452 			if ( is_array( $v ) ) {
       
   453 				$input_array[ $k ] = static::remove_json_comments( $v );
       
   454 			}
       
   455 		}
       
   456 
       
   457 		return $input_array;
       
   458 	}
       
   459 
       
   460 	/**
   235 	 * Returns the custom post type that contains the user's origin config
   461 	 * Returns the custom post type that contains the user's origin config
   236 	 * for the active theme or a void array if none are found.
   462 	 * for the active theme or an empty array if none are found.
   237 	 *
   463 	 *
   238 	 * This can also create and return a new draft custom post type.
   464 	 * This can also create and return a new draft custom post type.
   239 	 *
   465 	 *
   240 	 * @since 5.9.0
   466 	 * @since 5.9.0
   241 	 *
   467 	 *
   251 	 */
   477 	 */
   252 	public static function get_user_data_from_wp_global_styles( $theme, $create_post = false, $post_status_filter = array( 'publish' ) ) {
   478 	public static function get_user_data_from_wp_global_styles( $theme, $create_post = false, $post_status_filter = array( 'publish' ) ) {
   253 		if ( ! $theme instanceof WP_Theme ) {
   479 		if ( ! $theme instanceof WP_Theme ) {
   254 			$theme = wp_get_theme();
   480 			$theme = wp_get_theme();
   255 		}
   481 		}
       
   482 
       
   483 		/*
       
   484 		 * Bail early if the theme does not support a theme.json.
       
   485 		 *
       
   486 		 * Since wp_theme_has_theme_json() only supports the active
       
   487 		 * theme, the extra condition for whether $theme is the active theme is
       
   488 		 * present here.
       
   489 		 */
       
   490 		if ( $theme->get_stylesheet() === get_stylesheet() && ! wp_theme_has_theme_json() ) {
       
   491 			return array();
       
   492 		}
       
   493 
   256 		$user_cpt         = array();
   494 		$user_cpt         = array();
   257 		$post_type_filter = 'wp_global_styles';
   495 		$post_type_filter = 'wp_global_styles';
       
   496 		$stylesheet       = $theme->get_stylesheet();
   258 		$args             = array(
   497 		$args             = array(
   259 			'numberposts' => 1,
   498 			'posts_per_page'         => 1,
   260 			'orderby'     => 'date',
   499 			'orderby'                => 'date',
   261 			'order'       => 'desc',
   500 			'order'                  => 'desc',
   262 			'post_type'   => $post_type_filter,
   501 			'post_type'              => $post_type_filter,
   263 			'post_status' => $post_status_filter,
   502 			'post_status'            => $post_status_filter,
   264 			'tax_query'   => array(
   503 			'ignore_sticky_posts'    => true,
       
   504 			'no_found_rows'          => true,
       
   505 			'update_post_meta_cache' => false,
       
   506 			'update_post_term_cache' => false,
       
   507 			'tax_query'              => array(
   265 				array(
   508 				array(
   266 					'taxonomy' => 'wp_theme',
   509 					'taxonomy' => 'wp_theme',
   267 					'field'    => 'name',
   510 					'field'    => 'name',
   268 					'terms'    => $theme->get_stylesheet(),
   511 					'terms'    => $stylesheet,
   269 				),
   512 				),
   270 			),
   513 			),
   271 		);
   514 		);
   272 
   515 
   273 		$cache_key = sprintf( 'wp_global_styles_%s', md5( serialize( $args ) ) );
   516 		$global_style_query = new WP_Query();
   274 		$post_id   = wp_cache_get( $cache_key );
   517 		$recent_posts       = $global_style_query->query( $args );
   275 
   518 		if ( count( $recent_posts ) === 1 ) {
   276 		if ( (int) $post_id > 0 ) {
   519 			$user_cpt = get_object_vars( $recent_posts[0] );
   277 			return get_post( $post_id, ARRAY_A );
       
   278 		}
       
   279 
       
   280 		// Special case: '-1' is a results not found.
       
   281 		if ( -1 === $post_id && ! $create_post ) {
       
   282 			return $user_cpt;
       
   283 		}
       
   284 
       
   285 		$recent_posts = wp_get_recent_posts( $args );
       
   286 		if ( is_array( $recent_posts ) && ( count( $recent_posts ) === 1 ) ) {
       
   287 			$user_cpt = $recent_posts[0];
       
   288 		} elseif ( $create_post ) {
   520 		} elseif ( $create_post ) {
   289 			$cpt_post_id = wp_insert_post(
   521 			$cpt_post_id = wp_insert_post(
   290 				array(
   522 				array(
   291 					'post_content' => '{"version": ' . WP_Theme_JSON::LATEST_SCHEMA . ', "isGlobalStylesUserThemeJSON": true }',
   523 					'post_content' => '{"version": ' . WP_Theme_JSON::LATEST_SCHEMA . ', "isGlobalStylesUserThemeJSON": true }',
   292 					'post_status'  => 'publish',
   524 					'post_status'  => 'publish',
   293 					'post_title'   => 'Custom Styles',
   525 					'post_title'   => 'Custom Styles', // Do not make string translatable, see https://core.trac.wordpress.org/ticket/54518.
   294 					'post_type'    => $post_type_filter,
   526 					'post_type'    => $post_type_filter,
   295 					'post_name'    => 'wp-global-styles-' . urlencode( wp_get_theme()->get_stylesheet() ),
   527 					'post_name'    => sprintf( 'wp-global-styles-%s', urlencode( $stylesheet ) ),
   296 					'tax_input'    => array(
   528 					'tax_input'    => array(
   297 						'wp_theme' => array( wp_get_theme()->get_stylesheet() ),
   529 						'wp_theme' => array( $stylesheet ),
   298 					),
   530 					),
   299 				),
   531 				),
   300 				true
   532 				true
   301 			);
   533 			);
   302 			$user_cpt    = get_post( $cpt_post_id, ARRAY_A );
   534 			if ( ! is_wp_error( $cpt_post_id ) ) {
   303 		}
   535 				$user_cpt = get_object_vars( get_post( $cpt_post_id ) );
   304 		$cache_expiration = $user_cpt ? DAY_IN_SECONDS : HOUR_IN_SECONDS;
   536 			}
   305 		wp_cache_set( $cache_key, $user_cpt ? $user_cpt['ID'] : -1, '', $cache_expiration );
   537 		}
   306 
   538 
   307 		return $user_cpt;
   539 		return $user_cpt;
   308 	}
   540 	}
   309 
   541 
   310 	/**
   542 	/**
   311 	 * Returns the user's origin config.
   543 	 * Returns the user's origin config.
   312 	 *
   544 	 *
   313 	 * @since 5.9.0
   545 	 * @since 5.9.0
       
   546 	 * @since 6.6.0 The 'isGlobalStylesUserThemeJSON' flag is left on the user data.
       
   547 	 *              Register the block style variations coming from the user data.
   314 	 *
   548 	 *
   315 	 * @return WP_Theme_JSON Entity that holds styles for user data.
   549 	 * @return WP_Theme_JSON Entity that holds styles for user data.
   316 	 */
   550 	 */
   317 	public static function get_user_data() {
   551 	public static function get_user_data() {
   318 		if ( null !== static::$user ) {
   552 		if ( null !== static::$user && static::has_same_registered_blocks( 'user' ) ) {
   319 			return static::$user;
   553 			return static::$user;
   320 		}
   554 		}
   321 
   555 
   322 		$config   = array();
   556 		$config   = array();
   323 		$user_cpt = static::get_user_data_from_wp_global_styles( wp_get_theme() );
   557 		$user_cpt = static::get_user_data_from_wp_global_styles( wp_get_theme() );
   325 		if ( array_key_exists( 'post_content', $user_cpt ) ) {
   559 		if ( array_key_exists( 'post_content', $user_cpt ) ) {
   326 			$decoded_data = json_decode( $user_cpt['post_content'], true );
   560 			$decoded_data = json_decode( $user_cpt['post_content'], true );
   327 
   561 
   328 			$json_decoding_error = json_last_error();
   562 			$json_decoding_error = json_last_error();
   329 			if ( JSON_ERROR_NONE !== $json_decoding_error ) {
   563 			if ( JSON_ERROR_NONE !== $json_decoding_error ) {
   330 				trigger_error( 'Error when decoding a theme.json schema for user data. ' . json_last_error_msg() );
   564 				wp_trigger_error( __METHOD__, 'Error when decoding a theme.json schema for user data. ' . json_last_error_msg() );
   331 				return new WP_Theme_JSON( $config, 'custom' );
   565 				/**
   332 			}
   566 				 * Filters the data provided by the user for global styles & settings.
   333 
   567 				 *
   334 			// Very important to verify that the flag isGlobalStylesUserThemeJSON is true.
   568 				 * @since 6.1.0
   335 			// If it's not true then the content was not escaped and is not safe.
   569 				 *
       
   570 				 * @param WP_Theme_JSON_Data $theme_json Class to access and update the underlying data.
       
   571 				 */
       
   572 				$theme_json = apply_filters( 'wp_theme_json_data_user', new WP_Theme_JSON_Data( $config, 'custom' ) );
       
   573 
       
   574 				/*
       
   575 				 * Backward compatibility for extenders returning a WP_Theme_JSON_Data
       
   576 				 * compatible class that is not a WP_Theme_JSON_Data object.
       
   577 				 */
       
   578 				if ( $theme_json instanceof WP_Theme_JSON_Data ) {
       
   579 					return $theme_json->get_theme_json();
       
   580 				} else {
       
   581 					$config = $theme_json->get_data();
       
   582 					return new WP_Theme_JSON( $config, 'custom' );
       
   583 				}
       
   584 			}
       
   585 
       
   586 			/*
       
   587 			 * Very important to verify that the flag isGlobalStylesUserThemeJSON is true.
       
   588 			 * If it's not true then the content was not escaped and is not safe.
       
   589 			 */
   336 			if (
   590 			if (
   337 				is_array( $decoded_data ) &&
   591 				is_array( $decoded_data ) &&
   338 				isset( $decoded_data['isGlobalStylesUserThemeJSON'] ) &&
   592 				isset( $decoded_data['isGlobalStylesUserThemeJSON'] ) &&
   339 				$decoded_data['isGlobalStylesUserThemeJSON']
   593 				$decoded_data['isGlobalStylesUserThemeJSON']
   340 			) {
   594 			) {
   341 				unset( $decoded_data['isGlobalStylesUserThemeJSON'] );
   595 				unset( $decoded_data['isGlobalStylesUserThemeJSON'] );
   342 				$config = $decoded_data;
   596 				$config = $decoded_data;
   343 			}
   597 			}
   344 		}
   598 		}
   345 		static::$user = new WP_Theme_JSON( $config, 'custom' );
   599 
       
   600 		/** This filter is documented in wp-includes/class-wp-theme-json-resolver.php */
       
   601 		$theme_json = apply_filters( 'wp_theme_json_data_user', new WP_Theme_JSON_Data( $config, 'custom' ) );
       
   602 
       
   603 		/*
       
   604 		 * Backward compatibility for extenders returning a WP_Theme_JSON_Data
       
   605 		 * compatible class that is not a WP_Theme_JSON_Data object.
       
   606 		 */
       
   607 		if ( $theme_json instanceof WP_Theme_JSON_Data ) {
       
   608 			static::$user = $theme_json->get_theme_json();
       
   609 		} else {
       
   610 			$config       = $theme_json->get_data();
       
   611 			static::$user = new WP_Theme_JSON( $config, 'custom' );
       
   612 		}
   346 
   613 
   347 		return static::$user;
   614 		return static::$user;
   348 	}
   615 	}
   349 
   616 
   350 	/**
   617 	/**
   351 	 * Returns the data merged from multiple origins.
   618 	 * Returns the data merged from multiple origins.
   352 	 *
   619 	 *
   353 	 * There are three sources of data (origins) for a site:
   620 	 * There are four sources of data (origins) for a site:
   354 	 * default, theme, and custom. The custom's has higher priority
   621 	 *
   355 	 * than the theme's, and the theme's higher than default's.
   622 	 * - default => WordPress
       
   623 	 * - blocks  => each one of the blocks provides data for itself
       
   624 	 * - theme   => the active theme
       
   625 	 * - custom  => data provided by the user
       
   626 	 *
       
   627 	 * The custom's has higher priority than the theme's, the theme's higher than blocks',
       
   628 	 * and block's higher than default's.
   356 	 *
   629 	 *
   357 	 * Unlike the getters
   630 	 * Unlike the getters
   358 	 * {@link https://developer.wordpress.org/reference/classes/wp_theme_json_resolver/get_core_data/ get_core_data},
   631 	 * {@link https://developer.wordpress.org/reference/classes/wp_theme_json_resolver/get_core_data/ get_core_data},
   359 	 * {@link https://developer.wordpress.org/reference/classes/wp_theme_json_resolver/get_theme_data/ get_theme_data},
   632 	 * {@link https://developer.wordpress.org/reference/classes/wp_theme_json_resolver/get_theme_data/ get_theme_data},
   360 	 * and {@link https://developer.wordpress.org/reference/classes/wp_theme_json_resolver/get_user_data/ get_user_data},
   633 	 * and {@link https://developer.wordpress.org/reference/classes/wp_theme_json_resolver/get_user_data/ get_user_data},
   361 	 * this method returns data after it has been merged with the previous origins.
   634 	 * this method returns data after it has been merged with the previous origins.
   362 	 * This means that if the same piece of data is declared in different origins
   635 	 * This means that if the same piece of data is declared in different origins
   363 	 * (user, theme, and core), the last origin overrides the previous.
   636 	 * (default, blocks, theme, custom), the last origin overrides the previous.
   364 	 *
   637 	 *
   365 	 * For example, if the user has set a background color
   638 	 * For example, if the user has set a background color
   366 	 * for the paragraph block, and the theme has done it as well,
   639 	 * for the paragraph block, and the theme has done it as well,
   367 	 * the user preference wins.
   640 	 * the user preference wins.
   368 	 *
   641 	 *
   369 	 * @since 5.8.0
   642 	 * @since 5.8.0
   370 	 * @since 5.9.0 Added user data, removed the `$settings` parameter,
   643 	 * @since 5.9.0 Added user data, removed the `$settings` parameter,
   371 	 *              added the `$origin` parameter.
   644 	 *              added the `$origin` parameter.
   372 	 *
   645 	 * @since 6.1.0 Added block data and generation of spacingSizes array.
   373 	 * @param string $origin Optional. To what level should we merge data.
   646 	 * @since 6.2.0 Changed ' $origin' parameter values to 'default', 'blocks', 'theme' or 'custom'.
   374 	 *                       Valid values are 'theme' or 'custom'. Default 'custom'.
   647 	 *
       
   648 	 * @param string $origin Optional. To what level should we merge data: 'default', 'blocks', 'theme' or 'custom'.
       
   649 	 *                       'custom' is used as default value as well as fallback value if the origin is unknown.
   375 	 * @return WP_Theme_JSON
   650 	 * @return WP_Theme_JSON
   376 	 */
   651 	 */
   377 	public static function get_merged_data( $origin = 'custom' ) {
   652 	public static function get_merged_data( $origin = 'custom' ) {
   378 		if ( is_array( $origin ) ) {
   653 		if ( is_array( $origin ) ) {
   379 			_deprecated_argument( __FUNCTION__, '5.9.0' );
   654 			_deprecated_argument( __FUNCTION__, '5.9.0' );
   380 		}
   655 		}
   381 
   656 
   382 		$result = new WP_Theme_JSON();
   657 		$result = new WP_Theme_JSON();
   383 		$result->merge( static::get_core_data() );
   658 		$result->merge( static::get_core_data() );
       
   659 		if ( 'default' === $origin ) {
       
   660 			return $result;
       
   661 		}
       
   662 
       
   663 		$result->merge( static::get_block_data() );
       
   664 		if ( 'blocks' === $origin ) {
       
   665 			return $result;
       
   666 		}
       
   667 
   384 		$result->merge( static::get_theme_data() );
   668 		$result->merge( static::get_theme_data() );
   385 
   669 		if ( 'theme' === $origin ) {
   386 		if ( 'custom' === $origin ) {
   670 			return $result;
   387 			$result->merge( static::get_user_data() );
   671 		}
   388 		}
   672 
       
   673 		$result->merge( static::get_user_data() );
   389 
   674 
   390 		return $result;
   675 		return $result;
   391 	}
   676 	}
   392 
   677 
   393 	/**
   678 	/**
   415 	/**
   700 	/**
   416 	 * Determines whether the active theme has a theme.json file.
   701 	 * Determines whether the active theme has a theme.json file.
   417 	 *
   702 	 *
   418 	 * @since 5.8.0
   703 	 * @since 5.8.0
   419 	 * @since 5.9.0 Added a check in the parent theme.
   704 	 * @since 5.9.0 Added a check in the parent theme.
       
   705 	 * @deprecated 6.2.0 Use wp_theme_has_theme_json() instead.
   420 	 *
   706 	 *
   421 	 * @return bool
   707 	 * @return bool
   422 	 */
   708 	 */
   423 	public static function theme_has_support() {
   709 	public static function theme_has_support() {
   424 		if ( ! isset( static::$theme_has_support ) ) {
   710 		_deprecated_function( __METHOD__, '6.2.0', 'wp_theme_has_theme_json()' );
   425 			static::$theme_has_support = (
   711 
   426 				is_readable( static::get_file_path_from_theme( 'theme.json' ) ) ||
   712 		return wp_theme_has_theme_json();
   427 				is_readable( static::get_file_path_from_theme( 'theme.json', true ) )
       
   428 			);
       
   429 		}
       
   430 
       
   431 		return static::$theme_has_support;
       
   432 	}
   713 	}
   433 
   714 
   434 	/**
   715 	/**
   435 	 * Builds the path to the given file and checks that it is readable.
   716 	 * Builds the path to the given file and checks that it is readable.
   436 	 *
   717 	 *
   454 	 * Cleans the cached data so it can be recalculated.
   735 	 * Cleans the cached data so it can be recalculated.
   455 	 *
   736 	 *
   456 	 * @since 5.8.0
   737 	 * @since 5.8.0
   457 	 * @since 5.9.0 Added the `$user`, `$user_custom_post_type_id`,
   738 	 * @since 5.9.0 Added the `$user`, `$user_custom_post_type_id`,
   458 	 *              and `$i18n_schema` variables to reset.
   739 	 *              and `$i18n_schema` variables to reset.
       
   740 	 * @since 6.1.0 Added the `$blocks` and `$blocks_cache` variables
       
   741 	 *              to reset.
   459 	 */
   742 	 */
   460 	public static function clean_cached_data() {
   743 	public static function clean_cached_data() {
   461 		static::$core                     = null;
   744 		static::$core                     = null;
       
   745 		static::$blocks                   = null;
       
   746 		static::$blocks_cache             = array(
       
   747 			'core'   => array(),
       
   748 			'blocks' => array(),
       
   749 			'theme'  => array(),
       
   750 			'user'   => array(),
       
   751 		);
   462 		static::$theme                    = null;
   752 		static::$theme                    = null;
   463 		static::$user                     = null;
   753 		static::$user                     = null;
   464 		static::$user_custom_post_type_id = null;
   754 		static::$user_custom_post_type_id = null;
   465 		static::$theme_has_support        = null;
       
   466 		static::$i18n_schema              = null;
   755 		static::$i18n_schema              = null;
   467 	}
   756 	}
   468 
   757 
   469 	/**
   758 	/**
       
   759 	 * Returns an array of all nested JSON files within a given directory.
       
   760 	 *
       
   761 	 * @since 6.2.0
       
   762 	 *
       
   763 	 * @param string $dir The directory to recursively iterate and list files of.
       
   764 	 * @return array The merged array.
       
   765 	 */
       
   766 	private static function recursively_iterate_json( $dir ) {
       
   767 		$nested_files      = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $dir ) );
       
   768 		$nested_json_files = iterator_to_array( new RegexIterator( $nested_files, '/^.+\.json$/i', RecursiveRegexIterator::GET_MATCH ) );
       
   769 		return $nested_json_files;
       
   770 	}
       
   771 
       
   772 	/**
       
   773 	 * Determines if a supplied style variation matches the provided scope.
       
   774 	 *
       
   775 	 * For backwards compatibility, if a variation does not define any scope
       
   776 	 * related property, e.g. `blockTypes`, it is assumed to be a theme style
       
   777 	 * variation.
       
   778 	 *
       
   779 	 * @since 6.6.0
       
   780 	 *
       
   781 	 * @param array  $variation Theme.json shaped style variation object.
       
   782 	 * @param string $scope     Scope to check e.g. theme, block etc.
       
   783 	 * @return boolean
       
   784 	 */
       
   785 	private static function style_variation_has_scope( $variation, $scope ) {
       
   786 		if ( 'block' === $scope ) {
       
   787 			return isset( $variation['blockTypes'] );
       
   788 		}
       
   789 
       
   790 		if ( 'theme' === $scope ) {
       
   791 			return ! isset( $variation['blockTypes'] );
       
   792 		}
       
   793 
       
   794 		return false;
       
   795 	}
       
   796 
       
   797 	/**
   470 	 * Returns the style variations defined by the theme.
   798 	 * Returns the style variations defined by the theme.
   471 	 *
   799 	 *
   472 	 * @since 6.0.0
   800 	 * @since 6.0.0
   473 	 *
   801 	 * @since 6.2.0 Returns parent theme variations if theme is a child.
       
   802 	 * @since 6.6.0 Added configurable scope parameter to allow filtering
       
   803 	 *              theme.json partial files by the scope to which they
       
   804 	 *              can be applied e.g. theme vs block etc.
       
   805 	 *              Added basic caching for read theme.json partial files.
       
   806 	 *
       
   807 	 * @param string $scope The scope or type of style variation to retrieve e.g. theme, block etc.
   474 	 * @return array
   808 	 * @return array
   475 	 */
   809 	 */
   476 	public static function get_style_variations() {
   810 	public static function get_style_variations( $scope = 'theme' ) {
   477 		$variations     = array();
   811 		$variation_files    = array();
   478 		$base_directory = get_stylesheet_directory() . '/styles';
   812 		$variations         = array();
       
   813 		$base_directory     = get_stylesheet_directory() . '/styles';
       
   814 		$template_directory = get_template_directory() . '/styles';
   479 		if ( is_dir( $base_directory ) ) {
   815 		if ( is_dir( $base_directory ) ) {
   480 			$nested_files      = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $base_directory ) );
   816 			$variation_files = static::recursively_iterate_json( $base_directory );
   481 			$nested_html_files = iterator_to_array( new RegexIterator( $nested_files, '/^.+\.json$/i', RecursiveRegexIterator::GET_MATCH ) );
   817 		}
   482 			ksort( $nested_html_files );
   818 		if ( is_dir( $template_directory ) && $template_directory !== $base_directory ) {
   483 			foreach ( $nested_html_files as $path => $file ) {
   819 			$variation_files_parent = static::recursively_iterate_json( $template_directory );
   484 				$decoded_file = wp_json_file_decode( $path, array( 'associative' => true ) );
   820 			// If the child and parent variation file basename are the same, only include the child theme's.
   485 				if ( is_array( $decoded_file ) ) {
   821 			foreach ( $variation_files_parent as $parent_path => $parent ) {
   486 					$translated = static::translate( $decoded_file, wp_get_theme()->get( 'TextDomain' ) );
   822 				foreach ( $variation_files as $child_path => $child ) {
   487 					$variation  = ( new WP_Theme_JSON( $translated ) )->get_raw_data();
   823 					if ( basename( $parent_path ) === basename( $child_path ) ) {
   488 					if ( empty( $variation['title'] ) ) {
   824 						unset( $variation_files_parent[ $parent_path ] );
   489 						$variation['title'] = basename( $path, '.json' );
       
   490 					}
   825 					}
   491 					$variations[] = $variation;
       
   492 				}
   826 				}
   493 			}
   827 			}
       
   828 			$variation_files = array_merge( $variation_files, $variation_files_parent );
       
   829 		}
       
   830 		ksort( $variation_files );
       
   831 		foreach ( $variation_files as $path => $file ) {
       
   832 			$decoded_file = self::read_json_file( $path );
       
   833 			if ( is_array( $decoded_file ) && static::style_variation_has_scope( $decoded_file, $scope ) ) {
       
   834 				$translated = static::translate( $decoded_file, wp_get_theme()->get( 'TextDomain' ) );
       
   835 				$variation  = ( new WP_Theme_JSON( $translated ) )->get_raw_data();
       
   836 				if ( empty( $variation['title'] ) ) {
       
   837 					$variation['title'] = basename( $path, '.json' );
       
   838 				}
       
   839 				$variations[] = $variation;
       
   840 			}
   494 		}
   841 		}
   495 		return $variations;
   842 		return $variations;
   496 	}
   843 	}
   497 
   844 
       
   845 	/**
       
   846 	 * Resolves relative paths in theme.json styles to theme absolute paths
       
   847 	 * and returns them in an array that can be embedded
       
   848 	 * as the value of `_link` object in REST API responses.
       
   849 	 *
       
   850 	 * @since 6.6.0
       
   851 	 *
       
   852 	 * @param WP_Theme_JSON $theme_json A theme json instance.
       
   853 	 * @return array An array of resolved paths.
       
   854 	 */
       
   855 	public static function get_resolved_theme_uris( $theme_json ) {
       
   856 		$resolved_theme_uris = array();
       
   857 
       
   858 		if ( ! $theme_json instanceof WP_Theme_JSON ) {
       
   859 			return $resolved_theme_uris;
       
   860 		}
       
   861 
       
   862 		$theme_json_data = $theme_json->get_raw_data();
       
   863 
       
   864 		// Top level styles.
       
   865 		$background_image_url = isset( $theme_json_data['styles']['background']['backgroundImage']['url'] ) ? $theme_json_data['styles']['background']['backgroundImage']['url'] : null;
       
   866 
       
   867 		/*
       
   868 		 * The same file convention when registering web fonts.
       
   869 		 * See: WP_Font_Face_Resolver::to_theme_file_uri.
       
   870 		 */
       
   871 		$placeholder = 'file:./';
       
   872 		if (
       
   873 			isset( $background_image_url ) &&
       
   874 			is_string( $background_image_url ) &&
       
   875 			// Skip if the src doesn't start with the placeholder, as there's nothing to replace.
       
   876 			str_starts_with( $background_image_url, $placeholder )
       
   877 		) {
       
   878 			$file_type          = wp_check_filetype( $background_image_url );
       
   879 			$src_url            = str_replace( $placeholder, '', $background_image_url );
       
   880 			$resolved_theme_uri = array(
       
   881 				'name'   => $background_image_url,
       
   882 				'href'   => sanitize_url( get_theme_file_uri( $src_url ) ),
       
   883 				'target' => 'styles.background.backgroundImage.url',
       
   884 			);
       
   885 			if ( isset( $file_type['type'] ) ) {
       
   886 				$resolved_theme_uri['type'] = $file_type['type'];
       
   887 			}
       
   888 			$resolved_theme_uris[] = $resolved_theme_uri;
       
   889 		}
       
   890 
       
   891 		return $resolved_theme_uris;
       
   892 	}
       
   893 
       
   894 	/**
       
   895 	 * Resolves relative paths in theme.json styles to theme absolute paths
       
   896 	 * and merges them with incoming theme JSON.
       
   897 	 *
       
   898 	 * @since 6.6.0
       
   899 	 *
       
   900 	 * @param WP_Theme_JSON $theme_json A theme json instance.
       
   901 	 * @return WP_Theme_JSON Theme merged with resolved paths, if any found.
       
   902 	 */
       
   903 	public static function resolve_theme_file_uris( $theme_json ) {
       
   904 		$resolved_urls = static::get_resolved_theme_uris( $theme_json );
       
   905 		if ( empty( $resolved_urls ) ) {
       
   906 			return $theme_json;
       
   907 		}
       
   908 
       
   909 		$resolved_theme_json_data = array(
       
   910 			'version' => WP_Theme_JSON::LATEST_SCHEMA,
       
   911 		);
       
   912 
       
   913 		foreach ( $resolved_urls as $resolved_url ) {
       
   914 			$path = explode( '.', $resolved_url['target'] );
       
   915 			_wp_array_set( $resolved_theme_json_data, $path, $resolved_url['href'] );
       
   916 		}
       
   917 
       
   918 		$theme_json->merge( new WP_Theme_JSON( $resolved_theme_json_data ) );
       
   919 
       
   920 		return $theme_json;
       
   921 	}
       
   922 
       
   923 	/**
       
   924 	 * Adds variations sourced from block style variations files to the supplied theme.json data.
       
   925 	 *
       
   926 	 * @since 6.6.0
       
   927 	 *
       
   928 	 * @param array $data       Array following the theme.json specification.
       
   929 	 * @param array $variations Shared block style variations.
       
   930 	 * @return array Theme json data including shared block style variation definitions.
       
   931 	 */
       
   932 	private static function inject_variations_from_block_style_variation_files( $data, $variations ) {
       
   933 		if ( empty( $variations ) ) {
       
   934 			return $data;
       
   935 		}
       
   936 
       
   937 		foreach ( $variations as $variation ) {
       
   938 			if ( empty( $variation['styles'] ) || empty( $variation['blockTypes'] ) ) {
       
   939 				continue;
       
   940 			}
       
   941 
       
   942 			$variation_name = $variation['slug'] ?? _wp_to_kebab_case( $variation['title'] );
       
   943 
       
   944 			foreach ( $variation['blockTypes'] as $block_type ) {
       
   945 				// First, override partial styles with any top-level styles.
       
   946 				$top_level_data = $data['styles']['variations'][ $variation_name ] ?? array();
       
   947 				if ( ! empty( $top_level_data ) ) {
       
   948 					$variation['styles'] = array_replace_recursive( $variation['styles'], $top_level_data );
       
   949 				}
       
   950 
       
   951 				// Then, override styles so far with any block-level styles.
       
   952 				$block_level_data = $data['styles']['blocks'][ $block_type ]['variations'][ $variation_name ] ?? array();
       
   953 				if ( ! empty( $block_level_data ) ) {
       
   954 					$variation['styles'] = array_replace_recursive( $variation['styles'], $block_level_data );
       
   955 				}
       
   956 
       
   957 				$path = array( 'styles', 'blocks', $block_type, 'variations', $variation_name );
       
   958 				_wp_array_set( $data, $path, $variation['styles'] );
       
   959 			}
       
   960 		}
       
   961 
       
   962 		return $data;
       
   963 	}
       
   964 
       
   965 	/**
       
   966 	 * Adds variations sourced from the block styles registry to the supplied theme.json data.
       
   967 	 *
       
   968 	 * @since 6.6.0
       
   969 	 *
       
   970 	 * @param array $data Array following the theme.json specification.
       
   971 	 * @return array Theme json data including shared block style variation definitions.
       
   972 	 */
       
   973 	private static function inject_variations_from_block_styles_registry( $data ) {
       
   974 		$registry = WP_Block_Styles_Registry::get_instance();
       
   975 		$styles   = $registry->get_all_registered();
       
   976 
       
   977 		foreach ( $styles as $block_type => $variations ) {
       
   978 			foreach ( $variations as $variation_name => $variation ) {
       
   979 				if ( empty( $variation['style_data'] ) ) {
       
   980 					continue;
       
   981 				}
       
   982 
       
   983 				// First, override registry styles with any top-level styles.
       
   984 				$top_level_data = $data['styles']['variations'][ $variation_name ] ?? array();
       
   985 				if ( ! empty( $top_level_data ) ) {
       
   986 					$variation['style_data'] = array_replace_recursive( $variation['style_data'], $top_level_data );
       
   987 				}
       
   988 
       
   989 				// Then, override styles so far with any block-level styles.
       
   990 				$block_level_data = $data['styles']['blocks'][ $block_type ]['variations'][ $variation_name ] ?? array();
       
   991 				if ( ! empty( $block_level_data ) ) {
       
   992 					$variation['style_data'] = array_replace_recursive( $variation['style_data'], $block_level_data );
       
   993 				}
       
   994 
       
   995 				$path = array( 'styles', 'blocks', $block_type, 'variations', $variation_name );
       
   996 				_wp_array_set( $data, $path, $variation['style_data'] );
       
   997 			}
       
   998 		}
       
   999 
       
  1000 		return $data;
       
  1001 	}
   498 }
  1002 }