--- a/wp/wp-includes/user.php Fri Sep 05 18:40:08 2025 +0200
+++ b/wp/wp-includes/user.php Fri Sep 05 18:52:52 2025 +0200
@@ -48,10 +48,10 @@
'remember' => false,
);
- if ( ! empty( $_POST['log'] ) ) {
+ if ( ! empty( $_POST['log'] ) && is_string( $_POST['log'] ) ) {
$credentials['user_login'] = wp_unslash( $_POST['log'] );
}
- if ( ! empty( $_POST['pwd'] ) ) {
+ if ( ! empty( $_POST['pwd'] ) && is_string( $_POST['pwd'] ) ) {
$credentials['user_password'] = $_POST['pwd'];
}
if ( ! empty( $_POST['rememberme'] ) ) {
@@ -150,7 +150,12 @@
* @param string $password Password for authentication.
* @return WP_User|WP_Error WP_User on success, WP_Error on failure.
*/
-function wp_authenticate_username_password( $user, $username, $password ) {
+function wp_authenticate_username_password(
+ $user,
+ $username,
+ #[\SensitiveParameter]
+ $password
+) {
if ( $user instanceof WP_User ) {
return $user;
}
@@ -200,7 +205,9 @@
return $user;
}
- if ( ! wp_check_password( $password, $user->user_pass, $user->ID ) ) {
+ $valid = wp_check_password( $password, $user->user_pass, $user->ID );
+
+ if ( ! $valid ) {
return new WP_Error(
'incorrect_password',
sprintf(
@@ -214,6 +221,10 @@
);
}
+ if ( wp_password_needs_rehash( $user->user_pass, $user->ID ) ) {
+ wp_set_password( $password, $user->ID );
+ }
+
return $user;
}
@@ -228,7 +239,12 @@
* @param string $password Password for authentication.
* @return WP_User|WP_Error WP_User on success, WP_Error on failure.
*/
-function wp_authenticate_email_password( $user, $email, $password ) {
+function wp_authenticate_email_password(
+ $user,
+ $email,
+ #[\SensitiveParameter]
+ $password
+) {
if ( $user instanceof WP_User ) {
return $user;
}
@@ -272,7 +288,9 @@
return $user;
}
- if ( ! wp_check_password( $password, $user->user_pass, $user->ID ) ) {
+ $valid = wp_check_password( $password, $user->user_pass, $user->ID );
+
+ if ( ! $valid ) {
return new WP_Error(
'incorrect_password',
sprintf(
@@ -286,6 +304,10 @@
);
}
+ if ( wp_password_needs_rehash( $user->user_pass, $user->ID ) ) {
+ wp_set_password( $password, $user->ID );
+ }
+
return $user;
}
@@ -301,7 +323,12 @@
* @param string $password Password. If not empty, cancels the cookie authentication.
* @return WP_User|WP_Error WP_User on success, WP_Error on failure.
*/
-function wp_authenticate_cookie( $user, $username, $password ) {
+function wp_authenticate_cookie(
+ $user,
+ $username,
+ #[\SensitiveParameter]
+ $password
+) {
global $auth_secure_cookie;
if ( $user instanceof WP_User ) {
@@ -342,7 +369,12 @@
* @return WP_User|WP_Error|null WP_User on success, WP_Error on failure, null if
* null is passed in and this isn't an API request.
*/
-function wp_authenticate_application_password( $input_user, $username, $password ) {
+function wp_authenticate_application_password(
+ $input_user,
+ $username,
+ #[\SensitiveParameter]
+ $password
+) {
if ( $input_user instanceof WP_User ) {
return $input_user;
}
@@ -425,7 +457,7 @@
$hashed_passwords = WP_Application_Passwords::get_user_application_passwords( $user->ID );
foreach ( $hashed_passwords as $key => $item ) {
- if ( ! wp_check_password( $password, $item['password'], $user->ID ) ) {
+ if ( ! WP_Application_Passwords::check_password( $password, $item['password'] ) ) {
continue;
}
@@ -584,9 +616,19 @@
function count_user_posts( $userid, $post_type = 'post', $public_only = false ) {
global $wpdb;
+ $post_type = array_unique( (array) $post_type );
+ sort( $post_type );
+
$where = get_posts_by_author_sql( $post_type, true, $userid, $public_only );
-
- $count = $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->posts $where" );
+ $query = "SELECT COUNT(*) FROM $wpdb->posts $where";
+
+ $last_changed = wp_cache_get_last_changed( 'posts' );
+ $cache_key = 'count_user_posts:' . md5( $query ) . ':' . $last_changed;
+ $count = wp_cache_get( $cache_key, 'post-queries' );
+ if ( false === $count ) {
+ $count = $wpdb->get_var( $query );
+ wp_cache_set( $cache_key, $count, 'post-queries' );
+ }
/**
* Filters the number of posts a user has written.
@@ -618,25 +660,38 @@
function count_many_users_posts( $users, $post_type = 'post', $public_only = false ) {
global $wpdb;
- $count = array();
if ( empty( $users ) || ! is_array( $users ) ) {
- return $count;
+ return array();
+ }
+
+ /**
+ * Filters whether to short-circuit performing the post counts.
+ *
+ * When filtering, return an array of posts counts as strings, keyed
+ * by the user ID.
+ *
+ * @since 6.8.0
+ *
+ * @param string[]|null $count The post counts. Return a non-null value to short-circuit.
+ * @param int[] $users Array of user IDs.
+ * @param string|string[] $post_type Single post type or array of post types to check.
+ * @param bool $public_only Whether to only return counts for public posts.
+ */
+ $pre = apply_filters( 'pre_count_many_users_posts', null, $users, $post_type, $public_only );
+ if ( null !== $pre ) {
+ return $pre;
}
$userlist = implode( ',', array_map( 'absint', $users ) );
$where = get_posts_by_author_sql( $post_type, true, null, $public_only );
$result = $wpdb->get_results( "SELECT post_author, COUNT(*) FROM $wpdb->posts $where AND post_author IN ($userlist) GROUP BY post_author", ARRAY_N );
+
+ $count = array_fill_keys( $users, 0 );
foreach ( $result as $row ) {
$count[ $row[0] ] = $row[1];
}
- foreach ( $users as $id ) {
- if ( ! isset( $count[ $id ] ) ) {
- $count[ $id ] = 0;
- }
- }
-
return $count;
}
@@ -776,6 +831,19 @@
}
/**
+ * Retrieves user info by user ID.
+ *
+ * @since 6.7.0
+ *
+ * @param int $user_id User ID.
+ *
+ * @return WP_User|false WP_User object on success, false on failure.
+ */
+function get_user( $user_id ) {
+ return get_user_by( 'id', $user_id );
+}
+
+/**
* Retrieves list of users matching criteria.
*
* @since 3.1.0
@@ -1138,7 +1206,13 @@
*
* @param int $user_id User ID.
* @param string $meta_key Metadata name.
- * @param mixed $meta_value Metadata value. Must be serializable if non-scalar.
+ * @param mixed $meta_value Metadata value. Arrays and objects are stored as serialized data and
+ * will be returned as the same type when retrieved. Other data types will
+ * be stored as strings in the database:
+ * - false is stored and retrieved as an empty string ('')
+ * - true is stored and retrieved as '1'
+ * - numbers (both integer and float) are stored and retrieved as strings
+ * Must be serializable if non-scalar.
* @param bool $unique Optional. Whether the same key should not be added.
* Default false.
* @return int|false Meta ID on success, false on failure.
@@ -1185,7 +1259,13 @@
* @return mixed An array of values if `$single` is false.
* The value of meta data field if `$single` is true.
* False for an invalid `$user_id` (non-numeric, zero, or negative value).
- * An empty string if a valid but non-existing user ID is passed.
+ * An empty array if a valid but non-existing user ID is passed and `$single` is false.
+ * An empty string if a valid but non-existing user ID is passed and `$single` is true.
+ * Note: Non-serialized values are returned as strings:
+ * - false values are returned as empty strings ('')
+ * - true values are returned as '1'
+ * - numbers (both integer and float) are returned as strings
+ * Arrays and objects retain their original type.
*/
function get_user_meta( $user_id, $key = '', $single = false ) {
return get_metadata( 'user', $user_id, $key, $single );
@@ -1549,12 +1629,10 @@
/**
* Creates dropdown HTML content of users.
*
- * The content can either be displayed, which it is by default or retrieved by
- * setting the 'echo' argument. The 'include' and 'exclude' arguments do not
- * need to be used; all users will be displayed in that case. Only one can be
- * used, either 'include' or 'exclude', but not both.
- *
- * The available arguments are as follows:
+ * The content can either be displayed, which it is by default, or retrieved by
+ * setting the 'echo' argument to false. The 'include' and 'exclude' arguments
+ * are optional; if they are not specified, all users will be displayed. Only one
+ * can be used in a single call, either 'include' or 'exclude', but not both.
*
* @since 2.3.0
* @since 4.5.0 Added the 'display_name_with_login' value for 'show'.
@@ -2354,7 +2432,7 @@
$admin_color = empty( $userdata['admin_color'] ) ? 'fresh' : $userdata['admin_color'];
$meta['admin_color'] = preg_replace( '|[^a-z0-9 _.\-@]|i', '', $admin_color );
- $meta['use_ssl'] = empty( $userdata['use_ssl'] ) ? 0 : (bool) $userdata['use_ssl'];
+ $meta['use_ssl'] = empty( $userdata['use_ssl'] ) ? '0' : '1';
$meta['show_admin_bar_front'] = empty( $userdata['show_admin_bar_front'] ) ? 'true' : $userdata['show_admin_bar_front'];
@@ -2378,6 +2456,7 @@
*
* @since 4.9.0
* @since 5.8.0 The `$userdata` parameter was added.
+ * @since 6.8.0 The user's password is now hashed using bcrypt by default instead of phpass.
*
* @param array $data {
* Values and keys for the user.
@@ -2386,7 +2465,7 @@
* @type string $user_pass The user's password.
* @type string $user_email The user's email.
* @type string $user_url The user's url.
- * @type string $user_nicename The user's nice name. Defaults to a URL-safe version of user's login
+ * @type string $user_nicename The user's nice name. Defaults to a URL-safe version of user's login.
* @type string $display_name The user's display name.
* @type string $user_registered MySQL timestamp describing the moment when the user registered. Defaults to
* the current UTC timestamp.
@@ -2768,8 +2847,6 @@
$current_user = wp_get_current_user();
if ( $current_user->ID === $user_id ) {
if ( isset( $plaintext_pass ) ) {
- wp_clear_auth_cookie();
-
/*
* Here we calculate the expiration length of the current auth cookie and compare it to the default expiration.
* If it's greater than this, then we know the user checked 'Remember Me' when they logged in.
@@ -2778,13 +2855,20 @@
/** This filter is documented in wp-includes/pluggable.php */
$default_cookie_life = apply_filters( 'auth_cookie_expiration', ( 2 * DAY_IN_SECONDS ), $user_id, false );
+ wp_clear_auth_cookie();
+
$remember = false;
-
- if ( false !== $logged_in_cookie && ( $logged_in_cookie['expiration'] - time() ) > $default_cookie_life ) {
+ $token = '';
+
+ if ( false !== $logged_in_cookie ) {
+ $token = $logged_in_cookie['token'];
+ }
+
+ if ( false !== $logged_in_cookie && ( (int) $logged_in_cookie['expiration'] - time() ) > $default_cookie_life ) {
$remember = true;
}
- wp_set_auth_cookie( $user_id, $remember );
+ wp_set_auth_cookie( $user_id, $remember, '', $token );
}
}
@@ -2818,7 +2902,12 @@
* @return int|WP_Error The newly created user's ID or a WP_Error object if the user could not
* be created.
*/
-function wp_create_user( $username, $password, $email = '' ) {
+function wp_create_user(
+ $username,
+ #[\SensitiveParameter]
+ $password,
+ $email = ''
+) {
$user_login = wp_slash( $username );
$user_email = wp_slash( $email );
$user_pass = $password;
@@ -2915,14 +3004,10 @@
*
* @since 4.4.0
*
- * @global PasswordHash $wp_hasher Portable PHP password hashing framework instance.
- *
* @param WP_User $user User to retrieve password reset key for.
* @return string|WP_Error Password reset key on success. WP_Error on error.
*/
function get_password_reset_key( $user ) {
- global $wp_hasher;
-
if ( ! ( $user instanceof WP_User ) ) {
return new WP_Error( 'invalidcombo', __( '<strong>Error:</strong> There is no account with that username or email address.' ) );
}
@@ -2968,13 +3053,7 @@
*/
do_action( 'retrieve_password_key', $user->user_login, $key );
- // Now insert the key, hashed, into the DB.
- if ( empty( $wp_hasher ) ) {
- require_once ABSPATH . WPINC . '/class-phpass.php';
- $wp_hasher = new PasswordHash( 8, true );
- }
-
- $hashed = time() . ':' . $wp_hasher->HashPassword( $key );
+ $hashed = time() . ':' . wp_fast_hash( $key );
$key_saved = wp_update_user(
array(
@@ -3000,15 +3079,15 @@
*
* @since 3.1.0
*
- * @global PasswordHash $wp_hasher Portable PHP password hashing framework instance.
- *
- * @param string $key Hash to validate sending user's password.
+ * @param string $key The password reset key.
* @param string $login The user login.
* @return WP_User|WP_Error WP_User object on success, WP_Error object for invalid or expired keys.
*/
-function check_password_reset_key( $key, $login ) {
- global $wp_hasher;
-
+function check_password_reset_key(
+ #[\SensitiveParameter]
+ $key,
+ $login
+) {
$key = preg_replace( '/[^a-z0-9]/i', '', $key );
if ( empty( $key ) || ! is_string( $key ) ) {
@@ -3025,11 +3104,6 @@
return new WP_Error( 'invalid_key', __( 'Invalid key.' ) );
}
- if ( empty( $wp_hasher ) ) {
- require_once ABSPATH . WPINC . '/class-phpass.php';
- $wp_hasher = new PasswordHash( 8, true );
- }
-
/**
* Filters the expiration time of password reset keys.
*
@@ -3051,7 +3125,7 @@
return new WP_Error( 'invalid_key', __( 'Invalid key.' ) );
}
- $hash_is_correct = $wp_hasher->CheckPassword( $key, $pass_key );
+ $hash_is_correct = wp_verify_fast_hash( $key, $pass_key );
if ( $hash_is_correct && $expiration_time && time() < $expiration_time ) {
return $user;
@@ -3066,7 +3140,7 @@
/**
* Filters the return value of check_password_reset_key() when an
- * old-style key is used.
+ * old-style key or an expired key is used.
*
* @since 3.7.0 Previously plain-text keys were stored in the database.
* @since 4.3.0 Previously key hashes were stored without an expiration time.
@@ -3087,14 +3161,13 @@
* @since 2.5.0
* @since 5.7.0 Added `$user_login` parameter.
*
- * @global wpdb $wpdb WordPress database abstraction object.
- * @global PasswordHash $wp_hasher Portable PHP password hashing framework instance.
+ * @global wpdb $wpdb WordPress database abstraction object.
*
* @param string $user_login Optional. Username to send a password retrieval email for.
* Defaults to `$_POST['user_login']` if not set.
* @return true|WP_Error True when finished, WP_Error object on error.
*/
-function retrieve_password( $user_login = null ) {
+function retrieve_password( $user_login = '' ) {
$errors = new WP_Error();
$user_data = false;
@@ -3219,7 +3292,15 @@
$message .= sprintf( __( 'Username: %s' ), $user_login ) . "\r\n\r\n";
$message .= __( 'If this was a mistake, ignore this email and nothing will happen.' ) . "\r\n\r\n";
$message .= __( 'To reset your password, visit the following address:' ) . "\r\n\r\n";
- $message .= network_site_url( "wp-login.php?action=rp&key=$key&login=" . rawurlencode( $user_login ), 'login' ) . '&wp_lang=' . $locale . "\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_login ) . "&key=$key&action=rp", 'login' ) . '&wp_lang=' . $locale . "\r\n\r\n";
if ( ! is_user_logged_in() ) {
$requester_ip = $_SERVER['REMOTE_ADDR'];
@@ -3335,7 +3416,11 @@
* @param WP_User $user The user
* @param string $new_pass New password for the user in plaintext
*/
-function reset_password( $user, $new_pass ) {
+function reset_password(
+ $user,
+ #[\SensitiveParameter]
+ $new_pass
+) {
/**
* Fires before the user's password is reset.
*
@@ -4148,7 +4233,7 @@
* Data relating to the account action email.
*
* @type WP_User_Request $request User request object.
- * @type string $user_email The email address confirming a request
+ * @type string $user_email The email address confirming a request.
* @type string $description Description of the action being performed so the user knows what the email is for.
* @type string $manage_url The link to click manage privacy requests of this type.
* @type string $sitename The site name sending the mail.
@@ -4199,7 +4284,7 @@
* Data relating to the account action email.
*
* @type WP_User_Request $request User request object.
- * @type string $user_email The email address confirming a request
+ * @type string $user_email The email address confirming a request.
* @type string $description Description of the action being performed
* so the user knows what the email is for.
* @type string $manage_url The link to click manage privacy requests of this type.
@@ -4239,7 +4324,7 @@
* Data relating to the account action email.
*
* @type WP_User_Request $request User request object.
- * @type string $user_email The email address confirming a request
+ * @type string $user_email The email address confirming a request.
* @type string $description Description of the action being performed so the user knows what the email is for.
* @type string $manage_url The link to click manage privacy requests of this type.
* @type string $sitename The site name sending the mail.
@@ -4270,7 +4355,7 @@
* Data relating to the account action email.
*
* @type WP_User_Request $request User request object.
- * @type string $user_email The email address confirming a request
+ * @type string $user_email The email address confirming a request.
* @type string $description Description of the action being performed so the user knows what the email is for.
* @type string $manage_url The link to click manage privacy requests of this type.
* @type string $sitename The site name sending the mail.
@@ -4857,28 +4942,19 @@
*
* @since 4.9.6
*
- * @global PasswordHash $wp_hasher Portable PHP password hashing framework instance.
- *
* @param int $request_id Request ID.
* @return string Confirmation key.
*/
function wp_generate_user_request_key( $request_id ) {
- global $wp_hasher;
-
// Generate something random for a confirmation key.
$key = wp_generate_password( 20, false );
- // Return the key, hashed.
- if ( empty( $wp_hasher ) ) {
- require_once ABSPATH . WPINC . '/class-phpass.php';
- $wp_hasher = new PasswordHash( 8, true );
- }
-
+ // Save the key, hashed.
wp_update_post(
array(
'ID' => $request_id,
'post_status' => 'request-pending',
- 'post_password' => $wp_hasher->HashPassword( $key ),
+ 'post_password' => wp_fast_hash( $key ),
)
);
@@ -4890,15 +4966,15 @@
*
* @since 4.9.6
*
- * @global PasswordHash $wp_hasher Portable PHP password hashing framework instance.
- *
* @param string $request_id ID of the request being confirmed.
* @param string $key Provided key to validate.
* @return true|WP_Error True on success, WP_Error on failure.
*/
-function wp_validate_user_request_key( $request_id, $key ) {
- global $wp_hasher;
-
+function wp_validate_user_request_key(
+ $request_id,
+ #[\SensitiveParameter]
+ $key
+) {
$request_id = absint( $request_id );
$request = wp_get_user_request( $request_id );
$saved_key = $request->confirm_key;
@@ -4916,11 +4992,6 @@
return new WP_Error( 'missing_key', __( 'The confirmation key is missing from this personal data request.' ) );
}
- if ( empty( $wp_hasher ) ) {
- require_once ABSPATH . WPINC . '/class-phpass.php';
- $wp_hasher = new PasswordHash( 8, true );
- }
-
/**
* Filters the expiration time of confirm keys.
*
@@ -4931,7 +5002,7 @@
$expiration_duration = (int) apply_filters( 'user_request_key_expiration', DAY_IN_SECONDS );
$expiration_time = $key_request_time + $expiration_duration;
- if ( ! $wp_hasher->CheckPassword( $key, $saved_key ) ) {
+ if ( ! wp_verify_fast_hash( $key, $saved_key ) ) {
return new WP_Error( 'invalid_key', __( 'The confirmation key is invalid for this personal data request.' ) );
}