[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/style-engine/ -> class-wp-style-engine.php (source)

   1  <?php
   2  /**
   3   * Style Engine: WP_Style_Engine class
   4   *
   5   * @package WordPress
   6   * @subpackage StyleEngine
   7   * @since 6.1.0
   8   */
   9  
  10  /**
  11   * The main class integrating all other WP_Style_Engine_* classes.
  12   *
  13   * The Style Engine aims to provide a consistent API for rendering styling for blocks
  14   * across both client-side and server-side applications.
  15   *
  16   * This class is final and should not be extended.
  17   *
  18   * This class is for internal Core usage and is not supposed to be used by extenders
  19   * (plugins and/or themes). This is a low-level API that may need to do breaking changes.
  20   * Please, use wp_style_engine_get_styles() instead.
  21   *
  22   * @access private
  23   * @since 6.1.0
  24   * @since 6.3.0 Added support for text-columns.
  25   * @since 6.4.0 Added support for background.backgroundImage.
  26   * @since 6.5.0 Added support for background.backgroundPosition,
  27   *              background.backgroundRepeat and dimensions.aspectRatio.
  28   * @since 6.7.0 Added support for typography.writingMode.
  29   * @since 7.0.0 Added support for typography.textIndent.
  30   * @since 7.2.0 Added support for typography.textShadow.
  31   */
  32  #[AllowDynamicProperties]
  33  final class WP_Style_Engine {
  34      /**
  35       * The values `background-clip` accepts.
  36       *
  37       * @since 7.2.0
  38       * @var string[]
  39       */
  40      const VALID_BACKGROUND_CLIP_VALUES = array( 'border-box', 'padding-box', 'content-box', 'text' );
  41  
  42      /**
  43       * Style definitions that contain the instructions to parse/output valid Gutenberg styles from a block's attributes.
  44       *
  45       * For every style definition, the following properties are valid:
  46       *
  47       *  - classnames    => (array) an array of classnames to be returned for block styles. The key is a classname or pattern.
  48       *                    A value of `true` means the classname should be applied always. Otherwise, a valid CSS property (string)
  49       *                    to match the incoming value, e.g., "color" to match var:preset|color|somePresetSlug.
  50       *  - css_vars      => (array) an array of key value pairs used to generate CSS var values.
  51       *                     The key should be the CSS property name that matches the second element of the preset string value,
  52       *                     i.e., "color" in var:preset|color|somePresetSlug. The value is a CSS var pattern (e.g. `--wp--preset--color--$slug`),
  53       *                     whose `$slug` fragment will be replaced with the preset slug, which is the third element of the preset string value,
  54       *                     i.e., `somePresetSlug` in var:preset|color|somePresetSlug.
  55       *  - property_keys => (array) array of keys whose values represent a valid CSS property, e.g., "margin" or "border".
  56       *  - path          => (array) a path that accesses the corresponding style value in the block style object.
  57       *  - value_func    => (string) the name of a function to generate a CSS definition array for a particular style object. The output of this function should be `array( "$property" => "$value", ... )`.
  58       *
  59       * @since 6.1.0
  60       * @since 7.1.0 Added `background.gradient` property.
  61       * @since 7.2.0 Added `background.backgroundClip` property.
  62       * @var array
  63       */
  64      const BLOCK_STYLE_DEFINITIONS_METADATA = array(
  65          'background' => array(
  66              'backgroundImage'      => array(
  67                  'property_keys' => array(
  68                      'default' => 'background-image',
  69                  ),
  70                  'value_func'    => array( self::class, 'get_url_or_value_css_declaration' ),
  71                  'path'          => array( 'background', 'backgroundImage' ),
  72              ),
  73              'backgroundPosition'   => array(
  74                  'property_keys' => array(
  75                      'default' => 'background-position',
  76                  ),
  77                  'path'          => array( 'background', 'backgroundPosition' ),
  78              ),
  79              'backgroundRepeat'     => array(
  80                  'property_keys' => array(
  81                      'default' => 'background-repeat',
  82                  ),
  83                  'path'          => array( 'background', 'backgroundRepeat' ),
  84              ),
  85              'backgroundSize'       => array(
  86                  'property_keys' => array(
  87                      'default' => 'background-size',
  88                  ),
  89                  'path'          => array( 'background', 'backgroundSize' ),
  90              ),
  91              'backgroundAttachment' => array(
  92                  'property_keys' => array(
  93                      'default' => 'background-attachment',
  94                  ),
  95                  'path'          => array( 'background', 'backgroundAttachment' ),
  96              ),
  97              'gradient'             => array(
  98                  'property_keys' => array(
  99                      'default' => 'background-image',
 100                  ),
 101                  'css_vars'      => array(
 102                      'gradient' => '--wp--preset--gradient--$slug',
 103                  ),
 104                  'path'          => array( 'background', 'gradient' ),
 105                  'classnames'    => array(
 106                      'has-background' => true,
 107                  ),
 108              ),
 109              'backgroundClip'       => array(
 110                  'value_func' => array( self::class, 'get_background_clip_css_declarations' ),
 111                  'path'       => array( 'background', 'backgroundClip' ),
 112              ),
 113          ),
 114          'color'      => array(
 115              'text'       => array(
 116                  'property_keys' => array(
 117                      'default' => 'color',
 118                  ),
 119                  'path'          => array( 'color', 'text' ),
 120                  'css_vars'      => array(
 121                      'color' => '--wp--preset--color--$slug',
 122                  ),
 123                  'classnames'    => array(
 124                      'has-text-color'  => true,
 125                      'has-$slug-color' => 'color',
 126                  ),
 127              ),
 128              'background' => array(
 129                  'property_keys' => array(
 130                      'default' => 'background-color',
 131                  ),
 132                  'path'          => array( 'color', 'background' ),
 133                  'css_vars'      => array(
 134                      'color' => '--wp--preset--color--$slug',
 135                  ),
 136                  'classnames'    => array(
 137                      'has-background'             => true,
 138                      'has-$slug-background-color' => 'color',
 139                  ),
 140              ),
 141              'gradient'   => array(
 142                  'property_keys' => array(
 143                      'default' => 'background',
 144                  ),
 145                  'path'          => array( 'color', 'gradient' ),
 146                  'css_vars'      => array(
 147                      'gradient' => '--wp--preset--gradient--$slug',
 148                  ),
 149                  'classnames'    => array(
 150                      'has-background'                => true,
 151                      'has-$slug-gradient-background' => 'gradient',
 152                  ),
 153              ),
 154          ),
 155          'border'     => array(
 156              'color'  => array(
 157                  'property_keys' => array(
 158                      'default'    => 'border-color',
 159                      'individual' => 'border-%s-color',
 160                  ),
 161                  'path'          => array( 'border', 'color' ),
 162                  'classnames'    => array(
 163                      'has-border-color'       => true,
 164                      'has-$slug-border-color' => 'color',
 165                  ),
 166              ),
 167              'radius' => array(
 168                  'property_keys' => array(
 169                      'default'    => 'border-radius',
 170                      'individual' => 'border-%s-radius',
 171                  ),
 172                  'path'          => array( 'border', 'radius' ),
 173                  'css_vars'      => array(
 174                      'border-radius' => '--wp--preset--border-radius--$slug',
 175                  ),
 176              ),
 177              'style'  => array(
 178                  'property_keys' => array(
 179                      'default'    => 'border-style',
 180                      'individual' => 'border-%s-style',
 181                  ),
 182                  'path'          => array( 'border', 'style' ),
 183              ),
 184              'width'  => array(
 185                  'property_keys' => array(
 186                      'default'    => 'border-width',
 187                      'individual' => 'border-%s-width',
 188                  ),
 189                  'path'          => array( 'border', 'width' ),
 190              ),
 191              'top'    => array(
 192                  'value_func' => array( self::class, 'get_individual_property_css_declarations' ),
 193                  'path'       => array( 'border', 'top' ),
 194                  'css_vars'   => array(
 195                      'color' => '--wp--preset--color--$slug',
 196                  ),
 197              ),
 198              'right'  => array(
 199                  'value_func' => array( self::class, 'get_individual_property_css_declarations' ),
 200                  'path'       => array( 'border', 'right' ),
 201                  'css_vars'   => array(
 202                      'color' => '--wp--preset--color--$slug',
 203                  ),
 204              ),
 205              'bottom' => array(
 206                  'value_func' => array( self::class, 'get_individual_property_css_declarations' ),
 207                  'path'       => array( 'border', 'bottom' ),
 208                  'css_vars'   => array(
 209                      'color' => '--wp--preset--color--$slug',
 210                  ),
 211              ),
 212              'left'   => array(
 213                  'value_func' => array( self::class, 'get_individual_property_css_declarations' ),
 214                  'path'       => array( 'border', 'left' ),
 215                  'css_vars'   => array(
 216                      'color' => '--wp--preset--color--$slug',
 217                  ),
 218              ),
 219          ),
 220          'shadow'     => array(
 221              'shadow' => array(
 222                  'property_keys' => array(
 223                      'default' => 'box-shadow',
 224                  ),
 225                  'path'          => array( 'shadow' ),
 226                  'css_vars'      => array(
 227                      'shadow' => '--wp--preset--shadow--$slug',
 228                  ),
 229              ),
 230          ),
 231          'dimensions' => array(
 232              'aspectRatio' => array(
 233                  'property_keys' => array(
 234                      'default' => 'aspect-ratio',
 235                  ),
 236                  'path'          => array( 'dimensions', 'aspectRatio' ),
 237                  'classnames'    => array(
 238                      'has-aspect-ratio' => true,
 239                  ),
 240              ),
 241              'height'      => array(
 242                  'property_keys' => array(
 243                      'default' => 'height',
 244                  ),
 245                  'path'          => array( 'dimensions', 'height' ),
 246                  'css_vars'      => array(
 247                      'dimension' => '--wp--preset--dimension--$slug',
 248                  ),
 249              ),
 250              'minHeight'   => array(
 251                  'property_keys' => array(
 252                      'default' => 'min-height',
 253                  ),
 254                  'path'          => array( 'dimensions', 'minHeight' ),
 255                  'css_vars'      => array(
 256                      'dimension' => '--wp--preset--dimension--$slug',
 257                  ),
 258              ),
 259              'minWidth'    => array(
 260                  'property_keys' => array(
 261                      'default' => 'min-width',
 262                  ),
 263                  'path'          => array( 'dimensions', 'minWidth' ),
 264                  'css_vars'      => array(
 265                      'dimension' => '--wp--preset--dimension--$slug',
 266                  ),
 267              ),
 268              'objectFit'   => array(
 269                  'property_keys' => array(
 270                      'default' => 'object-fit',
 271                  ),
 272                  'path'          => array( 'dimensions', 'objectFit' ),
 273              ),
 274              'width'       => array(
 275                  'property_keys' => array(
 276                      'default' => 'width',
 277                  ),
 278                  'path'          => array( 'dimensions', 'width' ),
 279                  'css_vars'      => array(
 280                      'dimension' => '--wp--preset--dimension--$slug',
 281                  ),
 282              ),
 283          ),
 284          'spacing'    => array(
 285              'padding' => array(
 286                  'property_keys' => array(
 287                      'default'    => 'padding',
 288                      'individual' => 'padding-%s',
 289                  ),
 290                  'path'          => array( 'spacing', 'padding' ),
 291                  'css_vars'      => array(
 292                      'spacing' => '--wp--preset--spacing--$slug',
 293                  ),
 294              ),
 295              'margin'  => array(
 296                  'property_keys' => array(
 297                      'default'    => 'margin',
 298                      'individual' => 'margin-%s',
 299                  ),
 300                  'path'          => array( 'spacing', 'margin' ),
 301                  'css_vars'      => array(
 302                      'spacing' => '--wp--preset--spacing--$slug',
 303                  ),
 304              ),
 305          ),
 306          'typography' => array(
 307              'fontSize'       => array(
 308                  'property_keys' => array(
 309                      'default' => 'font-size',
 310                  ),
 311                  'path'          => array( 'typography', 'fontSize' ),
 312                  'css_vars'      => array(
 313                      'font-size' => '--wp--preset--font-size--$slug',
 314                  ),
 315                  'classnames'    => array(
 316                      'has-$slug-font-size' => 'font-size',
 317                  ),
 318              ),
 319              'fontFamily'     => array(
 320                  'property_keys' => array(
 321                      'default' => 'font-family',
 322                  ),
 323                  'css_vars'      => array(
 324                      'font-family' => '--wp--preset--font-family--$slug',
 325                  ),
 326                  'path'          => array( 'typography', 'fontFamily' ),
 327                  'classnames'    => array(
 328                      'has-$slug-font-family' => 'font-family',
 329                  ),
 330              ),
 331              'fontStyle'      => array(
 332                  'property_keys' => array(
 333                      'default' => 'font-style',
 334                  ),
 335                  'path'          => array( 'typography', 'fontStyle' ),
 336              ),
 337              'fontWeight'     => array(
 338                  'property_keys' => array(
 339                      'default' => 'font-weight',
 340                  ),
 341                  'path'          => array( 'typography', 'fontWeight' ),
 342              ),
 343              'lineHeight'     => array(
 344                  'property_keys' => array(
 345                      'default' => 'line-height',
 346                  ),
 347                  'path'          => array( 'typography', 'lineHeight' ),
 348              ),
 349              'textColumns'    => array(
 350                  'property_keys' => array(
 351                      'default' => 'column-count',
 352                  ),
 353                  'path'          => array( 'typography', 'textColumns' ),
 354              ),
 355              'textDecoration' => array(
 356                  'property_keys' => array(
 357                      'default' => 'text-decoration',
 358                  ),
 359                  'path'          => array( 'typography', 'textDecoration' ),
 360              ),
 361              'textIndent'     => array(
 362                  'property_keys' => array(
 363                      'default' => 'text-indent',
 364                  ),
 365                  'path'          => array( 'typography', 'textIndent' ),
 366              ),
 367              'textShadow'     => array(
 368                  'property_keys' => array(
 369                      'default' => 'text-shadow',
 370                  ),
 371                  'css_vars'      => array(
 372                      'text-shadow' => '--wp--preset--text-shadow--$slug',
 373                  ),
 374                  'path'          => array( 'typography', 'textShadow' ),
 375                  'classnames'    => array(
 376                      'has-$slug-text-shadow' => 'text-shadow',
 377                  ),
 378              ),
 379              'textTransform'  => array(
 380                  'property_keys' => array(
 381                      'default' => 'text-transform',
 382                  ),
 383                  'path'          => array( 'typography', 'textTransform' ),
 384              ),
 385              'letterSpacing'  => array(
 386                  'property_keys' => array(
 387                      'default' => 'letter-spacing',
 388                  ),
 389                  'path'          => array( 'typography', 'letterSpacing' ),
 390              ),
 391              'writingMode'    => array(
 392                  'property_keys' => array(
 393                      'default' => 'writing-mode',
 394                  ),
 395                  'path'          => array( 'typography', 'writingMode' ),
 396              ),
 397          ),
 398      );
 399  
 400      /**
 401       * Util: Extracts the slug in kebab case from a preset string,
 402       * e.g. `heavenly-blue` from `var:preset|color|heavenlyBlue`.
 403       *
 404       * @since 6.1.0
 405       *
 406       * @param string $style_value  A single CSS preset value.
 407       * @param string $property_key The CSS property that is the second element of the preset string.
 408       *                             Used for matching.
 409       * @return string The slug, or empty string if not found.
 410       */
 411  	protected static function get_slug_from_preset_value( $style_value, $property_key ) {
 412          if ( is_string( $style_value ) && is_string( $property_key )
 413              && str_contains( $style_value, "var:preset|{$property_key}|" )
 414          ) {
 415              $index_to_splice = strrpos( $style_value, '|' ) + 1;
 416              return _wp_to_kebab_case( substr( $style_value, $index_to_splice ) );
 417          }
 418          return '';
 419      }
 420  
 421      /**
 422       * Util: Generates a CSS var string, e.g. `var(--wp--preset--color--background)`
 423       * from a preset string such as `var:preset|space|50`.
 424       *
 425       * @since 6.1.0
 426       *
 427       * @param string   $style_value A single CSS preset value.
 428       * @param string[] $css_vars    An associate array of CSS var patterns
 429       *                              used to generate the var string.
 430       * @return string The CSS var, or an empty string if no match for slug found.
 431       */
 432  	protected static function get_css_var_value( $style_value, $css_vars ) {
 433          foreach ( $css_vars as $property_key => $css_var_pattern ) {
 434              $slug = static::get_slug_from_preset_value( $style_value, $property_key );
 435              if ( static::is_valid_style_value( $slug ) ) {
 436                  $var = strtr(
 437                      $css_var_pattern,
 438                      array( '$slug' => $slug )
 439                  );
 440                  return "var($var)";
 441              }
 442          }
 443          return '';
 444      }
 445  
 446      /**
 447       * Util: Checks whether an incoming block style value is valid.
 448       *
 449       * @since 6.1.0
 450       *
 451       * @param string $style_value A single CSS preset value.
 452       * @return bool
 453       */
 454  	protected static function is_valid_style_value( $style_value ) {
 455          return '0' === $style_value || ! empty( $style_value );
 456      }
 457  
 458      /**
 459       * Stores a CSS rule using the provided CSS selector and CSS declarations.
 460       *
 461       * @since 6.1.0
 462       * @since 6.6.0 Added the `$rules_group` parameter.
 463       * @since 7.1.0 Extended `$css_declarations` parameter to accept `WP_Style_Engine_CSS_Declarations` object.
 464       *
 465       * @param string                                    $store_name       A valid store key.
 466       * @param string                                    $css_selector     When a selector is passed, the function will return
 467       *                                                                    a full CSS rule `$selector { ...rules }`
 468       *                                                                    otherwise a concatenated string of properties and values.
 469       * @param string[]|WP_Style_Engine_CSS_Declarations $css_declarations An associative array of CSS definitions,
 470       *                                                                    e.g. `array( "$property" => "$value", "$property" => "$value" )`,
 471       *                                                                    or a WP_Style_Engine_CSS_Declarations object.
 472       * @param string                                    $rules_group      Optional. A parent CSS selector in the case of nested CSS,
 473       *                                                                    or a CSS nested @rule, such as `@media (min-width: 80rem)`
 474       *                                                                    or `@layer module`.
 475       */
 476  	public static function store_css_rule( $store_name, $css_selector, $css_declarations, $rules_group = '' ) {
 477          if ( empty( $store_name ) || empty( $css_selector ) || empty( $css_declarations ) ) {
 478              return;
 479          }
 480          static::get_store( $store_name )->add_rule( $css_selector, $rules_group )->add_declarations( $css_declarations );
 481      }
 482  
 483      /**
 484       * Returns a store by store key.
 485       *
 486       * @since 6.1.0
 487       *
 488       * @param string $store_name A store key.
 489       * @return WP_Style_Engine_CSS_Rules_Store|null
 490       */
 491  	public static function get_store( $store_name ) {
 492          return WP_Style_Engine_CSS_Rules_Store::get_store( $store_name );
 493      }
 494  
 495      /**
 496       * Returns classnames and CSS based on the values in a styles object.
 497       *
 498       * Return values are parsed based on the instructions in BLOCK_STYLE_DEFINITIONS_METADATA.
 499       *
 500       * @since 6.1.0
 501       *
 502       * @param array $block_styles The style object.
 503       * @param array $options      {
 504       *     Optional. An array of options. Default empty array.
 505       *
 506       *     @type bool        $convert_vars_to_classnames Whether to skip converting incoming CSS var patterns,
 507       *                                                   e.g. `var:preset|<PRESET_TYPE>|<PRESET_SLUG>`,
 508       *                                                   to `var( --wp--preset--* )` values. Default false.
 509       *     @type string      $selector                   Optional. When a selector is passed,
 510       *                                                   the value of `$css` in the return value will comprise
 511       *                                                   a full CSS rule `$selector { ...$css_declarations }`,
 512       *                                                   otherwise, the value will be a concatenated string
 513       *                                                   of CSS declarations.
 514       * }
 515       * @return array {
 516       *     @type string[] $classnames   Array of class names.
 517       *     @type string[] $declarations An associative array of CSS definitions,
 518       *                                  e.g. `array( "$property" => "$value", "$property" => "$value" )`.
 519       * }
 520       */
 521  	public static function parse_block_styles( $block_styles, $options ) {
 522          $parsed_styles = array(
 523              'classnames'   => array(),
 524              'declarations' => array(),
 525          );
 526          if ( empty( $block_styles ) || ! is_array( $block_styles ) ) {
 527              return $parsed_styles;
 528          }
 529  
 530          // Collect CSS and classnames.
 531          foreach ( static::BLOCK_STYLE_DEFINITIONS_METADATA as $definition_group_key => $definition_group_style ) {
 532              if ( empty( $block_styles[ $definition_group_key ] ) ) {
 533                  continue;
 534              }
 535              foreach ( $definition_group_style as $style_definition ) {
 536                  $style_value = _wp_array_get( $block_styles, $style_definition['path'], null );
 537  
 538                  if ( ! static::is_valid_style_value( $style_value ) ) {
 539                      continue;
 540                  }
 541  
 542                  $classnames = static::get_classnames( $style_value, $style_definition );
 543                  if ( ! empty( $classnames ) ) {
 544                      $parsed_styles['classnames'] = array_merge( $parsed_styles['classnames'], $classnames );
 545                  }
 546  
 547                  $css_declarations = static::get_css_declarations( $style_value, $style_definition, $options );
 548                  if ( ! empty( $css_declarations ) ) {
 549                      /*
 550                       * Combine background gradient and background image into a single
 551                       * comma-separated background-image value, matching the JS style engine.
 552                       */
 553                      if ( isset( $css_declarations['background-image'] ) && isset( $parsed_styles['declarations']['background-image'] ) ) {
 554                          $css_declarations['background-image'] = $css_declarations['background-image'] . ', ' . $parsed_styles['declarations']['background-image'];
 555                      }
 556                      $parsed_styles['declarations'] = array_merge( $parsed_styles['declarations'], $css_declarations );
 557                  }
 558              }
 559          }
 560  
 561          return $parsed_styles;
 562      }
 563  
 564      /**
 565       * Returns classnames, and generates classname(s) from a CSS preset property pattern,
 566       * e.g. `var:preset|<PRESET_TYPE>|<PRESET_SLUG>`.
 567       *
 568       * @since 6.1.0
 569       *
 570       * @param string $style_value      A single raw style value or CSS preset property
 571       *                                 from the `$block_styles` array.
 572       * @param array  $style_definition A single style definition from BLOCK_STYLE_DEFINITIONS_METADATA.
 573       * @return string[] An array of CSS classnames, or empty array if there are none.
 574       */
 575  	protected static function get_classnames( $style_value, $style_definition ) {
 576          if ( empty( $style_value ) ) {
 577              return array();
 578          }
 579  
 580          $classnames = array();
 581          if ( ! empty( $style_definition['classnames'] ) ) {
 582              foreach ( $style_definition['classnames'] as $classname => $property_key ) {
 583                  if ( true === $property_key ) {
 584                      $classnames[] = $classname;
 585                      continue;
 586                  }
 587  
 588                  $slug = static::get_slug_from_preset_value( $style_value, $property_key );
 589  
 590                  if ( $slug ) {
 591                      /*
 592                       * Right now we expect a classname pattern to be stored in BLOCK_STYLE_DEFINITIONS_METADATA.
 593                       * One day, if there are no stored schemata, we could allow custom patterns or
 594                       * generate classnames based on other properties
 595                       * such as a path or a value or a prefix passed in options.
 596                       */
 597                      $classnames[] = strtr( $classname, array( '$slug' => $slug ) );
 598                  }
 599              }
 600          }
 601  
 602          return $classnames;
 603      }
 604  
 605      /**
 606       * Returns an array of CSS declarations based on valid block style values.
 607       *
 608       * @since 6.1.0
 609       *
 610       * @param mixed $style_value      A single raw style value from $block_styles array.
 611       * @param array $style_definition A single style definition from BLOCK_STYLE_DEFINITIONS_METADATA.
 612       * @param array $options          {
 613       *     Optional. An array of options. Default empty array.
 614       *
 615       *     @type bool $convert_vars_to_classnames Whether to skip converting incoming CSS var patterns,
 616       *                                            e.g. `var:preset|<PRESET_TYPE>|<PRESET_SLUG>`,
 617       *                                            to `var( --wp--preset--* )` values. Default false.
 618       * }
 619       * @return string[] An associative array of CSS definitions, e.g. `array( "$property" => "$value", "$property" => "$value" )`.
 620       */
 621  	protected static function get_css_declarations( $style_value, $style_definition, $options = array() ) {
 622          if ( isset( $style_definition['value_func'] ) && is_callable( $style_definition['value_func'] ) ) {
 623              return call_user_func( $style_definition['value_func'], $style_value, $style_definition, $options );
 624          }
 625  
 626          $css_declarations     = array();
 627          $style_property_keys  = $style_definition['property_keys'];
 628          $should_skip_css_vars = isset( $options['convert_vars_to_classnames'] ) && true === $options['convert_vars_to_classnames'];
 629  
 630          /*
 631           * Build CSS var values from `var:preset|<PRESET_TYPE>|<PRESET_SLUG>` values, e.g, `var(--wp--css--rule-slug )`.
 632           * Check if the value is a CSS preset and there's a corresponding css_var pattern in the style definition.
 633           */
 634          if ( is_string( $style_value ) && str_contains( $style_value, 'var:' ) ) {
 635              if ( ! $should_skip_css_vars && ! empty( $style_definition['css_vars'] ) ) {
 636                  $css_var = static::get_css_var_value( $style_value, $style_definition['css_vars'] );
 637                  if ( static::is_valid_style_value( $css_var ) ) {
 638                      $css_declarations[ $style_property_keys['default'] ] = $css_var;
 639                  }
 640              }
 641              return $css_declarations;
 642          }
 643  
 644          /*
 645           * Default rule builder.
 646           * If the input contains an array, assume box model-like properties
 647           * for styles such as margins and padding.
 648           */
 649          if ( is_array( $style_value ) ) {
 650              // Bail out early if the `'individual'` property is not defined.
 651              if ( ! isset( $style_property_keys['individual'] ) ) {
 652                  return $css_declarations;
 653              }
 654  
 655              foreach ( $style_value as $key => $value ) {
 656                  if ( is_string( $value ) && str_contains( $value, 'var:' ) && ! $should_skip_css_vars && ! empty( $style_definition['css_vars'] ) ) {
 657                      $value = static::get_css_var_value( $value, $style_definition['css_vars'] );
 658                  }
 659  
 660                  $individual_property = sprintf( $style_property_keys['individual'], _wp_to_kebab_case( $key ) );
 661  
 662                  if ( $individual_property && static::is_valid_style_value( $value ) ) {
 663                      $css_declarations[ $individual_property ] = $value;
 664                  }
 665              }
 666  
 667              return $css_declarations;
 668          }
 669  
 670          $css_declarations[ $style_property_keys['default'] ] = $style_value;
 671          return $css_declarations;
 672      }
 673  
 674      /**
 675       * Style value parser that returns a CSS definition array comprising style properties
 676       * that have keys representing individual style properties, otherwise known as longhand CSS properties.
 677       *
 678       * Example:
 679       *
 680       *     "$style_property-$individual_feature: $value;"
 681       *
 682       * Which could represent the following:
 683       *
 684       *     "border-{top|right|bottom|left}-{color|width|style}: {value};"
 685       *
 686       * or:
 687       *
 688       *     "border-image-{outset|source|width|repeat|slice}: {value};"
 689       *
 690       * @since 6.1.0
 691       *
 692       * @param array $style_value                    A single raw style value from `$block_styles` array.
 693       * @param array $individual_property_definition A single style definition from BLOCK_STYLE_DEFINITIONS_METADATA
 694       *                                              representing an individual property of a CSS property,
 695       *                                              e.g. 'top' in 'border-top'.
 696       * @param array $options                        {
 697       *     Optional. An array of options. Default empty array.
 698       *
 699       *     @type bool $convert_vars_to_classnames Whether to skip converting incoming CSS var patterns,
 700       *                                            e.g. `var:preset|<PRESET_TYPE>|<PRESET_SLUG>`,
 701       *                                            to `var( --wp--preset--* )` values. Default false.
 702       * }
 703       * @return string[] An associative array of CSS definitions, e.g. `array( "$property" => "$value", "$property" => "$value" )`.
 704       */
 705  	protected static function get_individual_property_css_declarations( $style_value, $individual_property_definition, $options = array() ) {
 706          if ( ! is_array( $style_value ) || empty( $style_value ) || empty( $individual_property_definition['path'] ) ) {
 707              return array();
 708          }
 709  
 710          /*
 711           * The first item in $individual_property_definition['path'] array
 712           * tells us the style property, e.g. "border". We use this to get a corresponding
 713           * CSS style definition such as "color" or "width" from the same group.
 714           *
 715           * The second item in $individual_property_definition['path'] array
 716           * refers to the individual property marker, e.g. "top".
 717           */
 718          $definition_group_key    = $individual_property_definition['path'][0];
 719          $individual_property_key = $individual_property_definition['path'][1];
 720          $should_skip_css_vars    = isset( $options['convert_vars_to_classnames'] ) && true === $options['convert_vars_to_classnames'];
 721          $css_declarations        = array();
 722  
 723          foreach ( $style_value as $css_property => $value ) {
 724              if ( empty( $value ) ) {
 725                  continue;
 726              }
 727  
 728              // Build a path to the individual rules in definitions.
 729              $style_definition_path = array( $definition_group_key, $css_property );
 730              $style_definition      = _wp_array_get( static::BLOCK_STYLE_DEFINITIONS_METADATA, $style_definition_path, null );
 731  
 732              if ( $style_definition && isset( $style_definition['property_keys']['individual'] ) ) {
 733                  // Set a CSS var if there is a valid preset value.
 734                  if ( is_string( $value ) && str_contains( $value, 'var:' )
 735                      && ! $should_skip_css_vars && ! empty( $individual_property_definition['css_vars'] )
 736                  ) {
 737                      $value = static::get_css_var_value( $value, $individual_property_definition['css_vars'] );
 738                  }
 739  
 740                  $individual_css_property = sprintf( $style_definition['property_keys']['individual'], $individual_property_key );
 741  
 742                  $css_declarations[ $individual_css_property ] = $value;
 743              }
 744          }
 745          return $css_declarations;
 746      }
 747  
 748      /**
 749       * Style value parser that constructs a CSS definition array comprising a single CSS property and value.
 750       * If the provided value is an array containing a `url` property, the function will return a CSS definition array
 751       * with a single property and value, with `url` escaped and injected into a CSS `url()` function,
 752       * e.g., array( 'background-image' => "url( '...' )" ).
 753       *
 754       * @since 6.4.0
 755       *
 756       * @param array $style_value      A single raw style value from $block_styles array.
 757       * @param array $style_definition A single style definition from BLOCK_STYLE_DEFINITIONS_METADATA.
 758       * @return string[] An associative array of CSS definitions, e.g., array( "$property" => "$value", "$property" => "$value" ).
 759       */
 760  	protected static function get_url_or_value_css_declaration( $style_value, $style_definition ) {
 761          if ( empty( $style_value ) ) {
 762              return array();
 763          }
 764  
 765          $css_declarations = array();
 766  
 767          if ( isset( $style_definition['property_keys']['default'] ) ) {
 768              $value = null;
 769  
 770              if ( ! empty( $style_value['url'] ) ) {
 771                  $value = "url('" . $style_value['url'] . "')";
 772              } elseif ( is_string( $style_value ) ) {
 773                  $value = $style_value;
 774              }
 775  
 776              if ( null !== $value ) {
 777                  $css_declarations[ $style_definition['property_keys']['default'] ] = $value;
 778              }
 779          }
 780  
 781          return $css_declarations;
 782      }
 783  
 784      /**
 785       * Style value parser that returns the CSS declarations for background clipping.
 786       *
 787       * For the `text` value the background is clipped to the block's text, which
 788       * requires the vendor prefixed properties and a transparent fill color.
 789       *
 790       * @since 7.2.0
 791       *
 792       * @param string $style_value      A single raw style value from $block_styles array.
 793       * @param array  $style_definition A single style definition from BLOCK_STYLE_DEFINITIONS_METADATA.
 794       * @return array<string, string> An associative array of CSS definitions, mapping properties to values.
 795       *
 796       * @phpstan-return array<non-falsy-string, non-falsy-string>
 797       */
 798  	protected static function get_background_clip_css_declarations( $style_value, $style_definition ) {
 799          if ( ! in_array( $style_value, static::VALID_BACKGROUND_CLIP_VALUES, true ) ) {
 800              return array();
 801          }
 802  
 803          $css_declarations = array(
 804              'background-clip' => $style_value,
 805          );
 806  
 807          if ( 'text' === $style_value ) {
 808              $css_declarations['-webkit-background-clip'] = 'text';
 809              $css_declarations['-webkit-text-fill-color'] = 'transparent';
 810          } else {
 811              /*
 812               * Only the fill color is restored. `-webkit-background-clip` is an
 813               * alias of `background-clip` in Chromium, so resetting it here would
 814               * discard the value set above. The fill color is inherited, so it
 815               * needs its initial value rather than `unset`, which would take a
 816               * transparent fill from an ancestor clipping to text.
 817               */
 818              $css_declarations['-webkit-text-fill-color'] = 'currentColor';
 819          }
 820  
 821          return $css_declarations;
 822      }
 823  
 824      /**
 825       * Returns compiled CSS from CSS declarations.
 826       *
 827       * @since 6.1.0
 828       *
 829       * @param string[] $css_declarations An associative array of CSS definitions,
 830       *                                   e.g. `array( "$property" => "$value", "$property" => "$value" )`.
 831       * @param string   $css_selector     When a selector is passed, the function will return
 832       *                                   a full CSS rule `$selector { ...rules }`,
 833       *                                   otherwise a concatenated string of properties and values.
 834       * @return string A compiled CSS string.
 835       */
 836  	public static function compile_css( $css_declarations, $css_selector ) {
 837          if ( empty( $css_declarations ) || ! is_array( $css_declarations ) ) {
 838              return '';
 839          }
 840  
 841          // Return an entire rule if there is a selector.
 842          if ( $css_selector ) {
 843              $css_rule = new WP_Style_Engine_CSS_Rule( $css_selector, $css_declarations );
 844              return $css_rule->get_css();
 845          }
 846  
 847          $css_declarations = new WP_Style_Engine_CSS_Declarations( $css_declarations );
 848          return $css_declarations->get_declarations_string();
 849      }
 850  
 851      /**
 852       * Returns a compiled stylesheet from stored CSS rules.
 853       *
 854       * @since 6.1.0
 855       *
 856       * @param WP_Style_Engine_CSS_Rule[] $css_rules An array of WP_Style_Engine_CSS_Rule objects
 857       *                                              from a store or otherwise.
 858       * @param array                      $options   {
 859       *     Optional. An array of options. Default empty array.
 860       *
 861       *     @type string|null $context  An identifier describing the origin of the style object,
 862       *                                 e.g. 'block-supports' or 'global-styles'. Default 'block-supports'.
 863       *                                 When set, the style engine will attempt to store the CSS rules.
 864       *     @type bool        $optimize Whether to optimize the CSS output, e.g. combine rules.
 865       *                                 Default false.
 866       *     @type bool        $prettify Whether to add new lines and indents to output.
 867       *                                 Defaults to whether the `SCRIPT_DEBUG` constant is defined.
 868       * }
 869       * @return string A compiled stylesheet from stored CSS rules.
 870       */
 871  	public static function compile_stylesheet_from_css_rules( $css_rules, $options = array() ) {
 872          $processor = new WP_Style_Engine_Processor();
 873          $processor->add_rules( $css_rules );
 874          return $processor->get_css( $options );
 875      }
 876  }


Generated : Sun Oct 25 08:20:35 2026 Cross-referenced by PHPXref