wp/wp-includes/block-supports/elements.php
changeset 21 48c4eec2b7e6
parent 19 3d72ae0968f4
equal deleted inserted replaced
20:7b1b88e27a20 21:48c4eec2b7e6
     5  * @package WordPress
     5  * @package WordPress
     6  * @since 5.8.0
     6  * @since 5.8.0
     7  */
     7  */
     8 
     8 
     9 /**
     9 /**
    10  * Get the elements class names.
    10  * Gets the elements class names.
    11  *
    11  *
    12  * @since 6.0.0
    12  * @since 6.0.0
    13  * @access private
    13  * @access private
    14  *
    14  *
    15  * @param array $block Block object.
    15  * @param array $block Block object.
    16  * @return string      The unique class name.
    16  * @return string The unique class name.
    17  */
    17  */
    18 function wp_get_elements_class_name( $block ) {
    18 function wp_get_elements_class_name( $block ) {
    19 	return 'wp-elements-' . md5( serialize( $block ) );
    19 	return 'wp-elements-' . md5( serialize( $block ) );
    20 }
    20 }
    21 
    21 
    22 /**
    22 /**
    23  * Update the block content with elements class names.
    23  * Determines whether an elements class name should be added to the block.
    24  *
    24  *
    25  * @since 5.8.0
    25  * @since 6.6.0
    26  * @access private
    26  * @access private
    27  *
    27  *
    28  * @param string $block_content Rendered block content.
    28  * @param  array $block   Block object.
    29  * @param array  $block         Block object.
    29  * @param  array $options Per element type options e.g. whether to skip serialization.
    30  * @return string Filtered block content.
    30  * @return boolean Whether the block needs an elements class name.
    31  */
    31  */
    32 function wp_render_elements_support( $block_content, $block ) {
    32 function wp_should_add_elements_class_name( $block, $options ) {
    33 	if ( ! $block_content ) {
    33 	if ( ! isset( $block['attrs']['style']['elements'] ) ) {
    34 		return $block_content;
    34 		return false;
    35 	}
    35 	}
    36 
    36 
    37 	$block_type                    = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] );
    37 	$element_color_properties = array(
    38 	$skip_link_color_serialization = wp_should_skip_block_supports_serialization( $block_type, 'color', 'link' );
    38 		'button'  => array(
    39 
    39 			'skip'  => isset( $options['button']['skip'] ) ? $options['button']['skip'] : false,
    40 	if ( $skip_link_color_serialization ) {
    40 			'paths' => array(
    41 		return $block_content;
    41 				array( 'button', 'color', 'text' ),
    42 	}
    42 				array( 'button', 'color', 'background' ),
    43 
    43 				array( 'button', 'color', 'gradient' ),
    44 	$link_color = null;
    44 			),
    45 	if ( ! empty( $block['attrs'] ) ) {
    45 		),
    46 		$link_color = _wp_array_get( $block['attrs'], array( 'style', 'elements', 'link', 'color', 'text' ), null );
    46 		'link'    => array(
    47 	}
    47 			'skip'  => isset( $options['link']['skip'] ) ? $options['link']['skip'] : false,
    48 
    48 			'paths' => array(
    49 	/*
    49 				array( 'link', 'color', 'text' ),
    50 	 * For now we only care about link color.
    50 				array( 'link', ':hover', 'color', 'text' ),
    51 	 * This code in the future when we have a public API
    51 			),
    52 	 * should take advantage of WP_Theme_JSON::compute_style_properties
    52 		),
    53 	 * and work for any element and style.
    53 		'heading' => array(
    54 	 */
    54 			'skip'  => isset( $options['heading']['skip'] ) ? $options['heading']['skip'] : false,
    55 	if ( null === $link_color ) {
    55 			'paths' => array(
    56 		return $block_content;
    56 				array( 'heading', 'color', 'text' ),
    57 	}
    57 				array( 'heading', 'color', 'background' ),
    58 
    58 				array( 'heading', 'color', 'gradient' ),
    59 	$class_name = wp_get_elements_class_name( $block );
    59 				array( 'h1', 'color', 'text' ),
    60 
    60 				array( 'h1', 'color', 'background' ),
    61 	// Like the layout hook this assumes the hook only applies to blocks with a single wrapper.
    61 				array( 'h1', 'color', 'gradient' ),
    62 	// Retrieve the opening tag of the first HTML element.
    62 				array( 'h2', 'color', 'text' ),
    63 	$html_element_matches = array();
    63 				array( 'h2', 'color', 'background' ),
    64 	preg_match( '/<[^>]+>/', $block_content, $html_element_matches, PREG_OFFSET_CAPTURE );
    64 				array( 'h2', 'color', 'gradient' ),
    65 	$first_element = $html_element_matches[0][0];
    65 				array( 'h3', 'color', 'text' ),
    66 	// If the first HTML element has a class attribute just add the new class
    66 				array( 'h3', 'color', 'background' ),
    67 	// as we do on layout and duotone.
    67 				array( 'h3', 'color', 'gradient' ),
    68 	if ( strpos( $first_element, 'class="' ) !== false ) {
    68 				array( 'h4', 'color', 'text' ),
    69 		$content = preg_replace(
    69 				array( 'h4', 'color', 'background' ),
    70 			'/' . preg_quote( 'class="', '/' ) . '/',
    70 				array( 'h4', 'color', 'gradient' ),
    71 			'class="' . $class_name . ' ',
    71 				array( 'h5', 'color', 'text' ),
    72 			$block_content,
    72 				array( 'h5', 'color', 'background' ),
    73 			1
    73 				array( 'h5', 'color', 'gradient' ),
    74 		);
    74 				array( 'h6', 'color', 'text' ),
    75 	} else {
    75 				array( 'h6', 'color', 'background' ),
    76 		// If the first HTML element has no class attribute we should inject the attribute before the attribute at the end.
    76 				array( 'h6', 'color', 'gradient' ),
    77 		$first_element_offset = $html_element_matches[0][1];
    77 			),
    78 		$content              = substr_replace( $block_content, ' class="' . $class_name . '"', $first_element_offset + strlen( $first_element ) - 1, 0 );
    78 		),
    79 	}
    79 	);
    80 
    80 
    81 	return $content;
    81 	$elements_style_attributes = $block['attrs']['style']['elements'];
    82 }
    82 
    83 
    83 	foreach ( $element_color_properties as $element_config ) {
    84 /**
    84 		if ( $element_config['skip'] ) {
    85  * Render the elements stylesheet.
    85 			continue;
       
    86 		}
       
    87 
       
    88 		foreach ( $element_config['paths'] as $path ) {
       
    89 			if ( null !== _wp_array_get( $elements_style_attributes, $path, null ) ) {
       
    90 				return true;
       
    91 			}
       
    92 		}
       
    93 	}
       
    94 
       
    95 	return false;
       
    96 }
       
    97 
       
    98 /**
       
    99  * Render the elements stylesheet and adds elements class name to block as required.
    86  *
   100  *
    87  * In the case of nested blocks we want the parent element styles to be rendered before their descendants.
   101  * In the case of nested blocks we want the parent element styles to be rendered before their descendants.
    88  * This solves the issue of an element (e.g.: link color) being styled in both the parent and a descendant:
   102  * This solves the issue of an element (e.g.: link color) being styled in both the parent and a descendant:
    89  * we want the descendant style to take priority, and this is done by loading it after, in DOM order.
   103  * we want the descendant style to take priority, and this is done by loading it after, in DOM order.
    90  *
   104  *
    91  * @since 6.0.0
   105  * @since 6.0.0
       
   106  * @since 6.1.0 Implemented the style engine to generate CSS and classnames.
       
   107  * @since 6.6.0 Element block support class and styles are generated via the `render_block_data` filter instead of `pre_render_block`.
    92  * @access private
   108  * @access private
    93  *
   109  *
    94  * @param string|null $pre_render   The pre-rendered content. Default null.
   110  * @param array $parsed_block The parsed block.
    95  * @param array       $block        The block being rendered.
   111  * @return array The same parsed block with elements classname added if appropriate.
    96  *
   112  */
    97  * @return null
   113 function wp_render_elements_support_styles( $parsed_block ) {
    98  */
       
    99 function wp_render_elements_support_styles( $pre_render, $block ) {
       
   100 	$block_type                    = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] );
       
   101 	$skip_link_color_serialization = wp_should_skip_block_supports_serialization( $block_type, 'color', 'link' );
       
   102 	if ( $skip_link_color_serialization ) {
       
   103 		return null;
       
   104 	}
       
   105 
       
   106 	$link_color = null;
       
   107 	if ( ! empty( $block['attrs'] ) ) {
       
   108 		$link_color = _wp_array_get( $block['attrs'], array( 'style', 'elements', 'link', 'color', 'text' ), null );
       
   109 	}
       
   110 
       
   111 	/*
   114 	/*
   112 	* For now we only care about link color.
   115 	 * The generation of element styles and classname were moved to the
   113 	* This code in the future when we have a public API
   116 	 * `render_block_data` filter in 6.6.0 to avoid filtered attributes
   114 	* should take advantage of WP_Theme_JSON::compute_style_properties
   117 	 * breaking the application of the elements CSS class.
   115 	* and work for any element and style.
   118 	 *
   116 	*/
   119 	 * @see https://github.com/WordPress/gutenberg/pull/59535
   117 	if ( null === $link_color ) {
   120 	 *
   118 		return null;
   121 	 * The change in filter means, the argument types for this function
   119 	}
   122 	 * have changed and require deprecating.
   120 
   123 	 */
   121 	$class_name = wp_get_elements_class_name( $block );
   124 	if ( is_string( $parsed_block ) ) {
   122 
   125 		_deprecated_argument(
   123 	if ( strpos( $link_color, 'var:preset|color|' ) !== false ) {
   126 			__FUNCTION__,
   124 		// Get the name from the string and add proper styles.
   127 			'6.6.0',
   125 		$index_to_splice = strrpos( $link_color, '|' ) + 1;
   128 			__( 'Use as a `pre_render_block` filter is deprecated. Use with `render_block_data` instead.' )
   126 		$link_color_name = substr( $link_color, $index_to_splice );
   129 		);
   127 		$link_color      = "var(--wp--preset--color--$link_color_name)";
   130 	}
   128 	}
   131 
   129 	$link_color_declaration = esc_html( safecss_filter_attr( "color: $link_color" ) );
   132 	$block_type           = WP_Block_Type_Registry::get_instance()->get_registered( $parsed_block['blockName'] );
   130 
   133 	$element_block_styles = isset( $parsed_block['attrs']['style']['elements'] ) ? $parsed_block['attrs']['style']['elements'] : null;
   131 	$style = ".$class_name a{" . $link_color_declaration . ';}';
   134 
   132 
   135 	if ( ! $element_block_styles ) {
   133 	wp_enqueue_block_support_styles( $style );
   136 		return $parsed_block;
   134 
   137 	}
   135 	return null;
   138 
   136 }
   139 	$skip_link_color_serialization         = wp_should_skip_block_supports_serialization( $block_type, 'color', 'link' );
   137 
   140 	$skip_heading_color_serialization      = wp_should_skip_block_supports_serialization( $block_type, 'color', 'heading' );
   138 add_filter( 'render_block', 'wp_render_elements_support', 10, 2 );
   141 	$skip_button_color_serialization       = wp_should_skip_block_supports_serialization( $block_type, 'color', 'button' );
   139 add_filter( 'pre_render_block', 'wp_render_elements_support_styles', 10, 2 );
   142 	$skips_all_element_color_serialization = $skip_link_color_serialization &&
       
   143 		$skip_heading_color_serialization &&
       
   144 		$skip_button_color_serialization;
       
   145 
       
   146 	if ( $skips_all_element_color_serialization ) {
       
   147 		return $parsed_block;
       
   148 	}
       
   149 
       
   150 	$options = array(
       
   151 		'button'  => array( 'skip' => $skip_button_color_serialization ),
       
   152 		'link'    => array( 'skip' => $skip_link_color_serialization ),
       
   153 		'heading' => array( 'skip' => $skip_heading_color_serialization ),
       
   154 	);
       
   155 
       
   156 	if ( ! wp_should_add_elements_class_name( $parsed_block, $options ) ) {
       
   157 		return $parsed_block;
       
   158 	}
       
   159 
       
   160 	$class_name         = wp_get_elements_class_name( $parsed_block );
       
   161 	$updated_class_name = isset( $parsed_block['attrs']['className'] ) ? $parsed_block['attrs']['className'] . " $class_name" : $class_name;
       
   162 
       
   163 	_wp_array_set( $parsed_block, array( 'attrs', 'className' ), $updated_class_name );
       
   164 
       
   165 	// Generate element styles based on selector and store in style engine for enqueuing.
       
   166 	$element_types = array(
       
   167 		'button'  => array(
       
   168 			'selector' => ".$class_name .wp-element-button, .$class_name .wp-block-button__link",
       
   169 			'skip'     => $skip_button_color_serialization,
       
   170 		),
       
   171 		'link'    => array(
       
   172 			'selector'       => ".$class_name a:where(:not(.wp-element-button))",
       
   173 			'hover_selector' => ".$class_name a:where(:not(.wp-element-button)):hover",
       
   174 			'skip'           => $skip_link_color_serialization,
       
   175 		),
       
   176 		'heading' => array(
       
   177 			'selector' => ".$class_name h1, .$class_name h2, .$class_name h3, .$class_name h4, .$class_name h5, .$class_name h6",
       
   178 			'skip'     => $skip_heading_color_serialization,
       
   179 			'elements' => array( 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' ),
       
   180 		),
       
   181 	);
       
   182 
       
   183 	foreach ( $element_types as $element_type => $element_config ) {
       
   184 		if ( $element_config['skip'] ) {
       
   185 			continue;
       
   186 		}
       
   187 
       
   188 		$element_style_object = isset( $element_block_styles[ $element_type ] ) ? $element_block_styles[ $element_type ] : null;
       
   189 
       
   190 		// Process primary element type styles.
       
   191 		if ( $element_style_object ) {
       
   192 			wp_style_engine_get_styles(
       
   193 				$element_style_object,
       
   194 				array(
       
   195 					'selector' => $element_config['selector'],
       
   196 					'context'  => 'block-supports',
       
   197 				)
       
   198 			);
       
   199 
       
   200 			if ( isset( $element_style_object[':hover'] ) ) {
       
   201 				wp_style_engine_get_styles(
       
   202 					$element_style_object[':hover'],
       
   203 					array(
       
   204 						'selector' => $element_config['hover_selector'],
       
   205 						'context'  => 'block-supports',
       
   206 					)
       
   207 				);
       
   208 			}
       
   209 		}
       
   210 
       
   211 		// Process related elements e.g. h1-h6 for headings.
       
   212 		if ( isset( $element_config['elements'] ) ) {
       
   213 			foreach ( $element_config['elements'] as $element ) {
       
   214 				$element_style_object = isset( $element_block_styles[ $element ] )
       
   215 					? $element_block_styles[ $element ]
       
   216 					: null;
       
   217 
       
   218 				if ( $element_style_object ) {
       
   219 					wp_style_engine_get_styles(
       
   220 						$element_style_object,
       
   221 						array(
       
   222 							'selector' => ".$class_name $element",
       
   223 							'context'  => 'block-supports',
       
   224 						)
       
   225 					);
       
   226 				}
       
   227 			}
       
   228 		}
       
   229 	}
       
   230 
       
   231 	return $parsed_block;
       
   232 }
       
   233 
       
   234 /**
       
   235  * Ensure the elements block support class name generated, and added to
       
   236  * block attributes, in the `render_block_data` filter gets applied to the
       
   237  * block's markup.
       
   238  *
       
   239  * @see wp_render_elements_support_styles
       
   240  * @since 6.6.0
       
   241  *
       
   242  * @param  string $block_content Rendered block content.
       
   243  * @param  array  $block         Block object.
       
   244  * @return string                Filtered block content.
       
   245  */
       
   246 function wp_render_elements_class_name( $block_content, $block ) {
       
   247 	$class_string = $block['attrs']['className'] ?? '';
       
   248 	preg_match( '/\bwp-elements-\S+\b/', $class_string, $matches );
       
   249 
       
   250 	if ( empty( $matches ) ) {
       
   251 		return $block_content;
       
   252 	}
       
   253 
       
   254 	$tags = new WP_HTML_Tag_Processor( $block_content );
       
   255 
       
   256 	if ( $tags->next_tag() ) {
       
   257 		$tags->add_class( $matches[0] );
       
   258 	}
       
   259 
       
   260 	return $tags->get_updated_html();
       
   261 }
       
   262 
       
   263 add_filter( 'render_block', 'wp_render_elements_class_name', 10, 2 );
       
   264 add_filter( 'render_block_data', 'wp_render_elements_support_styles', 10, 1 );