From a5fc134e24bbc3b233c31abc6445c617d5279ff4 Mon Sep 17 00:00:00 2001
From: Pantheon Automation <bot@getpantheon.com>
Date: Mon, 24 Jun 2024 20:58:23 +0000
Subject: [PATCH] Update to WordPress 6.5.5. For more information, see
 https://wordpress.org/news/2024/06/wordpress-6-5-5/

---
 wp-admin/about.php                            | 27 +++++++++++++-
 wp-admin/includes/plugin-install.php          | 16 ++-------
 wp-includes/blocks.php                        | 36 ++++++++++++++++---
 wp-includes/blocks/template-part.php          |  2 +-
 wp-includes/fonts.php                         |  2 +-
 wp-includes/formatting.php                    |  3 +-
 wp-includes/functions.php                     |  3 ++
 .../html-api/class-wp-html-tag-processor.php  |  9 ++++-
 .../class-wp-rest-font-faces-controller.php   |  4 +--
 wp-includes/version.php                       |  2 +-
 10 files changed, 79 insertions(+), 25 deletions(-)

diff --git a/wp-admin/about.php b/wp-admin/about.php
index 5e07e3c931..a004fb17a4 100644
--- a/wp-admin/about.php
+++ b/wp-admin/about.php
@@ -44,6 +44,31 @@
 		<div class="about__section changelog has-subtle-background-color">
 			<div class="column">
 				<h2><?php _e( 'Maintenance and Security Release' ); ?></h2>
+				<p>
+					<?php
+					printf(
+						/* translators: 1: WordPress version number, 2: Plural number of bugs. */
+						_n(
+							'<strong>Version %1$s</strong> addressed some security issues and fixed %2$s bug.',
+							'<strong>Version %1$s</strong> addressed some security issues and fixed %2$s bugs.',
+							3
+						),
+						'6.5.5',
+						'3'
+					);
+					?>
+					<?php
+					printf(
+						/* translators: %s: HelpHub URL. */
+						__( 'For more information, see <a href="%s">the release notes</a>.' ),
+						sprintf(
+							/* translators: %s: WordPress version. */
+							esc_url( __( 'https://wordpress.org/support/wordpress-version/version-%s/' ) ),
+							sanitize_title( '6.5.5' )
+						)
+					);
+					?>
+				</p>
 				<p>
 					<?php
 					printf(
@@ -68,7 +93,7 @@
 						)
 					);
 					?>
