diff --git a/src/wp-includes/customize/class-wp-customize-custom-css-setting.php b/src/wp-includes/customize/class-wp-customize-custom-css-setting.php
index aab0e475304ea..0d8324e367a3a 100644
--- a/src/wp-includes/customize/class-wp-customize-custom-css-setting.php
+++ b/src/wp-includes/customize/class-wp-customize-custom-css-setting.php
@@ -144,35 +144,6 @@ public function value() {
return $value;
}
- /**
- * Validate a received value for being valid CSS.
- *
- * Checks for imbalanced braces, brackets, and comments.
- * Notifications are rendered when the customizer state is saved.
- *
- * @since 4.7.0
- * @since 4.9.0 Checking for balanced characters has been moved client-side via linting in code editor.
- * @since 5.9.0 Renamed `$css` to `$value` for PHP 8 named parameter support.
- *
- * @param string $value CSS to validate.
- * @return true|WP_Error True if the input was validated, otherwise WP_Error.
- */
- public function validate( $value ) {
- // Restores the more descriptive, specific name for use within this method.
- $css = $value;
-
- $validity = new WP_Error();
-
- if ( preg_match( '#?\w+#', $css ) ) {
- $validity->add( 'illegal_markup', __( 'Markup is not allowed in CSS.' ) );
- }
-
- if ( ! $validity->has_errors() ) {
- $validity = parent::validate( $css );
- }
- return $validity;
- }
-
/**
* Store the CSS setting value in the custom_css custom post type for the stylesheet.
*
diff --git a/src/wp-includes/theme.php b/src/wp-includes/theme.php
index 44343569a61b1..32a5c13b67953 100644
--- a/src/wp-includes/theme.php
+++ b/src/wp-includes/theme.php
@@ -2134,6 +2134,16 @@ function wp_update_custom_css_post( $css, $args = array() ) {
// Update post if it already exists, otherwise create a new one.
$post = wp_get_custom_css_post( $args['stylesheet'] );
+
+ /**
+ * Temporarily remove the {@see wp_filter_post_kses()} `content_save_pre` filter. CSS text is
+ * stored in post_content, but the filter would process it as HTML and may mangle valid CSS.
+ */
+ $kses_filter_priority = has_filter( 'content_save_pre', 'wp_filter_post_kses' );
+ if ( false !== $kses_filter_priority ) {
+ remove_filter( 'content_save_pre', 'wp_filter_post_kses', $kses_filter_priority );
+ }
+
if ( $post ) {
$post_data['ID'] = $post->ID;
$r = wp_update_post( wp_slash( $post_data ), true );
@@ -2153,6 +2163,10 @@ function wp_update_custom_css_post( $css, $args = array() ) {
}
}
+ if ( false !== $kses_filter_priority ) {
+ add_filter( 'content_save_pre', 'wp_filter_post_kses', $kses_filter_priority );
+ }
+
if ( is_wp_error( $r ) ) {
return $r;
}
diff --git a/tests/phpunit/tests/customize/custom-css-setting.php b/tests/phpunit/tests/customize/custom-css-setting.php
index 65cc3f717fe59..0899e640af320 100644
--- a/tests/phpunit/tests/customize/custom-css-setting.php
+++ b/tests/phpunit/tests/customize/custom-css-setting.php
@@ -375,27 +375,59 @@ public function filter_update_custom_css_data( $data, $args ) {
}
/**
- * Tests that validation errors are caught appropriately.
+ * Ensure that dangerous STYLE tag contents do not break HTML output.
*
- * Note that the $validity \WP_Error object must be reset each time
- * as it picks up the Errors and passes them to the next assertion.
- *
- * @covers WP_Customize_Custom_CSS_Setting::validate
+ * @ticket 64418
+ * @covers ::wp_update_custom_css_post
+ * @covers ::wp_custom_css_cb
*/
- public function test_validate() {
-
- // Empty CSS throws no errors.
- $result = $this->setting->validate( '' );
- $this->assertTrue( $result );
+ public function test_wp_custom_css_cb_escapes_dangerous_html() {
+ wp_update_custom_css_post(
+ '*::before { content: ""; }',
+ array(
+ 'stylesheet' => $this->setting->stylesheet,
+ )
+ );
+ $output = get_echo( 'wp_custom_css_cb' );
+ $expected = <<<'HTML'
+
+HTML;
+ $this->assertEqualHTML( $expected, $output );
+ }
- // Basic, valid CSS throws no errors.
- $basic_css = 'body { background: #f00; } h1.site-title { font-size: 36px; } a:hover { text-decoration: none; } input[type="text"] { padding: 1em; }';
- $result = $this->setting->validate( $basic_css );
- $this->assertTrue( $result );
+ /**
+ * @ticket 64418
+ * @covers WP_Customize_Custom_CSS_Setting::validate
+ */
+ public function test_validate_accepts_css_property_at_rule() {
+ $css = <<<'CSS'
+@property --animate {
+ syntax: "";
+ inherits: true;
+ initial-value: false;
+}
+CSS;
+ $this->assertTrue( $this->setting->validate( $css ) );
+ }
- // Check for markup.
- $unclosed_comment = $basic_css . '';
- $result = $this->setting->validate( $unclosed_comment );
- $this->assertArrayHasKey( 'illegal_markup', $result->errors );
+ /**
+ * @ticket 64418
+ * @covers ::wp_update_custom_css_post
+ * @covers ::wp_custom_css_cb
+ */
+ public function test_save_and_print_property_at_rule() {
+ $css = <<<'CSS'
+@property --animate {
+ syntax: "";
+ inherits: true;
+ initial-value: false;
+}
+CSS;
+ wp_update_custom_css_post( $css, array( 'stylesheet' => $this->setting->stylesheet ) );
+ $output = get_echo( 'wp_custom_css_cb' );
+ $expected = "\n";
+ $this->assertEqualHTML( $expected, $output );
}
}