PluginProbe
Gutenberg / 22.7.0
Gutenberg v22.7.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 +126 -1071 23.6.222.7.0 View file →
@@ -240,9 +240,9 @@
240 240 * removed the `--wp--style--block-gap` property.
241 241 * @since 6.2.0 Added `outline-*`, and `min-height` properties.
242 242 * @since 6.3.0 Added `writing-mode` property.
243 243 * @since 6.6.0 Added `background-[image|position|repeat|size]` properties.
244 - * @since 7.0.0 Added `dimensions.width`, `dimensions.height`, and
244 + * @since 7.0.0 Added `dimensions.width`, `dimensions.height`. and
245 245 * `typography.textIndent` properties.
246 246 *
247 247 * @var array
248 248 */
@@ -289,9 +289,8 @@
289 289 'margin-right' => array( 'spacing', 'margin', 'right' ),
290 290 'margin-bottom' => array( 'spacing', 'margin', 'bottom' ),
291 291 'margin-left' => array( 'spacing', 'margin', 'left' ),
292 292 'min-height' => array( 'dimensions', 'minHeight' ),
293 - 'min-width' => array( 'dimensions', 'minWidth' ),
294 293 'outline-color' => array( 'outline', 'color' ),
295 294 'outline-offset' => array( 'outline', 'offset' ),
296 295 'outline-style' => array( 'outline', 'style' ),
297 296 'outline-width' => array( 'outline', 'width' ),
@@ -305,9 +304,8 @@
305 304 '--wp--style--root--padding-right' => array( 'spacing', 'padding', 'right' ),
306 305 '--wp--style--root--padding-bottom' => array( 'spacing', 'padding', 'bottom' ),
307 306 '--wp--style--root--padding-left' => array( 'spacing', 'padding', 'left' ),
308 307 'text-decoration' => array( 'typography', 'textDecoration' ),
309 - 'text-shadow' => array( 'typography', 'textShadow' ),
310 308 'text-transform' => array( 'typography', 'textTransform' ),
311 309 'text-indent' => array( 'typography', 'textIndent' ),
312 310 'filter' => array( 'filter', 'duotone' ),
313 311 'box-shadow' => array( 'shadow' ),
@@ -346,9 +344,8 @@
346 344 array( 'layout', 'wideSize' ),
347 345 ),
348 346 'background-image' => array(
349 347 array( 'background', 'backgroundImage', 'url' ),
350 - array( 'background', 'gradient' ),
351 348 ),
352 349 );
353 350
354 351
@@ -389,11 +386,10 @@
389 386 * @since 6.3.0 Removed `layout.definitions`. Added `typography.writingMode`.
390 387 * @since 6.4.0 Added `layout.allowEditing`.
391 388 * @since 6.4.0 Added `lightbox`.
392 389 * @since 7.0.0 Added type markers to the schema for boolean values.
393 - * @since 7.0.0 Added `dimensions.width`, `dimensions.height`, and
390 + * @since 7.0.0 Added `dimensions.width`, `dimensions.height`. and
394 391 * `typography.textIndent` properties.
395 - * @since 7.1.0 Added `viewport` property.
396 392 * @var array
397 393 */
398 394 const VALID_SETTINGS = array(
399 395 'appearanceTools' => null,
@@ -400,13 +396,9 @@
400 396 'useRootPaddingAwareAlignments' => null,
401 397 'background' => array(
402 398 'backgroundImage' => null,
403 399 'backgroundSize' => null,
404 - 'gradient' => null,
405 400 ),
406 - 'blockVisibility' => array(
407 - 'allowEditing' => true,
408 - ),
409 401 'border' => array(
410 402 'color' => null,
411 403 'radius' => null,
412 404 'style' => null,
@@ -437,9 +429,8 @@
437 429 'defaultAspectRatios' => null,
438 430 'dimensionSizes' => null,
439 431 'height' => null,
440 432 'minHeight' => null,
441 - 'minWidth' => null,
442 433 'width' => null,
443 434 ),
444 435 'layout' => array(
445 436 'contentSize' => null,
@@ -486,12 +477,8 @@
486 477 'textIndent' => null,
487 478 'textTransform' => null,
488 479 'writingMode' => null,
489 480 ),
490 - 'viewport' => array(
491 - 'mobile' => null,
492 - 'tablet' => null,
493 - ),
494 481 );
495 482
496 483 const FONT_FAMILY_SCHEMA = array(
497 484 array(
@@ -530,9 +517,9 @@
530 517 * updated `blockGap` to be allowed at any level.
531 518 * @since 6.2.0 Added `outline`, and `minHeight` properties.
532 519 * @since 6.6.0 Added `background` sub properties to top-level only.
533 520 * @since 6.6.0 Added `dimensions.aspectRatio`.
534 - * @since 7.0.0 Added `dimensions.width`, `dimensions.height`, and
521 + * @since 7.0.0 Added `dimensions.width`, `dimensions.height`. and
535 522 * `typography.textIndent` properties.
536 523 * @var array
537 524 */
538 525 const VALID_STYLES = array(
@@ -541,9 +528,8 @@
541 528 'backgroundAttachment' => null,
542 529 'backgroundPosition' => null,
543 530 'backgroundRepeat' => null,
544 531 'backgroundSize' => null,
545 - 'gradient' => null,
546 532 ),
547 533 'border' => array(
548 534 'color' => null,
549 535 'radius' => null,
@@ -562,9 +548,8 @@
562 548 'dimensions' => array(
563 549 'aspectRatio' => null,
564 550 'height' => null,
565 551 'minHeight' => null,
566 - 'minWidth' => null,
567 552 'width' => null,
568 553 ),
569 554 'filter' => array(
570 555 'duotone' => null,
@@ -591,9 +576,8 @@
591 576 'textAlign' => null,
592 577 'textColumns' => null,
593 578 'textDecoration' => null,
594 579 'textIndent' => null,
595 - 'textShadow' => null,
596 580 'textTransform' => null,
597 581 'writingMode' => null,
598 582 ),
599 583 'css' => null,
@@ -625,205 +609,12 @@
625 609 * @since 7.0.0
626 610 * @var array
627 611 */
628 612 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' ),
613 + 'core/button' => array( ':hover', ':focus', ':focus-visible', ':active' ),
631 614 );
632 615
633 616 /**
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 617 * The valid elements that can be found under styles.
827 618 *
828 619 * @since 5.8.0
829 620 * @since 6.1.0 Added `heading`, `button`, and `caption` elements.
@@ -919,29 +710,13 @@
919 710 * @param array $node The node data (block or variation).
920 711 * @param string $base_selector The base selector.
921 712 * @param array $settings The theme settings.
922 713 * @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 714 * @return array Array of pseudo-selector declarations.
926 715 */
927 - private function process_pseudo_selectors( $node, $base_selector, $settings, $block_name, $block_metadata = null, $style_variation = null ) {
716 + private static function process_pseudo_selectors( $node, $base_selector, $settings, $block_name ) {
928 717 $pseudo_declarations = array();
929 - $add_declarations = static function ( $selector, $declarations ) use ( &$pseudo_declarations ) {
930 - if ( empty( $declarations ) ) {
931 - return;
932 - }
933 718
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 719 if ( ! isset( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block_name ] ) ) {
945 720 return $pseudo_declarations;
946 721 }
947 722
@@ -946,28 +721,11 @@
946 721 }
947 722
948 723 foreach ( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block_name ] as $pseudo_selector ) {
949 724 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 );
725 + $combined_selector = static::append_to_selector( $base_selector, $pseudo_selector );
726 + $declarations = static::compute_style_properties( $node[ $pseudo_selector ], $settings, null, null );
727 + $pseudo_declarations[ $combined_selector ] = $declarations;
970 728 }
971 729 }
972 730
973 731 return $pseudo_declarations;
@@ -1001,9 +759,8 @@
1001 759 */
1002 760 const APPEARANCE_TOOLS_OPT_INS = array(
1003 761 array( 'background', 'backgroundImage' ),
1004 762 array( 'background', 'backgroundSize' ),
1005 - array( 'background', 'gradient' ),
1006 763 array( 'border', 'color' ),
1007 764 array( 'border', 'radius' ),
1008 765 array( 'border', 'style' ),
1009 766 array( 'border', 'width' ),
@@ -1013,9 +770,8 @@
1013 770 array( 'color', 'caption' ),
1014 771 array( 'dimensions', 'aspectRatio' ),
1015 772 array( 'dimensions', 'height' ),
1016 773 array( 'dimensions', 'minHeight' ),
1017 - array( 'dimensions', 'minWidth' ),
1018 774 array( 'dimensions', 'width' ),
1019 775 // BEGIN EXPERIMENTAL.
1020 776 // Allow `position.fixed` to be opted-in by default.
1021 777 // Sticky position support was backported to WordPress 6.2 in https://core.trac.wordpress.org/ticket/57618.
@@ -1055,12 +811,9 @@
1055 811 if ( ! in_array( $origin, static::VALID_ORIGINS, true ) ) {
1056 812 $origin = 'theme';
1057 813 }
1058 814
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'] );
1062 - }
815 + $this->theme_json = WP_Theme_JSON_Schema_Gutenberg::migrate( $theme_json, $origin );
1063 816 $blocks_metadata = static::get_blocks_metadata();
1064 817 $valid_block_names = array_keys( $blocks_metadata );
1065 818 $valid_element_names = array_keys( static::ELEMENTS );
1066 819 $valid_variations = static::get_valid_block_style_variations( $blocks_metadata );
@@ -1265,19 +1018,17 @@
1265 1018 }
1266 1019 }
1267 1020
1268 1021 // 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 );
1022 + $schema = array();
1023 + $schema_styles_elements = array();
1272 1024
1273 1025 /*
1274 - * Set allowed element pseudo selectors and responsive breakpoint states.
1026 + * Set allowed element pseudo selectors based on per element allow list.
1275 1027 * Target data structure in schema:
1276 1028 * e.g.
1277 1029 * - top level elements: `$schema['styles']['elements']['link'][':hover']`.
1278 1030 * - 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 1031 */
1281 1032 foreach ( $valid_element_names as $element ) {
1282 1033 $schema_styles_elements[ $element ] = $styles_non_top_level;
1283 1034
@@ -1285,13 +1036,8 @@
1285 1036 foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element ] as $pseudo_selector ) {
1286 1037 $schema_styles_elements[ $element ][ $pseudo_selector ] = $styles_non_top_level;
1287 1038 }
1288 1039 }
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 1040 }
1295 1041
1296 1042 $schema_styles_blocks = array();
1297 1043 $schema_settings_blocks = array();
@@ -1297,36 +1043,21 @@
1297 1043 $schema_settings_blocks = array();
1298 1044
1299 1045 /*
1300 1046 * Generate a schema for blocks.
1301 - * - Block styles can contain `elements`, `variations`, and responsive breakpoint state definitions.
1047 + * - Block styles can contain `elements` & `variations` definitions.
1302 1048 * - 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.
1049 + * - Variations can contain styles for inner `blocks`.
1050 + * - Variation inner `blocks` styles can contain `elements`.
1305 1051 *
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.
1052 + * As each variation needs a `blocks` schema but further nested
1053 + * inner `blocks`, the overall schema will be generated in multiple passes.
1308 1054 */
1309 1055 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'] );
1056 + $schema_settings_blocks[ $block ] = static::VALID_SETTINGS;
1314 1057 $schema_styles_blocks[ $block ] = $styles_non_top_level;
1315 1058 $schema_styles_blocks[ $block ]['elements'] = $schema_styles_elements;
1316 1059
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 1060 // Add pseudo-selectors for blocks that support them.
1330 1061 if ( isset( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block ] ) ) {
1331 1062 foreach ( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block ] as $pseudo_selector ) {
1332 1063 $schema_styles_blocks[ $block ][ $pseudo_selector ] = $styles_non_top_level;
@@ -1331,23 +1062,8 @@
1331 1062 foreach ( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block ] as $pseudo_selector ) {
1332 1063 $schema_styles_blocks[ $block ][ $pseudo_selector ] = $styles_non_top_level;
1333 1064 }
1334 1065 }
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 1066 }
1351 1067
1352 1068 $block_style_variation_styles = static::VALID_STYLES;
1353 1069 $block_style_variation_styles['blocks'] = $schema_styles_blocks;
@@ -1371,21 +1087,8 @@
1371 1087 if ( ! empty( $style_variation_names ) ) {
1372 1088 foreach ( $style_variation_names as $variation_name ) {
1373 1089 $variation_schema = $block_style_variation_styles;
1374 1090
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 1091 // Add pseudo-selectors to variations for blocks that support them.
1389 1092 if ( isset( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block ] ) ) {
1390 1093 foreach ( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block ] as $pseudo_selector ) {
1391 1094 $variation_schema[ $pseudo_selector ] = $styles_non_top_level;
@@ -1418,12 +1121,8 @@
1418 1121 }
1419 1122
1420 1123 $result = static::remove_keys_not_in_schema( $input[ $subtree ], $schema[ $subtree ] );
1421 1124
1422 - if ( 'settings' === $subtree && array_key_exists( 'viewport', $input[ $subtree ] ) ) {
1423 - $result['viewport'] = static::sanitize_viewport_settings( $input[ $subtree ]['viewport'] );
1424 - }
1425 -
1426 1125 if ( empty( $result ) ) {
1427 1126 unset( $output[ $subtree ] );
1428 1127 } else {
1429 1128 $output[ $subtree ] = static::resolve_custom_css_format( $result );
@@ -1451,38 +1150,14 @@
1451 1150 protected static function append_to_selector( $selector, $to_append ) {
1452 1151 if ( ! str_contains( $selector, ',' ) ) {
1453 1152 return $selector . $to_append;
1454 1153 }
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 - * @see 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 1154 $new_selectors = array();
1480 - $selectors = static::split_selector_list( $selector );
1155 + $selectors = explode( ',', $selector );
1481 1156 foreach ( $selectors as $sel ) {
1482 1157 $new_selectors[] = $sel . $to_append;
1483 1158 }
1484 - return implode( ', ', $new_selectors );
1159 + return implode( ',', $new_selectors );
1485 1160 }
1486 1161
1487 1162 /**
1488 1163 * Prepends a sub-selector to an existing one.
@@ -1500,199 +1175,17 @@
1500 1175 protected static function prepend_to_selector( $selector, $to_prepend ) {
1501 1176 if ( ! str_contains( $selector, ',' ) ) {
1502 1177 return $to_prepend . $selector;
1503 1178 }
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 - * @see 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 1179 $new_selectors = array();
1529 - $selectors = static::split_selector_list( $selector );
1180 + $selectors = explode( ',', $selector );
1530 1181 foreach ( $selectors as $sel ) {
1531 1182 $new_selectors[] = $to_prepend . $sel;
1532 1183 }
1533 -
1534 - return implode( ', ', $new_selectors );
1184 + return implode( ',', $new_selectors );
1535 1185 }
1536 1186
1537 1187 /**
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 - * @see https://www.w3.org/TR/selectors/#parse-selector
1569 - * @see 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 - * @see https://www.w3.org/TR/css-syntax/#whitespace
1675 - * @see 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 1188 * Returns the metadata for each block.
1696 1189 *
1697 1190 * Example:
1698 1191 *
@@ -1796,13 +1289,8 @@
1796 1289
1797 1290 if ( ! empty( $style_selectors ) ) {
1798 1291 static::$blocks_metadata[ $block_name ]['styleVariations'] = $style_selectors;
1799 1292 }
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 1293 }
1806 1294
1807 1295 return static::$blocks_metadata;
1808 1296 }
@@ -2397,13 +1885,8 @@
2397 1885 }
2398 1886 }
2399 1887 }
2400 1888 }
2401 -
2402 - if ( ! empty( $options['media_query'] ) && ! empty( $block_rules ) ) {
2403 - $block_rules = $options['media_query'] . '{' . $block_rules . '}';
2404 - }
2405 -
2406 1889 return $block_rules;
2407 1890 }
2408 1891
2409 1892 /**
@@ -2564,9 +2047,8 @@
2564 2047 * Given a selector and a declaration list,
2565 2048 * creates the corresponding ruleset.
2566 2049 *
2567 2050 * @since 5.8.0
2568 - * @since 7.1.0 Skip declarations whose value is not a plain string (booleans, arrays, objects, etc.).
2569 2051 *
2570 2052 * @param string $selector CSS selector.
2571 2053 * @param array $declarations List of declarations.
2572 2054 * @return string The resulting CSS ruleset.
@@ -2578,20 +2060,9 @@
2578 2060
2579 2061 $declaration_block = array_reduce(
2580 2062 $declarations,
2581 2063 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 - },
2064 + return $carry .= $element['name'] . ': ' . $element['value'] . ';'; },
2594 2065 ''
2595 2066 );
2596 2067
2597 2068 return $selector . '{' . $declaration_block . '}';
@@ -2667,14 +2138,16 @@
2667 2138 if ( ! $scope || ! $selector ) {
2668 2139 return $selector;
2669 2140 }
2670 2141
2671 - $scopes = static::split_selector_list( $scope );
2672 - $selectors = static::split_selector_list( $selector );
2142 + $scopes = explode( ',', $scope );
2143 + $selectors = explode( ',', $selector );
2673 2144
2674 2145 $selectors_scoped = array();
2675 2146 foreach ( $scopes as $outer ) {
2676 2147 foreach ( $selectors as $inner ) {
2148 + $outer = trim( $outer );
2149 + $inner = trim( $inner );
2677 2150 if ( ! empty( $outer ) && ! empty( $inner ) ) {
2678 2151 $selectors_scoped[] = $outer . ' ' . $inner;
2679 2152 } elseif ( empty( $outer ) ) {
2680 2153 $selectors_scoped[] = $inner;
@@ -3028,23 +2501,14 @@
3028 2501 * If the value is a URL, it will be converted to a CSS `url()` value.
3029 2502 * For an uploaded image (images with a database ID), apply size and position
3030 2503 * defaults equal to those applied in block supports in lib/background.php.
3031 2504 */
3032 - if ( 'background-image' === $css_property ) {
3033 - $background_image_input = array();
3034 - if ( ! empty( $value ) ) {
3035 - $background_image_input['backgroundImage'] = $value;
3036 - }
3037 - $gradient_value = $styles['background']['gradient'] ?? null;
3038 - if ( ! empty( $gradient_value ) ) {
3039 - $background_image_input['gradient'] = $gradient_value;
3040 - }
3041 - if ( ! empty( $background_image_input ) ) {
3042 - $background_styles = gutenberg_style_engine_get_styles(
3043 - array( 'background' => $background_image_input )
3044 - );
3045 - $value = $background_styles['declarations'][ $css_property ] ?? null;
3046 - }
2505 + if ( 'background-image' === $css_property && ! empty( $value ) ) {
2506 + $background_styles = gutenberg_style_engine_get_styles(
2507 + array( 'background' => array( 'backgroundImage' => $value ) )
2508 + );
2509 +
2510 + $value = $background_styles['declarations'][ $css_property ];
3047 2511 }
3048 2512 if ( empty( $value ) && static::ROOT_BLOCK_SELECTOR !== $selector && ! empty( $styles['background']['backgroundImage']['id'] ) ) {
3049 2513 if ( 'background-size' === $css_property ) {
3050 2514 $value = 'cover';
@@ -3502,11 +2966,10 @@
3502 2966 if ( ! isset( $theme_json['styles']['blocks'] ) ) {
3503 2967 return $nodes;
3504 2968 }
3505 2969
3506 - $include_variations = $options['include_block_style_variations'] ?? false;
3507 - $include_node_paths_only = $options['include_node_paths_only'] ?? false;
3508 - $responsive_media_queries = static::get_viewport_media_queries( $theme_json['settings']['viewport'] ?? null );
2970 + $include_variations = $options['include_block_style_variations'] ?? false;
2971 + $include_node_paths_only = $options['include_node_paths_only'] ?? false;
3509 2972
3510 2973 // If only node paths are to be returned, skip selector assignment.
3511 2974 if ( ! $include_node_paths_only ) {
3512 2975 $selectors = empty( $selectors ) ? static::get_blocks_metadata() : $selectors;
@@ -3550,9 +3013,8 @@
3550 3013
3551 3014 if ( $include_variations && isset( $node['variations'] ) ) {
3552 3015 foreach ( $node['variations'] as $variation => $node ) {
3553 3016 $variation_selectors[] = array(
3554 - 'name' => $variation,
3555 3017 'path' => array( 'styles', 'blocks', $name, 'variations', $variation ),
3556 3018 'selector' => $selectors[ $name ]['styleVariations'][ $variation ],
3557 3019 );
3558 3020 }
@@ -3562,209 +3024,61 @@
3562 3024 'name' => $name,
3563 3025 'path' => $node_path,
3564 3026 'selector' => $selector,
3565 3027 'selectors' => $feature_selectors,
3566 - 'elements' => $selectors[ $name ]['elements'] ?? array(),
3567 3028 'duotone' => $duotone_selector,
3568 3029 'variations' => $variation_selectors,
3569 3030 'css' => $selector,
3570 3031 );
3571 3032
3572 - // Responsive block nodes: emit one node per breakpoint that has styles.
3573 - // These are rendered immediately after the base block node so that
3574 - // the cascade order is: .block{} → @media{.block{}}
3575 - foreach ( array_keys( $responsive_media_queries ) as $breakpoint ) {
3576 - if ( isset( $theme_json['styles']['blocks'][ $name ][ $breakpoint ] ) ) {
3577 - $nodes[] = array(
3578 - 'name' => $name,
3579 - 'path' => array( 'styles', 'blocks', $name, $breakpoint ),
3580 - 'media_query' => $responsive_media_queries[ $breakpoint ],
3581 - 'selector' => $selector,
3582 - 'selectors' => $feature_selectors,
3583 - 'elements' => $selectors[ $name ]['elements'] ?? array(),
3584 - 'variations' => $variation_selectors,
3585 - 'css' => $selector,
3586 - );
3587 - }
3588 - }
3589 -
3590 3033 // Handle any pseudo selectors for the block.
3591 3034 if ( isset( static::VALID_BLOCK_PSEUDO_SELECTORS[ $name ] ) ) {
3592 3035 foreach ( static::VALID_BLOCK_PSEUDO_SELECTORS[ $name ] as $pseudo_selector ) {
3593 - $has_pseudo = isset( $theme_json['styles']['blocks'][ $name ][ $pseudo_selector ] );
3594 - $has_responsive_pseudo = false;
3595 - foreach ( array_keys( $responsive_media_queries ) as $breakpoint ) {
3596 - if ( isset( $theme_json['styles']['blocks'][ $name ][ $breakpoint ][ $pseudo_selector ] ) ) {
3597 - $has_responsive_pseudo = true;
3598 - break;
3599 - }
3600 - }
3601 -
3602 - if ( ! $has_pseudo && ! $has_responsive_pseudo ) {
3603 - continue;
3604 - }
3605 -
3606 - /*
3607 - * Append the pseudo-selector to each feature selector so that
3608 - * get_feature_declarations_for_node generates CSS scoped to the
3609 - * pseudo-state (e.g. '.wp-block-button:hover') rather than the
3610 - * default state (e.g. '.wp-block-button').
3611 - */
3612 - $pseudo_feature_selectors = array();
3613 - foreach ( $feature_selectors ?? array() as $feature => $feature_selector ) {
3614 - if ( is_array( $feature_selector ) ) {
3615 - $pseudo_feature_selectors[ $feature ] = array();
3616 - foreach ( $feature_selector as $subfeature => $subfeature_selector ) {
3617 - $pseudo_feature_selectors[ $feature ][ $subfeature ] = static::append_to_selector( $subfeature_selector, $pseudo_selector );
3618 - }
3619 - } else {
3620 - $pseudo_feature_selectors[ $feature ] = static::append_to_selector( $feature_selector, $pseudo_selector );
3621 - }
3622 - }
3623 -
3624 - if ( $has_pseudo ) {
3036 + if ( isset( $theme_json['styles']['blocks'][ $name ][ $pseudo_selector ] ) ) {
3625 3037 $nodes[] = array(
3626 3038 'name' => $name,
3627 3039 'path' => array( 'styles', 'blocks', $name, $pseudo_selector ),
3628 3040 'selector' => static::append_to_selector( $selector, $pseudo_selector ),
3629 - 'selectors' => $pseudo_feature_selectors,
3630 - 'elements' => $selectors[ $name ]['elements'] ?? array(),
3041 + 'selectors' => $feature_selectors,
3631 3042 'duotone' => $duotone_selector,
3632 3043 'variations' => $variation_selectors,
3633 3044 'css' => static::append_to_selector( $selector, $pseudo_selector ),
3634 3045 );
3635 3046 }
3636 -
3637 - // Responsive pseudo nodes: emit one node per breakpoint that has
3638 - // this pseudo state, immediately after the default pseudo node.
3639 - // Cascade order: .block:hover{} → @media{.block:hover{}}
3640 - foreach ( array_keys( $responsive_media_queries ) as $breakpoint ) {
3641 - if ( isset( $theme_json['styles']['blocks'][ $name ][ $breakpoint ][ $pseudo_selector ] ) ) {
3642 - $nodes[] = array(
3643 - 'name' => $name,
3644 - 'path' => array( 'styles', 'blocks', $name, $breakpoint, $pseudo_selector ),
3645 - 'media_query' => $responsive_media_queries[ $breakpoint ],
3646 - 'selector' => static::append_to_selector( $selector, $pseudo_selector ),
3647 - 'selectors' => $pseudo_feature_selectors,
3648 - 'elements' => $selectors[ $name ]['elements'] ?? array(),
3649 - 'variations' => $variation_selectors,
3650 - 'css' => static::append_to_selector( $selector, $pseudo_selector ),
3651 - );
3652 - }
3653 - }
3654 3047 }
3655 3048 }
3656 -
3657 - // Handle custom states (e.g. '-current' for navigation).
3658 - if ( isset( static::VALID_BLOCK_CUSTOM_STATES[ $name ] ) ) {
3659 - foreach ( static::VALID_BLOCK_CUSTOM_STATES[ $name ] as $custom_state ) {
3660 - if (
3661 - isset( $theme_json['styles']['blocks'][ $name ][ $custom_state ] ) &&
3662 - isset( $selectors[ $name ]['states'][ $custom_state ] )
3663 - ) {
3664 - $custom_css_selector = $selectors[ $name ]['states'][ $custom_state ];
3665 - $nodes[] = array(
3666 - 'name' => $name,
3667 - 'path' => array( 'styles', 'blocks', $name, $custom_state ),
3668 - 'selector' => $custom_css_selector,
3669 - 'selectors' => $feature_selectors,
3670 - 'elements' => $selectors[ $name ]['elements'] ?? array(),
3671 - 'duotone' => $duotone_selector,
3672 - 'variations' => $variation_selectors,
3673 - 'css' => $custom_css_selector,
3674 - );
3675 -
3676 - // Sub-pseudo-selectors within the custom state.
3677 - if ( isset( static::VALID_BLOCK_PSEUDO_SELECTORS[ $name ] ) ) {
3678 - foreach ( static::VALID_BLOCK_PSEUDO_SELECTORS[ $name ] as $pseudo ) {
3679 - if ( isset( $theme_json['styles']['blocks'][ $name ][ $custom_state ][ $pseudo ] ) ) {
3680 - $compound_css_selector = static::append_to_selector( $custom_css_selector, $pseudo );
3681 - $nodes[] = array(
3682 - 'name' => $name,
3683 - 'path' => array( 'styles', 'blocks', $name, $custom_state, $pseudo ),
3684 - 'selector' => $compound_css_selector,
3685 - 'selectors' => $feature_selectors,
3686 - 'elements' => $selectors[ $name ]['elements'] ?? array(),
3687 - 'duotone' => $duotone_selector,
3688 - 'variations' => $variation_selectors,
3689 - 'css' => $compound_css_selector,
3690 - );
3691 - }
3692 - }
3693 - }
3694 - }
3695 - }
3696 - }
3697 3049 }
3698 3050 if ( isset( $theme_json['styles']['blocks'][ $name ]['elements'] ) ) {
3699 3051 foreach ( $theme_json['styles']['blocks'][ $name ]['elements'] as $element => $node ) {
3700 - $element_path = array( 'styles', 'blocks', $name, 'elements', $element );
3052 + $node_path = array( 'styles', 'blocks', $name, 'elements', $element );
3701 3053 if ( $include_node_paths_only ) {
3702 3054 $nodes[] = array(
3703 - 'path' => $element_path,
3055 + 'path' => $node_path,
3704 3056 );
3705 3057 continue;
3706 3058 }
3707 3059
3708 - $element_selector = $selectors[ $name ]['elements'][ $element ];
3709 -
3710 3060 $nodes[] = array(
3711 - 'path' => $element_path,
3712 - 'selector' => $element_selector,
3061 + 'path' => $node_path,
3062 + 'selector' => $selectors[ $name ]['elements'][ $element ],
3713 3063 );
3714 3064
3715 - // Responsive element nodes: one node per breakpoint that has
3716 - // styles for this element. Cascade: a{} → @media{a{}}
3717 - foreach ( array_keys( $responsive_media_queries ) as $breakpoint ) {
3718 - if ( isset( $theme_json['styles']['blocks'][ $name ][ $breakpoint ]['elements'][ $element ] ) ) {
3719 - $nodes[] = array(
3720 - 'path' => array( 'styles', 'blocks', $name, $breakpoint, 'elements', $element ),
3721 - 'selector' => $element_selector,
3722 - 'media_query' => $responsive_media_queries[ $breakpoint ],
3723 - );
3724 - }
3725 - }
3726 -
3727 3065 // Handle any pseudo selectors for the element.
3728 3066 if ( isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element ] ) ) {
3729 3067 foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element ] as $pseudo_selector ) {
3730 - // Create element pseudo node if default or any responsive breakpoint has the pseudo.
3731 - $has_element_pseudo = isset( $theme_json['styles']['blocks'][ $name ]['elements'][ $element ][ $pseudo_selector ] );
3732 - if ( ! $has_element_pseudo ) {
3733 - foreach ( array_keys( $responsive_media_queries ) as $bp ) {
3734 - if ( isset( $theme_json['styles']['blocks'][ $name ][ $bp ]['elements'][ $element ][ $pseudo_selector ] ) ) {
3735 - $has_element_pseudo = true;
3736 - break;
3737 - }
3738 - }
3739 - }
3740 -
3741 - if ( $has_element_pseudo ) {
3742 - $element_pseudo_path = array( 'styles', 'blocks', $name, 'elements', $element );
3068 + if ( isset( $theme_json['styles']['blocks'][ $name ]['elements'][ $element ][ $pseudo_selector ] ) ) {
3069 + $node_path = array( 'styles', 'blocks', $name, 'elements', $element );
3743 3070 if ( $include_node_paths_only ) {
3744 3071 $nodes[] = array(
3745 - 'path' => $element_pseudo_path,
3072 + 'path' => $node_path,
3746 3073 );
3747 3074 continue;
3748 3075 }
3749 3076
3750 3077 $nodes[] = array(
3751 - 'path' => $element_pseudo_path,
3752 - 'selector' => static::append_to_selector( $element_selector, $pseudo_selector ),
3078 + 'path' => $node_path,
3079 + 'selector' => static::append_to_selector( $selectors[ $name ]['elements'][ $element ], $pseudo_selector ),
3753 3080 );
3754 -
3755 - // Responsive element pseudo nodes: one node per breakpoint
3756 - // that has this pseudo state for this element.
3757 - // Cascade: a:hover{} → @media{a:hover{}}
3758 - foreach ( array_keys( $responsive_media_queries ) as $breakpoint ) {
3759 - if ( isset( $theme_json['styles']['blocks'][ $name ][ $breakpoint ]['elements'][ $element ][ $pseudo_selector ] ) ) {
3760 - $nodes[] = array(
3761 - 'path' => array( 'styles', 'blocks', $name, $breakpoint, 'elements', $element ),
3762 - 'selector' => static::append_to_selector( $element_selector, $pseudo_selector ),
3763 - 'media_query' => $responsive_media_queries[ $breakpoint ],
3764 - );
3765 - }
3766 - }
3767 3081 }
3768 3082 }
3769 3083 }
3770 3084 }
@@ -3784,15 +3098,13 @@
3784 3098 *
3785 3099 * @return string Styles for the block.
3786 3100 */
3787 3101 public function get_styles_for_block( $block_metadata ) {
3788 - $node = _wp_array_get( $this->theme_json, $block_metadata['path'], array() );
3789 - $use_root_padding = isset( $this->theme_json['settings']['useRootPaddingAwareAlignments'] ) && true === $this->theme_json['settings']['useRootPaddingAwareAlignments'];
3790 - $selector = $block_metadata['selector'];
3791 - $settings = $this->theme_json['settings'] ?? null;
3792 - $is_root_selector = static::ROOT_BLOCK_SELECTOR === $selector;
3793 - $media_query = $block_metadata['media_query'] ?? null;
3794 - $responsive_media_queries = static::get_viewport_media_queries( $settings['viewport'] ?? null );
3102 + $node = _wp_array_get( $this->theme_json, $block_metadata['path'], array() );
3103 + $use_root_padding = isset( $this->theme_json['settings']['useRootPaddingAwareAlignments'] ) && true === $this->theme_json['settings']['useRootPaddingAwareAlignments'];
3104 + $selector = $block_metadata['selector'];
3105 + $settings = $this->theme_json['settings'] ?? null;
3106 + $is_root_selector = static::ROOT_BLOCK_SELECTOR === $selector;
3795 3107
3796 3108 $feature_declarations = static::get_feature_declarations_for_node( $block_metadata, $node );
3797 3109
3798 3110 // Update text indent selector for paragraph blocks based on the textIndent setting.
@@ -3797,24 +3109,20 @@
3797 3109
3798 3110 // Update text indent selector for paragraph blocks based on the textIndent setting.
3799 3111 $block_name = $block_metadata['name'] ?? null;
3800 3112 $feature_declarations = static::update_paragraph_text_indent_selector( $feature_declarations, $settings, $block_name );
3801 - $block_elements = $block_metadata['elements'] ?? array();
3802 3113
3803 3114 // Update button width declarations for percentage values to use calc() with block gap.
3804 3115 $feature_declarations = static::update_button_width_declarations( $feature_declarations, $settings );
3805 3116
3806 3117 // If there are style variations, generate the declarations for them, including any feature selectors the block may have.
3807 - // Responsive nodes (those with a media_query) do not process variations — variation responsive
3808 - // CSS is handled by the variation's own responsive nodes or the existing variation loop.
3809 - $style_variation_declarations = array();
3810 - $style_variation_custom_css = array();
3811 - $style_variation_responsive_css = array();
3812 - $style_variation_responsive_pseudo_css = array();
3813 - $style_variation_layout_metadata = array();
3814 - if ( ! $media_query && ! empty( $block_metadata['variations'] ) ) {
3118 + $style_variation_declarations = array();
3119 + $style_variation_custom_css = array();
3120 + $style_variation_layout_metadata = array();
3121 + if ( ! empty( $block_metadata['variations'] ) ) {
3815 3122 foreach ( $block_metadata['variations'] as $style_variation ) {
3816 - $style_variation_node = _wp_array_get( $this->theme_json, $style_variation['path'], array() );
3123 + $style_variation_node = _wp_array_get( $this->theme_json, $style_variation['path'], array() );
3124 + $clean_style_variation_selector = trim( $style_variation['selector'] );
3817 3125
3818 3126 // Generate any feature/subfeature style declarations for the current style variation.
3819 3127 $variation_declarations = static::get_feature_declarations_for_node( $block_metadata, $style_variation_node );
3820 3128
@@ -3825,10 +3133,27 @@
3825 3133 $variation_declarations = static::update_button_width_declarations( $variation_declarations, $settings );
3826 3134
3827 3135 // Combine selectors with style variation's selector and add to overall style variation declarations.
3828 3136 foreach ( $variation_declarations as $current_selector => $new_declarations ) {
3829 - $combined_selectors = static::get_block_style_variation_feature_selector( $style_variation, $current_selector );
3137 + /*
3138 + * Clean up any whitespace between comma separated selectors.
3139 + * This prevents these spaces breaking compound selectors such as:
3140 + * - `.wp-block-list:not(.wp-block-list .wp-block-list)`
3141 + * - `.wp-block-image img, .wp-block-image.my-class img`
3142 + */
3143 + $clean_current_selector = preg_replace( '/,\s+/', ',', $current_selector );
3144 + $shortened_selector = str_replace( $block_metadata['selector'], '', $clean_current_selector );
3830 3145
3146 + // Prepend the variation selector to the current selector.
3147 + $split_selectors = explode( ',', $shortened_selector );
3148 + $updated_selectors = array_map(
3149 + static function ( $split_selector ) use ( $clean_style_variation_selector ) {
3150 + return $clean_style_variation_selector . $split_selector;
3151 + },
3152 + $split_selectors
3153 + );
3154 + $combined_selectors = implode( ',', $updated_selectors );
3155 +
3831 3156 // Add the new declarations to the overall results under the modified selector.
3832 3157 $style_variation_declarations[ $combined_selectors ] = $new_declarations;
3833 3158 }
3834 3159 // Compute declarations for remaining styles not covered by feature level selectors.
@@ -3835,9 +3160,9 @@
3835 3160 $style_variation_declarations[ $style_variation['selector'] ] = static::compute_style_properties( $style_variation_node, $settings, null, $this->theme_json );
3836 3161
3837 3162 // Process pseudo-selectors for this variation (e.g., :hover, :focus).
3838 3163 $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 );
3839 - $variation_pseudo_declarations = $this->process_pseudo_selectors( $style_variation_node, $style_variation['selector'], $settings, $block_name, $block_metadata, $style_variation );
3164 + $variation_pseudo_declarations = static::process_pseudo_selectors( $style_variation_node, $style_variation['selector'], $settings, $block_name );
3840 3165 $style_variation_declarations = array_merge( $style_variation_declarations, $variation_pseudo_declarations );
3841 3166
3842 3167 // Store custom CSS for the style variation.
3843 3168 if ( isset( $style_variation_node['css'] ) ) {
@@ -3854,114 +3179,11 @@
3854 3179 'metadata' => $variation_metadata_with_selector,
3855 3180 'node' => $style_variation_node,
3856 3181 );
3857 3182 }
3858 -
3859 - // Store responsive breakpoint CSS for the style variation.
3860 - // This includes both base properties and feature-level selectors.
3861 - $variation_responsive_css = '';
3862 - $variation_responsive_pseudo_css = '';
3863 -
3864 - foreach ( array_keys( $responsive_media_queries ) as $breakpoint ) {
3865 - if ( ! isset( $style_variation_node[ $breakpoint ] ) ) {
3866 - continue;
3867 - }
3868 -
3869 - $breakpoint_node = $style_variation_node[ $breakpoint ];
3870 - $breakpoint_media = $responsive_media_queries[ $breakpoint ];
3871 - // Process feature-level declarations for this breakpoint.
3872 - $breakpoint_feature_declarations = static::get_feature_declarations_for_node( $block_metadata, $breakpoint_node );
3873 - $breakpoint_feature_declarations = static::update_paragraph_text_indent_selector( $breakpoint_feature_declarations, $settings, $block_name );
3874 - $breakpoint_feature_declarations = static::update_button_width_declarations( $breakpoint_feature_declarations, $settings );
3875 - foreach ( $breakpoint_feature_declarations as $feature_selector => $feature_decl ) {
3876 - $combined_selectors = static::get_block_style_variation_feature_selector( $style_variation, $feature_selector );
3877 -
3878 - $feature_ruleset = static::to_ruleset( ':root :where(' . $combined_selectors . ')', $feature_decl );
3879 - $variation_responsive_css .= $breakpoint_media . '{' . $feature_ruleset . '}';
3880 - }
3881 -
3882 - // Process base properties for this breakpoint.
3883 - $breakpoint_declarations = static::compute_style_properties( $breakpoint_node, $settings, null, $this->theme_json );
3884 - if ( ! empty( $breakpoint_declarations ) ) {
3885 - $base_ruleset = static::to_ruleset( ':root :where(' . $style_variation['selector'] . ')', $breakpoint_declarations );
3886 - $variation_responsive_css .= $breakpoint_media . '{' . $base_ruleset . '}';
3887 - }
3888 -
3889 - $breakpoint_pseudo_declarations = $this->process_pseudo_selectors( $breakpoint_node, $style_variation['selector'], $settings, $block_name, $block_metadata, $style_variation );
3890 - foreach ( $breakpoint_pseudo_declarations as $pseudo_selector => $pseudo_declarations ) {
3891 - if ( empty( $pseudo_declarations ) ) {
3892 - continue;
3893 - }
3894 - $pseudo_ruleset = static::to_ruleset( ':root :where(' . $pseudo_selector . ')', $pseudo_declarations );
3895 - $variation_responsive_pseudo_css .= $breakpoint_media . '{' . $pseudo_ruleset . '}';
3896 - }
3897 -
3898 - // Process custom CSS for this breakpoint.
3899 - if ( isset( $breakpoint_node['css'] ) ) {
3900 - $breakpoint_custom_css = static::process_blocks_custom_css( $breakpoint_node['css'], $style_variation['selector'] );
3901 - $variation_responsive_css .= $breakpoint_media . '{' . $breakpoint_custom_css . '}';
3902 - }
3903 -
3904 - // Process blockGap responsive layout styles for this variation.
3905 - if ( isset( $breakpoint_node['spacing']['blockGap'] ) ) {
3906 - $variation_layout_metadata = $style_variation;
3907 - $variation_layout_metadata['selector'] = $style_variation['selector'] . $block_metadata['css'];
3908 - $variation_responsive_css .= $this->get_layout_styles(
3909 - $variation_layout_metadata,
3910 - array(
3911 - 'node' => $breakpoint_node,
3912 - 'media_query' => $breakpoint_media,
3913 - )
3914 - );
3915 - }
3916 -
3917 - // Process nested element styles for this breakpoint state.
3918 - if ( isset( $breakpoint_node['elements'] ) && ! empty( $block_elements ) ) {
3919 - foreach ( $breakpoint_node['elements'] as $element_name => $element_node ) {
3920 - if ( ! isset( $block_elements[ $element_name ] ) ) {
3921 - continue;
3922 - }
3923 -
3924 - $variation_element_selector = static::get_block_style_variation_feature_selector( $style_variation, $block_elements[ $element_name ] );
3925 -
3926 - $element_declarations = static::compute_style_properties( $element_node, $settings, null, $this->theme_json );
3927 - if ( ! empty( $element_declarations ) ) {
3928 - $element_ruleset = static::to_ruleset( ':root :where(' . $variation_element_selector . ')', $element_declarations );
3929 - $variation_responsive_css .= $breakpoint_media . '{' . $element_ruleset . '}';
3930 - }
3931 -
3932 - if ( isset( $element_node['css'] ) ) {
3933 - $element_custom_css = static::process_blocks_custom_css( $element_node['css'], $variation_element_selector );
3934 - $variation_responsive_css .= $breakpoint_media . '{' . $element_custom_css . '}';
3935 - }
3936 -
3937 - if ( isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element_name ] ) ) {
3938 - foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element_name ] as $pseudo_selector ) {
3939 - if ( ! isset( $element_node[ $pseudo_selector ] ) ) {
3940 - continue;
3941 - }
3942 -
3943 - $pseudo_declarations = static::compute_style_properties( $element_node[ $pseudo_selector ], $settings, null, $this->theme_json );
3944 - if ( empty( $pseudo_declarations ) ) {
3945 - continue;
3946 - }
3947 -
3948 - $pseudo_selector_ruleset = static::to_ruleset( ':root :where(' . static::append_to_selector( $variation_element_selector, $pseudo_selector ) . ')', $pseudo_declarations );
3949 - $variation_responsive_pseudo_css .= $breakpoint_media . '{' . $pseudo_selector_ruleset . '}';
3950 - }
3951 - }
3952 - }
3953 - }
3954 - }
3955 -
3956 - if ( ! empty( $variation_responsive_css ) ) {
3957 - $style_variation_responsive_css[ $style_variation['selector'] ] = $variation_responsive_css;
3958 - }
3959 - if ( ! empty( $variation_responsive_pseudo_css ) ) {
3960 - $style_variation_responsive_pseudo_css[ $style_variation['selector'] ] = $variation_responsive_pseudo_css;
3961 - }
3962 3183 }
3963 3184 }
3185 +
3964 3186 /*
3965 3187 * Get a reference to element name from path.
3966 3188 * $block_metadata['path'] = array( 'styles','elements','link' );
3967 3189 * Make sure that $block_metadata['path'] describes an element node, like [ 'styles', 'element', 'link' ].
@@ -3977,8 +3199,25 @@
3977 3199 $element_pseudo_allowed = static::VALID_ELEMENT_PSEUDO_SELECTORS[ $current_element ];
3978 3200 }
3979 3201
3980 3202 /*
3203 + * Check if we're processing a block pseudo-selector.
3204 + * $block_metadata['path'] = array( 'styles', 'blocks', 'core/button', ':hover' );
3205 + */
3206 + $is_processing_block_pseudo = false;
3207 + $block_pseudo_selector = null;
3208 + if ( in_array( 'blocks', $block_metadata['path'], true ) && count( $block_metadata['path'] ) >= 4 ) {
3209 + $block_name = static::get_block_name_from_metadata_path( $block_metadata ); // 'core/button'
3210 + $last_path_element = $block_metadata['path'][ count( $block_metadata['path'] ) - 1 ]; // ':hover'
3211 +
3212 + if ( isset( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block_name ] ) &&
3213 + in_array( $last_path_element, static::VALID_BLOCK_PSEUDO_SELECTORS[ $block_name ], true ) ) {
3214 + $is_processing_block_pseudo = true;
3215 + $block_pseudo_selector = $last_path_element;
3216 + }
3217 + }
3218 +
3219 + /*
3981 3220 * Check for allowed pseudo classes (e.g. ":hover") from the $selector ("a:hover").
3982 3221 * This also resets the array keys.
3983 3222 */
3984 3223 $pseudo_matches = array_values(
@@ -4005,14 +3244,17 @@
4005 3244 isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $current_element ] )
4006 3245 && in_array( $pseudo_selector, static::VALID_ELEMENT_PSEUDO_SELECTORS[ $current_element ], true )
4007 3246 ) {
4008 3247 $declarations = static::compute_style_properties( $node[ $pseudo_selector ], $settings, null, $this->theme_json, $selector, $use_root_padding );
3248 + } elseif ( $is_processing_block_pseudo ) {
3249 + // Process block pseudo-selector styles.
3250 + // For block pseudo-selectors, we need to get the block data first, then access the pseudo-selector.
3251 + $block_name = static::get_block_name_from_metadata_path( $block_metadata ); // 'core/button'
3252 + $block_data = _wp_array_get( $this->theme_json, array( 'styles', 'blocks', $block_name ), array() );
3253 + $pseudo_data = $block_data[ $block_pseudo_selector ] ?? array();
3254 +
3255 + $declarations = static::compute_style_properties( $pseudo_data, $settings, null, $this->theme_json, $selector, $use_root_padding );
4009 3256 } else {
4010 - /*
4011 - * For block pseudo-selector nodes (e.g. ':hover'), $node has already had any
4012 - * feature-selector properties (e.g. writingMode) removed by get_feature_declarations_for_node,
4013 - * so those properties are not output twice.
4014 - */
4015 3257 $declarations = static::compute_style_properties( $node, $settings, null, $this->theme_json, $selector, $use_root_padding );
4016 3258 }
4017 3259
4018 3260 $block_rules = '';
@@ -4123,39 +3365,20 @@
4123 3365 }
4124 3366 if ( isset( $style_variation_custom_css[ $style_variation_selector ] ) ) {
4125 3367 $block_rules .= $style_variation_custom_css[ $style_variation_selector ];
4126 3368 }
4127 - if ( isset( $style_variation_responsive_css[ $style_variation_selector ] ) ) {
4128 - $block_rules .= $style_variation_responsive_css[ $style_variation_selector ];
4129 - }
4130 3369 }
4131 - /*
4132 - * Responsive pseudo styles must be output after default pseudo styles
4133 - * so viewport state styles win in the cascade.
4134 - */
4135 - foreach ( $style_variation_responsive_pseudo_css as $responsive_pseudo_css ) {
4136 - $block_rules .= $responsive_pseudo_css;
4137 - }
4138 3370
4139 - // Compute selector for block custom CSS.
4140 - $css_feature_selector = $block_metadata['selectors']['css'] ?? null;
4141 - if ( is_array( $css_feature_selector ) ) {
4142 - $css_feature_selector = $css_feature_selector['root'] ?? null;
4143 - }
4144 - $css_selector = is_string( $css_feature_selector ) ? $css_feature_selector : $selector;
4145 -
4146 3371 // 7. Generate and append any custom CSS rules.
4147 3372 if ( isset( $node['css'] ) && ! $is_root_selector ) {
3373 + $css_feature_selector = $block_metadata['selectors']['css'] ?? null;
3374 + if ( is_array( $css_feature_selector ) ) {
3375 + $css_feature_selector = $css_feature_selector['root'] ?? null;
3376 + }
3377 + $css_selector = is_string( $css_feature_selector ) ? $css_feature_selector : $selector;
4148 3378 $block_rules .= $this->process_blocks_custom_css( $node['css'], $css_selector );
4149 3379 }
4150 3380
4151 - // 8. Wrap the entire block output in a media query if this is a responsive node.
4152 - // Responsive nodes are created by get_block_nodes() for each breakpoint and carry
4153 - // a 'media_query' key.
4154 - if ( $media_query && ! empty( $block_rules ) ) {
4155 - $block_rules = $media_query . '{' . $block_rules . '}';
4156 - }
4157 -
4158 3381 return $block_rules;
4159 3382 }
4160 3383
4161 3384 /**
@@ -4631,11 +3854,8 @@
4631 3854
4632 3855 $sanitized = array();
4633 3856
4634 3857 $theme_json = WP_Theme_JSON_Schema_Gutenberg::migrate( $theme_json, $origin );
4635 - if ( isset( $theme_json['styles'] ) ) {
4636 - $theme_json['styles'] = gutenberg_resolve_style_state_aliases( $theme_json['styles'] );
4637 - }
4638 3858
4639 3859 $blocks_metadata = static::get_blocks_metadata();
4640 3860 $valid_block_names = array_keys( $blocks_metadata );
4641 3861 $valid_element_names = array_keys( static::ELEMENTS );
@@ -4642,12 +3862,11 @@
4642 3862 $valid_variations = static::get_valid_block_style_variations( $blocks_metadata );
4643 3863
4644 3864 $theme_json = static::sanitize( $theme_json, $valid_block_names, $valid_element_names, $valid_variations );
4645 3865
4646 - $blocks_metadata = static::get_blocks_metadata();
4647 - $style_options = array( 'include_block_style_variations' => true ); // Allow variations data.
4648 - $style_nodes = static::get_style_nodes( $theme_json, $blocks_metadata, $style_options );
4649 - $responsive_media_queries = static::get_viewport_media_queries( $theme_json['settings']['viewport'] ?? null );
3866 + $blocks_metadata = static::get_blocks_metadata();
3867 + $style_options = array( 'include_block_style_variations' => true ); // Allow variations data.
3868 + $style_nodes = static::get_style_nodes( $theme_json, $blocks_metadata, $style_options );
4650 3869
4651 3870 foreach ( $style_nodes as $metadata ) {
4652 3871 $input = _wp_array_get( $theme_json, $metadata['path'], array() );
4653 3872 if ( empty( $input ) ) {
@@ -4653,12 +3872,8 @@
4653 3872 if ( empty( $input ) ) {
4654 3873 continue;
4655 3874 }
4656 3875
4657 - $block_name = in_array( 'blocks', $metadata['path'], true )
4658 - ? static::get_block_name_from_metadata_path( $metadata )
4659 - : null;
4660 -
4661 3876 // The global styles custom CSS is not sanitized, but can only be edited by users with 'edit_css' capability.
4662 3877 if ( isset( $input['css'] ) && current_user_can( 'edit_css' ) ) {
4663 3878 $output = $input;
4664 3879 } else {
@@ -4682,36 +3897,8 @@
4682 3897 }
4683 3898 }
4684 3899 }
4685 3900
4686 - // Re-add and process responsive breakpoint styles.
4687 - foreach ( array_keys( $responsive_media_queries ) as $breakpoint ) {
4688 - if ( isset( $input[ $breakpoint ] ) ) {
4689 - $output[ $breakpoint ] = static::remove_insecure_styles( $input[ $breakpoint ] );
4690 -
4691 - if ( isset( $input[ $breakpoint ]['elements'] ) ) {
4692 - $output[ $breakpoint ]['elements'] = static::remove_insecure_element_styles( $input[ $breakpoint ]['elements'], $responsive_media_queries );
4693 - }
4694 -
4695 - if ( isset( $input[ $breakpoint ]['blocks'] ) ) {
4696 - $output[ $breakpoint ]['blocks'] = static::remove_insecure_inner_block_styles( $input[ $breakpoint ]['blocks'], $responsive_media_queries );
4697 - }
4698 -
4699 - if ( $block_name && isset( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block_name ] ) ) {
4700 - foreach ( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block_name ] as $pseudo_selector ) {
4701 - if ( isset( $input[ $breakpoint ][ $pseudo_selector ] ) ) {
4702 - $output[ $breakpoint ][ $pseudo_selector ] = static::remove_insecure_styles( $input[ $breakpoint ][ $pseudo_selector ] );
4703 - }
4704 - }
4705 - }
4706 -
4707 - // Responsive custom CSS is allowed for users with 'edit_css' capability.
4708 - if ( isset( $input[ $breakpoint ]['css'] ) && current_user_can( 'edit_css' ) ) {
4709 - $output[ $breakpoint ]['css'] = $input[ $breakpoint ]['css'];
4710 - }
4711 - }
4712 - }
4713 -
4714 3901 if ( ! empty( $output ) ) {
4715 3902 _wp_array_set( $sanitized, $metadata['path'], $output );
4716 3903 }
4717 3904
@@ -4724,43 +3911,15 @@
4724 3911
4725 3912 $variation_output = static::remove_insecure_styles( $variation_input );
4726 3913
4727 3914 if ( isset( $variation_input['blocks'] ) ) {
4728 - $variation_output['blocks'] = static::remove_insecure_inner_block_styles( $variation_input['blocks'], $responsive_media_queries );
3915 + $variation_output['blocks'] = static::remove_insecure_inner_block_styles( $variation_input['blocks'] );
4729 3916 }
4730 3917
4731 3918 if ( isset( $variation_input['elements'] ) ) {
4732 - $variation_output['elements'] = static::remove_insecure_element_styles( $variation_input['elements'], $responsive_media_queries );
3919 + $variation_output['elements'] = static::remove_insecure_element_styles( $variation_input['elements'] );
4733 3920 }
4734 3921
4735 - // Re-add and process responsive breakpoint styles for variations.
4736 - foreach ( array_keys( $responsive_media_queries ) as $breakpoint ) {
4737 - if ( isset( $variation_input[ $breakpoint ] ) ) {
4738 - $variation_output[ $breakpoint ] = static::remove_insecure_styles( $variation_input[ $breakpoint ] );
4739 -
4740 - if ( isset( $variation_input[ $breakpoint ]['elements'] ) ) {
4741 - $variation_output[ $breakpoint ]['elements'] = static::remove_insecure_element_styles( $variation_input[ $breakpoint ]['elements'], $responsive_media_queries );
4742 - }
4743 -
4744 - if ( isset( $variation_input[ $breakpoint ]['blocks'] ) ) {
4745 - $variation_output[ $breakpoint ]['blocks'] = static::remove_insecure_inner_block_styles( $variation_input[ $breakpoint ]['blocks'], $responsive_media_queries );
4746 - }
4747 -
4748 - if ( $block_name && isset( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block_name ] ) ) {
4749 - foreach ( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block_name ] as $pseudo_selector ) {
4750 - if ( isset( $variation_input[ $breakpoint ][ $pseudo_selector ] ) ) {
4751 - $variation_output[ $breakpoint ][ $pseudo_selector ] = static::remove_insecure_styles( $variation_input[ $breakpoint ][ $pseudo_selector ] );
4752 - }
4753 - }
4754 - }
4755 -
4756 - // Responsive custom CSS is allowed for users with 'edit_css' capability.
4757 - if ( isset( $variation_input[ $breakpoint ]['css'] ) && current_user_can( 'edit_css' ) ) {
4758 - $variation_output[ $breakpoint ]['css'] = $variation_input[ $breakpoint ]['css'];
4759 - }
4760 - }
4761 - }
4762 -
4763 3922 if ( ! empty( $variation_output ) ) {
4764 3923 _wp_array_set( $sanitized, $variation['path'], $variation_output );
4765 3924 }
4766 3925 }
@@ -4773,9 +3932,9 @@
4773 3932 if ( empty( $input ) ) {
4774 3933 continue;
4775 3934 }
4776 3935
4777 - $output = static::remove_insecure_settings( $input, array( 'settings' ) === $metadata['path'] );
3936 + $output = static::remove_insecure_settings( $input );
4778 3937 if ( ! empty( $output ) ) {
4779 3938 _wp_array_set( $sanitized, $metadata['path'], $output );
4780 3939 }
4781 3940 }
@@ -4797,19 +3956,14 @@
4797 3956
4798 3957 /**
4799 3958 * Remove insecure element styles within a variation or block.
4800 3959 *
4801 - * When responsive media queries are provided, nested responsive state styles
4802 - * for those media-query keys are re-added after the base sanitization pass.
4803 - *
4804 3960 * @since 6.8.0
4805 3961 *
4806 - * @param array $elements The elements to process.
4807 - * @param array|null $responsive_media_queries Optional. Media queries whose keys define allowed
4808 - * viewport states. Default null.
3962 + * @param array $elements The elements to process.
4809 3963 * @return array The sanitized elements styles.
4810 3964 */
4811 - protected static function remove_insecure_element_styles( $elements, $responsive_media_queries = null ) {
3965 + protected static function remove_insecure_element_styles( $elements ) {
4812 3966 $sanitized = array();
4813 3967 $valid_element_names = array_keys( static::ELEMENTS );
4814 3968
4815 3969 foreach ( $valid_element_names as $element_name ) {
@@ -4824,25 +3978,8 @@
4824 3978 }
4825 3979 }
4826 3980 }
4827 3981
4828 - if ( null !== $responsive_media_queries ) {
4829 - // Re-add and process responsive breakpoint styles for elements.
4830 - foreach ( array_keys( $responsive_media_queries ) as $breakpoint ) {
4831 - if ( isset( $element_input[ $breakpoint ] ) ) {
4832 - $element_output[ $breakpoint ] = static::remove_insecure_styles( $element_input[ $breakpoint ] );
4833 -
4834 - if ( isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element_name ] ) ) {
4835 - foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element_name ] as $pseudo_selector ) {
4836 - if ( isset( $element_input[ $breakpoint ][ $pseudo_selector ] ) ) {
4837 - $element_output[ $breakpoint ][ $pseudo_selector ] = static::remove_insecure_styles( $element_input[ $breakpoint ][ $pseudo_selector ] );
4838 - }
4839 - }
4840 - }
4841 - }
4842 - }
4843 - }
4844 -
4845 3982 $sanitized[ $element_name ] = $element_output;
4846 3983 }
4847 3984 }
4848 3985 return $sanitized;
@@ -4850,44 +3987,22 @@
4850 3987
4851 3988 /**
4852 3989 * Remove insecure styles from inner blocks and their elements.
4853 3990 *
4854 - * When responsive media queries are provided, nested responsive state styles
4855 - * for those media-query keys are re-added after the base sanitization pass.
4856 - *
4857 3991 * @since 6.8.0
4858 3992 *
4859 - * @param array $blocks The block styles to process.
4860 - * @param array|null $responsive_media_queries Optional. Media queries whose keys define allowed
4861 - * viewport states. Default null.
3993 + * @param array $blocks The block styles to process.
4862 3994 * @return array Sanitized block type styles.
4863 3995 */
4864 - protected static function remove_insecure_inner_block_styles( $blocks, $responsive_media_queries = null ) {
3996 + protected static function remove_insecure_inner_block_styles( $blocks ) {
4865 3997 $sanitized = array();
4866 3998 foreach ( $blocks as $block_type => $block_input ) {
4867 3999 $block_output = static::remove_insecure_styles( $block_input );
4868 4000
4869 4001 if ( isset( $block_input['elements'] ) ) {
4870 - $block_output['elements'] = static::remove_insecure_element_styles( $block_input['elements'], $responsive_media_queries );
4002 + $block_output['elements'] = static::remove_insecure_element_styles( $block_input['elements'] );
4871 4003 }
4872 4004
4873 - if ( null !== $responsive_media_queries ) {
4874 - // Re-add and process responsive breakpoint styles for inner blocks.
4875 - foreach ( array_keys( $responsive_media_queries ) as $breakpoint ) {
4876 - if ( isset( $block_input[ $breakpoint ] ) ) {
4877 - $block_output[ $breakpoint ] = static::remove_insecure_styles( $block_input[ $breakpoint ] );
4878 -
4879 - if ( isset( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block_type ] ) ) {
4880 - foreach ( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block_type ] as $pseudo_selector ) {
4881 - if ( isset( $block_input[ $breakpoint ][ $pseudo_selector ] ) ) {
4882 - $block_output[ $breakpoint ][ $pseudo_selector ] = static::remove_insecure_styles( $block_input[ $breakpoint ][ $pseudo_selector ] );
4883 - }
4884 - }
4885 - }
4886 - }
4887 - }
4888 - }
4889 -
4890 4005 $sanitized[ $block_type ] = $block_output;
4891 4006 }
4892 4007 return $sanitized;
4893 4008 }
@@ -4926,14 +4041,12 @@
4926 4041 * without the insecure settings.
4927 4042 *
4928 4043 * @since 5.9.0
4929 4044 *
4930 - * @param array $input Node to process.
4931 - * @param bool $allow_viewport Whether to preserve and sanitize top-level
4932 - * viewport settings.
4045 + * @param array $input Node to process.
4933 4046 * @return array
4934 4047 */
4935 - protected static function remove_insecure_settings( $input, $allow_viewport = true ) {
4048 + protected static function remove_insecure_settings( $input ) {
4936 4049 $output = array();
4937 4050 foreach ( static::PRESETS_METADATA as $preset_metadata ) {
4938 4051 foreach ( static::VALID_ORIGINS as $origin ) {
4939 4052 $path_with_origin = $preset_metadata['path'];
@@ -4984,12 +4097,8 @@
4984 4097
4985 4098 // Preserve all valid settings that have type markers in VALID_SETTINGS.
4986 4099 self::preserve_valid_typed_settings( $input, $output, static::VALID_SETTINGS );
4987 4100
4988 - if ( $allow_viewport && array_key_exists( 'viewport', $input ) ) {
4989 - $output['viewport'] = static::sanitize_viewport_settings( $input['viewport'] );
4990 - }
4991 -
4992 4101 return $output;
4993 4102 }
4994 4103
4995 4104 /**
@@ -5674,9 +4783,9 @@
5674 4783 $prefix = 'var:';
5675 4784 $prefix_len = strlen( $prefix );
5676 4785 $token_in = '|';
5677 4786 $token_out = '--';
5678 - if ( str_starts_with( $value, $prefix ) ) {
4787 + if ( 0 === strpos( $value, $prefix ) ) {
5679 4788 $unwrapped_name = str_replace(
5680 4789 $token_in,
5681 4790 $token_out,
5682 4791 substr( $value, $prefix_len )
@@ -5698,9 +4807,9 @@
5698 4807 private static function resolve_custom_css_format( $tree ) {
5699 4808 $prefix = 'var:';
5700 4809
5701 4810 foreach ( $tree as $key => $data ) {
5702 - if ( is_string( $data ) && str_starts_with( $data, $prefix ) ) {
4811 + if ( is_string( $data ) && 0 === strpos( $data, $prefix ) ) {
5703 4812 $tree[ $key ] = self::convert_custom_properties( $data );
5704 4813 } elseif ( is_array( $data ) ) {
5705 4814 $tree[ $key ] = self::resolve_custom_css_format( $data );
5706 4815 }
@@ -5803,27 +4912,16 @@
5803 4912 return $variation_class;
5804 4913 }
5805 4914
5806 4915 $limit = 1;
5807 - $selector_parts = static::split_selector_list( $block_selector );
4916 + $selector_parts = explode( ',', $block_selector );
5808 4917 $result = array();
5809 4918
5810 - /*
5811 - * Append the variation class to each selector's ancestor: the first
5812 - * run of characters before any combinator (whitespace) or pseudo-class
5813 - * (`:`). Only the first match is replaced.
5814 - *
5815 - * Examples ("custom" variation):
5816 - * - `.wp-block` => `.wp-block.is-style-custom`
5817 - * - `.wp-block .inner` => `.wp-block.is-style-custom .inner`
5818 - * - `.wp-block:where(.a .b)` => `.wp-block.is-style-custom:where(.a .b)`
5819 - * - `:where(.outer .inner)` => `:where(.outer.is-style-custom .inner)`
5820 - */
5821 4919 foreach ( $selector_parts as $part ) {
5822 4920 $result[] = preg_replace_callback(
5823 - '/[^\s:]+/',
4921 + '/((?::\([^)]+\))?\s*)([^\s:]+)/',
5824 4922 function ( $matches ) use ( $variation_class ) {
5825 - return $matches[0] . $variation_class;
4923 + return $matches[1] . $matches[2] . $variation_class;
5826 4924 },
5827 4925 $part,
5828 4926 $limit
5829 4927 );
@@ -5828,52 +4926,9 @@
5828 4926 $limit
5829 4927 );
5830 4928 }
5831 4929
5832 - return implode( ', ', $result );
5833 - }
5834 -
5835 - /**
5836 - * Applies a block style variation class to a feature selector.
5837 - *
5838 - * Feature selectors can target a different element than the block's root
5839 - * selector. For example, the Button block's root selector targets the inner
5840 - * link, while its dimensions width selector targets the outer wrapper. Apply
5841 - * the variation class directly to the selector that will receive the
5842 - * declarations instead of deriving it by subtracting the root selector from
5843 - * the feature selector.
5844 - *
5845 - * @param array $style_variation Style variation metadata.
5846 - * @param string $feature_selector CSS selector for the feature.
5847 - * @return string Feature selector with block style variation selector added.
5848 - */
5849 - protected static function get_block_style_variation_feature_selector( $style_variation, $feature_selector ) {
5850 - $variation_path = $style_variation['path'] ?? array();
5851 - $variation_name = $style_variation['name'] ?? ( is_array( $variation_path ) ? end( $variation_path ) : null );
5852 -
5853 - if ( ! $variation_name ) {
5854 - return $style_variation['selector'] ?? $feature_selector;
5855 - }
5856 -
5857 - $variation_class = ".is-style-$variation_name";
5858 - $selector_parts = static::split_selector_list( $feature_selector );
5859 - $selector_parts = array_map(
5860 - static function ( $selector ) use ( $variation_class ) {
5861 - $prefix = $variation_class . ' ';
5862 -
5863 - if ( str_starts_with( $selector, $prefix ) ) {
5864 - return substr( $selector, strlen( $prefix ) );
5865 - }
5866 -
5867 - return $selector;
5868 - },
5869 - $selector_parts
5870 - );
5871 -
5872 - return static::get_block_style_variation_selector(
5873 - $variation_name,
5874 - implode( ', ', $selector_parts )
5875 - );
4930 + return implode( ',', $result );
5876 4931 }
5877 4932
5878 4933 /**
5879 4934 * Collects valid block style variations keyed by block type.