From baa9ab420d8b470506e919291bcc58f57e6cdfad Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Wed, 1 Feb 2023 12:35:45 +1300 Subject: [PATCH 01/23] Add initial backports --- src/wp-includes/block-editor.php | 6 ++++ src/wp-includes/class-wp-theme-json.php | 6 ++++ .../global-styles-and-settings.php | 3 +- ...class-wp-rest-global-styles-controller.php | 30 +++++++++++++++++++ 4 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/wp-includes/block-editor.php b/src/wp-includes/block-editor.php index 4a62837f49ea2..41ef518305b05 100644 --- a/src/wp-includes/block-editor.php +++ b/src/wp-includes/block-editor.php @@ -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. + $editor_settings['styles'][] = array( + 'css' => wp_get_global_stylesheet( array( 'custom-css' ) ), + '__unstableType' => 'user', + 'isGlobalStyles' => true, + ); } else { // If there is no `theme.json` file, ensure base layout styles are still available. $block_classes = array( diff --git a/src/wp-includes/class-wp-theme-json.php b/src/wp-includes/class-wp-theme-json.php index 71801df2af174..a846e9f41ee68 100644 --- a/src/wp-includes/class-wp-theme-json.php +++ b/src/wp-includes/class-wp-theme-json.php @@ -425,6 +425,7 @@ class WP_Theme_JSON { 'textDecoration' => null, 'textTransform' => null, ), + 'css' => null, ); /** @@ -1002,6 +1003,11 @@ public function get_stylesheet( $types = array( 'variables', 'styles', 'presets' $stylesheet .= $this->get_preset_classes( $setting_nodes, $origins ); } + // Load the custom CSS last so it has the highest specificity. + if ( in_array( 'custom-css', $types, true ) ) { + $stylesheet .= _wp_array_get( $this->theme_json, array( 'styles', 'css' ) ); + } + return $stylesheet; } diff --git a/src/wp-includes/global-styles-and-settings.php b/src/wp-includes/global-styles-and-settings.php index 5836bf15fadcc..ef6d6dd1a2730 100644 --- a/src/wp-includes/global-styles-and-settings.php +++ b/src/wp-includes/global-styles-and-settings.php @@ -126,6 +126,7 @@ function wp_get_global_styles( $path = array(), $context = array() ) { * * @since 5.9.0 * @since 6.1.0 Added 'base-layout-styles' support. + * @since 6.2.0 Support for custom-css type added. * * @param array $types Optional. Types of styles to load. * It accepts as values 'variables', 'presets', 'styles', 'base-layout-styles'. @@ -174,7 +175,7 @@ function wp_get_global_stylesheet( $types = array() ) { if ( empty( $types ) && ! $supports_theme_json ) { $types = array( 'variables', 'presets', 'base-layout-styles' ); } elseif ( empty( $types ) ) { - $types = array( 'variables', 'styles', 'presets' ); + $types = array( 'variables', 'styles', 'presets', 'custom-css' ); } /* diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php index b504e23c35ba6..e231da0428ed1 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php @@ -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; @@ -290,6 +294,7 @@ 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. @@ -313,6 +318,10 @@ protected function prepare_item_for_database( $request ) { $config = array(); if ( isset( $request['styles'] ) ) { $config['styles'] = $request['styles']; + $validate_custom_css = $this->validate_custom_css( $request['styles']['css'] ); + if ( is_wp_error( $validate_custom_css ) ) { + return $validate_custom_css; + } } elseif ( isset( $existing_config['styles'] ) ) { $config['styles'] = $existing_config['styles']; } @@ -657,4 +666,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( '# 400 ) + ); + } + return true; + } } From b314cf3b58010a9f1e7c4cb27269876f9cf6d319 Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Thu, 2 Feb 2023 09:32:51 +1300 Subject: [PATCH 02/23] Test that invalid CSS is rejected --- .../rest-global-styles-controller.php | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/phpunit/tests/rest-api/rest-global-styles-controller.php b/tests/phpunit/tests/rest-api/rest-global-styles-controller.php index 8fc86d2205c7f..9b0fee3559995 100644 --- a/tests/phpunit/tests/rest-api/rest-global-styles-controller.php +++ b/tests/phpunit/tests/rest-api/rest-global-styles-controller.php @@ -532,4 +532,38 @@ 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 ); + $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 ); + $request = new WP_REST_Request( 'PUT', '/wp/v2/global-styles/' . self::$global_styles_id ); + $request->set_body_params( + array( + 'styles' => array( 'css' => '

test

body { color: red; }' ), + ) + ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_custom_css_illegal_markup', $response, 400 ); + } + } From f0a244b2dcf28075af0b8cd111421322ac6e1a92 Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Thu, 2 Feb 2023 09:36:05 +1300 Subject: [PATCH 03/23] Add tests for global styles custom CSS --- src/wp-includes/block-editor.php | 2 +- src/wp-includes/class-wp-theme-json.php | 26 +++++- src/wp-includes/default-filters.php | 3 + .../global-styles-and-settings.php | 37 +++++++- src/wp-includes/script-loader.php | 20 ++++ tests/phpunit/tests/theme/wpThemeJson.php | 91 +++++++++++++++++++ 6 files changed, 173 insertions(+), 6 deletions(-) diff --git a/src/wp-includes/block-editor.php b/src/wp-includes/block-editor.php index 41ef518305b05..46531cbe526c7 100644 --- a/src/wp-includes/block-editor.php +++ b/src/wp-includes/block-editor.php @@ -410,7 +410,7 @@ function get_block_editor_settings( array $custom_settings, $block_editor_contex } // Add the custom CSS as separate style sheet so any invalid CSS entered by users does not break other global styles. $editor_settings['styles'][] = array( - 'css' => wp_get_global_stylesheet( array( 'custom-css' ) ), + 'css' => wp_get_global_styles_custom_css(), '__unstableType' => 'user', 'isGlobalStyles' => true, ); diff --git a/src/wp-includes/class-wp-theme-json.php b/src/wp-includes/class-wp-theme-json.php index a846e9f41ee68..b8ed6eeef882c 100644 --- a/src/wp-includes/class-wp-theme-json.php +++ b/src/wp-includes/class-wp-theme-json.php @@ -1003,9 +1003,29 @@ public function get_stylesheet( $types = array( 'variables', 'styles', 'presets' $stylesheet .= $this->get_preset_classes( $setting_nodes, $origins ); } - // Load the custom CSS last so it has the highest specificity. - if ( in_array( 'custom-css', $types, true ) ) { - $stylesheet .= _wp_array_get( $this->theme_json, array( 'styles', 'css' ) ); + return $stylesheet; + } + + /** + * Returns the global styles custom css. + * + * @since 6.2.0 + * + * @return string + */ + 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; diff --git a/src/wp-includes/default-filters.php b/src/wp-includes/default-filters.php index 4e6340700cc15..09be9e576b6d2 100644 --- a/src/wp-includes/default-filters.php +++ b/src/wp-includes/default-filters.php @@ -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 ); diff --git a/src/wp-includes/global-styles-and-settings.php b/src/wp-includes/global-styles-and-settings.php index ef6d6dd1a2730..3c4e3c695e99e 100644 --- a/src/wp-includes/global-styles-and-settings.php +++ b/src/wp-includes/global-styles-and-settings.php @@ -126,7 +126,6 @@ function wp_get_global_styles( $path = array(), $context = array() ) { * * @since 5.9.0 * @since 6.1.0 Added 'base-layout-styles' support. - * @since 6.2.0 Support for custom-css type added. * * @param array $types Optional. Types of styles to load. * It accepts as values 'variables', 'presets', 'styles', 'base-layout-styles'. @@ -175,7 +174,7 @@ function wp_get_global_stylesheet( $types = array() ) { if ( empty( $types ) && ! $supports_theme_json ) { $types = array( 'variables', 'presets', 'base-layout-styles' ); } elseif ( empty( $types ) ) { - $types = array( 'variables', 'styles', 'presets', 'custom-css' ); + $types = array( 'variables', 'styles', 'presets' ); } /* @@ -226,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. + * + * @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; + $cache_key = 'gutenberg_get_global_custom_css'; + $cache_group = 'theme_json'; + if ( $can_use_cached ) { + $cached = wp_cache_get( $cache_key, $cache_group ); + if ( $cached ) { + return $cached; + } + } + + if ( ! wp_theme_has_theme_json() ) { + return ''; + } + + $tree = WP_Theme_JSON_Resolver_Gutenberg::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). * @@ -370,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' ); WP_Theme_JSON_Resolver::clean_cached_data(); } diff --git a/src/wp-includes/script-loader.php b/src/wp-includes/script-loader.php index 63f5c4fe28f39..e1a2207f72172 100644 --- a/src/wp-includes/script-loader.php +++ b/src/wp-includes/script-loader.php @@ -2454,6 +2454,26 @@ 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 ); + + $custom_css = wp_get_custom_css(); + $custom_css .= gutenberg_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. * diff --git a/tests/phpunit/tests/theme/wpThemeJson.php b/tests/phpunit/tests/theme/wpThemeJson.php index 113a1f01800ab..c600c0408c6a7 100644 --- a/tests/phpunit/tests/theme/wpThemeJson.php +++ b/tests/phpunit/tests/theme/wpThemeJson.php @@ -4505,4 +4505,95 @@ 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->assertEquals( $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 + * + * + * @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', + ), + ), + ), + ), + ), + ), + ); + } } From cecd9cac6dd66962f878bc5ace41801693303fff Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Wed, 1 Feb 2023 13:10:16 +1300 Subject: [PATCH 04/23] fix function name --- src/wp-includes/script-loader.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/wp-includes/script-loader.php b/src/wp-includes/script-loader.php index e1a2207f72172..2245b08c5717d 100644 --- a/src/wp-includes/script-loader.php +++ b/src/wp-includes/script-loader.php @@ -2468,12 +2468,13 @@ function wp_enqueue_global_styles_custom_css() { remove_action( 'wp_head', 'wp_custom_css_cb', 101 ); $custom_css = wp_get_custom_css(); - $custom_css .= gutenberg_get_global_styles_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. * From 5a11f8a666ab7a9dd2146084b2b0595d130d1077 Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Wed, 1 Feb 2023 13:15:58 +1300 Subject: [PATCH 05/23] Fix class naming issue --- src/wp-includes/global-styles-and-settings.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/global-styles-and-settings.php b/src/wp-includes/global-styles-and-settings.php index 3c4e3c695e99e..87e98c214d349 100644 --- a/src/wp-includes/global-styles-and-settings.php +++ b/src/wp-includes/global-styles-and-settings.php @@ -248,7 +248,7 @@ function wp_get_global_styles_custom_css() { return ''; } - $tree = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data(); + $tree = WP_Theme_JSON_Resolver::get_merged_data(); $stylesheet = $tree->get_custom_css(); if ( $can_use_cached ) { From bd7fe93ced4b4112ddafa708c129e358c6f66d84 Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Wed, 1 Feb 2023 14:21:39 +1300 Subject: [PATCH 06/23] Fix linting issue --- src/wp-includes/global-styles-and-settings.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/global-styles-and-settings.php b/src/wp-includes/global-styles-and-settings.php index 87e98c214d349..f374400ffdeb0 100644 --- a/src/wp-includes/global-styles-and-settings.php +++ b/src/wp-includes/global-styles-and-settings.php @@ -227,7 +227,7 @@ function wp_get_global_stylesheet( $types = array() ) { /** * Gets the global styles custom css from theme.json. - * + * * @since 6.2.0 Added custom css support to theme.json. * * @return string Stylesheet. From 76d3f9935b9dac2e1c2acbee22f04ddf138671b8 Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Thu, 2 Feb 2023 09:38:52 +1300 Subject: [PATCH 07/23] Add tests for global custom CSS --- tests/phpunit/tests/theme/wpThemeJson.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/phpunit/tests/theme/wpThemeJson.php b/tests/phpunit/tests/theme/wpThemeJson.php index c600c0408c6a7..a4a9466cee193 100644 --- a/tests/phpunit/tests/theme/wpThemeJson.php +++ b/tests/phpunit/tests/theme/wpThemeJson.php @@ -4518,7 +4518,6 @@ public function test_get_custom_css_handles_global_custom_css() { ), ) ); - $custom_css = 'body { color:purple; }'; $this->assertEquals( $custom_css, $theme_json->get_custom_css() ); } From c4463763d1161afb552d60e7131317b4f3f35333 Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Wed, 1 Feb 2023 15:28:59 +1300 Subject: [PATCH 08/23] add debugging to unit test --- tests/phpunit/tests/rest-api/rest-global-styles-controller.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/phpunit/tests/rest-api/rest-global-styles-controller.php b/tests/phpunit/tests/rest-api/rest-global-styles-controller.php index 9b0fee3559995..fce7bceecc550 100644 --- a/tests/phpunit/tests/rest-api/rest-global-styles-controller.php +++ b/tests/phpunit/tests/rest-api/rest-global-styles-controller.php @@ -547,6 +547,7 @@ public function test_update_item_valid_styles_css() { ); $response = rest_get_server()->dispatch( $request ); $data = $response->get_data(); + echo var_dump( $data ); $this->assertSame( 'body { color: red; }', $data['styles']['css'] ); } From 8f7afa8381a438b28f6e96bdf656bd2b5ed64f49 Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Wed, 1 Feb 2023 15:39:52 +1300 Subject: [PATCH 09/23] check for array or object --- .../tests/rest-api/rest-global-styles-controller.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/phpunit/tests/rest-api/rest-global-styles-controller.php b/tests/phpunit/tests/rest-api/rest-global-styles-controller.php index fce7bceecc550..9b93e4987f249 100644 --- a/tests/phpunit/tests/rest-api/rest-global-styles-controller.php +++ b/tests/phpunit/tests/rest-api/rest-global-styles-controller.php @@ -547,8 +547,11 @@ public function test_update_item_valid_styles_css() { ); $response = rest_get_server()->dispatch( $request ); $data = $response->get_data(); - echo var_dump( $data ); - $this->assertSame( 'body { color: red; }', $data['styles']['css'] ); + if (is_array($data['styles'])) { + $this->assertSame( 'body { color: red; }', $data['styles']['css'] ); + } else { + $this->assertSame( 'body { color: red; }', $data['styles']->css ); + } } /** From 1f74f52b333e9bb07a99b13851679725f3c6a436 Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Wed, 1 Feb 2023 15:49:08 +1300 Subject: [PATCH 10/23] Remove debugging --- .../tests/rest-api/rest-global-styles-controller.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/phpunit/tests/rest-api/rest-global-styles-controller.php b/tests/phpunit/tests/rest-api/rest-global-styles-controller.php index 9b93e4987f249..9b0fee3559995 100644 --- a/tests/phpunit/tests/rest-api/rest-global-styles-controller.php +++ b/tests/phpunit/tests/rest-api/rest-global-styles-controller.php @@ -547,11 +547,7 @@ public function test_update_item_valid_styles_css() { ); $response = rest_get_server()->dispatch( $request ); $data = $response->get_data(); - if (is_array($data['styles'])) { - $this->assertSame( 'body { color: red; }', $data['styles']['css'] ); - } else { - $this->assertSame( 'body { color: red; }', $data['styles']->css ); - } + $this->assertSame( 'body { color: red; }', $data['styles']['css'] ); } /** From 6787594c52f03ef181ca84c7e217c936519c1eb1 Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Wed, 1 Feb 2023 16:24:34 +1300 Subject: [PATCH 11/23] Add in check for edit-css capabilities --- src/wp-includes/class-wp-theme-json.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/wp-includes/class-wp-theme-json.php b/src/wp-includes/class-wp-theme-json.php index b8ed6eeef882c..2908fb94d7e69 100644 --- a/src/wp-includes/class-wp-theme-json.php +++ b/src/wp-includes/class-wp-theme-json.php @@ -2766,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. From e4d53a2e08255ad29341e1bea8fdd0289f38fff2 Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Wed, 1 Feb 2023 16:41:54 +1300 Subject: [PATCH 12/23] Add tests for custom CSS user capabilities --- tests/phpunit/tests/theme/wpThemeJson.php | 30 +++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/phpunit/tests/theme/wpThemeJson.php b/tests/phpunit/tests/theme/wpThemeJson.php index a4a9466cee193..f745462489338 100644 --- a/tests/phpunit/tests/theme/wpThemeJson.php +++ b/tests/phpunit/tests/theme/wpThemeJson.php @@ -14,6 +14,36 @@ */ class Tests_Theme_wpThemeJson extends WP_UnitTestCase { + /** + * Administrator ID. + * + * @var int + */ + private static $administrator_id; + + /** + * User ID. + * + * @var int + */ + private static $user_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 ); + } + + static::$user_id = self::factory()->user->create(); + } + /** * @ticket 52991 * @ticket 54336 From 400d4377751701ee1e4919bfb028ac1303a2aaf9 Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Thu, 2 Feb 2023 09:28:52 +1300 Subject: [PATCH 13/23] Fix permissions issue on multisite test --- .../phpunit/tests/rest-api/rest-global-styles-controller.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/phpunit/tests/rest-api/rest-global-styles-controller.php b/tests/phpunit/tests/rest-api/rest-global-styles-controller.php index 9b0fee3559995..207046d990aeb 100644 --- a/tests/phpunit/tests/rest-api/rest-global-styles-controller.php +++ b/tests/phpunit/tests/rest-api/rest-global-styles-controller.php @@ -54,6 +54,10 @@ public function set_up() { * @param WP_UnitTest_Factory $factory Helper that lets us create fake data. */ public static function wpSetupBeforeClass( $factory ) { + if ( is_multisite() ) { + grant_super_admin( self::$admin_id ); + } + self::$admin_id = $factory->user->create( array( 'role' => 'administrator', From eec32b690c93aead8c832a6bba4d8418a6ed2106 Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Thu, 2 Feb 2023 10:02:06 +1300 Subject: [PATCH 14/23] Put permissions assignment in the right place! --- .../tests/rest-api/rest-global-styles-controller.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/phpunit/tests/rest-api/rest-global-styles-controller.php b/tests/phpunit/tests/rest-api/rest-global-styles-controller.php index 207046d990aeb..e4dd9a34f1731 100644 --- a/tests/phpunit/tests/rest-api/rest-global-styles-controller.php +++ b/tests/phpunit/tests/rest-api/rest-global-styles-controller.php @@ -54,10 +54,6 @@ public function set_up() { * @param WP_UnitTest_Factory $factory Helper that lets us create fake data. */ public static function wpSetupBeforeClass( $factory ) { - if ( is_multisite() ) { - grant_super_admin( self::$admin_id ); - } - self::$admin_id = $factory->user->create( array( 'role' => 'administrator', @@ -70,6 +66,10 @@ public static function wpSetupBeforeClass( $factory ) { ) ); + if ( is_multisite() ) { + grant_super_admin( self::$admin_id ); + } + // This creates the global styles for the current theme. self::$global_styles_id = $factory->post->create( array( From c9fcd81d8f9642c7e79f335e0c0ef9520f6bb82b Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Thu, 2 Feb 2023 10:48:50 +1300 Subject: [PATCH 15/23] Set permissions in tests instead of setup to prevent changing permissions for other tests that don't require it --- .../tests/rest-api/rest-global-styles-controller.php | 11 ++++++----- tests/phpunit/tests/theme/wpThemeJson.php | 9 --------- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/tests/phpunit/tests/rest-api/rest-global-styles-controller.php b/tests/phpunit/tests/rest-api/rest-global-styles-controller.php index e4dd9a34f1731..f20fde92ae663 100644 --- a/tests/phpunit/tests/rest-api/rest-global-styles-controller.php +++ b/tests/phpunit/tests/rest-api/rest-global-styles-controller.php @@ -66,10 +66,6 @@ public static function wpSetupBeforeClass( $factory ) { ) ); - if ( is_multisite() ) { - grant_super_admin( self::$admin_id ); - } - // This creates the global styles for the current theme. self::$global_styles_id = $factory->post->create( array( @@ -543,6 +539,9 @@ public function test_assign_edit_css_action_admin() { */ 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( @@ -560,6 +559,9 @@ public function test_update_item_valid_styles_css() { */ 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( @@ -569,5 +571,4 @@ public function test_update_item_invalid_styles_css() { $response = rest_get_server()->dispatch( $request ); $this->assertErrorResponse( 'rest_custom_css_illegal_markup', $response, 400 ); } - } diff --git a/tests/phpunit/tests/theme/wpThemeJson.php b/tests/phpunit/tests/theme/wpThemeJson.php index f745462489338..7f5f895affe00 100644 --- a/tests/phpunit/tests/theme/wpThemeJson.php +++ b/tests/phpunit/tests/theme/wpThemeJson.php @@ -21,13 +21,6 @@ class Tests_Theme_wpThemeJson extends WP_UnitTestCase { */ private static $administrator_id; - /** - * User ID. - * - * @var int - */ - private static $user_id; - public static function set_up_before_class() { parent::set_up_before_class(); @@ -40,8 +33,6 @@ public static function set_up_before_class() { if ( is_multisite() ) { grant_super_admin( self::$administrator_id ); } - - static::$user_id = self::factory()->user->create(); } /** From 7450db95cb06607175b5cc684892d40a3dd48b6d Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Thu, 2 Feb 2023 10:58:26 +1300 Subject: [PATCH 16/23] Fixes from code review --- .../class-wp-rest-global-styles-controller.php | 12 +++++++----- tests/phpunit/tests/theme/wpThemeJson.php | 3 +-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php index e231da0428ed1..a20cd5e90c06a 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php @@ -297,7 +297,7 @@ public function update_item( $request ) { * @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(); @@ -318,9 +318,11 @@ protected function prepare_item_for_database( $request ) { $config = array(); if ( isset( $request['styles'] ) ) { $config['styles'] = $request['styles']; - $validate_custom_css = $this->validate_custom_css( $request['styles']['css'] ); - if ( is_wp_error( $validate_custom_css ) ) { - return $validate_custom_css; + 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']; @@ -681,7 +683,7 @@ private function validate_custom_css( $css ) { if ( preg_match( '# 400 ) ); } diff --git a/tests/phpunit/tests/theme/wpThemeJson.php b/tests/phpunit/tests/theme/wpThemeJson.php index 7f5f895affe00..a1d89983a806f 100644 --- a/tests/phpunit/tests/theme/wpThemeJson.php +++ b/tests/phpunit/tests/theme/wpThemeJson.php @@ -4540,7 +4540,7 @@ public function test_get_custom_css_handles_global_custom_css() { ) ); $custom_css = 'body { color:purple; }'; - $this->assertEquals( $custom_css, $theme_json->get_custom_css() ); + $this->assertSame( $custom_css, $theme_json->get_custom_css() ); } /** @@ -4550,7 +4550,6 @@ public function test_get_custom_css_handles_global_custom_css() { * * @dataProvider data_custom_css_for_user_caps * - * * @param string $user_property The property name for current user. * @param array $expected Expected results. */ From d6c6e65c12d706cd15ef490fb0205d0534ef4f31 Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Thu, 2 Feb 2023 11:30:04 +1300 Subject: [PATCH 17/23] Remove unnecessary description Co-authored-by: Tonya Mork --- src/wp-includes/global-styles-and-settings.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/global-styles-and-settings.php b/src/wp-includes/global-styles-and-settings.php index f374400ffdeb0..2f00d49caa5ba 100644 --- a/src/wp-includes/global-styles-and-settings.php +++ b/src/wp-includes/global-styles-and-settings.php @@ -228,7 +228,7 @@ function wp_get_global_stylesheet( $types = array() ) { /** * Gets the global styles custom css from theme.json. * - * @since 6.2.0 Added custom css support to theme.json. + * @since 6.2.0 * * @return string Stylesheet. */ From 20442c98d7f93bf44a3596b4c3b364910b360b2b Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Thu, 2 Feb 2023 11:33:54 +1300 Subject: [PATCH 18/23] But make user definition that was removed by mistake Co-authored-by: Tonya Mork --- tests/phpunit/tests/theme/wpThemeJson.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/phpunit/tests/theme/wpThemeJson.php b/tests/phpunit/tests/theme/wpThemeJson.php index a1d89983a806f..efa709ef756dc 100644 --- a/tests/phpunit/tests/theme/wpThemeJson.php +++ b/tests/phpunit/tests/theme/wpThemeJson.php @@ -21,6 +21,13 @@ class Tests_Theme_wpThemeJson extends WP_UnitTestCase { */ private static $administrator_id; + /** + * User ID. + * + * @var int + */ + private static $user_id; + public static function set_up_before_class() { parent::set_up_before_class(); @@ -33,6 +40,8 @@ public static function set_up_before_class() { if ( is_multisite() ) { grant_super_admin( self::$administrator_id ); } + + static::$user_id = self::factory()->user->create(); } /** From d055fa5fbd4e4df5be45b832ff9c435921533042 Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Thu, 2 Feb 2023 11:42:48 +1300 Subject: [PATCH 19/23] Change ordering of $request[styles] assignment to $config[styles] --- .../endpoints/class-wp-rest-global-styles-controller.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php index a20cd5e90c06a..c8eee60ed3632 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php @@ -317,13 +317,13 @@ protected function prepare_item_for_database( $request ) { if ( isset( $request['styles'] ) || isset( $request['settings'] ) ) { $config = array(); if ( isset( $request['styles'] ) ) { - $config['styles'] = $request['styles']; - if ( isset( $config['styles']['css'] ) ) { - $css_validation_result = $this->validate_custom_css( $config['styles']['css'] ); + if ( isset( $request['styles']['css'] ) ) { + $css_validation_result = $this->validate_custom_css( $request['styles']['css'] ); if ( is_wp_error( $css_validation_result ) ) { return $css_validation_result; } } + $config['styles'] = $request['styles']; } elseif ( isset( $existing_config['styles'] ) ) { $config['styles'] = $existing_config['styles']; } From d37e7f2e8f56fc1fa53da56a56b643923599fdd3 Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Thu, 2 Feb 2023 11:48:14 +1300 Subject: [PATCH 20/23] Change cache key and add doc comments Co-authored-by: Tonya Mork --- .../global-styles-and-settings.php | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/src/wp-includes/global-styles-and-settings.php b/src/wp-includes/global-styles-and-settings.php index 2f00d49caa5ba..40a53549aced2 100644 --- a/src/wp-includes/global-styles-and-settings.php +++ b/src/wp-includes/global-styles-and-settings.php @@ -233,10 +233,33 @@ function wp_get_global_stylesheet( $types = array() ) { * @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. +function wp_get_global_styles_custom_css() { + /* + * Ignore cache when `WP_DEBUG` is enabled, so it doesn't interfere with the theme + * developer's workflow. + * + * @todo Replace `WP_DEBUG` once an "in development mode" check is available in Core. + */ $can_use_cached = ! WP_DEBUG; - $cache_key = 'gutenberg_get_global_custom_css'; - $cache_group = 'theme_json'; + + /* + * By using the 'theme_json' group, this data is marked to be non-persistent across requests. + * @see `wp_cache_add_non_persistent_groups()`. + * + * The rationale for this is to make sure derived data from theme.json + * is always fresh from the potential modifications done via hooks + * that can use dynamic data (modify the stylesheet depending on some option, + * settings depending on user permissions, etc.). + * See some of the existing hooks to modify theme.json behavior: + * @see https://make.wordpress.org/core/2022/10/10/filters-for-theme-json-data/ + * + * A different alternative considered was to invalidate the cache upon certain + * events such as options add/update/delete, user meta, etc. + * It was judged not enough, hence this approach. + * @see https://github.com/WordPress/gutenberg/pull/45372 + */ + $cache_key = 'wp_get_global_styles_custom_css'; + $cache_group = 'theme_json'; if ( $can_use_cached ) { $cached = wp_cache_get( $cache_key, $cache_group ); if ( $cached ) { From d71a7feccd8cd0e280e76ac9999986a3e66463f3 Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Thu, 2 Feb 2023 11:49:00 +1300 Subject: [PATCH 21/23] match cache key to method name Co-authored-by: Tonya Mork --- src/wp-includes/global-styles-and-settings.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/global-styles-and-settings.php b/src/wp-includes/global-styles-and-settings.php index 40a53549aced2..2fa8f23b2a851 100644 --- a/src/wp-includes/global-styles-and-settings.php +++ b/src/wp-includes/global-styles-and-settings.php @@ -425,6 +425,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' ); + wp_cache_delete( 'wp_get_global_styles_custom_css', 'theme_json' ); WP_Theme_JSON_Resolver::clean_cached_data(); } From b6586848fd544808c03158149fd44de5713294df Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Thu, 2 Feb 2023 12:04:34 +1300 Subject: [PATCH 22/23] Move check for theme.json to top of function --- src/wp-includes/global-styles-and-settings.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/wp-includes/global-styles-and-settings.php b/src/wp-includes/global-styles-and-settings.php index 2fa8f23b2a851..8ef77735f1cbf 100644 --- a/src/wp-includes/global-styles-and-settings.php +++ b/src/wp-includes/global-styles-and-settings.php @@ -233,7 +233,9 @@ function wp_get_global_stylesheet( $types = array() ) { * @return string Stylesheet. */ function wp_get_global_styles_custom_css() { -function wp_get_global_styles_custom_css() { + if ( ! wp_theme_has_theme_json() ) { + return ''; + } /* * Ignore cache when `WP_DEBUG` is enabled, so it doesn't interfere with the theme * developer's workflow. @@ -267,10 +269,6 @@ function wp_get_global_styles_custom_css() { } } - if ( ! wp_theme_has_theme_json() ) { - return ''; - } - $tree = WP_Theme_JSON_Resolver::get_merged_data(); $stylesheet = $tree->get_custom_css(); From 726135697aa32a40e01031d246f631c8de8407e2 Mon Sep 17 00:00:00 2001 From: Felix Arntz Date: Thu, 2 Feb 2023 09:30:46 -0800 Subject: [PATCH 23/23] Update inline comment. --- src/wp-includes/block-editor.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/wp-includes/block-editor.php b/src/wp-includes/block-editor.php index f611ae4f4fd1a..acf7b5d55089b 100644 --- a/src/wp-includes/block-editor.php +++ b/src/wp-includes/block-editor.php @@ -410,7 +410,11 @@ 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. + + /* + * Add the custom CSS as a separate stylesheet so any invalid CSS + * entered by users does not break other global styles. + */ $editor_settings['styles'][] = array( 'css' => wp_get_global_styles_custom_css(), '__unstableType' => 'user',