Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 142 additions & 0 deletions src/wp-includes/block-supports/transform.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
<?php
/**
* Transform block support flag.
*
* Each transform feature outputs its own individual CSS property, such as
* `rotate`, never the `transform` shorthand, so features combine with each
* other and with any transform a block already applies.
*
* @package WordPress
* @since 7.3.0
*/

/**
* Registers the style block attribute for block types that support it.
*
* @since 7.3.0
* @access private
*
* @param WP_Block_Type $block_type Block Type.
*/
function wp_register_transform_support( $block_type ) {
if ( ! $block_type->attributes ) {
$block_type->attributes = array();
}

// Check for existing style attribute definition e.g. from block.json.
if ( array_key_exists( 'style', $block_type->attributes ) ) {
return;
}

if ( block_has_support( $block_type, 'transform', false ) ) {
$block_type->attributes['style'] = array(
'type' => 'object',
);
}
}

/**
* Renders a block's transform styles.
*
* Features are output as inline styles on the block wrapper. When the block
* has a transform feature selector, e.g. `.wp-block-image img`, they are
* output instead to the block supports stylesheet under that selector, so the
* wrapper and anything else in it, such as a caption, are left as they are.
* A feature that skips serialization is left for the block to apply.
*
* State styles, such as `:hover` or `@mobile`, are rendered by the block
* states support.
*
* @since 7.3.0
* @access private
*
* @param string $block_content Rendered block content.
* @param array $block Block object.
* @return string Filtered block content.
*/
function wp_render_transform_support( $block_content, $block ) {
if ( empty( $block_content ) || empty( $block['blockName'] ) ) {
return $block_content;
}

$transform = $block['attrs']['style']['transform'] ?? null;
if ( empty( $transform ) || ! is_array( $transform ) ) {
return $block_content;
}

$block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] );
if ( ! $block_type || ! block_has_support( $block_type, 'transform', false ) ) {
return $block_content;
}

$inline_styles = array();
$selector_styles = array();

foreach ( array( 'rotate' ) as $feature ) {
if (
! isset( $transform[ $feature ] ) ||
! block_has_support( $block_type, array( 'transform', $feature ), false ) ||
wp_should_skip_block_supports_serialization( $block_type, 'transform', $feature )
) {
continue;
}

$selector = wp_get_block_css_selector( $block_type, array( 'transform', $feature ) ) ?? wp_get_block_css_selector( $block_type, 'transform' );
if ( $selector ) {
$selector_styles[ $selector ][ $feature ] = $transform[ $feature ];
} else {
$inline_styles[ $feature ] = $transform[ $feature ];
}
}

$processor = new WP_HTML_Tag_Processor( $block_content );
if ( ! $processor->next_tag() ) {
return $block_content;
}

$inline_css = wp_style_engine_get_styles( array( 'transform' => $inline_styles ) )['css'] ?? '';
if ( ! empty( $inline_css ) ) {
$existing_style = $processor->get_attribute( 'style' );
$existing_style = is_string( $existing_style ) && '' !== trim( $existing_style ) ? rtrim( trim( $existing_style ), ';' ) . ';' : '';
$processor->set_attribute( 'style', $existing_style . $inline_css );
}

$css_rules = array();
foreach ( $selector_styles as $selector => $styles ) {
$declarations = wp_style_engine_get_styles( array( 'transform' => $styles ) )['declarations'] ?? array();
if ( ! empty( $declarations ) ) {
$css_rules[] = array(
'selector' => $selector,
'declarations' => $declarations,
);
}
}

if ( ! empty( $css_rules ) ) {
$class_name = wp_unique_id_from_values( $css_rules, 'wp-transform-' );
foreach ( $css_rules as $index => $css_rule ) {
$css_rules[ $index ]['selector'] = wp_build_state_selector( ".$class_name", $css_rule['selector'], '', $block['blockName'] );
}

wp_style_engine_get_stylesheet_from_css_rules(
$css_rules,
array(
'context' => 'block-supports',
'prettify' => false,
)
);
$processor->add_class( $class_name );
}

return $processor->get_updated_html();
}

// Register the block support.
WP_Block_Supports::get_instance()->register(
'transform',
array(
'register_attribute' => 'wp_register_transform_support',
)
);

