PluginProbe
Gutenberg / 16.2.0
Gutenberg v16.2.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/class-wp-theme-json-gutenberg.php +443 -2637 23.7.0 → 16.2.0 View file →
@@ -37,16 +37,8 @@
37 37 */
38 38 protected static $blocks_metadata = array();
39 39
40 40 /**
41 - * The CSS selector for the top-level preset settings.
42 - *
43 - * @since 6.6.0
44 - * @var string
45 - */
46 - const ROOT_CSS_PROPERTIES_SELECTOR = ':root';
47 -
48 - /**
49 41 * The CSS selector for the top-level styles.
50 42 *
51 43 * @since 5.8.0
52 44 * @var string
@@ -122,23 +114,12 @@
122 114 * `use_default_names` preset key, and simplified the metadata structure.
123 115 * @since 6.0.0 Replaced `override` with `prevent_override` and updated the
124 116 * `prevent_override` value for `color.duotone` to use `color.defaultDuotone`.
125 117 * @since 6.2.0 Added 'shadow' presets.
126 - * @since 6.6.0 Updated the 'prevent_override' value for font size presets to use 'typography.defaultFontSizes' and spacing size presets to use `spacing.defaultSpacingSizes`.
127 - * @since 6.6.0 Added `aspectRatios`.
128 118 * @var array
129 119 */
130 120 const PRESETS_METADATA = array(
131 121 array(
132 - 'path' => array( 'dimensions', 'aspectRatios' ),
133 - 'prevent_override' => array( 'dimensions', 'defaultAspectRatios' ),
134 - 'use_default_names' => false,
135 - 'value_key' => 'ratio',
136 - 'css_vars' => '--wp--preset--aspect-ratio--$slug',
137 - 'classes' => array(),
138 - 'properties' => array( 'aspect-ratio' ),
139 - ),
140 - array(
141 122 'path' => array( 'color', 'palette' ),
142 123 'prevent_override' => array( 'color', 'defaultPalette' ),
143 124 'use_default_names' => false,
144 125 'value_key' => 'color',
@@ -169,9 +150,9 @@
169 150 'properties' => array( 'filter' ),
170 151 ),
171 152 array(
172 153 'path' => array( 'typography', 'fontSizes' ),
173 - 'prevent_override' => array( 'typography', 'defaultFontSizes' ),
154 + 'prevent_override' => false,
174 155 'use_default_names' => true,
175 156 'value_func' => 'gutenberg_get_typography_font_size_value',
176 157 'css_vars' => '--wp--preset--font-size--$slug',
177 158 'classes' => array( '.has-$slug-font-size' => 'font-size' ),
@@ -187,9 +168,9 @@
187 168 'properties' => array( 'font-family' ),
188 169 ),
189 170 array(
190 171 'path' => array( 'spacing', 'spacingSizes' ),
191 - 'prevent_override' => array( 'spacing', 'defaultSpacingSizes' ),
172 + 'prevent_override' => false,
192 173 'use_default_names' => true,
193 174 'value_key' => 'size',
194 175 'css_vars' => '--wp--preset--spacing--$slug',
195 176 'classes' => array(),
@@ -203,26 +184,8 @@
203 184 'css_vars' => '--wp--preset--shadow--$slug',
204 185 'classes' => array(),
205 186 'properties' => array( 'box-shadow' ),
206 187 ),
207 - array(
208 - 'path' => array( 'border', 'radiusSizes' ),
209 - 'prevent_override' => false,
210 - 'use_default_names' => false,
211 - 'value_key' => 'size',
212 - 'css_vars' => '--wp--preset--border-radius--$slug',
213 - 'classes' => array(),
214 - 'properties' => array( 'border-radius' ),
215 - ),
216 - array(
217 - 'path' => array( 'dimensions', 'dimensionSizes' ),
218 - 'prevent_override' => false,
219 - 'use_default_names' => false,
220 - 'value_key' => 'size',
221 - 'css_vars' => '--wp--preset--dimension--$slug',
222 - 'classes' => array(),
223 - 'properties' => array( 'width', 'height', 'min-height' ),
224 - ),
225 188 );
226 189
227 190 /**
228 191 * Metadata for style properties.
@@ -239,23 +202,14 @@
239 202 * `--wp--style--root--padding-*`, and `box-shadow` properties,
240 203 * removed the `--wp--style--block-gap` property.
241 204 * @since 6.2.0 Added `outline-*`, and `min-height` properties.
242 205 * @since 6.3.0 Added `writing-mode` property.
243 - * @since 6.6.0 Added `background-[image|position|repeat|size]` properties.
244 - * @since 7.0.0 Added `dimensions.width`, `dimensions.height`, and
245 - * `typography.textIndent` properties.
246 206 *
247 207 * @var array
248 208 */
249 209 const PROPERTIES_METADATA = array(
250 - 'aspect-ratio' => array( 'dimensions', 'aspectRatio' ),
251 210 'background' => array( 'color', 'gradient' ),
252 211 'background-color' => array( 'color', 'background' ),
253 - 'background-image' => array( 'background', 'backgroundImage' ),
254 - 'background-position' => array( 'background', 'backgroundPosition' ),
255 - 'background-repeat' => array( 'background', 'backgroundRepeat' ),
256 - 'background-size' => array( 'background', 'backgroundSize' ),
257 - 'background-attachment' => array( 'background', 'backgroundAttachment' ),
258 212 'border-radius' => array( 'border', 'radius' ),
259 213 'border-top-left-radius' => array( 'border', 'radius', 'topLeft' ),
260 214 'border-top-right-radius' => array( 'border', 'radius', 'topRight' ),
261 215 'border-bottom-left-radius' => array( 'border', 'radius', 'bottomLeft' ),
@@ -275,9 +229,8 @@
275 229 'border-left-color' => array( 'border', 'left', 'color' ),
276 230 'border-left-width' => array( 'border', 'left', 'width' ),
277 231 'border-left-style' => array( 'border', 'left', 'style' ),
278 232 'color' => array( 'color', 'text' ),
279 - 'text-align' => array( 'typography', 'textAlign' ),
280 233 'column-count' => array( 'typography', 'textColumns' ),
281 234 'font-family' => array( 'typography', 'fontFamily' ),
282 235 'font-size' => array( 'typography', 'fontSize' ),
283 236 'font-style' => array( 'typography', 'fontStyle' ),
@@ -289,9 +242,8 @@
289 242 'margin-right' => array( 'spacing', 'margin', 'right' ),
290 243 'margin-bottom' => array( 'spacing', 'margin', 'bottom' ),
291 244 'margin-left' => array( 'spacing', 'margin', 'left' ),
292 245 'min-height' => array( 'dimensions', 'minHeight' ),
293 - 'min-width' => array( 'dimensions', 'minWidth' ),
294 246 'outline-color' => array( 'outline', 'color' ),
295 247 'outline-offset' => array( 'outline', 'offset' ),
296 248 'outline-style' => array( 'outline', 'style' ),
297 249 'outline-width' => array( 'outline', 'width' ),
@@ -305,15 +257,11 @@
305 257 '--wp--style--root--padding-right' => array( 'spacing', 'padding', 'right' ),
306 258 '--wp--style--root--padding-bottom' => array( 'spacing', 'padding', 'bottom' ),
307 259 '--wp--style--root--padding-left' => array( 'spacing', 'padding', 'left' ),
308 260 'text-decoration' => array( 'typography', 'textDecoration' ),
309 - 'text-shadow' => array( 'typography', 'textShadow' ),
310 261 'text-transform' => array( 'typography', 'textTransform' ),
311 - 'text-indent' => array( 'typography', 'textIndent' ),
312 262 'filter' => array( 'filter', 'duotone' ),
313 263 'box-shadow' => array( 'shadow' ),
314 - 'height' => array( 'dimensions', 'height' ),
315 - 'width' => array( 'dimensions', 'width' ),
316 264 'writing-mode' => array( 'typography', 'writingMode' ),
317 265 );
318 266
319 267 /**
@@ -323,37 +271,45 @@
323 271 * paths to the value in theme.json & block attributes.
324 272 *
325 273 * Indirect properties are not output directly by `compute_style_properties`,
326 274 * but are used elsewhere in the processing of global styles. The indirect
327 - * property is used to validate whether a style value is allowed.
275 + * property is used to validate whether or not a style value is allowed.
328 276 *
329 277 * @since 6.2.0
330 - * @since 6.6.0 Added background-image properties.
331 278 *
332 279 * @var array
333 280 */
334 281 const INDIRECT_PROPERTIES_METADATA = array(
335 - 'gap' => array(
282 + 'gap' => array(
336 283 array( 'spacing', 'blockGap' ),
337 284 ),
338 - 'column-gap' => array(
285 + 'column-gap' => array(
339 286 array( 'spacing', 'blockGap', 'left' ),
340 287 ),
341 - 'row-gap' => array(
288 + 'row-gap' => array(
342 289 array( 'spacing', 'blockGap', 'top' ),
343 290 ),
344 - 'max-width' => array(
291 + 'max-width' => array(
345 292 array( 'layout', 'contentSize' ),
346 293 array( 'layout', 'wideSize' ),
347 294 ),
348 - 'background-image' => array(
349 - array( 'background', 'backgroundImage', 'url' ),
350 - array( 'background', 'gradient' ),
351 - ),
352 295 );
353 296
297 + /**
298 + * Protected style properties.
299 + *
300 + * These style properties are only rendered if a setting enables it
301 + * via a value other than `null`.
302 + *
303 + * Each element maps the style property to the corresponding theme.json
304 + * setting key.
305 + *
306 + * @since 5.9.0
307 + */
308 + const PROTECTED_PROPERTIES = array(
309 + 'spacing.blockGap' => array( 'spacing', 'blockGap' ),
310 + );
354 311
355 -
356 312 /**
357 313 * The top-level keys a theme.json can have.
358 314 *
359 315 * @since 5.8.0 As `ALLOWED_TOP_LEVEL_KEYS`.
@@ -362,9 +318,8 @@
362 318 * @since 6.3.0 Added the `description` value.
363 319 * @var string[]
364 320 */
365 321 const VALID_TOP_LEVEL_KEYS = array(
366 - 'blockTypes',
367 322 'customTemplates',
368 323 'description',
369 324 'patterns',
370 325 'settings',
@@ -370,10 +325,10 @@
370 325 'settings',
371 326 'styles',
372 327 'templateParts',
373 328 'title',
374 - 'slug',
375 329 'version',
330 + 'behaviors',
376 331 );
377 332
378 333 /**
379 334 * The valid properties under the settings key.
@@ -386,33 +341,18 @@
386 341 * @since 6.1.0 Added `layout.definitions` and `useRootPaddingAwareAlignments`.
387 342 * @since 6.2.0 Added `dimensions.minHeight`, 'shadow.presets', 'shadow.defaultPresets',
388 343 * `position.fixed` and `position.sticky`.
389 344 * @since 6.3.0 Removed `layout.definitions`. Added `typography.writingMode`.
390 - * @since 6.4.0 Added `layout.allowEditing`.
391 - * @since 6.4.0 Added `lightbox`.
392 - * @since 7.0.0 Added type markers to the schema for boolean values.
393 - * @since 7.0.0 Added `dimensions.width`, `dimensions.height`, and
394 - * `typography.textIndent` properties.
395 - * @since 7.1.0 Added `viewport` property.
396 345 * @var array
397 346 */
398 347 const VALID_SETTINGS = array(
399 348 'appearanceTools' => null,
400 349 'useRootPaddingAwareAlignments' => null,
401 - 'background' => array(
402 - 'backgroundImage' => null,
403 - 'backgroundSize' => null,
404 - 'gradient' => null,
405 - ),
406 - 'blockVisibility' => array(
407 - 'allowEditing' => true,
408 - ),
409 350 'border' => array(
410 - 'color' => null,
411 - 'radius' => null,
412 - 'style' => null,
413 - 'width' => null,
414 - 'radiusSizes' => null,
351 + 'color' => null,
352 + 'radius' => null,
353 + 'style' => null,
354 + 'width' => null,
415 355 ),
416 356 'color' => array(
417 357 'background' => null,
418 358 'custom' => null,
@@ -431,40 +371,26 @@
431 371 'text' => null,
432 372 ),
433 373 'custom' => null,
434 374 'dimensions' => array(
435 - 'aspectRatio' => null,
436 - 'aspectRatios' => null,
437 - 'defaultAspectRatios' => null,
438 - 'dimensionSizes' => null,
439 - 'height' => null,
440 - 'minHeight' => null,
441 - 'minWidth' => null,
442 - 'width' => null,
375 + 'minHeight' => null,
443 376 ),
444 377 'layout' => array(
445 - 'contentSize' => null,
446 - 'wideSize' => null,
447 - 'allowEditing' => null,
448 - 'allowCustomContentAndWideSize' => null,
378 + 'contentSize' => null,
379 + 'wideSize' => null,
449 380 ),
450 - 'lightbox' => array(
451 - 'enabled' => true,
452 - 'allowEditing' => true,
453 - ),
454 381 'position' => array(
455 382 'fixed' => null,
456 383 'sticky' => null,
457 384 ),
458 385 'spacing' => array(
459 - 'customSpacingSize' => null,
460 - 'defaultSpacingSizes' => null,
461 - 'spacingSizes' => null,
462 - 'spacingScale' => null,
463 - 'blockGap' => null,
464 - 'margin' => null,
465 - 'padding' => null,
466 - 'units' => null,
386 + 'customSpacingSize' => null,
387 + 'spacingSizes' => null,
388 + 'spacingScale' => null,
389 + 'blockGap' => null,
390 + 'margin' => null,
391 + 'padding' => null,
392 + 'units' => null,
467 393 ),
468 394 'shadow' => array(
469 395 'presets' => null,
470 396 'defaultPresets' => null,
@@ -469,56 +395,25 @@
469 395 'presets' => null,
470 396 'defaultPresets' => null,
471 397 ),
472 398 'typography' => array(
473 - 'fluid' => null,
474 - 'customFontSize' => null,
475 - 'defaultFontSizes' => null,
476 - 'dropCap' => null,
477 - 'fontFamilies' => null,
478 - 'fontSizes' => null,
479 - 'fontStyle' => null,
480 - 'fontWeight' => null,
481 - 'letterSpacing' => null,
482 - 'lineHeight' => null,
483 - 'textAlign' => null,
484 - 'textColumns' => null,
485 - 'textDecoration' => null,
486 - 'textIndent' => null,
487 - 'textTransform' => null,
488 - 'writingMode' => null,
399 + 'fluid' => null,
400 + 'customFontSize' => null,
401 + 'dropCap' => null,
402 + 'fontFamilies' => null,
403 + 'fontSizes' => null,
404 + 'fontStyle' => null,
405 + 'fontWeight' => null,
406 + 'letterSpacing' => null,
407 + 'lineHeight' => null,
408 + 'textColumns' => null,
409 + 'textDecoration' => null,
410 + 'textTransform' => null,
411 + 'writingMode' => null,
489 412 ),
490 - 'viewport' => array(
491 - 'mobile' => null,
492 - 'tablet' => null,
493 - ),
413 + 'behaviors' => null,
494 414 );
495 415
496 - const FONT_FAMILY_SCHEMA = array(
497 - array(
498 - 'fontFamily' => null,
499 - 'name' => null,
500 - 'slug' => null,
501 - 'fontFace' => array(
502 - array(
503 - 'ascentOverride' => null,
504 - 'descentOverride' => null,
505 - 'fontDisplay' => null,
506 - 'fontFamily' => null,
507 - 'fontFeatureSettings' => null,
508 - 'fontStyle' => null,
509 - 'fontStretch' => null,
510 - 'fontVariationSettings' => null,
511 - 'fontWeight' => null,
512 - 'lineGapOverride' => null,
513 - 'sizeAdjust' => null,
514 - 'src' => null,
515 - 'unicodeRange' => null,
516 - ),
517 - ),
518 - ),
519 - );
520 -
521 416 /**
522 417 * The valid properties under the styles key.
523 418 *
524 419 * @since 5.8.0 As `ALLOWED_STYLES`.
@@ -528,23 +423,12 @@
528 423 * @since 6.1.0 Added new side properties for `border`,
529 424 * added new property `shadow`,
530 425 * updated `blockGap` to be allowed at any level.
531 426 * @since 6.2.0 Added `outline`, and `minHeight` properties.
532 - * @since 6.6.0 Added `background` sub properties to top-level only.
533 - * @since 6.6.0 Added `dimensions.aspectRatio`.
534 - * @since 7.0.0 Added `dimensions.width`, `dimensions.height`, and
535 - * `typography.textIndent` properties.
427 + *
536 428 * @var array
537 429 */
538 430 const VALID_STYLES = array(
539 - 'background' => array(
540 - 'backgroundImage' => null,
541 - 'backgroundAttachment' => null,
542 - 'backgroundPosition' => null,
543 - 'backgroundRepeat' => null,
544 - 'backgroundSize' => null,
545 - 'gradient' => null,
546 - ),
547 431 'border' => array(
548 432 'color' => null,
549 433 'radius' => null,
550 434 'style' => null,
@@ -559,13 +443,9 @@
559 443 'gradient' => null,
560 444 'text' => null,
561 445 ),
562 446 'dimensions' => array(
563 - 'aspectRatio' => null,
564 - 'height' => null,
565 - 'minHeight' => null,
566 - 'minWidth' => null,
567 - 'width' => null,
447 + 'minHeight' => null,
568 448 ),
569 449 'filter' => array(
570 450 'duotone' => null,
571 451 ),
@@ -587,13 +467,10 @@
587 467 'fontStyle' => null,
588 468 'fontWeight' => null,
589 469 'letterSpacing' => null,
590 470 'lineHeight' => null,
591 - 'textAlign' => null,
592 471 'textColumns' => null,
593 472 'textDecoration' => null,
594 - 'textIndent' => null,
595 - 'textShadow' => null,
596 473 'textTransform' => null,
597 474 'writingMode' => null,
598 475 ),
599 476 'css' => null,
@@ -601,9 +478,9 @@
601 478
602 479 /**
603 480 * Defines which pseudo selectors are enabled for which elements.
604 481 *
605 - * The order of the selectors should be: link, any-link, visited, hover, focus, focus-visible, active.
482 + * The order of the selectors should be: link, any-link, visited, hover, focus, active.
606 483 * This is to ensure the user action (hover, focus and active) styles have a higher
607 484 * specificity than the visited styles, which in turn have a higher specificity than
608 485 * the unvisited styles.
609 486 *
@@ -611,219 +488,15 @@
611 488 * Note: this will affect both top-level and block-level elements.
612 489 *
613 490 * @since 6.1.0
614 491 * @since 6.2.0 Added support for `:link` and `:any-link`.
615 - * @since 6.8.0 Added support for `:focus-visible`.
616 492 */
617 493 const VALID_ELEMENT_PSEUDO_SELECTORS = array(
618 - 'link' => array( ':link', ':any-link', ':visited', ':hover', ':focus', ':focus-visible', ':active' ),
619 - 'button' => array( ':link', ':any-link', ':visited', ':hover', ':focus', ':focus-visible', ':active' ),
494 + 'link' => array( ':link', ':any-link', ':visited', ':hover', ':focus', ':active' ),
495 + 'button' => array( ':link', ':any-link', ':visited', ':hover', ':focus', ':active' ),
620 496 );
621 497
622 498 /**
623 - * The valid pseudo-selectors that can be used for blocks.
624 - *
625 - * @since 7.0.0
626 - * @var array
627 - */
628 - const VALID_BLOCK_PSEUDO_SELECTORS = array(
629 - 'core/button' => array( ':hover', ':focus', ':focus-visible', ':active' ),
630 - 'core/navigation-link' => array( ':hover', ':focus', ':focus-visible', ':active' ),
631 - );
632 -
633 - /**
634 - * Default viewport breakpoint sizes.
635 - *
636 - * @since 7.1.0
637 - * @var array
638 - */
639 - const DEFAULT_VIEWPORT_BREAKPOINTS = array(
640 - 'mobile' => '480px',
641 - 'tablet' => '782px',
642 - );
643 -
644 - /**
645 - * Returns CSS media queries for responsive viewport style states.
646 - *
647 - * Breakpoint values are read from `settings.viewport`, sanitized, and
648 - * normalized before the media query strings are generated. By default, the
649 - * returned keys are the theme.json style-state names (`@mobile`, `@tablet`).
650 - * When `$options['include_desktop']` is truthy, `@desktop` is included.
651 - *
652 - * @since 7.1.0
653 - *
654 - * @param mixed $viewport_settings Viewport settings from theme.json.
655 - * @param array $options {
656 - * Optional. Options for generating media queries.
657 - *
658 - * @type bool $include_desktop Whether to include the desktop media query. Default false.
659 - * }
660 - * @return array Responsive media queries.
661 - */
662 - public static function get_viewport_media_queries( $viewport_settings = null, $options = array() ) {
663 - $breakpoints = static::sanitize_viewport_settings( $viewport_settings );
664 -
665 - $responsive_media_queries = array();
666 -
667 - if ( isset( $breakpoints['mobile'] ) ) {
668 - $responsive_media_queries['@mobile'] = "@media (width <= {$breakpoints['mobile']})";
669 - }
670 -
671 - if ( isset( $breakpoints['tablet'] ) ) {
672 - $responsive_media_queries['@tablet'] = isset( $breakpoints['mobile'] )
673 - ? sprintf(
674 - '@media (%s < width <= %s)',
675 - $breakpoints['mobile'],
676 - $breakpoints['tablet']
677 - )
678 - : "@media (width <= {$breakpoints['tablet']})";
679 - }
680 -
681 - if ( ! empty( $options['include_desktop'] ) ) {
682 - if ( isset( $breakpoints['tablet'] ) ) {
683 - $desktop_breakpoint = $breakpoints['tablet'];
684 - } else {
685 - $desktop_breakpoint = $breakpoints['mobile'];
686 - }
687 -
688 - $responsive_media_queries['@desktop'] =
689 - "@media (width > {$desktop_breakpoint})";
690 - }
691 -
692 - return $responsive_media_queries;
693 - }
694 -
695 - /**
696 - * Checks whether a viewport breakpoint value is a safe CSS length.
697 - *
698 - * Viewport breakpoints are limited to numeric `px`, `em`, and `rem` lengths.
699 - * CSS functions, percentages, and other units are rejected because breakpoint
700 - * values are interpolated into generated media queries.
701 - *
702 - * @since 7.1.0
703 - *
704 - * @param mixed $value Value to check.
705 - * @return bool Whether the value is valid.
706 - */
707 - private static function is_valid_viewport_breakpoint_size( $value ) {
708 - if ( ! is_string( $value ) ) {
709 - return false;
710 - }
711 -
712 - $value = trim( $value );
713 - if ( '' === $value ) {
714 - return false;
715 - }
716 -
717 - return 1 === preg_match( '/^(?:\d+|\d*\.\d+)(?:px|em|rem)$/', $value );
718 - }
719 -
720 - /**
721 - * Converts a valid viewport breakpoint size to pixels for ordering checks.
722 - *
723 - * Generated media queries keep the original units. This method only
724 - * normalizes values so `mobile` and `tablet` can be compared safely. `em`
725 - * and `rem` lengths use a 16px base for comparison.
726 - *
727 - * @since 7.1.0
728 - *
729 - * @param mixed $value Viewport breakpoint size.
730 - * @return float|null Viewport breakpoint size in pixels, or null when invalid.
731 - */
732 - private static function get_viewport_breakpoint_value_in_pixels( $value ) {
733 - if ( ! static::is_valid_viewport_breakpoint_size( $value ) ) {
734 - return null;
735 - }
736 -
737 - $value = trim( $value );
738 - $unit = substr( $value, -3 );
739 - if ( 'rem' === $unit ) {
740 - $number = (float) substr( $value, 0, -3 );
741 - } else {
742 - $unit = substr( $value, -2 );
743 - $number = (float) substr( $value, 0, -2 );
744 - }
745 -
746 - /*
747 - * Use the most common browser default font size as the base for em/rem
748 - * media query conversions. This pixel value is only used to compare
749 - * breakpoint order; generated media queries keep the original units.
750 - */
751 - return 'px' === $unit ? $number : $number * 16;
752 - }
753 -
754 - /**
755 - * Sanitizes and normalizes viewport breakpoint settings.
756 - *
757 - * Keeps only supported breakpoint keys, trims valid CSS lengths, and returns
758 - * the default breakpoints when no valid custom breakpoint is provided. When
759 - * only one breakpoint is valid, it remains keyed by its configured state and
760 - * uses a single max-width media query. When `tablet` is not larger than
761 - * `mobile`, it is removed.
762 - *
763 - * @since 7.1.0
764 - *
765 - * @param mixed $viewport_settings Viewport settings from theme.json.
766 - * @return array Sanitized viewport breakpoint settings.
767 - */
768 - private static function sanitize_viewport_settings( $viewport_settings ) {
769 - if ( ! is_array( $viewport_settings ) ) {
770 - return static::DEFAULT_VIEWPORT_BREAKPOINTS;
771 - }
772 -
773 - $breakpoints = array();
774 - foreach ( array_keys( static::DEFAULT_VIEWPORT_BREAKPOINTS ) as $breakpoint ) {
775 - $value = $viewport_settings[ $breakpoint ] ?? null;
776 - $px = static::get_viewport_breakpoint_value_in_pixels( $value );
777 - if ( null !== $px ) {
778 - $breakpoints[ $breakpoint ] = array(
779 - 'value' => trim( $value ),
780 - 'px' => $px,
781 - );
782 - }
783 - }
784 -
785 - if ( empty( $breakpoints ) ) {
786 - return static::DEFAULT_VIEWPORT_BREAKPOINTS;
787 - }
788 -
789 - if ( 1 === count( $breakpoints ) ) {
790 - $breakpoint = key( $breakpoints );
791 - return array( $breakpoint => $breakpoints[ $breakpoint ]['value'] );
792 - }
793 -
794 - $sanitized = array( 'mobile' => $breakpoints['mobile']['value'] );
795 -
796 - if ( isset( $breakpoints['tablet'] ) && $breakpoints['mobile']['px'] < $breakpoints['tablet']['px']
797 - ) {
798 - $sanitized['tablet'] = $breakpoints['tablet']['value'];
799 - }
800 -
801 - return $sanitized;
802 - }
803 -
804 - /**
805 - * Custom states for blocks that map to CSS class selectors rather than
806 - * CSS pseudo-selectors. Values use the '-' prefix (e.g. '-current') to
807 - * distinguish them from real CSS pseudo-selectors and breakpoint states.
808 - *
809 - * The CSS selector for each state is defined in the block's block.json
810 - * under `selectors.states`, e.g.:
811 - *
812 - * "selectors": { "states": { "-current": ".some-css-selector" } }
813 - *
814 - * This constant controls which states are valid in theme.json for a given
815 - * block. Blocks listed here also inherit their VALID_BLOCK_PSEUDO_SELECTORS
816 - * as valid sub-states, producing compound selectors such as
817 - * `.wp-block-navigation-item.current-menu-item:hover`.
818 - *
819 - * @var array
820 - */
821 - const VALID_BLOCK_CUSTOM_STATES = array(
822 - 'core/navigation-link' => array( '-current' ),
823 - );
824 -
825 - /**
826 499 * The valid elements that can be found under styles.
827 500 *
828 501 * @since 5.8.0
829 502 * @since 6.1.0 Added `heading`, `button`, and `caption` elements.
@@ -829,23 +502,21 @@
829 502 * @since 6.1.0 Added `heading`, `button`, and `caption` elements.
830 503 * @var string[]
831 504 */
832 505 const ELEMENTS = array(
833 - 'link' => 'a:where(:not(.wp-element-button))', // The `where` is needed to lower the specificity.
834 - 'heading' => 'h1, h2, h3, h4, h5, h6',
835 - 'h1' => 'h1',
836 - 'h2' => 'h2',
837 - 'h3' => 'h3',
838 - 'h4' => 'h4',
839 - 'h5' => 'h5',
840 - 'h6' => 'h6',
506 + 'link' => 'a:where(:not(.wp-element-button))', // The `where` is needed to lower the specificity.
507 + 'heading' => 'h1, h2, h3, h4, h5, h6',
508 + 'h1' => 'h1',
509 + 'h2' => 'h2',
510 + 'h3' => 'h3',
511 + 'h4' => 'h4',
512 + 'h5' => 'h5',
513 + 'h6' => 'h6',
841 514 // We have the .wp-block-button__link class so that this will target older buttons that have been serialized.
842 - 'button' => '.wp-element-button, .wp-block-button__link',
515 + 'button' => '.wp-element-button, .wp-block-button__link',
843 516 // The block classes are necessary to target older content that won't use the new class names.
844 - 'caption' => '.wp-element-caption, .wp-block-audio figcaption, .wp-block-embed figcaption, .wp-block-gallery figcaption, .wp-block-image figcaption, .wp-block-table figcaption, .wp-block-video figcaption',
845 - 'cite' => 'cite',
846 - 'select' => 'select',
847 - 'textInput' => 'textarea, input:where([type=email],[type=number],[type=password],[type=search],[type=text],[type=tel],[type=url])',
517 + 'caption' => '.wp-element-caption, .wp-block-audio figcaption, .wp-block-embed figcaption, .wp-block-gallery figcaption, .wp-block-image figcaption, .wp-block-table figcaption, .wp-block-video figcaption',
518 + 'cite' => 'cite',
848 519 );
849 520
850 521 const __EXPERIMENTAL_ELEMENT_CLASS_NAMES = array(
851 522 'button' => 'wp-element-button',
@@ -861,120 +532,13 @@
861 532 */
862 533 const BLOCK_SUPPORT_FEATURE_LEVEL_SELECTORS = array(
863 534 '__experimentalBorder' => 'border',
864 535 'color' => 'color',
865 - 'dimensions' => 'dimensions',
866 536 'spacing' => 'spacing',
867 537 'typography' => 'typography',
868 538 );
869 539
870 540 /**
871 - * Return the input schema at the root and per origin.
872 - *
873 - * @since 6.5.0
874 - *
875 - * @param array $schema The base schema.
876 - * @return array The schema at the root and per origin.
877 - *
878 - * Example:
879 - * schema_in_root_and_per_origin(
880 - * array(
881 - * 'fontFamily' => null,
882 - * 'slug' => null,
883 - * )
884 - * )
885 - *
886 - * Returns:
887 - * array(
888 - * 'fontFamily' => null,
889 - * 'slug' => null,
890 - * 'default' => array(
891 - * 'fontFamily' => null,
892 - * 'slug' => null,
893 - * ),
894 - * 'blocks' => array(
895 - * 'fontFamily' => null,
896 - * 'slug' => null,
897 - * ),
898 - * 'theme' => array(
899 - * 'fontFamily' => null,
900 - * 'slug' => null,
901 - * ),
902 - * 'custom' => array(
903 - * 'fontFamily' => null,
904 - * 'slug' => null,
905 - * ),
906 - * )
907 - */
908 - protected static function schema_in_root_and_per_origin( $schema ) {
909 - $schema_in_root_and_per_origin = $schema;
910 - foreach ( static::VALID_ORIGINS as $origin ) {
911 - $schema_in_root_and_per_origin[ $origin ] = $schema;
912 - }
913 - return $schema_in_root_and_per_origin;
914 - }
915 -
916 - /**
917 - * Processes pseudo-selectors for any node (block or variation).
918 - *
919 - * @param array $node The node data (block or variation).
920 - * @param string $base_selector The base selector.
921 - * @param array $settings The theme settings.
922 - * @param string $block_name The block name.
923 - * @param array|null $block_metadata Metadata about the block to get styles for.
924 - * @param array|null $style_variation Style variation metadata.
925 - * @return array Array of pseudo-selector declarations.
926 - */
927 - private function process_pseudo_selectors( $node, $base_selector, $settings, $block_name, $block_metadata = null, $style_variation = null ) {
928 - $pseudo_declarations = array();
929 - $add_declarations = static function ( $selector, $declarations ) use ( &$pseudo_declarations ) {
930 - if ( empty( $declarations ) ) {
931 - return;
932 - }
933 -
934 - if ( isset( $pseudo_declarations[ $selector ] ) ) {
935 - $pseudo_declarations[ $selector ] = array_merge(
936 - $pseudo_declarations[ $selector ],
937 - $declarations
938 - );
939 - } else {
940 - $pseudo_declarations[ $selector ] = $declarations;
941 - }
942 - };
943 -
944 - if ( ! isset( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block_name ] ) ) {
945 - return $pseudo_declarations;
946 - }
947 -
948 - foreach ( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block_name ] as $pseudo_selector ) {
949 - if ( isset( $node[ $pseudo_selector ] ) ) {
950 - $pseudo_node = $node[ $pseudo_selector ];
951 -
952 - if ( is_array( $block_metadata ) ) {
953 - $feature_declarations = $this->get_feature_declarations_for_node( $block_metadata, $pseudo_node );
954 - $feature_declarations = static::update_paragraph_text_indent_selector( $feature_declarations, $settings, $block_name );
955 - $feature_declarations = static::update_button_width_declarations( $feature_declarations, $settings );
956 -
957 - foreach ( $feature_declarations as $feature_selector => $declarations ) {
958 - $target_selector = is_array( $style_variation )
959 - ? static::get_block_style_variation_feature_selector( $style_variation, $feature_selector )
960 - : $feature_selector;
961 - $combined_selector = static::append_to_selector( $target_selector, $pseudo_selector );
962 -
963 - $add_declarations( $combined_selector, $declarations );
964 - }
965 - }
966 -
967 - $combined_selector = static::append_to_selector( $base_selector, $pseudo_selector );
968 - $declarations = static::compute_style_properties( $pseudo_node, $settings, null, null );
969 - $add_declarations( $combined_selector, $declarations );
970 - }
971 - }
972 -
973 - return $pseudo_declarations;
974 - }
975 -
976 - /**
977 541 * Returns a class name by an element name.
978 542 *
979 543 * @since 6.1.0
980 544 *
@@ -983,9 +547,11 @@
983 547 */
984 548 public static function get_element_class_name( $element ) {
985 549 $class_name = '';
986 550
987 - if ( isset( static::__EXPERIMENTAL_ELEMENT_CLASS_NAMES[ $element ] ) ) {
551 + // TODO: Replace array_key_exists() with isset() check once WordPress drops
552 + // support for PHP 5.6. See https://core.trac.wordpress.org/ticket/57067.
553 + if ( array_key_exists( $element, static::__EXPERIMENTAL_ELEMENT_CLASS_NAMES ) ) {
988 554 $class_name = static::__EXPERIMENTAL_ELEMENT_CLASS_NAMES[ $element ];
989 555 }
990 556
991 557 return $class_name;
@@ -995,15 +561,11 @@
995 561 * Options that settings.appearanceTools enables.
996 562 *
997 563 * @since 6.0.0
998 564 * @since 6.2.0 Added `dimensions.minHeight` and `position.sticky`.
999 - * @since 7.0.0 Added `dimensions.width` and `dimensions.height`.
1000 565 * @var array
1001 566 */
1002 567 const APPEARANCE_TOOLS_OPT_INS = array(
1003 - array( 'background', 'backgroundImage' ),
1004 - array( 'background', 'backgroundSize' ),
1005 - array( 'background', 'gradient' ),
1006 568 array( 'border', 'color' ),
1007 569 array( 'border', 'radius' ),
1008 570 array( 'border', 'style' ),
1009 571 array( 'border', 'width' ),
@@ -1010,13 +572,9 @@
1010 572 array( 'color', 'link' ),
1011 573 array( 'color', 'heading' ),
1012 574 array( 'color', 'button' ),
1013 575 array( 'color', 'caption' ),
1014 - array( 'dimensions', 'aspectRatio' ),
1015 - array( 'dimensions', 'height' ),
1016 576 array( 'dimensions', 'minHeight' ),
1017 - array( 'dimensions', 'minWidth' ),
1018 - array( 'dimensions', 'width' ),
1019 577 // BEGIN EXPERIMENTAL.
1020 578 // Allow `position.fixed` to be opted-in by default.
1021 579 // Sticky position support was backported to WordPress 6.2 in https://core.trac.wordpress.org/ticket/57618.
1022 580 // While `fixed` was included as a valid setting, exposing it by default is still experimental.
@@ -1026,9 +584,8 @@
1026 584 array( 'spacing', 'blockGap' ),
1027 585 array( 'spacing', 'margin' ),
1028 586 array( 'spacing', 'padding' ),
1029 587 array( 'typography', 'lineHeight' ),
1030 - array( 'typography', 'textColumns' ),
1031 588 );
1032 589
1033 590 /**
1034 591 * The latest version of the schema in use.
@@ -1034,40 +591,39 @@
1034 591 * The latest version of the schema in use.
1035 592 *
1036 593 * @since 5.8.0
1037 594 * @since 5.9.0 Changed value from 1 to 2.
1038 - * @since 6.5.0 Changed value from 2 to 3.
1039 595 * @var int
1040 596 */
1041 - const LATEST_SCHEMA = 3;
597 + const LATEST_SCHEMA = 2;
1042 598
1043 599 /**
1044 600 * Constructor.
1045 601 *
1046 602 * @since 5.8.0
1047 - * @since 6.6.0 Key spacingScale by origin, and pre-generate the spacingSizes from spacingScale.
1048 - * Added unwrapping of shared block style variations into block type variations if registered.
1049 603 *
1050 604 * @param array $theme_json A structure that follows the theme.json schema.
1051 605 * @param string $origin Optional. What source of data this object represents.
1052 - * One of 'blocks', 'default', 'theme', or 'custom'. Default 'theme'.
606 + * One of 'default', 'theme', or 'custom'. Default 'theme'.
1053 607 */
1054 - public function __construct( $theme_json = array( 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA ), $origin = 'theme' ) {
608 + public function __construct( $theme_json = array(), $origin = 'theme' ) {
1055 609 if ( ! in_array( $origin, static::VALID_ORIGINS, true ) ) {
1056 610 $origin = 'theme';
1057 611 }
1058 612
1059 - $this->theme_json = WP_Theme_JSON_Schema_Gutenberg::migrate( $theme_json, $origin );
1060 - if ( isset( $this->theme_json['styles'] ) ) {
1061 - $this->theme_json['styles'] = gutenberg_resolve_style_state_aliases( $this->theme_json['styles'] );
613 + $this->theme_json = WP_Theme_JSON_Schema::migrate( $theme_json );
614 + $registry = WP_Block_Type_Registry::get_instance();
615 + $valid_block_names = array_keys( $registry->get_all_registered() );
616 + $valid_element_names = array_keys( static::ELEMENTS );
617 + $valid_variations = array();
618 + foreach ( self::get_blocks_metadata() as $block_name => $block_meta ) {
619 + if ( ! isset( $block_meta['styleVariations'] ) ) {
620 + continue;
621 + }
622 + $valid_variations[ $block_name ] = array_keys( $block_meta['styleVariations'] );
1062 623 }
1063 - $blocks_metadata = static::get_blocks_metadata();
1064 - $valid_block_names = array_keys( $blocks_metadata );
1065 - $valid_element_names = array_keys( static::ELEMENTS );
1066 - $valid_variations = static::get_valid_block_style_variations( $blocks_metadata );
1067 - $this->theme_json = static::unwrap_shared_block_style_variations( $this->theme_json, $valid_variations );
1068 - $this->theme_json = static::sanitize( $this->theme_json, $valid_block_names, $valid_element_names, $valid_variations );
1069 - $this->theme_json = static::maybe_opt_in_into_settings( $this->theme_json );
624 + $theme_json = static::sanitize( $this->theme_json, $valid_block_names, $valid_element_names, $valid_variations );
625 + $this->theme_json = static::maybe_opt_in_into_settings( $theme_json );
1070 626
1071 627 // Internally, presets are keyed by origin.
1072 628 $nodes = static::get_setting_nodes( $this->theme_json );
1073 629 foreach ( $nodes as $node ) {
@@ -1084,100 +640,11 @@
1084 640 }
1085 641 }
1086 642 }
1087 643 }
1088 -
1089 - // In addition to presets, spacingScale (which generates presets) is also keyed by origin.
1090 - $scale_path = array( 'settings', 'spacing', 'spacingScale' );
1091 - $spacing_scale = _wp_array_get( $this->theme_json, $scale_path, null );
1092 - if ( null !== $spacing_scale ) {
1093 - // If the spacingScale is not already keyed by origin.
1094 - if ( empty( array_intersect( array_keys( $spacing_scale ), static::VALID_ORIGINS ) ) ) {
1095 - _wp_array_set( $this->theme_json, $scale_path, array( $origin => $spacing_scale ) );
1096 - }
1097 - }
1098 -
1099 - // Pre-generate the spacingSizes from spacingScale.
1100 - $scale_path = array( 'settings', 'spacing', 'spacingScale', $origin );
1101 - $spacing_scale = _wp_array_get( $this->theme_json, $scale_path, null );
1102 - if ( isset( $spacing_scale ) ) {
1103 - $sizes_path = array( 'settings', 'spacing', 'spacingSizes', $origin );
1104 - $spacing_sizes = _wp_array_get( $this->theme_json, $sizes_path, array() );
1105 - $spacing_scale_sizes = static::compute_spacing_sizes( $spacing_scale );
1106 - $merged_spacing_sizes = static::merge_spacing_sizes( $spacing_scale_sizes, $spacing_sizes );
1107 - _wp_array_set( $this->theme_json, $sizes_path, $merged_spacing_sizes );
1108 - }
1109 644 }
1110 645
1111 646 /**
1112 - * Unwraps shared block style variations.
1113 - *
1114 - * It takes the shared variations (styles.variations.variationName) and
1115 - * applies them to all the blocks that have the given variation registered
1116 - * (styles.blocks.blockType.variations.variationName).
1117 - *
1118 - * For example, given the `core/paragraph` and `core/group` blocks have
1119 - * registered the `section-a` style variation, and given the following input:
1120 - *
1121 - * {
1122 - * "styles": {
1123 - * "variations": {
1124 - * "section-a": { "color": { "background": "backgroundColor" } }
1125 - * }
1126 - * }
1127 - * }
1128 - *
1129 - * It returns the following output:
1130 - *
1131 - * {
1132 - * "styles": {
1133 - * "blocks": {
1134 - * "core/paragraph": {
1135 - * "variations": {
1136 - * "section-a": { "color": { "background": "backgroundColor" } }
1137 - * },
1138 - * },
1139 - * "core/group": {
1140 - * "variations": {
1141 - * "section-a": { "color": { "background": "backgroundColor" } }
1142 - * }
1143 - * }
1144 - * }
1145 - * }
1146 - * }
1147 - *
1148 - * @since 6.6.0
1149 - *
1150 - * @param array $theme_json A structure that follows the theme.json schema.
1151 - * @param array $valid_variations Valid block style variations.
1152 - *
1153 - * @return array Theme json data with shared variation definitions unwrapped under appropriate block types.
1154 - */
1155 - private static function unwrap_shared_block_style_variations( $theme_json, $valid_variations ) {
1156 - if ( empty( $theme_json['styles']['variations'] ) || empty( $valid_variations ) ) {
1157 - return $theme_json;
1158 - }
1159 -
1160 - $new_theme_json = $theme_json;
1161 - $variations = $new_theme_json['styles']['variations'];
1162 -
1163 - foreach ( $valid_variations as $block_type => $registered_variations ) {
1164 - foreach ( $registered_variations as $variation_name ) {
1165 - $block_level_data = $new_theme_json['styles']['blocks'][ $block_type ]['variations'][ $variation_name ] ?? array();
1166 - $top_level_data = $variations[ $variation_name ] ?? array();
1167 - $merged_data = array_replace_recursive( $top_level_data, $block_level_data );
1168 - if ( ! empty( $merged_data ) ) {
1169 - _wp_array_set( $new_theme_json, array( 'styles', 'blocks', $block_type, 'variations', $variation_name ), $merged_data );
1170 - }
1171 - }
1172 - }
1173 -
1174 - unset( $new_theme_json['styles']['variations'] );
1175 -
1176 - return $new_theme_json;
1177 - }
1178 -
1179 - /**
1180 647 * Enables some opt-in settings if theme declared support.
1181 648 *
1182 649 * @since 5.9.0
1183 650 *
@@ -1228,9 +695,8 @@
1228 695 * Sanitizes the input according to the schemas.
1229 696 *
1230 697 * @since 5.8.0
1231 698 * @since 5.9.0 Added the `$valid_block_names` and `$valid_element_name` parameters.
1232 - * @since 6.6.0 Extended schema definition to allow enhanced block style variations.
1233 699 *
1234 700 * @param array $input Structure to sanitize.
1235 701 * @param array $valid_block_names List of valid block names.
1236 702 * @param array $valid_element_names List of valid element names.
@@ -1265,96 +731,33 @@
1265 731 }
1266 732 }
1267 733
1268 734 // Build the schema based on valid block & element names.
1269 - $schema = array();
1270 - $schema_styles_elements = array();
1271 - $responsive_media_queries = static::get_viewport_media_queries( $input['settings']['viewport'] ?? null );
735 + $schema = array();
736 + $schema_styles_elements = array();
1272 737
1273 738 /*
1274 - * Set allowed element pseudo selectors and responsive breakpoint states.
739 + * Set allowed element pseudo selectors based on per element allow list.
1275 740 * Target data structure in schema:
1276 741 * e.g.
1277 742 * - top level elements: `$schema['styles']['elements']['link'][':hover']`.
1278 743 * - block level elements: `$schema['styles']['blocks']['core/button']['elements']['link'][':hover']`.
1279 - * - block responsive elements: `$schema['styles']['blocks']['core/button']['@tablet']['elements']['link'][':hover']`.
1280 744 */
1281 745 foreach ( $valid_element_names as $element ) {
1282 746 $schema_styles_elements[ $element ] = $styles_non_top_level;
1283 747
1284 - if ( isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element ] ) ) {
748 + // TODO: Replace array_key_exists() with isset() check once WordPress drops
749 + // support for PHP 5.6. See https://core.trac.wordpress.org/ticket/57067.
750 + if ( array_key_exists( $element, static::VALID_ELEMENT_PSEUDO_SELECTORS ) ) {
1285 751 foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element ] as $pseudo_selector ) {
1286 752 $schema_styles_elements[ $element ][ $pseudo_selector ] = $styles_non_top_level;
1287 753 }
1288 754 }
1289 -
1290 - // Add responsive breakpoint states for elements.
1291 - foreach ( array_keys( $responsive_media_queries ) as $breakpoint_state ) {
1292 - $schema_styles_elements[ $element ][ $breakpoint_state ] = $styles_non_top_level;
1293 - }
1294 755 }
1295 756
1296 757 $schema_styles_blocks = array();
1297 758 $schema_settings_blocks = array();
1298 -
1299 - /*
1300 - * Generate a schema for blocks.
1301 - * - Block styles can contain `elements`, `variations`, and responsive breakpoint state definitions.
1302 - * - Variations definitions cannot be nested.
1303 - * - Variations can contain styles for inner `blocks`, `elements`, and responsive breakpoint states.
1304 - * - Variation inner `blocks` styles can contain `elements` and responsive breakpoint states.
1305 - *
1306 - * As each variation needs both a `blocks` schema and responsive `blocks` schemas
1307 - * for further nested inner `blocks`, the overall schema is generated in multiple passes.
1308 - */
1309 759 foreach ( $valid_block_names as $block ) {
1310 - $schema_settings_blocks[ $block ] = static::VALID_SETTINGS;
1311 - // `viewport` and `blockVisibility` are global-only settings and cannot be set per block for now.
1312 - unset( $schema_settings_blocks[ $block ]['viewport'] );
1313 - unset( $schema_settings_blocks[ $block ]['blockVisibility'] );
1314 - $schema_styles_blocks[ $block ] = $styles_non_top_level;
1315 - $schema_styles_blocks[ $block ]['elements'] = $schema_styles_elements;
1316 -
1317 - // Add responsive breakpoint states for all blocks.
1318 - foreach ( array_keys( $responsive_media_queries ) as $breakpoint_state ) {
1319 - $schema_styles_blocks[ $block ][ $breakpoint_state ] = $styles_non_top_level;
1320 - $schema_styles_blocks[ $block ][ $breakpoint_state ]['elements'] = $schema_styles_elements;
1321 -
1322 - if ( isset( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block ] ) ) {
1323 - foreach ( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block ] as $pseudo_selector ) {
1324 - $schema_styles_blocks[ $block ][ $breakpoint_state ][ $pseudo_selector ] = $styles_non_top_level;
1325 - }
1326 - }
1327 - }
1328 -
1329 - // Add pseudo-selectors for blocks that support them.
1330 - if ( isset( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block ] ) ) {
1331 - foreach ( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block ] as $pseudo_selector ) {
1332 - $schema_styles_blocks[ $block ][ $pseudo_selector ] = $styles_non_top_level;
1333 - }
1334 - }
1335 -
1336 - // Add custom states for blocks that support them (e.g. '-current' for navigation).
1337 - if ( isset( static::VALID_BLOCK_CUSTOM_STATES[ $block ] ) ) {
1338 - foreach ( static::VALID_BLOCK_CUSTOM_STATES[ $block ] as $custom_state ) {
1339 - $custom_state_schema = $styles_non_top_level;
1340 - // The same pseudo-selectors valid for the block at the top level
1341 - // are also valid within each custom state.
1342 - if ( isset( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block ] ) ) {
1343 - foreach ( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block ] as $pseudo ) {
1344 - $custom_state_schema[ $pseudo ] = $styles_non_top_level;
1345 - }
1346 - }
1347 - $schema_styles_blocks[ $block ][ $custom_state ] = $custom_state_schema;
1348 - }
1349 - }
1350 - }
1351 -
1352 - $block_style_variation_styles = static::VALID_STYLES;
1353 - $block_style_variation_styles['blocks'] = $schema_styles_blocks;
1354 - $block_style_variation_styles['elements'] = $schema_styles_elements;
1355 -
1356 - foreach ( $valid_block_names as $block ) {
1357 760 // Build the schema for each block style variation.
1358 761 $style_variation_names = array();
1359 762 if (
1360 763 ! empty( $input['styles']['blocks'][ $block ]['variations'] ) &&
@@ -1368,44 +771,22 @@
1368 771 }
1369 772
1370 773 $schema_styles_variations = array();
1371 774 if ( ! empty( $style_variation_names ) ) {
1372 - foreach ( $style_variation_names as $variation_name ) {
1373 - $variation_schema = $block_style_variation_styles;
1374 -
1375 - // Add responsive breakpoint states to block style variations.
1376 - foreach ( array_keys( $responsive_media_queries ) as $breakpoint_state ) {
1377 - $variation_schema[ $breakpoint_state ] = $styles_non_top_level;
1378 - $variation_schema[ $breakpoint_state ]['elements'] = $schema_styles_elements;
1379 - $variation_schema[ $breakpoint_state ]['blocks'] = $schema_styles_blocks;
1380 -
1381 - if ( isset( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block ] ) ) {
1382 - foreach ( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block ] as $pseudo_selector ) {
1383 - $variation_schema[ $breakpoint_state ][ $pseudo_selector ] = $styles_non_top_level;
1384 - }
1385 - }
1386 - }
1387 -
1388 - // Add pseudo-selectors to variations for blocks that support them.
1389 - if ( isset( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block ] ) ) {
1390 - foreach ( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block ] as $pseudo_selector ) {
1391 - $variation_schema[ $pseudo_selector ] = $styles_non_top_level;
1392 - }
1393 - }
1394 -
1395 - $schema_styles_variations[ $variation_name ] = $variation_schema;
1396 - }
775 + $schema_styles_variations = array_fill_keys( $style_variation_names, $styles_non_top_level );
1397 776 }
1398 777
778 + $schema_settings_blocks[ $block ] = static::VALID_SETTINGS;
779 + $schema_styles_blocks[ $block ] = $styles_non_top_level;
780 + $schema_styles_blocks[ $block ]['elements'] = $schema_styles_elements;
1399 781 $schema_styles_blocks[ $block ]['variations'] = $schema_styles_variations;
1400 782 }
1401 783
1402 - $schema['styles'] = static::VALID_STYLES;
1403 - $schema['styles']['blocks'] = $schema_styles_blocks;
1404 - $schema['styles']['elements'] = $schema_styles_elements;
1405 - $schema['settings'] = static::VALID_SETTINGS;
1406 - $schema['settings']['blocks'] = $schema_settings_blocks;
1407 - $schema['settings']['typography']['fontFamilies'] = static::schema_in_root_and_per_origin( static::FONT_FAMILY_SCHEMA );
784 + $schema['styles'] = static::VALID_STYLES;
785 + $schema['styles']['blocks'] = $schema_styles_blocks;
786 + $schema['styles']['elements'] = $schema_styles_elements;
787 + $schema['settings'] = static::VALID_SETTINGS;
788 + $schema['settings']['blocks'] = $schema_settings_blocks;
1408 789
1409 790 // Remove anything that's not present in the schema.
1410 791 foreach ( array( 'styles', 'settings' ) as $subtree ) {
1411 792 if ( ! isset( $input[ $subtree ] ) ) {
@@ -1418,12 +799,8 @@
1418 799 }
1419 800
1420 801 $result = static::remove_keys_not_in_schema( $input[ $subtree ], $schema[ $subtree ] );
1421 802
1422 - if ( 'settings' === $subtree && array_key_exists( 'viewport', $input[ $subtree ] ) ) {
1423 - $result['viewport'] = static::sanitize_viewport_settings( $input[ $subtree ]['viewport'] );
1424 - }
1425 -
1426 803 if ( empty( $result ) ) {
1427 804 unset( $output[ $subtree ] );
1428 805 } else {
1429 806 $output[ $subtree ] = static::resolve_custom_css_format( $result );
@@ -1451,38 +828,14 @@
1451 828 protected static function append_to_selector( $selector, $to_append ) {
1452 829 if ( ! str_contains( $selector, ',' ) ) {
1453 830 return $selector . $to_append;
1454 831 }
1455 -
1456 - /**
1457 - * Check for an opportunity to skip the more-costly selector splitting.
1458 - * This should be possible if there are no comments, strings, functions,
1459 - * URLs, escapes, or comment declaration openers (CDOs).
1460 - *
1461 - * Note that this means the fast-path will not apply for selectors like
1462 - * the following incomplete list:
1463 - *
1464 - * - `[class ~= "wide"]`
1465 - * - `.wp-block:is(.is-style-a, .is-style-b)`
1466 - * - `:nth-child(1)`
1467 - *
1468 - * These syntax forms all present opportunities where a comma may not
1469 - * separate selectors. If none of the start characters are present,
1470 - * there should be no way for a comma to mean anything other than a
1471 - * comma token. The exception are syntax errors, which are not handled here.
1472 - *
1473 - * @link https://www.w3.org/TR/css-syntax-3/#parse-comma-separated-list-of-component-values
1474 - */
1475 - if ( strlen( $selector ) === strcspn( $selector, '/\'"(<\\' ) ) {
1476 - return str_replace( ',', $to_append . ',', $selector ) . $to_append;
1477 - }
1478 -
1479 832 $new_selectors = array();
1480 - $selectors = static::split_selector_list( $selector );
833 + $selectors = explode( ',', $selector );
1481 834 foreach ( $selectors as $sel ) {
1482 835 $new_selectors[] = $sel . $to_append;
1483 836 }
1484 - return implode( ', ', $new_selectors );
837 + return implode( ',', $new_selectors );
1485 838 }
1486 839
1487 840 /**
1488 841 * Prepends a sub-selector to an existing one.
@@ -1500,199 +853,17 @@
1500 853 protected static function prepend_to_selector( $selector, $to_prepend ) {
1501 854 if ( ! str_contains( $selector, ',' ) ) {
1502 855 return $to_prepend . $selector;
1503 856 }
1504 -
1505 - /**
1506 - * Check for an opportunity to skip the more-costly selector splitting.
1507 - * This should be possible if there are no comments, strings, functions,
1508 - * URLs, escapes, or comment declaration openers (CDOs).
1509 - *
1510 - * Note that this means the fast-path will not apply for selectors like
1511 - * the following incomplete list:
1512 - *
1513 - * - `[class ~= "wide"]`
1514 - * - `.wp-block:is(.is-style-a, .is-style-b)`
1515 - * - `:nth-child(1)`
1516 - *
1517 - * These syntax forms all present opportunities where a comma may not
1518 - * separate selectors. If none of the start characters are present,
1519 - * there should be no way for a comma to mean anything other than a
1520 - * comma token. The exception are syntax errors, which are not handled here.
1521 - *
1522 - * @link https://www.w3.org/TR/css-syntax-3/#parse-comma-separated-list-of-component-values
1523 - */
1524 - if ( strlen( $selector ) === strcspn( $selector, '/\'"(<\\' ) ) {
1525 - return $to_prepend . str_replace( ',', ',' . $to_prepend, $selector );
1526 - }
1527 -
1528 857 $new_selectors = array();
1529 - $selectors = static::split_selector_list( $selector );
858 + $selectors = explode( ',', $selector );
1530 859 foreach ( $selectors as $sel ) {
1531 860 $new_selectors[] = $to_prepend . $sel;
1532 861 }
1533 -
1534 - return implode( ', ', $new_selectors );
862 + return implode( ',', $new_selectors );
1535 863 }
1536 864
1537 865 /**
1538 - * Splits a selector list into separate selectors.
1539 - *
1540 - * While selectors are joined by commas, not all commas separate top-level selectors.
1541 - * This method only separates top-level selectors, so some commas may appear inside
1542 - * strings, nested selectors, and comments. Leading and trailing CSS whitespace is
1543 - * trimmed from the returned list items.
1544 - *
1545 - * Non-selector content, such as comments, are retained in the list in the same item
1546 - * as the selector content they follow.
1547 - *
1548 - * Example:
1549 - *
1550 - * array( '.wp-block' ) === self::split_selector_list( '.wp-block' );
1551 - * array( '.one', '.two' ) === self::split_selector_list( '.one, .two' );
1552 - *
1553 - * // Nested selector lists are retained within their containing selector.
1554 - * array( ':is(.a, .b)', 'c' ) === self::split_selector_list( ':is(.a, .b), .c' );
1555 - *
1556 - * // Commas within strings do not separate selectors.
1557 - * $selectors = self::split_selector_list( '[data-label="Save, continue"],.fallback' );
1558 - * $selectors === array( '[data-label="Save, continue"]', '.fallback' )
1559 - *
1560 - * array( 'lang(zh, "*-hant")', '.foo' ) === self::split_selector_list( 'lang(zh, "*-hant"), .foo' );
1561 - *
1562 - * // Identifiers may contain escaped commas.
1563 - * array( '.foo\,bar', '.baz' ) === self::split_selector_list( '.foo\,bar,.baz' );
1564 - *
1565 - * // Comments stay with the selector they follow.
1566 - * array( '.a /* a, the first *\/', '.b' ) === self::split_selector_list( '.a /* a, the first *\/,.b' );
1567 - *
1568 - * @link https://www.w3.org/TR/selectors/#parse-selector
1569 - * @link https://www.w3.org/TR/css-syntax-3/
1570 - *
1571 - * @param string $selector CSS selector list.
1572 - * @return string[] Selectors.
1573 - */
1574 - protected static function split_selector_list( $selector ): array {
1575 - if ( ! str_contains( $selector, ',' ) ) {
1576 - // See note on trimming CSS whitespace in main loop.
1577 - return array( trim( $selector, " \t\n" ) );
1578 - }
1579 -
1580 - $selectors = array();
1581 - $selector_length = strlen( $selector );
1582 - $parentheses_depth = 0;
1583 - $at = 0;
1584 - $was_at = 0;
1585 -
1586 - while ( $at < $selector_length ) {
1587 - $next_at = $at + strcspn( $selector, '/,\'"()<-\\', $at );
1588 - if ( $next_at >= $selector_length ) {
1589 - break;
1590 - }
1591 -
1592 - $next_cp = $selector[ $next_at ];
1593 -
1594 - // Escaped syntax characters do not act as delimiters.
1595 - if ( '\\' === $next_cp ) {
1596 - $at = min( $next_at + 2, $selector_length );
1597 - continue;
1598 - }
1599 -
1600 - /*
1601 - * Start of a parenthesized expression, which maintains a stack of parentheses.
1602 - * For the sake of this function, no selector list will be split inside parentheses.
1603 - * Therefore it’s possible to jump ahead until this list completes.
1604 - */
1605 - if ( '(' === $next_cp || ')' === $next_cp ) {
1606 - $parentheses_depth += '(' === $next_cp ? 1 : -1;
1607 - $at = $next_at + 1;
1608 - continue;
1609 - }
1610 -
1611 - // Start of a string, which will be incorporated into the selector in which it’s found.
1612 - if ( "'" === $next_cp || '"' === $next_cp ) {
1613 - $end_of_string = $next_at + 1;
1614 - while ( $end_of_string < $selector_length ) {
1615 - $end_of_string += strcspn( $selector, "{$next_cp}\\", $end_of_string );
1616 - if ( $end_of_string >= $selector_length ) {
1617 - break;
1618 - }
1619 -
1620 - $end_cp = $selector[ $end_of_string ];
1621 -
1622 - // Skip escaped characters.
1623 - if ( '\\' === $end_cp ) {
1624 - $end_of_string = $end_of_string + 2;
1625 - continue;
1626 - }
1627 -
1628 - if ( $next_cp === $end_cp ) {
1629 - ++$end_of_string;
1630 - break;
1631 - }
1632 -
1633 - ++$end_of_string;
1634 - }
1635 -
1636 - $at = $end_of_string;
1637 - continue;
1638 - }
1639 -
1640 - // Start of a comment, which will be incorporated into the selector in which it’s found.
1641 - if ( '/' === $next_cp && ( $next_at + 1 ) < $selector_length && '*' === $selector[ $next_at + 1 ] ) {
1642 - $comment_end_at = strpos( $selector, '*/', $next_at + 1 );
1643 - $is_terminated = false !== $comment_end_at;
1644 - $after_comment = $is_terminated ? $comment_end_at + 2 : strlen( $selector );
1645 - $at = $after_comment;
1646 - continue;
1647 - }
1648 -
1649 - // Start of a CDO or CDC, which will be incorporated into the selector in which it’s found.
1650 - if (
1651 - ( '<' === $next_cp && 0 === substr_compare( $selector, '<!--', $next_at, 4 ) ) ||
1652 - ( '-' === $next_cp && 0 === substr_compare( $selector, '-->', $next_at, 3 ) )
1653 - ) {
1654 - $at = $next_at + ( '<' === $next_cp ? 4 : 3 );
1655 - continue;
1656 - }
1657 -
1658 - // Everything else is either a comma token or part of a selector.
1659 - if ( ',' === $next_cp && 0 === $parentheses_depth ) {
1660 - /**
1661 - * Trim each selector so that downstream code doesn’t see whitespace
1662 - * as the first character in a selector and get confused.
1663 - *
1664 - * There is inconsistency in this because comments and other syntax
1665 - * are included which are also not part of the selector itself, but
1666 - * a tradeoff is made between removing common syntax which carries
1667 - * no meaning and rarer syntax which leaves auxiliary information.
1668 - *
1669 - * > A newline, U+0009 CHARACTER TABULATION, or U+0020 SPACE.
1670 - * > Note that U+000D CARRIAGE RETURN and U+000C FORM FEED are
1671 - * > not included in this definition, as they are converted
1672 - * > to U+000A LINE FEED during preprocessing.
1673 - *
1674 - * @link https://www.w3.org/TR/css-syntax/#whitespace
1675 - * @link https://www.w3.org/TR/css-syntax/#newline
1676 - */
1677 - $selectors[] = trim( substr( $selector, $was_at, $next_at - $was_at ), " \t\n" );
1678 - $at = $next_at + 1;
1679 - $was_at = $at;
1680 - continue;
1681 - }
1682 -
1683 - $at = $next_at + 1;
1684 - }
1685 -
1686 - if ( $was_at < $selector_length ) {
1687 - // See note on trimming CSS whitespace in main loop.
1688 - $selectors[] = trim( substr( $selector, $was_at ), " \t\n" );
1689 - }
1690 -
1691 - return $selectors;
1692 - }
1693 -
1694 - /**
1695 866 * Returns the metadata for each block.
1696 867 *
1697 868 * Example:
1698 869 *
@@ -1717,39 +888,19 @@
1717 888 *
1718 889 * @since 5.8.0
1719 890 * @since 5.9.0 Added `duotone` key with CSS selector.
1720 891 * @since 6.1.0 Added `features` key with block support feature level selectors.
1721 - * @since 6.6.0 Added non-core block style variations to generated metadata.
1722 892 *
1723 893 * @return array Block metadata.
1724 894 */
1725 895 protected static function get_blocks_metadata() {
1726 896 // NOTE: the compat/6.1 version of this method in Gutenberg did not have these changes.
1727 - $registry = WP_Block_Type_Registry::get_instance();
1728 - $blocks = $registry->get_all_registered();
1729 - $style_registry = WP_Block_Styles_Registry::get_instance();
897 + $registry = WP_Block_Type_Registry::get_instance();
898 + $blocks = $registry->get_all_registered();
1730 899
1731 900 // Is there metadata for all currently registered blocks?
1732 901 $blocks = array_diff_key( $blocks, static::$blocks_metadata );
1733 902 if ( empty( $blocks ) ) {
1734 - /*
1735 - * New block styles may have been registered within WP_Block_Styles_Registry.
1736 - * Update block metadata for any new block style variations.
1737 - */
1738 - $registered_styles = $style_registry->get_all_registered();
1739 - foreach ( static::$blocks_metadata as $block_name => $block_metadata ) {
1740 - if ( ! empty( $registered_styles[ $block_name ] ) ) {
1741 - $style_selectors = $block_metadata['styleVariations'] ?? array();
1742 -
1743 - foreach ( $registered_styles[ $block_name ] as $block_style ) {
1744 - if ( ! isset( $style_selectors[ $block_style['name'] ] ) ) {
1745 - $style_selectors[ $block_style['name'] ] = static::get_block_style_variation_selector( $block_style['name'], $block_metadata['selector'] );
1746 - }
1747 - }
1748 -
1749 - static::$blocks_metadata[ $block_name ]['styleVariations'] = $style_selectors;
1750 - }
1751 - }
1752 903 return static::$blocks_metadata;
1753 904 }
1754 905
1755 906 foreach ( $blocks as $block_name => $block_type ) {
@@ -1767,13 +918,13 @@
1767 918 $duotone_selector = wp_get_block_css_selector( $block_type, 'filter.duotone' );
1768 919
1769 920 // Keep backwards compatibility for support.color.__experimentalDuotone.
1770 921 if ( null === $duotone_selector ) {
1771 - $duotone_support = $block_type->supports['color']['__experimentalDuotone'] ?? null;
922 + $duotone_support = _wp_array_get( $block_type->supports, array( 'color', '__experimentalDuotone' ), null );
1772 923
1773 924 if ( $duotone_support ) {
1774 925 $root_selector = wp_get_block_css_selector( $block_type );
1775 - $duotone_selector = static::scope_selector( $root_selector, $duotone_support );
926 + $duotone_selector = WP_Theme_JSON_Gutenberg::scope_selector( $root_selector, $duotone_support );
1776 927 }
1777 928 }
1778 929
1779 930 if ( null !== $duotone_selector ) {
@@ -1780,29 +931,16 @@
1780 931 static::$blocks_metadata[ $block_name ]['duotone'] = $duotone_selector;
1781 932 }
1782 933
1783 934 // If the block has style variations, append their selectors to the block metadata.
1784 - $style_selectors = array();
1785 935 if ( ! empty( $block_type->styles ) ) {
936 + $style_selectors = array();
1786 937 foreach ( $block_type->styles as $style ) {
1787 - $style_selectors[ $style['name'] ] = static::get_block_style_variation_selector( $style['name'], static::$blocks_metadata[ $block_name ]['selector'] );
938 + // The style variation classname is duplicated in the selector to ensure that it overrides core block styles.
939 + $style_selectors[ $style['name'] ] = static::append_to_selector( '.is-style-' . $style['name'] . '.is-style-' . $style['name'], static::$blocks_metadata[ $block_name ]['selector'] );
1788 940 }
1789 - }
1790 -
1791 - // Block style variations can be registered through the WP_Block_Styles_Registry as well as block.json.
1792 - $registered_styles = $style_registry->get_registered_styles_for_block( $block_name );
1793 - foreach ( $registered_styles as $style ) {
1794 - $style_selectors[ $style['name'] ] = static::get_block_style_variation_selector( $style['name'], static::$blocks_metadata[ $block_name ]['selector'] );
1795 - }
1796 -
1797 - if ( ! empty( $style_selectors ) ) {
1798 941 static::$blocks_metadata[ $block_name ]['styleVariations'] = $style_selectors;
1799 942 }
1800 -
1801 - // If the block has custom states defined in block.json, store their selectors.
1802 - if ( ! empty( $block_type->selectors['states'] ) && is_array( $block_type->selectors['states'] ) ) {
1803 - static::$blocks_metadata[ $block_name ]['states'] = $block_type->selectors['states'];
1804 - }
1805 943 }
1806 944
1807 945 return static::$blocks_metadata;
1808 946 }
@@ -1812,9 +950,8 @@
1812 950 *
1813 951 * It is recursive and modifies the input in-place.
1814 952 *
1815 953 * @since 5.8.0
1816 - * @since 7.0.0 Added type validation for boolean values.
1817 954 *
1818 955 * @param array $tree Input to process.
1819 956 * @param array $schema Schema to adhere to.
1820 957 * @return array The modified $tree.
@@ -1819,51 +956,23 @@
1819 956 * @param array $schema Schema to adhere to.
1820 957 * @return array The modified $tree.
1821 958 */
1822 959 protected static function remove_keys_not_in_schema( $tree, $schema ) {
1823 - if ( ! is_array( $tree ) ) {
1824 - return $tree;
1825 - }
960 + $tree = array_intersect_key( $tree, $schema );
1826 961
1827 - foreach ( $tree as $key => $value ) {
1828 - // Remove keys not in the schema or with null/empty values.
1829 - if ( ! array_key_exists( $key, $schema ) ) {
1830 - unset( $tree[ $key ] );
962 + foreach ( $schema as $key => $data ) {
963 + if ( ! isset( $tree[ $key ] ) ) {
1831 964 continue;
1832 965 }
1833 966
1834 - // Validate type if schema specifies a boolean marker.
1835 - if ( is_bool( $schema[ $key ] ) ) {
1836 - // Schema expects a boolean value - validate the input matches.
1837 - if ( ! is_bool( $value ) ) {
1838 - unset( $tree[ $key ] );
1839 - continue;
1840 - }
1841 - // Type matches, keep the value and continue to next key.
1842 - continue;
1843 - }
967 + if ( is_array( $schema[ $key ] ) && is_array( $tree[ $key ] ) ) {
968 + $tree[ $key ] = static::remove_keys_not_in_schema( $tree[ $key ], $schema[ $key ] );
1844 969
1845 - if ( is_array( $schema[ $key ] ) ) {
1846 - if ( ! is_array( $value ) ) {
970 + if ( empty( $tree[ $key ] ) ) {
1847 971 unset( $tree[ $key ] );
1848 - } elseif ( wp_is_numeric_array( $value ) ) {
1849 - // If indexed, process each item in the array.
1850 - foreach ( $value as $item_key => $item_value ) {
1851 - if ( isset( $schema[ $key ][0] ) && is_array( $schema[ $key ][0] ) ) {
1852 - $tree[ $key ][ $item_key ] = self::remove_keys_not_in_schema( $item_value, $schema[ $key ][0] );
1853 - } else {
1854 - // If the schema does not define a further structure, keep the value as is.
1855 - $tree[ $key ][ $item_key ] = $item_value;
1856 - }
1857 - }
1858 - } else {
1859 - // If associative, process as a single object.
1860 - $tree[ $key ] = self::remove_keys_not_in_schema( $value, $schema[ $key ] );
1861 -
1862 - if ( empty( $tree[ $key ] ) ) {
1863 - unset( $tree[ $key ] );
1864 - }
1865 972 }
973 + } elseif ( is_array( $schema[ $key ] ) && ! is_array( $tree[ $key ] ) ) {
974 + unset( $tree[ $key ] );
1866 975 }
1867 976 }
1868 977
1869 978 return $tree;
@@ -1904,23 +1013,17 @@
1904 1013 * the theme.json structure this object represents.
1905 1014 *
1906 1015 * @since 5.8.0
1907 1016 * @since 5.9.0 Removed the `$type` parameter`, added the `$types` and `$origins` parameters.
1908 - * @since 6.6.0 Added option to skip root layout or block style variation styles.
1909 1017 *
1910 1018 * @param array $types Types of styles to load. Will load all by default. It accepts:
1911 1019 * - `variables`: only the CSS Custom Properties for presets & custom ones.
1912 1020 * - `styles`: only the styles section in theme.json.
1913 1021 * - `presets`: only the classes for the presets.
1914 - * - `custom-css`: only the custom CSS.
1915 1022 * @param array $origins A list of origins to include. By default it includes VALID_ORIGINS.
1916 1023 * @param array $options An array of options for now used for internal purposes only (may change without notice).
1917 - * The options currently supported are:
1918 - * - 'scope' that makes sure all style are scoped to a given selector
1919 - * - `root_selector` which overwrites and forces a given selector to be used on the root node
1920 - * - `skip_root_layout_styles` which omits root layout styles from the generated stylesheet.
1921 - * - `base_layout_styles` which when true generates only base layout styles without alignment rules. Defaults to false.
1922 - * - `include_block_style_variations` which includes CSS for block style variations.
1024 + * The options currently supported are 'scope' that makes sure all style are scoped to a given selector,
1025 + * and root_selector which overwrites and forces a given selector to be used on the root node.
1923 1026 * @return string The resulting stylesheet.
1924 1027 */
1925 1028 public function get_stylesheet( $types = array( 'variables', 'styles', 'presets' ), $origins = null, $options = array() ) {
1926 1029 if ( null === $origins ) {
@@ -1939,9 +1042,9 @@
1939 1042 }
1940 1043 }
1941 1044
1942 1045 $blocks_metadata = static::get_blocks_metadata();
1943 - $style_nodes = static::get_style_nodes( $this->theme_json, $blocks_metadata, $options );
1046 + $style_nodes = static::get_style_nodes( $this->theme_json, $blocks_metadata );
1944 1047 $setting_nodes = static::get_setting_nodes( $this->theme_json, $blocks_metadata );
1945 1048
1946 1049 $root_style_key = array_search( static::ROOT_BLOCK_SELECTOR, array_column( $style_nodes, 'selector' ), true );
1947 1050 $root_settings_key = array_search( static::ROOT_BLOCK_SELECTOR, array_column( $setting_nodes, 'selector' ), true );
@@ -1950,11 +1053,10 @@
1950 1053 foreach ( $setting_nodes as &$node ) {
1951 1054 $node['selector'] = static::scope_selector( $options['scope'], $node['selector'] );
1952 1055 }
1953 1056 foreach ( $style_nodes as &$node ) {
1954 - $node = static::scope_style_node_selectors( $options['scope'], $node );
1057 + $node['selector'] = static::scope_selector( $options['scope'], $node['selector'] );
1955 1058 }
1956 - unset( $node );
1957 1059 }
1958 1060
1959 1061 if ( ! empty( $options['root_selector'] ) ) {
1960 1062 if ( false !== $root_settings_key ) {
@@ -1960,9 +1062,9 @@
1960 1062 if ( false !== $root_settings_key ) {
1961 1063 $setting_nodes[ $root_settings_key ]['selector'] = $options['root_selector'];
1962 1064 }
1963 1065 if ( false !== $root_style_key ) {
1964 - $style_nodes[ $root_style_key ]['selector'] = $options['root_selector'];
1066 + $setting_nodes[ $root_style_key ]['selector'] = $options['root_selector'];
1965 1067 }
1966 1068 }
1967 1069
1968 1070 $stylesheet = '';
@@ -1971,12 +1073,46 @@
1971 1073 $stylesheet .= $this->get_css_variables( $setting_nodes, $origins );
1972 1074 }
1973 1075
1974 1076 if ( in_array( 'styles', $types, true ) ) {
1975 - if ( false !== $root_style_key && empty( $options['skip_root_layout_styles'] ) ) {
1976 - $stylesheet .= $this->get_root_layout_rules( $style_nodes[ $root_style_key ]['selector'], $style_nodes[ $root_style_key ], $options );
1077 + if ( false !== $root_style_key ) {
1078 + $stylesheet .= $this->get_root_layout_rules( $style_nodes[ $root_style_key ]['selector'], $style_nodes[ $root_style_key ] );
1977 1079 }
1978 1080 $stylesheet .= $this->get_block_classes( $style_nodes );
1081 + } elseif ( in_array( 'base-layout-styles', $types, true ) ) {
1082 + $root_selector = static::ROOT_BLOCK_SELECTOR;
1083 + $columns_selector = '.wp-block-columns';
1084 + $post_template_selector = '.wp-block-post-template';
1085 + if ( ! empty( $options['scope'] ) ) {
1086 + $root_selector = static::scope_selector( $options['scope'], $root_selector );
1087 + $columns_selector = static::scope_selector( $options['scope'], $columns_selector );
1088 + $post_template_selector = static::scope_selector( $options['scope'], $post_template_selector );
1089 + }
1090 + if ( ! empty( $options['root_selector'] ) ) {
1091 + $root_selector = $options['root_selector'];
1092 + }
1093 + // Base layout styles are provided as part of `styles`, so only output separately if explicitly requested.
1094 + // For backwards compatibility, the Columns block is explicitly included, to support a different default gap value.
1095 + $base_styles_nodes = array(
1096 + array(
1097 + 'path' => array( 'styles' ),
1098 + 'selector' => $root_selector,
1099 + ),
1100 + array(
1101 + 'path' => array( 'styles', 'blocks', 'core/columns' ),
1102 + 'selector' => $columns_selector,
1103 + 'name' => 'core/columns',
1104 + ),
1105 + array(
1106 + 'path' => array( 'styles', 'blocks', 'core/post-template' ),
1107 + 'selector' => $post_template_selector,
1108 + 'name' => 'core/post-template',
1109 + ),
1110 + );
1111 +
1112 + foreach ( $base_styles_nodes as $base_style_node ) {
1113 + $stylesheet .= $this->get_layout_styles( $base_style_node );
1114 + }
1979 1115 }
1980 1116
1981 1117 if ( in_array( 'presets', $types, true ) ) {
1982 1118 $stylesheet .= $this->get_preset_classes( $setting_nodes, $origins );
@@ -1981,14 +1117,8 @@
1981 1117 if ( in_array( 'presets', $types, true ) ) {
1982 1118 $stylesheet .= $this->get_preset_classes( $setting_nodes, $origins );
1983 1119 }
1984 1120
1985 - // Load the custom CSS last so it has the highest specificity.
1986 - if ( in_array( 'custom-css', $types, true ) ) {
1987 - // Add the global styles root CSS.
1988 - $stylesheet .= _wp_array_get( $this->theme_json, array( 'styles', 'css' ) );
1989 - }
1990 -
1991 1121 return $stylesheet;
1992 1122 }
1993 1123
1994 1124 /**
@@ -1999,52 +1129,17 @@
1999 1129 * @param string $css The CSS to process.
2000 1130 * @param string $selector The selector to nest.
2001 1131 * @return string The processed CSS.
2002 1132 */
2003 - public static function process_blocks_custom_css( $css, $selector ) {
1133 + protected function process_blocks_custom_css( $css, $selector ) {
2004 1134 $processed_css = '';
2005 1135
2006 - if ( empty( $css ) ) {
2007 - return $processed_css;
2008 - }
2009 -
2010 1136 // Split CSS nested rules.
2011 1137 $parts = explode( '&', $css );
2012 1138 foreach ( $parts as $part ) {
2013 - if ( empty( $part ) ) {
2014 - continue;
2015 - }
2016 - $is_root_css = ( ! str_contains( $part, '{' ) );
2017 - if ( $is_root_css ) {
2018 - // If the part doesn't contain braces, it applies to the root level.
2019 - $processed_css .= ':root :where(' . trim( $selector ) . '){' . trim( $part ) . '}';
2020 - } else {
2021 - // If the part contains braces, it's a nested CSS rule.
2022 - $part = explode( '{', str_replace( '}', '', $part ) );
2023 - if ( count( $part ) !== 2 ) {
2024 - continue;
2025 - }
2026 - $nested_selector = $part[0];
2027 - $css_value = $part[1];
2028 -
2029 - /*
2030 - * Handle pseudo elements such as ::before, ::after etc. Regex will also
2031 - * capture any leading combinator such as >, +, or ~, as well as spaces.
2032 - * This allows pseudo elements as descendants e.g. `.parent ::before`.
2033 - */
2034 - $matches = array();
2035 - $has_pseudo_element = preg_match( '/([>+~\s]*::[a-zA-Z-]+)/', $nested_selector, $matches );
2036 - $pseudo_part = $has_pseudo_element ? $matches[1] : '';
2037 - $nested_selector = $has_pseudo_element ? str_replace( $pseudo_part, '', $nested_selector ) : $nested_selector;
2038 -
2039 - // Finalize selector and re-append pseudo element if required.
2040 - $part_selector = str_starts_with( $nested_selector, ' ' )
2041 - ? static::scope_selector( $selector, $nested_selector )
2042 - : static::append_to_selector( $selector, $nested_selector );
2043 - $final_selector = ":root :where($part_selector)$pseudo_part";
2044 -
2045 - $processed_css .= $final_selector . '{' . trim( $css_value ) . '}';
2046 - }
1139 + $processed_css .= ( ! str_contains( $part, '{' ) )
1140 + ? trim( $selector ) . '{' . trim( $part ) . '}' // If the part doesn't contain braces, it applies to the root level.
1141 + : trim( $selector . $part ); // Prepend the selector, which effectively replaces the "&" character.
2047 1142 }
2048 1143 return $processed_css;
2049 1144 }
2050 1145
@@ -2051,77 +1146,30 @@
2051 1146 /**
2052 1147 * Returns the global styles custom css.
2053 1148 *
2054 1149 * @since 6.2.0
2055 - * @deprecated 6.7.0 Use {@see 'get_stylesheet'} instead.
2056 1150 *
2057 1151 * @return string The global styles custom CSS.
2058 1152 */
2059 1153 public function get_custom_css() {
2060 - _deprecated_function( __METHOD__, '6.7.0', 'get_stylesheet' );
2061 - $block_custom_css = '';
2062 - $block_nodes = $this->get_block_custom_css_nodes();
2063 - foreach ( $block_nodes as $node ) {
2064 - // The node selector will have its specificity set to 0-1-0 within process_blocks_custom_css.
2065 - $block_custom_css .= $this->get_block_custom_css( $node['css'], $node['selector'] );
2066 - }
1154 + // Add the global styles root CSS.
1155 + $stylesheet = _wp_array_get( $this->theme_json, array( 'styles', 'css' ), '' );
2067 1156
2068 - return $this->get_base_custom_css() . $block_custom_css;
2069 - }
2070 -
2071 - /**
2072 - * Returns the global styles base custom CSS.
2073 - * This function is deprecated; please do not sync to core.
2074 - *
2075 - * @return string The global styles base custom CSS.
2076 - */
2077 - public function get_base_custom_css() {
2078 - _deprecated_function( __METHOD__, 'Gutenberg 18.6.0', 'get_stylesheet' );
2079 - return $this->theme_json['styles']['css'] ?? '';
2080 - }
2081 -
2082 - /**
2083 - * Returns the block nodes with custom CSS.
2084 - * This function is deprecated; please do not sync to core.
2085 - *
2086 - * @return array The block nodes.
2087 - */
2088 - public function get_block_custom_css_nodes() {
2089 - _deprecated_function( __METHOD__, 'Gutenberg 18.6.0', 'get_block_nodes' );
2090 - $block_nodes = array();
2091 -
2092 1157 // Add the global styles block CSS.
2093 1158 if ( isset( $this->theme_json['styles']['blocks'] ) ) {
2094 1159 foreach ( $this->theme_json['styles']['blocks'] as $name => $node ) {
2095 - $custom_block_css = $this->theme_json['styles']['blocks'][ $name ]['css'] ?? null;
1160 + $custom_block_css = _wp_array_get( $this->theme_json, array( 'styles', 'blocks', $name, 'css' ) );
2096 1161 if ( $custom_block_css ) {
2097 - $block_nodes[] = array(
2098 - 'name' => $name,
2099 - 'selector' => static::$blocks_metadata[ $name ]['selector'],
2100 - 'css' => $custom_block_css,
2101 - );
1162 + $selector = static::$blocks_metadata[ $name ]['selector'];
1163 + $stylesheet .= $this->process_blocks_custom_css( $custom_block_css, $selector );
2102 1164 }
2103 1165 }
2104 1166 }
2105 1167
2106 - return $block_nodes;
1168 + return $stylesheet;
2107 1169 }
2108 1170
2109 1171 /**
2110 - * Returns the global styles custom CSS for a single block.
2111 - * This function is deprecated; please do not sync to core.
2112 - *
2113 - * @param array $css The block css node.
2114 - * @param string $selector The block selector.
2115 - *
2116 - * @return string The global styles custom CSS for the block.
2117 - */
2118 - public function get_block_custom_css( $css, $selector ) {
2119 - _deprecated_function( __METHOD__, 'Gutenberg 18.6.0', 'get_styles_for_block' );
2120 - return $this->process_blocks_custom_css( $css, $selector );
2121 - }
2122 -
2123 - /**
2124 1172 * Returns the page templates of the active theme.
2125 1173 *
2126 1174 * @since 5.9.0
2127 1175 *
@@ -2135,10 +1183,10 @@
2135 1183
2136 1184 foreach ( $this->theme_json['customTemplates'] as $item ) {
2137 1185 if ( isset( $item['name'] ) ) {
2138 1186 $custom_templates[ $item['name'] ] = array(
2139 - 'title' => $item['title'] ?? '',
2140 - 'postTypes' => $item['postTypes'] ?? array( 'page' ),
1187 + 'title' => isset( $item['title'] ) ? $item['title'] : '',
1188 + 'postTypes' => isset( $item['postTypes'] ) ? $item['postTypes'] : array( 'page' ),
2141 1189 );
2142 1190 }
2143 1191 }
2144 1192 return $custom_templates;
@@ -2159,10 +1207,10 @@
2159 1207
2160 1208 foreach ( $this->theme_json['templateParts'] as $item ) {
2161 1209 if ( isset( $item['name'] ) ) {
2162 1210 $template_parts[ $item['name'] ] = array(
2163 - 'title' => $item['title'] ?? '',
2164 - 'area' => $item['area'] ?? '',
1211 + 'title' => isset( $item['title'] ) ? $item['title'] : '',
1212 + 'area' => isset( $item['area'] ) ? $item['area'] : '',
2165 1213 );
2166 1214 }
2167 1215 }
2168 1216 return $template_parts;
@@ -2207,12 +1255,11 @@
2207 1255 *
2208 1256 * @since 6.1.0
2209 1257 *
2210 1258 * @param array $block_metadata Metadata about the block to get styles for.
2211 - * @param array $options Optional. An array of options for now used for internal purposes only.
2212 1259 * @return string Layout styles for the block.
2213 1260 */
2214 - protected function get_layout_styles( $block_metadata, $options = array() ) {
1261 + protected function get_layout_styles( $block_metadata ) {
2215 1262 $block_rules = '';
2216 1263 $block_type = null;
2217 1264
2218 1265 // Skip outputting layout styles if explicitly disabled.
@@ -2226,14 +1273,14 @@
2226 1273 return $block_rules;
2227 1274 }
2228 1275 }
2229 1276
2230 - $selector = $block_metadata['selector'] ?? '';
2231 - $has_block_gap_support = isset( $this->theme_json['settings']['spacing']['blockGap'] );
1277 + $selector = isset( $block_metadata['selector'] ) ? $block_metadata['selector'] : '';
1278 + $has_block_gap_support = _wp_array_get( $this->theme_json, array( 'settings', 'spacing', 'blockGap' ) ) !== null;
2232 1279 $has_fallback_gap_support = ! $has_block_gap_support; // This setting isn't useful yet: it exists as a placeholder for a future explicit fallback gap styles support.
2233 - $node = $options['node'] ?? _wp_array_get( $this->theme_json, $block_metadata['path'], array() );
1280 + $node = _wp_array_get( $this->theme_json, $block_metadata['path'], array() );
2234 1281 $layout_definitions = gutenberg_get_layout_definitions();
2235 - $layout_selector_pattern = '/^[a-zA-Z0-9\-\.\,\ *+>:\(\)]*$/'; // Allow alphanumeric classnames, spaces, wildcard, sibling, child combinator and pseudo class selectors.
1282 + $layout_selector_pattern = '/^[a-zA-Z0-9\-\.\ *+>:\(\)]*$/'; // Allow alphanumeric classnames, spaces, wildcard, sibling, child combinator and pseudo class selectors.
2236 1283
2237 1284 // Gap styles will only be output if the theme has block gap support, or supports a fallback gap.
2238 1285 // Default layout gap styles will be skipped for themes that do not explicitly opt-in to blockGap with a `true` or `false` value.
2239 1286 if ( $has_block_gap_support || $has_fallback_gap_support ) {
@@ -2241,9 +1288,9 @@
2241 1288 // Use a fallback gap value if block gap support is not available.
2242 1289 if ( ! $has_block_gap_support ) {
2243 1290 $block_gap_value = static::ROOT_BLOCK_SELECTOR === $selector ? '0.5em' : null;
2244 1291 if ( ! empty( $block_type ) ) {
2245 - $block_gap_value = $block_type->supports['spacing']['blockGap']['__experimentalDefault'] ?? null;
1292 + $block_gap_value = _wp_array_get( $block_type->supports, array( 'spacing', 'blockGap', '__experimentalDefault' ), null );
2246 1293 }
2247 1294 } else {
2248 1295 $block_gap_value = static::get_property_value( $node, array( 'spacing', 'blockGap' ) );
2249 1296 }
@@ -2267,10 +1314,10 @@
2267 1314 if ( ! $has_block_gap_support && 'flex' !== $layout_definition_key && 'grid' !== $layout_definition_key ) {
2268 1315 continue;
2269 1316 }
2270 1317
2271 - $class_name = $layout_definition['className'] ?? false;
2272 - $spacing_rules = $layout_definition['spacingStyles'] ?? array();
1318 + $class_name = sanitize_title( _wp_array_get( $layout_definition, array( 'className' ), false ) );
1319 + $spacing_rules = _wp_array_get( $layout_definition, array( 'spacingStyles' ), array() );
2273 1320
2274 1321 if (
2275 1322 ! empty( $class_name ) &&
2276 1323 ! empty( $spacing_rules )
@@ -2302,9 +1349,9 @@
2302 1349 $class_name,
2303 1350 $spacing_rule['selector']
2304 1351 );
2305 1352 } else {
2306 - $format = static::ROOT_BLOCK_SELECTOR === $selector ? ':root :where(.%2$s)%3$s' : ':root :where(%1$s-%2$s)%3$s';
1353 + $format = static::ROOT_BLOCK_SELECTOR === $selector ? ':where(%s .%s) %s' : '%s-%s%s';
2307 1354 $layout_selector = sprintf(
2308 1355 $format,
2309 1356 $selector,
2310 1357 $class_name,
@@ -2324,10 +1371,10 @@
2324 1371 static::ROOT_BLOCK_SELECTOR === $selector
2325 1372 ) {
2326 1373 $valid_display_modes = array( 'block', 'flex', 'grid' );
2327 1374 foreach ( $layout_definitions as $layout_definition ) {
2328 - $class_name = $layout_definition['className'] ?? false;
2329 - $base_style_rules = $layout_definition['baseStyles'] ?? array();
1375 + $class_name = sanitize_title( _wp_array_get( $layout_definition, array( 'className' ), false ) );
1376 + $base_style_rules = _wp_array_get( $layout_definition, array( 'baseStyles' ), array() );
2330 1377
2331 1378 if (
2332 1379 ! empty( $class_name ) &&
2333 1380 is_array( $base_style_rules )
@@ -2356,14 +1403,8 @@
2356 1403
2357 1404 foreach ( $base_style_rules as $base_style_rule ) {
2358 1405 $declarations = array();
2359 1406
2360 - // Skip outputting base styles for flow and constrained layout types when base_layout_styles is enabled.
2361 - // These themes don't use .wp-site-blocks wrapper, so these layout-specific alignment styles aren't needed.
2362 - if ( ! empty( $options['base_layout_styles'] ) && ( 'default' === $layout_definition['name'] || 'constrained' === $layout_definition['name'] ) ) {
2363 - continue;
2364 - }
2365 -
2366 1407 if (
2367 1408 isset( $base_style_rule['selector'] ) &&
2368 1409 preg_match( $layout_selector_pattern, $base_style_rule['selector'] ) &&
2369 1410 ! empty( $base_style_rule['rules'] )
@@ -2368,18 +1409,8 @@
2368 1409 preg_match( $layout_selector_pattern, $base_style_rule['selector'] ) &&
2369 1410 ! empty( $base_style_rule['rules'] )
2370 1411 ) {
2371 1412 foreach ( $base_style_rule['rules'] as $css_property => $css_value ) {
2372 - // Skip rules that reference content size or wide size if they are not defined in the theme.json.
2373 - if (
2374 - is_string( $css_value ) &&
2375 - ( str_contains( $css_value, '--global--content-size' ) || str_contains( $css_value, '--global--wide-size' ) ) &&
2376 - ! isset( $this->theme_json['settings']['layout']['contentSize'] ) &&
2377 - ! isset( $this->theme_json['settings']['layout']['wideSize'] )
2378 - ) {
2379 - continue;
2380 - }
2381 -
2382 1413 if ( static::is_safe_css_declaration( $css_property, $css_value ) ) {
2383 1414 $declarations[] = array(
2384 1415 'name' => $css_property,
2385 1416 'value' => $css_value,
@@ -2387,9 +1418,10 @@
2387 1418 }
2388 1419 }
2389 1420
2390 1421 $layout_selector = sprintf(
2391 - '.%s%s',
1422 + '%s .%s%s',
1423 + $selector,
2392 1424 $class_name,
2393 1425 $base_style_rule['selector']
2394 1426 );
2395 1427 $block_rules .= static::to_ruleset( $layout_selector, $declarations );
@@ -2397,13 +1429,8 @@
2397 1429 }
2398 1430 }
2399 1431 }
2400 1432 }
2401 -
2402 - if ( ! empty( $options['media_query'] ) && ! empty( $block_rules ) ) {
2403 - $block_rules = $options['media_query'] . '{' . $block_rules . '}';
2404 - }
2405 -
2406 1433 return $block_rules;
2407 1434 }
2408 1435
2409 1436 /**
@@ -2424,9 +1451,9 @@
2424 1451 * .has-value-gradient-background {
2425 1452 * background: value;
2426 1453 * }
2427 1454 *
2428 - * :where(p).has-value-gradient-background {
1455 + * p.has-value-gradient-background {
2429 1456 * background: value;
2430 1457 * }
2431 1458 *
2432 1459 * @since 5.9.0
@@ -2478,53 +1505,18 @@
2478 1505 if ( null === $metadata['selector'] ) {
2479 1506 continue;
2480 1507 }
2481 1508
2482 - $selector = $metadata['selector'];
2483 - $feature_selectors = $metadata['selectors'] ?? array();
2484 - $node = _wp_array_get( $this->theme_json, $metadata['path'], array() );
1509 + $selector = $metadata['selector'];
2485 1510
2486 - /*
2487 - * Group preset declarations by selector. Blocks that define
2488 - * feature-level selectors need their preset CSS variables
2489 - * output under that feature selector instead of the block's
2490 - * root selector.
2491 - */
2492 - $vars_by_selector = array();
2493 - $vars_by_selector[ $selector ] = array();
2494 -
2495 - foreach ( static::PRESETS_METADATA as $preset_metadata ) {
2496 - if ( empty( $preset_metadata['css_vars'] ) ) {
2497 - continue;
2498 - }
2499 -
2500 - $values_by_slug = static::get_settings_values_by_slug( $node, $preset_metadata, $origins );
2501 - if ( empty( $values_by_slug ) ) {
2502 - continue;
2503 - }
2504 -
2505 - $target = static::get_feature_selector( $feature_selectors, $preset_metadata['path'][0], $selector );
2506 -
2507 - if ( ! isset( $vars_by_selector[ $target ] ) ) {
2508 - $vars_by_selector[ $target ] = array();
2509 - }
2510 -
2511 - foreach ( $values_by_slug as $slug => $value ) {
2512 - $vars_by_selector[ $target ][] = array(
2513 - 'name' => static::replace_slug_in_string( $preset_metadata['css_vars'], $slug ),
2514 - 'value' => $value,
2515 - );
2516 - }
1511 + $node = _wp_array_get( $this->theme_json, $metadata['path'], array() );
1512 + $declarations = static::compute_preset_vars( $node, $origins );
1513 + $theme_vars_declarations = static::compute_theme_vars( $node );
1514 + foreach ( $theme_vars_declarations as $theme_vars_declaration ) {
1515 + $declarations[] = $theme_vars_declaration;
2517 1516 }
2518 1517
2519 - // Theme vars always use the block's default selector.
2520 - foreach ( static::compute_theme_vars( $node ) as $theme_var ) {
2521 - $vars_by_selector[ $selector ][] = $theme_var;
2522 - }
2523 -
2524 - foreach ( $vars_by_selector as $rule_selector => $declarations ) {
2525 - $stylesheet .= static::to_ruleset( $rule_selector, $declarations );
2526 - }
1518 + $stylesheet .= static::to_ruleset( $selector, $declarations );
2527 1519 }
2528 1520
2529 1521 return $stylesheet;
2530 1522 }
@@ -2529,44 +1521,12 @@
2529 1521 return $stylesheet;
2530 1522 }
2531 1523
2532 1524 /**
2533 - * Returns the appropriate selector for a block support feature's
2534 - * preset CSS variables.
2535 - *
2536 - * If the block defines a feature-level selector (as a string or an
2537 - * object with a `root` key), that selector is returned. Otherwise,
2538 - * the block's default selector is used.
2539 - *
2540 - * @param array<string, string|array<string, string>> $feature_selectors The block's feature selectors map.
2541 - * @param string $feature_key The feature to look up (e.g. 'dimensions').
2542 - * @param string $default_selector Fallback selector.
2543 - * @return string The resolved selector.
2544 - */
2545 - private static function get_feature_selector( array $feature_selectors, string $feature_key, string $default_selector ): string {
2546 - if ( ! isset( $feature_selectors[ $feature_key ] ) ) {
2547 - return $default_selector;
2548 - }
2549 -
2550 - $feature = $feature_selectors[ $feature_key ];
2551 -
2552 - if ( is_string( $feature ) ) {
2553 - return $feature;
2554 - }
2555 -
2556 - if ( isset( $feature['root'] ) && is_string( $feature['root'] ) ) {
2557 - return $feature['root'];
2558 - }
2559 -
2560 - return $default_selector;
2561 - }
2562 -
2563 - /**
2564 1525 * Given a selector and a declaration list,
2565 1526 * creates the corresponding ruleset.
2566 1527 *
2567 1528 * @since 5.8.0
2568 - * @since 7.1.0 Skip declarations whose value is not a plain string (booleans, arrays, objects, etc.).
2569 1529 *
2570 1530 * @param string $selector CSS selector.
2571 1531 * @param array $declarations List of declarations.
2572 1532 * @return string The resulting CSS ruleset.
@@ -2578,20 +1538,9 @@
2578 1538
2579 1539 $declaration_block = array_reduce(
2580 1540 $declarations,
2581 1541 static function ( $carry, $element ) {
2582 - $value = $element['value'];
2583 -
2584 - if ( is_numeric( $value ) ) {
2585 - $value = (string) $value;
2586 - }
2587 -
2588 - if ( ! is_string( $value ) ) {
2589 - return $carry;
2590 - }
2591 -
2592 - return $carry .= $element['name'] . ': ' . $value . ';';
2593 - },
1542 + return $carry .= $element['name'] . ': ' . $element['value'] . ';'; },
2594 1543 ''
2595 1544 );
2596 1545
2597 1546 return $selector . '{' . $declaration_block . '}';
@@ -2609,9 +1558,9 @@
2609 1558 * @param array $origins List of origins to process.
2610 1559 * @return string The result of processing the presets.
2611 1560 */
2612 1561 protected static function compute_preset_classes( $settings, $selector, $origins ) {
2613 - if ( static::ROOT_BLOCK_SELECTOR === $selector || static::ROOT_CSS_PROPERTIES_SELECTOR === $selector ) {
1562 + if ( static::ROOT_BLOCK_SELECTOR === $selector ) {
2614 1563 // Classes at the global level do not need any CSS prefixed,
2615 1564 // and we don't want to increase its specificity.
2616 1565 $selector = '';
2617 1566 }
@@ -2617,12 +1566,8 @@
2617 1566 }
2618 1567
2619 1568 $stylesheet = '';
2620 1569 foreach ( static::PRESETS_METADATA as $preset_metadata ) {
2621 - if ( empty( $preset_metadata['classes'] ) ) {
2622 - continue;
2623 - }
2624 -
2625 1570 $slugs = static::get_settings_slugs( $settings, $preset_metadata, $origins );
2626 1571 foreach ( $preset_metadata['classes'] as $class => $property ) {
2627 1572 foreach ( $slugs as $slug ) {
2628 1573 $css_var = static::replace_slug_in_string( $preset_metadata['css_vars'], $slug );
@@ -2627,18 +1572,10 @@
2627 1572 foreach ( $slugs as $slug ) {
2628 1573 $css_var = static::replace_slug_in_string( $preset_metadata['css_vars'], $slug );
2629 1574 $class_name = static::replace_slug_in_string( $class, $slug );
2630 1575
2631 - /*
2632 - * $selector is often empty (root-level presets), in which case the
2633 - * bare class is used. For block-level presets the block selector is
2634 - * wrapped in `:where()` so the class keeps the same 0-1-0 specificity
2635 - * as a root-level preset. Without this, block-level palette rules
2636 - * (e.g. `p.has-x-color`) out-rank equally-important rules that also
2637 - * target the same property at 0-1-0, such as per-instance responsive
2638 - * state styles.
2639 - */
2640 - $new_selector = '' === $selector ? $class_name : ':where(' . $selector . ')' . $class_name;
1576 + // $selector is often empty, so we can save ourselves the `append_to_selector()` call then.
1577 + $new_selector = '' === $selector ? $class_name : static::append_to_selector( $selector, $class_name );
2641 1578 $stylesheet .= static::to_ruleset(
2642 1579 $new_selector,
2643 1580 array(
2644 1581 array(
@@ -2671,18 +1608,16 @@
2671 1608 * @param string $selector Original selector.
2672 1609 * @return string Scoped selector.
2673 1610 */
2674 1611 public static function scope_selector( $scope, $selector ) {
2675 - if ( ! $scope || ! $selector ) {
2676 - return $selector;
2677 - }
1612 + $scopes = explode( ',', $scope );
1613 + $selectors = explode( ',', $selector );
2678 1614
2679 - $scopes = static::split_selector_list( $scope );
2680 - $selectors = static::split_selector_list( $selector );
2681 -
2682 1615 $selectors_scoped = array();
2683 1616 foreach ( $scopes as $outer ) {
2684 1617 foreach ( $selectors as $inner ) {
1618 + $outer = trim( $outer );
1619 + $inner = trim( $inner );
2685 1620 if ( ! empty( $outer ) && ! empty( $inner ) ) {
2686 1621 $selectors_scoped[] = $outer . ' ' . $inner;
2687 1622 } elseif ( empty( $outer ) ) {
2688 1623 $selectors_scoped[] = $inner;
@@ -2696,41 +1631,8 @@
2696 1631 return $result;
2697 1632 }
2698 1633
2699 1634 /**
2700 - * Scopes the selectors for a given style node. This includes the primary
2701 - * selector, i.e. `$node['selector']`, as well as any custom selectors for
2702 - * features and subfeatures, e.g. `$node['selectors']['border']` etc.
2703 - *
2704 - * @since 6.6.0
2705 - *
2706 - * @param string $scope Selector to scope to.
2707 - * @param array $node Style node with selectors to scope.
2708 - *
2709 - * @return array Node with updated selectors.
2710 - */
2711 - protected static function scope_style_node_selectors( $scope, $node ) {
2712 - $node['selector'] = static::scope_selector( $scope, $node['selector'] );
2713 -
2714 - if ( empty( $node['selectors'] ) ) {
2715 - return $node;
2716 - }
2717 -
2718 - foreach ( $node['selectors'] as $feature => $selector ) {
2719 - if ( is_string( $selector ) ) {
2720 - $node['selectors'][ $feature ] = static::scope_selector( $scope, $selector );
2721 - }
2722 - if ( is_array( $selector ) ) {
2723 - foreach ( $selector as $subfeature => $subfeature_selector ) {
2724 - $node['selectors'][ $feature ][ $subfeature ] = static::scope_selector( $scope, $subfeature_selector );
2725 - }
2726 - }
2727 - }
2728 -
2729 - return $node;
2730 - }
2731 -
2732 - /**
2733 1635 * Gets preset values keyed by slugs based on settings and metadata.
2734 1636 *
2735 1637 * <code>
2736 1638 * $settings = array(
@@ -2758,9 +1660,8 @@
2758 1660 * // );
2759 1661 * </code>
2760 1662 *
2761 1663 * @since 5.9.0
2762 - * @since 6.6.0 Passing $settings to the callbacks defined in static::PRESETS_METADATA.
2763 1664 *
2764 1665 * @param array $settings Settings to process.
2765 1666 * @param array $preset_metadata One of the PRESETS_METADATA values.
2766 1667 * @param array $origins List of origins to process.
@@ -2785,9 +1686,9 @@
2785 1686 isset( $preset_metadata['value_func'] ) &&
2786 1687 is_callable( $preset_metadata['value_func'] )
2787 1688 ) {
2788 1689 $value_func = $preset_metadata['value_func'];
2789 - $value = call_user_func( $value_func, $preset, $settings );
1690 + $value = call_user_func( $value_func, $preset );
2790 1691 } else {
2791 1692 // If we don't have a value, then don't add it to the result.
2792 1693 continue;
2793 1694 }
@@ -2864,12 +1765,8 @@
2864 1765 */
2865 1766 protected static function compute_preset_vars( $settings, $origins ) {
2866 1767 $declarations = array();
2867 1768 foreach ( static::PRESETS_METADATA as $preset_metadata ) {
2868 - if ( empty( $preset_metadata['css_vars'] ) ) {
2869 - continue;
2870 - }
2871 -
2872 1769 $values_by_slug = static::get_settings_values_by_slug( $settings, $preset_metadata, $origins );
2873 1770 foreach ( $values_by_slug as $slug => $value ) {
2874 1771 $declarations[] = array(
2875 1772 'name' => static::replace_slug_in_string( $preset_metadata['css_vars'], $slug ),
@@ -2899,9 +1796,9 @@
2899 1796 * @return array The modified $declarations.
2900 1797 */
2901 1798 protected static function compute_theme_vars( $settings ) {
2902 1799 $declarations = array();
2903 - $custom_values = $settings['custom'] ?? array();
1800 + $custom_values = _wp_array_get( $settings, array( 'custom' ), array() );
2904 1801 $css_vars = static::flatten_tree( $custom_values );
2905 1802 foreach ( $css_vars as $key => $value ) {
2906 1803 $declarations[] = array(
2907 1804 'name' => '--wp--custom--' . $key,
@@ -2977,9 +1874,9 @@
2977 1874 *
2978 1875 * ```php
2979 1876 * array(
2980 1877 * 'name' => 'property_name',
2981 - * 'value' => 'property_value',
1878 + * 'value' => 'property_value,
2982 1879 * )
2983 1880 * ```
2984 1881 *
2985 1882 * @since 5.8.0
@@ -2984,11 +1881,8 @@
2984 1881 *
2985 1882 * @since 5.8.0
2986 1883 * @since 5.9.0 Added the `$settings` and `$properties` parameters.
2987 1884 * @since 6.1.0 Added `$theme_json`, `$selector`, and `$use_root_padding` parameters.
2988 - * @since 6.5.0 Output a `min-height: unset` rule when `aspect-ratio` is set.
2989 - * @since 6.6.0 Passing current theme JSON settings to wp_get_typography_font_size_value(). Using style engine to correctly fetch background CSS values.
2990 - * @since 6.7.0 Allow ref resolution of background properties.
2991 1885 *
2992 1886 * @param array $styles Styles to process.
2993 1887 * @param array $settings Theme settings.
2994 1888 * @param array $properties Properties metadata.
@@ -2997,31 +1891,25 @@
2997 1891 * @param boolean $use_root_padding Whether to add custom properties at root level.
2998 1892 * @return array Returns the modified $declarations.
2999 1893 */
3000 1894 protected static function compute_style_properties( $styles, $settings = array(), $properties = null, $theme_json = null, $selector = null, $use_root_padding = null ) {
1895 + if ( null === $properties ) {
1896 + $properties = static::PROPERTIES_METADATA;
1897 + }
1898 +
1899 + $declarations = array();
3001 1900 if ( empty( $styles ) ) {
3002 - return array();
1901 + return $declarations;
3003 1902 }
3004 1903
3005 - if ( null === $properties ) {
3006 - $properties = static::PROPERTIES_METADATA;
3007 - }
3008 - $declarations = array();
3009 1904 $root_variable_duplicates = array();
3010 - $root_style_length = strlen( '--wp--style--root--' );
3011 1905
3012 1906 foreach ( $properties as $css_property => $value_path ) {
3013 - if ( ! is_array( $value_path ) ) {
3014 - continue;
3015 - }
1907 + $value = static::get_property_value( $styles, $value_path, $theme_json );
3016 1908
3017 - $is_root_style = str_starts_with( $css_property, '--wp--style--root--' );
3018 - if ( $is_root_style && ( static::ROOT_BLOCK_SELECTOR !== $selector || ! $use_root_padding ) ) {
1909 + if ( str_starts_with( $css_property, '--wp--style--root--' ) && ( static::ROOT_BLOCK_SELECTOR !== $selector || ! $use_root_padding ) ) {
3019 1910 continue;
3020 1911 }
3021 -
3022 - $value = static::get_property_value( $styles, $value_path, $theme_json );
3023 -
3024 1912 // Root-level padding styles don't currently support strings with CSS shorthand values.
3025 1913 // This may change: https://github.com/WordPress/gutenberg/issues/40132.
3026 1914 if ( '--wp--style--root--padding' === $css_property && is_string( $value ) ) {
3027 1915 continue;
@@ -3026,44 +1914,25 @@
3026 1914 if ( '--wp--style--root--padding' === $css_property && is_string( $value ) ) {
3027 1915 continue;
3028 1916 }
3029 1917
3030 - if ( $is_root_style && $use_root_padding ) {
3031 - $root_variable_duplicates[] = substr( $css_property, $root_style_length );
1918 + if ( str_starts_with( $css_property, '--wp--style--root--' ) && $use_root_padding ) {
1919 + $root_variable_duplicates[] = substr( $css_property, strlen( '--wp--style--root--' ) );
3032 1920 }
3033 1921
3034 - /*
3035 - * Processes background image styles.
3036 - * If the value is a URL, it will be converted to a CSS `url()` value.
3037 - * For an uploaded image (images with a database ID), apply size and position
3038 - * defaults equal to those applied in block supports in lib/background.php.
3039 - */
3040 - if ( 'background-image' === $css_property ) {
3041 - $background_image_input = array();
3042 - if ( ! empty( $value ) ) {
3043 - $background_image_input['backgroundImage'] = $value;
1922 + // Look up protected properties, keyed by value path.
1923 + // Skip protected properties that are explicitly set to `null`.
1924 + if ( is_array( $value_path ) ) {
1925 + $path_string = implode( '.', $value_path );
1926 + if (
1927 + // TODO: Replace array_key_exists() with isset() check once WordPress drops
1928 + // support for PHP 5.6. See https://core.trac.wordpress.org/ticket/57067.
1929 + array_key_exists( $path_string, static::PROTECTED_PROPERTIES ) &&
1930 + _wp_array_get( $settings, static::PROTECTED_PROPERTIES[ $path_string ], null ) === null
1931 + ) {
1932 + continue;
3044 1933 }
3045 - $gradient_value = $styles['background']['gradient'] ?? null;
3046 - if ( ! empty( $gradient_value ) ) {
3047 - $background_image_input['gradient'] = $gradient_value;
3048 - }
3049 - if ( ! empty( $background_image_input ) ) {
3050 - $background_styles = gutenberg_style_engine_get_styles(
3051 - array( 'background' => $background_image_input )
3052 - );
3053 - $value = $background_styles['declarations'][ $css_property ] ?? null;
3054 - }
3055 1934 }
3056 - if ( empty( $value ) && static::ROOT_BLOCK_SELECTOR !== $selector && ! empty( $styles['background']['backgroundImage']['id'] ) ) {
3057 - if ( 'background-size' === $css_property ) {
3058 - $value = 'cover';
3059 - }
3060 - // If the background size is set to `contain` and no position is set, set the position to `center`.
3061 - if ( 'background-position' === $css_property ) {
3062 - $background_size = $styles['background']['backgroundSize'] ?? null;
3063 - $value = 'contain' === $background_size ? '50% 50%' : null;
3064 - }
3065 - }
3066 1935
3067 1936 // Skip if empty and not "0" or value represents array of longhand values.
3068 1937 $has_missing_value = empty( $value ) && ! is_numeric( $value );
3069 1938 if ( $has_missing_value || is_array( $value ) ) {
@@ -3077,22 +1946,12 @@
3077 1946 * if fluid typography has been activated and also
3078 1947 * whether the incoming value can be converted to a fluid value.
3079 1948 * Values that already have a clamp() function will not pass the test,
3080 1949 * and therefore the original $value will be returned.
3081 - * Pass the current theme_json settings to override any global settings.
3082 1950 */
3083 - $value = gutenberg_get_typography_font_size_value( array( 'size' => $value ), $settings );
1951 + $value = gutenberg_get_typography_font_size_value( array( 'size' => $value ) );
3084 1952 }
3085 1953
3086 - if ( 'aspect-ratio' === $css_property ) {
3087 - // For aspect ratio to work, other dimensions rules must be unset.
3088 - // This ensures that a fixed height does not override the aspect ratio.
3089 - $declarations[] = array(
3090 - 'name' => 'min-height',
3091 - 'value' => 'unset',
3092 - );
3093 - }
3094 -
3095 1954 $declarations[] = array(
3096 1955 'name' => $css_property,
3097 1956 'value' => $value,
3098 1957 );
@@ -3118,9 +1977,8 @@
3118 1977 *
3119 1978 * @since 5.8.0
3120 1979 * @since 5.9.0 Added support for values of array type, which are returned as is.
3121 1980 * @since 6.1.0 Added the `$theme_json` parameter.
3122 - * @since 6.7.0 Added support for background image refs
3123 1981 *
3124 1982 * @param array $styles Styles subtree.
3125 1983 * @param array $path Which property to process.
3126 1984 * @param array $theme_json Theme JSON array.
@@ -3135,19 +1993,17 @@
3135 1993 return '';
3136 1994 }
3137 1995
3138 1996 /*
3139 - * Where the current value is an array with a 'ref' key pointing
3140 - * to a path, this converts that path into the value at that path.
1997 + * This converts references to a path to the value at that path
1998 + * where the values is an array with a "ref" key, pointing to a path.
3141 1999 * For example: { "ref": "style.color.background" } => "#fff".
3142 2000 */
3143 2001 if ( is_array( $value ) && isset( $value['ref'] ) ) {
3144 2002 $value_path = explode( '.', $value['ref'] );
3145 - $ref_value = _wp_array_get( $theme_json, $value_path, null );
3146 - // Background Image refs can refer to a string or an array containing a URL string.
3147 - $ref_value_url = $ref_value['url'] ?? null;
2003 + $ref_value = _wp_array_get( $theme_json, $value_path );
3148 2004 // Only use the ref value if we find anything.
3149 - if ( ! empty( $ref_value ) && ( is_string( $ref_value ) || is_string( $ref_value_url ) ) ) {
2005 + if ( ! empty( $ref_value ) && is_string( $ref_value ) ) {
3150 2006 $value = $ref_value;
3151 2007 }
3152 2008
3153 2009 if ( is_array( $ref_value ) && isset( $ref_value['ref'] ) ) {
@@ -3167,8 +2023,12 @@
3167 2023 );
3168 2024 }
3169 2025 }
3170 2026
2027 + if ( is_array( $value ) ) {
2028 + return $value;
2029 + }
2030 +
3171 2031 return $value;
3172 2032 }
3173 2033
3174 2034 /**
@@ -3199,9 +2059,9 @@
3199 2059
3200 2060 // Top-level.
3201 2061 $nodes[] = array(
3202 2062 'path' => array( 'settings' ),
3203 - 'selector' => static::ROOT_CSS_PROPERTIES_SELECTOR,
2063 + 'selector' => static::ROOT_BLOCK_SELECTOR,
3204 2064 );
3205 2065
3206 2066 // Calculate paths for blocks.
3207 2067 if ( ! isset( $theme_json['settings']['blocks'] ) ) {
@@ -3214,11 +2074,10 @@
3214 2074 $selector = $selectors[ $name ]['selector'];
3215 2075 }
3216 2076
3217 2077 $nodes[] = array(
3218 - 'path' => array( 'settings', 'blocks', $name ),
3219 - 'selector' => $selector,
3220 - 'selectors' => $selectors[ $name ]['selectors'] ?? array(),
2078 + 'path' => array( 'settings', 'blocks', $name ),
2079 + 'selector' => $selector,
3221 2080 );
3222 2081 }
3223 2082
3224 2083 return $nodes;
@@ -3243,14 +2102,11 @@
3243 2102 * @since 5.8.0
3244 2103 *
3245 2104 * @param array $theme_json The tree to extract style nodes from.
3246 2105 * @param array $selectors List of selectors per block.
3247 - * @param array $options An array of options to facilitate filtering style node generation
3248 - * The options currently supported are:
3249 - * - `include_block_style_variations` which includes CSS for block style variations.
3250 2106 * @return array An array of style nodes metadata.
3251 2107 */
3252 - protected static function get_style_nodes( $theme_json, $selectors = array(), $options = array() ) {
2108 + protected static function get_style_nodes( $theme_json, $selectors = array() ) {
3253 2109 $nodes = array();
3254 2110 if ( ! isset( $theme_json['styles'] ) ) {
3255 2111 return $nodes;
3256 2112 }
@@ -3273,9 +2129,11 @@
3273 2129 'selector' => static::ELEMENTS[ $element ],
3274 2130 );
3275 2131
3276 2132 // Handle any pseudo selectors for the element.
3277 - if ( isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element ] ) ) {
2133 + // TODO: Replace array_key_exists() with isset() check once WordPress drops
2134 + // support for PHP 5.6. See https://core.trac.wordpress.org/ticket/57067.
2135 + if ( array_key_exists( $element, static::VALID_ELEMENT_PSEUDO_SELECTORS ) ) {
3278 2136 foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element ] as $pseudo_selector ) {
3279 2137
3280 2138 if ( isset( $theme_json['styles']['elements'][ $element ][ $pseudo_selector ] ) ) {
3281 2139 $nodes[] = array(
@@ -3292,13 +2150,9 @@
3292 2150 if ( ! isset( $theme_json['styles']['blocks'] ) ) {
3293 2151 return $nodes;
3294 2152 }
3295 2153
3296 - $block_options = $options;
3297 - if ( ! isset( $block_options['include_block_style_variations'] ) ) {
3298 - $block_options['include_block_style_variations'] = true;
3299 - }
3300 - $block_nodes = static::get_block_nodes( $theme_json, $selectors, $block_options );
2154 + $block_nodes = static::get_block_nodes( $theme_json, $selectors );
3301 2155 foreach ( $block_nodes as $block_node ) {
3302 2156 $nodes[] = $block_node;
3303 2157 }
3304 2158
@@ -3365,132 +2219,8 @@
3365 2219 return $declarations;
3366 2220 }
3367 2221
3368 2222 /**
3369 - * Updates button width declarations to use a calc() formula for percentage values.
3370 - *
3371 - * When a percentage width is set on the Button block via Global Styles, the
3372 - * resulting CSS needs to account for block gap spacing so that buttons tile
3373 - * correctly on a row (e.g. 4 buttons at 25% width all fit on one row).
3374 - *
3375 - * This mirrors the dynamic calc() formula applied at the block instance level
3376 - * in the button block's stylesheet (style.scss).
3377 - *
3378 - * @since 7.1.0
3379 - *
3380 - * @param array $feature_declarations The feature declarations keyed by selector.
3381 - * @param array $settings The theme.json settings.
3382 - * @return array The updated feature declarations.
3383 - */
3384 - private static function update_button_width_declarations( $feature_declarations, $settings ) {
3385 - if ( ! isset( $feature_declarations['.wp-block-button'] ) ) {
3386 - return $feature_declarations;
3387 - }
3388 -
3389 - foreach ( $feature_declarations['.wp-block-button'] as &$declaration ) {
3390 - if ( 'width' !== $declaration['name'] || ! isset( $declaration['value'] ) ) {
3391 - continue;
3392 - }
3393 -
3394 - $value = $declaration['value'];
3395 - $percentage = null;
3396 -
3397 - // Case 1: Direct percentage value e.g. "25%".
3398 - if ( is_string( $value ) && str_ends_with( $value, '%' ) ) {
3399 - $percentage = (float) $value;
3400 - }
3401 -
3402 - // Case 2: Preset CSS var e.g. "var(--wp--preset--dimension--50)".
3403 - if ( null === $percentage && is_string( $value ) && str_starts_with( $value, 'var(--wp--preset--dimension--' ) ) {
3404 - // Extract the slug from the var name.
3405 - $slug = substr( $value, strlen( 'var(--wp--preset--dimension--' ), -1 );
3406 -
3407 - /*
3408 - * Look up the preset size across all origins.
3409 - * Check block-level settings first (core/button), then top-level settings.
3410 - */
3411 - $dimension_sizes = ( $settings['blocks']['core/button']['dimensions']['dimensionSizes'] ?? array() )
3412 - + ( $settings['dimensions']['dimensionSizes'] ?? array() );
3413 - foreach ( $dimension_sizes as $origin_sizes ) {
3414 - if ( ! is_array( $origin_sizes ) ) {
3415 - continue;
3416 - }
3417 - foreach ( $origin_sizes as $preset ) {
3418 - if ( isset( $preset['slug'] ) && $slug === $preset['slug'] && isset( $preset['size'] ) ) {
3419 - $size = $preset['size'];
3420 - if ( is_string( $size ) && str_ends_with( $size, '%' ) ) {
3421 - $percentage = (float) $size;
3422 - }
3423 - break 2;
3424 - }
3425 - }
3426 - }
3427 - }
3428 -
3429 - if ( null === $percentage ) {
3430 - continue;
3431 - }
3432 -
3433 - /*
3434 - * Apply the same calc() formula as the block instance level (style.scss).
3435 - * The numeric percentage value is used as a unitless number:
3436 - * - Multiplied by 1% to get the percentage width.
3437 - * - Divided by 100 to calculate the gap adjustment proportion.
3438 - */
3439 - $declaration['value'] = sprintf(
3440 - 'calc(%s * 1%% - (var(--wp--style--block-gap, 0.5em) * (1 - %s / 100)))',
3441 - $percentage,
3442 - $percentage
3443 - );
3444 - }
3445 - unset( $declaration );
3446 -
3447 - return $feature_declarations;
3448 - }
3449 -
3450 - /**
3451 - * Updates the text indent selector for paragraph blocks based on the textIndent setting.
3452 - *
3453 - * The textIndent setting can be 'subsequent' (default), 'all', or false.
3454 - * When set to 'all', the selector should be '.wp-block-paragraph' instead of
3455 - * '.wp-block-paragraph + .wp-block-paragraph' to apply indent to all paragraphs.
3456 - *
3457 - * @since 7.0.0
3458 - *
3459 - * @param array $feature_declarations The feature declarations keyed by selector.
3460 - * @param array $settings The theme.json settings.
3461 - * @param string $block_name The block name being processed.
3462 - * @return array The updated feature declarations.
3463 - */
3464 - private static function update_paragraph_text_indent_selector( $feature_declarations, $settings, $block_name ) {
3465 - if ( 'core/paragraph' !== $block_name ) {
3466 - return $feature_declarations;
3467 - }
3468 -
3469 - // Check block-level settings first, then fall back to global settings.
3470 - $block_settings = $settings['blocks']['core/paragraph'] ?? null;
3471 - $text_indent_setting = $block_settings['typography']['textIndent']
3472 - ?? $settings['typography']['textIndent']
3473 - ?? 'subsequent';
3474 -
3475 - if ( 'all' !== $text_indent_setting ) {
3476 - return $feature_declarations;
3477 - }
3478 -
3479 - // Look for the text indent selector and replace it.
3480 - $old_selector = '.wp-block-paragraph + .wp-block-paragraph';
3481 - $new_selector = '.wp-block-paragraph';
3482 -
3483 - if ( isset( $feature_declarations[ $old_selector ] ) ) {
3484 - $declarations = $feature_declarations[ $old_selector ];
3485 - unset( $feature_declarations[ $old_selector ] );
3486 - $feature_declarations[ $new_selector ] = $declarations;
3487 - }
3488 -
3489 - return $feature_declarations;
3490 - }
3491 -
3492 - /**
3493 2223 * An internal method to get the block nodes from a theme.json file.
3494 2224 *
3495 2225 * @since 6.1.0
3496 2226 *
@@ -3495,284 +2225,74 @@
3495 2225 * @since 6.1.0
3496 2226 *
3497 2227 * @param array $theme_json The theme.json converted to an array.
3498 2228 * @param array $selectors Optional list of selectors per block.
3499 - * @param array $options {
3500 - * Optional. An array of options for now used for internal purposes only (may change without notice).
3501 - *
3502 - * @type bool $include_block_style_variations Includes nodes for block style variations. Default false.
3503 - * @type bool $include_node_paths_only Return only block nodes node paths. Default false.
3504 - * }
3505 2229 * @return array The block nodes in theme.json.
3506 2230 */
3507 - private static function get_block_nodes( $theme_json, $selectors = array(), $options = array() ) {
3508 - $nodes = array();
2231 + private static function get_block_nodes( $theme_json, $selectors = array() ) {
2232 + $selectors = empty( $selectors ) ? static::get_blocks_metadata() : $selectors;
2233 + $nodes = array();
2234 + if ( ! isset( $theme_json['styles'] ) ) {
2235 + return $nodes;
2236 + }
3509 2237
2238 + // Blocks.
3510 2239 if ( ! isset( $theme_json['styles']['blocks'] ) ) {
3511 2240 return $nodes;
3512 2241 }
3513 2242
3514 - $include_variations = $options['include_block_style_variations'] ?? false;
3515 - $include_node_paths_only = $options['include_node_paths_only'] ?? false;
3516 - $responsive_media_queries = static::get_viewport_media_queries( $theme_json['settings']['viewport'] ?? null );
3517 -
3518 - // If only node paths are to be returned, skip selector assignment.
3519 - if ( ! $include_node_paths_only ) {
3520 - $selectors = empty( $selectors ) ? static::get_blocks_metadata() : $selectors;
3521 - }
3522 -
3523 2243 foreach ( $theme_json['styles']['blocks'] as $name => $node ) {
3524 - $node_path = array( 'styles', 'blocks', $name );
3525 - if ( $include_node_paths_only ) {
3526 - $variation_paths = array();
3527 - if ( $include_variations && isset( $node['variations'] ) ) {
3528 - foreach ( $node['variations'] as $variation => $variation_node ) {
3529 - $variation_paths[] = array(
3530 - 'path' => array( 'styles', 'blocks', $name, 'variations', $variation ),
3531 - );
3532 - }
3533 - }
3534 - $node = array(
3535 - 'path' => $node_path,
3536 - );
3537 - if ( ! empty( $variation_paths ) ) {
3538 - $node['variations'] = $variation_paths;
3539 - }
3540 - $nodes[] = $node;
3541 - } else {
3542 - $selector = null;
3543 - if ( isset( $selectors[ $name ]['selector'] ) ) {
3544 - $selector = $selectors[ $name ]['selector'];
3545 - }
2244 + $selector = null;
2245 + if ( isset( $selectors[ $name ]['selector'] ) ) {
2246 + $selector = $selectors[ $name ]['selector'];
2247 + }
3546 2248
3547 - $duotone_selector = null;
3548 - if ( isset( $selectors[ $name ]['duotone'] ) ) {
3549 - $duotone_selector = $selectors[ $name ]['duotone'];
3550 - }
2249 + $duotone_selector = null;
2250 + if ( isset( $selectors[ $name ]['duotone'] ) ) {
2251 + $duotone_selector = $selectors[ $name ]['duotone'];
2252 + }
3551 2253
3552 - $feature_selectors = null;
3553 - if ( isset( $selectors[ $name ]['selectors'] ) ) {
3554 - $feature_selectors = $selectors[ $name ]['selectors'];
3555 - }
2254 + $feature_selectors = null;
2255 + if ( isset( $selectors[ $name ]['selectors'] ) ) {
2256 + $feature_selectors = $selectors[ $name ]['selectors'];
2257 + }
3556 2258
3557 - $variation_selectors = array();
3558 -
3559 - if ( $include_variations && isset( $node['variations'] ) ) {
3560 - foreach ( $node['variations'] as $variation => $node ) {
3561 - $variation_selectors[] = array(
3562 - 'name' => $variation,
3563 - 'path' => array( 'styles', 'blocks', $name, 'variations', $variation ),
3564 - 'selector' => $selectors[ $name ]['styleVariations'][ $variation ],
3565 - );
3566 - }
2259 + $variation_selectors = array();
2260 + if ( isset( $node['variations'] ) ) {
2261 + foreach ( $node['variations'] as $variation => $node ) {
2262 + $variation_selectors[] = array(
2263 + 'path' => array( 'styles', 'blocks', $name, 'variations', $variation ),
2264 + 'selector' => $selectors[ $name ]['styleVariations'][ $variation ],
2265 + );
3567 2266 }
2267 + }
3568 2268
3569 - $nodes[] = array(
3570 - 'name' => $name,
3571 - 'path' => $node_path,
3572 - 'selector' => $selector,
3573 - 'selectors' => $feature_selectors,
3574 - 'elements' => $selectors[ $name ]['elements'] ?? array(),
3575 - 'duotone' => $duotone_selector,
3576 - 'variations' => $variation_selectors,
3577 - 'css' => $selector,
3578 - );
2269 + $nodes[] = array(
2270 + 'name' => $name,
2271 + 'path' => array( 'styles', 'blocks', $name ),
2272 + 'selector' => $selector,
2273 + 'selectors' => $feature_selectors,
2274 + 'duotone' => $duotone_selector,
2275 + 'variations' => $variation_selectors,
2276 + );
3579 2277
3580 - // Responsive block nodes: emit one node per breakpoint that has styles.
3581 - // These are rendered immediately after the base block node so that
3582 - // the cascade order is: .block{} → @media{.block{}}
3583 - foreach ( array_keys( $responsive_media_queries ) as $breakpoint ) {
3584 - if ( isset( $theme_json['styles']['blocks'][ $name ][ $breakpoint ] ) ) {
3585 - $nodes[] = array(
3586 - 'name' => $name,
3587 - 'path' => array( 'styles', 'blocks', $name, $breakpoint ),
3588 - 'media_query' => $responsive_media_queries[ $breakpoint ],
3589 - 'selector' => $selector,
3590 - 'selectors' => $feature_selectors,
3591 - 'elements' => $selectors[ $name ]['elements'] ?? array(),
3592 - 'variations' => $variation_selectors,
3593 - 'css' => $selector,
3594 - );
3595 - }
3596 - }
3597 -
3598 - // Handle any pseudo selectors for the block.
3599 - if ( isset( static::VALID_BLOCK_PSEUDO_SELECTORS[ $name ] ) ) {
3600 - foreach ( static::VALID_BLOCK_PSEUDO_SELECTORS[ $name ] as $pseudo_selector ) {
3601 - $has_pseudo = isset( $theme_json['styles']['blocks'][ $name ][ $pseudo_selector ] );
3602 - $has_responsive_pseudo = false;
3603 - foreach ( array_keys( $responsive_media_queries ) as $breakpoint ) {
3604 - if ( isset( $theme_json['styles']['blocks'][ $name ][ $breakpoint ][ $pseudo_selector ] ) ) {
3605 - $has_responsive_pseudo = true;
3606 - break;
3607 - }
3608 - }
3609 -
3610 - if ( ! $has_pseudo && ! $has_responsive_pseudo ) {
3611 - continue;
3612 - }
3613 -
3614 - /*
3615 - * Append the pseudo-selector to each feature selector so that
3616 - * get_feature_declarations_for_node generates CSS scoped to the
3617 - * pseudo-state (e.g. '.wp-block-button:hover') rather than the
3618 - * default state (e.g. '.wp-block-button').
3619 - */
3620 - $pseudo_feature_selectors = array();
3621 - foreach ( $feature_selectors ?? array() as $feature => $feature_selector ) {
3622 - if ( is_array( $feature_selector ) ) {
3623 - $pseudo_feature_selectors[ $feature ] = array();
3624 - foreach ( $feature_selector as $subfeature => $subfeature_selector ) {
3625 - $pseudo_feature_selectors[ $feature ][ $subfeature ] = static::append_to_selector( $subfeature_selector, $pseudo_selector );
3626 - }
3627 - } else {
3628 - $pseudo_feature_selectors[ $feature ] = static::append_to_selector( $feature_selector, $pseudo_selector );
3629 - }
3630 - }
3631 -
3632 - if ( $has_pseudo ) {
3633 - $nodes[] = array(
3634 - 'name' => $name,
3635 - 'path' => array( 'styles', 'blocks', $name, $pseudo_selector ),
3636 - 'selector' => static::append_to_selector( $selector, $pseudo_selector ),
3637 - 'selectors' => $pseudo_feature_selectors,
3638 - 'elements' => $selectors[ $name ]['elements'] ?? array(),
3639 - 'duotone' => $duotone_selector,
3640 - 'variations' => $variation_selectors,
3641 - 'css' => static::append_to_selector( $selector, $pseudo_selector ),
3642 - );
3643 - }
3644 -
3645 - // Responsive pseudo nodes: emit one node per breakpoint that has
3646 - // this pseudo state, immediately after the default pseudo node.
3647 - // Cascade order: .block:hover{} → @media{.block:hover{}}
3648 - foreach ( array_keys( $responsive_media_queries ) as $breakpoint ) {
3649 - if ( isset( $theme_json['styles']['blocks'][ $name ][ $breakpoint ][ $pseudo_selector ] ) ) {
3650 - $nodes[] = array(
3651 - 'name' => $name,
3652 - 'path' => array( 'styles', 'blocks', $name, $breakpoint, $pseudo_selector ),
3653 - 'media_query' => $responsive_media_queries[ $breakpoint ],
3654 - 'selector' => static::append_to_selector( $selector, $pseudo_selector ),
3655 - 'selectors' => $pseudo_feature_selectors,
3656 - 'elements' => $selectors[ $name ]['elements'] ?? array(),
3657 - 'variations' => $variation_selectors,
3658 - 'css' => static::append_to_selector( $selector, $pseudo_selector ),
3659 - );
3660 - }
3661 - }
3662 - }
3663 - }
3664 -
3665 - // Handle custom states (e.g. '-current' for navigation).
3666 - if ( isset( static::VALID_BLOCK_CUSTOM_STATES[ $name ] ) ) {
3667 - foreach ( static::VALID_BLOCK_CUSTOM_STATES[ $name ] as $custom_state ) {
3668 - if (
3669 - isset( $theme_json['styles']['blocks'][ $name ][ $custom_state ] ) &&
3670 - isset( $selectors[ $name ]['states'][ $custom_state ] )
3671 - ) {
3672 - $custom_css_selector = $selectors[ $name ]['states'][ $custom_state ];
3673 - $nodes[] = array(
3674 - 'name' => $name,
3675 - 'path' => array( 'styles', 'blocks', $name, $custom_state ),
3676 - 'selector' => $custom_css_selector,
3677 - 'selectors' => $feature_selectors,
3678 - 'elements' => $selectors[ $name ]['elements'] ?? array(),
3679 - 'duotone' => $duotone_selector,
3680 - 'variations' => $variation_selectors,
3681 - 'css' => $custom_css_selector,
3682 - );
3683 -
3684 - // Sub-pseudo-selectors within the custom state.
3685 - if ( isset( static::VALID_BLOCK_PSEUDO_SELECTORS[ $name ] ) ) {
3686 - foreach ( static::VALID_BLOCK_PSEUDO_SELECTORS[ $name ] as $pseudo ) {
3687 - if ( isset( $theme_json['styles']['blocks'][ $name ][ $custom_state ][ $pseudo ] ) ) {
3688 - $compound_css_selector = static::append_to_selector( $custom_css_selector, $pseudo );
3689 - $nodes[] = array(
3690 - 'name' => $name,
3691 - 'path' => array( 'styles', 'blocks', $name, $custom_state, $pseudo ),
3692 - 'selector' => $compound_css_selector,
3693 - 'selectors' => $feature_selectors,
3694 - 'elements' => $selectors[ $name ]['elements'] ?? array(),
3695 - 'duotone' => $duotone_selector,
3696 - 'variations' => $variation_selectors,
3697 - 'css' => $compound_css_selector,
3698 - );
3699 - }
3700 - }
3701 - }
3702 - }
3703 - }
3704 - }
3705 - }
3706 2278 if ( isset( $theme_json['styles']['blocks'][ $name ]['elements'] ) ) {
3707 2279 foreach ( $theme_json['styles']['blocks'][ $name ]['elements'] as $element => $node ) {
3708 - $element_path = array( 'styles', 'blocks', $name, 'elements', $element );
3709 - if ( $include_node_paths_only ) {
3710 - $nodes[] = array(
3711 - 'path' => $element_path,
3712 - );
3713 - continue;
3714 - }
3715 -
3716 - $element_selector = $selectors[ $name ]['elements'][ $element ];
3717 -
3718 2280 $nodes[] = array(
3719 - 'path' => $element_path,
3720 - 'selector' => $element_selector,
2281 + 'path' => array( 'styles', 'blocks', $name, 'elements', $element ),
2282 + 'selector' => $selectors[ $name ]['elements'][ $element ],
3721 2283 );
3722 2284
3723 - // Responsive element nodes: one node per breakpoint that has
3724 - // styles for this element. Cascade: a{} → @media{a{}}
3725 - foreach ( array_keys( $responsive_media_queries ) as $breakpoint ) {
3726 - if ( isset( $theme_json['styles']['blocks'][ $name ][ $breakpoint ]['elements'][ $element ] ) ) {
3727 - $nodes[] = array(
3728 - 'path' => array( 'styles', 'blocks', $name, $breakpoint, 'elements', $element ),
3729 - 'selector' => $element_selector,
3730 - 'media_query' => $responsive_media_queries[ $breakpoint ],
3731 - );
3732 - }
3733 - }
3734 -
3735 2285 // Handle any pseudo selectors for the element.
3736 - if ( isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element ] ) ) {
2286 + // TODO: Replace array_key_exists() with isset() check once WordPress drops
2287 + // support for PHP 5.6. See https://core.trac.wordpress.org/ticket/57067.
2288 + if ( array_key_exists( $element, static::VALID_ELEMENT_PSEUDO_SELECTORS ) ) {
3737 2289 foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element ] as $pseudo_selector ) {
3738 - // Create element pseudo node if default or any responsive breakpoint has the pseudo.
3739 - $has_element_pseudo = isset( $theme_json['styles']['blocks'][ $name ]['elements'][ $element ][ $pseudo_selector ] );
3740 - if ( ! $has_element_pseudo ) {
3741 - foreach ( array_keys( $responsive_media_queries ) as $bp ) {
3742 - if ( isset( $theme_json['styles']['blocks'][ $name ][ $bp ]['elements'][ $element ][ $pseudo_selector ] ) ) {
3743 - $has_element_pseudo = true;
3744 - break;
3745 - }
3746 - }
3747 - }
3748 -
3749 - if ( $has_element_pseudo ) {
3750 - $element_pseudo_path = array( 'styles', 'blocks', $name, 'elements', $element );
3751 - if ( $include_node_paths_only ) {
3752 - $nodes[] = array(
3753 - 'path' => $element_pseudo_path,
3754 - );
3755 - continue;
3756 - }
3757 -
2290 + if ( isset( $theme_json['styles']['blocks'][ $name ]['elements'][ $element ][ $pseudo_selector ] ) ) {
3758 2291 $nodes[] = array(
3759 - 'path' => $element_pseudo_path,
3760 - 'selector' => static::append_to_selector( $element_selector, $pseudo_selector ),
2292 + 'path' => array( 'styles', 'blocks', $name, 'elements', $element ),
2293 + 'selector' => static::append_to_selector( $selectors[ $name ]['elements'][ $element ], $pseudo_selector ),
3761 2294 );
3762 -
3763 - // Responsive element pseudo nodes: one node per breakpoint
3764 - // that has this pseudo state for this element.
3765 - // Cascade: a:hover{} → @media{a:hover{}}
3766 - foreach ( array_keys( $responsive_media_queries ) as $breakpoint ) {
3767 - if ( isset( $theme_json['styles']['blocks'][ $name ][ $breakpoint ]['elements'][ $element ][ $pseudo_selector ] ) ) {
3768 - $nodes[] = array(
3769 - 'path' => array( 'styles', 'blocks', $name, $breakpoint, 'elements', $element ),
3770 - 'selector' => static::append_to_selector( $element_selector, $pseudo_selector ),
3771 - 'media_query' => $responsive_media_queries[ $breakpoint ],
3772 - );
3773 - }
3774 - }
3775 2295 }
3776 2296 }
3777 2297 }
3778 2298 }
@@ -3785,9 +2305,8 @@
3785 2305 /**
3786 2306 * Gets the CSS rules for a particular block from theme.json.
3787 2307 *
3788 2308 * @since 6.1.0
3789 - * @since 6.6.0 Setting a min-height of HTML when root styles have a background gradient or image.
3790 2309 *
3791 2310 * @param array $block_metadata Metadata about the block to get styles for.
3792 2311 *
3793 2312 * @return string Styles for the block.
@@ -3792,184 +2311,49 @@
3792 2311 *
3793 2312 * @return string Styles for the block.
3794 2313 */
3795 2314 public function get_styles_for_block( $block_metadata ) {
3796 - $node = _wp_array_get( $this->theme_json, $block_metadata['path'], array() );
3797 - $use_root_padding = isset( $this->theme_json['settings']['useRootPaddingAwareAlignments'] ) && true === $this->theme_json['settings']['useRootPaddingAwareAlignments'];
3798 - $selector = $block_metadata['selector'];
3799 - $settings = $this->theme_json['settings'] ?? null;
3800 - $is_root_selector = static::ROOT_BLOCK_SELECTOR === $selector;
3801 - $media_query = $block_metadata['media_query'] ?? null;
3802 - $responsive_media_queries = static::get_viewport_media_queries( $settings['viewport'] ?? null );
2315 + $node = _wp_array_get( $this->theme_json, $block_metadata['path'], array() );
2316 + $use_root_padding = isset( $this->theme_json['settings']['useRootPaddingAwareAlignments'] ) && true === $this->theme_json['settings']['useRootPaddingAwareAlignments'];
2317 + $selector = $block_metadata['selector'];
2318 + $settings = _wp_array_get( $this->theme_json, array( 'settings' ) );
3803 2319
3804 2320 $feature_declarations = static::get_feature_declarations_for_node( $block_metadata, $node );
3805 2321
3806 - // Update text indent selector for paragraph blocks based on the textIndent setting.
3807 - $block_name = $block_metadata['name'] ?? null;
3808 - $feature_declarations = static::update_paragraph_text_indent_selector( $feature_declarations, $settings, $block_name );
3809 - $block_elements = $block_metadata['elements'] ?? array();
3810 -
3811 - // Update button width declarations for percentage values to use calc() with block gap.
3812 - $feature_declarations = static::update_button_width_declarations( $feature_declarations, $settings );
3813 -
3814 2322 // If there are style variations, generate the declarations for them, including any feature selectors the block may have.
3815 - // Responsive nodes (those with a media_query) do not process variations — variation responsive
3816 - // CSS is handled by the variation's own responsive nodes or the existing variation loop.
3817 - $style_variation_declarations = array();
3818 - $style_variation_custom_css = array();
3819 - $style_variation_responsive_css = array();
3820 - $style_variation_responsive_pseudo_css = array();
3821 - $style_variation_layout_metadata = array();
3822 - if ( ! $media_query && ! empty( $block_metadata['variations'] ) ) {
2323 + $style_variation_declarations = array();
2324 + if ( ! empty( $block_metadata['variations'] ) ) {
3823 2325 foreach ( $block_metadata['variations'] as $style_variation ) {
3824 - $style_variation_node = _wp_array_get( $this->theme_json, $style_variation['path'], array() );
2326 + $style_variation_node = _wp_array_get( $this->theme_json, $style_variation['path'], array() );
2327 + $clean_style_variation_selector = trim( $style_variation['selector'] );
3825 2328
3826 2329 // Generate any feature/subfeature style declarations for the current style variation.
3827 2330 $variation_declarations = static::get_feature_declarations_for_node( $block_metadata, $style_variation_node );
3828 2331
3829 - // Update text indent selector for paragraph blocks based on the textIndent setting.
3830 - $variation_declarations = static::update_paragraph_text_indent_selector( $variation_declarations, $settings, $block_name );
3831 -
3832 - // Update button width declarations for percentage values to use calc() with block gap.
3833 - $variation_declarations = static::update_button_width_declarations( $variation_declarations, $settings );
3834 -
3835 2332 // Combine selectors with style variation's selector and add to overall style variation declarations.
3836 2333 foreach ( $variation_declarations as $current_selector => $new_declarations ) {
3837 - $combined_selectors = static::get_block_style_variation_feature_selector( $style_variation, $current_selector );
2334 + // If current selector includes block classname, remove it but leave the whitespace in.
2335 + $shortened_selector = str_replace( $block_metadata['selector'] . ' ', ' ', $current_selector );
3838 2336
2337 + // Prepend the variation selector to the current selector.
2338 + $split_selectors = explode( ',', $shortened_selector );
2339 + $updated_selectors = array_map(
2340 + static function( $split_selector ) use ( $clean_style_variation_selector ) {
2341 + return $clean_style_variation_selector . $split_selector;
2342 + },
2343 + $split_selectors
2344 + );
2345 + $combined_selectors = implode( ',', $updated_selectors );
2346 +
3839 2347 // Add the new declarations to the overall results under the modified selector.
3840 2348 $style_variation_declarations[ $combined_selectors ] = $new_declarations;
3841 2349 }
2350 +
3842 2351 // Compute declarations for remaining styles not covered by feature level selectors.
3843 2352 $style_variation_declarations[ $style_variation['selector'] ] = static::compute_style_properties( $style_variation_node, $settings, null, $this->theme_json );
3844 -
3845 - // Process pseudo-selectors for this variation (e.g., :hover, :focus).
3846 - $block_name = $block_metadata['name'] ?? ( in_array( 'blocks', $block_metadata['path'], true ) && count( $block_metadata['path'] ) >= 3 ? static::get_block_name_from_metadata_path( $block_metadata ) : null );
3847 - $variation_pseudo_declarations = $this->process_pseudo_selectors( $style_variation_node, $style_variation['selector'], $settings, $block_name, $block_metadata, $style_variation );
3848 - $style_variation_declarations = array_merge( $style_variation_declarations, $variation_pseudo_declarations );
3849 -
3850 - // Store custom CSS for the style variation.
3851 - if ( isset( $style_variation_node['css'] ) ) {
3852 - $style_variation_custom_css[ $style_variation['selector'] ] = $this->process_blocks_custom_css( $style_variation_node['css'], $style_variation['selector'] );
3853 - }
3854 -
3855 - // Store variation metadata and node for layout styles generation.
3856 - // Only store if the variation has blockGap defined.
3857 - if ( isset( $style_variation_node['spacing']['blockGap'] ) ) {
3858 - // Append block selector to the variation selector for proper targeting.
3859 - $variation_metadata_with_selector = $style_variation;
3860 - $variation_metadata_with_selector['selector'] = $style_variation['selector'] . $block_metadata['css'];
3861 - $style_variation_layout_metadata[ $style_variation['selector'] ] = array(
3862 - 'metadata' => $variation_metadata_with_selector,
3863 - 'node' => $style_variation_node,
3864 - );
3865 - }
3866 -
3867 - // Store responsive breakpoint CSS for the style variation.
3868 - // This includes both base properties and feature-level selectors.
3869 - $variation_responsive_css = '';
3870 - $variation_responsive_pseudo_css = '';
3871 -
3872 - foreach ( array_keys( $responsive_media_queries ) as $breakpoint ) {
3873 - if ( ! isset( $style_variation_node[ $breakpoint ] ) ) {
3874 - continue;
3875 - }
3876 -
3877 - $breakpoint_node = $style_variation_node[ $breakpoint ];
3878 - $breakpoint_media = $responsive_media_queries[ $breakpoint ];
3879 - // Process feature-level declarations for this breakpoint.
3880 - $breakpoint_feature_declarations = static::get_feature_declarations_for_node( $block_metadata, $breakpoint_node );
3881 - $breakpoint_feature_declarations = static::update_paragraph_text_indent_selector( $breakpoint_feature_declarations, $settings, $block_name );
3882 - $breakpoint_feature_declarations = static::update_button_width_declarations( $breakpoint_feature_declarations, $settings );
3883 - foreach ( $breakpoint_feature_declarations as $feature_selector => $feature_decl ) {
3884 - $combined_selectors = static::get_block_style_variation_feature_selector( $style_variation, $feature_selector );
3885 -
3886 - $feature_ruleset = static::to_ruleset( ':root :where(' . $combined_selectors . ')', $feature_decl );
3887 - $variation_responsive_css .= $breakpoint_media . '{' . $feature_ruleset . '}';
3888 - }
3889 -
3890 - // Process base properties for this breakpoint.
3891 - $breakpoint_declarations = static::compute_style_properties( $breakpoint_node, $settings, null, $this->theme_json );
3892 - if ( ! empty( $breakpoint_declarations ) ) {
3893 - $base_ruleset = static::to_ruleset( ':root :where(' . $style_variation['selector'] . ')', $breakpoint_declarations );
3894 - $variation_responsive_css .= $breakpoint_media . '{' . $base_ruleset . '}';
3895 - }
3896 -
3897 - $breakpoint_pseudo_declarations = $this->process_pseudo_selectors( $breakpoint_node, $style_variation['selector'], $settings, $block_name, $block_metadata, $style_variation );
3898 - foreach ( $breakpoint_pseudo_declarations as $pseudo_selector => $pseudo_declarations ) {
3899 - if ( empty( $pseudo_declarations ) ) {
3900 - continue;
3901 - }
3902 - $pseudo_ruleset = static::to_ruleset( ':root :where(' . $pseudo_selector . ')', $pseudo_declarations );
3903 - $variation_responsive_pseudo_css .= $breakpoint_media . '{' . $pseudo_ruleset . '}';
3904 - }
3905 -
3906 - // Process custom CSS for this breakpoint.
3907 - if ( isset( $breakpoint_node['css'] ) ) {
3908 - $breakpoint_custom_css = static::process_blocks_custom_css( $breakpoint_node['css'], $style_variation['selector'] );
3909 - $variation_responsive_css .= $breakpoint_media . '{' . $breakpoint_custom_css . '}';
3910 - }
3911 -
3912 - // Process blockGap responsive layout styles for this variation.
3913 - if ( isset( $breakpoint_node['spacing']['blockGap'] ) ) {
3914 - $variation_layout_metadata = $style_variation;
3915 - $variation_layout_metadata['selector'] = $style_variation['selector'] . $block_metadata['css'];
3916 - $variation_responsive_css .= $this->get_layout_styles(
3917 - $variation_layout_metadata,
3918 - array(
3919 - 'node' => $breakpoint_node,
3920 - 'media_query' => $breakpoint_media,
3921 - )
3922 - );
3923 - }
3924 -
3925 - // Process nested element styles for this breakpoint state.
3926 - if ( isset( $breakpoint_node['elements'] ) && ! empty( $block_elements ) ) {
3927 - foreach ( $breakpoint_node['elements'] as $element_name => $element_node ) {
3928 - if ( ! isset( $block_elements[ $element_name ] ) ) {
3929 - continue;
3930 - }
3931 -
3932 - $variation_element_selector = static::get_block_style_variation_feature_selector( $style_variation, $block_elements[ $element_name ] );
3933 -
3934 - $element_declarations = static::compute_style_properties( $element_node, $settings, null, $this->theme_json );
3935 - if ( ! empty( $element_declarations ) ) {
3936 - $element_ruleset = static::to_ruleset( ':root :where(' . $variation_element_selector . ')', $element_declarations );
3937 - $variation_responsive_css .= $breakpoint_media . '{' . $element_ruleset . '}';
3938 - }
3939 -
3940 - if ( isset( $element_node['css'] ) ) {
3941 - $element_custom_css = static::process_blocks_custom_css( $element_node['css'], $variation_element_selector );
3942 - $variation_responsive_css .= $breakpoint_media . '{' . $element_custom_css . '}';
3943 - }
3944 -
3945 - if ( isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element_name ] ) ) {
3946 - foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element_name ] as $pseudo_selector ) {
3947 - if ( ! isset( $element_node[ $pseudo_selector ] ) ) {
3948 - continue;
3949 - }
3950 -
3951 - $pseudo_declarations = static::compute_style_properties( $element_node[ $pseudo_selector ], $settings, null, $this->theme_json );
3952 - if ( empty( $pseudo_declarations ) ) {
3953 - continue;
3954 - }
3955 -
3956 - $pseudo_selector_ruleset = static::to_ruleset( ':root :where(' . static::append_to_selector( $variation_element_selector, $pseudo_selector ) . ')', $pseudo_declarations );
3957 - $variation_responsive_pseudo_css .= $breakpoint_media . '{' . $pseudo_selector_ruleset . '}';
3958 - }
3959 - }
3960 - }
3961 - }
3962 - }
3963 -
3964 - if ( ! empty( $variation_responsive_css ) ) {
3965 - $style_variation_responsive_css[ $style_variation['selector'] ] = $variation_responsive_css;
3966 - }
3967 - if ( ! empty( $variation_responsive_pseudo_css ) ) {
3968 - $style_variation_responsive_pseudo_css[ $style_variation['selector'] ] = $variation_responsive_pseudo_css;
3969 - }
3970 2353 }
3971 2354 }
2355 +
3972 2356 /*
3973 2357 * Get a reference to element name from path.
3974 2358 * $block_metadata['path'] = array( 'styles','elements','link' );
3975 2359 * Make sure that $block_metadata['path'] describes an element node, like [ 'styles', 'element', 'link' ].
@@ -3980,9 +2364,11 @@
3980 2364 $current_element = $is_processing_element ? $block_metadata['path'][ count( $block_metadata['path'] ) - 1 ] : null;
3981 2365
3982 2366 $element_pseudo_allowed = array();
3983 2367
3984 - if ( isset( $current_element, static::VALID_ELEMENT_PSEUDO_SELECTORS[ $current_element ] ) ) {
2368 + // TODO: Replace array_key_exists() with isset() check once WordPress drops
2369 + // support for PHP 5.6. See https://core.trac.wordpress.org/ticket/57067.
2370 + if ( array_key_exists( $current_element, static::VALID_ELEMENT_PSEUDO_SELECTORS ) ) {
3985 2371 $element_pseudo_allowed = static::VALID_ELEMENT_PSEUDO_SELECTORS[ $current_element ];
3986 2372 }
3987 2373
3988 2374 /*
@@ -3991,19 +2377,15 @@
3991 2377 */
3992 2378 $pseudo_matches = array_values(
3993 2379 array_filter(
3994 2380 $element_pseudo_allowed,
3995 - static function ( $pseudo_selector ) use ( $selector ) {
3996 - /*
3997 - * Check if the pseudo selector is in the current selector,
3998 - * ensuring it is not followed by a dash (e.g., :focus should not match :focus-visible).
3999 - */
4000 - return preg_match( '/' . preg_quote( $pseudo_selector, '/' ) . '(?!-)/', $selector ) === 1;
2381 + static function( $pseudo_selector ) use ( $selector ) {
2382 + return str_contains( $selector, $pseudo_selector );
4001 2383 }
4002 2384 )
4003 2385 );
4004 2386
4005 - $pseudo_selector = $pseudo_matches[0] ?? null;
2387 + $pseudo_selector = isset( $pseudo_matches[0] ) ? $pseudo_matches[0] : null;
4006 2388
4007 2389 /*
4008 2390 * If the current selector is a pseudo selector that's defined in the allow list for the current
4009 2391 * element then compute the style properties for it.
@@ -4009,18 +2391,15 @@
4009 2391 * element then compute the style properties for it.
4010 2392 * Otherwise just compute the styles for the default selector as normal.
4011 2393 */
4012 2394 if ( $pseudo_selector && isset( $node[ $pseudo_selector ] ) &&
4013 - isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $current_element ] )
2395 + // TODO: Replace array_key_exists() with isset() check once WordPress drops
2396 + // support for PHP 5.6. See https://core.trac.wordpress.org/ticket/57067.
2397 + array_key_exists( $current_element, static::VALID_ELEMENT_PSEUDO_SELECTORS )
4014 2398 && in_array( $pseudo_selector, static::VALID_ELEMENT_PSEUDO_SELECTORS[ $current_element ], true )
4015 2399 ) {
4016 2400 $declarations = static::compute_style_properties( $node[ $pseudo_selector ], $settings, null, $this->theme_json, $selector, $use_root_padding );
4017 2401 } else {
4018 - /*
4019 - * For block pseudo-selector nodes (e.g. ':hover'), $node has already had any
4020 - * feature-selector properties (e.g. writingMode) removed by get_feature_declarations_for_node,
4021 - * so those properties are not output twice.
4022 - */
4023 2402 $declarations = static::compute_style_properties( $node, $settings, null, $this->theme_json, $selector, $use_root_padding );
4024 2403 }
4025 2404
4026 2405 $block_rules = '';
@@ -4025,18 +2404,12 @@
4025 2404
4026 2405 $block_rules = '';
4027 2406
4028 2407 /*
4029 - * 1. Bespoke declaration modifiers:
4030 - * - 'filter': Separate the declarations that use the general selector
2408 + * 1. Separate the declarations that use the general selector
4031 2409 * from the ones using the duotone selector.
4032 - * - 'background|background-image': set the html min-height to 100%
4033 - * to ensure the background covers the entire viewport.
4034 - *
4035 2410 */
4036 - $declarations_duotone = array();
4037 - $should_set_root_min_height = false;
4038 -
2411 + $declarations_duotone = array();
4039 2412 foreach ( $declarations as $index => $declaration ) {
4040 2413 if ( 'filter' === $declaration['name'] ) {
4041 2414 /*
4042 2415 * 'unset' filters happen when a filter is unset
@@ -4051,59 +2424,17 @@
4051 2424 $declarations_duotone[] = $declaration;
4052 2425 }
4053 2426 unset( $declarations[ $index ] );
4054 2427 }
4055 -
4056 - if ( $is_root_selector && ( 'background-image' === $declaration['name'] || 'background' === $declaration['name'] ) ) {
4057 - $should_set_root_min_height = true;
4058 - }
4059 2428 }
4060 2429
4061 - /*
4062 - * If root styles has a background-image or a background (gradient) set,
4063 - * set the min-height to '100%'. Minus `--wp-admin--admin-bar--height` for logged-in view.
4064 - * Setting the CSS rule on the HTML tag ensures background gradients and images behave similarly,
4065 - * and matches the behavior of the site editor.
4066 - */
4067 - if ( $should_set_root_min_height ) {
4068 - $block_rules .= static::to_ruleset(
4069 - 'html',
4070 - array(
4071 - array(
4072 - 'name' => 'min-height',
4073 - 'value' => 'calc(100% - var(--wp-admin--admin-bar--height, 0px))',
4074 - ),
4075 - )
4076 - );
4077 - }
4078 -
4079 2430 // Update declarations if there are separators with only background color defined.
4080 2431 if ( '.wp-block-separator' === $selector ) {
4081 2432 $declarations = static::update_separator_declarations( $declarations );
4082 2433 }
4083 2434
4084 - /*
4085 - * Root selector (body) styles should not be wrapped in `:root where()` to keep
4086 - * specificity at (0,0,1) and maintain backwards compatibility.
4087 - *
4088 - * Top-level element styles using element-only specificity selectors should
4089 - * not get wrapped in `:root :where()` to maintain backwards compatibility.
4090 - *
4091 - * Pseudo classes, e.g. :hover, :focus etc., are a class-level selector so
4092 - * still need to be wrapped in `:root :where` to cap specificity for nested
4093 - * variations etc. Pseudo selectors won't match the ELEMENTS selector exactly.
4094 - */
4095 - $element_only_selector = $is_root_selector || (
4096 - $current_element &&
4097 - isset( static::ELEMENTS[ $current_element ] ) &&
4098 - // buttons, captions etc. still need `:root :where()` as they are class based selectors.
4099 - ! isset( static::__EXPERIMENTAL_ELEMENT_CLASS_NAMES[ $current_element ] ) &&
4100 - static::ELEMENTS[ $current_element ] === $selector
4101 - );
4102 -
4103 2435 // 2. Generate and append the rules that use the general selector.
4104 - $general_selector = $element_only_selector ? $selector : ":root :where($selector)";
4105 - $block_rules .= static::to_ruleset( $general_selector, $declarations );
2436 + $block_rules .= static::to_ruleset( $selector, $declarations );
4106 2437
4107 2438 // 3. Generate and append the rules that use the duotone selector.
4108 2439 if ( isset( $block_metadata['duotone'] ) && ! empty( $declarations_duotone ) ) {
4109 2440 $block_rules .= static::to_ruleset( $block_metadata['duotone'], $declarations_duotone );
@@ -4110,9 +2441,9 @@
4110 2441 }
4111 2442
4112 2443 // 4. Generate Layout block gap styles.
4113 2444 if (
4114 - ! $is_root_selector &&
2445 + static::ROOT_BLOCK_SELECTOR !== $selector &&
4115 2446 ! empty( $block_metadata['name'] )
4116 2447 ) {
4117 2448 $block_rules .= $this->get_layout_styles( $block_metadata );
4118 2449 }
@@ -4118,52 +2449,16 @@
4118 2449 }
4119 2450
4120 2451 // 5. Generate and append the feature level rulesets.
4121 2452 foreach ( $feature_declarations as $feature_selector => $individual_feature_declarations ) {
4122 - $block_rules .= static::to_ruleset( ":root :where($feature_selector)", $individual_feature_declarations );
2453 + $block_rules .= static::to_ruleset( $feature_selector, $individual_feature_declarations );
4123 2454 }
4124 2455
4125 2456 // 6. Generate and append the style variation rulesets.
4126 2457 foreach ( $style_variation_declarations as $style_variation_selector => $individual_style_variation_declarations ) {
4127 - $block_rules .= static::to_ruleset( ":root :where($style_variation_selector)", $individual_style_variation_declarations );
4128 - if ( isset( $style_variation_layout_metadata[ $style_variation_selector ] ) ) {
4129 - $variation_data = $style_variation_layout_metadata[ $style_variation_selector ];
4130 - $block_rules .= $this->get_layout_styles( $variation_data['metadata'], array( 'node' => $variation_data['node'] ) );
4131 - }
4132 - if ( isset( $style_variation_custom_css[ $style_variation_selector ] ) ) {
4133 - $block_rules .= $style_variation_custom_css[ $style_variation_selector ];
4134 - }
4135 - if ( isset( $style_variation_responsive_css[ $style_variation_selector ] ) ) {
4136 - $block_rules .= $style_variation_responsive_css[ $style_variation_selector ];
4137 - }
2458 + $block_rules .= static::to_ruleset( $style_variation_selector, $individual_style_variation_declarations );
4138 2459 }
4139 - /*
4140 - * Responsive pseudo styles must be output after default pseudo styles
4141 - * so viewport state styles win in the cascade.
4142 - */
4143 - foreach ( $style_variation_responsive_pseudo_css as $responsive_pseudo_css ) {
4144 - $block_rules .= $responsive_pseudo_css;
4145 - }
4146 2460
4147 - // Compute selector for block custom CSS.
4148 - $css_feature_selector = $block_metadata['selectors']['css'] ?? null;
4149 - if ( is_array( $css_feature_selector ) ) {
4150 - $css_feature_selector = $css_feature_selector['root'] ?? null;
4151 - }
4152 - $css_selector = is_string( $css_feature_selector ) ? $css_feature_selector : $selector;
4153 -
4154 - // 7. Generate and append any custom CSS rules.
4155 - if ( isset( $node['css'] ) && ! $is_root_selector ) {
4156 - $block_rules .= $this->process_blocks_custom_css( $node['css'], $css_selector );
4157 - }
4158 -
4159 - // 8. Wrap the entire block output in a media query if this is a responsive node.
4160 - // Responsive nodes are created by get_block_nodes() for each breakpoint and carry
4161 - // a 'media_query' key.
4162 - if ( $media_query && ! empty( $block_rules ) ) {
4163 - $block_rules = $media_query . '{' . $block_rules . '}';
4164 - }
4165 -
4166 2461 return $block_rules;
4167 2462 }
4168 2463
4169 2464 /**
@@ -4169,42 +2464,42 @@
4169 2464 /**
4170 2465 * Outputs the CSS for layout rules on the root.
4171 2466 *
4172 2467 * @since 6.1.0
4173 - * @since 6.6.0 Use `ROOT_CSS_PROPERTIES_SELECTOR` for CSS custom properties.
4174 2468 *
4175 - * @param string $selector The root node selector.
2469 + * @param string $selector The root node selector.
4176 2470 * @param array $block_metadata The metadata for the root block.
4177 - * @param array $options Optional. An array of options. Default empty array.
4178 2471 * @return string The additional root rules CSS.
4179 2472 */
4180 - public function get_root_layout_rules( $selector, $block_metadata, $options = array() ) {
2473 + public function get_root_layout_rules( $selector, $block_metadata ) {
4181 2474 $css = '';
4182 - $settings = $this->theme_json['settings'] ?? array();
2475 + $settings = _wp_array_get( $this->theme_json, array( 'settings' ) );
4183 2476 $use_root_padding = isset( $this->theme_json['settings']['useRootPaddingAwareAlignments'] ) && true === $this->theme_json['settings']['useRootPaddingAwareAlignments'];
4184 2477
4185 2478 /*
2479 + * Reset default browser margin on the root body element.
2480 + * This is set on the root selector **before** generating the ruleset
2481 + * from the `theme.json`. This is to ensure that if the `theme.json` declares
2482 + * `margin` in its `spacing` declaration for the `body` element then these
2483 + * user-generated values take precedence in the CSS cascade.
2484 + * @link https://github.com/WordPress/gutenberg/issues/36147.
2485 + */
2486 + $css .= 'body { margin: 0;';
2487 +
2488 + /*
4186 2489 * If there are content and wide widths in theme.json, output them
4187 2490 * as custom properties on the body element so all blocks can use them.
4188 2491 */
4189 2492 if ( isset( $settings['layout']['contentSize'] ) || isset( $settings['layout']['wideSize'] ) ) {
4190 - $content_size = $settings['layout']['contentSize'] ?? $settings['layout']['wideSize'];
2493 + $content_size = isset( $settings['layout']['contentSize'] ) ? $settings['layout']['contentSize'] : $settings['layout']['wideSize'];
4191 2494 $content_size = static::is_safe_css_declaration( 'max-width', $content_size ) ? $content_size : 'initial';
4192 - $wide_size = $settings['layout']['wideSize'] ?? $settings['layout']['contentSize'];
2495 + $wide_size = isset( $settings['layout']['wideSize'] ) ? $settings['layout']['wideSize'] : $settings['layout']['contentSize'];
4193 2496 $wide_size = static::is_safe_css_declaration( 'max-width', $wide_size ) ? $wide_size : 'initial';
4194 - $css .= static::ROOT_CSS_PROPERTIES_SELECTOR . ' { --wp--style--global--content-size: ' . $content_size . ';';
4195 - $css .= '--wp--style--global--wide-size: ' . $wide_size . '; }';
2497 + $css .= '--wp--style--global--content-size: ' . $content_size . ';';
2498 + $css .= '--wp--style--global--wide-size: ' . $wide_size . ';';
4196 2499 }
4197 2500
4198 - /*
4199 - * Reset default browser margin on the body element.
4200 - * This is set on the body selector **before** generating the ruleset
4201 - * from the `theme.json`. This is to ensure that if the `theme.json` declares
4202 - * `margin` in its `spacing` declaration for the `body` element then these
4203 - * user-generated values take precedence in the CSS cascade.
4204 - * @link https://github.com/WordPress/gutenberg/issues/36147.
4205 - */
4206 - $css .= ':where(body) { margin: 0; }';
2501 + $css .= '}';
4207 2502
4208 2503 if ( $use_root_padding ) {
4209 2504 // Top and bottom padding are applied to the outer block container.
4210 2505 $css .= '.wp-site-blocks { padding-top: var(--wp--style--root--padding-top); padding-bottom: var(--wp--style--root--padding-bottom); }';
@@ -4209,35 +2504,36 @@
4209 2504 // Top and bottom padding are applied to the outer block container.
4210 2505 $css .= '.wp-site-blocks { padding-top: var(--wp--style--root--padding-top); padding-bottom: var(--wp--style--root--padding-bottom); }';
4211 2506 // Right and left padding are applied to the first container with `.has-global-padding` class.
4212 2507 $css .= '.has-global-padding { padding-right: var(--wp--style--root--padding-right); padding-left: var(--wp--style--root--padding-left); }';
2508 + // Nested containers with `.has-global-padding` class do not get padding.
2509 + $css .= '.has-global-padding :where(.has-global-padding) { padding-right: 0; padding-left: 0; }';
4213 2510 // Alignfull children of the container with left and right padding have negative margins so they can still be full width.
4214 2511 $css .= '.has-global-padding > .alignfull { margin-right: calc(var(--wp--style--root--padding-right) * -1); margin-left: calc(var(--wp--style--root--padding-left) * -1); }';
4215 - // Nested children of the container with left and right padding that are not full aligned do not get padding, unless they are direct children of an alignfull flow container.
4216 - $css .= '.has-global-padding :where(:not(.alignfull.is-layout-flow) > .has-global-padding:not(.wp-block-block, .alignfull)) { padding-right: 0; padding-left: 0; }';
4217 - // Alignfull direct children of the containers that are targeted by the rule above do not need negative margins.
4218 - $css .= '.has-global-padding :where(:not(.alignfull.is-layout-flow) > .has-global-padding:not(.wp-block-block, .alignfull)) > .alignfull { margin-left: 0; margin-right: 0; }';
2512 + // The above rule is negated for alignfull children of nested containers.
2513 + $css .= '.has-global-padding :where(.has-global-padding) > .alignfull { margin-right: 0; margin-left: 0; }';
2514 + // Some of the children of alignfull blocks without content width should also get padding: text blocks and non-alignfull container blocks.
2515 + $css .= '.has-global-padding > .alignfull:where(:not(.has-global-padding)) > :where([class*="wp-block-"]:not(.alignfull):not([class*="__"]),.wp-block:not(.alignfull),p,h1,h2,h3,h4,h5,h6,ul,ol) { padding-right: var(--wp--style--root--padding-right); padding-left: var(--wp--style--root--padding-left); }';
2516 + // The above rule also has to be negated for blocks inside nested `.has-global-padding` blocks.
2517 + $css .= '.has-global-padding :where(.has-global-padding) > .alignfull:where(:not(.has-global-padding)) > :where([class*="wp-block-"]:not(.alignfull):not([class*="__"]),.wp-block:not(.alignfull),p,h1,h2,h3,h4,h5,h6,ul,ol) { padding-right: 0; padding-left: 0; }';
4219 2518 }
4220 2519
4221 - // Skip outputting alignment styles when base_layout_styles is enabled.
4222 - // These styles target .wp-site-blocks which is only used by block themes.
4223 - if ( empty( $options['base_layout_styles'] ) ) {
4224 - $css .= '.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }';
4225 - $css .= '.wp-site-blocks > .alignright { float: right; margin-left: 2em; }';
4226 - $css .= '.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }';
4227 - }
2520 + $css .= '.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }';
2521 + $css .= '.wp-site-blocks > .alignright { float: right; margin-left: 2em; }';
2522 + $css .= '.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }';
4228 2523
4229 - // Block gap styles will be output unless explicitly set to `null`.
4230 - if ( isset( $this->theme_json['settings']['spacing']['blockGap'] ) ) {
2524 + $block_gap_value = _wp_array_get( $this->theme_json, array( 'styles', 'spacing', 'blockGap' ), '0.5em' );
2525 + $has_block_gap_support = _wp_array_get( $this->theme_json, array( 'settings', 'spacing', 'blockGap' ) ) !== null;
2526 + if ( $has_block_gap_support ) {
4231 2527 $block_gap_value = static::get_property_value( $this->theme_json, array( 'styles', 'spacing', 'blockGap' ) );
4232 2528 $css .= ":where(.wp-site-blocks) > * { margin-block-start: $block_gap_value; margin-block-end: 0; }";
4233 - $css .= ':where(.wp-site-blocks) > :first-child { margin-block-start: 0; }';
4234 - $css .= ':where(.wp-site-blocks) > :last-child { margin-block-end: 0; }';
2529 + $css .= ':where(.wp-site-blocks) > :first-child:first-child { margin-block-start: 0; }';
2530 + $css .= ':where(.wp-site-blocks) > :last-child:last-child { margin-block-end: 0; }';
4235 2531
4236 2532 // For backwards compatibility, ensure the legacy block gap CSS variable is still available.
4237 - $css .= static::ROOT_CSS_PROPERTIES_SELECTOR . " { --wp--style--block-gap: $block_gap_value; }";
2533 + $css .= "$selector { --wp--style--block-gap: $block_gap_value; }";
4238 2534 }
4239 - $css .= $this->get_layout_styles( $block_metadata, $options );
2535 + $css .= $this->get_layout_styles( $block_metadata );
4240 2536
4241 2537 return $css;
4242 2538 }
4243 2539
@@ -4285,12 +2581,10 @@
4285 2581 * Merges new incoming data.
4286 2582 *
4287 2583 * @since 5.8.0
4288 2584 * @since 5.9.0 Duotone preset also has origins.
4289 - * @since 6.6.0 Use the spacingScale keyed by origin, and re-generate the
4290 - * spacingSizes from spacingScale.
4291 2585 *
4292 - * @param WP_Theme_JSON_Gutenberg $incoming Data to merge.
2586 + * @param WP_Theme_JSON $incoming Data to merge.
4293 2587 */
4294 2588 public function merge( $incoming ) {
4295 2589 $incoming_data = $incoming->get_raw_data();
4296 2590 $this->theme_json = array_replace_recursive( $this->theme_json, $incoming_data );
@@ -4295,42 +2589,8 @@
4295 2589 $incoming_data = $incoming->get_raw_data();
4296 2590 $this->theme_json = array_replace_recursive( $this->theme_json, $incoming_data );
4297 2591
4298 2592 /*
4299 - * Recompute all the spacing sizes based on the new hierarchy of data. In the constructor
4300 - * spacingScale and spacingSizes are both keyed by origin and VALID_ORIGINS is ordered, so
4301 - * we can allow partial spacingScale data to inherit missing data from earlier layers when
4302 - * computing the spacing sizes.
4303 - *
4304 - * This happens before the presets are merged to ensure that default spacing sizes can be
4305 - * removed from the theme origin if $prevent_override is true.
4306 - */
4307 - $flattened_spacing_scale = array();
4308 - foreach ( static::VALID_ORIGINS as $origin ) {
4309 - $scale_path = array( 'settings', 'spacing', 'spacingScale', $origin );
4310 -
4311 - // Apply the base spacing scale to the current layer.
4312 - $base_spacing_scale = _wp_array_get( $this->theme_json, $scale_path, array() );
4313 - $flattened_spacing_scale = array_replace( $flattened_spacing_scale, $base_spacing_scale );
4314 -
4315 - $spacing_scale = _wp_array_get( $incoming_data, $scale_path, null );
4316 - if ( ! isset( $spacing_scale ) ) {
4317 - continue;
4318 - }
4319 -
4320 - // Allow partial scale settings by merging with lower layers.
4321 - $flattened_spacing_scale = array_replace( $flattened_spacing_scale, $spacing_scale );
4322 -
4323 - // Generate and merge the scales for this layer.
4324 - $sizes_path = array( 'settings', 'spacing', 'spacingSizes', $origin );
4325 - $spacing_sizes = _wp_array_get( $incoming_data, $sizes_path, array() );
4326 - $spacing_scale_sizes = static::compute_spacing_sizes( $flattened_spacing_scale );
4327 - $merged_spacing_sizes = static::merge_spacing_sizes( $spacing_scale_sizes, $spacing_sizes );
4328 -
4329 - _wp_array_set( $incoming_data, $sizes_path, $merged_spacing_sizes );
4330 - }
4331 -
4332 - /*
4333 2593 * The array_replace_recursive algorithm merges at the leaf level,
4334 2594 * but we don't want leaf arrays to be merged, so we overwrite it.
4335 2595 *
4336 2596 * For leaf values that are sequential arrays it will use the numeric indexes for replacement.
@@ -4364,21 +2624,14 @@
4364 2624 _wp_array_set( $this->theme_json, $path, $content );
4365 2625 }
4366 2626
4367 2627 // Replace the presets.
4368 - foreach ( static::PRESETS_METADATA as $preset_metadata ) {
4369 - $prevent_override = $preset_metadata['prevent_override'];
4370 - if ( is_array( $prevent_override ) ) {
4371 - $global_path = array_merge( array( 'settings' ), $prevent_override );
4372 - $global_value = _wp_array_get( $this->theme_json, $global_path, null );
2628 + foreach ( static::PRESETS_METADATA as $preset ) {
2629 + $override_preset = ! static::get_metadata_boolean( $this->theme_json['settings'], $preset['prevent_override'], true );
4373 2630
4374 - $node_level_path = array_merge( $node['path'], $prevent_override );
4375 - $prevent_override = _wp_array_get( $this->theme_json, $node_level_path, $global_value );
4376 - }
4377 -
4378 2631 foreach ( static::VALID_ORIGINS as $origin ) {
4379 2632 $base_path = $node['path'];
4380 - foreach ( $preset_metadata['path'] as $leaf ) {
2633 + foreach ( $preset['path'] as $leaf ) {
4381 2634 $base_path[] = $leaf;
4382 2635 }
4383 2636
4384 2637 $path = $base_path;
@@ -4388,10 +2641,9 @@
4388 2641 if ( ! isset( $content ) ) {
4389 2642 continue;
4390 2643 }
4391 2644
4392 - // Set names for theme presets based on the slug if they are not set and can use default names.
4393 - if ( 'theme' === $origin && $preset_metadata['use_default_names'] ) {
2645 + if ( 'theme' === $origin && $preset['use_default_names'] ) {
4394 2646 foreach ( $content as $key => $item ) {
4395 2647 if ( ! isset( $item['name'] ) ) {
4396 2648 $name = static::get_name_from_defaults( $item['slug'], $base_path );
4397 2649 if ( null !== $name ) {
@@ -4400,49 +2652,24 @@
4400 2652 }
4401 2653 }
4402 2654 }
4403 2655
4404 - // Filter out default slugs from theme presets when defaults should not be overridden.
4405 - if ( 'theme' === $origin && $prevent_override ) {
4406 - $slugs_node = static::get_default_slugs( $this->theme_json, $node['path'] );
4407 - $preset_global = _wp_array_get( $slugs_global, $preset_metadata['path'], array() );
4408 - $preset_node = _wp_array_get( $slugs_node, $preset_metadata['path'], array() );
4409 - $preset_slugs = array_merge_recursive( $preset_global, $preset_node );
2656 + if (
2657 + ( 'theme' !== $origin ) ||
2658 + ( 'theme' === $origin && $override_preset )
2659 + ) {
2660 + _wp_array_set( $this->theme_json, $path, $content );
2661 + } else {
2662 + $slugs_node = static::get_default_slugs( $this->theme_json, $node['path'] );
2663 + $slugs = array_merge_recursive( $slugs_global, $slugs_node );
4410 2664
4411 - $content = static::filter_slugs( $content, $preset_slugs );
2665 + $slugs_for_preset = _wp_array_get( $slugs, $preset['path'], array() );
2666 + $content = static::filter_slugs( $content, $slugs_for_preset );
2667 + _wp_array_set( $this->theme_json, $path, $content );
4412 2668 }
4413 -
4414 - _wp_array_set( $this->theme_json, $path, $content );
4415 2669 }
4416 2670 }
4417 2671 }
4418 -
4419 - /*
4420 - * Style values are merged at the leaf level, however
4421 - * some values provide exceptions, namely style values that are
4422 - * objects and represent unique definitions for the style.
4423 - */
4424 - $style_nodes = static::get_block_nodes(
4425 - $this->theme_json,
4426 - array(),
4427 - array( 'include_node_paths_only' => true )
4428 - );
4429 -
4430 - // Add top-level styles.
4431 - $style_nodes[] = array( 'path' => array( 'styles' ) );
4432 -
4433 - foreach ( $style_nodes as $style_node ) {
4434 - $path = $style_node['path'];
4435 - /*
4436 - * Background image styles should be replaced, not merged,
4437 - * as they themselves are specific object definitions for the style.
4438 - */
4439 - $background_image_path = array_merge( $path, static::PROPERTIES_METADATA['background-image'] );
4440 - $content = _wp_array_get( $incoming_data, $background_image_path, null );
4441 - if ( isset( $content ) ) {
4442 - _wp_array_set( $this->theme_json, $background_image_path, $content );
4443 - }
4444 - }
4445 2672 }
4446 2673
4447 2674 /**
4448 2675 * Converts all filter (duotone) presets into SVGs.
@@ -4521,15 +2748,13 @@
4521 2748 }
4522 2749
4523 2750 return true;
4524 2751 }
4525 -
4526 - return false;
4527 2752 }
4528 2753
4529 2754 /**
4530 2755 * Returns the default slugs for all the presets in an associative array
4531 - * whose keys are the preset paths and the leaves is the list of slugs.
2756 + * whose keys are the preset paths and the leafs is the list of slugs.
4532 2757 *
4533 2758 * For example:
4534 2759 *
4535 2760 * array(
@@ -4624,38 +2849,31 @@
4624 2849 /**
4625 2850 * Removes insecure data from theme.json.
4626 2851 *
4627 2852 * @since 5.9.0
4628 - * @since 6.6.0 Added support for block style variation element styles and $origin parameter.
4629 2853 *
4630 - * @param array $theme_json Structure to sanitize.
4631 - * @param string $origin Optional. What source of data this object represents.
4632 - * One of 'blocks', 'default', 'theme', or 'custom'. Default 'theme'.
2854 + * @param array $theme_json Structure to sanitize.
4633 2855 * @return array Sanitized structure.
4634 2856 */
4635 - public static function remove_insecure_properties( $theme_json, $origin = 'theme' ) {
4636 - if ( ! in_array( $origin, static::VALID_ORIGINS, true ) ) {
4637 - $origin = 'theme';
4638 - }
4639 -
2857 + public static function remove_insecure_properties( $theme_json ) {
4640 2858 $sanitized = array();
4641 2859
4642 - $theme_json = WP_Theme_JSON_Schema_Gutenberg::migrate( $theme_json, $origin );
4643 - if ( isset( $theme_json['styles'] ) ) {
4644 - $theme_json['styles'] = gutenberg_resolve_style_state_aliases( $theme_json['styles'] );
4645 - }
2860 + $theme_json = WP_Theme_JSON_Schema::migrate( $theme_json );
4646 2861
4647 - $blocks_metadata = static::get_blocks_metadata();
4648 - $valid_block_names = array_keys( $blocks_metadata );
2862 + $valid_block_names = array_keys( static::get_blocks_metadata() );
4649 2863 $valid_element_names = array_keys( static::ELEMENTS );
4650 - $valid_variations = static::get_valid_block_style_variations( $blocks_metadata );
2864 + $valid_variations = array();
2865 + foreach ( self::get_blocks_metadata() as $block_name => $block_meta ) {
2866 + if ( ! isset( $block_meta['styleVariations'] ) ) {
2867 + continue;
2868 + }
2869 + $valid_variations[ $block_name ] = array_keys( $block_meta['styleVariations'] );
2870 + }
4651 2871
4652 2872 $theme_json = static::sanitize( $theme_json, $valid_block_names, $valid_element_names, $valid_variations );
4653 2873
4654 - $blocks_metadata = static::get_blocks_metadata();
4655 - $style_options = array( 'include_block_style_variations' => true ); // Allow variations data.
4656 - $style_nodes = static::get_style_nodes( $theme_json, $blocks_metadata, $style_options );
4657 - $responsive_media_queries = static::get_viewport_media_queries( $theme_json['settings']['viewport'] ?? null );
2874 + $blocks_metadata = static::get_blocks_metadata();
2875 + $style_nodes = static::get_style_nodes( $theme_json, $blocks_metadata );
4658 2876
4659 2877 foreach ( $style_nodes as $metadata ) {
4660 2878 $input = _wp_array_get( $theme_json, $metadata['path'], array() );
4661 2879 if ( empty( $input ) ) {
@@ -4661,12 +2879,8 @@
4661 2879 if ( empty( $input ) ) {
4662 2880 continue;
4663 2881 }
4664 2882
4665 - $block_name = in_array( 'blocks', $metadata['path'], true )
4666 - ? static::get_block_name_from_metadata_path( $metadata )
4667 - : null;
4668 -
4669 2883 // The global styles custom CSS is not sanitized, but can only be edited by users with 'edit_css' capability.
4670 2884 if ( isset( $input['css'] ) && current_user_can( 'edit_css' ) ) {
4671 2885 $output = $input;
4672 2886 } else {
@@ -4682,9 +2896,11 @@
4682 2896 /*
4683 2897 * $output is stripped of pseudo selectors. Re-add and process them
4684 2898 * or insecure styles here.
4685 2899 */
4686 - if ( isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $current_element ] ) ) {
2900 + // TODO: Replace array_key_exists() with isset() check once WordPress drops
2901 + // support for PHP 5.6. See https://core.trac.wordpress.org/ticket/57067.
2902 + if ( array_key_exists( $current_element, static::VALID_ELEMENT_PSEUDO_SELECTORS ) ) {
4687 2903 foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $current_element ] as $pseudo_selector ) {
4688 2904 if ( isset( $input[ $pseudo_selector ] ) ) {
4689 2905 $output[ $pseudo_selector ] = static::remove_insecure_styles( $input[ $pseudo_selector ] );
4690 2906 }
@@ -4690,90 +2906,11 @@
4690 2906 }
4691 2907 }
4692 2908 }
4693 2909
4694 - // Re-add and process responsive breakpoint styles.
4695 - foreach ( array_keys( $responsive_media_queries ) as $breakpoint ) {
4696 - if ( isset( $input[ $breakpoint ] ) ) {
4697 - $output[ $breakpoint ] = static::remove_insecure_styles( $input[ $breakpoint ] );
4698 -
4699 - if ( isset( $input[ $breakpoint ]['elements'] ) ) {
4700 - $output[ $breakpoint ]['elements'] = static::remove_insecure_element_styles( $input[ $breakpoint ]['elements'], $responsive_media_queries );
4701 - }
4702 -
4703 - if ( isset( $input[ $breakpoint ]['blocks'] ) ) {
4704 - $output[ $breakpoint ]['blocks'] = static::remove_insecure_inner_block_styles( $input[ $breakpoint ]['blocks'], $responsive_media_queries );
4705 - }
4706 -
4707 - if ( $block_name && isset( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block_name ] ) ) {
4708 - foreach ( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block_name ] as $pseudo_selector ) {
4709 - if ( isset( $input[ $breakpoint ][ $pseudo_selector ] ) ) {
4710 - $output[ $breakpoint ][ $pseudo_selector ] = static::remove_insecure_styles( $input[ $breakpoint ][ $pseudo_selector ] );
4711 - }
4712 - }
4713 - }
4714 -
4715 - // Responsive custom CSS is allowed for users with 'edit_css' capability.
4716 - if ( isset( $input[ $breakpoint ]['css'] ) && current_user_can( 'edit_css' ) ) {
4717 - $output[ $breakpoint ]['css'] = $input[ $breakpoint ]['css'];
4718 - }
4719 - }
4720 - }
4721 -
4722 2910 if ( ! empty( $output ) ) {
4723 2911 _wp_array_set( $sanitized, $metadata['path'], $output );
4724 2912 }
4725 -
4726 - if ( isset( $metadata['variations'] ) ) {
4727 - foreach ( $metadata['variations'] as $variation ) {
4728 - $variation_input = _wp_array_get( $theme_json, $variation['path'], array() );
4729 - if ( empty( $variation_input ) ) {
4730 - continue;
4731 - }
4732 -
4733 - $variation_output = static::remove_insecure_styles( $variation_input );
4734 -
4735 - if ( isset( $variation_input['blocks'] ) ) {
4736 - $variation_output['blocks'] = static::remove_insecure_inner_block_styles( $variation_input['blocks'], $responsive_media_queries );
4737 - }
4738 -
4739 - if ( isset( $variation_input['elements'] ) ) {
4740 - $variation_output['elements'] = static::remove_insecure_element_styles( $variation_input['elements'], $responsive_media_queries );
4741 - }
4742 -
4743 - // Re-add and process responsive breakpoint styles for variations.
4744 - foreach ( array_keys( $responsive_media_queries ) as $breakpoint ) {
4745 - if ( isset( $variation_input[ $breakpoint ] ) ) {
4746 - $variation_output[ $breakpoint ] = static::remove_insecure_styles( $variation_input[ $breakpoint ] );
4747 -
4748 - if ( isset( $variation_input[ $breakpoint ]['elements'] ) ) {
4749 - $variation_output[ $breakpoint ]['elements'] = static::remove_insecure_element_styles( $variation_input[ $breakpoint ]['elements'], $responsive_media_queries );
4750 - }
4751 -
4752 - if ( isset( $variation_input[ $breakpoint ]['blocks'] ) ) {
4753 - $variation_output[ $breakpoint ]['blocks'] = static::remove_insecure_inner_block_styles( $variation_input[ $breakpoint ]['blocks'], $responsive_media_queries );
4754 - }
4755 -
4756 - if ( $block_name && isset( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block_name ] ) ) {
4757 - foreach ( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block_name ] as $pseudo_selector ) {
4758 - if ( isset( $variation_input[ $breakpoint ][ $pseudo_selector ] ) ) {
4759 - $variation_output[ $breakpoint ][ $pseudo_selector ] = static::remove_insecure_styles( $variation_input[ $breakpoint ][ $pseudo_selector ] );
4760 - }
4761 - }
4762 - }
4763 -
4764 - // Responsive custom CSS is allowed for users with 'edit_css' capability.
4765 - if ( isset( $variation_input[ $breakpoint ]['css'] ) && current_user_can( 'edit_css' ) ) {
4766 - $variation_output[ $breakpoint ]['css'] = $variation_input[ $breakpoint ]['css'];
4767 - }
4768 - }
4769 - }
4770 -
4771 - if ( ! empty( $variation_output ) ) {
4772 - _wp_array_set( $sanitized, $variation['path'], $variation_output );
4773 - }
4774 - }
4775 - }
4776 2913 }
4777 2914
4778 2915 $setting_nodes = static::get_setting_nodes( $theme_json );
4779 2916 foreach ( $setting_nodes as $metadata ) {
@@ -4781,9 +2918,9 @@
4781 2918 if ( empty( $input ) ) {
4782 2919 continue;
4783 2920 }
4784 2921
4785 - $output = static::remove_insecure_settings( $input, array( 'settings' ) === $metadata['path'] );
2922 + $output = static::remove_insecure_settings( $input );
4786 2923 if ( ! empty( $output ) ) {
4787 2924 _wp_array_set( $sanitized, $metadata['path'], $output );
4788 2925 }
4789 2926 }
@@ -4803,145 +2940,17 @@
4803 2940 return $theme_json;
4804 2941 }
4805 2942
4806 2943 /**
4807 - * Remove insecure element styles within a variation or block.
4808 - *
4809 - * When responsive media queries are provided, nested responsive state styles
4810 - * for those media-query keys are re-added after the base sanitization pass.
4811 - *
4812 - * @since 6.8.0
4813 - *
4814 - * @param array $elements The elements to process.
4815 - * @param array|null $responsive_media_queries Optional. Media queries whose keys define allowed
4816 - * viewport states. Default null.
4817 - * @return array The sanitized elements styles.
4818 - */
4819 - protected static function remove_insecure_element_styles( $elements, $responsive_media_queries = null ) {
4820 - $sanitized = array();
4821 - $valid_element_names = array_keys( static::ELEMENTS );
4822 -
4823 - foreach ( $valid_element_names as $element_name ) {
4824 - $element_input = $elements[ $element_name ] ?? null;
4825 - if ( $element_input ) {
4826 - $element_output = static::remove_insecure_styles( $element_input );
4827 -
4828 - if ( isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element_name ] ) ) {
4829 - foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element_name ] as $pseudo_selector ) {
4830 - if ( isset( $element_input[ $pseudo_selector ] ) ) {
4831 - $element_output[ $pseudo_selector ] = static::remove_insecure_styles( $element_input[ $pseudo_selector ] );
4832 - }
4833 - }
4834 - }
4835 -
4836 - if ( null !== $responsive_media_queries ) {
4837 - // Re-add and process responsive breakpoint styles for elements.
4838 - foreach ( array_keys( $responsive_media_queries ) as $breakpoint ) {
4839 - if ( isset( $element_input[ $breakpoint ] ) ) {
4840 - $element_output[ $breakpoint ] = static::remove_insecure_styles( $element_input[ $breakpoint ] );
4841 -
4842 - if ( isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element_name ] ) ) {
4843 - foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element_name ] as $pseudo_selector ) {
4844 - if ( isset( $element_input[ $breakpoint ][ $pseudo_selector ] ) ) {
4845 - $element_output[ $breakpoint ][ $pseudo_selector ] = static::remove_insecure_styles( $element_input[ $breakpoint ][ $pseudo_selector ] );
4846 - }
4847 - }
4848 - }
4849 - }
4850 - }
4851 - }
4852 -
4853 - $sanitized[ $element_name ] = $element_output;
4854 - }
4855 - }
4856 - return $sanitized;
4857 - }
4858 -
4859 - /**
4860 - * Remove insecure styles from inner blocks and their elements.
4861 - *
4862 - * When responsive media queries are provided, nested responsive state styles
4863 - * for those media-query keys are re-added after the base sanitization pass.
4864 - *
4865 - * @since 6.8.0
4866 - *
4867 - * @param array $blocks The block styles to process.
4868 - * @param array|null $responsive_media_queries Optional. Media queries whose keys define allowed
4869 - * viewport states. Default null.
4870 - * @return array Sanitized block type styles.
4871 - */
4872 - protected static function remove_insecure_inner_block_styles( $blocks, $responsive_media_queries = null ) {
4873 - $sanitized = array();
4874 - foreach ( $blocks as $block_type => $block_input ) {
4875 - $block_output = static::remove_insecure_styles( $block_input );
4876 -
4877 - if ( isset( $block_input['elements'] ) ) {
4878 - $block_output['elements'] = static::remove_insecure_element_styles( $block_input['elements'], $responsive_media_queries );
4879 - }
4880 -
4881 - if ( null !== $responsive_media_queries ) {
4882 - // Re-add and process responsive breakpoint styles for inner blocks.
4883 - foreach ( array_keys( $responsive_media_queries ) as $breakpoint ) {
4884 - if ( isset( $block_input[ $breakpoint ] ) ) {
4885 - $block_output[ $breakpoint ] = static::remove_insecure_styles( $block_input[ $breakpoint ] );
4886 -
4887 - if ( isset( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block_type ] ) ) {
4888 - foreach ( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block_type ] as $pseudo_selector ) {
4889 - if ( isset( $block_input[ $breakpoint ][ $pseudo_selector ] ) ) {
4890 - $block_output[ $breakpoint ][ $pseudo_selector ] = static::remove_insecure_styles( $block_input[ $breakpoint ][ $pseudo_selector ] );
4891 - }
4892 - }
4893 - }
4894 - }
4895 - }
4896 - }
4897 -
4898 - $sanitized[ $block_type ] = $block_output;
4899 - }
4900 - return $sanitized;
4901 - }
4902 -
4903 - /**
4904 - * Preserves valid typed settings from input to output based on type markers in schema.
4905 - *
4906 - * Recursively iterates through the schema and validates/preserves settings
4907 - * that have type markers (e.g., boolean) in VALID_SETTINGS.
4908 - *
4909 - * @since 7.0.0
4910 - *
4911 - * @param array $input Input settings to process.
4912 - * @param array $output Output settings array (passed by reference).
4913 - * @param array $schema Schema to validate against (typically VALID_SETTINGS).
4914 - * @param array<string|int> $path Current path in the schema (for recursive calls).
4915 - */
4916 - private static function preserve_valid_typed_settings( $input, &$output, $schema, $path = array() ) {
4917 - foreach ( $schema as $key => $schema_value ) {
4918 - $current_path = array_merge( $path, array( $key ) );
4919 -
4920 - // Validate boolean type markers.
4921 - if ( is_bool( $schema_value ) ) {
4922 - $value = _wp_array_get( $input, $current_path, null );
4923 - if ( null !== $value && is_bool( $value ) ) {
4924 - _wp_array_set( $output, $current_path, $value ); // Preserve boolean value.
4925 - }
4926 - } elseif ( is_array( $schema_value ) ) {
4927 - self::preserve_valid_typed_settings( $input, $output, $schema_value, $current_path ); // Recurse into nested structure.
4928 - }
4929 - }
4930 - }
4931 -
4932 - /**
4933 2944 * Processes a setting node and returns the same node
4934 2945 * without the insecure settings.
4935 2946 *
4936 2947 * @since 5.9.0
4937 2948 *
4938 - * @param array $input Node to process.
4939 - * @param bool $allow_viewport Whether to preserve and sanitize top-level
4940 - * viewport settings.
2949 + * @param array $input Node to process.
4941 2950 * @return array
4942 2951 */
4943 - protected static function remove_insecure_settings( $input, $allow_viewport = true ) {
2952 + protected static function remove_insecure_settings( $input ) {
4944 2953 $output = array();
4945 2954 foreach ( static::PRESETS_METADATA as $preset_metadata ) {
4946 2955 foreach ( static::VALID_ORIGINS as $origin ) {
4947 2956 $path_with_origin = $preset_metadata['path'];
@@ -4989,15 +2998,8 @@
4989 2998
4990 2999 // Ensure indirect properties not included in any `PRESETS_METADATA` value are allowed.
4991 3000 static::remove_indirect_properties( $input, $output );
4992 3001
4993 - // Preserve all valid settings that have type markers in VALID_SETTINGS.
4994 - self::preserve_valid_typed_settings( $input, $output, static::VALID_SETTINGS );
4995 -
4996 - if ( $allow_viewport && array_key_exists( 'viewport', $input ) ) {
4997 - $output['viewport'] = static::sanitize_viewport_settings( $input['viewport'] );
4998 - }
4999 -
5000 3002 return $output;
5001 3003 }
5002 3004
5003 3005 /**
@@ -5098,24 +3100,39 @@
5098 3100 );
5099 3101
5100 3102 // Deprecated theme supports.
5101 3103 if ( isset( $settings['disableCustomColors'] ) ) {
3104 + if ( ! isset( $theme_settings['settings']['color'] ) ) {
3105 + $theme_settings['settings']['color'] = array();
3106 + }
5102 3107 $theme_settings['settings']['color']['custom'] = ! $settings['disableCustomColors'];
5103 3108 }
5104 3109
5105 3110 if ( isset( $settings['disableCustomGradients'] ) ) {
3111 + if ( ! isset( $theme_settings['settings']['color'] ) ) {
3112 + $theme_settings['settings']['color'] = array();
3113 + }
5106 3114 $theme_settings['settings']['color']['customGradient'] = ! $settings['disableCustomGradients'];
5107 3115 }
5108 3116
5109 3117 if ( isset( $settings['disableCustomFontSizes'] ) ) {
3118 + if ( ! isset( $theme_settings['settings']['typography'] ) ) {
3119 + $theme_settings['settings']['typography'] = array();
3120 + }
5110 3121 $theme_settings['settings']['typography']['customFontSize'] = ! $settings['disableCustomFontSizes'];
5111 3122 }
5112 3123
5113 3124 if ( isset( $settings['enableCustomLineHeight'] ) ) {
3125 + if ( ! isset( $theme_settings['settings']['typography'] ) ) {
3126 + $theme_settings['settings']['typography'] = array();
3127 + }
5114 3128 $theme_settings['settings']['typography']['lineHeight'] = $settings['enableCustomLineHeight'];
5115 3129 }
5116 3130
5117 3131 if ( isset( $settings['enableCustomUnits'] ) ) {
3132 + if ( ! isset( $theme_settings['settings']['spacing'] ) ) {
3133 + $theme_settings['settings']['spacing'] = array();
3134 + }
5118 3135 $theme_settings['settings']['spacing']['units'] = ( true === $settings['enableCustomUnits'] ) ?
5119 3136 array( 'px', 'em', 'rem', 'vh', 'vw', '%' ) :
5120 3137 $settings['enableCustomUnits'];
5121 3138 }
@@ -5120,12 +3137,18 @@
5120 3137 $settings['enableCustomUnits'];
5121 3138 }
5122 3139
5123 3140 if ( isset( $settings['colors'] ) ) {
3141 + if ( ! isset( $theme_settings['settings']['color'] ) ) {
3142 + $theme_settings['settings']['color'] = array();
3143 + }
5124 3144 $theme_settings['settings']['color']['palette'] = $settings['colors'];
5125 3145 }
5126 3146
5127 3147 if ( isset( $settings['gradients'] ) ) {
3148 + if ( ! isset( $theme_settings['settings']['color'] ) ) {
3149 + $theme_settings['settings']['color'] = array();
3150 + }
5128 3151 $theme_settings['settings']['color']['gradients'] = $settings['gradients'];
5129 3152 }
5130 3153
5131 3154 if ( isset( $settings['fontSizes'] ) ) {
@@ -5135,19 +3158,21 @@
5135 3158 if ( is_numeric( $font_size['size'] ) ) {
5136 3159 $font_sizes[ $key ]['size'] = $font_size['size'] . 'px';
5137 3160 }
5138 3161 }
3162 + if ( ! isset( $theme_settings['settings']['typography'] ) ) {
3163 + $theme_settings['settings']['typography'] = array();
3164 + }
5139 3165 $theme_settings['settings']['typography']['fontSizes'] = $font_sizes;
5140 3166 }
5141 3167
5142 3168 if ( isset( $settings['enableCustomSpacing'] ) ) {
3169 + if ( ! isset( $theme_settings['settings']['spacing'] ) ) {
3170 + $theme_settings['settings']['spacing'] = array();
3171 + }
5143 3172 $theme_settings['settings']['spacing']['padding'] = $settings['enableCustomSpacing'];
5144 3173 }
5145 3174
5146 - if ( isset( $settings['spacingSizes'] ) ) {
5147 - $theme_settings['settings']['spacing']['spacingSizes'] = $settings['spacingSizes'];
5148 - }
5149 -
5150 3175 return $theme_settings;
5151 3176 }
5152 3177
5153 3178 /**
@@ -5311,19 +3336,14 @@
5311 3336 /**
5312 3337 * Sets the spacingSizes array based on the spacingScale values from theme.json.
5313 3338 *
5314 3339 * @since 6.1.0
5315 - * @deprecated 6.6.0 No longer used as the spacingSizes are automatically
5316 - * generated in the constructor and merge methods instead
5317 - * of manually after instantiation.
5318 3340 *
5319 3341 * @return null|void
5320 3342 */
5321 3343 public function set_spacing_sizes() {
5322 - _deprecated_function( __METHOD__, '6.6.0' );
3344 + $spacing_scale = _wp_array_get( $this->theme_json, array( 'settings', 'spacing', 'spacingScale' ), array() );
5323 3345
5324 - $spacing_scale = $this->theme_json['settings']['spacing']['spacingScale']['default'] ?? array();
5325 -
5326 3346 // Gutenberg didn't have the 1st isset check.
5327 3347 if ( ! isset( $spacing_scale['steps'] )
5328 3348 || ! is_numeric( $spacing_scale['steps'] )
5329 3349 || ! isset( $spacing_scale['mediumStep'] )
@@ -5344,101 +3364,8 @@
5344 3364 if ( 0 === $spacing_scale['steps'] ) {
5345 3365 return null;
5346 3366 }
5347 3367
5348 - $spacing_sizes = static::compute_spacing_sizes( $spacing_scale );
5349 -
5350 - // If there are 7 or less steps in the scale revert to numbers for labels instead of t-shirt sizes.
5351 - if ( $spacing_scale['steps'] <= 7 ) {
5352 - for ( $spacing_sizes_count = 0; $spacing_sizes_count < count( $spacing_sizes ); $spacing_sizes_count++ ) {
5353 - $spacing_sizes[ $spacing_sizes_count ]['name'] = (string) ( $spacing_sizes_count + 1 );
5354 - }
5355 - }
5356 -
5357 - _wp_array_set( $this->theme_json, array( 'settings', 'spacing', 'spacingSizes', 'default' ), $spacing_sizes );
5358 - }
5359 -
5360 - /**
5361 - * Merges two sets of spacing size presets.
5362 - *
5363 - * @since 6.6.0
5364 - *
5365 - * @param array $base The base set of spacing sizes.
5366 - * @param array $incoming The set of spacing sizes to merge with the base. Duplicate slugs will override the base values.
5367 - * @return array The merged set of spacing sizes.
5368 - */
5369 - private static function merge_spacing_sizes( $base, $incoming ) {
5370 - // Preserve the order if there are no base (spacingScale) values.
5371 - if ( empty( $base ) ) {
5372 - return $incoming;
5373 - }
5374 - $merged = array();
5375 - foreach ( $base as $item ) {
5376 - $merged[ $item['slug'] ] = $item;
5377 - }
5378 - foreach ( $incoming as $item ) {
5379 - $merged[ $item['slug'] ] = $item;
5380 - }
5381 - ksort( $merged, SORT_NUMERIC );
5382 - return array_values( $merged );
5383 - }
5384 -
5385 - /**
5386 - * Generates a set of spacing sizes by starting with a medium size and
5387 - * applying an operator with an increment value to generate the rest of the
5388 - * sizes outward from the medium size. The medium slug is '50' with the rest
5389 - * of the slugs being 10 apart. The generated names use t-shirt sizing.
5390 - *
5391 - * Example:
5392 - *
5393 - * $spacing_scale = array(
5394 - * 'steps' => 4,
5395 - * 'mediumStep' => 16,
5396 - * 'unit' => 'px',
5397 - * 'operator' => '+',
5398 - * 'increment' => 2,
5399 - * );
5400 - * $spacing_sizes = static::compute_spacing_sizes( $spacing_scale );
5401 - * // -> array(
5402 - * // array( 'name' => 'Small', 'slug' => '40', 'size' => '14px' ),
5403 - * // array( 'name' => 'Medium', 'slug' => '50', 'size' => '16px' ),
5404 - * // array( 'name' => 'Large', 'slug' => '60', 'size' => '18px' ),
5405 - * // array( 'name' => 'X-Large', 'slug' => '70', 'size' => '20px' ),
5406 - * // )
5407 - *
5408 - * @since 6.6.0
5409 - *
5410 - * @param array $spacing_scale {
5411 - * The spacing scale values. All are required.
5412 - *
5413 - * @type int $steps The number of steps in the scale. (up to 10 steps are supported.)
5414 - * @type float $mediumStep The middle value that gets the slug '50'. (For even number of steps, this becomes the first middle value.)
5415 - * @type string $unit The CSS unit to use for the sizes.
5416 - * @type string $operator The mathematical operator to apply to generate the other sizes. Either '+' or '*'.
5417 - * @type float $increment The value used with the operator to generate the other sizes.
5418 - * }
5419 - * @return array The spacing sizes presets or an empty array if some spacing scale values are missing or invalid.
5420 - */
5421 - private static function compute_spacing_sizes( $spacing_scale ) {
5422 - /*
5423 - * This condition is intentionally missing some checks on ranges for the values in order to
5424 - * keep backwards compatibility with the previous implementation.
5425 - */
5426 - if (
5427 - ! isset( $spacing_scale['steps'] ) ||
5428 - ! is_numeric( $spacing_scale['steps'] ) ||
5429 - 0 === $spacing_scale['steps'] ||
5430 - ! isset( $spacing_scale['mediumStep'] ) ||
5431 - ! is_numeric( $spacing_scale['mediumStep'] ) ||
5432 - ! isset( $spacing_scale['unit'] ) ||
5433 - ! isset( $spacing_scale['operator'] ) ||
5434 - ( '+' !== $spacing_scale['operator'] && '*' !== $spacing_scale['operator'] ) ||
5435 - ! isset( $spacing_scale['increment'] ) ||
5436 - ! is_numeric( $spacing_scale['increment'] )
5437 - ) {
5438 - return array();
5439 - }
5440 -
5441 3368 $unit = '%' === $spacing_scale['unit'] ? '%' : sanitize_title( $spacing_scale['unit'] );
5442 3369 $current_step = $spacing_scale['mediumStep'];
5443 3370 $steps_mid_point = round( $spacing_scale['steps'] / 2, 0 );
5444 3371 $x_small_count = null;
@@ -5471,9 +3398,9 @@
5471 3398 $x_small_count = 2;
5472 3399 }
5473 3400
5474 3401 if ( $below_midpoint_count < $steps_mid_point - 2 ) {
5475 - ++$x_small_count;
3402 + $x_small_count++;
5476 3403 }
5477 3404
5478 3405 $slug -= 10;
5479 3406 }
@@ -5508,9 +3435,9 @@
5508 3435 $x_large_count = 2;
5509 3436 }
5510 3437
5511 3438 if ( $above_midpoint_count > 1 ) {
5512 - ++$x_large_count;
3439 + $x_large_count++;
5513 3440 }
5514 3441
5515 3442 $slug += 10;
5516 3443 }
@@ -5519,9 +3446,16 @@
5519 3446 foreach ( $above_sizes as $above_sizes_item ) {
5520 3447 $spacing_sizes[] = $above_sizes_item;
5521 3448 }
5522 3449
5523 - return $spacing_sizes;
3450 + // If there are 7 or less steps in the scale revert to numbers for labels instead of t-shirt sizes.
3451 + if ( $spacing_scale['steps'] <= 7 ) {
3452 + for ( $spacing_sizes_count = 0; $spacing_sizes_count < count( $spacing_sizes ); $spacing_sizes_count++ ) {
3453 + $spacing_sizes[ $spacing_sizes_count ]['name'] = (string) ( $spacing_sizes_count + 1 );
3454 + }
3455 + }
3456 +
3457 + _wp_array_set( $this->theme_json, array( 'settings', 'spacing', 'spacingSizes', 'default' ), $spacing_sizes );
5524 3458 }
5525 3459
5526 3460 /**
5527 3461 * Returns the selectors metadata for a block.
@@ -5592,14 +3526,14 @@
5592 3526 if ( ! isset( $metadata['selectors'] ) ) {
5593 3527 return $declarations;
5594 3528 }
5595 3529
5596 - $settings = $this->theme_json['settings'] ?? null;
3530 + $settings = _wp_array_get( $this->theme_json, array( 'settings' ) );
5597 3531
5598 3532 foreach ( $metadata['selectors'] as $feature => $feature_selectors ) {
5599 - // Skip if this is the block's root selector, the custom CSS
5600 - // selector, or the block doesn't have any styles for the feature.
5601 - if ( 'root' === $feature || 'css' === $feature || empty( $node[ $feature ] ) ) {
3533 + // Skip if this is the block's root selector or the block doesn't
3534 + // have any styles for the feature.
3535 + if ( 'root' === $feature || empty( $node[ $feature ] ) ) {
5602 3536 continue;
5603 3537 }
5604 3538
5605 3539 if ( is_array( $feature_selectors ) ) {
@@ -5682,9 +3616,9 @@
5682 3616 $prefix = 'var:';
5683 3617 $prefix_len = strlen( $prefix );
5684 3618 $token_in = '|';
5685 3619 $token_out = '--';
5686 - if ( str_starts_with( $value, $prefix ) ) {
3620 + if ( 0 === strpos( $value, $prefix ) ) {
5687 3621 $unwrapped_name = str_replace(
5688 3622 $token_in,
5689 3623 $token_out,
5690 3624 substr( $value, $prefix_len )
@@ -5706,9 +3640,9 @@
5706 3640 private static function resolve_custom_css_format( $tree ) {
5707 3641 $prefix = 'var:';
5708 3642
5709 3643 foreach ( $tree as $key => $data ) {
5710 - if ( is_string( $data ) && str_starts_with( $data, $prefix ) ) {
3644 + if ( is_string( $data ) && 0 === strpos( $data, $prefix ) ) {
5711 3645 $tree[ $key ] = self::convert_custom_properties( $data );
5712 3646 } elseif ( is_array( $data ) ) {
5713 3647 $tree[ $key ] = self::resolve_custom_css_format( $data );
5714 3648 }
@@ -5720,10 +3654,8 @@
5720 3654 /**
5721 3655 * Replaces CSS variables with their values in place.
5722 3656 *
5723 3657 * @since 6.3.0
5724 - * @since 6.6.0 Check for empty style before processing.
5725 - *
5726 3658 * @param array $styles CSS declarations to convert.
5727 3659 * @param array $values key => value pairs to use for replacement.
5728 3660 * @return array
5729 3661 */
@@ -5728,12 +3660,8 @@
5728 3660 * @return array
5729 3661 */
5730 3662 private static function convert_variables_to_value( $styles, $values ) {
5731 3663 foreach ( $styles as $key => $style ) {
5732 - if ( empty( $style ) ) {
5733 - continue;
5734 - }
5735 -
5736 3664 if ( is_array( $style ) ) {
5737 3665 $styles[ $key ] = self::convert_variables_to_value( $style, $values );
5738 3666 continue;
5739 3667 }
@@ -5754,10 +3682,10 @@
5754 3682 $rule_to_replace,
5755 3683 $fallback,
5756 3684 ),
5757 3685 array(
5758 - $values[ $key_in_values ] ?? $rule_to_replace,
5759 - $values[ $fallback ] ?? $fallback,
3686 + isset( $values[ $key_in_values ] ) ? $values[ $key_in_values ] : $rule_to_replace,
3687 + isset( $values[ $fallback ] ) ? $values[ $fallback ] : $fallback,
5760 3688 ),
5761 3689 $resolved_style
5762 3690 );
5763 3691 }
@@ -5783,12 +3711,12 @@
5783 3711 $preset_vars = static::compute_preset_vars( $settings, static::VALID_ORIGINS );
5784 3712 $theme_vars = static::compute_theme_vars( $settings );
5785 3713 $vars = array_reduce(
5786 3714 array_merge( $preset_vars, $theme_vars ),
5787 - function ( $carry, $item ) {
3715 + function( $carry, $item ) {
5788 3716 $name = $item['name'];
5789 3717 $carry[ "var({$name})" ] = $item['value'];
5790 - return $carry;
3718 + return $carry;
5791 3719 },
5792 3720 array()
5793 3721 );
5794 3722
@@ -5795,127 +3723,5 @@
5795 3723 $theme_json->theme_json['styles'] = self::convert_variables_to_value( $styles, $vars );
5796 3724 return $theme_json;
5797 3725 }
5798 3726
5799 - /**
5800 - * Generates a selector for a block style variation.
5801 - *
5802 - * @param string $variation_name Name of the block style variation.
5803 - * @param string $block_selector CSS selector for the block.
5804 - *
5805 - * @return string Block selector with block style variation selector added to it.
5806 - */
5807 - protected static function get_block_style_variation_selector( $variation_name, $block_selector ) {
5808 - $variation_class = ".is-style-$variation_name";
5809 -
5810 - if ( ! $block_selector ) {
5811 - return $variation_class;
5812 - }
5813 -
5814 - $limit = 1;
5815 - $selector_parts = static::split_selector_list( $block_selector );
5816 - $result = array();
5817 -
5818 - /*
5819 - * Append the variation class to each selector's ancestor: the first
5820 - * run of characters before any combinator (whitespace) or pseudo-class
5821 - * (`:`). Only the first match is replaced.
5822 - *
5823 - * Examples ("custom" variation):
5824 - * - `.wp-block` => `.wp-block.is-style-custom`
5825 - * - `.wp-block .inner` => `.wp-block.is-style-custom .inner`
5826 - * - `.wp-block:where(.a .b)` => `.wp-block.is-style-custom:where(.a .b)`
5827 - * - `:where(.outer .inner)` => `:where(.outer.is-style-custom .inner)`
5828 - */
5829 - foreach ( $selector_parts as $part ) {
5830 - $result[] = preg_replace_callback(
5831 - '/[^\s:]+/',
5832 - function ( $matches ) use ( $variation_class ) {
5833 - return $matches[0] . $variation_class;
5834 - },
5835 - $part,
5836 - $limit
5837 - );
5838 - }
5839 -
5840 - return implode( ', ', $result );
5841 - }
5842 -
5843 - /**
5844 - * Applies a block style variation class to a feature selector.
5845 - *
5846 - * Feature selectors can target a different element than the block's root
5847 - * selector. For example, the Button block's root selector targets the inner
5848 - * link, while its dimensions width selector targets the outer wrapper. Apply
5849 - * the variation class directly to the selector that will receive the
5850 - * declarations instead of deriving it by subtracting the root selector from
5851 - * the feature selector.
5852 - *
5853 - * @param array $style_variation Style variation metadata.
5854 - * @param string $feature_selector CSS selector for the feature.
5855 - * @return string Feature selector with block style variation selector added.
5856 - */
5857 - protected static function get_block_style_variation_feature_selector( $style_variation, $feature_selector ) {
5858 - $variation_path = $style_variation['path'] ?? array();
5859 - $variation_name = $style_variation['name'] ?? ( is_array( $variation_path ) ? end( $variation_path ) : null );
5860 -
5861 - if ( ! $variation_name ) {
5862 - return $style_variation['selector'] ?? $feature_selector;
5863 - }
5864 -
5865 - $variation_class = ".is-style-$variation_name";
5866 - $selector_parts = static::split_selector_list( $feature_selector );
5867 - $selector_parts = array_map(
5868 - static function ( $selector ) use ( $variation_class ) {
5869 - $prefix = $variation_class . ' ';
5870 -
5871 - if ( str_starts_with( $selector, $prefix ) ) {
5872 - return substr( $selector, strlen( $prefix ) );
5873 - }
5874 -
5875 - return $selector;
5876 - },
5877 - $selector_parts
5878 - );
5879 -
5880 - return static::get_block_style_variation_selector(
5881 - $variation_name,
5882 - implode( ', ', $selector_parts )
5883 - );
5884 - }
5885 -
5886 - /**
5887 - * Collects valid block style variations keyed by block type.
5888 - *
5889 - * @since 6.6.0
5890 - * @since 6.8.0 Added the `$blocks_metadata` parameter.
5891 - *
5892 - * @param array $blocks_metadata Optional. List of metadata per block. Default is the metadata for all blocks.
5893 - * @return array Valid block style variations by block type.
5894 - */
5895 - protected static function get_valid_block_style_variations( $blocks_metadata = array() ) {
5896 - $valid_variations = array();
5897 - $blocks_metadata = empty( $blocks_metadata ) ? static::get_blocks_metadata() : $blocks_metadata;
5898 - foreach ( $blocks_metadata as $block_name => $block_meta ) {
5899 - if ( ! isset( $block_meta['styleVariations'] ) ) {
5900 - continue;
5901 - }
5902 - $valid_variations[ $block_name ] = array_keys( $block_meta['styleVariations'] );
5903 - }
5904 -
5905 - return $valid_variations;
5906 - }
5907 -
5908 - /**
5909 - * Extracts the block name from the block metadata path.
5910 - *
5911 - * @since 7.0
5912 - *
5913 - * @param array $block_metadata Block metadata.
5914 - * @return string|null The block name or null if not found.
5915 - */
5916 - private static function get_block_name_from_metadata_path( $block_metadata ) {
5917 - if ( isset( $block_metadata['path'] ) ) {
5918 - return $block_metadata['path'][2];
5919 - }
5920 - }
5921 3727 }