diff --git a/src/wp-includes/block-supports/dimensions.php b/src/wp-includes/block-supports/dimensions.php index da68f187c3915..9f8236ac663f2 100644 --- a/src/wp-includes/block-supports/dimensions.php +++ b/src/wp-includes/block-supports/dimensions.php @@ -143,7 +143,7 @@ function wp_render_dimensions_support( $block_content, $block ) { $tags->set_attribute( 'style', $updated_style ); if ( ! empty( $styles['classnames'] ) ) { - foreach ( explode( ' ', $styles['classnames'] ) as $class_name ) { + foreach ( WP_HTML_Tag_Processor::parse_class_list( $styles['classnames'] ) as $class_name ) { if ( str_contains( $class_name, 'aspect-ratio' ) && ! isset( $block_attributes['style']['dimensions']['aspectRatio'] ) diff --git a/src/wp-includes/bookmark-template.php b/src/wp-includes/bookmark-template.php index 893494a7e92cd..09980b8cc8790 100644 --- a/src/wp-includes/bookmark-template.php +++ b/src/wp-includes/bookmark-template.php @@ -234,11 +234,11 @@ function wp_list_bookmarks( $args = '' ) { $output = ''; - if ( ! is_array( $parsed_args['class'] ) ) { - $parsed_args['class'] = explode( ' ', $parsed_args['class'] ); + if ( is_string( $parsed_args['class'] ) ) { + $parsed_args['class'] = iterator_to_array( WP_HTML_Tag_Processor::parse_class_list( $parsed_args['class'] ) ); } $parsed_args['class'] = array_map( 'sanitize_html_class', $parsed_args['class'] ); - $parsed_args['class'] = trim( implode( ' ', $parsed_args['class'] ) ); + $parsed_args['class'] = implode( ' ', $parsed_args['class'] ); if ( $parsed_args['categorize'] ) { $cats = get_terms( diff --git a/src/wp-includes/class-wp-duotone.php b/src/wp-includes/class-wp-duotone.php index 7c7416b4399bc..c0c5a4f1897eb 100644 --- a/src/wp-includes/class-wp-duotone.php +++ b/src/wp-includes/class-wp-duotone.php @@ -1180,8 +1180,7 @@ public static function restore_image_outer_container( $block_content ) { $tags->set_bookmark( 'wrapper-div' ); $tags->next_tag(); - $inner_classnames = explode( ' ', $tags->get_attribute( 'class' ) ); - foreach ( $inner_classnames as $classname ) { + foreach ( WP_HTML_Tag_Processor::parse_class_list( $tags->get_attribute( 'class' ) ) as $classname ) { if ( 0 === strpos( $classname, 'wp-duotone' ) ) { $tags->remove_class( $classname ); $tags->seek( 'wrapper-div' ); diff --git a/src/wp-includes/customize/class-wp-customize-nav-menu-item-setting.php b/src/wp-includes/customize/class-wp-customize-nav-menu-item-setting.php index 07159e0d54d05..8ca5a20b19a70 100644 --- a/src/wp-includes/customize/class-wp-customize-nav-menu-item-setting.php +++ b/src/wp-includes/customize/class-wp-customize-nav-menu-item-setting.php @@ -617,7 +617,7 @@ public function value_as_wp_post_nav_menu_item() { // 'classes' should be an array, as in wp_setup_nav_menu_item(). if ( isset( $item->classes ) && is_scalar( $item->classes ) ) { - $item->classes = explode( ' ', $item->classes ); + $item->classes = iterator_to_array( WP_HTML_Tag_Processor::parse_class_list( $item->classes ) ); } $item->ID = $this->post_id; 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 31c4bc8a10654..3c9f247790822 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 @@ -1187,25 +1187,70 @@ public function class_list() { return; } - $seen = array(); + return self::parse_class_list( $class, $this->compat_mode ); + } - $is_quirks = self::QUIRKS_MODE === $this->compat_mode; + /** + * Generator for a foreach loop to step through each class name for the matched tag. + * + * This generator function is designed to be used inside a "foreach" loop. + * + * Example: + * + * $class_list = 'free <egg<\tlang-en'; + * foreach ( WP_HTML_Tag_Processor::parse_class_list( $class_list ) as $class_name ) { + * echo "{$class_name} "; + * } + * // Outputs: "free lang-en " + * + * The default behavior is normative for HTML5 documents in “no-quirks” mode. For + * rare documents with “quirks mode” DOCTYPE declarations, pass {@see self::QUIRKS_MODE} + * as the compatibility mode for ASCII-case-insensitive comparison of class names. Use + * this only when certain that the containing document is in no-quirks mode. + * + * Example: + * + * $class_list = 'wide naRRow WIDE Wide narrow'; + * $classes = WP_HTML_Tag_Processor::parse_class_list( $class_list ); + * $classes = iterator_to_array( $classes ); + * $classes === array( 'wide', 'naRRow', 'WIDE', 'Wide', 'narrow' ); + * + * $class_list = 'wide WIDE Wide'; + * $classes = WP_HTML_Tag_Processor::parse_class_list( $class_list, WP_HTML_Tag_Processor::QUIRKS_MODE ); + * $classes = iterator_to_array( $classes ); + * $classes === array( 'wide', 'naRRow' ); + * + * @since 6.9.0 + * + * @param string $class_list Contains a full decoded HTML `class` attribute, or plain + * list of space-separated CSS class names. + * @param string|null $compat_mode Optional. Specifies how to compare class names, whether + * byte-for-byte or ASCII-case-insensitively. Default is + * NO_QUIRKS_MODE, which compares byte for byte. + * @return Generator Iterates over each unique CSS class name in the given input list in order. + */ + public static function parse_class_list( $class_list, $compat_mode = self::NO_QUIRKS_MODE ) { + if ( '' === $class_list || ! is_string( $class_list ) ) { + return; + } - $at = 0; - while ( $at < strlen( $class ) ) { + $seen = array(); + $is_quirks = self::QUIRKS_MODE === $compat_mode; + $at = 0; + while ( $at < strlen( $class_list ) ) { // Skip past any initial boundary characters. - $at += strspn( $class, " \t\f\r\n", $at ); - if ( $at >= strlen( $class ) ) { + $at += strspn( $class_list, " \t\f\r\n", $at ); + if ( $at >= strlen( $class_list ) ) { return; } // Find the byte length until the next boundary. - $length = strcspn( $class, " \t\f\r\n", $at ); + $length = strcspn( $class_list, " \t\f\r\n", $at ); if ( 0 === $length ) { return; } - $name = str_replace( "\x00", "\u{FFFD}", substr( $class, $at, $length ) ); + $name = str_replace( "\x00", "\u{FFFD}", substr( $class_list, $at, $length ) ); if ( $is_quirks ) { $name = strtolower( $name ); } diff --git a/src/wp-includes/nav-menu.php b/src/wp-includes/nav-menu.php index d808c4e212d39..fafd7b246cb89 100644 --- a/src/wp-includes/nav-menu.php +++ b/src/wp-includes/nav-menu.php @@ -594,7 +594,7 @@ function wp_update_nav_menu_item( $menu_id = 0, $menu_item_db_id = 0, $menu_item update_post_meta( $menu_item_db_id, '_menu_item_object', sanitize_key( $args['menu-item-object'] ) ); update_post_meta( $menu_item_db_id, '_menu_item_target', sanitize_key( $args['menu-item-target'] ) ); - $args['menu-item-classes'] = array_map( 'sanitize_html_class', explode( ' ', $args['menu-item-classes'] ) ); + $args['menu-item-classes'] = array_map( 'sanitize_html_class', iterator_to_array( WP_HTML_Tag_Processor::parse_class_list( $args['menu-item-classes'] ) ) ); $args['menu-item-xfn'] = implode( ' ', array_map( 'sanitize_html_class', explode( ' ', $args['menu-item-xfn'] ) ) ); update_post_meta( $menu_item_db_id, '_menu_item_classes', $args['menu-item-classes'] ); update_post_meta( $menu_item_db_id, '_menu_item_xfn', $args['menu-item-xfn'] ); diff --git a/src/wp-includes/post-template.php b/src/wp-includes/post-template.php index 3d82ba81b641b..9be56fa88972d 100644 --- a/src/wp-includes/post-template.php +++ b/src/wp-includes/post-template.php @@ -496,18 +496,15 @@ function get_post_class( $css_class = '', $post = null ) { $classes = array(); - if ( $css_class ) { - if ( ! is_array( $css_class ) ) { - $css_class = preg_split( '#\s+#', $css_class ); - } - $classes = array_map( 'esc_attr', $css_class ); - } else { + if ( is_string( $css_class ) ) { + $classes = iterator_to_array( WP_HTML_Tag_Processor::parse_class_list( $css_class ) ); + } elseif ( ! is_array( $css_class ) ) { // Ensure that we always coerce class to being an array. - $css_class = array(); + $classes = array(); } if ( ! $post ) { - return $classes; + return array_values( array_unique( array_map( 'esc_attr', $classes ) ) ); } $classes[] = 'post-' . $post->ID; @@ -593,8 +590,6 @@ function get_post_class( $css_class = '', $post = null ) { } } - $classes = array_map( 'esc_attr', $classes ); - /** * Filters the list of CSS class names for the current post. * @@ -606,10 +601,9 @@ function get_post_class( $css_class = '', $post = null ) { */ $classes = apply_filters( 'post_class', $classes, $css_class, $post->ID ); - $classes = array_unique( $classes ); - $classes = array_values( $classes ); + $classes = array_map( 'esc_attr', $classes ); - return $classes; + return array_values( array_unique( $classes ) ); } /**