wp/wp-includes/pluggable.php
changeset 22 8c2e4d02f4ef
parent 21 48c4eec2b7e6
--- a/wp/wp-includes/pluggable.php	Fri Sep 05 18:40:08 2025 +0200
+++ b/wp/wp-includes/pluggable.php	Fri Sep 05 18:52:52 2025 +0200
@@ -251,7 +251,8 @@
 			require_once ABSPATH . WPINC . '/PHPMailer/PHPMailer.php';
 			require_once ABSPATH . WPINC . '/PHPMailer/SMTP.php';
 			require_once ABSPATH . WPINC . '/PHPMailer/Exception.php';
-			$phpmailer = new PHPMailer\PHPMailer\PHPMailer( true );
+			require_once ABSPATH . WPINC . '/class-wp-phpmailer.php';
+			$phpmailer = new WP_PHPMailer( true );
 
 			$phpmailer::$validator = static function ( $email ) {
 				return (bool) is_email( $email );
@@ -597,7 +598,11 @@
 	 * @return WP_User|WP_Error WP_User object if the credentials are valid,
 	 *                          otherwise WP_Error.
 	 */
-	function wp_authenticate( $username, $password ) {
+	function wp_authenticate(
+		$username,
+		#[\SensitiveParameter]
+		$password
+	) {
 		$username = sanitize_user( $username );
 		$password = trim( $password );
 
@@ -688,6 +693,7 @@
 	 *
 	 * @param string $cookie Optional. If used, will validate contents instead of cookie's.
 	 * @param string $scheme Optional. The cookie scheme to use: 'auth', 'secure_auth', or 'logged_in'.
+	 *                       Note: This does *not* default to 'auth' like other cookie functions.
 	 * @return int|false User ID if valid cookie, false if invalid.
 	 */
 	function wp_validate_auth_cookie( $cookie = '', $scheme = '' ) {
@@ -710,9 +716,10 @@
 		$username   = $cookie_elements['username'];
 		$hmac       = $cookie_elements['hmac'];
 		$token      = $cookie_elements['token'];
-		$expired    = $cookie_elements['expiration'];
 		$expiration = $cookie_elements['expiration'];
 
+		$expired = (int) $expiration;
+
 		// Allow a grace period for POST and Ajax requests.
 		if ( wp_doing_ajax() || 'POST' === $_SERVER['REQUEST_METHOD'] ) {
 			$expired += HOUR_IN_SECONDS;
@@ -762,13 +769,17 @@
 			return false;
 		}
 
-		$pass_frag = substr( $user->user_pass, 8, 4 );
+		if ( str_starts_with( $user->user_pass, '$P$' ) || str_starts_with( $user->user_pass, '$2y$' ) ) {
+			// Retain previous behaviour of phpass or vanilla bcrypt hashed passwords.
+			$pass_frag = substr( $user->user_pass, 8, 4 );
+		} else {
+			// Otherwise, use a substring from the end of the hash to avoid dealing with potentially long hash prefixes.
+			$pass_frag = substr( $user->user_pass, -4 );
+		}
 
 		$key = wp_hash( $username . '|' . $pass_frag . '|' . $expiration . '|' . $token, $scheme );
 
-		// If ext/hash is not present, compat.php's hash_hmac() does not support sha256.
-		$algo = function_exists( 'hash' ) ? 'sha256' : 'sha1';
-		$hash = hash_hmac( $algo, $username . '|' . $expiration . '|' . $token, $key );
+		$hash = hash_hmac( 'sha256', $username . '|' . $expiration . '|' . $token, $key );
 
 		if ( ! hash_equals( $hash, $hmac ) ) {
 			/**
@@ -865,13 +876,17 @@
 			$token   = $manager->create( $expiration );
 		}
 
-		$pass_frag = substr( $user->user_pass, 8, 4 );
+		if ( str_starts_with( $user->user_pass, '$P$' ) || str_starts_with( $user->user_pass, '$2y$' ) ) {
+			// Retain previous behaviour of phpass or vanilla bcrypt hashed passwords.
+			$pass_frag = substr( $user->user_pass, 8, 4 );
+		} else {
+			// Otherwise, use a substring from the end of the hash to avoid dealing with potentially long hash prefixes.
+			$pass_frag = substr( $user->user_pass, -4 );
+		}
 
 		$key = wp_hash( $user->user_login . '|' . $pass_frag . '|' . $expiration . '|' . $token, $scheme );
 
-		// If ext/hash is not present, compat.php's hash_hmac() does not support sha256.
-		$algo = function_exists( 'hash' ) ? 'sha256' : 'sha1';
-		$hash = hash_hmac( $algo, $user->user_login . '|' . $expiration . '|' . $token, $key );
+		$hash = hash_hmac( 'sha256', $user->user_login . '|' . $expiration . '|' . $token, $key );
 
 		$cookie = $user->user_login . '|' . $expiration . '|' . $token . '|' . $hash;
 
@@ -1719,8 +1734,6 @@
 			$emails = array_flip( $emails );
 		}
 
-		$switched_locale = switch_to_locale( get_locale() );
-
 		$comment_author_domain = '';
 		if ( WP_Http::is_ip_address( $comment->comment_author_IP ) ) {
 			$comment_author_domain = gethostbyaddr( $comment->comment_author_IP );
@@ -1733,74 +1746,6 @@
 		$blogname        = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES );
 		$comment_content = wp_specialchars_decode( $comment->comment_content );
 
-		switch ( $comment->comment_type ) {
-			case 'trackback':
-				/* translators: %s: Post title. */
-				$notify_message = sprintf( __( 'New trackback on your post "%s"' ), $post->post_title ) . "\r\n";
-				/* translators: 1: Trackback/pingback website name, 2: Website IP address, 3: Website hostname. */
-				$notify_message .= sprintf( __( 'Website: %1$s (IP address: %2$s, %3$s)' ), $comment->comment_author, $comment->comment_author_IP, $comment_author_domain ) . "\r\n";
-				/* translators: %s: Trackback/pingback/comment author URL. */
-				$notify_message .= sprintf( __( 'URL: %s' ), $comment->comment_author_url ) . "\r\n";
-				/* translators: %s: Comment text. */
-				$notify_message .= sprintf( __( 'Comment: %s' ), "\r\n" . $comment_content ) . "\r\n\r\n";
-				$notify_message .= __( 'You can see all trackbacks on this post here:' ) . "\r\n";
-				/* translators: Trackback notification email subject. 1: Site title, 2: Post title. */
-				$subject = sprintf( __( '[%1$s] Trackback: "%2$s"' ), $blogname, $post->post_title );
-				break;
-
-			case 'pingback':
-				/* translators: %s: Post title. */
-				$notify_message = sprintf( __( 'New pingback on your post "%s"' ), $post->post_title ) . "\r\n";
-				/* translators: 1: Trackback/pingback website name, 2: Website IP address, 3: Website hostname. */
-				$notify_message .= sprintf( __( 'Website: %1$s (IP address: %2$s, %3$s)' ), $comment->comment_author, $comment->comment_author_IP, $comment_author_domain ) . "\r\n";
-				/* translators: %s: Trackback/pingback/comment author URL. */
-				$notify_message .= sprintf( __( 'URL: %s' ), $comment->comment_author_url ) . "\r\n";
-				/* translators: %s: Comment text. */
-				$notify_message .= sprintf( __( 'Comment: %s' ), "\r\n" . $comment_content ) . "\r\n\r\n";
-				$notify_message .= __( 'You can see all pingbacks on this post here:' ) . "\r\n";
-				/* translators: Pingback notification email subject. 1: Site title, 2: Post title. */
-				$subject = sprintf( __( '[%1$s] Pingback: "%2$s"' ), $blogname, $post->post_title );
-				break;
-
-			default: // Comments.
-				/* translators: %s: Post title. */
-				$notify_message = sprintf( __( 'New comment on your post "%s"' ), $post->post_title ) . "\r\n";
-				/* translators: 1: Comment author's name, 2: Comment author's IP address, 3: Comment author's hostname. */
-				$notify_message .= sprintf( __( 'Author: %1$s (IP address: %2$s, %3$s)' ), $comment->comment_author, $comment->comment_author_IP, $comment_author_domain ) . "\r\n";
-				/* translators: %s: Comment author email. */
-				$notify_message .= sprintf( __( 'Email: %s' ), $comment->comment_author_email ) . "\r\n";
-				/* translators: %s: Trackback/pingback/comment author URL. */
-				$notify_message .= sprintf( __( 'URL: %s' ), $comment->comment_author_url ) . "\r\n";
-
-				if ( $comment->comment_parent && user_can( $post->post_author, 'edit_comment', $comment->comment_parent ) ) {
-					/* translators: Comment moderation. %s: Parent comment edit URL. */
-					$notify_message .= sprintf( __( 'In reply to: %s' ), admin_url( "comment.php?action=editcomment&c={$comment->comment_parent}#wpbody-content" ) ) . "\r\n";
-				}
-
-				/* translators: %s: Comment text. */
-				$notify_message .= sprintf( __( 'Comment: %s' ), "\r\n" . $comment_content ) . "\r\n\r\n";
-				$notify_message .= __( 'You can see all comments on this post here:' ) . "\r\n";
-				/* translators: Comment notification email subject. 1: Site title, 2: Post title. */
-				$subject = sprintf( __( '[%1$s] Comment: "%2$s"' ), $blogname, $post->post_title );
-				break;
-		}
-
-		$notify_message .= get_permalink( $comment->comment_post_ID ) . "#comments\r\n\r\n";
-		/* translators: %s: Comment URL. */
-		$notify_message .= sprintf( __( 'Permalink: %s' ), get_comment_link( $comment ) ) . "\r\n";
-
-		if ( user_can( $post->post_author, 'edit_comment', $comment->comment_ID ) ) {
-			if ( EMPTY_TRASH_DAYS ) {
-				/* translators: Comment moderation. %s: Comment action URL. */
-				$notify_message .= sprintf( __( 'Trash it: %s' ), admin_url( "comment.php?action=trash&c={$comment->comment_ID}#wpbody-content" ) ) . "\r\n";
-			} else {
-				/* translators: Comment moderation. %s: Comment action URL. */
-				$notify_message .= sprintf( __( 'Delete it: %s' ), admin_url( "comment.php?action=delete&c={$comment->comment_ID}#wpbody-content" ) ) . "\r\n";
-			}
-			/* translators: Comment moderation. %s: Comment action URL. */
-			$notify_message .= sprintf( __( 'Spam it: %s' ), admin_url( "comment.php?action=spam&c={$comment->comment_ID}#wpbody-content" ) ) . "\r\n";
-		}
-
 		$wp_email = 'wordpress@' . preg_replace( '#^www\.#', '', wp_parse_url( network_home_url(), PHP_URL_HOST ) );
 
 		if ( '' === $comment->comment_author ) {
@@ -1823,26 +1768,6 @@
 		}
 
 		/**
-		 * Filters the comment notification email text.
-		 *
-		 * @since 1.5.2
-		 *
-		 * @param string $notify_message The comment notification email text.
-		 * @param string $comment_id     Comment ID as a numeric string.
-		 */
-		$notify_message = apply_filters( 'comment_notification_text', $notify_message, $comment->comment_ID );
-
-		/**
-		 * Filters the comment notification email subject.
-		 *
-		 * @since 1.5.2
-		 *
-		 * @param string $subject    The comment notification email subject.
-		 * @param string $comment_id Comment ID as a numeric string.
-		 */
-		$subject = apply_filters( 'comment_notification_subject', $subject, $comment->comment_ID );
-
-		/**
 		 * Filters the comment notification email headers.
 		 *
 		 * @since 1.5.2
@@ -1853,11 +1778,107 @@
 		$message_headers = apply_filters( 'comment_notification_headers', $message_headers, $comment->comment_ID );
 
 		foreach ( $emails as $email ) {
+			$user = get_user_by( 'email', $email );
+
+			if ( $user ) {
+				$switched_locale = switch_to_user_locale( $user->ID );
+			} else {
+				$switched_locale = switch_to_locale( get_locale() );
+			}
+
+			switch ( $comment->comment_type ) {
+				case 'trackback':
+					/* translators: %s: Post title. */
+					$notify_message = sprintf( __( 'New trackback on your post "%s"' ), $post->post_title ) . "\r\n";
+					/* translators: 1: Trackback/pingback website name, 2: Website IP address, 3: Website hostname. */
+					$notify_message .= sprintf( __( 'Website: %1$s (IP address: %2$s, %3$s)' ), $comment->comment_author, $comment->comment_author_IP, $comment_author_domain ) . "\r\n";
+					/* translators: %s: Trackback/pingback/comment author URL. */
+					$notify_message .= sprintf( __( 'URL: %s' ), $comment->comment_author_url ) . "\r\n";
+					/* translators: %s: Comment text. */
+					$notify_message .= sprintf( __( 'Comment: %s' ), "\r\n" . $comment_content ) . "\r\n\r\n";
+					$notify_message .= __( 'You can see all trackbacks on this post here:' ) . "\r\n";
+					/* translators: Trackback notification email subject. 1: Site title, 2: Post title. */
+					$subject = sprintf( __( '[%1$s] Trackback: "%2$s"' ), $blogname, $post->post_title );
+					break;
+
+				case 'pingback':
+					/* translators: %s: Post title. */
+					$notify_message = sprintf( __( 'New pingback on your post "%s"' ), $post->post_title ) . "\r\n";
+					/* translators: 1: Trackback/pingback website name, 2: Website IP address, 3: Website hostname. */
+					$notify_message .= sprintf( __( 'Website: %1$s (IP address: %2$s, %3$s)' ), $comment->comment_author, $comment->comment_author_IP, $comment_author_domain ) . "\r\n";
+					/* translators: %s: Trackback/pingback/comment author URL. */
+					$notify_message .= sprintf( __( 'URL: %s' ), $comment->comment_author_url ) . "\r\n";
+					/* translators: %s: Comment text. */
+					$notify_message .= sprintf( __( 'Comment: %s' ), "\r\n" . $comment_content ) . "\r\n\r\n";
+					$notify_message .= __( 'You can see all pingbacks on this post here:' ) . "\r\n";
+					/* translators: Pingback notification email subject. 1: Site title, 2: Post title. */
+					$subject = sprintf( __( '[%1$s] Pingback: "%2$s"' ), $blogname, $post->post_title );
+					break;
+
+				default: // Comments.
+					/* translators: %s: Post title. */
+					$notify_message = sprintf( __( 'New comment on your post "%s"' ), $post->post_title ) . "\r\n";
+					/* translators: 1: Comment author's name, 2: Comment author's IP address, 3: Comment author's hostname. */
+					$notify_message .= sprintf( __( 'Author: %1$s (IP address: %2$s, %3$s)' ), $comment->comment_author, $comment->comment_author_IP, $comment_author_domain ) . "\r\n";
+					/* translators: %s: Comment author email. */
+					$notify_message .= sprintf( __( 'Email: %s' ), $comment->comment_author_email ) . "\r\n";
+					/* translators: %s: Trackback/pingback/comment author URL. */
+					$notify_message .= sprintf( __( 'URL: %s' ), $comment->comment_author_url ) . "\r\n";
+
+					if ( $comment->comment_parent && user_can( $post->post_author, 'edit_comment', $comment->comment_parent ) ) {
+						/* translators: Comment moderation. %s: Parent comment edit URL. */
+						$notify_message .= sprintf( __( 'In reply to: %s' ), admin_url( "comment.php?action=editcomment&c={$comment->comment_parent}#wpbody-content" ) ) . "\r\n";
+					}
+
+					/* translators: %s: Comment text. */
+					$notify_message .= sprintf( __( 'Comment: %s' ), "\r\n" . $comment_content ) . "\r\n\r\n";
+					$notify_message .= __( 'You can see all comments on this post here:' ) . "\r\n";
+					/* translators: Comment notification email subject. 1: Site title, 2: Post title. */
+					$subject = sprintf( __( '[%1$s] Comment: "%2$s"' ), $blogname, $post->post_title );
+					break;
+			}
+
+			$notify_message .= get_permalink( $comment->comment_post_ID ) . "#comments\r\n\r\n";
+			/* translators: %s: Comment URL. */
+			$notify_message .= sprintf( __( 'Permalink: %s' ), get_comment_link( $comment ) ) . "\r\n";
+
+			if ( user_can( $post->post_author, 'edit_comment', $comment->comment_ID ) ) {
+				if ( EMPTY_TRASH_DAYS ) {
+					/* translators: Comment moderation. %s: Comment action URL. */
+					$notify_message .= sprintf( __( 'Trash it: %s' ), admin_url( "comment.php?action=trash&c={$comment->comment_ID}#wpbody-content" ) ) . "\r\n";
+				} else {
+					/* translators: Comment moderation. %s: Comment action URL. */
+					$notify_message .= sprintf( __( 'Delete it: %s' ), admin_url( "comment.php?action=delete&c={$comment->comment_ID}#wpbody-content" ) ) . "\r\n";
+				}
+				/* translators: Comment moderation. %s: Comment action URL. */
+				$notify_message .= sprintf( __( 'Spam it: %s' ), admin_url( "comment.php?action=spam&c={$comment->comment_ID}#wpbody-content" ) ) . "\r\n";
+			}
+
+			/**
+			 * Filters the comment notification email text.
+			 *
+			 * @since 1.5.2
+			 *
+			 * @param string $notify_message The comment notification email text.
+			 * @param string $comment_id     Comment ID as a numeric string.
+			 */
+			$notify_message = apply_filters( 'comment_notification_text', $notify_message, $comment->comment_ID );
+
+			/**
+			 * Filters the comment notification email subject.
+			 *
+			 * @since 1.5.2
+			 *
+			 * @param string $subject    The comment notification email subject.
+			 * @param string $comment_id Comment ID as a numeric string.
+			 */
+			$subject = apply_filters( 'comment_notification_subject', $subject, $comment->comment_ID );
+
 			wp_mail( $email, wp_specialchars_decode( $subject ), $notify_message, $message_headers );
-		}
-
-		if ( $switched_locale ) {
-			restore_previous_locale();
+
+			if ( $switched_locale ) {
+				restore_previous_locale();
+			}
 		}
 
 		return true;
@@ -1908,8 +1929,6 @@
 			}
 		}
 
