diff --git a/src/wp-includes/html-api/class-wp-html-tag-processor.php b/src/wp-includes/html-api/class-wp-html-tag-processor.php index 8fc75938c9384..5ecb756709d9b 100644 --- a/src/wp-includes/html-api/class-wp-html-tag-processor.php +++ b/src/wp-includes/html-api/class-wp-html-tag-processor.php @@ -679,12 +679,14 @@ class WP_HTML_Tag_Processor { * // Add the `wp-block-group` class, remove the `wp-group` class. * $classname_updates = array( * // Indexed by a comparable class name. - * 'wp-block-group' => WP_HTML_Tag_Processor::ADD_CLASS, - * 'wp-group' => WP_HTML_Tag_Processor::REMOVE_CLASS + * 'wp-block-group' => [ WP_HTML_Tag_Processor::ADD_CLASS, 'wp-block-group'], + * 'wp-group' => [ WP_HTML_Tag_Processor::REMOVE_CLASS ], * ); * * @since 6.2.0 - * @var bool[] + * @since 6.7.0 Changed the structure of values to an array + * + * @var array[] */ private $classname_updates = array(); @@ -2203,13 +2205,13 @@ private function class_name_updates_to_attributes_updates() { // If this class is marked for removal, start processing the next one. $remove_class = ( - isset( $this->classname_updates[ $name ] ) && - self::REMOVE_CLASS === $this->classname_updates[ $name ] + isset( $this->classname_updates[ strtolower( $name ) ] ) && + self::REMOVE_CLASS === $this->classname_updates[ strtolower( $name ) ][0] ); // If a class has already been seen then skip it; it should not be added twice. if ( ! $remove_class ) { - $this->classname_updates[ $name ] = self::SKIP_CLASS; + $this->classname_updates[ strtolower( $name ) ] = array( self::SKIP_CLASS ); } if ( $remove_class ) { @@ -2234,12 +2236,12 @@ private function class_name_updates_to_attributes_updates() { } // Add new classes by appending those which haven't already been seen. - foreach ( $this->classname_updates as $name => $operation ) { - if ( self::ADD_CLASS === $operation ) { + foreach ( $this->classname_updates as $operation ) { + if ( self::ADD_CLASS === $operation[0] ) { $modified = true; $class .= strlen( $class ) > 0 ? ' ' : ''; - $class .= $name; + $class .= $operation[1]; } } @@ -3126,7 +3128,10 @@ public function remove_attribute( $name ) { /** * Adds a new class name to the currently matched tag. * + * Adds unique CSS class names. Case insensitive duplicate classes will not be added. + * * @since 6.2.0 + * @since 6.7.0 Class name matching for uniqueness is case-insensitive. * * @param string $class_name The class name to add. * @return bool Whether the class was set to be added. @@ -3139,7 +3144,7 @@ public function add_class( $class_name ) { return false; } - $this->classname_updates[ $class_name ] = self::ADD_CLASS; + $this->classname_updates[ strtolower( $class_name ) ] = array( self::ADD_CLASS, $class_name ); return true; } @@ -3147,7 +3152,10 @@ public function add_class( $class_name ) { /** * Removes a class name from the currently matched tag. * + * Remove matching CSS class names. Case-insensitive matching class names will be removed. + * * @since 6.2.0 + * @since 6.7.0 Class name matching for removal is case-insensitive. * * @param string $class_name The class name to remove. * @return bool Whether the class was set to be removed. @@ -3161,7 +3169,7 @@ public function remove_class( $class_name ) { } if ( null !== $this->tag_name_starts_at ) { - $this->classname_updates[ $class_name ] = self::REMOVE_CLASS; + $this->classname_updates[ strtolower( $class_name ) ] = array( self::REMOVE_CLASS ); } return true; diff --git a/tests/phpunit/tests/html-api/wpHtmlTagProcessor.php b/tests/phpunit/tests/html-api/wpHtmlTagProcessor.php index fad1000dd763c..c040f61ea0449 100644 --- a/tests/phpunit/tests/html-api/wpHtmlTagProcessor.php +++ b/tests/phpunit/tests/html-api/wpHtmlTagProcessor.php @@ -2875,4 +2875,54 @@ public function insert_after( $new_html ) { 'Should have properly applied the update from in front of the cursor.' ); } + + /** + * @ticket 61531 + * + * @covers ::add_class + */ + public function test_add_class_does_not_duplicate_case_insensitive_classes() { + $processor = new WP_HTML_Tag_Processor( '
' ); + $processor->next_tag(); + $processor->add_class( 'upper' ); + $processor->add_class( 'LOWER' ); + $this->assertSame( + '
', + $processor->get_updated_html(), + '::add_class added case-insensitive duplicate class names.' + ); + } + + /** + * @ticket 61531 + * + * @covers ::add_class + */ + public function test_add_class_respects_provided_casing() { + $processor = new WP_HTML_Tag_Processor( '
' ); + $processor->next_tag(); + $processor->add_class( 'mIxEd-CaSiNg' ); + $this->assertSame( + '
', + $processor->get_updated_html(), + '::add_class did not respect the provided class name casing.' + ); + } + + /** + * @ticket 61531 + * + * @covers ::remove_class + */ + public function test_remove_class_removes_case_insensitive_matches() { + $processor = new WP_HTML_Tag_Processor( '
' ); + $processor->next_tag(); + $processor->remove_class( 'remove-a' ); + $processor->remove_class( 'REMOVE-B' ); + $this->assertSame( + '
', + $processor->get_updated_html(), + '::remove_class did not remove case-insensitive class name matches.' + ); + } }