From cb1b5fd86de46894c5a15220c8a0dba88454931c Mon Sep 17 00:00:00 2001 From: Ben Dwyer Date: Fri, 24 Jun 2022 15:52:29 +0100 Subject: [PATCH] Global Styles: Allow references to values in other locations in the tree (#41696) * 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 * fix rebase --- .../wordpress-6.1/class-wp-theme-json-6-1.php | 121 +++++++++++++++++- ...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, 233 insertions(+), 7 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 4bfdd34e2b2fb..c89309366d2de 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 @@ -222,7 +222,6 @@ public static function remove_insecure_properties( $theme_json ) { return $theme_json; } - /** * Returns the metadata for each block. * @@ -451,7 +450,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 +472,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 +552,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 6c4e5b534a4f7..7f7d4a3ace2f1 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 78267771bcc46..ed5b867e77534 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 6f31e17fd4263..3b748a37a4eda 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 +