Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Style Engine: block supports backend #39446

Merged
merged 12 commits into from
Mar 28, 2022
39 changes: 17 additions & 22 deletions lib/block-supports/spacing.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/**
Expand Down
5 changes: 5 additions & 0 deletions lib/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
173 changes: 173 additions & 0 deletions lib/style-engine/class-wp-style-engine-gutenberg.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
<?php
/**
* WP_Style_Engine_Gutenberg class
*
* Generates classnames and block styles.
*
* @package Gutenberg
*/

if ( class_exists( 'WP_Style_Engine_Gutenberg' ) ) {
return;
}

/**
* Singleton class representing the style engine.
*
* Consolidates rendering block styles to reduce duplication and streamline
* CSS styles generation.
*
* @since 6.0.0
*/
class WP_Style_Engine_Gutenberg {
/**
* Container for the main instance of the class.
*
* @var WP_Style_Engine_Gutenberg|null
*/
private static $instance = null;

/**
* Style definitions that contain the instructions to
* parse/output valid Gutenberg styles from a block's attributes.
* For every style definition, the follow properties are valid:
*
* - property_key => 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<string> $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 = '';
ramonjd marked this conversation as resolved.
Show resolved Hide resolved

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;
}
}
156 changes: 156 additions & 0 deletions phpunit/style-engine/class-wp-style-engine-gutenberg-test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
<?php
/**
* Tests the Style Engine class and associated functionality.
*
* @package Gutenberg
* @subpackage style-engine
*/

/**
* Tests for registering, storing and generating styles.
*/
class WP_Style_Engine_Gutenberg_Test extends WP_UnitTestCase {
/**
* Tests various manifestations of the $block_styles argument.
*
* @dataProvider data_block_styles_fixtures
*/
function test_generate_css( $block_styles, $options, $expected_output ) {
$style_engine = WP_Style_Engine_Gutenberg::get_instance();
$generated_styles = $style_engine->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;',
),
);
}
}