[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/block-supports/ -> block-style-variations.php (source)

   1  <?php
   2  /**
   3   * Block support to enable per-section styling of block types via
   4   * block style variations.
   5   *
   6   * @package WordPress
   7   * @since 6.6.0
   8   */
   9  
  10  /**
  11   * Determines the block style variation names within a CSS class string.
  12   *
  13   * @since 6.6.0
  14   *
  15   * @param string|null $class_string CSS class string to look for a variation in.
  16   * @return string[]|null The block style variation names (empty if none found), or null if a string was not supplied.
  17   * @phpstan-return ( $class_string is string ? list<non-empty-string> : null )
  18   */
  19  function wp_get_block_style_variation_name_from_class( $class_string ): ?array {
  20      if ( ! is_string( $class_string ) ) {
  21          return null;
  22      }
  23  
  24      preg_match_all( '/\bis-style-(?!default)(\S+)\b/', $class_string, $matches );
  25      return $matches[1];
  26  }
  27  
  28  /**
  29   * Recursively resolves any `ref` values within a block style variation's data.
  30   *
  31   * @since 6.6.0
  32   * @access private
  33   *
  34   * @param array $variation_data Reference to the variation data being processed.
  35   * @param array $theme_json     Theme.json data to retrieve referenced values from.
  36   */
  37  function wp_resolve_block_style_variation_ref_values( &$variation_data, $theme_json ) {
  38      foreach ( $variation_data as $key => &$value ) {
  39          // Only need to potentially process arrays.
  40          if ( is_array( $value ) ) {
  41              // If ref value is set, attempt to find its matching value and update it.
  42              if ( array_key_exists( 'ref', $value ) ) {
  43                  // Clean up any invalid ref value.
  44                  if ( empty( $value['ref'] ) || ! is_string( $value['ref'] ) ) {
  45                      unset( $variation_data[ $key ] );
  46                  }
  47  
  48                  $value_path = explode( '.', $value['ref'] ?? '' );
  49                  $ref_value  = _wp_array_get( $theme_json, $value_path );
  50  
  51                  // Only update the current value if the referenced path matched a value.
  52                  if ( null === $ref_value ) {
  53                      unset( $variation_data[ $key ] );
  54                  } else {
  55                      $value = $ref_value;
  56                  }
  57              } else {
  58                  // Recursively look for ref instances.
  59                  wp_resolve_block_style_variation_ref_values( $value, $theme_json );
  60              }
  61          }
  62      }
  63  }
  64  /**
  65   * Renders the block style variation's styles.
  66   *
  67   * In the case of nested blocks with variations applied, we want the parent
  68   * variation's styles to be rendered before their descendants. This solves the
  69   * issue of a block type being styled in both the parent and descendant: we want
  70   * the descendant style to take priority, and this is done by loading it after,
  71   * in the DOM order. This is why the variation stylesheet generation is in a
  72   * different filter.
  73   *
  74   * @since 6.6.0
  75   * @access private
  76   *
  77   * @param array $parsed_block The parsed block.
  78   * @return array The parsed block with block style variation classname added.
  79   */
  80  function wp_render_block_style_variation_support_styles( $parsed_block ) {
  81      $classes    = $parsed_block['attrs']['className'] ?? null;
  82      $variations = wp_get_block_style_variation_name_from_class( $classes );
  83  
  84      if ( ! $variations ) {
  85          return $parsed_block;
  86      }
  87  
  88      $tree       = WP_Theme_JSON_Resolver::get_merged_data();
  89      $theme_json = $tree->get_raw_data();
  90  
  91      // Only the first block style variation with data is supported.
  92      $variation_data = array();
  93      foreach ( $variations as $variation ) {
  94          $variation_data = $theme_json['styles']['blocks'][ $parsed_block['blockName'] ]['variations'][ $variation ] ?? array();
  95  
  96          if ( ! empty( $variation_data ) ) {
  97              break;
  98          }
  99      }
 100  
 101      if ( empty( $variation_data ) ) {
 102          return $parsed_block;
 103      }
 104  
 105      /*
 106       * Recursively resolve any ref values with the appropriate value within the
 107       * theme_json data.
 108       */
 109      wp_resolve_block_style_variation_ref_values( $variation_data, $theme_json );
 110  
 111      $variation_instance = wp_unique_id( $variation . '--' );
 112      $class_name         = "is-style-$variation_instance";
 113      $updated_class_name = $parsed_block['attrs']['className'] . " $class_name";
 114  
 115      /*
 116       * Even though block style variations are effectively theme.json partials,
 117       * they can't be processed completely as though they are.
 118       *
 119       * Block styles support custom selectors to direct specific types of styles
 120       * to inner elements. For example, borders on Image block's get applied to
 121       * the inner `img` element rather than the wrapping `figure`.
 122       *
 123       * The following relocates the "root" block style variation styles to
 124       * under an appropriate blocks property to leverage the preexisting style
 125       * generation for simple block style variations. This way they get the
 126       * custom selectors they need.
 127       *
 128       * The inner elements and block styles for the variation itself are
 129       * still included at the top level but scoped by the variation's selector
 130       * when the stylesheet is generated.
 131       */
 132      $elements_data = $variation_data['elements'] ?? array();
 133      $blocks_data   = $variation_data['blocks'] ?? array();
 134      unset( $variation_data['elements'] );
 135      unset( $variation_data['blocks'] );
 136  
 137      _wp_array_set(
 138          $blocks_data,
 139          array( $parsed_block['blockName'], 'variations', $variation_instance ),
 140          $variation_data
 141      );
 142  
 143      $config = array(
 144          'version'  => WP_Theme_JSON::LATEST_SCHEMA,
 145          'settings' => array(
 146              'spacing' => array(
 147                  'blockGap' => true,
 148              ),
 149          ),
 150          'styles'   => array(
 151              'elements' => $elements_data,
 152              'blocks'   => $blocks_data,
 153          ),
 154      );
 155  
 156      // Ensure variation state styles know about any custom viewport breakpoints.
 157      if ( isset( $theme_json['settings']['viewport'] ) ) {
 158          $config['settings']['viewport'] = $theme_json['settings']['viewport'];
 159      }
 160  
 161      // Turn off filter that excludes block nodes. They are needed here for the variation's inner block types.
 162      if ( ! is_admin() ) {
 163          remove_filter( 'wp_theme_json_get_style_nodes', 'wp_filter_out_block_nodes' );
 164      }
 165  
 166      // Temporarily prevent variation instance from being sanitized while processing theme.json.
 167      $styles_registry = WP_Block_Styles_Registry::get_instance();
 168      $styles_registry->register( $parsed_block['blockName'], array( 'name' => $variation_instance ) );
 169  
 170      $variation_theme_json = new WP_Theme_JSON( $config, 'blocks' );
 171      $variation_styles     = $variation_theme_json->get_stylesheet(
 172          array( 'styles' ),
 173          array( 'custom' ),
 174          array(
 175              'include_block_style_variations' => true,
 176              'skip_root_layout_styles'        => true,
 177              'scope'                          => ".$class_name",
 178          )
 179      );
 180  
 181      // Clean up temporary block style now instance styles have been processed.
 182      $styles_registry->unregister( $parsed_block['blockName'], $variation_instance );
 183  
 184      // Restore filter that excludes block nodes.
 185      if ( ! is_admin() ) {
 186          add_filter( 'wp_theme_json_get_style_nodes', 'wp_filter_out_block_nodes' );
 187      }
 188  
 189      if ( empty( $variation_styles ) ) {
 190          return $parsed_block;
 191      }
 192  
 193      wp_register_style( 'block-style-variation-styles', false, array( 'wp-block-library', 'global-styles' ) );
 194      wp_add_inline_style( 'block-style-variation-styles', $variation_styles );
 195  
 196      /*
 197       * Add variation instance class name to block's className string so it can
 198       * be enforced in the block markup via render_block filter.
 199       */
 200      _wp_array_set( $parsed_block, array( 'attrs', 'className' ), $updated_class_name );
 201  
 202      return $parsed_block;
 203  }
 204  
 205  /**
 206   * Ensures the variation block support class name generated and added to
 207   * block attributes in the `render_block_data` filter gets applied to the
 208   * block's markup.
 209   *
 210   * @since 6.6.0
 211   * @access private
 212   *
 213   * @see wp_render_block_style_variation_support_styles
 214   *
 215   * @param string $block_content Rendered block content.
 216   * @param array  $block         Block object.
 217   * @return string Filtered block content.
 218   */
 219  function wp_render_block_style_variation_class_name( $block_content, $block ) {
 220      if ( ! $block_content || empty( $block['attrs']['className'] ) ) {
 221          return $block_content;
 222      }
 223  
 224      $block_class_name = $block['attrs']['className'];
 225      if ( ! is_string( $block_class_name ) ) {
 226          return $block_content;
 227      }
 228  
 229      /*
 230       * Matches a class prefixed by `is-style`, followed by the
 231       * variation slug, then `--`, and finally an instance number.
 232       */
 233      preg_match( '/\bis-style-(\S+?--\d+)\b/', $block_class_name, $matches );
 234  
 235      if ( empty( $matches ) ) {
 236          return $block_content;
 237      }
 238  
 239      $tags = new WP_HTML_Tag_Processor( $block_content );
 240  
 241      if ( $tags->next_tag() ) {
 242          /*
 243           * Ensure the variation instance class name set in the
 244           * `render_block_data` filter is applied in markup.
 245           * See `wp_render_block_style_variation_support_styles`.
 246           */
 247          $tags->add_class( $matches[0] );
 248      }
 249  
 250      return $tags->get_updated_html();
 251  }
 252  
 253  /**
 254   * Enqueues styles for block style variations.
 255   *
 256   * @since 6.6.0
 257   * @access private
 258   */
 259  function wp_enqueue_block_style_variation_styles() {
 260      wp_enqueue_style( 'block-style-variation-styles' );
 261  }
 262  
 263  // Register the block support.
 264  WP_Block_Supports::get_instance()->register( 'block-style-variation', array() );
 265  
 266  add_filter( 'render_block_data', 'wp_render_block_style_variation_support_styles', 10, 2 );
 267  add_filter( 'render_block', 'wp_render_block_style_variation_class_name', 10, 2 );
 268  add_action( 'wp_enqueue_scripts', 'wp_enqueue_block_style_variation_styles', 1 );
 269  
 270  /**
 271   * Registers block style variations read in from theme.json partials.
 272   *
 273   * @since 6.6.0
 274   * @access private
 275   *
 276   * @param array $variations Shared block style variations.
 277   */
 278  function wp_register_block_style_variations_from_theme_json_partials( $variations ) {
 279      if ( empty( $variations ) ) {
 280          return;
 281      }
 282  
 283      $registry = WP_Block_Styles_Registry::get_instance();
 284  
 285      foreach ( $variations as $variation ) {
 286          if ( empty( $variation['blockTypes'] ) || empty( $variation['styles'] ) ) {
 287              continue;
 288          }
 289  
 290          $variation_name  = $variation['slug'] ?? _wp_to_kebab_case( $variation['title'] );
 291          $variation_label = $variation['title'] ?? $variation_name;
 292  
 293          foreach ( $variation['blockTypes'] as $block_type ) {
 294              $registered_styles = $registry->get_registered_styles_for_block( $block_type );
 295  
 296              // Register block style variation if it hasn't already been registered.
 297              if ( ! array_key_exists( $variation_name, $registered_styles ) ) {
 298                  register_block_style(
 299                      $block_type,
 300                      array(
 301                          'name'  => $variation_name,
 302                          'label' => $variation_label,
 303                      )
 304                  );
 305              }
 306          }
 307      }
 308  }


Generated : Fri Oct 9 08:20:33 2026 Cross-referenced by PHPXref