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

Backport global styles custom css changes #3896

Closed
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
baa9ab4
Add initial backports
glendaviesnz Jan 31, 2023
b314cf3
Test that invalid CSS is rejected
glendaviesnz Feb 1, 2023
f0a244b
Add tests for global styles custom CSS
glendaviesnz Feb 1, 2023
cecd9ca
fix function name
glendaviesnz Feb 1, 2023
5a11f8a
Fix class naming issue
glendaviesnz Feb 1, 2023
bd7fe93
Fix linting issue
glendaviesnz Feb 1, 2023
76d3f99
Add tests for global custom CSS
glendaviesnz Feb 1, 2023
c446376
add debugging to unit test
glendaviesnz Feb 1, 2023
8f7afa8
check for array or object
glendaviesnz Feb 1, 2023
1f74f52
Remove debugging
glendaviesnz Feb 1, 2023
6787594
Add in check for edit-css capabilities
glendaviesnz Feb 1, 2023
e4d53a2
Add tests for custom CSS user capabilities
glendaviesnz Feb 1, 2023
400d437
Fix permissions issue on multisite test
glendaviesnz Feb 1, 2023
eec32b6
Put permissions assignment in the right place!
glendaviesnz Feb 1, 2023
c9fcd81
Set permissions in tests instead of setup to prevent changing permiss…
glendaviesnz Feb 1, 2023
7450db9
Fixes from code review
glendaviesnz Feb 1, 2023
d6c6e65
Remove unnecessary description
glendaviesnz Feb 1, 2023
20442c9
But make user definition that was removed by mistake
glendaviesnz Feb 1, 2023
d055fa5
Change ordering of $request[styles] assignment to $config[styles]
glendaviesnz Feb 1, 2023
d37e7f2
Change cache key and add doc comments
glendaviesnz Feb 1, 2023
d71a7fe
match cache key to method name
glendaviesnz Feb 1, 2023
b658684
Move check for theme.json to top of function
glendaviesnz Feb 1, 2023
6a986eb
Merge branch 'trunk' into backport/global-styles-custom-css
felixarntz Feb 2, 2023
7261356
Update inline comment.
felixarntz Feb 2, 2023
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
6 changes: 6 additions & 0 deletions src/wp-includes/block-editor.php
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,12 @@ function get_block_editor_settings( array $custom_settings, $block_editor_contex
$block_classes['css'] = $actual_css;
$global_styles[] = $block_classes;
}
// Add the custom CSS as separate style sheet so any invalid CSS entered by users does not break other global styles.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment is too long on one line.

Suggested change
// Add the custom CSS as separate style sheet so any invalid CSS entered by users does not break other global styles.
/*
* Add the custom CSS as separate style sheet so any invalid CSS entered
* by users does not break other global styles.
*/

Helps to improve readability.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two things:

  1. I think it needs an "a" before the word "separate".
  2. Also, isn't "style sheet" one word, e.g. stylesheet?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can take care of committing this and address this feedback prior to the commit.