-				</p>				
+				</p>
 				<p>
 					<?php
 					printf(
diff --git a/wp-admin/includes/plugin-install.php b/wp-admin/includes/plugin-install.php
index a3afbcb8e9..704d81b9a6 100644
--- a/wp-admin/includes/plugin-install.php
+++ b/wp-admin/includes/plugin-install.php
@@ -917,7 +917,7 @@ function install_plugin_information() {
  * }
  * @param bool         $compatible_php   The result of a PHP compatibility check.
  * @param bool         $compatible_wp    The result of a WP compatibility check.
- * @return string $button The markup for the dependency row button.
+ * @return string The markup for the dependency row button. An empty string if the user does not have capabilities.
  */
 function wp_get_plugin_action_button( $name, $data, $compatible_php, $compatible_wp ) {
 	$button           = '';
@@ -947,16 +947,6 @@ function wp_get_plugin_action_button( $name, $data, $compatible_php, $compatible
 	$all_plugin_dependencies_installed = $installed_plugin_dependencies_count === $plugin_dependencies_count;
 	$all_plugin_dependencies_active    = $active_plugin_dependencies_count === $plugin_dependencies_count;
 
-	sprintf(
-		'<a class="install-now button" data-slug="%s" href="%s" aria-label="%s" data-name="%s">%s</a>',
-		esc_attr( $data->slug ),
-		esc_url( $status['url'] ),
-		/* translators: %s: Plugin name and version. */
-		esc_attr( sprintf( _x( 'Install %s now', 'plugin' ), $name ) ),
-		esc_attr( $name ),
-		_x( 'Install Now', 'plugin' )
-	);
-
 	if ( current_user_can( 'install_plugins' ) || current_user_can( 'update_plugins' ) ) {
 		switch ( $status['status'] ) {
 			case 'install':
@@ -1053,7 +1043,7 @@ function wp_get_plugin_action_button( $name, $data, $compatible_php, $compatible
 				}
 				break;
 		}
-
-		return $button;
 	}
+
+	return $button;
 }
diff --git a/wp-includes/blocks.php b/wp-includes/blocks.php
index 854992de57..5b452c69c4 100644
--- a/wp-includes/blocks.php
+++ b/wp-includes/blocks.php
@@ -1447,7 +1447,7 @@ function _filter_block_content_callback( $matches ) {
  * @return array The filtered and sanitized block object result.
  */
 function filter_block_kses( $block, $allowed_html, $allowed_protocols = array() ) {
-	$block['attrs'] = filter_block_kses_value( $block['attrs'], $allowed_html, $allowed_protocols );
+	$block['attrs'] = filter_block_kses_value( $block['attrs'], $allowed_html, $allowed_protocols, $block );
 
 	if ( is_array( $block['innerBlocks'] ) ) {
 		foreach ( $block['innerBlocks'] as $i => $inner_block ) {
@@ -1463,6 +1463,7 @@ function filter_block_kses( $block, $allowed_html, $allowed_protocols = array()
  * non-allowable HTML.
  *
  * @since 5.3.1
+ * @since 6.5.5 Added the `$block_context` parameter.
  *
  * @param string[]|string $value             The attribute value to filter.
  * @param array[]|string  $allowed_html      An array of allowed HTML elements and attributes,
@@ -1470,13 +1471,18 @@ function filter_block_kses( $block, $allowed_html, $allowed_protocols = array()
  *                                           for the list of accepted context names.
  * @param string[]        $allowed_protocols Optional. Array of allowed URL protocols.
  *                                           Defaults to the result of wp_allowed_protocols().
+ * @param array           $block_context     Optional. The block the attribute belongs to, in parsed block array format.
  * @return string[]|string The filtered and sanitized result.
  */
-function filter_block_kses_value( $value, $allowed_html, $allowed_protocols = array() ) {
+function filter_block_kses_value( $value, $allowed_html, $allowed_protocols = array(), $block_context = null ) {
 	if ( is_array( $value ) ) {
 		foreach ( $value as $key => $inner_value ) {
-			$filtered_key   = filter_block_kses_value( $key, $allowed_html, $allowed_protocols );
-			$filtered_value = filter_block_kses_value( $inner_value, $allowed_html, $allowed_protocols );
+			$filtered_key   = filter_block_kses_value( $key, $allowed_html, $allowed_protocols, $block_context );
+			$filtered_value = filter_block_kses_value( $inner_value, $allowed_html, $allowed_protocols, $block_context );
+
+			if ( isset( $block_context['blockName'] ) && 'core/template-part' === $block_context['blockName'] ) {
+				$filtered_value = filter_block_core_template_part_attributes( $filtered_value, $filtered_key, $allowed_html );
+			}
 
 			if ( $filtered_key !== $key ) {
 				unset( $value[ $key ] );
@@ -1491,6 +1497,28 @@ function filter_block_kses_value( $value, $allowed_html, $allowed_protocols = ar
 	return $value;
 }
 
+/**
+ * Sanitizes the value of the Template Part block's `tagName` attribute.
+ *
+ * @since 6.5.5
+ *
+ * @param string          $attribute_value   The attribute value to filter.
+ * @param string          $attribute_name    The attribute name.
+ * @param array[]|string  $allowed_html      An array of allowed HTML elements and attributes,
+ *                                           or a context name such as 'post'. See wp_kses_allowed_html()
+ *                                           for the list of accepted context names.
+ * @return string The sanitized attribute value.
+ */
+function filter_block_core_template_part_attributes( $attribute_value, $attribute_name, $allowed_html ) {
+	if ( empty( $attribute_value ) || 'tagName' !== $attribute_name ) {
+		return $attribute_value;
+	}
+	if ( ! is_array( $allowed_html ) ) {
+		$allowed_html = wp_kses_allowed_html( $allowed_html );
+	}
+	return isset( $allowed_html[ $attribute_value ] ) ? $attribute_value : '';
+}
+
 /**
  * Parses blocks out of a content string, and renders those appropriate for the excerpt.
  *
diff --git a/wp-includes/blocks/template-part.php b/wp-includes/blocks/template-part.php
index 8c01c5a4bc..4332bb305b 100644
--- a/wp-includes/blocks/template-part.php
+++ b/wp-includes/blocks/template-part.php
@@ -157,7 +157,7 @@ function render_block_core_template_part( $attributes ) {
 	global $wp_embed;
 	$content = $wp_embed->autoembed( $content );
 
-	if ( empty( $attributes['tagName'] ) ) {
+	if ( empty( $attributes['tagName'] ) || tag_escape( $attributes['tagName'] ) !== $attributes['tagName'] ) {
 		$area_tag = 'div';
 		if ( $area_definition && isset( $area_definition['area_tag'] ) ) {
 			$area_tag = $area_definition['area_tag'];
diff --git a/wp-includes/fonts.php b/wp-includes/fonts.php
index 4806662160..0d7cff55f7 100644
--- a/wp-includes/fonts.php
+++ b/wp-includes/fonts.php
@@ -228,7 +228,7 @@ function _wp_before_delete_font_face( $post_id, $post ) {
 	}
 
 	$font_files = get_post_meta( $post_id, '_wp_font_face_file', false );
-	$font_dir   = wp_get_font_dir()['path'];
+	$font_dir   = untrailingslashit( wp_get_font_dir()['basedir'] );
 
 	foreach ( $font_files as $font_file ) {
 		wp_delete_file( $font_dir . '/' . $font_file );
diff --git a/wp-includes/formatting.php b/wp-includes/formatting.php
index a756f6d12a..b097d000ef 100644
--- a/wp-includes/formatting.php
+++ b/wp-includes/formatting.php
@@ -4802,12 +4802,13 @@ static function ( $matches ) {
  * Escapes an HTML tag name.
  *
  * @since 2.5.0
+ * @since 6.5.5 Allow hyphens in tag names (i.e. custom elements).
  *
  * @param string $tag_name
  * @return string
  */
 function tag_escape( $tag_name ) {
-	$safe_tag = strtolower( preg_replace( '/[^a-zA-Z0-9_:]/', '', $tag_name ) );
+	$safe_tag = strtolower( preg_replace( '/[^a-zA-Z0-9-_:]/', '', $tag_name ) );
 	/**
 	 * Filters a string cleaned and escaped for output as an HTML tag.
 	 *
diff --git a/wp-includes/functions.php b/wp-includes/functions.php
index fd305be20b..f9d617660a 100644
--- a/wp-includes/functions.php
+++ b/wp-includes/functions.php
@@ -6192,6 +6192,9 @@ function validate_file( $file, $allowed_files = array() ) {
 		return 0;
 	}
 
+	// Normalize path for Windows servers
+	$file = wp_normalize_path( $file );
+
 	// `../` on its own is not allowed:
 	if ( '../' === $file ) {
 		return 1;
diff --git a/wp-includes/html-api/class-wp-html-tag-processor.php b/wp-includes/html-api/class-wp-html-tag-processor.php
index c540ea96c1..bf7e026bce 100644
--- a/wp-includes/html-api/class-wp-html-tag-processor.php
+++ b/wp-includes/html-api/class-wp-html-tag-processor.php
@@ -2968,7 +2968,14 @@ public function set_attribute( $name, $value ) {
 		if ( true === $value ) {
 			$updated_attribute = $name;
 		} else {
-			$escaped_new_value = esc_attr( $value );
+			$comparable_name = strtolower( $name );
+
+			/*
+			 * Escape URL attributes.
+			 *
+			 * @see https://html.spec.whatwg.org/#attributes-3
+			 */
+			$escaped_new_value = in_array( $comparable_name, wp_kses_uri_attributes() ) ? esc_url( $value ) : esc_attr( $value );
 			$updated_attribute = "{$name}=\"{$escaped_new_value}\"";
 		}
 
diff --git a/wp-includes/rest-api/endpoints/class-wp-rest-font-faces-controller.php b/wp-includes/rest-api/endpoints/class-wp-rest-font-faces-controller.php
index c7f72d4ec1..c448787fc7 100644
--- a/wp-includes/rest-api/endpoints/class-wp-rest-font-faces-controller.php
+++ b/wp-includes/rest-api/endpoints/class-wp-rest-font-faces-controller.php
@@ -916,8 +916,8 @@ protected function relative_fonts_path( $path ) {
 		$new_path = $path;
 
 		$fonts_dir = wp_get_font_dir();
-		if ( str_starts_with( $new_path, $fonts_dir['path'] ) ) {
-			$new_path = str_replace( $fonts_dir, '', $new_path );
+		if ( str_starts_with( $new_path, $fonts_dir['basedir'] ) ) {
+			$new_path = str_replace( $fonts_dir['basedir'], '', $new_path );
 			$new_path = ltrim( $new_path, '/' );
 		}
 
diff --git a/wp-includes/version.php b/wp-includes/version.php
index 33870bd802..bdcc71966d 100644
--- a/wp-includes/version.php
+++ b/wp-includes/version.php
@@ -16,7 +16,7 @@
  *
  * @global string $wp_version
  */
-$wp_version = '6.5.4';
+$wp_version = '6.5.5';
 
 /**
  * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.