wp/wp-includes/class-wp-theme-json.php
changeset 22 8c2e4d02f4ef
parent 21 48c4eec2b7e6
equal deleted inserted replaced
21:48c4eec2b7e6 22:8c2e4d02f4ef
   224 	 * @since 6.2.0 Added `outline-*`, and `min-height` properties.
   224 	 * @since 6.2.0 Added `outline-*`, and `min-height` properties.
   225 	 * @since 6.3.0 Added `column-count` property.
   225 	 * @since 6.3.0 Added `column-count` property.
   226 	 * @since 6.4.0 Added `writing-mode` property.
   226 	 * @since 6.4.0 Added `writing-mode` property.
   227 	 * @since 6.5.0 Added `aspect-ratio` property.
   227 	 * @since 6.5.0 Added `aspect-ratio` property.
   228 	 * @since 6.6.0 Added `background-[image|position|repeat|size]` properties.
   228 	 * @since 6.6.0 Added `background-[image|position|repeat|size]` properties.
   229 	 *
   229 	 * @since 6.7.0 Added `background-attachment` property.
   230 	 * @var array
   230 	 * @var array
   231 	 */
   231 	 */
   232 	const PROPERTIES_METADATA = array(
   232 	const PROPERTIES_METADATA = array(
   233 		'aspect-ratio'                      => array( 'dimensions', 'aspectRatio' ),
   233 		'aspect-ratio'                      => array( 'dimensions', 'aspectRatio' ),
   234 		'background'                        => array( 'color', 'gradient' ),
   234 		'background'                        => array( 'color', 'gradient' ),
   235 		'background-color'                  => array( 'color', 'background' ),
   235 		'background-color'                  => array( 'color', 'background' ),
   236 		'background-image'                  => array( 'background', 'backgroundImage' ),
   236 		'background-image'                  => array( 'background', 'backgroundImage' ),
   237 		'background-position'               => array( 'background', 'backgroundPosition' ),
   237 		'background-position'               => array( 'background', 'backgroundPosition' ),
   238 		'background-repeat'                 => array( 'background', 'backgroundRepeat' ),
   238 		'background-repeat'                 => array( 'background', 'backgroundRepeat' ),
   239 		'background-size'                   => array( 'background', 'backgroundSize' ),
   239 		'background-size'                   => array( 'background', 'backgroundSize' ),
       
   240 		'background-attachment'             => array( 'background', 'backgroundAttachment' ),
   240 		'border-radius'                     => array( 'border', 'radius' ),
   241 		'border-radius'                     => array( 'border', 'radius' ),
   241 		'border-top-left-radius'            => array( 'border', 'radius', 'topLeft' ),
   242 		'border-top-left-radius'            => array( 'border', 'radius', 'topLeft' ),
   242 		'border-top-right-radius'           => array( 'border', 'radius', 'topRight' ),
   243 		'border-top-right-radius'           => array( 'border', 'radius', 'topRight' ),
   243 		'border-bottom-left-radius'         => array( 'border', 'radius', 'bottomLeft' ),
   244 		'border-bottom-left-radius'         => array( 'border', 'radius', 'bottomLeft' ),
   244 		'border-bottom-right-radius'        => array( 'border', 'radius', 'bottomRight' ),
   245 		'border-bottom-right-radius'        => array( 'border', 'radius', 'bottomRight' ),
   303 	 * but are used elsewhere in the processing of global styles. The indirect
   304 	 * but are used elsewhere in the processing of global styles. The indirect
   304 	 * property is used to validate whether a style value is allowed.
   305 	 * property is used to validate whether a style value is allowed.
   305 	 *
   306 	 *
   306 	 * @since 6.2.0
   307 	 * @since 6.2.0
   307 	 * @since 6.6.0 Added background-image properties.
   308 	 * @since 6.6.0 Added background-image properties.
   308 	 *
       
   309 	 * @var array
   309 	 * @var array
   310 	 */
   310 	 */
   311 	const INDIRECT_PROPERTIES_METADATA = array(
   311 	const INDIRECT_PROPERTIES_METADATA = array(
   312 		'gap'              => array(
   312 		'gap'              => array(
   313 			array( 'spacing', 'blockGap' ),
   313 			array( 'spacing', 'blockGap' ),
   335 	 *
   335 	 *
   336 	 * Each element maps the style property to the corresponding theme.json
   336 	 * Each element maps the style property to the corresponding theme.json
   337 	 * setting key.
   337 	 * setting key.
   338 	 *
   338 	 *
   339 	 * @since 5.9.0
   339 	 * @since 5.9.0
       
   340 	 * @var array
   340 	 */
   341 	 */
   341 	const PROTECTED_PROPERTIES = array(
   342 	const PROTECTED_PROPERTIES = array(
   342 		'spacing.blockGap' => array( 'spacing', 'blockGap' ),
   343 		'spacing.blockGap' => array( 'spacing', 'blockGap' ),
   343 	);
   344 	);
   344 
   345 
   467 			'textTransform'    => null,
   468 			'textTransform'    => null,
   468 			'writingMode'      => null,
   469 			'writingMode'      => null,
   469 		),
   470 		),
   470 	);
   471 	);
   471 
   472 
   472 	/*
   473 	/**
   473 	 * The valid properties for fontFamilies under settings key.
   474 	 * The valid properties for fontFamilies under settings key.
   474 	 *
   475 	 *
   475 	 * @since 6.5.0
   476 	 * @since 6.5.0
   476 	 *
       
   477 	 * @var array
   477 	 * @var array
   478 	 */
   478 	 */
   479 	const FONT_FAMILY_SCHEMA = array(
   479 	const FONT_FAMILY_SCHEMA = array(
   480 		array(
   480 		array(
   481 			'fontFamily' => null,
   481 			'fontFamily' => null,
   513 	 *              updated `blockGap` to be allowed at any level.
   513 	 *              updated `blockGap` to be allowed at any level.
   514 	 * @since 6.2.0 Added `outline`, and `minHeight` properties.
   514 	 * @since 6.2.0 Added `outline`, and `minHeight` properties.
   515 	 * @since 6.3.0 Added support for `typography.textColumns`.
   515 	 * @since 6.3.0 Added support for `typography.textColumns`.
   516 	 * @since 6.5.0 Added support for `dimensions.aspectRatio`.
   516 	 * @since 6.5.0 Added support for `dimensions.aspectRatio`.
   517 	 * @since 6.6.0 Added `background` sub properties to top-level only.
   517 	 * @since 6.6.0 Added `background` sub properties to top-level only.
   518 	 *
       
   519 	 * @var array
   518 	 * @var array
   520 	 */
   519 	 */
   521 	const VALID_STYLES = array(
   520 	const VALID_STYLES = array(
   522 		'background' => array(
   521 		'background' => array(
   523 			'backgroundImage'    => 'top',
   522 			'backgroundImage'      => null,
   524 			'backgroundPosition' => 'top',
   523 			'backgroundPosition'   => null,
   525 			'backgroundRepeat'   => 'top',
   524 			'backgroundRepeat'     => null,
   526 			'backgroundSize'     => 'top',
   525 			'backgroundSize'       => null,
       
   526 			'backgroundAttachment' => null,
   527 		),
   527 		),
   528 		'border'     => array(
   528 		'border'     => array(
   529 			'color'  => null,
   529 			'color'  => null,
   530 			'radius' => null,
   530 			'radius' => null,
   531 			'style'  => null,
   531 			'style'  => null,
   576 	);
   576 	);
   577 
   577 
   578 	/**
   578 	/**
   579 	 * Defines which pseudo selectors are enabled for which elements.
   579 	 * Defines which pseudo selectors are enabled for which elements.
   580 	 *
   580 	 *
   581 	 * The order of the selectors should be: link, any-link, visited, hover, focus, active.
   581 	 * The order of the selectors should be: link, any-link, visited, hover, focus, focus-visible, active.
   582 	 * This is to ensure the user action (hover, focus and active) styles have a higher
   582 	 * This is to ensure the user action (hover, focus and active) styles have a higher
   583 	 * specificity than the visited styles, which in turn have a higher specificity than
   583 	 * specificity than the visited styles, which in turn have a higher specificity than
   584 	 * the unvisited styles.
   584 	 * the unvisited styles.
   585 	 *
   585 	 *
   586 	 * See https://core.trac.wordpress.org/ticket/56928.
   586 	 * See https://core.trac.wordpress.org/ticket/56928.
   587 	 * Note: this will affect both top-level and block-level elements.
   587 	 * Note: this will affect both top-level and block-level elements.
   588 	 *
   588 	 *
   589 	 * @since 6.1.0
   589 	 * @since 6.1.0
   590 	 * @since 6.2.0 Added support for ':link' and ':any-link'.
   590 	 * @since 6.2.0 Added support for ':link' and ':any-link'.
       
   591 	 * @since 6.8.0 Added support for ':focus-visible'.
       
   592 	 * @var array
   591 	 */
   593 	 */
   592 	const VALID_ELEMENT_PSEUDO_SELECTORS = array(
   594 	const VALID_ELEMENT_PSEUDO_SELECTORS = array(
   593 		'link'   => array( ':link', ':any-link', ':visited', ':hover', ':focus', ':active' ),
   595 		'link'   => array( ':link', ':any-link', ':visited', ':hover', ':focus', ':focus-visible', ':active' ),
   594 		'button' => array( ':link', ':any-link', ':visited', ':hover', ':focus', ':active' ),
   596 		'button' => array( ':link', ':any-link', ':visited', ':hover', ':focus', ':focus-visible', ':active' ),
   595 	);
   597 	);
   596 
   598 
   597 	/**
   599 	/**
   598 	 * The valid elements that can be found under styles.
   600 	 * The valid elements that can be found under styles.
   599 	 *
   601 	 *
   754 		if ( ! in_array( $origin, static::VALID_ORIGINS, true ) ) {
   756 		if ( ! in_array( $origin, static::VALID_ORIGINS, true ) ) {
   755 			$origin = 'theme';
   757 			$origin = 'theme';
   756 		}
   758 		}
   757 
   759 
   758 		$this->theme_json    = WP_Theme_JSON_Schema::migrate( $theme_json, $origin );
   760 		$this->theme_json    = WP_Theme_JSON_Schema::migrate( $theme_json, $origin );
   759 		$valid_block_names   = array_keys( static::get_blocks_metadata() );
   761 		$blocks_metadata     = static::get_blocks_metadata();
       
   762 		$valid_block_names   = array_keys( $blocks_metadata );
   760 		$valid_element_names = array_keys( static::ELEMENTS );
   763 		$valid_element_names = array_keys( static::ELEMENTS );
   761 		$valid_variations    = static::get_valid_block_style_variations();
   764 		$valid_variations    = static::get_valid_block_style_variations( $blocks_metadata );
   762 		$this->theme_json    = static::unwrap_shared_block_style_variations( $this->theme_json, $valid_variations );
   765 		$this->theme_json    = static::unwrap_shared_block_style_variations( $this->theme_json, $valid_variations );
   763 		$this->theme_json    = static::sanitize( $this->theme_json, $valid_block_names, $valid_element_names, $valid_variations );
   766 		$this->theme_json    = static::sanitize( $this->theme_json, $valid_block_names, $valid_element_names, $valid_variations );
   764 		$this->theme_json    = static::maybe_opt_in_into_settings( $this->theme_json );
   767 		$this->theme_json    = static::maybe_opt_in_into_settings( $this->theme_json );
   765 
   768 
   766 		// Internally, presets are keyed by origin.
   769 		// Internally, presets are keyed by origin.
   933 	 * @param array $valid_element_names List of valid element names.
   936 	 * @param array $valid_element_names List of valid element names.
   934 	 * @param array $valid_variations    List of valid variations per block.
   937 	 * @param array $valid_variations    List of valid variations per block.
   935 	 * @return array The sanitized output.
   938 	 * @return array The sanitized output.
   936 	 */
   939 	 */
   937 	protected static function sanitize( $input, $valid_block_names, $valid_element_names, $valid_variations ) {
   940 	protected static function sanitize( $input, $valid_block_names, $valid_element_names, $valid_variations ) {
   938 
       
   939 		$output = array();
   941 		$output = array();
   940 
   942 
   941 		if ( ! is_array( $input ) ) {
   943 		if ( ! is_array( $input ) ) {
   942 			return $output;
   944 			return $output;
   943 		}
   945 		}
  1314 	 *
  1316 	 *
  1315 	 * @param string[] $types   Types of styles to load. Will load all by default. It accepts:
  1317 	 * @param string[] $types   Types of styles to load. Will load all by default. It accepts:
  1316 	 *                          - `variables`: only the CSS Custom Properties for presets & custom ones.
  1318 	 *                          - `variables`: only the CSS Custom Properties for presets & custom ones.
  1317 	 *                          - `styles`: only the styles section in theme.json.
  1319 	 *                          - `styles`: only the styles section in theme.json.
  1318 	 *                          - `presets`: only the classes for the presets.
  1320 	 *                          - `presets`: only the classes for the presets.
       
  1321 	 *                          - `base-layout-styles`: only the base layout styles.
       
  1322 	 *                          - `custom-css`: only the custom CSS.
  1319 	 * @param string[] $origins A list of origins to include. By default it includes VALID_ORIGINS.
  1323 	 * @param string[] $origins A list of origins to include. By default it includes VALID_ORIGINS.
  1320 	 * @param array    $options {
  1324 	 * @param array    $options {
  1321 	 *     Optional. An array of options for now used for internal purposes only (may change without notice).
  1325 	 *     Optional. An array of options for now used for internal purposes only (may change without notice).
  1322 	 *
  1326 	 *
  1323 	 *     @type string $scope                           Makes sure all style are scoped to a given selector
  1327 	 *     @type string $scope                           Makes sure all style are scoped to a given selector
  1421 
  1425 
  1422 		if ( in_array( 'presets', $types, true ) ) {
  1426 		if ( in_array( 'presets', $types, true ) ) {
  1423 			$stylesheet .= $this->get_preset_classes( $setting_nodes, $origins );
  1427 			$stylesheet .= $this->get_preset_classes( $setting_nodes, $origins );
  1424 		}
  1428 		}
  1425 
  1429 
       
  1430 		// Load the custom CSS last so it has the highest specificity.
       
  1431 		if ( in_array( 'custom-css', $types, true ) ) {
       
  1432 			// Add the global styles root CSS.
       
  1433 			$stylesheet .= _wp_array_get( $this->theme_json, array( 'styles', 'css' ) );
       
  1434 		}
       
  1435 
  1426 		return $stylesheet;
  1436 		return $stylesheet;
  1427 	}
  1437 	}
  1428 
  1438 
  1429 	/**
  1439 	/**
  1430 	 * Processes the CSS, to apply nesting.
  1440 	 * Processes the CSS, to apply nesting.
  1486 
  1496 
  1487 	/**
  1497 	/**
  1488 	 * Returns the global styles custom CSS.
  1498 	 * Returns the global styles custom CSS.
  1489 	 *
  1499 	 *
  1490 	 * @since 6.2.0
  1500 	 * @since 6.2.0
       
  1501 	 * @deprecated 6.7.0 Use {@see 'get_stylesheet'} instead.
  1491 	 *
  1502 	 *
  1492 	 * @return string The global styles custom CSS.
  1503 	 * @return string The global styles custom CSS.
  1493 	 */
  1504 	 */
  1494 	public function get_custom_css() {
  1505 	public function get_custom_css() {
       
  1506 		_deprecated_function( __METHOD__, '6.7.0', 'get_stylesheet' );
  1495 		// Add the global styles root CSS.
  1507 		// Add the global styles root CSS.
  1496 		$stylesheet = isset( $this->theme_json['styles']['css'] ) ? $this->theme_json['styles']['css'] : '';
  1508 		$stylesheet = isset( $this->theme_json['styles']['css'] ) ? $this->theme_json['styles']['css'] : '';
  1497 
  1509 
  1498 		// Add the global styles block CSS.
  1510 		// Add the global styles block CSS.
  1499 		if ( isset( $this->theme_json['styles']['blocks'] ) ) {
  1511 		if ( isset( $this->theme_json['styles']['blocks'] ) ) {
  2282 	 * Given a styles array, it extracts the style properties
  2294 	 * Given a styles array, it extracts the style properties
  2283 	 * and adds them to the $declarations array following the format:
  2295 	 * and adds them to the $declarations array following the format:
  2284 	 *
  2296 	 *
  2285 	 *     array(
  2297 	 *     array(
  2286 	 *       'name'  => 'property_name',
  2298 	 *       'name'  => 'property_name',
  2287 	 *       'value' => 'property_value,
  2299 	 *       'value' => 'property_value',
  2288 	 *     )
  2300 	 *     )
  2289 	 *
  2301 	 *
  2290 	 * @since 5.8.0
  2302 	 * @since 5.8.0
  2291 	 * @since 5.9.0 Added the `$settings` and `$properties` parameters.
  2303 	 * @since 5.9.0 Added the `$settings` and `$properties` parameters.
  2292 	 * @since 6.1.0 Added `$theme_json`, `$selector`, and `$use_root_padding` parameters.
  2304 	 * @since 6.1.0 Added `$theme_json`, `$selector`, and `$use_root_padding` parameters.
  2293 	 * @since 6.5.0 Output a `min-height: unset` rule when `aspect-ratio` is set.
  2305 	 * @since 6.5.0 Output a `min-height: unset` rule when `aspect-ratio` is set.
  2294 	 * @since 6.6.0 Pass current theme JSON settings to wp_get_typography_font_size_value(), and process background properties.
  2306 	 * @since 6.6.0 Pass current theme JSON settings to wp_get_typography_font_size_value(), and process background properties.
       
  2307 	 * @since 6.7.0 `ref` resolution of background properties, and assigning custom default values.
  2295 	 *
  2308 	 *
  2296 	 * @param array   $styles Styles to process.
  2309 	 * @param array   $styles Styles to process.
  2297 	 * @param array   $settings Theme settings.
  2310 	 * @param array   $settings Theme settings.
  2298 	 * @param array   $properties Properties metadata.
  2311 	 * @param array   $properties Properties metadata.
  2299 	 * @param array   $theme_json Theme JSON array.
  2312 	 * @param array   $theme_json Theme JSON array.
  2300 	 * @param string  $selector The style block selector.
  2313 	 * @param string  $selector The style block selector.
  2301 	 * @param boolean $use_root_padding Whether to add custom properties at root level.
  2314 	 * @param boolean $use_root_padding Whether to add custom properties at root level.
  2302 	 * @return array  Returns the modified $declarations.
  2315 	 * @return array Returns the modified $declarations.
  2303 	 */
  2316 	 */
  2304 	protected static function compute_style_properties( $styles, $settings = array(), $properties = null, $theme_json = null, $selector = null, $use_root_padding = null ) {
  2317 	protected static function compute_style_properties( $styles, $settings = array(), $properties = null, $theme_json = null, $selector = null, $use_root_padding = null ) {
       
  2318 		if ( empty( $styles ) ) {
       
  2319 			return array();
       
  2320 		}
       
  2321 
  2305 		if ( null === $properties ) {
  2322 		if ( null === $properties ) {
  2306 			$properties = static::PROPERTIES_METADATA;
  2323 			$properties = static::PROPERTIES_METADATA;
  2307 		}
  2324 		}
  2308 
  2325 		$declarations             = array();
  2309 		$declarations = array();
       
  2310 		if ( empty( $styles ) ) {
       
  2311 			return $declarations;
       
  2312 		}
       
  2313 
       
  2314 		$root_variable_duplicates = array();
  2326 		$root_variable_duplicates = array();
       
  2327 		$root_style_length        = strlen( '--wp--style--root--' );
  2315 
  2328 
  2316 		foreach ( $properties as $css_property => $value_path ) {
  2329 		foreach ( $properties as $css_property => $value_path ) {
       
  2330 			if ( ! is_array( $value_path ) ) {
       
  2331 				continue;
       
  2332 			}
       
  2333 
       
  2334 			$is_root_style = str_starts_with( $css_property, '--wp--style--root--' );
       
  2335 			if ( $is_root_style && ( static::ROOT_BLOCK_SELECTOR !== $selector || ! $use_root_padding ) ) {
       
  2336 				continue;
       
  2337 			}
       
  2338 
  2317 			$value = static::get_property_value( $styles, $value_path, $theme_json );
  2339 			$value = static::get_property_value( $styles, $value_path, $theme_json );
  2318 
  2340 
  2319 			if ( str_starts_with( $css_property, '--wp--style--root--' ) && ( static::ROOT_BLOCK_SELECTOR !== $selector || ! $use_root_padding ) ) {
       
  2320 				continue;
       
  2321 			}
       
  2322 			/*
  2341 			/*
  2323 			 * Root-level padding styles don't currently support strings with CSS shorthand values.
  2342 			 * Root-level padding styles don't currently support strings with CSS shorthand values.
  2324 			 * This may change: https://github.com/WordPress/gutenberg/issues/40132.
  2343 			 * This may change: https://github.com/WordPress/gutenberg/issues/40132.
  2325 			 */
  2344 			 */
  2326 			if ( '--wp--style--root--padding' === $css_property && is_string( $value ) ) {
  2345 			if ( '--wp--style--root--padding' === $css_property && is_string( $value ) ) {
  2327 				continue;
  2346 				continue;
  2328 			}
  2347 			}
  2329 
  2348 
  2330 			if ( str_starts_with( $css_property, '--wp--style--root--' ) && $use_root_padding ) {
  2349 			if ( $is_root_style && $use_root_padding ) {
  2331 				$root_variable_duplicates[] = substr( $css_property, strlen( '--wp--style--root--' ) );
  2350 				$root_variable_duplicates[] = substr( $css_property, $root_style_length );
       
  2351 			}
       
  2352 
       
  2353 			/*
       
  2354 			 * Processes background image styles.
       
  2355 			 * If the value is a URL, it will be converted to a CSS `url()` value.
       
  2356 			 * For uploaded image (images with a database ID), apply size and position defaults,
       
  2357 			 * equal to those applied in block supports in lib/background.php.
       
  2358 			 */
       
  2359 			if ( 'background-image' === $css_property && ! empty( $value ) ) {
       
  2360 				$background_styles = wp_style_engine_get_styles(
       
  2361 					array( 'background' => array( 'backgroundImage' => $value ) )
       
  2362 				);
       
  2363 				$value             = $background_styles['declarations'][ $css_property ];
       
  2364 			}
       
  2365 			if ( empty( $value ) && static::ROOT_BLOCK_SELECTOR !== $selector && ! empty( $styles['background']['backgroundImage']['id'] ) ) {
       
  2366 				if ( 'background-size' === $css_property ) {
       
  2367 					$value = 'cover';
       
  2368 				}
       
  2369 				// If the background size is set to `contain` and no position is set, set the position to `center`.
       
  2370 				if ( 'background-position' === $css_property ) {
       
  2371 					$background_size = $styles['background']['backgroundSize'] ?? null;
       
  2372 					$value           = 'contain' === $background_size ? '50% 50%' : null;
       
  2373 				}
       
  2374 			}
       
  2375 
       
  2376 			// Skip if empty and not "0" or value represents array of longhand values.
       
  2377 			$has_missing_value = empty( $value ) && ! is_numeric( $value );
       
  2378 			if ( $has_missing_value || is_array( $value ) ) {
       
  2379 				continue;
  2332 			}
  2380 			}
  2333 
  2381 
  2334 			/*
  2382 			/*
  2335 			 * Look up protected properties, keyed by value path.
  2383 			 * Look up protected properties, keyed by value path.
  2336 			 * Skip protected properties that are explicitly set to `null`.
  2384 			 * Skip protected properties that are explicitly set to `null`.
  2337 			 */
  2385 			 */
  2338 			if ( is_array( $value_path ) ) {
  2386 			$path_string = implode( '.', $value_path );
  2339 				$path_string = implode( '.', $value_path );
  2387 			if (
  2340 				if (
  2388 				isset( static::PROTECTED_PROPERTIES[ $path_string ] ) &&
  2341 					isset( static::PROTECTED_PROPERTIES[ $path_string ] ) &&
  2389 				_wp_array_get( $settings, static::PROTECTED_PROPERTIES[ $path_string ], null ) === null
  2342 					_wp_array_get( $settings, static::PROTECTED_PROPERTIES[ $path_string ], null ) === null
  2390 			) {
  2343 				) {
       
  2344 					continue;
       
  2345 				}
       
  2346 			}
       
  2347 
       
  2348 			// Processes background styles.
       
  2349 			if ( 'background' === $value_path[0] && isset( $styles['background'] ) ) {
       
  2350 				$background_styles = wp_style_engine_get_styles( array( 'background' => $styles['background'] ) );
       
  2351 				$value             = isset( $background_styles['declarations'][ $css_property ] ) ? $background_styles['declarations'][ $css_property ] : $value;
       
  2352 			}
       
  2353 
       
  2354 			// Skip if empty and not "0" or value represents array of longhand values.
       
  2355 			$has_missing_value = empty( $value ) && ! is_numeric( $value );
       
  2356 			if ( $has_missing_value || is_array( $value ) ) {
       
  2357 				continue;
  2391 				continue;
  2358 			}
  2392 			}
  2359 
  2393 
  2360 			// Calculates fluid typography rules where available.
  2394 			// Calculates fluid typography rules where available.
  2361 			if ( 'font-size' === $css_property ) {
  2395 			if ( 'font-size' === $css_property ) {
  2408 	 * @since 6.1.0 Added the `$theme_json` parameter.
  2442 	 * @since 6.1.0 Added the `$theme_json` parameter.
  2409 	 * @since 6.3.0 It no longer converts the internal format "var:preset|color|secondary"
  2443 	 * @since 6.3.0 It no longer converts the internal format "var:preset|color|secondary"
  2410 	 *              to the standard form "--wp--preset--color--secondary".
  2444 	 *              to the standard form "--wp--preset--color--secondary".
  2411 	 *              This is already done by the sanitize method,
  2445 	 *              This is already done by the sanitize method,
  2412 	 *              so every property will be in the standard form.
  2446 	 *              so every property will be in the standard form.
       
  2447 	 * @since 6.7.0 Added support for background image refs.
  2413 	 *
  2448 	 *
  2414 	 * @param array $styles Styles subtree.
  2449 	 * @param array $styles Styles subtree.
  2415 	 * @param array $path   Which property to process.
  2450 	 * @param array $path   Which property to process.
  2416 	 * @param array $theme_json Theme JSON array.
  2451 	 * @param array $theme_json Theme JSON array.
  2417 	 * @return string|array Style property value.
  2452 	 * @return string|array Style property value.
  2424 			return '';
  2459 			return '';
  2425 		}
  2460 		}
  2426 
  2461 
  2427 		/*
  2462 		/*
  2428 		 * This converts references to a path to the value at that path
  2463 		 * This converts references to a path to the value at that path
  2429 		 * where the values is an array with a "ref" key, pointing to a path.
  2464 		 * where the value is an array with a "ref" key, pointing to a path.
  2430 		 * For example: { "ref": "style.color.background" } => "#fff".
  2465 		 * For example: { "ref": "style.color.background" } => "#fff".
       
  2466 		 * In the case of backgroundImage, if both a ref and a URL are present in the value,
       
  2467 		 * the URL takes precedence and the ref is ignored.
  2431 		 */
  2468 		 */
  2432 		if ( is_array( $value ) && isset( $value['ref'] ) ) {
  2469 		if ( is_array( $value ) && isset( $value['ref'] ) ) {
  2433 			$value_path = explode( '.', $value['ref'] );
  2470 			$value_path = explode( '.', $value['ref'] );
  2434 			$ref_value  = _wp_array_get( $theme_json, $value_path );
  2471 			$ref_value  = _wp_array_get( $theme_json, $value_path );
       
  2472 			// Background Image refs can refer to a string or an array containing a URL string.
       
  2473 			$ref_value_url = $ref_value['url'] ?? null;
  2435 			// Only use the ref value if we find anything.
  2474 			// Only use the ref value if we find anything.
  2436 			if ( ! empty( $ref_value ) && is_string( $ref_value ) ) {
  2475 			if ( ! empty( $ref_value ) && ( is_string( $ref_value ) || is_string( $ref_value_url ) ) ) {
  2437 				$value = $ref_value;
  2476 				$value = $ref_value;
  2438 			}
  2477 			}
  2439 
  2478 
  2440 			if ( is_array( $ref_value ) && isset( $ref_value['ref'] ) ) {
  2479 			if ( is_array( $ref_value ) && isset( $ref_value['ref'] ) ) {
  2441 				$path_string      = json_encode( $path );
  2480 				$path_string      = json_encode( $path );
  2654 	 * An internal method to get the block nodes from a theme.json file.
  2693 	 * An internal method to get the block nodes from a theme.json file.
  2655 	 *
  2694 	 *
  2656 	 * @since 6.1.0
  2695 	 * @since 6.1.0
  2657 	 * @since 6.3.0 Refactored and stabilized selectors API.
  2696 	 * @since 6.3.0 Refactored and stabilized selectors API.
  2658 	 * @since 6.6.0 Added optional selectors and options for generating block nodes.
  2697 	 * @since 6.6.0 Added optional selectors and options for generating block nodes.
       
  2698 	 * @since 6.7.0 Added $include_node_paths_only option.
  2659 	 *
  2699 	 *
  2660 	 * @param array $theme_json The theme.json converted to an array.
  2700 	 * @param array $theme_json The theme.json converted to an array.
  2661 	 * @param array $selectors  Optional list of selectors per block.
  2701 	 * @param array $selectors  Optional list of selectors per block.
  2662 	 * @param array $options {
  2702 	 * @param array $options {
  2663 	 *     Optional. An array of options for now used for internal purposes only (may change without notice).
  2703 	 *     Optional. An array of options for now used for internal purposes only (may change without notice).
  2664 	 *
  2704 	 *
  2665 	 *     @type bool   $include_block_style_variations  Includes nodes for block style variations. Default false.
  2705 	 *     @type bool $include_block_style_variations Include nodes for block style variations. Default false.
       
  2706 	 *     @type bool $include_node_paths_only        Return only block nodes node paths. Default false.
  2666 	 * }
  2707 	 * }
  2667 	 * @return array The block nodes in theme.json.
  2708 	 * @return array The block nodes in theme.json.
  2668 	 */
  2709 	 */
  2669 	private static function get_block_nodes( $theme_json, $selectors = array(), $options = array() ) {
  2710 	private static function get_block_nodes( $theme_json, $selectors = array(), $options = array() ) {
  2670 		$selectors = empty( $selectors ) ? static::get_blocks_metadata() : $selectors;
  2711 		$nodes = array();
  2671 		$nodes     = array();
  2712 
  2672 		if ( ! isset( $theme_json['styles'] ) ) {
       
  2673 			return $nodes;
       
  2674 		}
       
  2675 
       
  2676 		// Blocks.
       
  2677 		if ( ! isset( $theme_json['styles']['blocks'] ) ) {
  2713 		if ( ! isset( $theme_json['styles']['blocks'] ) ) {
  2678 			return $nodes;
  2714 			return $nodes;
  2679 		}
  2715 		}
  2680 
  2716 
       
  2717 		$include_variations      = $options['include_block_style_variations'] ?? false;
       
  2718 		$include_node_paths_only = $options['include_node_paths_only'] ?? false;
       
  2719 
       
  2720 		// If only node paths are to be returned, skip selector assignment.
       
  2721 		if ( ! $include_node_paths_only ) {
       
  2722 			$selectors = empty( $selectors ) ? static::get_blocks_metadata() : $selectors;
       
  2723 		}
       
  2724 
  2681 		foreach ( $theme_json['styles']['blocks'] as $name => $node ) {
  2725 		foreach ( $theme_json['styles']['blocks'] as $name => $node ) {
  2682 			$selector = null;
  2726 			$node_path = array( 'styles', 'blocks', $name );
  2683 			if ( isset( $selectors[ $name ]['selector'] ) ) {
  2727 			if ( $include_node_paths_only ) {
  2684 				$selector = $selectors[ $name ]['selector'];
  2728 				$variation_paths = array();
  2685 			}
  2729 				if ( $include_variations && isset( $node['variations'] ) ) {
  2686 
  2730 					foreach ( $node['variations'] as $variation => $variation_node ) {
  2687 			$duotone_selector = null;
  2731 						$variation_paths[] = array(
  2688 			if ( isset( $selectors[ $name ]['duotone'] ) ) {
  2732 							'path' => array( 'styles', 'blocks', $name, 'variations', $variation ),
  2689 				$duotone_selector = $selectors[ $name ]['duotone'];
  2733 						);
  2690 			}
  2734 					}
  2691 
  2735 				}
  2692 			$feature_selectors = null;
  2736 				$node = array(
  2693 			if ( isset( $selectors[ $name ]['selectors'] ) ) {
  2737 					'path' => $node_path,
  2694 				$feature_selectors = $selectors[ $name ]['selectors'];
  2738 				);
  2695 			}
  2739 				if ( ! empty( $variation_paths ) ) {
  2696 
  2740 					$node['variations'] = $variation_paths;
  2697 			$variation_selectors = array();
  2741 				}
  2698 			$include_variations  = $options['include_block_style_variations'] ?? false;
  2742 				$nodes[] = $node;
  2699 			if ( $include_variations && isset( $node['variations'] ) ) {
  2743 			} else {
  2700 				foreach ( $node['variations'] as $variation => $node ) {
  2744 				$selector = null;
  2701 					$variation_selectors[] = array(
  2745 				if ( isset( $selectors[ $name ]['selector'] ) ) {
  2702 						'path'     => array( 'styles', 'blocks', $name, 'variations', $variation ),
  2746 					$selector = $selectors[ $name ]['selector'];
  2703 						'selector' => $selectors[ $name ]['styleVariations'][ $variation ],
  2747 				}
  2704 					);
  2748 
  2705 				}
  2749 				$duotone_selector = null;
  2706 			}
  2750 				if ( isset( $selectors[ $name ]['duotone'] ) ) {
  2707 
  2751 					$duotone_selector = $selectors[ $name ]['duotone'];
  2708 			$nodes[] = array(
  2752 				}
  2709 				'name'       => $name,
  2753 
  2710 				'path'       => array( 'styles', 'blocks', $name ),
  2754 				$feature_selectors = null;
  2711 				'selector'   => $selector,
  2755 				if ( isset( $selectors[ $name ]['selectors'] ) ) {
  2712 				'selectors'  => $feature_selectors,
  2756 					$feature_selectors = $selectors[ $name ]['selectors'];
  2713 				'duotone'    => $duotone_selector,
  2757 				}
  2714 				'features'   => $feature_selectors,
  2758 
  2715 				'variations' => $variation_selectors,
  2759 				$variation_selectors = array();
  2716 			);
  2760 				if ( $include_variations && isset( $node['variations'] ) ) {
       
  2761 					foreach ( $node['variations'] as $variation => $node ) {
       
  2762 						$variation_selectors[] = array(
       
  2763 							'path'     => array( 'styles', 'blocks', $name, 'variations', $variation ),
       
  2764 							'selector' => $selectors[ $name ]['styleVariations'][ $variation ],
       
  2765 						);
       
  2766 					}
       
  2767 				}
       
  2768 
       
  2769 				$nodes[] = array(
       
  2770 					'name'       => $name,
       
  2771 					'path'       => $node_path,
       
  2772 					'selector'   => $selector,
       
  2773 					'selectors'  => $feature_selectors,
       
  2774 					'duotone'    => $duotone_selector,
       
  2775 					'features'   => $feature_selectors,
       
  2776 					'variations' => $variation_selectors,
       
  2777 					'css'        => $selector,
       
  2778 				);
       
  2779 			}
  2717 
  2780 
  2718 			if ( isset( $theme_json['styles']['blocks'][ $name ]['elements'] ) ) {
  2781 			if ( isset( $theme_json['styles']['blocks'][ $name ]['elements'] ) ) {
  2719 				foreach ( $theme_json['styles']['blocks'][ $name ]['elements'] as $element => $node ) {
  2782 				foreach ( $theme_json['styles']['blocks'][ $name ]['elements'] as $element => $node ) {
       
  2783 					$node_path = array( 'styles', 'blocks', $name, 'elements', $element );
       
  2784 					if ( $include_node_paths_only ) {
       
  2785 						$nodes[] = array(
       
  2786 							'path' => $node_path,
       
  2787 						);
       
  2788 						continue;
       
  2789 					}
       
  2790 
  2720 					$nodes[] = array(
  2791 					$nodes[] = array(
  2721 						'path'     => array( 'styles', 'blocks', $name, 'elements', $element ),
  2792 						'path'     => $node_path,
  2722 						'selector' => $selectors[ $name ]['elements'][ $element ],
  2793 						'selector' => $selectors[ $name ]['elements'][ $element ],
  2723 					);
  2794 					);
  2724 
  2795 
  2725 					// Handle any pseudo selectors for the element.
  2796 					// Handle any pseudo selectors for the element.
  2726 					if ( isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element ] ) ) {
  2797 					if ( isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element ] ) ) {
  2727 						foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element ] as $pseudo_selector ) {
  2798 						foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element ] as $pseudo_selector ) {
  2728 							if ( isset( $theme_json['styles']['blocks'][ $name ]['elements'][ $element ][ $pseudo_selector ] ) ) {
  2799 							if ( isset( $theme_json['styles']['blocks'][ $name ]['elements'][ $element ][ $pseudo_selector ] ) ) {
       
  2800 								$node_path = array( 'styles', 'blocks', $name, 'elements', $element );
       
  2801 								if ( $include_node_paths_only ) {
       
  2802 									$nodes[] = array(
       
  2803 										'path' => $node_path,
       
  2804 									);
       
  2805 									continue;
       
  2806 								}
       
  2807 
  2729 								$nodes[] = array(
  2808 								$nodes[] = array(
  2730 									'path'     => array( 'styles', 'blocks', $name, 'elements', $element ),
  2809 									'path'     => $node_path,
  2731 									'selector' => static::append_to_selector( $selectors[ $name ]['elements'][ $element ], $pseudo_selector ),
  2810 									'selector' => static::append_to_selector( $selectors[ $name ]['elements'][ $element ], $pseudo_selector ),
  2732 								);
  2811 								);
  2733 							}
  2812 							}
  2734 						}
  2813 						}
  2735 					}
  2814 					}
  2747 	 * @since 6.6.0 Setting a min-height of HTML when root styles have a background gradient or image.
  2826 	 * @since 6.6.0 Setting a min-height of HTML when root styles have a background gradient or image.
  2748 	 *              Updated general global styles specificity to 0-1-0.
  2827 	 *              Updated general global styles specificity to 0-1-0.
  2749 	 *              Fixed custom CSS output in block style variations.
  2828 	 *              Fixed custom CSS output in block style variations.
  2750 	 *
  2829 	 *
  2751 	 * @param array $block_metadata Metadata about the block to get styles for.
  2830 	 * @param array $block_metadata Metadata about the block to get styles for.
  2752 	 *
       
  2753 	 * @return string Styles for the block.
  2831 	 * @return string Styles for the block.
  2754 	 */
  2832 	 */
  2755 	public function get_styles_for_block( $block_metadata ) {
  2833 	public function get_styles_for_block( $block_metadata ) {
  2756 		$node                 = _wp_array_get( $this->theme_json, $block_metadata['path'], array() );
  2834 		$node                 = _wp_array_get( $this->theme_json, $block_metadata['path'], array() );
  2757 		$use_root_padding     = isset( $this->theme_json['settings']['useRootPaddingAwareAlignments'] ) && true === $this->theme_json['settings']['useRootPaddingAwareAlignments'];
  2835 		$use_root_padding     = isset( $this->theme_json['settings']['useRootPaddingAwareAlignments'] ) && true === $this->theme_json['settings']['useRootPaddingAwareAlignments'];
  2771 				// Generate any feature/subfeature style declarations for the current style variation.
  2849 				// Generate any feature/subfeature style declarations for the current style variation.
  2772 				$variation_declarations = static::get_feature_declarations_for_node( $block_metadata, $style_variation_node );
  2850 				$variation_declarations = static::get_feature_declarations_for_node( $block_metadata, $style_variation_node );
  2773 
  2851 
  2774 				// Combine selectors with style variation's selector and add to overall style variation declarations.
  2852 				// Combine selectors with style variation's selector and add to overall style variation declarations.
  2775 				foreach ( $variation_declarations as $current_selector => $new_declarations ) {
  2853 				foreach ( $variation_declarations as $current_selector => $new_declarations ) {
  2776 					// If current selector includes block classname, remove it but leave the whitespace in.
  2854 					/*
  2777 					$shortened_selector = str_replace( $block_metadata['selector'] . ' ', ' ', $current_selector );
  2855 					 * Clean up any whitespace between comma separated selectors.
       
  2856 					 * This prevents these spaces breaking compound selectors such as:
       
  2857 					 * - `.wp-block-list:not(.wp-block-list .wp-block-list)`
       
  2858 					 * - `.wp-block-image img, .wp-block-image.my-class img`
       
  2859 					 */
       
  2860 					$clean_current_selector = preg_replace( '/,\s+/', ',', $current_selector );
       
  2861 					$shortened_selector     = str_replace( $block_metadata['selector'], '', $clean_current_selector );
  2778 
  2862 
  2779 					// Prepend the variation selector to the current selector.
  2863 					// Prepend the variation selector to the current selector.
  2780 					$split_selectors    = explode( ',', $shortened_selector );
  2864 					$split_selectors    = explode( ',', $shortened_selector );
  2781 					$updated_selectors  = array_map(
  2865 					$updated_selectors  = array_map(
  2782 						static function ( $split_selector ) use ( $clean_style_variation_selector ) {
  2866 						static function ( $split_selector ) use ( $clean_style_variation_selector ) {
  2820 		 */
  2904 		 */
  2821 		$pseudo_matches = array_values(
  2905 		$pseudo_matches = array_values(
  2822 			array_filter(
  2906 			array_filter(
  2823 				$element_pseudo_allowed,
  2907 				$element_pseudo_allowed,
  2824 				static function ( $pseudo_selector ) use ( $selector ) {
  2908 				static function ( $pseudo_selector ) use ( $selector ) {
  2825 					return str_contains( $selector, $pseudo_selector );
  2909 					/*
       
  2910 					 * Check if the pseudo selector is in the current selector,
       
  2911 					 * ensuring it is not followed by a dash (e.g., :focus should not match :focus-visible).
       
  2912 					 */
       
  2913 					return preg_match( '/' . preg_quote( $pseudo_selector, '/' ) . '(?!-)/', $selector ) === 1;
  2826 				}
  2914 				}
  2827 			)
  2915 			)
  2828 		);
  2916 		);
  2829 
  2917 
  2830 		$pseudo_selector = isset( $pseudo_matches[0] ) ? $pseudo_matches[0] : null;
  2918 		$pseudo_selector = isset( $pseudo_matches[0] ) ? $pseudo_matches[0] : null;
  2947 			if ( isset( $style_variation_custom_css[ $style_variation_selector ] ) ) {
  3035 			if ( isset( $style_variation_custom_css[ $style_variation_selector ] ) ) {
  2948 				$block_rules .= $style_variation_custom_css[ $style_variation_selector ];
  3036 				$block_rules .= $style_variation_custom_css[ $style_variation_selector ];
  2949 			}
  3037 			}
  2950 		}
  3038 		}
  2951 
  3039 
  2952 		// 7. Generate and append any custom CSS rules pertaining to nested block style variations.
  3040 		// 7. Generate and append any custom CSS rules.
  2953 		if ( isset( $node['css'] ) && ! $is_root_selector ) {
  3041 		if ( isset( $node['css'] ) && ! $is_root_selector ) {
  2954 			$block_rules .= $this->process_blocks_custom_css( $node['css'], $selector );
  3042 			$block_rules .= $this->process_blocks_custom_css( $node['css'], $selector );
  2955 		}
  3043 		}
  2956 
  3044 
  2957 		return $block_rules;
  3045 		return $block_rules;
  2972 		$css              = '';
  3060 		$css              = '';
  2973 		$settings         = isset( $this->theme_json['settings'] ) ? $this->theme_json['settings'] : array();
  3061 		$settings         = isset( $this->theme_json['settings'] ) ? $this->theme_json['settings'] : array();
  2974 		$use_root_padding = isset( $this->theme_json['settings']['useRootPaddingAwareAlignments'] ) && true === $this->theme_json['settings']['useRootPaddingAwareAlignments'];
  3062 		$use_root_padding = isset( $this->theme_json['settings']['useRootPaddingAwareAlignments'] ) && true === $this->theme_json['settings']['useRootPaddingAwareAlignments'];
  2975 
  3063 
  2976 		/*
  3064 		/*
  2977 		* If there are content and wide widths in theme.json, output them
  3065 		 * If there are content and wide widths in theme.json, output them
  2978 		* as custom properties on the body element so all blocks can use them.
  3066 		 * as custom properties on the body element so all blocks can use them.
  2979 		*/
  3067 		 */
  2980 		if ( isset( $settings['layout']['contentSize'] ) || isset( $settings['layout']['wideSize'] ) ) {
  3068 		if ( isset( $settings['layout']['contentSize'] ) || isset( $settings['layout']['wideSize'] ) ) {
  2981 			$content_size = isset( $settings['layout']['contentSize'] ) ? $settings['layout']['contentSize'] : $settings['layout']['wideSize'];
  3069 			$content_size = isset( $settings['layout']['contentSize'] ) ? $settings['layout']['contentSize'] : $settings['layout']['wideSize'];
  2982 			$content_size = static::is_safe_css_declaration( 'max-width', $content_size ) ? $content_size : 'initial';
  3070 			$content_size = static::is_safe_css_declaration( 'max-width', $content_size ) ? $content_size : 'initial';
  2983 			$wide_size    = isset( $settings['layout']['wideSize'] ) ? $settings['layout']['wideSize'] : $settings['layout']['contentSize'];
  3071 			$wide_size    = isset( $settings['layout']['wideSize'] ) ? $settings['layout']['wideSize'] : $settings['layout']['contentSize'];
  2984 			$wide_size    = static::is_safe_css_declaration( 'max-width', $wide_size ) ? $wide_size : 'initial';
  3072 			$wide_size    = static::is_safe_css_declaration( 'max-width', $wide_size ) ? $wide_size : 'initial';
  2985 			$css         .= static::ROOT_CSS_PROPERTIES_SELECTOR . ' { --wp--style--global--content-size: ' . $content_size . ';';
  3073 			$css         .= static::ROOT_CSS_PROPERTIES_SELECTOR . ' { --wp--style--global--content-size: ' . $content_size . ';';
  2986 			$css         .= '--wp--style--global--wide-size: ' . $wide_size . '; }';
  3074 			$css         .= '--wp--style--global--wide-size: ' . $wide_size . '; }';
  2987 		}
  3075 		}
  2988 
  3076 
  2989 		/*
  3077 		/*
  2990 		* Reset default browser margin on the body element.
  3078 		 * Reset default browser margin on the body element.
  2991 		* This is set on the body selector **before** generating the ruleset
  3079 		 * This is set on the body selector **before** generating the ruleset
  2992 		* from the `theme.json`. This is to ensure that if the `theme.json` declares
  3080 		 * from the `theme.json`. This is to ensure that if the `theme.json` declares
  2993 		* `margin` in its `spacing` declaration for the `body` element then these
  3081 		 * `margin` in its `spacing` declaration for the `body` element then these
  2994 		* user-generated values take precedence in the CSS cascade.
  3082 		 * user-generated values take precedence in the CSS cascade.
  2995 		* @link https://github.com/WordPress/gutenberg/issues/36147.
  3083 		 * @link https://github.com/WordPress/gutenberg/issues/36147.
  2996 		*/
  3084 		 */
  2997 		$css .= ':where(body) { margin: 0; }';
  3085 		$css .= ':where(body) { margin: 0; }';
  2998 
  3086 
  2999 		if ( $use_root_padding ) {
  3087 		if ( $use_root_padding ) {
  3000 			// Top and bottom padding are applied to the outer block container.
  3088 			// Top and bottom padding are applied to the outer block container.
  3001 			$css .= '.wp-site-blocks { padding-top: var(--wp--style--root--padding-top); padding-bottom: var(--wp--style--root--padding-bottom); }';
  3089 			$css .= '.wp-site-blocks { padding-top: var(--wp--style--root--padding-top); padding-bottom: var(--wp--style--root--padding-bottom); }';
  3069 	/**
  3157 	/**
  3070 	 * Merges new incoming data.
  3158 	 * Merges new incoming data.
  3071 	 *
  3159 	 *
  3072 	 * @since 5.8.0
  3160 	 * @since 5.8.0
  3073 	 * @since 5.9.0 Duotone preset also has origins.
  3161 	 * @since 5.9.0 Duotone preset also has origins.
       
  3162 	 * @since 6.7.0 Replace background image objects during merge.
  3074 	 *
  3163 	 *
  3075 	 * @param WP_Theme_JSON $incoming Data to merge.
  3164 	 * @param WP_Theme_JSON $incoming Data to merge.
  3076 	 */
  3165 	 */
  3077 	public function merge( $incoming ) {
  3166 	public function merge( $incoming ) {
  3078 		$incoming_data    = $incoming->get_raw_data();
  3167 		$incoming_data    = $incoming->get_raw_data();
  3192 
  3281 
  3193 					_wp_array_set( $this->theme_json, $path, $content );
  3282 					_wp_array_set( $this->theme_json, $path, $content );
  3194 				}
  3283 				}
  3195 			}
  3284 			}
  3196 		}
  3285 		}
       
  3286 
       
  3287 		/*
       
  3288 		 * Style values are merged at the leaf level, however
       
  3289 		 * some values provide exceptions, namely style values that are
       
  3290 		 * objects and represent unique definitions for the style.
       
  3291 		 */
       
  3292 		$style_nodes = static::get_block_nodes(
       
  3293 			$this->theme_json,
       
  3294 			array(),
       
  3295 			array( 'include_node_paths_only' => true )
       
  3296 		);
       
  3297 
       
  3298 		// Add top-level styles.
       
  3299 		$style_nodes[] = array( 'path' => array( 'styles' ) );
       
  3300 
       
  3301 		foreach ( $style_nodes as $style_node ) {
       
  3302 			$path = $style_node['path'];
       
  3303 			/*
       
  3304 			 * Background image styles should be replaced, not merged,
       
  3305 			 * as they themselves are specific object definitions for the style.
       
  3306 			 */
       
  3307 			$background_image_path = array_merge( $path, static::PROPERTIES_METADATA['background-image'] );
       
  3308 			$content               = _wp_array_get( $incoming_data, $background_image_path, null );
       
  3309 			if ( isset( $content ) ) {
       
  3310 				_wp_array_set( $this->theme_json, $background_image_path, $content );
       
  3311 			}
       
  3312 		}
  3197 	}
  3313 	}
  3198 
  3314 
  3199 	/**
  3315 	/**
  3200 	 * Converts all filter (duotone) presets into SVGs.
  3316 	 * Converts all filter (duotone) presets into SVGs.
  3201 	 *
  3317 	 *
  3220 			foreach ( $origins as $origin ) {
  3336 			foreach ( $origins as $origin ) {
  3221 				if ( ! isset( $duotone_presets[ $origin ] ) ) {
  3337 				if ( ! isset( $duotone_presets[ $origin ] ) ) {
  3222 					continue;
  3338 					continue;
  3223 				}
  3339 				}
  3224 				foreach ( $duotone_presets[ $origin ] as $duotone_preset ) {
  3340 				foreach ( $duotone_presets[ $origin ] as $duotone_preset ) {
  3225 					$filters .= wp_get_duotone_filter_svg( $duotone_preset );
  3341 					$filters .= WP_Duotone::get_filter_svg_from_preset( $duotone_preset );
  3226 				}
  3342 				}
  3227 			}
  3343 			}
  3228 		}
  3344 		}
  3229 
  3345 
  3230 		return $filters;
  3346 		return $filters;
  3377 	 * @since 5.9.0
  3493 	 * @since 5.9.0
  3378 	 * @since 6.3.2 Preserves global styles block variations when securing styles.
  3494 	 * @since 6.3.2 Preserves global styles block variations when securing styles.
  3379 	 * @since 6.6.0 Updated to allow variation element styles and $origin parameter.
  3495 	 * @since 6.6.0 Updated to allow variation element styles and $origin parameter.
  3380 	 *
  3496 	 *
  3381 	 * @param array  $theme_json Structure to sanitize.
  3497 	 * @param array  $theme_json Structure to sanitize.
  3382 	 * @param string $origin    Optional. What source of data this object represents.
  3498 	 * @param string $origin     Optional. What source of data this object represents.
  3383 	 *                          One of 'blocks', 'default', 'theme', or 'custom'. Default 'theme'.
  3499 	 *                           One of 'blocks', 'default', 'theme', or 'custom'. Default 'theme'.
  3384 	 * @return array Sanitized structure.
  3500 	 * @return array Sanitized structure.
  3385 	 */
  3501 	 */
  3386 	public static function remove_insecure_properties( $theme_json, $origin = 'theme' ) {
  3502 	public static function remove_insecure_properties( $theme_json, $origin = 'theme' ) {
  3387 		if ( ! in_array( $origin, static::VALID_ORIGINS, true ) ) {
  3503 		if ( ! in_array( $origin, static::VALID_ORIGINS, true ) ) {
  3388 			$origin = 'theme';
  3504 			$origin = 'theme';
  3390 
  3506 
  3391 		$sanitized = array();
  3507 		$sanitized = array();
  3392 
  3508 
  3393 		$theme_json = WP_Theme_JSON_Schema::migrate( $theme_json, $origin );
  3509 		$theme_json = WP_Theme_JSON_Schema::migrate( $theme_json, $origin );
  3394 
  3510 
  3395 		$valid_block_names   = array_keys( static::get_blocks_metadata() );
  3511 		$blocks_metadata     = static::get_blocks_metadata();
       
  3512 		$valid_block_names   = array_keys( $blocks_metadata );
  3396 		$valid_element_names = array_keys( static::ELEMENTS );
  3513 		$valid_element_names = array_keys( static::ELEMENTS );
  3397 		$valid_variations    = static::get_valid_block_style_variations();
  3514 		$valid_variations    = static::get_valid_block_style_variations( $blocks_metadata );
  3398 
  3515 
  3399 		$theme_json = static::sanitize( $theme_json, $valid_block_names, $valid_element_names, $valid_variations );
  3516 		$theme_json = static::sanitize( $theme_json, $valid_block_names, $valid_element_names, $valid_variations );
  3400 
  3517 
  3401 		$blocks_metadata = static::get_blocks_metadata();
  3518 		$blocks_metadata = static::get_blocks_metadata();
  3402 		$style_options   = array( 'include_block_style_variations' => true ); // Allow variations data.
  3519 		$style_options   = array( 'include_block_style_variations' => true ); // Allow variations data.
  3444 						continue;
  3561 						continue;
  3445 					}
  3562 					}
  3446 
  3563 
  3447 					$variation_output = static::remove_insecure_styles( $variation_input );
  3564 					$variation_output = static::remove_insecure_styles( $variation_input );
  3448 
  3565 
  3449 					// Process a variation's elements and element pseudo selector styles.
  3566 					if ( isset( $variation_input['blocks'] ) ) {
       
  3567 						$variation_output['blocks'] = static::remove_insecure_inner_block_styles( $variation_input['blocks'] );
       
  3568 					}
       
  3569 
  3450 					if ( isset( $variation_input['elements'] ) ) {
  3570 					if ( isset( $variation_input['elements'] ) ) {
  3451 						foreach ( $valid_element_names as $element_name ) {
  3571 						$variation_output['elements'] = static::remove_insecure_element_styles( $variation_input['elements'] );
  3452 							$element_input = $variation_input['elements'][ $element_name ] ?? null;
       
  3453 							if ( $element_input ) {
       
  3454 								$element_output = static::remove_insecure_styles( $element_input );
       
  3455 
       
  3456 								if ( isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element_name ] ) ) {
       
  3457 									foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element_name ] as $pseudo_selector ) {
       
  3458 										if ( isset( $element_input[ $pseudo_selector ] ) ) {
       
  3459 											$element_output[ $pseudo_selector ] = static::remove_insecure_styles( $element_input[ $pseudo_selector ] );
       
  3460 										}
       
  3461 									}
       
  3462 								}
       
  3463 
       
  3464 								if ( ! empty( $element_output ) ) {
       
  3465 									_wp_array_set( $variation_output, array( 'elements', $element_name ), $element_output );
       
  3466 								}
       
  3467 							}
       
  3468 						}
       
  3469 					}
  3572 					}
  3470 
  3573 
  3471 					if ( ! empty( $variation_output ) ) {
  3574 					if ( ! empty( $variation_output ) ) {
  3472 						_wp_array_set( $sanitized, $variation['path'], $variation_output );
  3575 						_wp_array_set( $sanitized, $variation['path'], $variation_output );
  3473 					}
  3576 					}
  3499 		} else {
  3602 		} else {
  3500 			$theme_json['settings'] = $sanitized['settings'];
  3603 			$theme_json['settings'] = $sanitized['settings'];
  3501 		}
  3604 		}
  3502 
  3605 
  3503 		return $theme_json;
  3606 		return $theme_json;
       
  3607 	}
       
  3608 
       
  3609 	/**
       
  3610 	 * Remove insecure element styles within a variation or block.
       
  3611 	 *
       
  3612 	 * @since 6.8.0
       
  3613 	 *
       
  3614 	 * @param array $elements The elements to process.
       
  3615 	 * @return array The sanitized elements styles.
       
  3616 	 */
       
  3617 	protected static function remove_insecure_element_styles( $elements ) {
       
  3618 		$sanitized           = array();
       
  3619 		$valid_element_names = array_keys( static::ELEMENTS );
       
  3620 
       
  3621 		foreach ( $valid_element_names as $element_name ) {
       
  3622 			$element_input = $elements[ $element_name ] ?? null;
       
  3623 			if ( $element_input ) {
       
  3624 				$element_output = static::remove_insecure_styles( $element_input );
       
  3625 
       
  3626 				if ( isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element_name ] ) ) {
       
  3627 					foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element_name ] as $pseudo_selector ) {
       
  3628 						if ( isset( $element_input[ $pseudo_selector ] ) ) {
       
  3629 							$element_output[ $pseudo_selector ] = static::remove_insecure_styles( $element_input[ $pseudo_selector ] );
       
  3630 						}
       
  3631 					}
       
  3632 				}
       
  3633 
       
  3634 				$sanitized[ $element_name ] = $element_output;
       
  3635 			}
       
  3636 		}
       
  3637 		return $sanitized;
       
  3638 	}
       
  3639 
       
  3640 	/**
       
  3641 	 * Remove insecure styles from inner blocks and their elements.
       
  3642 	 *
       
  3643 	 * @since 6.8.0
       
  3644 	 *
       
  3645 	 * @param array $blocks The block styles to process.
       
  3646 	 * @return array Sanitized block type styles.
       
  3647 	 */
       
  3648 	protected static function remove_insecure_inner_block_styles( $blocks ) {
       
  3649 		$sanitized = array();
       
  3650 		foreach ( $blocks as $block_type => $block_input ) {
       
  3651 			$block_output = static::remove_insecure_styles( $block_input );
       
  3652 
       
  3653 			if ( isset( $block_input['elements'] ) ) {
       
  3654 				$block_output['elements'] = static::remove_insecure_element_styles( $block_input['elements'] );
       
  3655 			}
       
  3656 
       
  3657 			$sanitized[ $block_type ] = $block_output;
       
  3658 		}
       
  3659 		return $sanitized;
  3504 	}
  3660 	}
  3505 
  3661 
  3506 	/**
  3662 	/**
  3507 	 * Processes a setting node and returns the same node
  3663 	 * Processes a setting node and returns the same node
  3508 	 * without the insecure settings.
  3664 	 * without the insecure settings.
  4110 	/**
  4266 	/**
  4111 	 * This is used to convert the internal representation of variables to the CSS representation.
  4267 	 * This is used to convert the internal representation of variables to the CSS representation.
  4112 	 * For example, `var:preset|color|vivid-green-cyan` becomes `var(--wp--preset--color--vivid-green-cyan)`.
  4268 	 * For example, `var:preset|color|vivid-green-cyan` becomes `var(--wp--preset--color--vivid-green-cyan)`.
  4113 	 *
  4269 	 *
  4114 	 * @since 6.3.0
  4270 	 * @since 6.3.0
       
  4271 	 *
  4115 	 * @param string $value The variable such as var:preset|color|vivid-green-cyan to convert.
  4272 	 * @param string $value The variable such as var:preset|color|vivid-green-cyan to convert.
  4116 	 * @return string The converted variable.
  4273 	 * @return string The converted variable.
  4117 	 */
  4274 	 */
  4118 	private static function convert_custom_properties( $value ) {
  4275 	private static function convert_custom_properties( $value ) {
  4119 		$prefix     = 'var:';
  4276 		$prefix     = 'var:';
  4135 	/**
  4292 	/**
  4136 	 * Given a tree, converts the internal representation of variables to the CSS representation.
  4293 	 * Given a tree, converts the internal representation of variables to the CSS representation.
  4137 	 * It is recursive and modifies the input in-place.
  4294 	 * It is recursive and modifies the input in-place.
  4138 	 *
  4295 	 *
  4139 	 * @since 6.3.0
  4296 	 * @since 6.3.0
  4140 	 * @param array $tree   Input to process.
  4297 	 *
       
  4298 	 * @param array $tree Input to process.
  4141 	 * @return array The modified $tree.
  4299 	 * @return array The modified $tree.
  4142 	 */
  4300 	 */
  4143 	private static function resolve_custom_css_format( $tree ) {
  4301 	private static function resolve_custom_css_format( $tree ) {
  4144 		$prefix = 'var:';
  4302 		$prefix = 'var:';
  4145 
  4303 
  4159 	 *
  4317 	 *
  4160 	 * @since 6.3.0
  4318 	 * @since 6.3.0
  4161 	 *
  4319 	 *
  4162 	 * @param object $block_type    The block type.
  4320 	 * @param object $block_type    The block type.
  4163 	 * @param string $root_selector The block's root selector.
  4321 	 * @param string $root_selector The block's root selector.
  4164 	 *
       
  4165 	 * @return array The custom selectors set by the block.
  4322 	 * @return array The custom selectors set by the block.
  4166 	 */
  4323 	 */
  4167 	protected static function get_block_selectors( $block_type, $root_selector ) {
  4324 	protected static function get_block_selectors( $block_type, $root_selector ) {
  4168 		if ( ! empty( $block_type->selectors ) ) {
  4325 		if ( ! empty( $block_type->selectors ) ) {
  4169 			return $block_type->selectors;
  4326 			return $block_type->selectors;
  4218 	 *
  4375 	 *
  4219 	 * @since 6.3.0
  4376 	 * @since 6.3.0
  4220 	 *
  4377 	 *
  4221 	 * @param object $metadata The related block metadata containing selectors.
  4378 	 * @param object $metadata The related block metadata containing selectors.
  4222 	 * @param object $node     A merged theme.json node for block or variation.
  4379 	 * @param object $node     A merged theme.json node for block or variation.
  4223 	 *
       
  4224 	 * @return array The style declarations for the node's features with custom
  4380 	 * @return array The style declarations for the node's features with custom
  4225 	 * selectors.
  4381 	 *               selectors.
  4226 	 */
  4382 	 */
  4227 	protected function get_feature_declarations_for_node( $metadata, &$node ) {
  4383 	protected function get_feature_declarations_for_node( $metadata, &$node ) {
  4228 		$declarations = array();
  4384 		$declarations = array();
  4229 
  4385 
  4230 		if ( ! isset( $metadata['selectors'] ) ) {
  4386 		if ( ! isset( $metadata['selectors'] ) ) {
  4378 
  4534 
  4379 	/**
  4535 	/**
  4380 	 * Resolves the values of CSS variables in the given styles.
  4536 	 * Resolves the values of CSS variables in the given styles.
  4381 	 *
  4537 	 *
  4382 	 * @since 6.3.0
  4538 	 * @since 6.3.0
       
  4539 	 *
  4383 	 * @param WP_Theme_JSON $theme_json The theme json resolver.
  4540 	 * @param WP_Theme_JSON $theme_json The theme json resolver.
  4384 	 *
       
  4385 	 * @return WP_Theme_JSON The $theme_json with resolved variables.
  4541 	 * @return WP_Theme_JSON The $theme_json with resolved variables.
  4386 	 */
  4542 	 */
  4387 	public static function resolve_variables( $theme_json ) {
  4543 	public static function resolve_variables( $theme_json ) {
  4388 		$settings    = $theme_json->get_settings();
  4544 		$settings    = $theme_json->get_settings();
  4389 		$styles      = $theme_json->get_raw_data()['styles'];
  4545 		$styles      = $theme_json->get_raw_data()['styles'];
  4439 
  4595 
  4440 	/**
  4596 	/**
  4441 	 * Collects valid block style variations keyed by block type.
  4597 	 * Collects valid block style variations keyed by block type.
  4442 	 *
  4598 	 *
  4443 	 * @since 6.6.0
  4599 	 * @since 6.6.0
  4444 	 *
  4600 	 * @since 6.8.0 Added the `$blocks_metadata` parameter.
       
  4601 	 *
       
  4602 	 * @param array $blocks_metadata Optional. List of metadata per block. Default is the metadata for all blocks.
  4445 	 * @return array Valid block style variations by block type.
  4603 	 * @return array Valid block style variations by block type.
  4446 	 */
  4604 	 */
  4447 	protected static function get_valid_block_style_variations() {
  4605 	protected static function get_valid_block_style_variations( $blocks_metadata = array() ) {
  4448 		$valid_variations = array();
  4606 		$valid_variations = array();
  4449 		foreach ( self::get_blocks_metadata() as $block_name => $block_meta ) {
  4607 		$blocks_metadata  = empty( $blocks_metadata ) ? static::get_blocks_metadata() : $blocks_metadata;
       
  4608 		foreach ( $blocks_metadata as $block_name => $block_meta ) {
  4450 			if ( ! isset( $block_meta['styleVariations'] ) ) {
  4609 			if ( ! isset( $block_meta['styleVariations'] ) ) {
  4451 				continue;
  4610 				continue;
  4452 			}
  4611 			}
  4453 			$valid_variations[ $block_name ] = array_keys( $block_meta['styleVariations'] );
  4612 			$valid_variations[ $block_name ] = array_keys( $block_meta['styleVariations'] );
  4454 		}
  4613 		}