<!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>[59802] trunk: Global Styles: Improve sanitization of block variation 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/59802">59802</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/59802","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>peterwilsoncc</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2025-02-10 22:27:49 +0000 (Mon, 10 Feb 2025)</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'>Global Styles: Improve sanitization of block variation styles.

Fixes an issue where block style variations containing inner block type and element styles would have those inner styles stripped when the user attempting to save Global Styles does not have the `unfiltered_html` capability.

Props aaronrobertshaw, mukesh27, andrewserong.
Fixes <a href="https://core.trac.wordpress.org/ticket/62372">#62372</a>.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunksrcwpincludesclasswpthemejsonphp">trunk/src/wp-includes/class-wp-theme-json.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="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     2025-02-10 22:21:51 UTC (rev 59801)
+++ trunk/src/wp-includes/class-wp-theme-json.php       2025-02-10 22:27:49 UTC (rev 59802)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3552,26 +3552,12 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                                        $variation_output = static::remove_insecure_styles( $variation_input );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                        // Process a variation's elements and element pseudo selector styles.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                 if ( isset( $variation_input['blocks'] ) ) {
+                                               $variation_output['blocks'] = static::remove_insecure_inner_block_styles( $variation_input['blocks'] );
+                                       }
+
</ins><span class="cx" style="display: block; padding: 0 10px">                                         if ( isset( $variation_input['elements'] ) ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                                foreach ( $valid_element_names as $element_name ) {
-                                                       $element_input = $variation_input['elements'][ $element_name ] ?? null;
-                                                       if ( $element_input ) {
-                                                               $element_output = static::remove_insecure_styles( $element_input );
-
-                                                               if ( isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element_name ] ) ) {
-                                                                       foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element_name ] as $pseudo_selector ) {
-                                                                               if ( isset( $element_input[ $pseudo_selector ] ) ) {
-                                                                                       $element_output[ $pseudo_selector ] = static::remove_insecure_styles( $element_input[ $pseudo_selector ] );
-                                                                               }
-                                                                       }
-                                                               }
-
-                                                               if ( ! empty( $element_output ) ) {
-                                                                       _wp_array_set( $variation_output, array( 'elements', $element_name ), $element_output );
-                                                               }
-                                                       }
-                                               }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                         $variation_output['elements'] = static::remove_insecure_element_styles( $variation_input['elements'] );
</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">                                        if ( ! empty( $variation_output ) ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3610,6 +3596,59 @@
</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">+         * Remove insecure element styles within a variation or block.
+        *
+        * @since 6.8.0
+        *
+        * @param array $elements The elements to process.
+        * @return array The sanitized elements styles.
+        */
+       protected static function remove_insecure_element_styles( $elements ) {
+               $sanitized           = array();
+               $valid_element_names = array_keys( static::ELEMENTS );
+
+               foreach ( $valid_element_names as $element_name ) {
+                       $element_input = $elements[ $element_name ] ?? null;
+                       if ( $element_input ) {
+                               $element_output = static::remove_insecure_styles( $element_input );
+
+                               if ( isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element_name ] ) ) {
+                                       foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element_name ] as $pseudo_selector ) {
+                                               if ( isset( $element_input[ $pseudo_selector ] ) ) {
+                                                       $element_output[ $pseudo_selector ] = static::remove_insecure_styles( $element_input[ $pseudo_selector ] );
+                                               }
+                                       }
+                               }
+
+                               $sanitized[ $element_name ] = $element_output;
+                       }
+               }
+               return $sanitized;
+       }
+
+       /**
+        * Remove insecure styles from inner blocks and their elements.
+        *
+        * @since 6.8.0
+        *
+        * @param array $blocks The block styles to process.
+        * @return array Sanitized block type styles.
+        */
+       protected static function remove_insecure_inner_block_styles( $blocks ) {
+               $sanitized = array();
+               foreach ( $blocks as $block_type => $block_input ) {
+                       $block_output = static::remove_insecure_styles( $block_input );
+
+                       if ( isset( $block_input['elements'] ) ) {
+                               $block_output['elements'] = static::remove_insecure_element_styles( $block_input['elements'] );
+                       }
+
+                       $sanitized[ $block_type ] = $block_output;
+               }
+               return $sanitized;
+       }
+
+       /**
</ins><span class="cx" style="display: block; padding: 0 10px">          * Processes a setting node and returns the same node
</span><span class="cx" style="display: block; padding: 0 10px">         * without the insecure settings.
</span><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   2025-02-10 22:21:51 UTC (rev 59801)
+++ trunk/tests/phpunit/tests/theme/wpThemeJson.php     2025-02-10 22:27:49 UTC (rev 59802)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -4707,6 +4707,190 @@
</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">+         * Test ensures that inner block type styles and their element styles are
+        * preserved for block style variations when removing insecure properties.
+        *
+        * @ticket 62372
+        */
+       public function test_block_style_variations_with_inner_blocks_and_elements() {
+               wp_set_current_user( static::$administrator_id );
+               register_block_style(
+                       array( 'core/group' ),
+                       array(
+                               'name'  => 'custom-group',
+                               'label' => 'Custom Group',
+                       )
+               );
+
+               $expected = array(
+                       'version' => WP_Theme_JSON::LATEST_SCHEMA,
+                       'styles'  => array(
+                               'blocks' => array(
+                                       'core/group' => array(
+                                               'color'      => array(
+                                                       'background' => 'blue',
+                                               ),
+                                               'variations' => array(
+                                                       'custom-group' => array(
+                                                               'color'    => array(
+                                                                       'background' => 'purple',
+                                                               ),
+                                                               'blocks'   => array(
+                                                                       'core/paragraph' => array(
+                                                                               'color'    => array(
+                                                                                       'text' => 'red',
+                                                                               ),
+                                                                               'elements' => array(
+                                                                                       'link' => array(
+                                                                                               'color'  => array(
+                                                                                                       'text' => 'blue',
+                                                                                               ),
+                                                                                               ':hover' => array(
+                                                                                                       'color' => array(
+                                                                                                               'text' => 'green',
+                                                                                                       ),
+                                                                                               ),
+                                                                                       ),
+                                                                               ),
+                                                                       ),
+                                                                       'core/heading'   => array(
+                                                                               'typography' => array(
+                                                                                       'fontSize' => '24px',
+                                                                               ),
+                                                                       ),
+                                                               ),
+                                                               'elements' => array(
+                                                                       'link' => array(
+                                                                               'color'  => array(
+                                                                                       'text' => 'yellow',
+                                                                               ),
+                                                                               ':hover' => array(
+                                                                                       'color' => array(
+                                                                                               'text' => 'orange',
+                                                                                       ),
+                                                                               ),
+                                                                       ),
+                                                               ),
+                                                       ),
+                                               ),
+                                       ),
+                               ),
+                       ),
+               );
+
+               $actual = WP_Theme_JSON::remove_insecure_properties( $expected );
+
+               // The sanitization processes blocks in a specific order which might differ to the theme.json input.
+               $this->assertEqualsCanonicalizing(
+                       $expected,
+                       $actual,
+                       'Block style variations data does not match when inner blocks or element styles present'
+               );
+       }
+
+       /**
+        * Test ensures that inner block type styles and their element styles for block
+        * style variations have all unsafe values removed.
+        *
+        * @ticket 62372
+        */
+       public function test_block_style_variations_with_invalid_inner_block_or_element_styles() {
+               wp_set_current_user( static::$administrator_id );
+               register_block_style(
+                       array( 'core/group' ),
+                       array(
+                               'name'  => 'custom-group',
+                               'label' => 'Custom Group',
+                       )
+               );
+
+               $input = array(
+                       'version' => WP_Theme_JSON::LATEST_SCHEMA,
+                       'styles'  => array(
+                               'blocks' => array(
+                                       'core/group' => array(
+                                               'variations' => array(
+                                                       'custom-group' => array(
+                                                               'blocks'   => array(
+                                                                       'core/paragraph' => array(
+                                                                               'color'      => array(
+                                                                                       'text' => 'red',
+                                                                               ),
+                                                                               'typography' => array(
+                                                                                       'fontSize' => 'alert(1)', // Should be removed.
+                                                                               ),
+                                                                               'elements'   => array(
+                                                                                       'link' => array(
+                                                                                               'color' => array(
+                                                                                                       'text' => 'blue',
+                                                                                               ),
+                                                                                               'css'   => 'unsafe-value', // Should be removed.
+                                                                                       ),
+                                                                               ),
+                                                                               'custom'     => 'unsafe-value', // Should be removed.
+                                                                       ),
+                                                               ),
+                                                               'elements' => array(
+                                                                       'link' => array(
+                                                                               'color'      => array(
+                                                                                       'text' => 'yellow',
+                                                                               ),
+                                                                               'javascript' => 'alert(1)', // Should be removed.
+                                                                       ),
+                                                               ),
+                                                       ),
+                                               ),
+                                       ),
+                               ),
+                       ),
+               );
+
+               $expected = array(
+                       'version' => WP_Theme_JSON::LATEST_SCHEMA,
+                       'styles'  => array(
+                               'blocks' => array(
+                                       'core/group' => array(
+                                               'variations' => array(
+                                                       'custom-group' => array(
+                                                               'blocks'   => array(
+                                                                       'core/paragraph' => array(
+                                                                               'color'    => array(
+                                                                                       'text' => 'red',
+                                                                               ),
+                                                                               'elements' => array(
+                                                                                       'link' => array(
+                                                                                               'color' => array(
+                                                                                                       'text' => 'blue',
+                                                                                               ),
+                                                                                       ),
+                                                                               ),
+                                                                       ),
+                                                               ),
+                                                               'elements' => array(
+                                                                       'link' => array(
+                                                                               'color' => array(
+                                                                                       'text' => 'yellow',
+                                                                               ),
+                                                                       ),
+                                                               ),
+                                                       ),
+                                               ),
+                                       ),
+                               ),
+                       ),
+               );
+
+               $actual = WP_Theme_JSON::remove_insecure_properties( $input );
+
+               // The sanitization processes blocks in a specific order which might differ to the theme.json input.
+               $this->assertEqualsCanonicalizing(
+                       $expected,
+                       $actual,
+                       'Insecure properties were not removed from block style variation inner block types or elements'
+               );
+       }
+
+       /**
</ins><span class="cx" style="display: block; padding: 0 10px">          * Tests generating the spacing presets array based on the spacing scale provided.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @ticket 56467
</span></span></pre>
</div>
</div>

</body>
</html>