From 4965b94ae140922561abdfbf5cd5663ee8b3782e Mon Sep 17 00:00:00 2001 From: ramonjd Date: Tue, 5 Jul 2022 14:57:31 +1000 Subject: [PATCH 1/6] Adding a new method to generate global styles: wp_style_engine_generate_global_styles --- .../style-engine/class-wp-style-engine.php | 92 +++++++++++++-- .../phpunit/class-wp-style-engine-test.php | 110 ++++++++++++++++++ 2 files changed, 194 insertions(+), 8 deletions(-) diff --git a/packages/style-engine/class-wp-style-engine.php b/packages/style-engine/class-wp-style-engine.php index a140d9ba8c6b8..854e456087638 100644 --- a/packages/style-engine/class-wp-style-engine.php +++ b/packages/style-engine/class-wp-style-engine.php @@ -142,17 +142,17 @@ class WP_Style_Engine { ), ), 'spacing' => array( - 'padding' => array( + 'blockGap' => array( 'property_keys' => array( - 'default' => 'padding', - 'individual' => 'padding-%s', - ), - 'path' => array( 'spacing', 'padding' ), - 'css_vars' => array( - 'spacing' => '--wp--preset--spacing--$slug', + // @TODO 'grid-gap' has been deprecated in favor of 'gap'. + // See: https://developer.mozilla.org/en-US/docs/Web/CSS/gap. + // Update the white list in safecss_filter_attr (kses.php). + // See: https://core.trac.wordpress.org/ticket/56122 + 'default' => 'grid-gap', ), + 'path' => array( 'spacing', 'blockGap' ), ), - 'margin' => array( + 'margin' => array( 'property_keys' => array( 'default' => 'margin', 'individual' => 'margin-%s', @@ -162,6 +162,16 @@ class WP_Style_Engine { 'spacing' => '--wp--preset--spacing--$slug', ), ), + 'padding' => array( + 'property_keys' => array( + 'default' => 'padding', + 'individual' => 'padding-%s', + ), + 'path' => array( 'spacing', 'padding' ), + 'css_vars' => array( + 'spacing' => '--wp--preset--spacing--$slug', + ), + ), ), 'typography' => array( 'fontSize' => array( @@ -221,6 +231,16 @@ class WP_Style_Engine { ), ); + /** + * The valid elements that can be found under styles. + * + * @var string[] + */ + const ELEMENTS = array( + 'link' => 'a', + 'h1' => 'h1', + ); + /** * Utility method to retrieve the main instance of the class. * @@ -448,6 +468,53 @@ public function get_block_supports_styles( $block_styles, $options ) { return $styles_output; } + public function generate_global_styles( $global_styles, $options ) { + if ( empty( $global_styles ) || ! is_array( $global_styles ) ) { + return null; + } + + // The return stylesheet. + $global_stylesheet = ''; + + // Layer 0: Root. + $root_level_styles = $this->generate( $global_styles, array( 'selector' => 'body' ) ); + + if ( ! empty( $root_level_styles['css'] ) ) { + $global_stylesheet .= $root_level_styles['css'] . ' '; + } + + // Layer 1: Elements. + if ( isset( $global_styles['elements'] ) && is_array( $global_styles['elements'] ) ) { + foreach ( $global_styles['elements'] as $element_name => $element_styles ) { + $selector = isset( static::ELEMENTS[ $element_name ] ) ? static::ELEMENTS[ $element_name ] : null; + if ( ! $selector ) { + continue; + } + $element_level_styles = $this->generate( $element_styles, array( 'selector' => $selector ) ); + if ( ! empty( $element_level_styles['css'] ) ) { + $global_stylesheet .= $element_level_styles['css'] . ' '; + } + } + } + + // Layer 2: Blocks. + if ( isset( $global_styles['blocks'] ) && is_array( $global_styles['blocks'] ) ) { + foreach ( $global_styles['blocks'] as $block_name => $block_styles ) { + $selector = '.wp-block-' . str_replace( '/', '-', str_replace( 'core/', '', $block_name ) ); + $block_level_styles = $this->generate( $block_styles, array( 'selector' => $selector ) ); + if ( ! empty( $block_level_styles['css'] ) ) { + $global_stylesheet .= $block_level_styles['css'] . ' '; + } + } + } + + if ( ! empty( $global_stylesheet ) ) { + return rtrim( $global_stylesheet ); + } + + return null; + } + /** * Style value parser that returns a CSS definition array comprising style properties * that have keys representing individual style properties, otherwise known as longhand CSS properties. @@ -522,3 +589,12 @@ function wp_style_engine_get_block_supports_styles( $block_styles, $options = ar } return null; } + + +function wp_style_engine_generate_global_styles( $global_styles, $options = array() ) { + if ( class_exists( 'WP_Style_Engine' ) ) { + $style_engine = WP_Style_Engine::get_instance(); + return $style_engine->generate_global_styles( $global_styles, $options ); + } + return null; +} diff --git a/packages/style-engine/phpunit/class-wp-style-engine-test.php b/packages/style-engine/phpunit/class-wp-style-engine-test.php index 13334233ca445..641408f868925 100644 --- a/packages/style-engine/phpunit/class-wp-style-engine-test.php +++ b/packages/style-engine/phpunit/class-wp-style-engine-test.php @@ -458,4 +458,114 @@ public function data_generate_block_supports_styles_fixtures() { ), ); } + + /** + * Tests generating styles and classnames based on various manifestations of the $global_styles argument. + * + * @dataProvider data_generate_global_styles_fixtures + */ + public function test_generate_global_styles( $global_styles, $options, $expected_output ) { + $generated_styles = wp_style_engine_generate_global_styles( $global_styles, $options ); + $this->assertSame( $expected_output, $generated_styles ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_generate_global_styles_fixtures() { + return array( + 'default_return_value' => array( + 'global_styles' => array(), + 'options' => null, + 'expected_output' => null, + ), + 'default_return_valid_top_level_css_rules' => array( + 'global_styles' => array( + 'color' => array( + 'text' => 'var(--wp--preset--color--foreground)', + 'background' => 'var(--wp--preset--color--background)', + ), + 'spacing' => array( + 'blockGap' => '2rem', + 'padding' => array( + 'top' => '10%', + 'right' => '20%', + 'bottom' => '10%', + 'left' => '20%', + ), + ), + 'typography' => array( + 'fontFamily' => 'var(--wp--preset--font-family--system-font)', + 'lineHeight' => 2, + 'fontSize' => '1rem', + ), + ), + 'options' => null, + 'expected_output' => 'body { color: var(--wp--preset--color--foreground); background-color: var(--wp--preset--color--background); padding-top: 10%; padding-right: 20%; padding-bottom: 10%; padding-left: 20%; font-size: 1rem; font-family: var(--wp--preset--font-family--system-font); line-height: 2; }', + ), + 'default_return_valid_element_level_css_rules' => array( + 'global_styles' => array( + 'spacing' => array( + 'margin' => '20%', + ), + 'elements' => array( + 'h1' => array( + 'border' => array( + 'radius' => '0', + ), + 'color' => array( + 'text' => '#ffffff', + 'background' => '#000000', + ), + ), + 'link' => array( + 'typography' => array( + 'fontStyle' => 'underline', + ), + 'margin' => array( + 'bottom' => '20px', + ), + 'color' => array( + 'text' => 'var(--wp--preset--color--foreground)', + ), + ), + ), + ), + 'options' => null, + 'expected_output' => 'body { margin: 20%; } h1 { color: #ffffff; background-color: #000000; border-radius: 0; } a { color: var(--wp--preset--color--foreground); font-style: underline; }', + ), + 'default_return_valid_block_level_css_rules' => array( + 'global_styles' => array( + 'spacing' => array( + 'margin' => '20%', + ), + 'blocks' => array( + 'core/button' => array( + 'border' => array( + 'radius' => '0', + ), + 'color' => array( + 'text' => 'piano-red', + 'background' => 'muddy-waters', + ), + ), + 'core/site-title' => array( + 'typography' => array( + 'fontSize' => 'clamp(2em, 2vw, 4em)', + 'fontFamily' => 'Roboto,Oxygen-Sans,Ubuntu,sans-serif', + 'fontStyle' => 'italic', + ), + 'margin' => array( + 'bottom' => '20px', + ), + ), + ), + ), + 'options' => null, + 'expected_output' => 'body { margin: 20%; } .wp-block-button { color: piano-red; background-color: muddy-waters; border-radius: 0; } .wp-block-site-title { font-family: Roboto,Oxygen-Sans,Ubuntu,sans-serif; font-style: italic; }', + ), + ); + } } From 78e79d78ec0b49c46b109f269d163147a2d86e34 Mon Sep 17 00:00:00 2001 From: ramonjd Date: Fri, 8 Jul 2022 15:57:25 +1000 Subject: [PATCH 2/6] Allowing for custom properties Integrating root style generation into global styles. --- .../wordpress-6.1/class-wp-theme-json-6-1.php | 49 +++++++----- ...class-wp-style-engine-css-declarations.php | 4 +- .../style-engine/class-wp-style-engine.php | 80 ++++++++++++------- test/emptytheme/theme.json | 21 +++++ 4 files changed, 103 insertions(+), 51 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 5bb0c4fa94423..be86db87aff02 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 @@ -617,8 +617,7 @@ public function get_stylesheet( $types = array( 'variables', 'styles', 'presets' * @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() ); - + $node = _wp_array_get( $this->theme_json, $block_metadata['path'], array() ); $selector = $block_metadata['selector']; $settings = _wp_array_get( $this->theme_json, array( 'settings' ) ); @@ -626,10 +625,8 @@ public function get_styles_for_block( $block_metadata ) { // $block_metadata['path'] = array('styles','elements','link'); // Make sure that $block_metadata['path'] describes an element node, like ['styles', 'element', 'link']. // Skip non-element paths like just ['styles']. - $is_processing_element = in_array( 'elements', $block_metadata['path'], true ); - - $current_element = $is_processing_element ? $block_metadata['path'][ count( $block_metadata['path'] ) - 1 ] : null; - + $is_processing_element = in_array( 'elements', $block_metadata['path'], true ); + $current_element = $is_processing_element ? $block_metadata['path'][ count( $block_metadata['path'] ) - 1 ] : null; $element_pseudo_allowed = isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $current_element ] ) ? static::VALID_ELEMENT_PSEUDO_SELECTORS[ $current_element ] : array(); // Check for allowed pseudo classes (e.g. ":hover") from the $selector ("a:hover"). @@ -654,8 +651,6 @@ function( $pseudo_selector ) use ( $selector ) { $declarations = static::compute_style_properties( $node, $settings, null, $this->theme_json ); } - $block_rules = ''; - // 1. Separate the ones who use the general selector // and the ones who use the duotone selector. $declarations_duotone = array(); @@ -666,20 +661,34 @@ function( $pseudo_selector ) use ( $selector ) { } } - /* - * Reset default browser margin on the root body element. - * This is set on the root selector **before** generating the ruleset - * from the `theme.json`. This is to ensure that if the `theme.json` declares - * `margin` in its `spacing` declaration for the `body` element then these - * user-generated values take precedence in the CSS cascade. - * @link https://github.com/WordPress/gutenberg/issues/36147. - */ - if ( static::ROOT_BLOCK_SELECTOR === $selector ) { - $block_rules .= 'body { margin: 0; }'; - } + $block_rules = ''; // 2. Generate and append the rules that use the general selector. - $block_rules .= static::to_ruleset( $selector, $declarations ); + if ( static::ROOT_BLOCK_SELECTOR === $selector ) { + /* + * Reset default browser margin on the root body element. + * This is set on the root selector **before** generating the ruleset + * from the `theme.json`. This is to ensure that if the `theme.json` declares + * `margin` in its `spacing` declaration for the `body` element then these + * user-generated values take precedence in the CSS cascade. + * @link https://github.com/WordPress/gutenberg/issues/36147. + */ + $block_rules .= 'body { margin: 0; /* reset */ }'; + + /* + Style engine is generating the root styles only. + This way we can iteratively introduce it to start generating styles.elements, and styles.blocks. + */ + $block_rules .= gutenberg_style_engine_generate_global_styles( + $node, + array( + 'selector' => $selector, + ) + ); + + } else { + $block_rules .= static::to_ruleset( $selector, $declarations ); + } // 3. Generate and append the rules that use the duotone selector. if ( isset( $block_metadata['duotone'] ) && ! empty( $declarations_duotone ) ) { diff --git a/packages/style-engine/class-wp-style-engine-css-declarations.php b/packages/style-engine/class-wp-style-engine-css-declarations.php index bd7cbe616f68d..f2a029f029551 100644 --- a/packages/style-engine/class-wp-style-engine-css-declarations.php +++ b/packages/style-engine/class-wp-style-engine-css-declarations.php @@ -98,7 +98,9 @@ public function get_declarations_string() { $declarations_array = $this->get_declarations(); $declarations_output = ''; foreach ( $declarations_array as $property => $value ) { - $filtered_declaration = esc_html( safecss_filter_attr( "{$property}: {$value}" ) ); + //$filtered_declaration = esc_html( safecss_filter_attr( "{$property}: {$value}" ) ); + // @TODO temporary to allow --wp--style--block-gap through. + $filtered_declaration = esc_html( "{$property}: {$value}" ); if ( $filtered_declaration ) { $declarations_output .= $filtered_declaration . '; '; } diff --git a/packages/style-engine/class-wp-style-engine.php b/packages/style-engine/class-wp-style-engine.php index 854e456087638..3e5f544a6410e 100644 --- a/packages/style-engine/class-wp-style-engine.php +++ b/packages/style-engine/class-wp-style-engine.php @@ -142,6 +142,16 @@ class WP_Style_Engine { ), ), 'spacing' => array( + 'padding' => array( + 'property_keys' => array( + 'default' => 'padding', + 'individual' => 'padding-%s', + ), + 'path' => array( 'spacing', 'padding' ), + 'css_vars' => array( + 'spacing' => '--wp--preset--spacing--$slug', + ), + ), 'blockGap' => array( 'property_keys' => array( // @TODO 'grid-gap' has been deprecated in favor of 'gap'. @@ -162,16 +172,6 @@ class WP_Style_Engine { 'spacing' => '--wp--preset--spacing--$slug', ), ), - 'padding' => array( - 'property_keys' => array( - 'default' => 'padding', - 'individual' => 'padding-%s', - ), - 'path' => array( 'spacing', 'padding' ), - 'css_vars' => array( - 'spacing' => '--wp--preset--spacing--$slug', - ), - ), ), 'typography' => array( 'fontSize' => array( @@ -354,20 +354,22 @@ protected static function get_classnames( $style_value, $style_definition ) { * * @param array $style_value A single raw style value from the generate() $block_styles array. * @param array $style_definition A single style definition from BLOCK_STYLE_DEFINITIONS_METADATA. - * @param boolean $should_skip_css_vars Whether to skip compiling CSS var values. + * @param array $options Options passed to the public generator functions. * * @return array An array of CSS definitions, e.g., array( "$property" => "$value" ). */ - protected static function get_css_declarations( $style_value, $style_definition, $should_skip_css_vars = false ) { + protected static function get_css_declarations( $style_value, $style_definition, $options ) { if ( isset( $style_definition['value_func'] ) && is_callable( $style_definition['value_func'] ) ) { - return call_user_func( $style_definition['value_func'], $style_value, $style_definition, $should_skip_css_vars ); + return call_user_func( $style_definition['value_func'], $style_value, $style_definition, $options ); } - $css_declarations = array(); - $style_property_keys = $style_definition['property_keys']; + $css_declarations = array(); + $style_property_keys = $style_definition['property_keys']; + $should_skip_css_vars = isset( $options['convert_vars_to_classnames'] ) && true === $options['convert_vars_to_classnames']; + $custom_css_property = isset( $options['custom_properties'] ) && is_array( $options['custom_properties'] ) ? _wp_array_get( $options['custom_properties'], $style_definition['path'], null ) : null; // Build CSS var values from var:? values, e.g, `var(--wp--css--rule-slug )` // Check if the value is a CSS preset and there's a corresponding css_var pattern in the style definition. @@ -375,7 +377,8 @@ protected static function get_css_declarations( $style_value, $style_definition, if ( ! $should_skip_css_vars && ! empty( $style_definition['css_vars'] ) ) { $css_var = static::get_css_var_value( $style_value, $style_definition['css_vars'] ); if ( $css_var ) { - $css_declarations[ $style_property_keys['default'] ] = $css_var; + $css_property = $custom_css_property ? $custom_css_property : $style_property_keys['default']; + $css_declarations[ $css_property ] = $css_var; } } return $css_declarations; @@ -389,13 +392,15 @@ protected static function get_css_declarations( $style_value, $style_definition, if ( is_string( $value ) && strpos( $value, 'var:' ) !== false && ! $should_skip_css_vars && ! empty( $style_definition['css_vars'] ) ) { $value = static::get_css_var_value( $value, $style_definition['css_vars'] ); } - $individual_property = sprintf( $style_property_keys['individual'], _wp_to_kebab_case( $key ) ); + $css_property = $custom_css_property ? $custom_css_property : $style_property_keys['individual']; + $individual_property = sprintf( $css_property, _wp_to_kebab_case( $key ) ); if ( static::is_valid_style_value( $style_value ) ) { $css_declarations[ $individual_property ] = $value; } } } else { - $css_declarations[ $style_property_keys['default'] ] = $style_value; + $css_property = $custom_css_property ? $custom_css_property : $style_property_keys['default']; + $css_declarations[ $css_property ] = $style_value; } return $css_declarations; @@ -421,9 +426,8 @@ public function get_block_supports_styles( $block_styles, $options ) { return null; } - $css_declarations = array(); - $classnames = array(); - $should_skip_css_vars = isset( $options['convert_vars_to_classnames'] ) && true === $options['convert_vars_to_classnames']; + $css_declarations = array(); + $classnames = array(); // Collect CSS and classnames. foreach ( static::BLOCK_STYLE_DEFINITIONS_METADATA as $definition_group_key => $definition_group_style ) { @@ -438,7 +442,7 @@ public function get_block_supports_styles( $block_styles, $options ) { } $classnames = array_merge( $classnames, static::get_classnames( $style_value, $style_definition ) ); - $css_declarations = array_merge( $css_declarations, static::get_css_declarations( $style_value, $style_definition, $should_skip_css_vars ) ); + $css_declarations = array_merge( $css_declarations, static::get_css_declarations( $style_value, $style_definition, $options ) ); } } @@ -473,16 +477,30 @@ public function generate_global_styles( $global_styles, $options ) { return null; } + // Treat the selector value as the root selector. + $css_selector = isset( $options['selector'] ) ? $options['selector'] : 'body'; + // The return stylesheet. $global_stylesheet = ''; // Layer 0: Root. - $root_level_styles = $this->generate( $global_styles, array( 'selector' => 'body' ) ); + $root_level_styles = $this->get_block_supports_styles( + $global_styles, + array( + 'selector' => $css_selector, + 'custom_properties' => array( + 'spacing' => array( + 'blockGap' => '--wp--style--block-gap', + ), + ), + ), + ); if ( ! empty( $root_level_styles['css'] ) ) { $global_stylesheet .= $root_level_styles['css'] . ' '; } + /* // Layer 1: Elements. if ( isset( $global_styles['elements'] ) && is_array( $global_styles['elements'] ) ) { foreach ( $global_styles['elements'] as $element_name => $element_styles ) { @@ -490,7 +508,7 @@ public function generate_global_styles( $global_styles, $options ) { if ( ! $selector ) { continue; } - $element_level_styles = $this->generate( $element_styles, array( 'selector' => $selector ) ); + $element_level_styles = $this->get_block_supports_styles( $element_styles, array( 'selector' => $selector ) ); if ( ! empty( $element_level_styles['css'] ) ) { $global_stylesheet .= $element_level_styles['css'] . ' '; } @@ -501,13 +519,13 @@ public function generate_global_styles( $global_styles, $options ) { if ( isset( $global_styles['blocks'] ) && is_array( $global_styles['blocks'] ) ) { foreach ( $global_styles['blocks'] as $block_name => $block_styles ) { $selector = '.wp-block-' . str_replace( '/', '-', str_replace( 'core/', '', $block_name ) ); - $block_level_styles = $this->generate( $block_styles, array( 'selector' => $selector ) ); + $block_level_styles = $this->get_block_supports_styles( $block_styles, array( 'selector' => $selector ) ); if ( ! empty( $block_level_styles['css'] ) ) { $global_stylesheet .= $block_level_styles['css'] . ' '; } } } - + */ if ( ! empty( $global_stylesheet ) ) { return rtrim( $global_stylesheet ); } @@ -522,19 +540,21 @@ public function generate_global_styles( $global_styles, $options ) { * "border-{top|right|bottom|left}-{color|width|style}: {value};" or, * "border-image-{outset|source|width|repeat|slice}: {value};" * - * @param array $style_value A single raw Gutenberg style attributes value for a CSS property. - * @param array $individual_property_definition A single style definition from BLOCK_STYLE_DEFINITIONS_METADATA. - * @param boolean $should_skip_css_vars Whether to skip compiling CSS var values. + * @param array $style_value A single raw Gutenberg style attributes value for a CSS property. + * @param array $individual_property_definition A single style definition from BLOCK_STYLE_DEFINITIONS_METADATA. + * @param array $options Options passed to the public generator functions. * * @return array An array of CSS definitions, e.g., array( "$property" => "$value" ). */ - protected static function get_individual_property_css_declarations( $style_value, $individual_property_definition, $should_skip_css_vars ) { + protected static function get_individual_property_css_declarations( $style_value, $individual_property_definition, $options ) { $css_declarations = array(); if ( ! is_array( $style_value ) || empty( $style_value ) || empty( $individual_property_definition['path'] ) ) { return $css_declarations; } + $should_skip_css_vars = isset( $options['convert_vars_to_classnames'] ) && true === $options['convert_vars_to_classnames']; + // The first item in $individual_property_definition['path'] array tells us the style property, e.g., "border". // We use this to get a corresponding CSS style definition such as "color" or "width" from the same group. // The second item in $individual_property_definition['path'] array refers to the individual property marker, e.g., "top". diff --git a/test/emptytheme/theme.json b/test/emptytheme/theme.json index ed2d7b8d0946a..7c1e3b601d33b 100644 --- a/test/emptytheme/theme.json +++ b/test/emptytheme/theme.json @@ -7,5 +7,26 @@ "wideSize": "1100px" } }, + "styles": { + "spacing": { + "margin": { + "top": "12px" + } + }, + "blocks": { + "core/columns": { + "spacing": { + "blockGap": "22px" + }, + "blocks": { + "core/social-links": { + "spacing": { + "blockGap": "55px" + } + } + } + } + } + }, "patterns": [ "short-text-surrounded-by-round-images", "partner-logos" ] } From e8a0dd02760eb376d6eeb389a399a47f0295af44 Mon Sep 17 00:00:00 2001 From: ramonjd Date: Mon, 11 Jul 2022 13:23:30 +1000 Subject: [PATCH 3/6] Created a valid custom property check Added prettify option. --- .../wordpress-6.1/class-wp-theme-json-6-1.php | 2 + ...class-wp-style-engine-css-declarations.php | 40 +++++++++++-- .../style-engine/class-wp-style-engine.php | 60 +++++++------------ 3 files changed, 59 insertions(+), 43 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 be86db87aff02..291dedc5995a1 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 @@ -679,10 +679,12 @@ function( $pseudo_selector ) use ( $selector ) { Style engine is generating the root styles only. This way we can iteratively introduce it to start generating styles.elements, and styles.blocks. */ + $block_rules .= gutenberg_style_engine_generate_global_styles( $node, array( 'selector' => $selector, + 'prettify' => defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG, ) ); diff --git a/packages/style-engine/class-wp-style-engine-css-declarations.php b/packages/style-engine/class-wp-style-engine-css-declarations.php index f2a029f029551..2aa5a1dcbd91c 100644 --- a/packages/style-engine/class-wp-style-engine-css-declarations.php +++ b/packages/style-engine/class-wp-style-engine-css-declarations.php @@ -40,6 +40,22 @@ public function __construct( $declarations = array() ) { $this->add_declarations( $declarations ); } + /** + * Checks a CSS property string to see whether it is a valid CSS custom property. + * In conjuction with static:sanitize_property it only allows `--wp--${slug}--{$kebab_case_css_property}`. + * + * This explicit checks is required because + * safecss_filter_attr_allow_css filter in safecss_filter_attr (kses.php) + * does not let through CSS custom variables. + * + * @param string $css_property The CSS property. + * + * @return boolean + */ + protected function is_valid_custom_property( $css_property ) { + return 1 === preg_match( '/^--wp--[a-z]+--[a-z0-9-]+$/', $css_property ); + } + /** * Add a single declaration. * @@ -92,20 +108,32 @@ public function get_declarations() { /** * Filters and compiles the CSS declarations. * + * @param array $options array( + * 'prettify' => (boolean) Whether to format the output with indents and new lines. + * );. + * * @return string The CSS declarations. */ - public function get_declarations_string() { + public function get_declarations_string( $options ) { + $should_prettify = isset( $options['prettify'] ) ? $options['prettify'] : null; $declarations_array = $this->get_declarations(); $declarations_output = ''; + foreach ( $declarations_array as $property => $value ) { - //$filtered_declaration = esc_html( safecss_filter_attr( "{$property}: {$value}" ) ); - // @TODO temporary to allow --wp--style--block-gap through. - $filtered_declaration = esc_html( "{$property}: {$value}" ); + $declaration = "{$property}: {$value}"; + if ( ! static::is_valid_custom_property( $property ) ) { + $declaration = safecss_filter_attr( $declaration ); + } + $filtered_declaration = esc_html( $declaration ); if ( $filtered_declaration ) { - $declarations_output .= $filtered_declaration . '; '; + if ( $should_prettify ) { + $declarations_output .= "\t$filtered_declaration;\n"; + } else { + $declarations_output .= $filtered_declaration . '; '; + } } } - return rtrim( $declarations_output ); + return rtrim( $declarations_output, ' ' ); } /** diff --git a/packages/style-engine/class-wp-style-engine.php b/packages/style-engine/class-wp-style-engine.php index 3e5f544a6410e..7e6567239e9d2 100644 --- a/packages/style-engine/class-wp-style-engine.php +++ b/packages/style-engine/class-wp-style-engine.php @@ -414,6 +414,7 @@ protected static function get_css_declarations( $style_value, $style_definition, * @param array $options array( * 'selector' => (string) When a selector is passed, `generate()` will return a full CSS rule `$selector { ...rules }`, otherwise a concatenated string of properties and values. * 'convert_vars_to_classnames' => (boolean) Whether to skip converting CSS var:? values to var( --wp--preset--* ) values. Default is `false`. + * 'prettify' => (boolean) Whether to format the output with indents and new lines. * );. * * @return array|null array( @@ -447,12 +448,13 @@ public function get_block_supports_styles( $block_styles, $options ) { } // Build CSS rules output. - $css_selector = isset( $options['selector'] ) ? $options['selector'] : null; - $style_rules = new WP_Style_Engine_CSS_Declarations( $css_declarations ); + $css_selector = isset( $options['selector'] ) ? $options['selector'] : null; + $should_prettify = isset( $options['prettify'] ) ? $options['prettify'] : null; + $style_rules = new WP_Style_Engine_CSS_Declarations( $css_declarations ); // The return object. $styles_output = array(); - $css = $style_rules->get_declarations_string(); + $css = $style_rules->get_declarations_string( array( 'prettify' => $should_prettify ) ); // Return css, if any. if ( ! empty( $css ) ) { @@ -460,7 +462,11 @@ public function get_block_supports_styles( $block_styles, $options ) { $styles_output['declarations'] = $style_rules->get_declarations(); // Return an entire rule if there is a selector. if ( $css_selector ) { - $styles_output['css'] = $css_selector . ' { ' . $css . ' }'; + if ( $should_prettify ) { + $styles_output['css'] = $css_selector . ' {' . "\n" . $css . '}' . "\n"; + } else { + $styles_output['css'] = $css_selector . ' { ' . $css . ' }'; + } } } @@ -484,48 +490,28 @@ public function generate_global_styles( $global_styles, $options ) { $global_stylesheet = ''; // Layer 0: Root. - $root_level_styles = $this->get_block_supports_styles( - $global_styles, - array( - 'selector' => $css_selector, - 'custom_properties' => array( - 'spacing' => array( - 'blockGap' => '--wp--style--block-gap', - ), + $root_level_options_defaults = array( + 'selector' => 'body', + 'custom_properties' => array( + 'spacing' => array( + 'blockGap' => '--wp--style--block-gap', ), ), ); + $root_level_options = wp_parse_args( $options, $root_level_options_defaults ); + $root_level_styles = $this->get_block_supports_styles( + $global_styles, + $root_level_options, + ); if ( ! empty( $root_level_styles['css'] ) ) { $global_stylesheet .= $root_level_styles['css'] . ' '; } - /* - // Layer 1: Elements. - if ( isset( $global_styles['elements'] ) && is_array( $global_styles['elements'] ) ) { - foreach ( $global_styles['elements'] as $element_name => $element_styles ) { - $selector = isset( static::ELEMENTS[ $element_name ] ) ? static::ELEMENTS[ $element_name ] : null; - if ( ! $selector ) { - continue; - } - $element_level_styles = $this->get_block_supports_styles( $element_styles, array( 'selector' => $selector ) ); - if ( ! empty( $element_level_styles['css'] ) ) { - $global_stylesheet .= $element_level_styles['css'] . ' '; - } - } - } + // @TODO Layer 1: Elements. + + // @TODO Layer 2: Blocks. - // Layer 2: Blocks. - if ( isset( $global_styles['blocks'] ) && is_array( $global_styles['blocks'] ) ) { - foreach ( $global_styles['blocks'] as $block_name => $block_styles ) { - $selector = '.wp-block-' . str_replace( '/', '-', str_replace( 'core/', '', $block_name ) ); - $block_level_styles = $this->get_block_supports_styles( $block_styles, array( 'selector' => $selector ) ); - if ( ! empty( $block_level_styles['css'] ) ) { - $global_stylesheet .= $block_level_styles['css'] . ' '; - } - } - } - */ if ( ! empty( $global_stylesheet ) ) { return rtrim( $global_stylesheet ); } From 629e8c6d6152c5b705d05dea1371269d649ac6ee Mon Sep 17 00:00:00 2001 From: ramonjd Date: Mon, 11 Jul 2022 16:04:16 +1000 Subject: [PATCH 4/6] Clean up. Remove prettify option tests Update unit tests Building in ref pointers --- .../wordpress-6.1/class-wp-theme-json-6-1.php | 8 +- ...class-wp-style-engine-css-declarations.php | 15 +--- .../style-engine/class-wp-style-engine.php | 58 ++++++++++---- .../phpunit/class-wp-style-engine-test.php | 76 +++++++++++-------- phpunit/class-wp-theme-json-test.php | 8 +- 5 files changed, 97 insertions(+), 68 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 291dedc5995a1..5499153c25c41 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 @@ -673,19 +673,15 @@ function( $pseudo_selector ) use ( $selector ) { * user-generated values take precedence in the CSS cascade. * @link https://github.com/WordPress/gutenberg/issues/36147. */ - $block_rules .= 'body { margin: 0; /* reset */ }'; + $block_rules .= 'body { margin: 0; }'; /* Style engine is generating the root styles only. This way we can iteratively introduce it to start generating styles.elements, and styles.blocks. */ - $block_rules .= gutenberg_style_engine_generate_global_styles( $node, - array( - 'selector' => $selector, - 'prettify' => defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG, - ) + array( 'selector' => $selector ) ); } else { diff --git a/packages/style-engine/class-wp-style-engine-css-declarations.php b/packages/style-engine/class-wp-style-engine-css-declarations.php index 2aa5a1dcbd91c..092b4e204647b 100644 --- a/packages/style-engine/class-wp-style-engine-css-declarations.php +++ b/packages/style-engine/class-wp-style-engine-css-declarations.php @@ -108,14 +108,9 @@ public function get_declarations() { /** * Filters and compiles the CSS declarations. * - * @param array $options array( - * 'prettify' => (boolean) Whether to format the output with indents and new lines. - * );. - * * @return string The CSS declarations. */ - public function get_declarations_string( $options ) { - $should_prettify = isset( $options['prettify'] ) ? $options['prettify'] : null; + public function get_declarations_string() { $declarations_array = $this->get_declarations(); $declarations_output = ''; @@ -126,14 +121,10 @@ public function get_declarations_string( $options ) { } $filtered_declaration = esc_html( $declaration ); if ( $filtered_declaration ) { - if ( $should_prettify ) { - $declarations_output .= "\t$filtered_declaration;\n"; - } else { - $declarations_output .= $filtered_declaration . '; '; - } + $declarations_output .= $filtered_declaration . '; '; } } - return rtrim( $declarations_output, ' ' ); + return rtrim( $declarations_output ); } /** diff --git a/packages/style-engine/class-wp-style-engine.php b/packages/style-engine/class-wp-style-engine.php index 7e6567239e9d2..382280ac14bbf 100644 --- a/packages/style-engine/class-wp-style-engine.php +++ b/packages/style-engine/class-wp-style-engine.php @@ -294,6 +294,20 @@ protected static function get_css_var_value( $style_value, $css_vars ) { return null; } + /** + * Using a given path, return a value from the $block_styles object. + * + * @param array $block_styles Styles from a block's attributes object. + * @param string $ref A dot syntax path to another value in the $block_styles object, e.g., `styles.color.text`. + * + * @return string|array A style value from the block styles object. + */ + protected static function get_ref_value( $block_styles = array(), $ref = '' ) { + $ref = preg_replace( '/^styles\./', '', $ref ); + $path = explode( '.', $ref ); + return _wp_array_get( $block_styles, $path, null ); + } + /** * Checks whether an incoming block style value is valid. * @@ -354,16 +368,17 @@ protected static function get_classnames( $style_value, $style_definition ) { * * @param array $style_value A single raw style value from the generate() $block_styles array. * @param array $style_definition A single style definition from BLOCK_STYLE_DEFINITIONS_METADATA. - * @param array $options Options passed to the public generator functions. + * @param array $block_styles Styles from a block's attributes object. + * @param array $options Options passed to the public generator functions. * * @return array An array of CSS definitions, e.g., array( "$property" => "$value" ). */ - protected static function get_css_declarations( $style_value, $style_definition, $options ) { + protected static function get_css_declarations( $style_value, $style_definition, $block_styles, $options ) { if ( isset( $style_definition['value_func'] ) && is_callable( $style_definition['value_func'] ) ) { - return call_user_func( $style_definition['value_func'], $style_value, $style_definition, $options ); + return call_user_func( $style_definition['value_func'], $style_value, $style_definition, $block_styles, $options ); } $css_declarations = array(); @@ -394,6 +409,12 @@ protected static function get_css_declarations( $style_value, $style_definition, } $css_property = $custom_css_property ? $custom_css_property : $style_property_keys['individual']; $individual_property = sprintf( $css_property, _wp_to_kebab_case( $key ) ); + + // If the style value contains a reference to another value in the tree. + if ( isset( $value['ref'] ) ) { + $value = static::get_ref_value( $block_styles, $value['ref'] ); + } + if ( static::is_valid_style_value( $style_value ) ) { $css_declarations[ $individual_property ] = $value; } @@ -414,7 +435,6 @@ protected static function get_css_declarations( $style_value, $style_definition, * @param array $options array( * 'selector' => (string) When a selector is passed, `generate()` will return a full CSS rule `$selector { ...rules }`, otherwise a concatenated string of properties and values. * 'convert_vars_to_classnames' => (boolean) Whether to skip converting CSS var:? values to var( --wp--preset--* ) values. Default is `false`. - * 'prettify' => (boolean) Whether to format the output with indents and new lines. * );. * * @return array|null array( @@ -438,23 +458,27 @@ public function get_block_supports_styles( $block_styles, $options ) { foreach ( $definition_group_style as $style_definition ) { $style_value = _wp_array_get( $block_styles, $style_definition['path'], null ); + // If the style value contains a reference to another value in the tree. + if ( isset( $style_value['ref'] ) ) { + $style_value = static::get_ref_value( $block_styles, $style_value['ref'] ); + } + if ( ! static::is_valid_style_value( $style_value ) ) { continue; } $classnames = array_merge( $classnames, static::get_classnames( $style_value, $style_definition ) ); - $css_declarations = array_merge( $css_declarations, static::get_css_declarations( $style_value, $style_definition, $options ) ); + $css_declarations = array_merge( $css_declarations, static::get_css_declarations( $style_value, $style_definition, $block_styles, $options ) ); } } // Build CSS rules output. - $css_selector = isset( $options['selector'] ) ? $options['selector'] : null; - $should_prettify = isset( $options['prettify'] ) ? $options['prettify'] : null; - $style_rules = new WP_Style_Engine_CSS_Declarations( $css_declarations ); + $css_selector = isset( $options['selector'] ) ? $options['selector'] : null; + $style_rules = new WP_Style_Engine_CSS_Declarations( $css_declarations ); // The return object. $styles_output = array(); - $css = $style_rules->get_declarations_string( array( 'prettify' => $should_prettify ) ); + $css = $style_rules->get_declarations_string(); // Return css, if any. if ( ! empty( $css ) ) { @@ -462,11 +486,7 @@ public function get_block_supports_styles( $block_styles, $options ) { $styles_output['declarations'] = $style_rules->get_declarations(); // Return an entire rule if there is a selector. if ( $css_selector ) { - if ( $should_prettify ) { - $styles_output['css'] = $css_selector . ' {' . "\n" . $css . '}' . "\n"; - } else { - $styles_output['css'] = $css_selector . ' { ' . $css . ' }'; - } + $styles_output['css'] = $css_selector . ' { ' . $css . ' }'; } } @@ -528,11 +548,12 @@ public function generate_global_styles( $global_styles, $options ) { * * @param array $style_value A single raw Gutenberg style attributes value for a CSS property. * @param array $individual_property_definition A single style definition from BLOCK_STYLE_DEFINITIONS_METADATA. - * @param array $options Options passed to the public generator functions. + * @param array $block_styles Styles from a block's attributes object. + * @param array $options Options passed to the public generator functions. * * @return array An array of CSS definitions, e.g., array( "$property" => "$value" ). */ - protected static function get_individual_property_css_declarations( $style_value, $individual_property_definition, $options ) { + protected static function get_individual_property_css_declarations( $style_value, $individual_property_definition, $block_styles, $options ) { $css_declarations = array(); if ( ! is_array( $style_value ) || empty( $style_value ) || empty( $individual_property_definition['path'] ) ) { @@ -557,6 +578,11 @@ protected static function get_individual_property_css_declarations( $style_value $style_definition = _wp_array_get( static::BLOCK_STYLE_DEFINITIONS_METADATA, $style_definition_path, null ); if ( $style_definition && isset( $style_definition['property_keys']['individual'] ) ) { + // If the style value contains a reference to another value in the tree. + if ( isset( $value['ref'] ) ) { + $value = static::get_ref_value( $block_styles, $value['ref'] ); + } + // Set a CSS var if there is a valid preset value. if ( is_string( $value ) && strpos( $value, 'var:' ) !== false && ! $should_skip_css_vars && ! empty( $individual_property_definition['css_vars'] ) ) { $value = static::get_css_var_value( $value, $individual_property_definition['css_vars'] ); diff --git a/packages/style-engine/phpunit/class-wp-style-engine-test.php b/packages/style-engine/phpunit/class-wp-style-engine-test.php index 641408f868925..25ccf7561813c 100644 --- a/packages/style-engine/phpunit/class-wp-style-engine-test.php +++ b/packages/style-engine/phpunit/class-wp-style-engine-test.php @@ -476,12 +476,12 @@ public function test_generate_global_styles( $global_styles, $options, $expected */ public function data_generate_global_styles_fixtures() { return array( - 'default_return_value' => array( + 'default_return_value' => array( 'global_styles' => array(), 'options' => null, 'expected_output' => null, ), - 'default_return_valid_top_level_css_rules' => array( + 'default_return_valid_top_level_css_rules' => array( 'global_styles' => array( 'color' => array( 'text' => 'var(--wp--preset--color--foreground)', @@ -503,45 +503,39 @@ public function data_generate_global_styles_fixtures() { ), ), 'options' => null, - 'expected_output' => 'body { color: var(--wp--preset--color--foreground); background-color: var(--wp--preset--color--background); padding-top: 10%; padding-right: 20%; padding-bottom: 10%; padding-left: 20%; font-size: 1rem; font-family: var(--wp--preset--font-family--system-font); line-height: 2; }', + 'expected_output' => 'body { color: var(--wp--preset--color--foreground); background-color: var(--wp--preset--color--background); padding-top: 10%; padding-right: 20%; padding-bottom: 10%; padding-left: 20%; --wp--style--block-gap: 2rem; font-size: 1rem; font-family: var(--wp--preset--font-family--system-font); line-height: 2; }', ), - 'default_return_valid_element_level_css_rules' => array( + 'compiles_with_ref_pointers' => array( 'global_styles' => array( - 'spacing' => array( - 'margin' => '20%', + 'color' => array( + 'background' => '#ffffff', + 'text' => array( 'ref' => 'styles.color.background' ), ), - 'elements' => array( - 'h1' => array( - 'border' => array( - 'radius' => '0', - ), - 'color' => array( - 'text' => '#ffffff', - 'background' => '#000000', - ), + 'spacing' => array( + 'blockGap' => '2rem', + 'padding' => array( + 'top' => array( 'ref' => 'styles.spacing.blockGap' ), + 'right' => '20%', + 'bottom' => array( 'ref' => 'styles.spacing.blockGap' ), + 'left' => '20%', ), - 'link' => array( - 'typography' => array( - 'fontStyle' => 'underline', - ), - 'margin' => array( - 'bottom' => '20px', - ), - 'color' => array( - 'text' => 'var(--wp--preset--color--foreground)', - ), + ), + 'border' => array( + 'top' => array( + 'width' => array( 'ref' => 'styles.spacing.padding.left' ), + 'style' => 'dashed', ), ), ), 'options' => null, - 'expected_output' => 'body { margin: 20%; } h1 { color: #ffffff; background-color: #000000; border-radius: 0; } a { color: var(--wp--preset--color--foreground); font-style: underline; }', + 'expected_output' => 'body { color: #ffffff; background-color: #ffffff; border-top-width: 20%; border-top-style: dashed; padding-top: 2rem; padding-right: 20%; padding-bottom: 2rem; padding-left: 20%; --wp--style--block-gap: 2rem; }', ), - 'default_return_valid_block_level_css_rules' => array( + 'ignores_other_top_level_keys' => array( 'global_styles' => array( - 'spacing' => array( + 'spacing' => array( 'margin' => '20%', ), - 'blocks' => array( + 'blocks' => array( 'core/button' => array( 'border' => array( 'radius' => '0', @@ -562,9 +556,31 @@ public function data_generate_global_styles_fixtures() { ), ), ), + 'elements' => array( + 'h1' => array( + 'border' => array( + 'radius' => '0', + ), + 'color' => array( + 'text' => '#ffffff', + 'background' => '#000000', + ), + ), + 'link' => array( + 'typography' => array( + 'fontStyle' => 'underline', + ), + 'margin' => array( + 'bottom' => '20px', + ), + 'color' => array( + 'text' => 'var(--wp--preset--color--foreground)', + ), + ), + ), ), 'options' => null, - 'expected_output' => 'body { margin: 20%; } .wp-block-button { color: piano-red; background-color: muddy-waters; border-radius: 0; } .wp-block-site-title { font-family: Roboto,Oxygen-Sans,Ubuntu,sans-serif; font-style: italic; }', + 'expected_output' => 'body { margin: 20%; }', ), ); } diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php index 0c830d8de9a9c..e98d820fb9c15 100644 --- a/phpunit/class-wp-theme-json-test.php +++ b/phpunit/class-wp-theme-json-test.php @@ -526,7 +526,7 @@ function test_get_property_value_valid() { ) ); - $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;}'; + $expected = 'body { margin: 0; }body { color: #000000; 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{background-color: #000000;color: #ffffff;}'; $this->assertEquals( $expected, $theme_json->get_stylesheet() ); } @@ -558,7 +558,7 @@ function test_get_property_value_loop() { ) ); - $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;}'; + $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() ); } @@ -589,7 +589,7 @@ function test_get_property_value_recursion() { ) ); - $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;}'; + $expected = 'body { margin: 0; }body { color: #ffffff; 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->assertEquals( $expected, $theme_json->get_stylesheet() ); } @@ -612,7 +612,7 @@ function test_get_property_value_self() { ) ); - $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; }'; + $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() ); } From 31ef9cbd5d9694f8d8424099cca98388d34e466e Mon Sep 17 00:00:00 2001 From: ramonjd Date: Mon, 11 Jul 2022 16:26:42 +1000 Subject: [PATCH 5/6] Reverting emptytheme theme.json Pleasing the linter Remove elements const --- .../style-engine/class-wp-style-engine.php | 48 +++++++++++-------- test/emptytheme/theme.json | 21 -------- 2 files changed, 29 insertions(+), 40 deletions(-) diff --git a/packages/style-engine/class-wp-style-engine.php b/packages/style-engine/class-wp-style-engine.php index 382280ac14bbf..70bde74efe34f 100644 --- a/packages/style-engine/class-wp-style-engine.php +++ b/packages/style-engine/class-wp-style-engine.php @@ -231,16 +231,6 @@ class WP_Style_Engine { ), ); - /** - * The valid elements that can be found under styles. - * - * @var string[] - */ - const ELEMENTS = array( - 'link' => 'a', - 'h1' => 'h1', - ); - /** * Utility method to retrieve the main instance of the class. * @@ -433,7 +423,7 @@ protected static function get_css_declarations( $style_value, $style_definition, * * @param array $block_styles Styles from a block's attributes object. * @param array $options array( - * 'selector' => (string) When a selector is passed, `generate()` will return a full CSS rule `$selector { ...rules }`, otherwise a concatenated string of properties and values. + * 'selector' => (string) When a selector is passed, the style engine will return a full CSS rule `$selector { ...rules }`, otherwise a concatenated string of properties and values. * 'convert_vars_to_classnames' => (boolean) Whether to skip converting CSS var:? values to var( --wp--preset--* ) values. Default is `false`. * );. * @@ -498,14 +488,21 @@ public function get_block_supports_styles( $block_styles, $options ) { return $styles_output; } + /** + * Returns a stylesheet of CSS rules from a theme.json/global styles object. + * + * @param array $global_styles Styles object from theme.json. + * @param array $options array( + * 'selector' => (string) When a selector is passed, the style engine will return a full CSS rule `$selector { ...rules }`, otherwise a concatenated string of properties and values. + * );. + * + * @return string A stylesheet. + */ public function generate_global_styles( $global_styles, $options ) { if ( empty( $global_styles ) || ! is_array( $global_styles ) ) { return null; } - // Treat the selector value as the root selector. - $css_selector = isset( $options['selector'] ) ? $options['selector'] : 'body'; - // The return stylesheet. $global_stylesheet = ''; @@ -513,15 +510,13 @@ public function generate_global_styles( $global_styles, $options ) { $root_level_options_defaults = array( 'selector' => 'body', 'custom_properties' => array( - 'spacing' => array( - 'blockGap' => '--wp--style--block-gap', - ), + 'spacing' => array( 'blockGap' => '--wp--style--block-gap' ), ), ); $root_level_options = wp_parse_args( $options, $root_level_options_defaults ); $root_level_styles = $this->get_block_supports_styles( $global_styles, - $root_level_options, + $root_level_options ); if ( ! empty( $root_level_styles['css'] ) ) { @@ -622,7 +617,22 @@ function wp_style_engine_get_block_supports_styles( $block_styles, $options = ar return null; } - +/** + * Global public interface method to WP_Style_Engine->generate_global_styles to generate a stylesheet styles from a single theme.json style object. + * See: https://developer.wordpress.org/block-editor/reference-guides/theme-json-reference/theme-json-living/#styles + * + * Example usage: + * + * $styles = wp_style_engine_generate_global_styles( array( 'color' => array( 'text' => '#cccccc' ) ) ); + * // Returns `body { color: #cccccc }`. + * + * @access public + * + * @param array $global_styles The value of a block's attributes.style. + * @param array $options An array of options to determine the output. + * + * @return string A stylesheet. + */ function wp_style_engine_generate_global_styles( $global_styles, $options = array() ) { if ( class_exists( 'WP_Style_Engine' ) ) { $style_engine = WP_Style_Engine::get_instance(); diff --git a/test/emptytheme/theme.json b/test/emptytheme/theme.json index 7c1e3b601d33b..ed2d7b8d0946a 100644 --- a/test/emptytheme/theme.json +++ b/test/emptytheme/theme.json @@ -7,26 +7,5 @@ "wideSize": "1100px" } }, - "styles": { - "spacing": { - "margin": { - "top": "12px" - } - }, - "blocks": { - "core/columns": { - "spacing": { - "blockGap": "22px" - }, - "blocks": { - "core/social-links": { - "spacing": { - "blockGap": "55px" - } - } - } - } - } - }, "patterns": [ "short-text-surrounded-by-round-images", "partner-logos" ] } From 5cc71198341e5242cf3adbd8ac888ffece268560 Mon Sep 17 00:00:00 2001 From: ramonjd Date: Tue, 12 Jul 2022 14:46:43 +1000 Subject: [PATCH 6/6] Removing blockGap support at the root level after the changes in https://github.com/WordPress/gutenberg/pull/40875, which moves things to the layout implementation. --- ...class-wp-style-engine-css-declarations.php | 22 +------------- .../style-engine/class-wp-style-engine.php | 29 ++++--------------- .../phpunit/class-wp-style-engine-test.php | 15 +++++----- 3 files changed, 14 insertions(+), 52 deletions(-) diff --git a/packages/style-engine/class-wp-style-engine-css-declarations.php b/packages/style-engine/class-wp-style-engine-css-declarations.php index 092b4e204647b..13ec0d5142bf5 100644 --- a/packages/style-engine/class-wp-style-engine-css-declarations.php +++ b/packages/style-engine/class-wp-style-engine-css-declarations.php @@ -40,22 +40,6 @@ public function __construct( $declarations = array() ) { $this->add_declarations( $declarations ); } - /** - * Checks a CSS property string to see whether it is a valid CSS custom property. - * In conjuction with static:sanitize_property it only allows `--wp--${slug}--{$kebab_case_css_property}`. - * - * This explicit checks is required because - * safecss_filter_attr_allow_css filter in safecss_filter_attr (kses.php) - * does not let through CSS custom variables. - * - * @param string $css_property The CSS property. - * - * @return boolean - */ - protected function is_valid_custom_property( $css_property ) { - return 1 === preg_match( '/^--wp--[a-z]+--[a-z0-9-]+$/', $css_property ); - } - /** * Add a single declaration. * @@ -115,11 +99,7 @@ public function get_declarations_string() { $declarations_output = ''; foreach ( $declarations_array as $property => $value ) { - $declaration = "{$property}: {$value}"; - if ( ! static::is_valid_custom_property( $property ) ) { - $declaration = safecss_filter_attr( $declaration ); - } - $filtered_declaration = esc_html( $declaration ); + $filtered_declaration = esc_html( safecss_filter_attr( "{$property}: {$value}" ) ); if ( $filtered_declaration ) { $declarations_output .= $filtered_declaration . '; '; } diff --git a/packages/style-engine/class-wp-style-engine.php b/packages/style-engine/class-wp-style-engine.php index 70bde74efe34f..9524fff044b35 100644 --- a/packages/style-engine/class-wp-style-engine.php +++ b/packages/style-engine/class-wp-style-engine.php @@ -142,7 +142,7 @@ class WP_Style_Engine { ), ), 'spacing' => array( - 'padding' => array( + 'padding' => array( 'property_keys' => array( 'default' => 'padding', 'individual' => 'padding-%s', @@ -152,17 +152,7 @@ class WP_Style_Engine { 'spacing' => '--wp--preset--spacing--$slug', ), ), - 'blockGap' => array( - 'property_keys' => array( - // @TODO 'grid-gap' has been deprecated in favor of 'gap'. - // See: https://developer.mozilla.org/en-US/docs/Web/CSS/gap. - // Update the white list in safecss_filter_attr (kses.php). - // See: https://core.trac.wordpress.org/ticket/56122 - 'default' => 'grid-gap', - ), - 'path' => array( 'spacing', 'blockGap' ), - ), - 'margin' => array( + 'margin' => array( 'property_keys' => array( 'default' => 'margin', 'individual' => 'margin-%s', @@ -374,7 +364,6 @@ protected static function get_css_declarations( $style_value, $style_definition, $css_declarations = array(); $style_property_keys = $style_definition['property_keys']; $should_skip_css_vars = isset( $options['convert_vars_to_classnames'] ) && true === $options['convert_vars_to_classnames']; - $custom_css_property = isset( $options['custom_properties'] ) && is_array( $options['custom_properties'] ) ? _wp_array_get( $options['custom_properties'], $style_definition['path'], null ) : null; // Build CSS var values from var:? values, e.g, `var(--wp--css--rule-slug )` // Check if the value is a CSS preset and there's a corresponding css_var pattern in the style definition. @@ -382,8 +371,7 @@ protected static function get_css_declarations( $style_value, $style_definition, if ( ! $should_skip_css_vars && ! empty( $style_definition['css_vars'] ) ) { $css_var = static::get_css_var_value( $style_value, $style_definition['css_vars'] ); if ( $css_var ) { - $css_property = $custom_css_property ? $custom_css_property : $style_property_keys['default']; - $css_declarations[ $css_property ] = $css_var; + $css_declarations[ $style_property_keys['default'] ] = $css_var; } } return $css_declarations; @@ -397,8 +385,7 @@ protected static function get_css_declarations( $style_value, $style_definition, if ( is_string( $value ) && strpos( $value, 'var:' ) !== false && ! $should_skip_css_vars && ! empty( $style_definition['css_vars'] ) ) { $value = static::get_css_var_value( $value, $style_definition['css_vars'] ); } - $css_property = $custom_css_property ? $custom_css_property : $style_property_keys['individual']; - $individual_property = sprintf( $css_property, _wp_to_kebab_case( $key ) ); + $individual_property = sprintf( $style_property_keys['individual'], _wp_to_kebab_case( $key ) ); // If the style value contains a reference to another value in the tree. if ( isset( $value['ref'] ) ) { @@ -410,8 +397,7 @@ protected static function get_css_declarations( $style_value, $style_definition, } } } else { - $css_property = $custom_css_property ? $custom_css_property : $style_property_keys['default']; - $css_declarations[ $css_property ] = $style_value; + $css_declarations[ $style_property_keys['default'] ] = $style_value; } return $css_declarations; @@ -508,10 +494,7 @@ public function generate_global_styles( $global_styles, $options ) { // Layer 0: Root. $root_level_options_defaults = array( - 'selector' => 'body', - 'custom_properties' => array( - 'spacing' => array( 'blockGap' => '--wp--style--block-gap' ), - ), + 'selector' => 'body', ); $root_level_options = wp_parse_args( $options, $root_level_options_defaults ); $root_level_styles = $this->get_block_supports_styles( diff --git a/packages/style-engine/phpunit/class-wp-style-engine-test.php b/packages/style-engine/phpunit/class-wp-style-engine-test.php index 25ccf7561813c..2cbe2ef62d449 100644 --- a/packages/style-engine/phpunit/class-wp-style-engine-test.php +++ b/packages/style-engine/phpunit/class-wp-style-engine-test.php @@ -488,8 +488,7 @@ public function data_generate_global_styles_fixtures() { 'background' => 'var(--wp--preset--color--background)', ), 'spacing' => array( - 'blockGap' => '2rem', - 'padding' => array( + 'padding' => array( 'top' => '10%', 'right' => '20%', 'bottom' => '10%', @@ -503,7 +502,7 @@ public function data_generate_global_styles_fixtures() { ), ), 'options' => null, - 'expected_output' => 'body { color: var(--wp--preset--color--foreground); background-color: var(--wp--preset--color--background); padding-top: 10%; padding-right: 20%; padding-bottom: 10%; padding-left: 20%; --wp--style--block-gap: 2rem; font-size: 1rem; font-family: var(--wp--preset--font-family--system-font); line-height: 2; }', + 'expected_output' => 'body { color: var(--wp--preset--color--foreground); background-color: var(--wp--preset--color--background); padding-top: 10%; padding-right: 20%; padding-bottom: 10%; padding-left: 20%; font-size: 1rem; font-family: var(--wp--preset--font-family--system-font); line-height: 2; }', ), 'compiles_with_ref_pointers' => array( 'global_styles' => array( @@ -512,11 +511,11 @@ public function data_generate_global_styles_fixtures() { 'text' => array( 'ref' => 'styles.color.background' ), ), 'spacing' => array( - 'blockGap' => '2rem', - 'padding' => array( - 'top' => array( 'ref' => 'styles.spacing.blockGap' ), + 'margin' => '2rem', + 'padding' => array( + 'top' => array( 'ref' => 'styles.spacing.margin' ), 'right' => '20%', - 'bottom' => array( 'ref' => 'styles.spacing.blockGap' ), + 'bottom' => array( 'ref' => 'styles.spacing.margin' ), 'left' => '20%', ), ), @@ -528,7 +527,7 @@ public function data_generate_global_styles_fixtures() { ), ), 'options' => null, - 'expected_output' => 'body { color: #ffffff; background-color: #ffffff; border-top-width: 20%; border-top-style: dashed; padding-top: 2rem; padding-right: 20%; padding-bottom: 2rem; padding-left: 20%; --wp--style--block-gap: 2rem; }', + 'expected_output' => 'body { color: #ffffff; background-color: #ffffff; border-top-width: 20%; border-top-style: dashed; padding-top: 2rem; padding-right: 20%; padding-bottom: 2rem; padding-left: 20%; margin: 2rem; }', ), 'ignores_other_top_level_keys' => array( 'global_styles' => array(