|
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 */ |
|
29 #[AllowDynamicProperties] |
|
30 final class WP_Style_Engine { |
|
31 /** |
|
32 * Style definitions that contain the instructions to parse/output valid Gutenberg styles from a block's attributes. |
|
33 * |
|
34 * For every style definition, the following properties are valid: |
|
35 * |
|
36 * - classnames => (array) an array of classnames to be returned for block styles. The key is a classname or pattern. |
|
37 * A value of `true` means the classname should be applied always. Otherwise, a valid CSS property (string) |
|
38 * to match the incoming value, e.g., "color" to match var:preset|color|somePresetSlug. |
|
39 * - css_vars => (array) an array of key value pairs used to generate CSS var values. |
|
40 * The key should be the CSS property name that matches the second element of the preset string value, |
|
41 * i.e., "color" in var:preset|color|somePresetSlug. The value is a CSS var pattern (e.g. `--wp--preset--color--$slug`), |
|
42 * whose `$slug` fragment will be replaced with the preset slug, which is the third element of the preset string value, |
|
43 * i.e., `somePresetSlug` in var:preset|color|somePresetSlug. |
|
44 * - property_keys => (array) array of keys whose values represent a valid CSS property, e.g., "margin" or "border". |
|
45 * - path => (array) a path that accesses the corresponding style value in the block style object. |
|
46 * - 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", ... )`. |
|
47 * |
|
48 * @since 6.1.0 |
|
49 * @var array |
|
50 */ |
|
51 const BLOCK_STYLE_DEFINITIONS_METADATA = array( |
|
52 'background' => array( |
|
53 'backgroundImage' => array( |
|
54 'property_keys' => array( |
|
55 'default' => 'background-image', |
|
56 ), |
|
57 'value_func' => array( self::class, 'get_url_or_value_css_declaration' ), |
|
58 'path' => array( 'background', 'backgroundImage' ), |
|
59 ), |
|
60 'backgroundPosition' => array( |
|
61 'property_keys' => array( |
|
62 'default' => 'background-position', |
|
63 ), |
|
64 'path' => array( 'background', 'backgroundPosition' ), |
|
65 ), |
|
66 'backgroundRepeat' => array( |
|
67 'property_keys' => array( |
|
68 'default' => 'background-repeat', |
|
69 ), |
|
70 'path' => array( 'background', 'backgroundRepeat' ), |
|
71 ), |
|
72 'backgroundSize' => array( |
|
73 'property_keys' => array( |
|
74 'default' => 'background-size', |
|
75 ), |
|
76 'path' => array( 'background', 'backgroundSize' ), |
|
77 ), |
|
78 ), |
|
79 'color' => array( |
|
80 'text' => array( |
|
81 'property_keys' => array( |
|
82 'default' => 'color', |
|
83 ), |
|
84 'path' => array( 'color', 'text' ), |
|
85 'css_vars' => array( |
|
86 'color' => '--wp--preset--color--$slug', |
|
87 ), |
|
88 'classnames' => array( |
|
89 'has-text-color' => true, |
|
90 'has-$slug-color' => 'color', |
|
91 ), |
|
92 ), |
|
93 'background' => array( |
|
94 'property_keys' => array( |
|
95 'default' => 'background-color', |
|
96 ), |
|
97 'path' => array( 'color', 'background' ), |
|
98 'css_vars' => array( |
|
99 'color' => '--wp--preset--color--$slug', |
|
100 ), |
|
101 'classnames' => array( |
|
102 'has-background' => true, |
|
103 'has-$slug-background-color' => 'color', |
|
104 ), |
|
105 ), |
|
106 'gradient' => array( |
|
107 'property_keys' => array( |
|
108 'default' => 'background', |
|
109 ), |
|
110 'path' => array( 'color', 'gradient' ), |
|
111 'css_vars' => array( |
|
112 'gradient' => '--wp--preset--gradient--$slug', |
|
113 ), |
|
114 'classnames' => array( |
|
115 'has-background' => true, |
|
116 'has-$slug-gradient-background' => 'gradient', |
|
117 ), |
|
118 ), |
|
119 ), |
|
120 'border' => array( |
|
121 'color' => array( |
|
122 'property_keys' => array( |
|
123 'default' => 'border-color', |
|
124 'individual' => 'border-%s-color', |
|
125 ), |
|
126 'path' => array( 'border', 'color' ), |
|
127 'classnames' => array( |
|
128 'has-border-color' => true, |
|
129 'has-$slug-border-color' => 'color', |
|
130 ), |
|
131 ), |
|
132 'radius' => array( |
|
133 'property_keys' => array( |
|
134 'default' => 'border-radius', |
|
135 'individual' => 'border-%s-radius', |
|
136 ), |
|
137 'path' => array( 'border', 'radius' ), |
|
138 ), |
|
139 'style' => array( |
|
140 'property_keys' => array( |
|
141 'default' => 'border-style', |
|
142 'individual' => 'border-%s-style', |
|
143 ), |
|
144 'path' => array( 'border', 'style' ), |
|
145 ), |
|
146 'width' => array( |
|
147 'property_keys' => array( |
|
148 'default' => 'border-width', |
|
149 'individual' => 'border-%s-width', |
|
150 ), |
|
151 'path' => array( 'border', 'width' ), |
|
152 ), |
|
153 'top' => array( |
|
154 'value_func' => array( self::class, 'get_individual_property_css_declarations' ), |
|
155 'path' => array( 'border', 'top' ), |
|
156 'css_vars' => array( |
|
157 'color' => '--wp--preset--color--$slug', |
|
158 ), |
|
159 ), |
|
160 'right' => array( |
|
161 'value_func' => array( self::class, 'get_individual_property_css_declarations' ), |
|
162 'path' => array( 'border', 'right' ), |
|
163 'css_vars' => array( |
|
164 'color' => '--wp--preset--color--$slug', |
|
165 ), |
|
166 ), |
|
167 'bottom' => array( |
|
168 'value_func' => array( self::class, 'get_individual_property_css_declarations' ), |
|
169 'path' => array( 'border', 'bottom' ), |
|
170 'css_vars' => array( |
|
171 'color' => '--wp--preset--color--$slug', |
|
172 ), |
|
173 ), |
|
174 'left' => array( |
|
175 'value_func' => array( self::class, 'get_individual_property_css_declarations' ), |
|
176 'path' => array( 'border', 'left' ), |
|
177 'css_vars' => array( |
|
178 'color' => '--wp--preset--color--$slug', |
|
179 ), |
|
180 ), |
|
181 ), |
|
182 'shadow' => array( |
|
183 'shadow' => array( |
|
184 'property_keys' => array( |
|
185 'default' => 'box-shadow', |
|
186 ), |
|
187 'path' => array( 'shadow' ), |
|
188 'css_vars' => array( |
|
189 'shadow' => '--wp--preset--shadow--$slug', |
|
190 ), |
|
191 ), |
|
192 ), |
|
193 'dimensions' => array( |
|
194 'aspectRatio' => array( |
|
195 'property_keys' => array( |
|
196 'default' => 'aspect-ratio', |
|
197 ), |
|
198 'path' => array( 'dimensions', 'aspectRatio' ), |
|
199 'classnames' => array( |
|
200 'has-aspect-ratio' => true, |
|
201 ), |
|
202 ), |
|
203 'minHeight' => array( |
|
204 'property_keys' => array( |
|
205 'default' => 'min-height', |
|
206 ), |
|
207 'path' => array( 'dimensions', 'minHeight' ), |
|
208 'css_vars' => array( |
|
209 'spacing' => '--wp--preset--spacing--$slug', |
|
210 ), |
|
211 ), |
|
212 ), |
|
213 'spacing' => array( |
|
214 'padding' => array( |
|
215 'property_keys' => array( |
|
216 'default' => 'padding', |
|
217 'individual' => 'padding-%s', |
|
218 ), |
|
219 'path' => array( 'spacing', 'padding' ), |
|
220 'css_vars' => array( |
|
221 'spacing' => '--wp--preset--spacing--$slug', |
|
222 ), |
|
223 ), |
|
224 'margin' => array( |
|
225 'property_keys' => array( |
|
226 'default' => 'margin', |
|
227 'individual' => 'margin-%s', |
|
228 ), |
|
229 'path' => array( 'spacing', 'margin' ), |
|
230 'css_vars' => array( |
|
231 'spacing' => '--wp--preset--spacing--$slug', |
|
232 ), |
|
233 ), |
|
234 ), |
|
235 'typography' => array( |
|
236 'fontSize' => array( |
|
237 'property_keys' => array( |
|
238 'default' => 'font-size', |
|
239 ), |
|
240 'path' => array( 'typography', 'fontSize' ), |
|
241 'css_vars' => array( |
|
242 'font-size' => '--wp--preset--font-size--$slug', |
|
243 ), |
|
244 'classnames' => array( |
|
245 'has-$slug-font-size' => 'font-size', |
|
246 ), |
|
247 ), |
|
248 'fontFamily' => array( |
|
249 'property_keys' => array( |
|
250 'default' => 'font-family', |
|
251 ), |
|
252 'css_vars' => array( |
|
253 'font-family' => '--wp--preset--font-family--$slug', |
|
254 ), |
|
255 'path' => array( 'typography', 'fontFamily' ), |
|
256 'classnames' => array( |
|
257 'has-$slug-font-family' => 'font-family', |
|
258 ), |
|
259 ), |
|
260 'fontStyle' => array( |
|
261 'property_keys' => array( |
|
262 'default' => 'font-style', |
|
263 ), |
|
264 'path' => array( 'typography', 'fontStyle' ), |
|
265 ), |
|
266 'fontWeight' => array( |
|
267 'property_keys' => array( |
|
268 'default' => 'font-weight', |
|
269 ), |
|
270 'path' => array( 'typography', 'fontWeight' ), |
|
271 ), |
|
272 'lineHeight' => array( |
|
273 'property_keys' => array( |
|
274 'default' => 'line-height', |
|
275 ), |
|
276 'path' => array( 'typography', 'lineHeight' ), |
|
277 ), |
|
278 'textColumns' => array( |
|
279 'property_keys' => array( |
|
280 'default' => 'column-count', |
|
281 ), |
|
282 'path' => array( 'typography', 'textColumns' ), |
|
283 ), |
|
284 'textDecoration' => array( |
|
285 'property_keys' => array( |
|
286 'default' => 'text-decoration', |
|
287 ), |
|
288 'path' => array( 'typography', 'textDecoration' ), |
|
289 ), |
|
290 'textTransform' => array( |
|
291 'property_keys' => array( |
|
292 'default' => 'text-transform', |
|
293 ), |
|
294 'path' => array( 'typography', 'textTransform' ), |
|
295 ), |
|
296 'letterSpacing' => array( |
|
297 'property_keys' => array( |
|
298 'default' => 'letter-spacing', |
|
299 ), |
|
300 'path' => array( 'typography', 'letterSpacing' ), |
|
301 ), |
|
302 ), |
|
303 ); |
|
304 |
|
305 /** |
|
306 * Util: Extracts the slug in kebab case from a preset string, |
|
307 * e.g. `heavenly-blue` from `var:preset|color|heavenlyBlue`. |
|
308 * |
|
309 * @since 6.1.0 |
|
310 * |
|
311 * @param string $style_value A single CSS preset value. |
|
312 * @param string $property_key The CSS property that is the second element of the preset string. |
|
313 * Used for matching. |
|
314 * @return string The slug, or empty string if not found. |
|
315 */ |
|
316 protected static function get_slug_from_preset_value( $style_value, $property_key ) { |
|
317 if ( is_string( $style_value ) && is_string( $property_key ) |
|
318 && str_contains( $style_value, "var:preset|{$property_key}|" ) |
|
319 ) { |
|
320 $index_to_splice = strrpos( $style_value, '|' ) + 1; |
|
321 return _wp_to_kebab_case( substr( $style_value, $index_to_splice ) ); |
|
322 } |
|
323 return ''; |
|
324 } |
|
325 |
|
326 /** |
|
327 * Util: Generates a CSS var string, e.g. `var(--wp--preset--color--background)` |
|
328 * from a preset string such as `var:preset|space|50`. |
|
329 * |
|
330 * @since 6.1.0 |
|
331 * |
|
332 * @param string $style_value A single CSS preset value. |
|
333 * @param string[] $css_vars An associate array of CSS var patterns |
|
334 * used to generate the var string. |
|
335 * @return string The CSS var, or an empty string if no match for slug found. |
|
336 */ |
|
337 protected static function get_css_var_value( $style_value, $css_vars ) { |
|
338 foreach ( $css_vars as $property_key => $css_var_pattern ) { |
|
339 $slug = static::get_slug_from_preset_value( $style_value, $property_key ); |
|
340 if ( static::is_valid_style_value( $slug ) ) { |
|
341 $var = strtr( |
|
342 $css_var_pattern, |
|
343 array( '$slug' => $slug ) |
|
344 ); |
|
345 return "var($var)"; |
|
346 } |
|
347 } |
|
348 return ''; |
|
349 } |
|
350 |
|
351 /** |
|
352 * Util: Checks whether an incoming block style value is valid. |
|
353 * |
|
354 * @since 6.1.0 |
|
355 * |
|
356 * @param string $style_value A single CSS preset value. |
|
357 * @return bool |
|
358 */ |
|
359 protected static function is_valid_style_value( $style_value ) { |
|
360 return '0' === $style_value || ! empty( $style_value ); |
|
361 } |
|
362 |
|
363 /** |
|
364 * Stores a CSS rule using the provided CSS selector and CSS declarations. |
|
365 * |
|
366 * @since 6.1.0 |
|
367 * @since 6.6.0 Added the `$rules_group` parameter. |
|
368 * |
|
369 * @param string $store_name A valid store key. |
|
370 * @param string $css_selector When a selector is passed, the function will return |
|
371 * a full CSS rule `$selector { ...rules }` |
|
372 * otherwise a concatenated string of properties and values. |
|
373 * @param string[] $css_declarations An associative array of CSS definitions, |
|
374 * e.g. `array( "$property" => "$value", "$property" => "$value" )`. |
|
375 * @param string $rules_group Optional. A parent CSS selector in the case of nested CSS, or a CSS nested @rule, |
|
376 * such as `@media (min-width: 80rem)` or `@layer module`. |
|
377 */ |
|
378 public static function store_css_rule( $store_name, $css_selector, $css_declarations, $rules_group = '' ) { |
|
379 if ( empty( $store_name ) || empty( $css_selector ) || empty( $css_declarations ) ) { |
|
380 return; |
|
381 } |
|
382 static::get_store( $store_name )->add_rule( $css_selector, $rules_group )->add_declarations( $css_declarations ); |
|
383 } |
|
384 |
|
385 /** |
|
386 * Returns a store by store key. |
|
387 * |
|
388 * @since 6.1.0 |
|
389 * |
|
390 * @param string $store_name A store key. |
|
391 * @return WP_Style_Engine_CSS_Rules_Store|null |
|
392 */ |
|
393 public static function get_store( $store_name ) { |
|
394 return WP_Style_Engine_CSS_Rules_Store::get_store( $store_name ); |
|
395 } |
|
396 |
|
397 /** |
|
398 * Returns classnames and CSS based on the values in a styles object. |
|
399 * |
|
400 * Return values are parsed based on the instructions in BLOCK_STYLE_DEFINITIONS_METADATA. |
|
401 * |
|
402 * @since 6.1.0 |
|
403 * |
|
404 * @param array $block_styles The style object. |
|
405 * @param array $options { |
|
406 * Optional. An array of options. Default empty array. |
|
407 * |
|
408 * @type bool $convert_vars_to_classnames Whether to skip converting incoming CSS var patterns, |
|
409 * e.g. `var:preset|<PRESET_TYPE>|<PRESET_SLUG>`, |
|
410 * to `var( --wp--preset--* )` values. Default false. |
|
411 * @type string $selector Optional. When a selector is passed, |
|
412 * the value of `$css` in the return value will comprise |
|
413 * a full CSS rule `$selector { ...$css_declarations }`, |
|
414 * otherwise, the value will be a concatenated string |
|
415 * of CSS declarations. |
|
416 * } |
|
417 * @return array { |
|
418 * @type string[] $classnames Array of class names. |
|
419 * @type string[] $declarations An associative array of CSS definitions, |
|
420 * e.g. `array( "$property" => "$value", "$property" => "$value" )`. |
|
421 * } |
|
422 */ |
|
423 public static function parse_block_styles( $block_styles, $options ) { |
|
424 $parsed_styles = array( |
|
425 'classnames' => array(), |
|
426 'declarations' => array(), |
|
427 ); |
|
428 if ( empty( $block_styles ) || ! is_array( $block_styles ) ) { |
|
429 return $parsed_styles; |
|
430 } |
|
431 |
|
432 // Collect CSS and classnames. |
|
433 foreach ( static::BLOCK_STYLE_DEFINITIONS_METADATA as $definition_group_key => $definition_group_style ) { |
|
434 if ( empty( $block_styles[ $definition_group_key ] ) ) { |
|
435 continue; |
|
436 } |
|
437 foreach ( $definition_group_style as $style_definition ) { |
|
438 $style_value = _wp_array_get( $block_styles, $style_definition['path'], null ); |
|
439 |
|
440 if ( ! static::is_valid_style_value( $style_value ) ) { |
|
441 continue; |
|
442 } |
|
443 |
|
444 $parsed_styles['classnames'] = array_merge( $parsed_styles['classnames'], static::get_classnames( $style_value, $style_definition ) ); |
|
445 $parsed_styles['declarations'] = array_merge( $parsed_styles['declarations'], static::get_css_declarations( $style_value, $style_definition, $options ) ); |
|
446 } |
|
447 } |
|
448 |
|
449 return $parsed_styles; |
|
450 } |
|
451 |
|
452 /** |
|
453 * Returns classnames, and generates classname(s) from a CSS preset property pattern, |
|
454 * e.g. `var:preset|<PRESET_TYPE>|<PRESET_SLUG>`. |
|
455 * |
|
456 * @since 6.1.0 |
|
457 * |
|
458 * @param string $style_value A single raw style value or CSS preset property |
|
459 * from the `$block_styles` array. |
|
460 * @param array $style_definition A single style definition from BLOCK_STYLE_DEFINITIONS_METADATA. |
|
461 * @return string[] An array of CSS classnames, or empty array if there are none. |
|
462 */ |
|
463 protected static function get_classnames( $style_value, $style_definition ) { |
|
464 if ( empty( $style_value ) ) { |
|
465 return array(); |
|
466 } |
|
467 |
|
468 $classnames = array(); |
|
469 if ( ! empty( $style_definition['classnames'] ) ) { |
|
470 foreach ( $style_definition['classnames'] as $classname => $property_key ) { |
|
471 if ( true === $property_key ) { |
|
472 $classnames[] = $classname; |
|
473 continue; |
|
474 } |
|
475 |
|
476 $slug = static::get_slug_from_preset_value( $style_value, $property_key ); |
|
477 |
|
478 if ( $slug ) { |
|
479 /* |
|
480 * Right now we expect a classname pattern to be stored in BLOCK_STYLE_DEFINITIONS_METADATA. |
|
481 * One day, if there are no stored schemata, we could allow custom patterns or |
|
482 * generate classnames based on other properties |
|
483 * such as a path or a value or a prefix passed in options. |
|
484 */ |
|
485 $classnames[] = strtr( $classname, array( '$slug' => $slug ) ); |
|
486 } |
|
487 } |
|
488 } |
|
489 |
|
490 return $classnames; |
|
491 } |
|
492 |
|
493 /** |
|
494 * Returns an array of CSS declarations based on valid block style values. |
|
495 * |
|
496 * @since 6.1.0 |
|
497 * |
|
498 * @param mixed $style_value A single raw style value from $block_styles array. |
|
499 * @param array $style_definition A single style definition from BLOCK_STYLE_DEFINITIONS_METADATA. |
|
500 * @param array $options { |
|
501 * Optional. An array of options. Default empty array. |
|
502 * |
|
503 * @type bool $convert_vars_to_classnames Whether to skip converting incoming CSS var patterns, |
|
504 * e.g. `var:preset|<PRESET_TYPE>|<PRESET_SLUG>`, |
|
505 * to `var( --wp--preset--* )` values. Default false. |
|
506 * } |
|
507 * @return string[] An associative array of CSS definitions, e.g. `array( "$property" => "$value", "$property" => "$value" )`. |
|
508 */ |
|
509 protected static function get_css_declarations( $style_value, $style_definition, $options = array() ) { |
|
510 if ( isset( $style_definition['value_func'] ) && is_callable( $style_definition['value_func'] ) ) { |
|
511 return call_user_func( $style_definition['value_func'], $style_value, $style_definition, $options ); |
|
512 } |
|
513 |
|
514 $css_declarations = array(); |
|
515 $style_property_keys = $style_definition['property_keys']; |
|
516 $should_skip_css_vars = isset( $options['convert_vars_to_classnames'] ) && true === $options['convert_vars_to_classnames']; |
|
517 |
|
518 /* |
|
519 * Build CSS var values from `var:preset|<PRESET_TYPE>|<PRESET_SLUG>` values, e.g, `var(--wp--css--rule-slug )`. |
|
520 * Check if the value is a CSS preset and there's a corresponding css_var pattern in the style definition. |
|
521 */ |
|
522 if ( is_string( $style_value ) && str_contains( $style_value, 'var:' ) ) { |
|
523 if ( ! $should_skip_css_vars && ! empty( $style_definition['css_vars'] ) ) { |
|
524 $css_var = static::get_css_var_value( $style_value, $style_definition['css_vars'] ); |
|
525 if ( static::is_valid_style_value( $css_var ) ) { |
|
526 $css_declarations[ $style_property_keys['default'] ] = $css_var; |
|
527 } |
|
528 } |
|
529 return $css_declarations; |
|
530 } |
|
531 |
|
532 /* |
|
533 * Default rule builder. |
|
534 * If the input contains an array, assume box model-like properties |
|
535 * for styles such as margins and padding. |
|
536 */ |
|
537 if ( is_array( $style_value ) ) { |
|
538 // Bail out early if the `'individual'` property is not defined. |
|
539 if ( ! isset( $style_property_keys['individual'] ) ) { |
|
540 return $css_declarations; |
|
541 } |
|
542 |
|
543 foreach ( $style_value as $key => $value ) { |
|
544 if ( is_string( $value ) && str_contains( $value, 'var:' ) && ! $should_skip_css_vars && ! empty( $style_definition['css_vars'] ) ) { |
|
545 $value = static::get_css_var_value( $value, $style_definition['css_vars'] ); |
|
546 } |
|
547 |
|
548 $individual_property = sprintf( $style_property_keys['individual'], _wp_to_kebab_case( $key ) ); |
|
549 |
|
550 if ( $individual_property && static::is_valid_style_value( $value ) ) { |
|
551 $css_declarations[ $individual_property ] = $value; |
|
552 } |
|
553 } |
|
554 |
|
555 return $css_declarations; |
|
556 } |
|
557 |
|
558 $css_declarations[ $style_property_keys['default'] ] = $style_value; |
|
559 return $css_declarations; |
|
560 } |
|
561 |
|
562 /** |
|
563 * Style value parser that returns a CSS definition array comprising style properties |
|
564 * that have keys representing individual style properties, otherwise known as longhand CSS properties. |
|
565 * |
|
566 * Example: |
|
567 * |
|
568 * "$style_property-$individual_feature: $value;" |
|
569 * |
|
570 * Which could represent the following: |
|
571 * |
|
572 * "border-{top|right|bottom|left}-{color|width|style}: {value};" |
|
573 * |
|
574 * or: |
|
575 * |
|
576 * "border-image-{outset|source|width|repeat|slice}: {value};" |
|
577 * |
|
578 * @since 6.1.0 |
|
579 * |
|
580 * @param array $style_value A single raw style value from `$block_styles` array. |
|
581 * @param array $individual_property_definition A single style definition from BLOCK_STYLE_DEFINITIONS_METADATA |
|
582 * representing an individual property of a CSS property, |
|
583 * e.g. 'top' in 'border-top'. |
|
584 * @param array $options { |
|
585 * Optional. An array of options. Default empty array. |
|
586 * |
|
587 * @type bool $convert_vars_to_classnames Whether to skip converting incoming CSS var patterns, |
|
588 * e.g. `var:preset|<PRESET_TYPE>|<PRESET_SLUG>`, |
|
589 * to `var( --wp--preset--* )` values. Default false. |
|
590 * } |
|
591 * @return string[] An associative array of CSS definitions, e.g. `array( "$property" => "$value", "$property" => "$value" )`. |
|
592 */ |
|
593 protected static function get_individual_property_css_declarations( $style_value, $individual_property_definition, $options = array() ) { |
|
594 if ( ! is_array( $style_value ) || empty( $style_value ) || empty( $individual_property_definition['path'] ) ) { |
|
595 return array(); |
|
596 } |
|
597 |
|
598 /* |
|
599 * The first item in $individual_property_definition['path'] array |
|
600 * tells us the style property, e.g. "border". We use this to get a corresponding |
|
601 * CSS style definition such as "color" or "width" from the same group. |
|
602 * |
|
603 * The second item in $individual_property_definition['path'] array |
|
604 * refers to the individual property marker, e.g. "top". |
|
605 */ |
|
606 $definition_group_key = $individual_property_definition['path'][0]; |
|
607 $individual_property_key = $individual_property_definition['path'][1]; |
|
608 $should_skip_css_vars = isset( $options['convert_vars_to_classnames'] ) && true === $options['convert_vars_to_classnames']; |
|
609 $css_declarations = array(); |
|
610 |
|
611 foreach ( $style_value as $css_property => $value ) { |
|
612 if ( empty( $value ) ) { |
|
613 continue; |
|
614 } |
|
615 |
|
616 // Build a path to the individual rules in definitions. |
|
617 $style_definition_path = array( $definition_group_key, $css_property ); |
|
618 $style_definition = _wp_array_get( static::BLOCK_STYLE_DEFINITIONS_METADATA, $style_definition_path, null ); |
|
619 |
|
620 if ( $style_definition && isset( $style_definition['property_keys']['individual'] ) ) { |
|
621 // Set a CSS var if there is a valid preset value. |
|
622 if ( is_string( $value ) && str_contains( $value, 'var:' ) |
|
623 && ! $should_skip_css_vars && ! empty( $individual_property_definition['css_vars'] ) |
|
624 ) { |
|
625 $value = static::get_css_var_value( $value, $individual_property_definition['css_vars'] ); |
|
626 } |
|
627 |
|
628 $individual_css_property = sprintf( $style_definition['property_keys']['individual'], $individual_property_key ); |
|
629 |
|
630 $css_declarations[ $individual_css_property ] = $value; |
|
631 } |
|
632 } |
|
633 return $css_declarations; |
|
634 } |
|
635 |
|
636 /** |
|
637 * Style value parser that constructs a CSS definition array comprising a single CSS property and value. |
|
638 * If the provided value is an array containing a `url` property, the function will return a CSS definition array |
|
639 * with a single property and value, with `url` escaped and injected into a CSS `url()` function, |
|
640 * e.g., array( 'background-image' => "url( '...' )" ). |
|
641 * |
|
642 * @since 6.4.0 |
|
643 * |
|
644 * @param array $style_value A single raw style value from $block_styles array. |
|
645 * @param array $style_definition A single style definition from BLOCK_STYLE_DEFINITIONS_METADATA. |
|
646 * @return string[] An associative array of CSS definitions, e.g., array( "$property" => "$value", "$property" => "$value" ). |
|
647 */ |
|
648 protected static function get_url_or_value_css_declaration( $style_value, $style_definition ) { |
|
649 if ( empty( $style_value ) ) { |
|
650 return array(); |
|
651 } |
|
652 |
|
653 $css_declarations = array(); |
|
654 |
|
655 if ( isset( $style_definition['property_keys']['default'] ) ) { |
|
656 $value = null; |
|
657 |
|
658 if ( ! empty( $style_value['url'] ) ) { |
|
659 $value = "url('" . $style_value['url'] . "')"; |
|
660 } elseif ( is_string( $style_value ) ) { |
|
661 $value = $style_value; |
|
662 } |
|
663 |
|
664 if ( null !== $value ) { |
|
665 $css_declarations[ $style_definition['property_keys']['default'] ] = $value; |
|
666 } |
|
667 } |
|
668 |
|
669 return $css_declarations; |
|
670 } |
|
671 |
|
672 /** |
|
673 * Returns compiled CSS from CSS declarations. |
|
674 * |
|
675 * @since 6.1.0 |
|
676 * |
|
677 * @param string[] $css_declarations An associative array of CSS definitions, |
|
678 * e.g. `array( "$property" => "$value", "$property" => "$value" )`. |
|
679 * @param string $css_selector When a selector is passed, the function will return |
|
680 * a full CSS rule `$selector { ...rules }`, |
|
681 * otherwise a concatenated string of properties and values. |
|
682 * @return string A compiled CSS string. |
|
683 */ |
|
684 public static function compile_css( $css_declarations, $css_selector ) { |
|
685 if ( empty( $css_declarations ) || ! is_array( $css_declarations ) ) { |
|
686 return ''; |
|
687 } |
|
688 |
|
689 // Return an entire rule if there is a selector. |
|
690 if ( $css_selector ) { |
|
691 $css_rule = new WP_Style_Engine_CSS_Rule( $css_selector, $css_declarations ); |
|
692 return $css_rule->get_css(); |
|
693 } |
|
694 |
|
695 $css_declarations = new WP_Style_Engine_CSS_Declarations( $css_declarations ); |
|
696 return $css_declarations->get_declarations_string(); |
|
697 } |
|
698 |
|
699 /** |
|
700 * Returns a compiled stylesheet from stored CSS rules. |
|
701 * |
|
702 * @since 6.1.0 |
|
703 * |
|
704 * @param WP_Style_Engine_CSS_Rule[] $css_rules An array of WP_Style_Engine_CSS_Rule objects |
|
705 * from a store or otherwise. |
|
706 * @param array $options { |
|
707 * Optional. An array of options. Default empty array. |
|
708 * |
|
709 * @type string|null $context An identifier describing the origin of the style object, |
|
710 * e.g. 'block-supports' or 'global-styles'. Default 'block-supports'. |
|
711 * When set, the style engine will attempt to store the CSS rules. |
|
712 * @type bool $optimize Whether to optimize the CSS output, e.g. combine rules. |
|
713 * Default false. |
|
714 * @type bool $prettify Whether to add new lines and indents to output. |
|
715 * Defaults to whether the `SCRIPT_DEBUG` constant is defined. |
|
716 * } |
|
717 * @return string A compiled stylesheet from stored CSS rules. |
|
718 */ |
|
719 public static function compile_stylesheet_from_css_rules( $css_rules, $options = array() ) { |
|
720 $processor = new WP_Style_Engine_Processor(); |
|
721 $processor->add_rules( $css_rules ); |
|
722 return $processor->get_css( $options ); |
|
723 } |
|
724 } |