$editor_settings['styles'][] = array(
'css' => wp_get_global_styles_custom_css(),
'__unstableType' => 'user',
'isGlobalStyles' => true,
);
} else {
// If there is no `theme.json` file, ensure base layout styles are still available.
$block_classes = array(
Expand Down
33 changes: 32 additions & 1 deletion src/wp-includes/class-wp-theme-json.php
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,7 @@ class WP_Theme_JSON {
'textDecoration' => null,
'textTransform' => null,
),
'css' => null,
);

/**
Expand Down Expand Up @@ -1005,6 +1006,31 @@ public function get_stylesheet( $types = array( 'variables', 'styles', 'presets'
return $stylesheet;
}

/**
* Returns the global styles custom css.
*
* @since 6.2.0
*
* @return string
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @return string
* @return string The global styles custom CSS.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hellofromtonya Addressed this as part of follow up 2cf1b08.

*/
public function get_custom_css() {
// Add the global styles root CSS.
$stylesheet = _wp_array_get( $this->theme_json, array( 'styles', 'css' ), '' );

// Add the global styles block CSS.
if ( isset( $this->theme_json['styles']['blocks'] ) ) {
foreach ( $this->theme_json['styles']['blocks'] as $name => $node ) {
$custom_block_css = _wp_array_get( $this->theme_json, array( 'styles', 'blocks', $name, 'css' ) );
if ( $custom_block_css ) {
$selector = static::$blocks_metadata[ $name ]['selector'];
$stylesheet .= $this->process_blocks_custom_css( $custom_block_css, $selector );
}
}
}

return $stylesheet;
}

/**
* Returns the page templates of the active theme.
*
Expand Down Expand Up @@ -2740,7 +2766,12 @@ public static function remove_insecure_properties( $theme_json ) {
continue;
}

$output = static::remove_insecure_styles( $input );
// The global styles custom CSS is not sanitized, but can only be edited by users with 'edit_css' capability.
if ( isset( $input['css'] ) && current_user_can( 'edit_css' ) ) {
$output = $input;
} else {
$output = static::remove_insecure_styles( $input );
}

/*
* Get a reference to element name from path.
Expand Down
3 changes: 3 additions & 0 deletions src/wp-includes/default-filters.php
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,9 @@
add_action( 'wp_enqueue_scripts', 'wp_enqueue_global_styles' );
add_action( 'wp_footer', 'wp_enqueue_global_styles', 1 );

// Global styles custom CSS.
add_action( 'wp_enqueue_scripts', 'wp_enqueue_global_styles_custom_css' );

// Block supports, and other styles parsed and stored in the Style Engine.
add_action( 'wp_enqueue_scripts', 'wp_enqueue_stored_styles' );
add_action( 'wp_footer', 'wp_enqueue_stored_styles', 1 );
Expand Down
34 changes: 34 additions & 0 deletions src/wp-includes/global-styles-and-settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,39 @@ function wp_get_global_stylesheet( $types = array() ) {
return $stylesheet;
}

/**
* Gets the global styles custom css from theme.json.
*
* @since 6.2.0 Added custom css support to theme.json.
glendaviesnz marked this conversation as resolved.
Show resolved Hide resolved
*
* @return string Stylesheet.
*/
function wp_get_global_styles_custom_css() {
// Ignore cache when `WP_DEBUG` is enabled, so it doesn't interfere with the theme developers workflow.
$can_use_cached = ! WP_DEBUG;
felixarntz marked this conversation as resolved.
Show resolved Hide resolved
$cache_key = 'gutenberg_get_global_custom_css';
felixarntz marked this conversation as resolved.
Show resolved Hide resolved
$cache_group = 'theme_json';
glendaviesnz marked this conversation as resolved.
Show resolved Hide resolved
if ( $can_use_cached ) {
$cached = wp_cache_get( $cache_key, $cache_group );
if ( $cached ) {
return $cached;
}
}

if ( ! wp_theme_has_theme_json() ) {
return '';
}
glendaviesnz marked this conversation as resolved.
Show resolved Hide resolved

$tree = WP_Theme_JSON_Resolver::get_merged_data();
$stylesheet = $tree->get_custom_css();

if ( $can_use_cached ) {
wp_cache_set( $cache_key, $stylesheet, $cache_group );
}

return $stylesheet;
}

/**
* Returns a string containing the SVGs to be referenced as filters (duotone).
*
Expand Down Expand Up @@ -369,5 +402,6 @@ function wp_clean_theme_json_cache() {
wp_cache_delete( 'wp_get_global_stylesheet', 'theme_json' );
wp_cache_delete( 'wp_get_global_settings_custom', 'theme_json' );
wp_cache_delete( 'wp_get_global_settings_theme', 'theme_json' );
wp_cache_delete( 'wp_get_global_custom_css', 'theme_json' );
glendaviesnz marked this conversation as resolved.
Show resolved Hide resolved
WP_Theme_JSON_Resolver::clean_cached_data();
}
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,10 @@ public function update_item( $request ) {
}

$changes = $this->prepare_item_for_database( $request );
if ( is_wp_error( $changes ) ) {
return $changes;
}

$result = wp_update_post( wp_slash( (array) $changes ), true, false );
if ( is_wp_error( $result ) ) {
return $result;
Expand All @@ -290,9 +294,10 @@ public function update_item( $request ) {
* Prepares a single global styles config for update.
*
* @since 5.9.0
* @since 6.2.0 Added validation of styles.css property.
*
* @param WP_REST_Request $request Request object.
* @return stdClass Changes to pass to wp_update_post.
* @return stdClass|WP_Error Prepared item on success. WP_Error on when the custom CSS is not valid.
*/
protected function prepare_item_for_database( $request ) {
$changes = new stdClass();
Expand All @@ -313,6 +318,12 @@ protected function prepare_item_for_database( $request ) {
$config = array();
if ( isset( $request['styles'] ) ) {
hellofromtonya marked this conversation as resolved.
Show resolved Hide resolved
$config['styles'] = $request['styles'];
if ( isset( $config['styles']['css'] ) ) {
$css_validation_result = $this->validate_custom_css( $config['styles']['css'] );
if ( is_wp_error( $css_validation_result ) ) {
return $css_validation_result;
}
}
} elseif ( isset( $existing_config['styles'] ) ) {
$config['styles'] = $existing_config['styles'];
}
Expand Down Expand Up @@ -657,4 +668,25 @@ public function get_theme_items( $request ) {

return $response;
}

/**
* Validate style.css as valid CSS.
*
* Currently just checks for invalid markup.
*
* @since 6.2.0
*
* @param string $css CSS to validate.
* @return true|WP_Error True if the input was validated, otherwise WP_Error.
*/
private function validate_custom_css( $css ) {
if ( preg_match( '#</?\w+#', $css ) ) {
return new WP_Error(
'rest_custom_css_illegal_markup',
__( 'Markup is not allowed in CSS.' ),
array( 'status' => 400 )
);
}
return true;
}
}
21 changes: 21 additions & 0 deletions src/wp-includes/script-loader.php
Original file line number Diff line number Diff line change
Expand Up @@ -2454,6 +2454,27 @@ function wp_enqueue_global_styles() {
wp_add_global_styles_for_blocks();
}

/**
* Enqueues the global styles custom css defined via theme.json.
*
* @since 6.2.0
*/
function wp_enqueue_global_styles_custom_css() {
if ( ! wp_is_block_theme() ) {
return;
}

// Don't enqueue Customizer's custom CSS separately.
remove_action( 'wp_head', 'wp_custom_css_cb', 101 );
felixarntz marked this conversation as resolved.
Show resolved Hide resolved

$custom_css = wp_get_custom_css();
$custom_css .= wp_get_global_styles_custom_css();

if ( ! empty( $custom_css ) ) {
wp_add_inline_style( 'global-styles', $custom_css );
}
}

/**
* Renders the SVG filters supplied by theme.json.
*
Expand Down
39 changes: 39 additions & 0 deletions tests/phpunit/tests/rest-api/rest-global-styles-controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -532,4 +532,43 @@ public function test_assign_edit_css_action_admin() {
$this->assertArrayHasKey( 'https://api.w.org/action-edit-css', $links );
}
}

/**
* @covers WP_REST_Global_Styles_Controller::update_item
* @ticket 57536
*/
public function test_update_item_valid_styles_css() {
wp_set_current_user( self::$admin_id );
if ( is_multisite() ) {
grant_super_admin( self::$admin_id );
}
$request = new WP_REST_Request( 'PUT', '/wp/v2/global-styles/' . self::$global_styles_id );
$request->set_body_params(
array(
'styles' => array( 'css' => 'body { color: red; }' ),
)
);
$response = rest_get_server()->dispatch( $request );
$data = $response->get_data();
$this->assertSame( 'body { color: red; }', $data['styles']['css'] );
}

/**
* @covers WP_REST_Global_Styles_Controller::update_item
* @ticket 57536
*/
public function test_update_item_invalid_styles_css() {
wp_set_current_user( self::$admin_id );
if ( is_multisite() ) {
grant_super_admin( self::$admin_id );
}
$request = new WP_REST_Request( 'PUT', '/wp/v2/global-styles/' . self::$global_styles_id );
$request->set_body_params(
array(
'styles' => array( 'css' => '<p>test</p> body { color: red; }' ),
)
);
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_custom_css_illegal_markup', $response, 400 );
}
}
110 changes: 110 additions & 0 deletions tests/phpunit/tests/theme/wpThemeJson.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,27 @@
*/
class Tests_Theme_wpThemeJson extends WP_UnitTestCase {

/**
* Administrator ID.
*
* @var int
*/
private static $administrator_id;

public static function set_up_before_class() {
parent::set_up_before_class();

static::$administrator_id = self::factory()->user->create(
array(
'role' => 'administrator',
)
);

if ( is_multisite() ) {
grant_super_admin( self::$administrator_id );
}
}
glendaviesnz marked this conversation as resolved.
Show resolved Hide resolved

/**
* @ticket 52991
* @ticket 54336
Expand Down Expand Up @@ -4505,4 +4526,93 @@ public function test_get_shadow_styles_for_blocks() {

$this->assertSame( $expected_styles, $theme_json->get_stylesheet() );
}

/**
* @ticket 57536
*/
public function test_get_custom_css_handles_global_custom_css() {
$theme_json = new WP_Theme_JSON(
array(
'version' => WP_Theme_JSON::LATEST_SCHEMA,
'styles' => array(
'css' => 'body { color:purple; }',
),
)
);
$custom_css = 'body { color:purple; }';
$this->assertSame( $custom_css, $theme_json->get_custom_css() );
}

/**
* Tests that custom CSS is kept for users with correct capabilities and removed for others.
*
* @ticket 57536
*
* @dataProvider data_custom_css_for_user_caps
*
hellofromtonya marked this conversation as resolved.
Show resolved Hide resolved
* @param string $user_property The property name for current user.
* @param array $expected Expected results.
*/
public function test_custom_css_for_user_caps( $user_property, array $expected ) {
wp_set_current_user( static::${$user_property} );

$actual = WP_Theme_JSON::remove_insecure_properties(
array(
'version' => WP_Theme_JSON::LATEST_SCHEMA,
'styles' => array(
'css' => 'body { color:purple; }',
'blocks' => array(
'core/separator' => array(
'color' => array(
'background' => 'blue',
),
),
),
),
)
);

$this->assertSameSetsWithIndex( $expected, $actual );
}

/**
* Data provider.
*
* @return array[]
*/
public function data_custom_css_for_user_caps() {
return array(
'allows custom css for users with caps' => array(
'user_property' => 'administrator_id',
'expected' => array(
'version' => WP_Theme_JSON::LATEST_SCHEMA,
'styles' => array(
'css' => 'body { color:purple; }',
'blocks' => array(
'core/separator' => array(
'color' => array(
'background' => 'blue',
),
),
),
),
),
),
'removes custom css for users without caps' => array(
'user_property' => 'user_id',
'expected' => array(
'version' => WP_Theme_JSON::LATEST_SCHEMA,
'styles' => array(
'blocks' => array(
'core/separator' => array(
'color' => array(
'background' => 'blue',
),
),
),
),
),
),
);
}
}