PluginProbe
Gutenberg / 24.0.0
Gutenberg v24.0.0
24.0.0 23.9.1 23.9.0 23.8.0 23.7.2 23.7.1 23.7.0 23.6.1 23.6.2 23.6.0 23.5.3 23.5.2 23.5.1 23.5.0 23.4.0 23.3.2 23.3.1 23.3.0 23.2.0 23.2.1 23.2.2 23.1.1 23.1.0 23.0.1 12.6.0 All 403 releases
← All changes | lib/block-supports/elements.php +258 -55 12.6.024.0.0 View file →
@@ -5,79 +5,282 @@
5 5 * @package gutenberg
6 6 */
7 7
8 8 /**
9 - * Render the elements stylesheet.
9 + * Update the block content with elements class names.
10 10 *
11 - * @param string $block_content Rendered block content.
12 - * @param array $block Block object.
13 - * @return string Filtered block content.
11 + * @deprecated 6.6.0 Use `gutenberg_render_elements_class_name` instead.
12 + *
13 + * @param string $block_content Rendered block content.
14 + * @return string Filtered block content.
14 15 */
15 -function gutenberg_render_elements_support( $block_content, $block ) {
16 +function gutenberg_render_elements_support( $block_content ) {
17 + _deprecated_function( __FUNCTION__, '6.6.0', 'gutenberg_render_elements_class_name' );
18 + return $block_content;
19 +}
16 20
17 - if ( ! $block_content ) {
18 - return $block_content;
21 +/**
22 + * Determines whether an elements class name should be added to the block.
23 + *
24 + * @param array $block Block object.
25 + * @param array $options Per element type options e.g. whether to skip serialization.
26 + *
27 + * @return boolean Whether the block needs an elements class name.
28 + */
29 +function gutenberg_should_add_elements_class_name( $block, $options ) {
30 + if ( ! isset( $block['attrs']['style']['elements'] ) ) {
31 + return false;
19 32 }
20 33
21 - $link_color = null;
22 - if ( ! empty( $block['attrs'] ) ) {
23 - $link_color = _wp_array_get( $block['attrs'], array( 'style', 'elements', 'link', 'color', 'text' ), null );
34 + $element_color_properties = array(
35 + 'button' => array(
36 + 'skip' => $options['button']['skip'] ?? false,
37 + 'paths' => array(
38 + array( 'button', 'color', 'text' ),
39 + array( 'button', 'color', 'background' ),
40 + array( 'button', 'color', 'gradient' ),
41 + ),
42 + ),
43 + 'link' => array(
44 + 'skip' => $options['link']['skip'] ?? false,
45 + 'paths' => array(
46 + array( 'link', 'color', 'text' ),
47 + array( 'link', ':hover', 'color', 'text' ),
48 + ),
49 + ),
50 + 'heading' => array(
51 + 'skip' => $options['heading']['skip'] ?? false,
52 + 'paths' => array(
53 + array( 'heading', 'color', 'text' ),
54 + array( 'heading', 'color', 'background' ),
55 + array( 'heading', 'color', 'gradient' ),
56 + array( 'h1', 'color', 'text' ),
57 + array( 'h1', 'color', 'background' ),
58 + array( 'h1', 'color', 'gradient' ),
59 + array( 'h2', 'color', 'text' ),
60 + array( 'h2', 'color', 'background' ),
61 + array( 'h2', 'color', 'gradient' ),
62 + array( 'h3', 'color', 'text' ),
63 + array( 'h3', 'color', 'background' ),
64 + array( 'h3', 'color', 'gradient' ),
65 + array( 'h4', 'color', 'text' ),
66 + array( 'h4', 'color', 'background' ),
67 + array( 'h4', 'color', 'gradient' ),
68 + array( 'h5', 'color', 'text' ),
69 + array( 'h5', 'color', 'background' ),
70 + array( 'h5', 'color', 'gradient' ),
71 + array( 'h6', 'color', 'text' ),
72 + array( 'h6', 'color', 'background' ),
73 + array( 'h6', 'color', 'gradient' ),
74 + ),
75 + ),
76 + );
77 +
78 + $elements_style_attributes = $block['attrs']['style']['elements'];
79 +
80 + foreach ( $element_color_properties as $element_config ) {
81 + if ( $element_config['skip'] ) {
82 + continue;
83 + }
84 +
85 + foreach ( $element_config['paths'] as $path ) {
86 + if ( null !== _wp_array_get( $elements_style_attributes, $path, null ) ) {
87 + return true;
88 + }
89 + }
24 90 }
25 91
92 + return false;
93 +}
94 +
95 +/**
96 + * Render the elements stylesheet and adds elements class name to block as required.
97 + *
98 + * In the case of nested blocks we want the parent element styles to be rendered before their descendants.
99 + * This solves the issue of an element (e.g.: link color) being styled in both the parent and a descendant:
100 + * we want the descendant style to take priority, and this is done by loading it after, in DOM order.
101 + *
102 + * @since 6.6.0 Element block support class and styles are generated via the `render_block_data` filter instead of `pre_render_block`
103 + *
104 + * @param array $parsed_block The parsed block.
105 + *
106 + * @return array The same parsed block with elements classname added if appropriate.
107 + */
108 +function gutenberg_render_elements_support_styles( $parsed_block ) {
26 109 /*
27 - * For now we only care about link color.
28 - * This code in the future when we have a public API
29 - * should take advantage of WP_Theme_JSON_Gutenberg::compute_style_properties
30 - * and work for any element and style.
31 - */
32 - if ( null === $link_color ) {
33 - return $block_content;
110 + * The generation of element styles and classname were moved to the
111 + * `render_block_data` filter in 6.6.0 to avoid filtered attributes
112 + * breaking the application of the elements CSS class.
113 + *
114 + * @link https://github.com/WordPress/gutenberg/pull/59535
115 + *
116 + * The change in filter means, the argument types for this function
117 + * have changed and require deprecating.
118 + */
119 + if ( is_string( $parsed_block ) ) {
120 + _deprecated_argument(
121 + __FUNCTION__,
122 + '6.6.0',
123 + __( 'Use as a `pre_render_block` filter is deprecated. Use with `render_block_data` instead.', 'gutenberg' )
124 + );
34 125 }
35 126
36 - $class_name = 'wp-elements-' . uniqid();
127 + $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $parsed_block['blockName'] );
128 + $element_block_styles = $parsed_block['attrs']['style']['elements'] ?? null;
37 129
38 - if ( strpos( $link_color, 'var:preset|color|' ) !== false ) {
39 - // Get the name from the string and add proper styles.
40 - $index_to_splice = strrpos( $link_color, '|' ) + 1;
41 - $link_color_name = substr( $link_color, $index_to_splice );
42 - $link_color = "var(--wp--preset--color--$link_color_name)";
130 + if ( ! $element_block_styles ) {
131 + return $parsed_block;
43 132 }
44 - $link_color_declaration = esc_html( safecss_filter_attr( "color: $link_color" ) );
45 133
46 - $style = "<style>.$class_name a{" . $link_color_declaration . ";}</style>\n";
134 + $skip_link_color_serialization = wp_should_skip_block_supports_serialization( $block_type, 'color', 'link' );
135 + $skip_heading_color_serialization = wp_should_skip_block_supports_serialization( $block_type, 'color', 'heading' );
136 + $skip_button_color_serialization = wp_should_skip_block_supports_serialization( $block_type, 'color', 'button' );
137 + $skips_all_element_color_serialization = $skip_link_color_serialization &&
138 + $skip_heading_color_serialization &&
139 + $skip_button_color_serialization;
47 140
48 - // Like the layout hook this assumes the hook only applies to blocks with a single wrapper.
49 - // Retrieve the opening tag of the first HTML element.
50 - $html_element_matches = array();
51 - preg_match( '/<[^>]+>/', $block_content, $html_element_matches, PREG_OFFSET_CAPTURE );
52 - $first_element = $html_element_matches[0][0];
53 - // If the first HTML element has a class attribute just add the new class
54 - // as we do on layout and duotone.
55 - if ( strpos( $first_element, 'class="' ) !== false ) {
56 - $content = preg_replace(
57 - '/' . preg_quote( 'class="', '/' ) . '/',
58 - 'class="' . $class_name . ' ',
59 - $block_content,
60 - 1
61 - );
62 - } else {
63 - // If the first HTML element has no class attribute we should inject the attribute before the attribute at the end.
64 - $first_element_offset = $html_element_matches[0][1];
65 - $content = substr_replace( $block_content, ' class="' . $class_name . '"', $first_element_offset + strlen( $first_element ) - 1, 0 );
141 + if ( $skips_all_element_color_serialization ) {
142 + return $parsed_block;
66 143 }
67 144
68 - // Ideally styles should be loaded in the head, but blocks may be parsed
69 - // after that, so loading in the footer for now.
70 - // See https://core.trac.wordpress.org/ticket/53494.
71 - add_action(
72 - 'wp_footer',
73 - function () use ( $style ) {
74 - echo $style;
145 + $options = array(
146 + 'button' => array( 'skip' => $skip_button_color_serialization ),
147 + 'link' => array( 'skip' => $skip_link_color_serialization ),
148 + 'heading' => array( 'skip' => $skip_heading_color_serialization ),
149 + );
150 +
151 + if ( ! gutenberg_should_add_elements_class_name( $parsed_block, $options ) ) {
152 + return $parsed_block;
153 + }
154 +
155 + $class_name = wp_get_elements_class_name( $parsed_block );
156 + $updated_class_name = isset( $parsed_block['attrs']['className'] ) ? $parsed_block['attrs']['className'] . " $class_name" : $class_name;
157 +
158 + _wp_array_set( $parsed_block, array( 'attrs', 'className' ), $updated_class_name );
159 +
160 + // Generate element styles based on selector and store in style engine for enqueuing.
161 + $element_types = array(
162 + 'button' => array(
163 + 'selector' => ".$class_name .wp-element-button, .$class_name .wp-block-button__link",
164 + 'skip' => $skip_button_color_serialization,
165 + ),
166 + 'link' => array(
167 + // :where(:not) matches theme.json selector.
168 + 'selector' => ".$class_name a:where(:not(.wp-element-button))",
169 + 'hover_selector' => ".$class_name a:where(:not(.wp-element-button)):hover",
170 + 'skip' => $skip_link_color_serialization,
171 + ),
172 + 'heading' => array(
173 + 'selector' => ".$class_name h1, .$class_name h2, .$class_name h3, .$class_name h4, .$class_name h5, .$class_name h6",
174 + 'skip' => $skip_heading_color_serialization,
175 + 'elements' => array( 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' ),
176 + ),
177 + );
178 +
179 + foreach ( $element_types as $element_type => $element_config ) {
180 + if ( $element_config['skip'] ) {
181 + continue;
75 182 }
76 - );
77 183
78 - return $content;
184 + $element_style_object = _wp_array_get( $element_block_styles, array( $element_type ), null );
185 +
186 + // Process primary element type styles.
187 + if ( $element_style_object ) {
188 + gutenberg_style_engine_get_styles(
189 + $element_style_object,
190 + array(
191 + 'selector' => $element_config['selector'],
192 + 'context' => 'block-supports',
193 + )
194 + );
195 +
196 + if ( isset( $element_style_object[':hover'], $element_config['hover_selector'] ) ) {
197 + gutenberg_style_engine_get_styles(
198 + $element_style_object[':hover'],
199 + array(
200 + 'selector' => $element_config['hover_selector'],
201 + 'context' => 'block-supports',
202 + )
203 + );
204 + }
205 + }
206 +
207 + // Process related elements e.g. h1-h6 for headings.
208 + if ( isset( $element_config['elements'] ) ) {
209 + foreach ( $element_config['elements'] as $element ) {
210 + $element_style_object = _wp_array_get( $element_block_styles, array( $element ), null );
211 +
212 + if ( $element_style_object ) {
213 + gutenberg_style_engine_get_styles(
214 + $element_style_object,
215 + array(
216 + 'selector' => ".$class_name $element",
217 + 'context' => 'block-supports',
218 + )
219 + );
220 + }
221 + }
222 + }
223 + }
224 +
225 + return $parsed_block;
79 226 }
80 227
81 -// Remove WordPress core filter to avoid rendering duplicate elements stylesheet.
82 -remove_filter( 'render_block', 'wp_render_elements_support', 10, 2 );
83 -add_filter( 'render_block', 'gutenberg_render_elements_support', 10, 2 );
228 +/**
229 + * Ensure the elements block support class name generated, and added to
230 + * block attributes, in the `render_block_data` filter gets applied to the
231 + * block's markup.
232 + *
233 + * @see gutenberg_render_elements_support_styles
234 + *
235 + * @param string $block_content Rendered block content.
236 + * @param array $block Block object.
237 + * @return string Filtered block content.
238 + *
239 + * @phpstan-param array{
240 + * attrs: array{
241 + * className?: string,
242 + * ...
243 + * },
244 + * ...
245 + * } $block
246 + */
247 +function gutenberg_render_elements_class_name( $block_content, $block ) {
248 + $class_name_attr = $block['attrs']['className'] ?? null;
249 + $class_name_prefix = 'wp-elements-';
250 + if ( ! is_string( $class_name_attr ) || ! str_contains( $class_name_attr, $class_name_prefix ) ) {
251 + return $block_content;
252 + }
253 +
254 + // Parse out the 'wp-elements-*' class name.
255 + $matched_class_name = null;
256 + $token_delimiter = " \t\f\r\n";
257 + $class_token = strtok( $class_name_attr, $token_delimiter );
258 + while ( false !== $class_token ) {
259 + if ( str_starts_with( $class_token, $class_name_prefix ) ) {
260 + $matched_class_name = $class_token;
261 + break;
262 + }
263 + $class_token = strtok( $token_delimiter );
264 + }
265 + if ( null === $matched_class_name ) {
266 + return $block_content;
267 + }
268 +
269 + $tags = new WP_HTML_Tag_Processor( $block_content );
270 + if ( $tags->next_tag() ) {
271 + $tags->add_class( $matched_class_name );
272 + }
273 +
274 + return $tags->get_updated_html();
275 +}
276 +
277 +// Remove deprecated WordPress core filters.
278 +remove_filter( 'render_block', 'wp_render_elements_support', 10 );
279 +remove_filter( 'pre_render_block', 'wp_render_elements_support_styles', 10 );
280 +
281 +// Remove WordPress core filters to avoid rendering duplicate elements stylesheet & attaching classes twice.
282 +remove_filter( 'render_block', 'wp_render_elements_class_name', 10 );
283 +remove_filter( 'render_block_data', 'wp_render_elements_support_styles', 10 );
284 +
285 +add_filter( 'render_block', 'gutenberg_render_elements_class_name', 10, 2 );
286 +add_filter( 'render_block_data', 'gutenberg_render_elements_support_styles', 10, 1 );