Changeset 54118 for trunk/src/wp-includes/class-wp-theme-json.php
- Timestamp:
- 09/10/2022 12:37:00 PM (2 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/wp-includes/class-wp-theme-json.php
r54105 r54118 345 345 346 346 /** 347 * Defines which pseudo selectors are enabled for which elements. 348 * 349 * Note: this will affect both top-level and block-level elements. 350 * 351 * @since 6.1.0 352 */ 353 const VALID_ELEMENT_PSEUDO_SELECTORS = array( 354 'link' => array( ':hover', ':focus', ':active', ':visited' ), 355 'button' => array( ':hover', ':focus', ':active', ':visited' ), 356 ); 357 358 /** 347 359 * The valid elements that can be found under styles. 348 360 * 349 361 * @since 5.8.0 350 * @since 6.1.0 Added `heading`, `button` , and `caption` to theelements.362 * @since 6.1.0 Added `heading`, `button`. and `caption` elements. 351 363 * @var string[] 352 364 */ 353 365 const ELEMENTS = array( 354 'link' => 'a ',366 'link' => 'a:where(:not(.wp-element-button))', // The `where` is needed to lower the specificity. 355 367 'heading' => 'h1, h2, h3, h4, h5, h6', 356 368 'h1' => 'h1', … … 366 378 ); 367 379 380 const __EXPERIMENTAL_ELEMENT_CLASS_NAMES = array( 381 'button' => 'wp-element-button', 382 'caption' => 'wp-element-caption', 383 ); 384 385 /** 386 * Returns a class name by an element name. 387 * 388 * @since 6.1.0 389 * 390 * @param string $element The name of the element. 391 * @return string The name of the class. 392 */ 393 public static function get_element_class_name( $element ) { 394 $class_name = ''; 395 396 if ( array_key_exists( $element, static::__EXPERIMENTAL_ELEMENT_CLASS_NAMES ) ) { 397 $class_name = static::__EXPERIMENTAL_ELEMENT_CLASS_NAMES[ $element ]; 398 } 399 400 return $class_name; 401 } 402 368 403 /** 369 404 * Options that settings.appearanceTools enables. … … 489 524 */ 490 525 protected static function sanitize( $input, $valid_block_names, $valid_element_names ) { 526 491 527 $output = array(); 492 528 … … 495 531 } 496 532 533 // Preserve only the top most level keys. 497 534 $output = array_intersect_key( $input, array_flip( static::VALID_TOP_LEVEL_KEYS ) ); 498 535 499 // Some styles are only meant to be available at the top-level (e.g.: blockGap), 500 // hence, the schema for blocks & elements should not have them. 536 /* 537 * Remove any rules that are annotated as "top" in VALID_STYLES constant. 538 * Some styles are only meant to be available at the top-level (e.g.: blockGap), 539 * hence, the schema for blocks & elements should not have them. 540 */ 501 541 $styles_non_top_level = static::VALID_STYLES; 502 542 foreach ( array_keys( $styles_non_top_level ) as $section ) { … … 511 551 $schema = array(); 512 552 $schema_styles_elements = array(); 553 554 /* 555 * Set allowed element pseudo selectors based on per element allow list. 556 * Target data structure in schema: 557 * e.g. 558 * - top level elements: `$schema['styles']['elements']['link'][':hover']`. 559 * - block level elements: `$schema['styles']['blocks']['core/button']['elements']['link'][':hover']`. 560 */ 513 561 foreach ( $valid_element_names as $element ) { 514 562 $schema_styles_elements[ $element ] = $styles_non_top_level; 515 } 563 564 if ( array_key_exists( $element, static::VALID_ELEMENT_PSEUDO_SELECTORS ) ) { 565 foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element ] as $pseudo_selector ) { 566 $schema_styles_elements[ $element ][ $pseudo_selector ] = $styles_non_top_level; 567 } 568 } 569 } 570 516 571 $schema_styles_blocks = array(); 517 572 $schema_settings_blocks = array(); … … 521 576 $schema_styles_blocks[ $block ]['elements'] = $schema_styles_elements; 522 577 } 578 523 579 $schema['styles'] = static::VALID_STYLES; 524 580 $schema['styles']['blocks'] = $schema_styles_blocks; … … 548 604 549 605 return $output; 606 } 607 608 /** 609 * Appends a sub-selector to an existing one. 610 * 611 * Given the compounded $selector "h1, h2, h3" 612 * and the $to_append selector ".some-class" the result will be 613 * "h1.some-class, h2.some-class, h3.some-class". 614 * 615 * @since 5.8.0 616 * @since 6.1.0 Added append position. 617 * 618 * @param string $selector Original selector. 619 * @param string $to_append Selector to append. 620 * @param string $position A position sub-selector should be appended. Default 'right'. 621 * @return string 622 */ 623 protected static function append_to_selector( $selector, $to_append, $position = 'right' ) { 624 $new_selectors = array(); 625 $selectors = explode( ',', $selector ); 626 foreach ( $selectors as $sel ) { 627 $new_selectors[] = 'right' === $position ? $sel . $to_append : $to_append . $sel; 628 } 629 return implode( ',', $new_selectors ); 550 630 } 551 631 … … 612 692 $element_selector = array(); 613 693 foreach ( $block_selectors as $selector ) { 614 $element_selector[] = $selector . ' ' . $el_selector; 694 if ( $selector === $el_selector ) { 695 $element_selector = array( $el_selector ); 696 break; 697 } 698 $element_selector[] = static::append_to_selector( $el_selector, $selector . ' ', 'left' ); 615 699 } 616 700 static::$blocks_metadata[ $block_name ]['elements'][ $el_name ] = implode( ',', $element_selector ); … … 811 895 continue; 812 896 } 813 814 $node = _wp_array_get( $this->theme_json, $metadata['path'], array() ); 815 $selector = $metadata['selector']; 816 $settings = _wp_array_get( $this->theme_json, array( 'settings' ) ); 817 $declarations = static::compute_style_properties( $node, $settings ); 818 819 // 1. Separate the ones who use the general selector 820 // and the ones who use the duotone selector. 821 $declarations_duotone = array(); 822 foreach ( $declarations as $index => $declaration ) { 823 if ( 'filter' === $declaration['name'] ) { 824 unset( $declarations[ $index ] ); 825 $declarations_duotone[] = $declaration; 826 } 827 } 828 829 /* 830 * Reset default browser margin on the root body element. 831 * This is set on the root selector **before** generating the ruleset 832 * from the `theme.json`. This is to ensure that if the `theme.json` declares 833 * `margin` in its `spacing` declaration for the `body` element then these 834 * user-generated values take precedence in the CSS cascade. 835 * @link https://github.com/WordPress/gutenberg/issues/36147. 836 */ 837 if ( static::ROOT_BLOCK_SELECTOR === $selector ) { 838 $block_rules .= 'body { margin: 0; }'; 839 } 840 841 // 2. Generate the rules that use the general selector. 842 $block_rules .= static::to_ruleset( $selector, $declarations ); 843 844 // 3. Generate the rules that use the duotone selector. 845 if ( isset( $metadata['duotone'] ) && ! empty( $declarations_duotone ) ) { 846 $selector_duotone = static::scope_selector( $metadata['selector'], $metadata['duotone'] ); 847 $block_rules .= static::to_ruleset( $selector_duotone, $declarations_duotone ); 848 } 849 850 if ( static::ROOT_BLOCK_SELECTOR === $selector ) { 851 $block_rules .= '.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }'; 852 $block_rules .= '.wp-site-blocks > .alignright { float: right; margin-left: 2em; }'; 853 $block_rules .= '.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }'; 854 855 $has_block_gap_support = _wp_array_get( $this->theme_json, array( 'settings', 'spacing', 'blockGap' ) ) !== null; 856 if ( $has_block_gap_support ) { 857 $block_rules .= '.wp-site-blocks > * { margin-block-start: 0; margin-block-end: 0; }'; 858 $block_rules .= '.wp-site-blocks > * + * { margin-block-start: var( --wp--style--block-gap ); }'; 859 } 860 } 897 $block_rules .= static::get_styles_for_block( $metadata ); 861 898 } 862 899 … … 971 1008 972 1009 return $selector . '{' . $declaration_block . '}'; 973 }974 975 /**976 * Function that appends a sub-selector to a existing one.977 *978 * Given the compounded $selector "h1, h2, h3"979 * and the $to_append selector ".some-class" the result will be980 * "h1.some-class, h2.some-class, h3.some-class".981 *982 * @since 5.8.0983 *984 * @param string $selector Original selector.985 * @param string $to_append Selector to append.986 * @return string987 */988 protected static function append_to_selector( $selector, $to_append ) {989 $new_selectors = array();990 $selectors = explode( ',', $selector );991 foreach ( $selectors as $sel ) {992 $new_selectors[] = $sel . $to_append;993 }994 995 return implode( ',', $new_selectors );996 1010 } 997 1011 … … 1313 1327 * @since 5.8.0 1314 1328 * @since 5.9.0 Added the `$settings` and `$properties` parameters. 1329 * @since 6.1.0 Added the `$theme_json` parameter. 1315 1330 * 1316 1331 * @param array $styles Styles to process. 1317 1332 * @param array $settings Theme settings. 1318 1333 * @param array $properties Properties metadata. 1334 * @param array $theme_json Theme JSON array. 1319 1335 * @return array Returns the modified $declarations. 1320 1336 */ 1321 protected static function compute_style_properties( $styles, $settings = array(), $properties = null ) {1337 protected static function compute_style_properties( $styles, $settings = array(), $properties = null, $theme_json = null ) { 1322 1338 if ( null === $properties ) { 1323 1339 $properties = static::PROPERTIES_METADATA; … … 1330 1346 1331 1347 foreach ( $properties as $css_property => $value_path ) { 1332 $value = static::get_property_value( $styles, $value_path );1348 $value = static::get_property_value( $styles, $value_path, $theme_json ); 1333 1349 1334 1350 // Look up protected properties, keyed by value path. … … 1366 1382 * "--wp--preset--color--secondary". 1367 1383 * 1384 * It also converts references to a path to the value 1385 * stored at that location, e.g. 1386 * { "ref": "style.color.background" } => "#fff". 1387 * 1368 1388 * @since 5.8.0 1369 1389 * @since 5.9.0 Added support for values of array type, which are returned as is. 1390 * @since 6.1.0 Added the `$theme_json` parameter. 1370 1391 * 1371 1392 * @param array $styles Styles subtree. 1372 1393 * @param array $path Which property to process. 1394 * @param array $theme_json Theme JSON array. 1373 1395 * @return string|array Style property value. 1374 1396 */ 1375 protected static function get_property_value( $styles, $path ) {1397 protected static function get_property_value( $styles, $path, $theme_json = null ) { 1376 1398 $value = _wp_array_get( $styles, $path, '' ); 1399 1400 /* 1401 * This converts references to a path to the value at that path 1402 * where the values is an array with a "ref" key, pointing to a path. 1403 * For example: { "ref": "style.color.background" } => "#fff". 1404 */ 1405 if ( is_array( $value ) && array_key_exists( 'ref', $value ) ) { 1406 $value_path = explode( '.', $value['ref'] ); 1407 $ref_value = _wp_array_get( $theme_json, $value_path ); 1408 // Only use the ref value if we find anything. 1409 if ( ! empty( $ref_value ) && is_string( $ref_value ) ) { 1410 $value = $ref_value; 1411 } 1412 1413 if ( is_array( $ref_value ) && array_key_exists( 'ref', $ref_value ) ) { 1414 $path_string = json_encode( $path ); 1415 $ref_value_string = json_encode( $ref_value ); 1416 _doing_it_wrong( 1417 'get_property_value', 1418 sprintf( 1419 /* translators: 1: theme.json, 2: Value name, 3: Value path, 4: Another value name. */ 1420 __( 'Your %1$s file uses a dynamic value (%2$s) for the path at %3$s. However, the value at %3$s is also a dynamic value (pointing to %4$s) and pointing to another dynamic value is not supported. Please update %3$s to point directly to %4$s.' ), 1421 'theme.json', 1422 $ref_value_string, 1423 $path_string, 1424 $ref_value['ref'] 1425 ), 1426 '6.1.0' 1427 ); 1428 } 1429 } 1377 1430 1378 1431 if ( '' === $value || is_array( $value ) ) { … … 1380 1433 } 1381 1434 1435 // Convert custom CSS properties. 1382 1436 $prefix = 'var:'; 1383 1437 $prefix_len = strlen( $prefix ); … … 1491 1545 'selector' => static::ELEMENTS[ $element ], 1492 1546 ); 1493 } 1547 1548 // Handle any pseudo selectors for the element. 1549 if ( array_key_exists( $element, static::VALID_ELEMENT_PSEUDO_SELECTORS ) ) { 1550 foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element ] as $pseudo_selector ) { 1551 1552 if ( isset( $theme_json['styles']['elements'][ $element ][ $pseudo_selector ] ) ) { 1553 $nodes[] = array( 1554 'path' => array( 'styles', 'elements', $element ), 1555 'selector' => static::append_to_selector( static::ELEMENTS[ $element ], $pseudo_selector ), 1556 ); 1557 } 1558 } 1559 } 1560 } 1561 } 1562 1563 // Blocks. 1564 if ( ! isset( $theme_json['styles']['blocks'] ) ) { 1565 return $nodes; 1566 } 1567 1568 $nodes = array_merge( $nodes, static::get_block_nodes( $theme_json ) ); 1569 1570 /** 1571 * Filters the list of style nodes with metadata. 1572 * 1573 * This allows for things like loading block CSS independently. 1574 * 1575 * @since 6.1.0 1576 * 1577 * @param array $nodes Style nodes with metadata. 1578 */ 1579 return apply_filters( 'get_style_nodes', $nodes ); 1580 } 1581 1582 /** 1583 * A public helper to get the block nodes from a theme.json file. 1584 * 1585 * @since 6.1.0 1586 * 1587 * @return array The block nodes in theme.json. 1588 */ 1589 public function get_styles_block_nodes() { 1590 return static::get_block_nodes( $this->theme_json ); 1591 } 1592 1593 /** 1594 * An internal method to get the block nodes from a theme.json file. 1595 * 1596 * @since 6.1.0 1597 * 1598 * @param array $theme_json The theme.json converted to an array. 1599 * @return array The block nodes in theme.json. 1600 */ 1601 private static function get_block_nodes( $theme_json ) { 1602 $selectors = static::get_blocks_metadata(); 1603 $nodes = array(); 1604 if ( ! isset( $theme_json['styles'] ) ) { 1605 return $nodes; 1494 1606 } 1495 1607 … … 1511 1623 1512 1624 $nodes[] = array( 1625 'name' => $name, 1513 1626 'path' => array( 'styles', 'blocks', $name ), 1514 1627 'selector' => $selector, … … 1522 1635 'selector' => $selectors[ $name ]['elements'][ $element ], 1523 1636 ); 1637 1638 // Handle any pseudo selectors for the element. 1639 if ( array_key_exists( $element, static::VALID_ELEMENT_PSEUDO_SELECTORS ) ) { 1640 foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element ] as $pseudo_selector ) { 1641 if ( isset( $theme_json['styles']['blocks'][ $name ]['elements'][ $element ][ $pseudo_selector ] ) ) { 1642 $nodes[] = array( 1643 'path' => array( 'styles', 'blocks', $name, 'elements', $element ), 1644 'selector' => static::append_to_selector( $selectors[ $name ]['elements'][ $element ], $pseudo_selector ), 1645 ); 1646 } 1647 } 1648 } 1524 1649 } 1525 1650 } … … 1527 1652 1528 1653 return $nodes; 1654 } 1655 1656 /** 1657 * Gets the CSS rules for a particular block from theme.json. 1658 * 1659 * @since 6.1.0 1660 * 1661 * @param array $block_metadata Meta data about the block to get styles for. 1662 * @return array Styles for the block. 1663 */ 1664 public function get_styles_for_block( $block_metadata ) { 1665 1666 $node = _wp_array_get( $this->theme_json, $block_metadata['path'], array() ); 1667 1668 $selector = $block_metadata['selector']; 1669 $settings = _wp_array_get( $this->theme_json, array( 'settings' ) ); 1670 1671 /* 1672 * Get a reference to element name from path. 1673 * $block_metadata['path'] = array( 'styles','elements','link' ); 1674 * Make sure that $block_metadata['path'] describes an element node, like [ 'styles', 'element', 'link' ]. 1675 * Skip non-element paths like just ['styles']. 1676 */ 1677 $is_processing_element = in_array( 'elements', $block_metadata['path'], true ); 1678 1679 $current_element = $is_processing_element ? $block_metadata['path'][ count( $block_metadata['path'] ) - 1 ] : null; 1680 1681 $element_pseudo_allowed = array(); 1682 1683 if ( array_key_exists( $current_element, static::VALID_ELEMENT_PSEUDO_SELECTORS ) ) { 1684 $element_pseudo_allowed = static::VALID_ELEMENT_PSEUDO_SELECTORS[ $current_element ]; 1685 } 1686 1687 /* 1688 * Check for allowed pseudo classes (e.g. ":hover") from the $selector ("a:hover"). 1689 * This also resets the array keys. 1690 */ 1691 $pseudo_matches = array_values( 1692 array_filter( 1693 $element_pseudo_allowed, 1694 function( $pseudo_selector ) use ( $selector ) { 1695 return str_contains( $selector, $pseudo_selector ); 1696 } 1697 ) 1698 ); 1699 1700 $pseudo_selector = isset( $pseudo_matches[0] ) ? $pseudo_matches[0] : null; 1701 1702 /* 1703 * If the current selector is a pseudo selector that's defined in the allow list for the current 1704 * element then compute the style properties for it. 1705 * Otherwise just compute the styles for the default selector as normal. 1706 */ 1707 if ( $pseudo_selector && isset( $node[ $pseudo_selector ] ) && 1708 array_key_exists( $current_element, static::VALID_ELEMENT_PSEUDO_SELECTORS ) 1709 && in_array( $pseudo_selector, static::VALID_ELEMENT_PSEUDO_SELECTORS[ $current_element ], true ) 1710 ) { 1711 $declarations = static::compute_style_properties( $node[ $pseudo_selector ], $settings, null, $this->theme_json ); 1712 } else { 1713 $declarations = static::compute_style_properties( $node, $settings, null, $this->theme_json ); 1714 } 1715 1716 $block_rules = ''; 1717 1718 /* 1719 * 1. Separate the declarations that use the general selector 1720 * from the ones using the duotone selector. 1721 */ 1722 $declarations_duotone = array(); 1723 foreach ( $declarations as $index => $declaration ) { 1724 if ( 'filter' === $declaration['name'] ) { 1725 unset( $declarations[ $index ] ); 1726 $declarations_duotone[] = $declaration; 1727 } 1728 } 1729 1730 /* 1731 * Reset default browser margin on the root body element. 1732 * This is set on the root selector **before** generating the ruleset 1733 * from the `theme.json`. This is to ensure that if the `theme.json` declares 1734 * `margin` in its `spacing` declaration for the `body` element then these 1735 * user-generated values take precedence in the CSS cascade. 1736 * @link https://github.com/WordPress/gutenberg/issues/36147. 1737 */ 1738 if ( static::ROOT_BLOCK_SELECTOR === $selector ) { 1739 $block_rules .= 'body { margin: 0; }'; 1740 } 1741 1742 // 2. Generate and append the rules that use the general selector. 1743 $block_rules .= static::to_ruleset( $selector, $declarations ); 1744 1745 // 3. Generate and append the rules that use the duotone selector. 1746 if ( isset( $block_metadata['duotone'] ) && ! empty( $declarations_duotone ) ) { 1747 $selector_duotone = static::scope_selector( $block_metadata['selector'], $block_metadata['duotone'] ); 1748 $block_rules .= static::to_ruleset( $selector_duotone, $declarations_duotone ); 1749 } 1750 1751 if ( static::ROOT_BLOCK_SELECTOR === $selector ) { 1752 $block_rules .= '.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }'; 1753 $block_rules .= '.wp-site-blocks > .alignright { float: right; margin-left: 2em; }'; 1754 $block_rules .= '.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }'; 1755 1756 $has_block_gap_support = _wp_array_get( $this->theme_json, array( 'settings', 'spacing', 'blockGap' ) ) !== null; 1757 if ( $has_block_gap_support ) { 1758 $block_rules .= '.wp-site-blocks > * { margin-block-start: 0; margin-block-end: 0; }'; 1759 $block_rules .= '.wp-site-blocks > * + * { margin-block-start: var( --wp--style--block-gap ); }'; 1760 } 1761 } 1762 1763 return $block_rules; 1529 1764 } 1530 1765 … … 1838 2073 $valid_block_names = array_keys( static::get_blocks_metadata() ); 1839 2074 $valid_element_names = array_keys( static::ELEMENTS ); 1840 $theme_json = static::sanitize( $theme_json, $valid_block_names, $valid_element_names ); 2075 2076 $theme_json = static::sanitize( $theme_json, $valid_block_names, $valid_element_names ); 1841 2077 1842 2078 $blocks_metadata = static::get_blocks_metadata(); 1843 2079 $style_nodes = static::get_style_nodes( $theme_json, $blocks_metadata ); 2080 1844 2081 foreach ( $style_nodes as $metadata ) { 1845 2082 $input = _wp_array_get( $theme_json, $metadata['path'], array() ); … … 1849 2086 1850 2087 $output = static::remove_insecure_styles( $input ); 2088 2089 /* 2090 * Get a reference to element name from path. 2091 * $metadata['path'] = array( 'styles', 'elements', 'link' ); 2092 */ 2093 $current_element = $metadata['path'][ count( $metadata['path'] ) - 1 ]; 2094 2095 /* 2096 * $output is stripped of pseudo selectors. Re-add and process them 2097 * or insecure styles here. 2098 */ 2099 if ( array_key_exists( $current_element, static::VALID_ELEMENT_PSEUDO_SELECTORS ) ) { 2100 foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $current_element ] as $pseudo_selector ) { 2101 if ( isset( $input[ $pseudo_selector ] ) ) { 2102 $output[ $pseudo_selector ] = static::remove_insecure_styles( $input[ $pseudo_selector ] ); 2103 } 2104 } 2105 } 2106 1851 2107 if ( ! empty( $output ) ) { 1852 2108 _wp_array_set( $sanitized, $metadata['path'], $output );
Note: See TracChangeset
for help on using the changeset viewer.