-		$switched_locale = switch_to_locale( get_locale() );
-
 		$comment_author_domain = '';
 		if ( WP_Http::is_ip_address( $comment->comment_author_IP ) ) {
 			$comment_author_domain = gethostbyaddr( $comment->comment_author_IP );
@@ -1924,77 +1943,6 @@
 		$blogname        = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES );
 		$comment_content = wp_specialchars_decode( $comment->comment_content );
 
-		switch ( $comment->comment_type ) {
-			case 'trackback':
-				/* translators: %s: Post title. */
-				$notify_message  = sprintf( __( 'A new trackback on the post "%s" is waiting for your approval' ), $post->post_title ) . "\r\n";
-				$notify_message .= get_permalink( $comment->comment_post_ID ) . "\r\n\r\n";
-				/* translators: 1: Trackback/pingback website name, 2: Website IP address, 3: Website hostname. */
-				$notify_message .= sprintf( __( 'Website: %1$s (IP address: %2$s, %3$s)' ), $comment->comment_author, $comment->comment_author_IP, $comment_author_domain ) . "\r\n";
-				/* translators: %s: Trackback/pingback/comment author URL. */
-				$notify_message .= sprintf( __( 'URL: %s' ), $comment->comment_author_url ) . "\r\n";
-				$notify_message .= __( 'Trackback excerpt: ' ) . "\r\n" . $comment_content . "\r\n\r\n";
-				break;
-
-			case 'pingback':
-				/* translators: %s: Post title. */
-				$notify_message  = sprintf( __( 'A new pingback on the post "%s" is waiting for your approval' ), $post->post_title ) . "\r\n";
-				$notify_message .= get_permalink( $comment->comment_post_ID ) . "\r\n\r\n";
-				/* translators: 1: Trackback/pingback website name, 2: Website IP address, 3: Website hostname. */
-				$notify_message .= sprintf( __( 'Website: %1$s (IP address: %2$s, %3$s)' ), $comment->comment_author, $comment->comment_author_IP, $comment_author_domain ) . "\r\n";
-				/* translators: %s: Trackback/pingback/comment author URL. */
-				$notify_message .= sprintf( __( 'URL: %s' ), $comment->comment_author_url ) . "\r\n";
-				$notify_message .= __( 'Pingback excerpt: ' ) . "\r\n" . $comment_content . "\r\n\r\n";
-				break;
-
-			default: // Comments.
-				/* translators: %s: Post title. */
-				$notify_message  = sprintf( __( 'A new comment on the post "%s" is waiting for your approval' ), $post->post_title ) . "\r\n";
-				$notify_message .= get_permalink( $comment->comment_post_ID ) . "\r\n\r\n";
-				/* translators: 1: Comment author's name, 2: Comment author's IP address, 3: Comment author's hostname. */
-				$notify_message .= sprintf( __( 'Author: %1$s (IP address: %2$s, %3$s)' ), $comment->comment_author, $comment->comment_author_IP, $comment_author_domain ) . "\r\n";
-				/* translators: %s: Comment author email. */
-				$notify_message .= sprintf( __( 'Email: %s' ), $comment->comment_author_email ) . "\r\n";
-				/* translators: %s: Trackback/pingback/comment author URL. */
-				$notify_message .= sprintf( __( 'URL: %s' ), $comment->comment_author_url ) . "\r\n";
-
-				if ( $comment->comment_parent ) {
-					/* translators: Comment moderation. %s: Parent comment edit URL. */
-					$notify_message .= sprintf( __( 'In reply to: %s' ), admin_url( "comment.php?action=editcomment&c={$comment->comment_parent}#wpbody-content" ) ) . "\r\n";
-				}
-
-				/* translators: %s: Comment text. */
-				$notify_message .= sprintf( __( 'Comment: %s' ), "\r\n" . $comment_content ) . "\r\n\r\n";
-				break;
-		}
-
-		/* translators: Comment moderation. %s: Comment action URL. */
-		$notify_message .= sprintf( __( 'Approve it: %s' ), admin_url( "comment.php?action=approve&c={$comment_id}#wpbody-content" ) ) . "\r\n";
-
-		if ( EMPTY_TRASH_DAYS ) {
-			/* translators: Comment moderation. %s: Comment action URL. */
-			$notify_message .= sprintf( __( 'Trash it: %s' ), admin_url( "comment.php?action=trash&c={$comment_id}#wpbody-content" ) ) . "\r\n";
-		} else {
-			/* translators: Comment moderation. %s: Comment action URL. */
-			$notify_message .= sprintf( __( 'Delete it: %s' ), admin_url( "comment.php?action=delete&c={$comment_id}#wpbody-content" ) ) . "\r\n";
-		}
-
-		/* translators: Comment moderation. %s: Comment action URL. */
-		$notify_message .= sprintf( __( 'Spam it: %s' ), admin_url( "comment.php?action=spam&c={$comment_id}#wpbody-content" ) ) . "\r\n";
-
-		$notify_message .= sprintf(
-			/* translators: Comment moderation. %s: Number of comments awaiting approval. */
-			_n(
-				'Currently %s comment is waiting for approval. Please visit the moderation panel:',
-				'Currently %s comments are waiting for approval. Please visit the moderation panel:',
-				$comments_waiting
-			),
-			number_format_i18n( $comments_waiting )
-		) . "\r\n";
-		$notify_message .= admin_url( 'edit-comments.php?comment_status=moderated#wpbody-content' ) . "\r\n";
-
-		/* translators: Comment moderation notification email subject. 1: Site title, 2: Post title. */
-		$subject         = sprintf( __( '[%1$s] Please moderate: "%2$s"' ), $blogname, $post->post_title );
 		$message_headers = '';
 
 		/**
@@ -2008,26 +1956,6 @@
 		$emails = apply_filters( 'comment_moderation_recipients', $emails, $comment_id );
 
 		/**
-		 * Filters the comment moderation email text.
-		 *
-		 * @since 1.5.2
-		 *
-		 * @param string $notify_message Text of the comment moderation email.
-		 * @param int    $comment_id     Comment ID.
-		 */
-		$notify_message = apply_filters( 'comment_moderation_text', $notify_message, $comment_id );
-
-		/**
-		 * Filters the comment moderation email subject.
-		 *
-		 * @since 1.5.2
-		 *
-		 * @param string $subject    Subject of the comment moderation email.
-		 * @param int    $comment_id Comment ID.
-		 */
-		$subject = apply_filters( 'comment_moderation_subject', $subject, $comment_id );
-
-		/**
 		 * Filters the comment moderation email headers.
 		 *
 		 * @since 2.8.0
@@ -2038,11 +1966,111 @@
 		$message_headers = apply_filters( 'comment_moderation_headers', $message_headers, $comment_id );
 
 		foreach ( $emails as $email ) {
+			$user = get_user_by( 'email', $email );
+
+			if ( $user ) {
+				$switched_locale = switch_to_user_locale( $user->ID );
+			} else {
+				$switched_locale = switch_to_locale( get_locale() );
+			}
+
+			switch ( $comment->comment_type ) {
+				case 'trackback':
+					/* translators: %s: Post title. */
+					$notify_message  = sprintf( __( 'A new trackback on the post "%s" is waiting for your approval' ), $post->post_title ) . "\r\n";
+					$notify_message .= get_permalink( $comment->comment_post_ID ) . "\r\n\r\n";
+					/* translators: 1: Trackback/pingback website name, 2: Website IP address, 3: Website hostname. */
+					$notify_message .= sprintf( __( 'Website: %1$s (IP address: %2$s, %3$s)' ), $comment->comment_author, $comment->comment_author_IP, $comment_author_domain ) . "\r\n";
+					/* translators: %s: Trackback/pingback/comment author URL. */
+					$notify_message .= sprintf( __( 'URL: %s' ), $comment->comment_author_url ) . "\r\n";
+					$notify_message .= __( 'Trackback excerpt: ' ) . "\r\n" . $comment_content . "\r\n\r\n";
+					break;
+
+				case 'pingback':
+					/* translators: %s: Post title. */
+					$notify_message  = sprintf( __( 'A new pingback on the post "%s" is waiting for your approval' ), $post->post_title ) . "\r\n";
+					$notify_message .= get_permalink( $comment->comment_post_ID ) . "\r\n\r\n";
+					/* translators: 1: Trackback/pingback website name, 2: Website IP address, 3: Website hostname. */
+					$notify_message .= sprintf( __( 'Website: %1$s (IP address: %2$s, %3$s)' ), $comment->comment_author, $comment->comment_author_IP, $comment_author_domain ) . "\r\n";
+					/* translators: %s: Trackback/pingback/comment author URL. */
+					$notify_message .= sprintf( __( 'URL: %s' ), $comment->comment_author_url ) . "\r\n";
+					$notify_message .= __( 'Pingback excerpt: ' ) . "\r\n" . $comment_content . "\r\n\r\n";
+					break;
+
+				default: // Comments.
+					/* translators: %s: Post title. */
+					$notify_message  = sprintf( __( 'A new comment on the post "%s" is waiting for your approval' ), $post->post_title ) . "\r\n";
+					$notify_message .= get_permalink( $comment->comment_post_ID ) . "\r\n\r\n";
+					/* translators: 1: Comment author's name, 2: Comment author's IP address, 3: Comment author's hostname. */
+					$notify_message .= sprintf( __( 'Author: %1$s (IP address: %2$s, %3$s)' ), $comment->comment_author, $comment->comment_author_IP, $comment_author_domain ) . "\r\n";
+					/* translators: %s: Comment author email. */
+					$notify_message .= sprintf( __( 'Email: %s' ), $comment->comment_author_email ) . "\r\n";
+					/* translators: %s: Trackback/pingback/comment author URL. */
+					$notify_message .= sprintf( __( 'URL: %s' ), $comment->comment_author_url ) . "\r\n";
+
+					if ( $comment->comment_parent ) {
+						/* translators: Comment moderation. %s: Parent comment edit URL. */
+						$notify_message .= sprintf( __( 'In reply to: %s' ), admin_url( "comment.php?action=editcomment&c={$comment->comment_parent}#wpbody-content" ) ) . "\r\n";
+					}
+
+					/* translators: %s: Comment text. */
+					$notify_message .= sprintf( __( 'Comment: %s' ), "\r\n" . $comment_content ) . "\r\n\r\n";
+					break;
+			}
+
+			/* translators: Comment moderation. %s: Comment action URL. */
+			$notify_message .= sprintf( __( 'Approve it: %s' ), admin_url( "comment.php?action=approve&c={$comment_id}#wpbody-content" ) ) . "\r\n";
+
+			if ( EMPTY_TRASH_DAYS ) {
+				/* translators: Comment moderation. %s: Comment action URL. */
+				$notify_message .= sprintf( __( 'Trash it: %s' ), admin_url( "comment.php?action=trash&c={$comment_id}#wpbody-content" ) ) . "\r\n";
+			} else {
+				/* translators: Comment moderation. %s: Comment action URL. */
+				$notify_message .= sprintf( __( 'Delete it: %s' ), admin_url( "comment.php?action=delete&c={$comment_id}#wpbody-content" ) ) . "\r\n";
+			}
+
+			/* translators: Comment moderation. %s: Comment action URL. */
+			$notify_message .= sprintf( __( 'Spam it: %s' ), admin_url( "comment.php?action=spam&c={$comment_id}#wpbody-content" ) ) . "\r\n";
+
+			$notify_message .= sprintf(
+				/* translators: Comment moderation. %s: Number of comments awaiting approval. */
+				_n(
+					'Currently %s comment is waiting for approval. Please visit the moderation panel:',
+					'Currently %s comments are waiting for approval. Please visit the moderation panel:',
+					$comments_waiting
+				),
+				number_format_i18n( $comments_waiting )
+			) . "\r\n";
+			$notify_message .= admin_url( 'edit-comments.php?comment_status=moderated#wpbody-content' ) . "\r\n";
+
+			/* translators: Comment moderation notification email subject. 1: Site title, 2: Post title. */
+			$subject = sprintf( __( '[%1$s] Please moderate: "%2$s"' ), $blogname, $post->post_title );
+
+			/**
+			 * Filters the comment moderation email text.
+			 *
+			 * @since 1.5.2
+			 *
+			 * @param string $notify_message Text of the comment moderation email.
+			 * @param int    $comment_id     Comment ID.
+			 */
+			$notify_message = apply_filters( 'comment_moderation_text', $notify_message, $comment_id );
+
+			/**
+			 * Filters the comment moderation email subject.
+			 *
+			 * @since 1.5.2
+			 *
+			 * @param string $subject    Subject of the comment moderation email.
+			 * @param int    $comment_id Comment ID.
+			 */
+			$subject = apply_filters( 'comment_moderation_subject', $subject, $comment_id );
+
 			wp_mail( $email, wp_specialchars_decode( $subject ), $notify_message, $message_headers );
