From 35e42ed189083238bca781fb0df65f4528a84d17 Mon Sep 17 00:00:00 2001 From: Ben Dwyer Date: Thu, 16 Jun 2022 12:44:07 +0100 Subject: [PATCH] Elements: Add an API make it easier to get class names (#41753) * Elements: Add an api make it easier to get class names * Add some PHP unit tests * Add a unit tests for elements * lint fix * fix PHP tests * Update lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php Co-authored-by: George Mamadashvili * Add @since * update form.js * fix PHP test Co-authored-by: George Mamadashvili Theme JSON: Add dynamic properties Theme JSON: Make it possible to use styles and settings from elsewhere in the tree also make button text dynamic fix type remove unconnected changes move the value replacement earlier and add validation use an object rather than a string to fetch other values adding a doing it wrong message remove color changes for buttons add an explanatory comment change source to ref Add unit tests Update lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php Co-authored-by: Adam Zielinski lint fixes fix fixtures remove incorrect change lint fixes --- .../wordpress-6.1/class-wp-theme-json-6-1.php | 126 +++++++++++++++++- ...class-wp-theme-json-resolver-gutenberg.php | 2 - phpunit/class-wp-theme-json-test.php | 115 ++++++++++++++++ .../blocks/core__table__deprecated-1.html | 2 +- 4 files changed, 239 insertions(+), 6 deletions(-) diff --git a/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php b/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php index 4bfdd34e2b2fb8..16532c191ed1c0 100644 --- a/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php +++ b/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php @@ -15,6 +15,7 @@ * @access private */ class WP_Theme_JSON_6_1 extends WP_Theme_JSON_6_0 { +<<<<<<< HEAD /** * Whitelist which defines which pseudo selectors are enabled for @@ -25,6 +26,8 @@ class WP_Theme_JSON_6_1 extends WP_Theme_JSON_6_0 { 'link' => array( ':hover', ':focus', ':active' ), ); +======= +>>>>>>> 4e177980fb (Elements: Add an API make it easier to get class names (#41753)) const ELEMENTS = array( 'link' => 'a', 'h1' => 'h1', @@ -53,6 +56,7 @@ public static function get_element_class_name( $element ) { return array_key_exists( $element, static::__EXPERIMENTAL_ELEMENT_CLASS_NAMES ) ? static::__EXPERIMENTAL_ELEMENT_CLASS_NAMES[ $element ] : ''; } +<<<<<<< HEAD /** * Sanitizes the input according to the schemas. * @@ -223,6 +227,8 @@ public static function remove_insecure_properties( $theme_json ) { } +======= +>>>>>>> 4e177980fb (Elements: Add an API make it easier to get class names (#41753)) /** * Returns the metadata for each block. * @@ -451,7 +457,6 @@ private static function get_block_nodes( $theme_json, $selectors = array() ) { * @return string Styles for the block. */ public function get_styles_for_block( $block_metadata ) { - $node = _wp_array_get( $this->theme_json, $block_metadata['path'], array() ); $selector = $block_metadata['selector']; @@ -474,9 +479,9 @@ public function get_styles_for_block( $block_metadata ) { // element then compute the style properties for it. // Otherwise just compute the styles for the default selector as normal. if ( $pseudo_selector && isset( $node[ $pseudo_selector ] ) && isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $current_element ] ) && in_array( $pseudo_selector, static::VALID_ELEMENT_PSEUDO_SELECTORS[ $current_element ], true ) ) { - $declarations = static::compute_style_properties( $node[ $pseudo_selector ], $settings ); + $declarations = static::compute_style_properties( $node[ $pseudo_selector ], $settings, null, $this->theme_json ); } else { - $declarations = static::compute_style_properties( $node, $settings ); + $declarations = static::compute_style_properties( $node, $settings, null, $this->theme_json ); } $block_rules = ''; @@ -554,4 +559,119 @@ protected function get_block_classes( $style_nodes ) { return $block_rules; } + + /** + * Given a styles array, it extracts the style properties + * and adds them to the $declarations array following the format: + * + * ```php + * array( + * 'name' => 'property_name', + * 'value' => 'property_value, + * ) + * ``` + * + * @param array $styles Styles to process. + * @param array $settings Theme settings. + * @param array $properties Properties metadata. + * @param array $theme_json Theme JSON array. + * @return array Returns the modified $declarations. + */ + protected static function compute_style_properties( $styles, $settings = array(), $properties = null, $theme_json = null ) { + if ( null === $properties ) { + $properties = static::PROPERTIES_METADATA; + } + + $declarations = array(); + if ( empty( $styles ) ) { + return $declarations; + } + + foreach ( $properties as $css_property => $value_path ) { + $value = static::get_property_value( $styles, $value_path, $theme_json ); + + // Look up protected properties, keyed by value path. + // Skip protected properties that are explicitly set to `null`. + if ( is_array( $value_path ) ) { + $path_string = implode( '.', $value_path ); + if ( + array_key_exists( $path_string, static::PROTECTED_PROPERTIES ) && + _wp_array_get( $settings, static::PROTECTED_PROPERTIES[ $path_string ], null ) === null + ) { + continue; + } + } + + // Skip if empty and not "0" or value represents array of longhand values. + $has_missing_value = empty( $value ) && ! is_numeric( $value ); + if ( $has_missing_value || is_array( $value ) ) { + continue; + } + + $declarations[] = array( + 'name' => $css_property, + 'value' => $value, + ); + } + + return $declarations; + } + + /** + * Returns the style property for the given path. + * + * It also converts CSS Custom Property stored as + * "var:preset|color|secondary" to the form + * "--wp--preset--color--secondary". + * + * It also converts references to a path to the value + * stored at that location, e.g. + * { "ref": "style.color.background" } => "#fff". + * + * @param array $styles Styles subtree. + * @param array $path Which property to process. + * @param array $theme_json Theme JSON array. + * @return string Style property value. + */ + protected static function get_property_value( $styles, $path, $theme_json = null ) { + $value = _wp_array_get( $styles, $path, '' ); + + // This converts references to a path to the value at that path + // where the values is an array with a "ref" key, pointing to a path. + // For example: { "ref": "style.color.background" } => "#fff". + if ( is_array( $value ) && array_key_exists( 'ref', $value ) ) { + $value_path = explode( '.', $value['ref'] ); + $ref_value = _wp_array_get( $theme_json, $value_path ); + // Only use the ref value if we find anything. + if ( ! empty( $ref_value ) && is_string( $ref_value ) ) { + $value = $ref_value; + } + + if ( is_array( $ref_value ) && array_key_exists( 'ref', $ref_value ) ) { + $path_string = json_encode( $path ); + $ref_value_string = json_encode( $ref_value ); + _doing_it_wrong( 'get_property_value', "Your theme.json file uses a dynamic value (${ref_value_string}) for the path at ${path_string}. However, the value at ${path_string} is also a dynamic value (pointing to ${ref_value['ref']}) and pointing to another dynamic value is not supported. Please update ${path_string} to point directly to ${ref_value['ref']}.", '6.1.0' ); + } + } + + if ( '' === $value || is_array( $value ) ) { + return $value; + } + + // Convert custom CSS properties. + $prefix = 'var:'; + $prefix_len = strlen( $prefix ); + $token_in = '|'; + $token_out = '--'; + if ( 0 === strncmp( $value, $prefix, $prefix_len ) ) { + $unwrapped_name = str_replace( + $token_in, + $token_out, + substr( $value, $prefix_len ) + ); + $value = "var(--wp--$unwrapped_name)"; + } + + return $value; + } } diff --git a/lib/experimental/class-wp-theme-json-resolver-gutenberg.php b/lib/experimental/class-wp-theme-json-resolver-gutenberg.php index 6c4e5b534a4f78..7f7d4a3ace2f19 100644 --- a/lib/experimental/class-wp-theme-json-resolver-gutenberg.php +++ b/lib/experimental/class-wp-theme-json-resolver-gutenberg.php @@ -176,6 +176,4 @@ public static function get_merged_data( $origin = 'custom' ) { return $result; } - - } diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php index 78267771bcc460..ed5b867e775345 100644 --- a/phpunit/class-wp-theme-json-test.php +++ b/phpunit/class-wp-theme-json-test.php @@ -2821,4 +2821,119 @@ function test_get_element_class_name_invalid() { $this->assertEquals( $expected, $actual ); } + + /** + * Testing that dynamic properties in theme.json return the value they refrence, e.g. + * array( 'ref' => 'styles.color.background' ) => "#ffffff". + */ + function test_get_property_value_valid() { + $theme_json = new WP_Theme_JSON_Gutenberg( + array( + 'version' => 2, + 'styles' => array( + 'color' => array( + 'background' => '#ffffff', + 'text' => '#000000', + ), + 'elements' => array( + 'button' => array( + 'color' => array( + 'background' => array( 'ref' => 'styles.color.text' ), + 'text' => array( 'ref' => 'styles.color.background' ), + ), + ), + ), + ), + ) + ); + + $expected = 'body { margin: 0; }body{background-color: #ffffff;color: #000000;}.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }.wp-element-button, .wp-block-button__link{background-color: #000000;color: #ffffff;}'; + $this->assertEquals( $expected, $theme_json->get_stylesheet() ); + } + + /** + * Testing that dynamic properties in theme.json that + * refer to other dynamic properties in a loop + * then they should be left untouched. + * + * @expectedIncorrectUsage get_property_value + */ + function test_get_property_value_loop() { + $theme_json = new WP_Theme_JSON_Gutenberg( + array( + 'version' => 2, + 'styles' => array( + 'color' => array( + 'background' => '#ffffff', + 'text' => array( 'ref' => 'styles.elements.button.color.background' ), + ), + 'elements' => array( + 'button' => array( + 'color' => array( + 'background' => array( 'ref' => 'styles.color.text' ), + 'text' => array( 'ref' => 'styles.color.background' ), + ), + ), + ), + ), + ) + ); + + $expected = 'body { margin: 0; }body{background-color: #ffffff;}.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }.wp-element-button, .wp-block-button__link{color: #ffffff;}'; + $this->assertSame( $expected, $theme_json->get_stylesheet() ); + } + + /** + * Testing that dynamic properties in theme.json that + * refer to other dynamic properties then they should be left unprocessed. + * + * @expectedIncorrectUsage get_property_value + */ + function test_get_property_value_recursion() { + $theme_json = new WP_Theme_JSON_Gutenberg( + array( + 'version' => 2, + 'styles' => array( + 'color' => array( + 'background' => '#ffffff', + 'text' => array( 'ref' => 'styles.color.background' ), + ), + 'elements' => array( + 'button' => array( + 'color' => array( + 'background' => array( 'ref' => 'styles.color.text' ), + 'text' => array( 'ref' => 'styles.color.background' ), + ), + ), + ), + ), + ) + ); + + $expected = 'body { margin: 0; }body{background-color: #ffffff;color: #ffffff;}.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }.wp-element-button, .wp-block-button__link{color: #ffffff;}'; + $this->assertEquals( $expected, $theme_json->get_stylesheet() ); + } + + /** + * Testing that dynamic properties in theme.json that + * refer to themselves then they should be left unprocessed. + * + * @expectedIncorrectUsage get_property_value + */ + function test_get_property_value_self() { + $theme_json = new WP_Theme_JSON_Gutenberg( + array( + 'version' => 2, + 'styles' => array( + 'color' => array( + 'background' => '#ffffff', + 'text' => array( 'ref' => 'styles.color.text' ), + ), + ), + ) + ); + + $expected = 'body { margin: 0; }body{background-color: #ffffff;}.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }'; + $this->assertEquals( $expected, $theme_json->get_stylesheet() ); + } } diff --git a/test/integration/fixtures/blocks/core__table__deprecated-1.html b/test/integration/fixtures/blocks/core__table__deprecated-1.html index 6f31e17fd4263e..3b748a37a4eda1 100644 --- a/test/integration/fixtures/blocks/core__table__deprecated-1.html +++ b/test/integration/fixtures/blocks/core__table__deprecated-1.html @@ -1,3 +1,3 @@
VersionMusicianDate
.70No musician chosen.May 27, 2003
1.0Miles DavisJanuary 3, 2004
Lots of versions skipped, see the full list
4.4Clifford BrownDecember 8, 2015
4.5Coleman HawkinsApril 12, 2016
4.6Pepper AdamsAugust 16, 2016
4.7Sarah VaughanDecember 6, 2016
Table Caption
- \ No newline at end of file +