<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[55192] trunk: Editor: Add support for custom CSS in global styles.</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { white-space: pre-line; overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="https://core.trac.wordpress.org/changeset/55192">55192</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"https://core.trac.wordpress.org/changeset/55192","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>flixos90</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2023-02-02 18:50:54 +0000 (Thu, 02 Feb 2023)</dd>
</dl>

<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>Editor: Add support for custom CSS in global styles.

This changeset introduces functions `wp_get_global_styles_custom_css()` and `wp_enqueue_global_styles_custom_css()`, which allow accessing and enqueuing custom CSS added via global styles.

Custom CSS via global styles is handled separately from custom CSS via the Customizer. If a site uses both features, the custom CSS from both sources will be loaded. The global styles custom CSS is then loaded after the Customizer custom CSS, so if there are any conflicts between the rules, the global styles take precedence.

Similarly to e.g. <a href="https://core.trac.wordpress.org/changeset/55185">[55185]</a>, the result is cached in a non-persistent cache, except when `WP_DEBUG` is on to avoid interrupting the theme developer's workflow.

Props glendaviesnz, oandregal, ntsekouras, mamaduka, davidbaumwald, hellofromtonya, flixos90.
Fixes <a href="https://core.trac.wordpress.org/ticket/57536">#57536</a>.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunksrcwpincludesblockeditorphp">trunk/src/wp-includes/block-editor.php</a></li>
<li><a href="#trunksrcwpincludesclasswpthemejsonphp">trunk/src/wp-includes/class-wp-theme-json.php</a></li>
<li><a href="#trunksrcwpincludesdefaultfiltersphp">trunk/src/wp-includes/default-filters.php</a></li>
<li><a href="#trunksrcwpincludesglobalstylesandsettingsphp">trunk/src/wp-includes/global-styles-and-settings.php</a></li>
<li><a href="#trunksrcwpincludesrestapiendpointsclasswprestglobalstylescontrollerphp">trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php</a></li>
<li><a href="#trunksrcwpincludesscriptloaderphp">trunk/src/wp-includes/script-loader.php</a></li>
<li><a href="#trunktestsphpunittestsrestapirestglobalstylescontrollerphp">trunk/tests/phpunit/tests/rest-api/rest-global-styles-controller.php</a></li>
<li><a href="#trunktestsphpunitteststhemewpThemeJsonphp">trunk/tests/phpunit/tests/theme/wpThemeJson.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunksrcwpincludesblockeditorphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/block-editor.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/block-editor.php    2023-02-02 16:34:04 UTC (rev 55191)
+++ trunk/src/wp-includes/block-editor.php      2023-02-02 18:50:54 UTC (rev 55192)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -410,6 +410,16 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        $block_classes['css'] = $actual_css;
</span><span class="cx" style="display: block; padding: 0 10px">                        $global_styles[]      = $block_classes;
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+               /*
+                * 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',
+                       'isGlobalStyles' => true,
+               );
</ins><span class="cx" style="display: block; padding: 0 10px">         } else {
</span><span class="cx" style="display: block; padding: 0 10px">                // If there is no `theme.json` file, ensure base layout styles are still available.
</span><span class="cx" style="display: block; padding: 0 10px">                $block_classes = array(
</span></span></pre></div>
<a id="trunksrcwpincludesclasswpthemejsonphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/class-wp-theme-json.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/class-wp-theme-json.php     2023-02-02 16:34:04 UTC (rev 55191)
+++ trunk/src/wp-includes/class-wp-theme-json.php       2023-02-02 18:50:54 UTC (rev 55192)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -425,6 +425,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        'textDecoration' => null,
</span><span class="cx" style="display: block; padding: 0 10px">                        'textTransform'  => null,
</span><span class="cx" style="display: block; padding: 0 10px">                ),
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                'css'        => null,
</ins><span class="cx" style="display: block; padding: 0 10px">         );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1006,6 +1007,31 @@
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * 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;
+       }
+
+       /**
</ins><span class="cx" style="display: block; padding: 0 10px">          * Returns the page templates of the active theme.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @since 5.9.0
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2740,7 +2766,12 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                continue;
</span><span class="cx" style="display: block; padding: 0 10px">                        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        $output = static::remove_insecure_styles( $input );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 // 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 );
+                       }
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                        /*
</span><span class="cx" style="display: block; padding: 0 10px">                         * Get a reference to element name from path.
</span></span></pre></div>
<a id="trunksrcwpincludesdefaultfiltersphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/default-filters.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/default-filters.php 2023-02-02 16:34:04 UTC (rev 55191)
+++ trunk/src/wp-includes/default-filters.php   2023-02-02 18:50:54 UTC (rev 55192)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -577,6 +577,9 @@
</span><span class="cx" style="display: block; padding: 0 10px"> add_action( 'wp_enqueue_scripts', 'wp_enqueue_global_styles' );
</span><span class="cx" style="display: block; padding: 0 10px"> add_action( 'wp_footer', 'wp_enqueue_global_styles', 1 );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+// Global styles custom CSS.
+add_action( 'wp_enqueue_scripts', 'wp_enqueue_global_styles_custom_css' );
+
</ins><span class="cx" style="display: block; padding: 0 10px"> // Block supports, and other styles parsed and stored in the Style Engine.
</span><span class="cx" style="display: block; padding: 0 10px"> add_action( 'wp_enqueue_scripts', 'wp_enqueue_stored_styles' );
</span><span class="cx" style="display: block; padding: 0 10px"> add_action( 'wp_footer', 'wp_enqueue_stored_styles', 1 );
</span></span></pre></div>
<a id="trunksrcwpincludesglobalstylesandsettingsphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/global-styles-and-settings.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/global-styles-and-settings.php      2023-02-02 16:34:04 UTC (rev 55191)
+++ trunk/src/wp-includes/global-styles-and-settings.php        2023-02-02 18:50:54 UTC (rev 55192)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -226,6 +226,60 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Gets the global styles custom css from theme.json.
+ *
+ * @since 6.2.0
+ *
+ * @return string Stylesheet.
+ */
+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.
+        *
+        * @todo Replace `WP_DEBUG` once an "in development mode" check is available in Core.
+        */
+       $can_use_cached = ! WP_DEBUG;
+
+       /*
+        * 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 ) {
+                       return $cached;
+               }
+       }
+
+       $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;
+}
+
+/**
</ins><span class="cx" style="display: block; padding: 0 10px">  * Returns a string containing the SVGs to be referenced as filters (duotone).
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @since 5.9.1
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -369,5 +423,6 @@
</span><span class="cx" style="display: block; padding: 0 10px">        wp_cache_delete( 'wp_get_global_styles_svg_filters', 'theme_json' );
</span><span class="cx" style="display: block; padding: 0 10px">        wp_cache_delete( 'wp_get_global_settings_custom', 'theme_json' );
</span><span class="cx" style="display: block; padding: 0 10px">        wp_cache_delete( 'wp_get_global_settings_theme', 'theme_json' );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        wp_cache_delete( 'wp_get_global_styles_custom_css', 'theme_json' );
</ins><span class="cx" style="display: block; padding: 0 10px">         WP_Theme_JSON_Resolver::clean_cached_data();
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre></div>
<a id="trunksrcwpincludesrestapiendpointsclasswprestglobalstylescontrollerphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php       2023-02-02 16:34:04 UTC (rev 55191)
+++ trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php 2023-02-02 18:50:54 UTC (rev 55192)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -268,6 +268,10 @@
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $changes = $this->prepare_item_for_database( $request );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                if ( is_wp_error( $changes ) ) {
+                       return $changes;
+               }
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 $result  = wp_update_post( wp_slash( (array) $changes ), true, false );
</span><span class="cx" style="display: block; padding: 0 10px">                if ( is_wp_error( $result ) ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        return $result;
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -290,9 +294,10 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * Prepares a single global styles config for update.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @since 5.9.0
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * @since 6.2.0 Added validation of styles.css property.
</ins><span class="cx" style="display: block; padding: 0 10px">          *
</span><span class="cx" style="display: block; padding: 0 10px">         * @param WP_REST_Request $request Request object.
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * @return stdClass Changes to pass to wp_update_post.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * @return stdClass|WP_Error Prepared item on success. WP_Error on when the custom CSS is not valid.
</ins><span class="cx" style="display: block; padding: 0 10px">          */
</span><span class="cx" style="display: block; padding: 0 10px">        protected function prepare_item_for_database( $request ) {
</span><span class="cx" style="display: block; padding: 0 10px">                $changes     = new stdClass();
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -312,6 +317,12 @@
</span><span class="cx" style="display: block; padding: 0 10px">                if ( isset( $request['styles'] ) || isset( $request['settings'] ) ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        $config = array();
</span><span class="cx" style="display: block; padding: 0 10px">                        if ( isset( $request['styles'] ) ) {
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                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;
+                                       }
+                               }
</ins><span class="cx" style="display: block; padding: 0 10px">                                 $config['styles'] = $request['styles'];
</span><span class="cx" style="display: block; padding: 0 10px">                        } elseif ( isset( $existing_config['styles'] ) ) {
</span><span class="cx" style="display: block; padding: 0 10px">                                $config['styles'] = $existing_config['styles'];
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -657,4 +668,25 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                return $response;
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+       /**
+        * 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;
+       }
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre></div>
<a id="trunksrcwpincludesscriptloaderphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/script-loader.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/script-loader.php   2023-02-02 16:34:04 UTC (rev 55191)
+++ trunk/src/wp-includes/script-loader.php     2023-02-02 18:50:54 UTC (rev 55192)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2455,6 +2455,27 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * 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 .= wp_get_global_styles_custom_css();
+
+       if ( ! empty( $custom_css ) ) {
+               wp_add_inline_style( 'global-styles', $custom_css );
+       }
+}
+
+/**
</ins><span class="cx" style="display: block; padding: 0 10px">  * Renders the SVG filters supplied by theme.json.
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * Note that this doesn't render the per-block user-defined
</span></span></pre></div>
<a id="trunktestsphpunittestsrestapirestglobalstylescontrollerphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/rest-api/rest-global-styles-controller.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/rest-api/rest-global-styles-controller.php      2023-02-02 16:34:04 UTC (rev 55191)
+++ trunk/tests/phpunit/tests/rest-api/rest-global-styles-controller.php        2023-02-02 18:50:54 UTC (rev 55192)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -532,4 +532,43 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        $this->assertArrayHasKey( 'https://api.w.org/action-edit-css', $links );
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+       /**
+        * @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 );
+       }
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre></div>
<a id="trunktestsphpunitteststhemewpThemeJsonphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/theme/wpThemeJson.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/theme/wpThemeJson.php   2023-02-02 16:34:04 UTC (rev 55191)
+++ trunk/tests/phpunit/tests/theme/wpThemeJson.php     2023-02-02 18:50:54 UTC (rev 55192)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -15,6 +15,36 @@
</span><span class="cx" style="display: block; padding: 0 10px"> class Tests_Theme_wpThemeJson extends WP_UnitTestCase {
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * 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();
+       }
+
+       /**
</ins><span class="cx" style="display: block; padding: 0 10px">          * @ticket 52991
</span><span class="cx" style="display: block; padding: 0 10px">         * @ticket 54336
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -4505,4 +4535,93 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertSame( $expected_styles, $theme_json->get_stylesheet() );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+       /**
+        * @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
+        *
+        * @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',
+                                                               ),
+                                                       ),
+                                               ),
+                                       ),
+                               ),
+                       ),
+               );
+       }
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre>
</div>
</div>

</body>
</html>