add_filter( 'render_block', 'wp_render_transform_support', 10, 2 );
29 changes: 25 additions & 4 deletions src/wp-includes/class-wp-theme-json.php
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ class WP_Theme_JSON {
* Added `text-indent` property.
* @since 7.1.0 Added `min-width` and `text-shadow`.
* @since 7.2.0 Added `background-clip`.
* @since 7.3.0 Added `rotate`.
* @var array
*/
const PROPERTIES_METADATA = array(
Expand Down Expand Up @@ -318,6 +319,7 @@ class WP_Theme_JSON {
'padding-right' => array( 'spacing', 'padding', 'right' ),
'padding-bottom' => array( 'spacing', 'padding', 'bottom' ),
'padding-left' => array( 'spacing', 'padding', 'left' ),
'rotate' => array( 'transform', 'rotate' ),
'--wp--style--root--padding' => array( 'spacing', 'padding' ),
'--wp--style--root--padding-top' => array( 'spacing', 'padding', 'top' ),
'--wp--style--root--padding-right' => array( 'spacing', 'padding', 'right' ),
Expand Down Expand Up @@ -435,6 +437,7 @@ class WP_Theme_JSON {
* Added support for `background.gradient`, `dimensions.minWidth` and `blockVisibility.allowEditing`.
* @since 7.2.0 Added support for `typography.textShadow`, `typography.textShadowPresets`,
* and `typography.defaultTextShadowPresets`.
* @since 7.3.0 Added support for `transform.rotate`.
* @var array
*/
const VALID_SETTINGS = array(
Expand Down Expand Up @@ -512,6 +515,9 @@ class WP_Theme_JSON {
'presets' => null,
'defaultPresets' => null,
),
'transform' => array(
'rotate' => null,
),
'typography' => array(
'fluid' => null,
'customFontSize' => null,
Expand Down Expand Up @@ -588,6 +594,7 @@ class WP_Theme_JSON {
* @since 7.1.0 Added support for `background.gradient`,`dimensions.minWidth`,
* and `typography.textShadow`.
* @since 7.2.0 Added support for `background.backgroundClip`.
* @since 7.3.0 Added support for `transform.rotate`, for blocks and block style variations only.
* @var array
*/
const VALID_STYLES = array(
Expand Down Expand Up @@ -637,6 +644,9 @@ class WP_Theme_JSON {
'padding' => null,
'blockGap' => null,
),
'transform' => array(
'rotate' => null,
),
'typography' => array(
'fontFamily' => null,
'fontSize' => null,
Expand Down Expand Up @@ -1365,18 +1375,22 @@ protected static function sanitize( $input, $valid_block_names, $valid_element_n
* - block level elements: `$schema['styles']['blocks']['core/button']['elements']['link'][':hover']`.
* - block responsive elements: `$schema['styles']['blocks']['core/button']['@tablet']['elements']['link'][':hover']`.
*/
// Transforms are only for blocks and block style variations, not the root or elements.
$element_styles = $styles_non_top_level;
unset( $element_styles['transform'] );

foreach ( $valid_element_names as $element ) {
$schema_styles_elements[ $element ] = $styles_non_top_level;
$schema_styles_elements[ $element ] = $element_styles;

if ( isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element ] ) ) {
foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element ] as $pseudo_selector ) {
$schema_styles_elements[ $element ][ $pseudo_selector ] = $styles_non_top_level;
$schema_styles_elements[ $element ][ $pseudo_selector ] = $element_styles;
}
}

// Add responsive breakpoint states for elements.
foreach ( array_keys( $responsive_media_queries ) as $breakpoint_state ) {
$schema_styles_elements[ $element ][ $breakpoint_state ] = $styles_non_top_level;
$schema_styles_elements[ $element ][ $breakpoint_state ] = $element_styles;
}
}

Expand Down Expand Up @@ -1507,6 +1521,7 @@ protected static function sanitize( $input, $valid_block_names, $valid_element_n
$schema['settings'] = static::VALID_SETTINGS;
$schema['settings']['blocks'] = $schema_settings_blocks;
$schema['settings']['typography']['fontFamilies'] = static::schema_in_root_and_per_origin( static::FONT_FAMILY_SCHEMA );
unset( $schema['styles']['transform'] );

/*
* Add block style variation states to the top-level styles schema.
Expand All @@ -1524,7 +1539,8 @@ protected static function sanitize( $input, $valid_block_names, $valid_element_n
* theme.json files are unaffected.
*/
if ( ! empty( $input['blockTypes'] ) && is_array( $input['blockTypes'] ) ) {
$variation_pseudo_selectors = array();
$schema['styles']['transform'] = static::VALID_STYLES['transform'];
$variation_pseudo_selectors = array();
foreach ( $input['blockTypes'] as $variation_block_type ) {
if ( is_string( $variation_block_type ) && isset( static::VALID_BLOCK_PSEUDO_SELECTORS[ $variation_block_type ] ) ) {
$variation_pseudo_selectors = array_merge(
Expand Down Expand Up @@ -3264,6 +3280,11 @@ protected static function compute_style_properties( $styles, $settings = array()
continue;
}

// Mirrors the style engine, which outputs a single angle only.
if ( 'rotate' === $css_property && ( ! is_string( $value ) || ! preg_match( WP_Style_Engine::VALID_ANGLE_PATTERN, $value ) ) ) {
continue;
}

// Calculates fluid typography rules where available.
if ( 'font-size' === $css_property ) {
/*
Expand Down
9 changes: 9 additions & 0 deletions src/wp-includes/kses.php
Original file line number Diff line number Diff line change
Expand Up @@ -3727,6 +3727,7 @@ function kses_init() {
* and URLs in the SVG element reference properties.
* @since 7.2.0 Added support for CSS anchor positioning properties and `text-shadow`.
* Added support for `background-clip`.
* @since 7.3.0 Added support for `rotate`, limited to a single angle.
*
* @param string $css A string of CSS rules, decoded from an HTML `style` attribute.
* @param string $deprecated Not used.
Expand Down Expand Up @@ -3976,6 +3977,7 @@ function safecss_filter_attr( $css, $deprecated = '' ) {

'transform',
'transform-origin',
'rotate',

'pointer-events',
'visibility',
Expand Down Expand Up @@ -4047,6 +4049,7 @@ function safecss_filter_attr( $css, $deprecated = '' ) {
$found = false;
$url_attr = false;
$gradient_attr = false;
$angle_attr = false;
$is_custom_var = false;

if ( ! str_contains( $css_item, ':' ) ) {
Expand All @@ -4065,6 +4068,7 @@ function safecss_filter_attr( $css, $deprecated = '' ) {
$found = true;
$url_attr = in_array( $css_selector, $css_url_data_types, true );
$gradient_attr = in_array( $css_selector, $css_gradient_data_types, true );
$angle_attr = 'rotate' === $css_selector;
}

if ( $is_custom_var ) {
Expand Down Expand Up @@ -4146,6 +4150,11 @@ function safecss_filter_attr( $css, $deprecated = '' ) {
*/
$allow_css = 0 === preg_match( '%[\\\(&=}]|/\*%', $css_test_string );

// Only allow a single angle for `rotate`, mirroring the style engine.
if ( $allow_css && $angle_attr ) {
$allow_css = 1 === preg_match( WP_Style_Engine::VALID_ANGLE_PATTERN, trim( $parts[1] ) );
}

/**
* Filters the check for unsafe CSS in `safecss_filter_attr`.
*
Expand Down
38 changes: 38 additions & 0 deletions src/wp-includes/style-engine/class-wp-style-engine.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
* @since 6.7.0 Added support for typography.writingMode.
* @since 7.0.0 Added support for typography.textIndent.
* @since 7.2.0 Added support for typography.textShadow.
* @since 7.3.0 Added support for transform.rotate.
*/
#[AllowDynamicProperties]
final class WP_Style_Engine {
Expand All @@ -39,6 +40,14 @@ final class WP_Style_Engine {
*/
const VALID_BACKGROUND_CLIP_VALUES = array( 'border-box', 'padding-box', 'content-box', 'text' );

/**
* Matches a single CSS angle: a number with a `deg`, `grad`, `rad` or `turn` unit, or `0`.
*
* @since 7.3.0
* @var string
*/
const VALID_ANGLE_PATTERN = '/^(?:[+-]?(?:\d+(?:\.\d+)?|\.\d+)(?:deg|grad|rad|turn)|0)$/iD';

/**
* Style definitions that contain the instructions to parse/output valid Gutenberg styles from a block's attributes.
*
Expand All @@ -59,6 +68,7 @@ final class WP_Style_Engine {
* @since 6.1.0
* @since 7.1.0 Added `background.gradient` property.
* @since 7.2.0 Added `background.backgroundClip` property.
* @since 7.3.0 Added `transform.rotate` property.
* @var array
*/
const BLOCK_STYLE_DEFINITIONS_METADATA = array(
Expand Down Expand Up @@ -303,6 +313,15 @@ final class WP_Style_Engine {
),
),
),
'transform' => array(
'rotate' => array(
'property_keys' => array(
'default' => 'rotate',
),
'value_func' => array( self::class, 'get_angle_css_declarations' ),
'path' => array( 'transform', 'rotate' ),
),
),
'typography' => array(
'fontSize' => array(
'property_keys' => array(
Expand Down Expand Up @@ -821,6 +840,25 @@ protected static function get_background_clip_css_declarations( $style_value, $s
return $css_declarations;
}

/**
* Style value parser that returns a CSS definition array for a property that takes a single angle.
*
* Lists, functions such as `calc()` or `var()`, and bare numbers are rejected.
*
* @since 7.3.0
*
* @param string $style_value A single raw style value from $block_styles array.
* @param array $style_definition A single style definition from BLOCK_STYLE_DEFINITIONS_METADATA.
* @return array<string, string> An associative array of CSS definitions, mapping properties to values.
*/
protected static function get_angle_css_declarations( $style_value, $style_definition ) {
if ( ! is_string( $style_value ) || ! preg_match( static::VALID_ANGLE_PATTERN, $style_value ) ) {
return array();
}

return array( $style_definition['property_keys']['default'] => $style_value );
}

/**
* Returns compiled CSS from CSS declarations.
*
Expand Down
1 change: 1 addition & 0 deletions src/wp-settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,7 @@
require ABSPATH . WPINC . '/block-supports/anchor.php';
require ABSPATH . WPINC . '/block-supports/block-visibility.php';
require ABSPATH . WPINC . '/block-supports/custom-css.php';
require ABSPATH . WPINC . '/block-supports/transform.php';
require ABSPATH . WPINC . '/block-supports/states.php';
require ABSPATH . WPINC . '/style-engine.php';
require ABSPATH . WPINC . '/style-engine/class-wp-style-engine.php';
Expand Down
Loading
Loading