diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index 9acef6006d4e68..cd1f683c1a5d6b 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -2818,7 +2818,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. diff --git a/lib/experimental/kses.php b/lib/experimental/kses.php index fd7531617939fc..830ac01c2ab0b6 100644 --- a/lib/experimental/kses.php +++ b/lib/experimental/kses.php @@ -62,5 +62,6 @@ function gutenberg_override_core_kses_init_filters() { } } -add_action( 'init', 'gutenberg_override_core_kses_init_filters' ); +// The 'kses_init_filters' is usually initialized with default priority. Use higher priority to override. +add_action( 'init', 'gutenberg_override_core_kses_init_filters', 20 ); add_action( 'set_current_user', 'gutenberg_override_core_kses_init_filters' ); diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php index 0d21f46ee9c291..795ad0ab4c8117 100644 --- a/phpunit/class-wp-theme-json-test.php +++ b/phpunit/class-wp-theme-json-test.php @@ -7,6 +7,36 @@ */ class WP_Theme_JSON_Gutenberg_Test 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(); + } + /** * @dataProvider data_get_layout_definitions * @@ -1598,4 +1628,73 @@ public function test_get_stylesheet_handles_custom_css() { $custom_css = 'body { color:purple; }'; $this->assertEquals( $custom_css, $theme_json->get_stylesheet( array( 'custom-css' ) ) ); } + + /** + * @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_Gutenberg::remove_insecure_properties( + array( + 'version' => WP_Theme_JSON_Gutenberg::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_Gutenberg::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_Gutenberg::LATEST_SCHEMA, + 'styles' => array( + 'blocks' => array( + 'core/separator' => array( + 'color' => array( + 'background' => 'blue', + ), + ), + ), + ), + ), + ), + ); + } }