diff --git a/lib/block-supports/spacing.php b/lib/block-supports/spacing.php index 78f5b59f90fb0..bb24d3d7b052f 100644 --- a/lib/block-supports/spacing.php +++ b/lib/block-supports/spacing.php @@ -42,36 +42,31 @@ function gutenberg_apply_spacing_support( $block_type, $block_attributes ) { if ( gutenberg_skip_spacing_serialization( $block_type ) ) { return array(); } - + $attributes = array(); $has_padding_support = gutenberg_block_has_support( $block_type, array( 'spacing', 'padding' ), false ); $has_margin_support = gutenberg_block_has_support( $block_type, array( 'spacing', 'margin' ), false ); - $styles = array(); - - if ( $has_padding_support ) { - $padding_value = _wp_array_get( $block_attributes, array( 'style', 'spacing', 'padding' ), null ); + $block_styles = isset( $block_attributes['style'] ) ? $block_attributes['style'] : null; - if ( is_array( $padding_value ) ) { - foreach ( $padding_value as $key => $value ) { - $styles[] = sprintf( 'padding-%s: %s;', $key, $value ); - } - } elseif ( null !== $padding_value ) { - $styles[] = sprintf( 'padding: %s;', $padding_value ); - } + if ( ! $block_styles ) { + return $attributes; } - if ( $has_margin_support ) { - $margin_value = _wp_array_get( $block_attributes, array( 'style', 'spacing', 'margin' ), null ); + $style_engine = WP_Style_Engine_Gutenberg::get_instance(); + $spacing_block_styles = array(); + $spacing_block_styles['padding'] = $has_padding_support ? _wp_array_get( $block_styles, array( 'spacing', 'padding' ), null ) : null; + $spacing_block_styles['margin'] = $has_margin_support ? _wp_array_get( $block_styles, array( 'spacing', 'margin' ), null ) : null; + $inline_styles = $style_engine->generate( + array( 'spacing' => $spacing_block_styles ), + array( + 'inline' => true, + ) + ); - if ( is_array( $margin_value ) ) { - foreach ( $margin_value as $key => $value ) { - $styles[] = sprintf( 'margin-%s: %s;', $key, $value ); - } - } elseif ( null !== $margin_value ) { - $styles[] = sprintf( 'margin: %s;', $margin_value ); - } + if ( ! empty( $inline_styles ) ) { + $attributes['style'] = $inline_styles; } - return empty( $styles ) ? array() : array( 'style' => implode( ' ', $styles ) ); + return $attributes; } /** diff --git a/lib/load.php b/lib/load.php index c6619708df3df..b72686b1f4c42 100644 --- a/lib/load.php +++ b/lib/load.php @@ -121,6 +121,11 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/global-styles.php'; require __DIR__ . '/pwa.php'; +// TODO: Move this to be loaded from the style engine package, via the build directory. +// Part of the build process should be to copy the PHP file to the correct location, +// similar to the loading behaviour in `blocks.php`. +require __DIR__ . '/style-engine/class-wp-style-engine-gutenberg.php'; + require __DIR__ . '/block-supports/elements.php'; require __DIR__ . '/block-supports/colors.php'; require __DIR__ . '/block-supports/typography.php'; diff --git a/lib/style-engine/class-wp-style-engine-gutenberg.php b/lib/style-engine/class-wp-style-engine-gutenberg.php new file mode 100644 index 0000000000000..d80ed2e0440e0 --- /dev/null +++ b/lib/style-engine/class-wp-style-engine-gutenberg.php @@ -0,0 +1,173 @@ + the key that represents a valid CSS property, e.g., "margin" or "border". + * - path => a path that accesses the corresponding style value in the block style object. + * - value_func => a function to generate an array of valid CSS rules for a particular style object. + * For example, `'padding' => 'array( 'top' => '1em' )` will return `array( 'padding-top' => '1em' )` + */ + const BLOCK_STYLE_DEFINITIONS_METADATA = array( + 'spacing' => array( + 'padding' => array( + 'property_key' => 'padding', + 'path' => array( 'spacing', 'padding' ), + 'value_func' => 'static::get_css_box_rules', + ), + 'margin' => array( + 'property_key' => 'margin', + 'path' => array( 'spacing', 'margin' ), + 'value_func' => 'static::get_css_box_rules', + ), + ), + ); + + /** + * Utility method to retrieve the main instance of the class. + * + * The instance will be created if it does not exist yet. + * + * @return WP_Style_Engine_Gutenberg The main instance. + */ + public static function get_instance() { + if ( null === self::$instance ) { + self::$instance = new self(); + } + + return self::$instance; + } + + /** + * Returns a CSS ruleset based on the instructions in BLOCK_STYLE_DEFINITIONS_METADATA. + * + * @param string|array $style_value A single raw Gutenberg style attributes value for a CSS property. + * @param array $path An array of strings representing a path to the style value. + * + * @return array A CSS ruleset compatible with generate(). + */ + protected function get_block_style_css_rules( $style_value, $path ) { + $style_definition = _wp_array_get( static::BLOCK_STYLE_DEFINITIONS_METADATA, $path, null ); + + if ( ! empty( $style_definition ) ) { + if ( + isset( $style_definition['value_func'] ) && + is_callable( $style_definition['value_func'] ) + ) { + return call_user_func( $style_definition['value_func'], $style_value, $style_definition['property_key'] ); + } + } + + return array(); + } + + /** + * Returns an CSS ruleset. + * Styles are bundled based on the instructions in BLOCK_STYLE_DEFINITIONS_METADATA. + * + * @param array $block_styles An array of styles from a block's attributes. + * @param array $options = array( + * 'inline' => (boolean) Whether to return inline CSS rules destined to be inserted in an HTML `style` attribute. + * 'path' => (array) Specify a block style to generate, otherwise it'll try all in BLOCK_STYLE_DEFINITIONS_METADATA. + * );. + * + * @return string A CSS ruleset formatted to be placed in an HTML `style` attribute. + */ + public function generate( $block_styles, $options = array() ) { + $output = ''; + + if ( empty( $block_styles ) ) { + return $output; + } + + $rules = array(); + + // If a path to a specific block style is defined, only return rules for that style. + if ( isset( $options['path'] ) && is_array( $options['path'] ) ) { + $style_value = _wp_array_get( $block_styles, $options['path'], null ); + if ( empty( $style_value ) ) { + return $output; + } + $rules = array_merge( $rules, $this->get_block_style_css_rules( $style_value, $options['path'] ) ); + } else { + // Otherwise build them all. + foreach ( self::BLOCK_STYLE_DEFINITIONS_METADATA as $definition_group ) { + foreach ( $definition_group as $style_definition ) { + $style_value = _wp_array_get( $block_styles, $style_definition['path'], null ); + if ( empty( $style_value ) ) { + continue; + } + $rules = array_merge( $rules, $this->get_block_style_css_rules( $style_value, $style_definition['path'] ) ); + } + } + } + + if ( ! empty( $rules ) ) { + // Generate inline style rules. + if ( isset( $options['inline'] ) && true === $options['inline'] ) { + foreach ( $rules as $rule => $value ) { + $filtered_css = esc_html( safecss_filter_attr( "{$rule}:{$value}" ) ); + if ( ! empty( $filtered_css ) ) { + $output .= $filtered_css . ';'; + } + } + } + } + + return $output; + } + + /** + * Returns a CSS ruleset for box model styles such as margins, padding, and borders. + * + * @param string|array $style_value A single raw Gutenberg style attributes value for a CSS property. + * @param string $style_property The CSS property for which we're creating a rule. + * + * @return array The class name for the added style. + */ + public static function get_css_box_rules( $style_value, $style_property ) { + $rules = array(); + + if ( ! $style_value ) { + return $rules; + } + + if ( is_array( $style_value ) ) { + foreach ( $style_value as $key => $value ) { + $rules[ "$style_property-$key" ] = $value; + } + } else { + $rules[ $style_property ] = $style_value; + } + return $rules; + } +} diff --git a/phpunit/style-engine/class-wp-style-engine-gutenberg-test.php b/phpunit/style-engine/class-wp-style-engine-gutenberg-test.php new file mode 100644 index 0000000000000..6407d6e74ea37 --- /dev/null +++ b/phpunit/style-engine/class-wp-style-engine-gutenberg-test.php @@ -0,0 +1,156 @@ +generate( + $block_styles, + $options + ); + $this->assertSame( $expected_output, $generated_styles ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_block_styles_fixtures() { + return array( + 'default_return_value' => array( + 'block_styles' => array(), + 'options' => null, + 'expected_output' => '', + ), + + 'inline_invalid_block_styles_empty' => array( + 'block_styles' => array(), + 'options' => array( + 'path' => array( 'spacing', 'padding' ), + 'inline' => true, + ), + 'expected_output' => '', + ), + + 'inline_invalid_block_styles_unknown_style' => array( + 'block_styles' => array( + 'pageBreakAfter' => 'verso', + ), + 'options' => array( + 'inline' => true, + ), + 'expected_output' => '', + ), + + 'inline_invalid_block_styles_unknown_definition' => array( + 'block_styles' => array( + 'pageBreakAfter' => 'verso', + ), + 'options' => array( + 'path' => array( 'pageBreakAfter', 'verso' ), + 'inline' => true, + ), + 'expected_output' => '', + ), + + 'inline_invalid_block_styles_unknown_property' => array( + 'block_styles' => array( + 'spacing' => array( + 'gap' => '1000vw', + ), + ), + 'options' => array( + 'path' => array( 'spacing', 'padding' ), + 'inline' => true, + ), + 'expected_output' => '', + ), + + 'inline_invalid_multiple_style_unknown_property' => array( + 'block_styles' => array( + 'spacing' => array( + 'gavin' => '1000vw', + ), + ), + 'options' => array( + 'inline' => true, + ), + 'expected_output' => '', + ), + + 'inline_valid_single_style_string' => array( + 'block_styles' => array( + 'spacing' => array( + 'margin' => '111px', + ), + ), + 'options' => array( + 'path' => array( 'spacing', 'margin' ), + 'inline' => true, + ), + 'expected_output' => 'margin:111px;', + ), + + 'inline_valid_single_style' => array( + 'block_styles' => array( + 'spacing' => array( + 'padding' => array( + 'top' => '42px', + 'left' => '2%', + 'bottom' => '44px', + 'right' => '5rem', + ), + 'margin' => array( + 'top' => '12rem', + 'left' => '2vh', + 'bottom' => '2px', + 'right' => '10em', + ), + ), + ), + 'options' => array( + 'path' => array( 'spacing', 'padding' ), + 'inline' => true, + ), + 'expected_output' => 'padding-top:42px;padding-left:2%;padding-bottom:44px;padding-right:5rem;', + ), + + 'inline_valid_multiple_style' => array( + 'block_styles' => array( + 'spacing' => array( + 'padding' => array( + 'top' => '42px', + 'left' => '2%', + 'bottom' => '44px', + 'right' => '5rem', + ), + 'margin' => array( + 'top' => '12rem', + 'left' => '2vh', + 'bottom' => '2px', + 'right' => '10em', + ), + ), + ), + 'options' => array( + 'inline' => true, + ), + 'expected_output' => 'padding-top:42px;padding-left:2%;padding-bottom:44px;padding-right:5rem;margin-top:12rem;margin-left:2vh;margin-bottom:2px;margin-right:10em;', + ), + ); + } +}