-		}
-
-		if ( $switched_locale ) {
-			restore_previous_locale();
+
+			if ( $switched_locale ) {
+				restore_previous_locale();
+			}
 		}
 
 		return true;
@@ -2063,6 +2091,15 @@
 		 * but check to see if it's the admin whose password we're changing, and skip this.
 		 */
 		if ( 0 !== strcasecmp( $user->user_email, get_option( 'admin_email' ) ) ) {
+
+			$admin_user = get_user_by( 'email', get_option( 'admin_email' ) );
+
+			if ( $admin_user ) {
+				$switched_locale = switch_to_user_locale( $admin_user->ID );
+			} else {
+				$switched_locale = switch_to_locale( get_locale() );
+			}
+
 			/* translators: %s: User name. */
 			$message = sprintf( __( 'Password changed for user: %s' ), $user->user_login ) . "\r\n";
 			/*
@@ -2103,6 +2140,10 @@
 				$wp_password_change_notification_email['message'],
 				$wp_password_change_notification_email['headers']
 			);
+
+			if ( $switched_locale ) {
+				restore_previous_locale();
+			}
 		}
 	}
 endif;
@@ -2152,7 +2193,14 @@
 		$send_notification_to_admin = apply_filters( 'wp_send_new_user_notification_to_admin', true, $user );
 
 		if ( 'user' !== $notify && true === $send_notification_to_admin ) {
-			$switched_locale = switch_to_locale( get_locale() );
+
+			$admin_user = get_user_by( 'email', get_option( 'admin_email' ) );
+
+			if ( $admin_user ) {
+				$switched_locale = switch_to_user_locale( $admin_user->ID );
+			} else {
+				$switched_locale = switch_to_locale( get_locale() );
+			}
 
 			/* translators: %s: Site title. */
 			$message = sprintf( __( 'New user registration on your site %s:' ), $blogname ) . "\r\n\r\n";
@@ -2224,7 +2272,15 @@
 		/* translators: %s: User login. */
 		$message  = sprintf( __( 'Username: %s' ), $user->user_login ) . "\r\n\r\n";
 		$message .= __( 'To set your password, visit the following address:' ) . "\r\n\r\n";
-		$message .= network_site_url( "wp-login.php?action=rp&key=$key&login=" . rawurlencode( $user->user_login ), 'login' ) . "\r\n\r\n";
+
+		/*
+		 * Since some user login names end in a period, this could produce ambiguous URLs that
+		 * end in a period. To avoid the ambiguity, ensure that the login is not the last query
+		 * arg in the URL. If moving it to the end, a trailing period will need to be escaped.
+		 *
+		 * @see https://core.trac.wordpress.org/tickets/42957
+		 */
+		$message .= network_site_url( 'wp-login.php?login=' . rawurlencode( $user->user_login ) . "&key=$key&action=rp", 'login' ) . "\r\n\r\n";
 
 		$message .= wp_login_url() . "\r\n";
 
@@ -2300,7 +2356,7 @@
 	/**
 	 * Verifies that a correct security nonce was used with time limit.
 	 *
-	 * A nonce is valid for 24 hours (by default).
+	 * A nonce is valid for between 12 and 24 hours (by default).
 	 *
 	 * @since 2.0.3
 	 *
@@ -2438,16 +2494,7 @@
 
 		static $duplicated_keys;
 		if ( null === $duplicated_keys ) {
-			$duplicated_keys = array(
-				'put your unique phrase here' => true,
-			);
-
-			/*
-			 * translators: This string should only be translated if wp-config-sample.php is localized.
-			 * You can check the localized release package or
-			 * https://i18n.svn.wordpress.org/<locale code>/branches/<wp version>/dist/wp-config-sample.php
-			 */
-			$duplicated_keys[ __( 'put your unique phrase here' ) ] = true;
+			$duplicated_keys = array();
 
 			foreach ( array( 'AUTH', 'SECURE_AUTH', 'LOGGED_IN', 'NONCE', 'SECRET' ) as $first ) {
 				foreach ( array( 'KEY', 'SALT' ) as $second ) {
@@ -2458,6 +2505,50 @@
 					$duplicated_keys[ $value ] = isset( $duplicated_keys[ $value ] );
 				}
 			}
+
+			$duplicated_keys['put your unique phrase here'] = true;
+
+			/*
+			 * translators: This string should only be translated if wp-config-sample.php is localized.
+			 * You can check the localized release package or
+			 * https://i18n.svn.wordpress.org/<locale code>/branches/<wp version>/dist/wp-config-sample.php
+			 */
+			$duplicated_keys[ __( 'put your unique phrase here' ) ] = true;
+		}
+
+		/*
+		 * Determine which options to prime.
+		 *
+		 * If the salt keys are undefined, use a duplicate value or the
+		 * default `put your unique phrase here` value the salt will be
+		 * generated via `wp_generate_password()` and stored as a site
+		 * option. These options will be primed to avoid repeated
+		 * database requests for undefined salts.
+		 */
+		$options_to_prime = array();
+		foreach ( array( 'auth', 'secure_auth', 'logged_in', 'nonce' ) as $key ) {
+			foreach ( array( 'key', 'salt' ) as $second ) {
+				$const = strtoupper( "{$key}_{$second}" );
+				if ( ! defined( $const ) || true === $duplicated_keys[ constant( $const ) ] ) {
+					$options_to_prime[] = "{$key}_{$second}";
+				}
+			}
+		}
+
+		if ( ! empty( $options_to_prime ) ) {
+			/*
+			 * Also prime `secret_key` used for undefined salting schemes.
+			 *
+			 * If the scheme is unknown, the default value for `secret_key` will be
+			 * used too for the salt. This should rarely happen, so the option is only
+			 * primed if other salts are undefined.
+			 *
+			 * At this point of execution it is known that a database call will be made
+			 * to prime salts, so the `secret_key` option can be primed regardless of the
+			 * constants status.
+			 */
+			$options_to_prime[] = 'secret_key';
+			wp_prime_site_option_caches( $options_to_prime );
 		}
 
 		$values = array(
@@ -2504,110 +2595,246 @@
 
 if ( ! function_exists( 'wp_hash' ) ) :
 	/**
-	 * Gets hash of given string.
+	 * Gets the hash of the given string.
+	 *
+	 * The default algorithm is md5 but can be changed to any algorithm supported by
+	 * `hash_hmac()`. Use the `hash_hmac_algos()` function to check the supported
+	 * algorithms.
 	 *
 	 * @since 2.0.3
+	 * @since 6.8.0 The `$algo` parameter was added.
+	 *
+	 * @throws InvalidArgumentException if the hashing algorithm is not supported.
 	 *
 	 * @param string $data   Plain text to hash.
 	 * @param string $scheme Authentication scheme (auth, secure_auth, logged_in, nonce).
+	 * @param string $algo   Hashing algorithm to use. Default: 'md5'.
 	 * @return string Hash of $data.
 	 */
-	function wp_hash( $data, $scheme = 'auth' ) {
+	function wp_hash( $data, $scheme = 'auth', $algo = 'md5' ) {
 		$salt = wp_salt( $scheme );
 
-		return hash_hmac( 'md5', $data, $salt );
+		// Ensure the algorithm is supported by the hash_hmac function.
+		if ( ! in_array( $algo, hash_hmac_algos(), true ) ) {
+			throw new InvalidArgumentException(
+				sprintf(
+					/* translators: 1: Name of a cryptographic hash algorithm. 2: List of supported algorithms. */
+					__( 'Unsupported hashing algorithm: %1$s. Supported algorithms are: %2$s' ),
+					$algo,
+					implode( ', ', hash_hmac_algos() )
+				)
+			);
+		}
+
+		return hash_hmac( $algo, $data, $salt );
 	}
 endif;
 
 if ( ! function_exists( 'wp_hash_password' ) ) :
 	/**
-	 * Creates a hash (encrypt) of a plain text password.
+	 * Creates a hash of a plain text password.
 	 *
 	 * For integration with other applications, this function can be overwritten to
-	 * instead use the other package password checking algorithm.
+	 * instead use the other package password hashing algorithm.
 	 *
 	 * @since 2.5.0
+	 * @since 6.8.0 The password is now hashed using bcrypt by default instead of phpass.
 	 *
-	 * @global PasswordHash $wp_hasher PHPass object.
+	 * @global PasswordHash $wp_hasher phpass object.
 	 *
 	 * @param string $password Plain text user password to hash.
 	 * @return string The hash string of the password.
 	 */
-	function wp_hash_password( $password ) {
+	function wp_hash_password(
+		#[\SensitiveParameter]
+		$password
+	) {
 		global $wp_hasher;
 
-		if ( empty( $wp_hasher ) ) {
-			require_once ABSPATH . WPINC . '/class-phpass.php';
-			// By default, use the portable hash from phpass.
-			$wp_hasher = new PasswordHash( 8, true );
+		if ( ! empty( $wp_hasher ) ) {
+			return $wp_hasher->HashPassword( trim( $password ) );
+		}
+
+		if ( strlen( $password ) > 4096 ) {
+			return '*';
 		}
 
-		return $wp_hasher->HashPassword( trim( $password ) );
+		/**
+		 * Filters the hashing algorithm to use in the password_hash() and password_needs_rehash() functions.
+		 *
+		 * The default is the value of the `PASSWORD_BCRYPT` constant which means bcrypt is used.
+		 *
+		 * **Important:** The only password hashing algorithm that is guaranteed to be available across PHP
+		 * installations is bcrypt. If you use any other algorithm you must make sure that it is available on
+		 * the server. The `password_algos()` function can be used to check which hashing algorithms are available.
+		 *
+		 * The hashing options can be controlled via the {@see 'wp_hash_password_options'} filter.
+		 *
+		 * Other available constants include:
+		 *
+		 * - `PASSWORD_ARGON2I`
+		 * - `PASSWORD_ARGON2ID`
+		 * - `PASSWORD_DEFAULT`
+		 *
+		 * @since 6.8.0
+		 *
+		 * @param string $algorithm The hashing algorithm. Default is the value of the `PASSWORD_BCRYPT` constant.
+		 */
+		$algorithm = apply_filters( 'wp_hash_password_algorithm', PASSWORD_BCRYPT );
+
+		/**
+		 * Filters the options passed to the password_hash() and password_needs_rehash() functions.
+		 *
+		 * The default hashing algorithm is bcrypt, but this can be changed via the {@see 'wp_hash_password_algorithm'}
+		 * filter. You must ensure that the options are appropriate for the algorithm in use.
+		 *
+		 * @since 6.8.0
+		 *
+		 * @param array $options    Array of options to pass to the password hashing functions.
+		 *                          By default this is an empty array which means the default
+		 *                          options will be used.
+		 * @param string $algorithm The hashing algorithm in use.
+		 */
+		$options = apply_filters( 'wp_hash_password_options', array(), $algorithm );
+
+		// Algorithms other than bcrypt don't need to use pre-hashing.
+		if ( PASSWORD_BCRYPT !== $algorithm ) {
+			return password_hash( $password, $algorithm, $options );
+		}
+
+		// Use SHA-384 to retain entropy from a password that's longer than 72 bytes, and a `wp-sha384` key for domain separation.
+		$password_to_hash = base64_encode( hash_hmac( 'sha384', trim( $password ), 'wp-sha384', true ) );
+
+		// Add a prefix to facilitate distinguishing vanilla bcrypt hashes.
+		return '$wp' . password_hash( $password_to_hash, $algorithm, $options );
 	}
 endif;
 
 if ( ! function_exists( 'wp_check_password' ) ) :
 	/**
-	 * Checks the plaintext password against the encrypted Password.
+	 * Checks a plaintext password against a hashed password.
 	 *
-	 * Maintains compatibility between old version and the new cookie authentication
-	 * protocol using PHPass library. The $hash parameter is the encrypted password
-	 * and the function compares the plain text password when encrypted similarly
-	 * against the already encrypted password to see if they match.
+	 * Note that this function may be used to check a value that is not a user password.
+	 * A plugin may use this function to check a password of a different type, and there
+	 * may not always be a user ID associated with the password.
 	 *
 	 * For integration with other applications, this function can be overwritten to
-	 * instead use the other package password checking algorithm.
+	 * instead use the other package password hashing algorithm.
 	 *
 	 * @since 2.5.0
+	 * @since 6.8.0 Passwords in WordPress are now hashed with bcrypt by default. A
+	 *              password that wasn't hashed with bcrypt will be checked with phpass.
 	 *
-	 * @global PasswordHash $wp_hasher PHPass object used for checking the password
-	 *                                 against the $hash + $password.
-	 * @uses PasswordHash::CheckPassword
+	 * @global PasswordHash $wp_hasher phpass object. Used as a fallback for verifying
+	 *                                 passwords that were hashed with phpass.
 	 *
-	 * @param string     $password Plaintext user's password.
-	 * @param string     $hash     Hash of the user's password to check against.
-	 * @param string|int $user_id  Optional. User ID.
+	 * @param string     $password Plaintext password.
+	 * @param string     $hash     Hash of the password to check against.
+	 * @param string|int $user_id  Optional. ID of a user associated with the password.
 	 * @return bool False, if the $password does not match the hashed password.
 	 */
-	function wp_check_password( $password, $hash, $user_id = '' ) {
+	function wp_check_password(
+		#[\SensitiveParameter]
+		$password,
+		$hash,
+		$user_id = ''
+	) {
 		global $wp_hasher;
 
-		// If the hash is still md5...
 		if ( strlen( $hash ) <= 32 ) {
+			// Check the hash using md5 regardless of the current hashing mechanism.
 			$check = hash_equals( $hash, md5( $password ) );
-			if ( $check && $user_id ) {
-				// Rehash using new hash.
-				wp_set_password( $password, $user_id );
-				$hash = wp_hash_password( $password );
-			}
-
-			/**
-			 * Filters whether the plaintext password matches the encrypted password.
-			 *
-			 * @since 2.5.0
-			 *
-			 * @param bool       $check    Whether the passwords match.
-			 * @param string     $password The plaintext password.
-			 * @param string     $hash     The hashed password.
-			 * @param string|int $user_id  User ID. Can be empty.
-			 */
-			return apply_filters( 'check_password', $check, $password, $hash, $user_id );
+		} elseif ( ! empty( $wp_hasher ) ) {
+			// Check the password using the overridden hasher.
+			$check = $wp_hasher->CheckPassword( $password, $hash );
+		} elseif ( strlen( $password ) > 4096 ) {
+			// Passwords longer than 4096 characters are not supported.
+			$check = false;
+		} elseif ( str_starts_with( $hash, '$wp' ) ) {
+			// Check the password using the current prefixed hash.
+			$password_to_verify = base64_encode( hash_hmac( 'sha384', $password, 'wp-sha384', true ) );
+			$check              = password_verify( $password_to_verify, substr( $hash, 3 ) );
+		} elseif ( str_starts_with( $hash, '$P$' ) ) {
+			// Check the password using phpass.
+			require_once ABSPATH . WPINC . '/class-phpass.php';
+			$check = ( new PasswordHash( 8, true ) )->CheckPassword( $password, $hash );
+		} else {
+			// Check the password using compat support for any non-prefixed hash.
+			$check = password_verify( $password, $hash );
 		}
 
-		/*
-		 * If the stored hash is longer than an MD5,
-		 * presume the new style phpass portable hash.
+		/**
+		 * Filters whether the plaintext password matches the hashed password.
+		 *
+		 * @since 2.5.0
+		 * @since 6.8.0 Passwords are now hashed with bcrypt by default.
+		 *              Old passwords may still be hashed with phpass or md5.
+		 *
+		 * @param bool       $check    Whether the passwords match.
+		 * @param string     $password The plaintext password.
+		 * @param string     $hash     The hashed password.
+		 * @param string|int $user_id  Optional ID of a user associated with the password.
+		 *                             Can be empty.
 		 */
-		if ( empty( $wp_hasher ) ) {
-			require_once ABSPATH . WPINC . '/class-phpass.php';
-			// By default, use the portable hash from phpass.
-			$wp_hasher = new PasswordHash( 8, true );
+		return apply_filters( 'check_password', $check, $password, $hash, $user_id );
+	}
+endif;
+
+if ( ! function_exists( 'wp_password_needs_rehash' ) ) :
+	/**
+	 * Checks whether a password hash needs to be rehashed.
+	 *
+	 * Passwords are hashed with bcrypt using the default cost. A password hashed in a prior version
+	 * of WordPress may still be hashed with phpass and will need to be rehashed. If the default cost
+	 * or algorithm is changed in PHP or WordPress then a password hashed in a previous version will
+	 * need to be rehashed.
+	 *
+	 * Note that, just like wp_check_password(), this function may be used to check a value that is
+	 * not a user password. A plugin may use this function to check a password of a different type,
+	 * and there may not always be a user ID associated with the password.
+	 *
+	 * @since 6.8.0
+	 *
+	 * @global PasswordHash $wp_hasher phpass object.
+	 *
+	 * @param string     $hash    Hash of a password to check.
+	 * @param string|int $user_id Optional. ID of a user associated with the password.
+	 * @return bool Whether the hash needs to be rehashed.
+	 */
+	function wp_password_needs_rehash( $hash, $user_id = '' ) {
+		global $wp_hasher;
+
+		if ( ! empty( $wp_hasher ) ) {
+			return false;
 		}
 
-		$check = $wp_hasher->CheckPassword( $password, $hash );
-
 		/** This filter is documented in wp-includes/pluggable.php */
-		return apply_filters( 'check_password', $check, $password, $hash, $user_id );
+		$algorithm = apply_filters( 'wp_hash_password_algorithm', PASSWORD_BCRYPT );
+
+		/** This filter is documented in wp-includes/pluggable.php */
+		$options = apply_filters( 'wp_hash_password_options', array(), $algorithm );
+
+		$prefixed = str_starts_with( $hash, '$wp' );
+
+		if ( ( PASSWORD_BCRYPT === $algorithm ) && ! $prefixed ) {
+			// If bcrypt is in use and the hash is not prefixed then it needs to be rehashed.
+			$needs_rehash = true;
+		} else {
+			// Otherwise check the hash minus its prefix if necessary.
+			$hash_to_check = $prefixed ? substr( $hash, 3 ) : $hash;
+			$needs_rehash  = password_needs_rehash( $hash_to_check, $algorithm, $options );
+		}
+
+		/**
+		 * Filters whether the password hash needs to be rehashed.
+		 *
+		 * @since 6.8.0
+		 *
+		 * @param bool       $needs_rehash Whether the password hash needs to be rehashed.
+		 * @param string     $hash         The password hash.
+		 * @param string|int $user_id      Optional. ID of a user associated with the password.
+		 */
+		return apply_filters( 'password_needs_rehash', $needs_rehash, $hash, $user_id );
 	}
 endif;
 
@@ -2749,7 +2976,7 @@
 
 if ( ! function_exists( 'wp_set_password' ) ) :
 	/**
-	 * Updates the user's password with a new encrypted one.
+	 * Updates the user's password with a new hashed one.
 	 *
 	 * For integration with other applications, this function can be overwritten to
 	 * instead use the other package password checking algorithm.
@@ -2759,15 +2986,22 @@
 	 * of password resets if precautions are not taken to ensure it does not execute on every page load.
 	 *
 	 * @since 2.5.0
+	 * @since 6.8.0 The password is now hashed using bcrypt by default instead of phpass.
 	 *
 	 * @global wpdb $wpdb WordPress database abstraction object.
 	 *
 	 * @param string $password The plaintext new user password.
 	 * @param int    $user_id  User ID.
 	 */
-	function wp_set_password( $password, $user_id ) {
+	function wp_set_password(
+		#[\SensitiveParameter]
+		$password,
+		$user_id
+	) {
 		global $wpdb;
 
+		$old_user_data = get_userdata( $user_id );
+
 		$hash = wp_hash_password( $password );
 		$wpdb->update(
 			$wpdb->users,
@@ -2784,11 +3018,13 @@
 		 * Fires after the user password is set.
 		 *
 		 * @since 6.2.0
+		 * @since 6.7.0 The `$old_user_data` parameter was added.
 		 *
-		 * @param string $password The plaintext password just set.
-		 * @param int    $user_id  The ID of the user whose password was just set.
+		 * @param string  $password      The plaintext password just set.
+		 * @param int     $user_id       The ID of the user whose password was just set.
+		 * @param WP_User $old_user_data Object containing user's data prior to update.
 		 */
-		do_action( 'wp_set_password', $password, $user_id );
+		do_action( 'wp_set_password', $password, $user_id, $old_user_data );
 	}
 endif;