--- a/wp/wp-includes/user.php Thu Sep 29 08:06:27 2022 +0200
+++ b/wp/wp-includes/user.php Fri Sep 05 18:40:08 2025 +0200
@@ -25,14 +25,28 @@
* @since 2.5.0
*
* @global string $auth_secure_cookie
- *
- * @param array $credentials Optional. User info in order to sign on.
+ * @global wpdb $wpdb WordPress database abstraction object.
+ *
+ * @param array $credentials {
+ * Optional. User info in order to sign on.
+ *
+ * @type string $user_login Username.
+ * @type string $user_password User password.
+ * @type bool $remember Whether to 'remember' the user. Increases the time
+ * that the cookie will be kept. Default false.
+ * }
* @param string|bool $secure_cookie Optional. Whether to use secure cookie.
* @return WP_User|WP_Error WP_User on success, WP_Error on failure.
*/
function wp_signon( $credentials = array(), $secure_cookie = '' ) {
+ global $auth_secure_cookie, $wpdb;
+
if ( empty( $credentials ) ) {
- $credentials = array(); // Back-compat for plugins passing an empty string.
+ $credentials = array(
+ 'user_login' => '',
+ 'user_password' => '',
+ 'remember' => false,
+ );
if ( ! empty( $_POST['log'] ) ) {
$credentials['user_login'] = wp_unslash( $_POST['log'] );
@@ -87,7 +101,7 @@
*/
$secure_cookie = apply_filters( 'secure_signon_cookie', $secure_cookie, $credentials );
- global $auth_secure_cookie; // XXX ugly hack to pass this to wp_authenticate_cookie().
+ // XXX ugly hack to pass this to wp_authenticate_cookie().
$auth_secure_cookie = $secure_cookie;
add_filter( 'authenticate', 'wp_authenticate_cookie', 30, 3 );
@@ -99,6 +113,20 @@
}
wp_set_auth_cookie( $user->ID, $credentials['remember'], $secure_cookie );
+
+ // Clear `user_activation_key` after a successful login.
+ if ( ! empty( $user->user_activation_key ) ) {
+ $wpdb->update(
+ $wpdb->users,
+ array(
+ 'user_activation_key' => '',
+ ),
+ array( 'ID' => $user->ID )
+ );
+
+ $user->user_activation_key = '';
+ }
+
/**
* Fires after the user has successfully logged in.
*
@@ -108,6 +136,7 @@
* @param WP_User $user WP_User object of the logged-in user.
*/
do_action( 'wp_login', $user->user_login, $user );
+
return $user;
}
@@ -134,11 +163,11 @@
$error = new WP_Error();
if ( empty( $username ) ) {
- $error->add( 'empty_username', __( '<strong>Error</strong>: The username field is empty.' ) );
+ $error->add( 'empty_username', __( '<strong>Error:</strong> The username field is empty.' ) );
}
if ( empty( $password ) ) {
- $error->add( 'empty_password', __( '<strong>Error</strong>: The password field is empty.' ) );
+ $error->add( 'empty_password', __( '<strong>Error:</strong> The password field is empty.' ) );
}
return $error;
@@ -151,7 +180,7 @@
'invalid_username',
sprintf(
/* translators: %s: User name. */
- __( '<strong>Error</strong>: The username <strong>%s</strong> is not registered on this site. If you are unsure of your username, try your email address instead.' ),
+ __( '<strong>Error:</strong> The username <strong>%s</strong> is not registered on this site. If you are unsure of your username, try your email address instead.' ),
$username
)
);
@@ -176,7 +205,7 @@
'incorrect_password',
sprintf(
/* translators: %s: User name. */
- __( '<strong>Error</strong>: The password you entered for the username %s is incorrect.' ),
+ __( '<strong>Error:</strong> The password you entered for the username %s is incorrect.' ),
'<strong>' . $username . '</strong>'
) .
' <a href="' . wp_lostpassword_url() . '">' .
@@ -213,11 +242,11 @@
if ( empty( $email ) ) {
// Uses 'empty_username' for back-compat with wp_signon().
- $error->add( 'empty_username', __( '<strong>Error</strong>: The email field is empty.' ) );
+ $error->add( 'empty_username', __( '<strong>Error:</strong> The email field is empty.' ) );
}
if ( empty( $password ) ) {
- $error->add( 'empty_password', __( '<strong>Error</strong>: The password field is empty.' ) );
+ $error->add( 'empty_password', __( '<strong>Error:</strong> The password field is empty.' ) );
}
return $error;
@@ -248,7 +277,7 @@
'incorrect_password',
sprintf(
/* translators: %s: Email address. */
- __( '<strong>Error</strong>: The password you entered for the email address %s is incorrect.' ),
+ __( '<strong>Error:</strong> The password you entered for the email address %s is incorrect.' ),
'<strong>' . $email . '</strong>'
) .
' <a href="' . wp_lostpassword_url() . '">' .
@@ -273,6 +302,8 @@
* @return WP_User|WP_Error WP_User on success, WP_Error on failure.
*/
function wp_authenticate_cookie( $user, $username, $password ) {
+ global $auth_secure_cookie;
+
if ( $user instanceof WP_User ) {
return $user;
}
@@ -283,8 +314,6 @@
return new WP_User( $user_id );
}
- global $auth_secure_cookie;
-
if ( $auth_secure_cookie ) {
$auth_cookie = SECURE_AUTH_COOKIE;
} else {
@@ -322,6 +351,7 @@
return $input_user;
}
+ // The 'REST_REQUEST' check here may happen too early for the constant to be available.
$is_api_request = ( ( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST ) || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) );
/**
@@ -351,12 +381,12 @@
if ( is_email( $username ) ) {
$error = new WP_Error(
'invalid_email',
- __( '<strong>Error</strong>: Unknown email address. Check again or try your username.' )
+ __( '<strong>Error:</strong> Unknown email address. Check again or try your username.' )
);
} else {
$error = new WP_Error(
'invalid_username',
- __( '<strong>Error</strong>: Unknown username. Check again or try your email address.' )
+ __( '<strong>Error:</strong> Unknown username. Check again or try your email address.' )
);
}
} elseif ( ! wp_is_application_passwords_available() ) {
@@ -503,7 +533,7 @@
$spammed = apply_filters( 'check_is_user_spammed', is_user_spammy( $user ), $user );
if ( $spammed ) {
- return new WP_Error( 'spammer_account', __( '<strong>Error</strong>: Your account has been marked as a spammer.' ) );
+ return new WP_Error( 'spammer_account', __( '<strong>Error:</strong> Your account has been marked as a spammer.' ) );
}
}
return $user;
@@ -691,7 +721,7 @@
* Updates user option with global blog capability.
*
* User options are just like user metadata except that they have support for
- * global blog options. If the 'global' parameter is false, which it is by default
+ * global blog options. If the 'is_global' parameter is false, which it is by default,
* it will prepend the WordPress table prefix to the option name.
*
* Deletes the user option if $newvalue is empty.
@@ -703,15 +733,15 @@
* @param int $user_id User ID.
* @param string $option_name User option name.
* @param mixed $newvalue User option value.
- * @param bool $global Optional. Whether option name is global or blog specific.
+ * @param bool $is_global Optional. Whether option name is global or blog specific.
* Default false (blog specific).
* @return int|bool User meta ID if the option didn't exist, true on successful update,
* false on failure.
*/
-function update_user_option( $user_id, $option_name, $newvalue, $global = false ) {
+function update_user_option( $user_id, $option_name, $newvalue, $is_global = false ) {
global $wpdb;
- if ( ! $global ) {
+ if ( ! $is_global ) {
$option_name = $wpdb->get_blog_prefix() . $option_name;
}
@@ -722,7 +752,7 @@
* Deletes user option with global blog capability.
*
* User options are just like user metadata except that they have support for
- * global blog options. If the 'global' parameter is false, which it is by default
+ * global blog options. If the 'is_global' parameter is false, which it is by default,
* it will prepend the WordPress table prefix to the option name.
*
* @since 3.0.0
@@ -731,16 +761,17 @@
*
* @param int $user_id User ID
* @param string $option_name User option name.
- * @param bool $global Optional. Whether option name is global or blog specific.
+ * @param bool $is_global Optional. Whether option name is global or blog specific.
* Default false (blog specific).
* @return bool True on success, false on failure.
*/
-function delete_user_option( $user_id, $option_name, $global = false ) {
+function delete_user_option( $user_id, $option_name, $is_global = false ) {
global $wpdb;
- if ( ! $global ) {
+ if ( ! $is_global ) {
$option_name = $wpdb->get_blog_prefix() . $option_name;
}
+
return delete_user_meta( $user_id, $option_name );
}
@@ -811,75 +842,91 @@
'include' => '',
);
- $args = wp_parse_args( $args, $defaults );
+ $parsed_args = wp_parse_args( $args, $defaults );
$return = '';
- $query_args = wp_array_slice_assoc( $args, array( 'orderby', 'order', 'number', 'exclude', 'include' ) );
+ $query_args = wp_array_slice_assoc( $parsed_args, array( 'orderby', 'order', 'number', 'exclude', 'include' ) );
$query_args['fields'] = 'ids';
- $users = get_users( $query_args );
+
+ /**
+ * Filters the query arguments for the list of all users of the site.
+ *
+ * @since 6.1.0
+ *
+ * @param array $query_args The query arguments for get_users().
+ * @param array $parsed_args The arguments passed to wp_list_users() combined with the defaults.
+ */
+ $query_args = apply_filters( 'wp_list_users_args', $query_args, $parsed_args );
+
+ $users = get_users( $query_args );
foreach ( $users as $user_id ) {
$user = get_userdata( $user_id );
- if ( $args['exclude_admin'] && 'admin' === $user->display_name ) {
+ if ( $parsed_args['exclude_admin'] && 'admin' === $user->display_name ) {
continue;
}
- if ( $args['show_fullname'] && '' !== $user->first_name && '' !== $user->last_name ) {
- $name = "$user->first_name $user->last_name";
+ if ( $parsed_args['show_fullname'] && '' !== $user->first_name && '' !== $user->last_name ) {
+ $name = sprintf(
+ /* translators: 1: User's first name, 2: Last name. */
+ _x( '%1$s %2$s', 'Display name based on first name and last name' ),
+ $user->first_name,
+ $user->last_name
+ );
} else {
$name = $user->display_name;
}
- if ( ! $args['html'] ) {
+ if ( ! $parsed_args['html'] ) {
$return .= $name . ', ';
continue; // No need to go further to process HTML.
}
- if ( 'list' === $args['style'] ) {
+ if ( 'list' === $parsed_args['style'] ) {
$return .= '<li>';
}
$row = $name;
- if ( ! empty( $args['feed_image'] ) || ! empty( $args['feed'] ) ) {
+ if ( ! empty( $parsed_args['feed_image'] ) || ! empty( $parsed_args['feed'] ) ) {
$row .= ' ';
- if ( empty( $args['feed_image'] ) ) {
+ if ( empty( $parsed_args['feed_image'] ) ) {
$row .= '(';
}
- $row .= '<a href="' . get_author_feed_link( $user->ID, $args['feed_type'] ) . '"';
+ $row .= '<a href="' . get_author_feed_link( $user->ID, $parsed_args['feed_type'] ) . '"';
$alt = '';
- if ( ! empty( $args['feed'] ) ) {
- $alt = ' alt="' . esc_attr( $args['feed'] ) . '"';
- $name = $args['feed'];
+ if ( ! empty( $parsed_args['feed'] ) ) {
+ $alt = ' alt="' . esc_attr( $parsed_args['feed'] ) . '"';
+ $name = $parsed_args['feed'];
}
$row .= '>';
- if ( ! empty( $args['feed_image'] ) ) {
- $row .= '<img src="' . esc_url( $args['feed_image'] ) . '" style="border: none;"' . $alt . ' />';
+ if ( ! empty( $parsed_args['feed_image'] ) ) {
+ $row .= '<img src="' . esc_url( $parsed_args['feed_image'] ) . '" style="border: none;"' . $alt . ' />';
} else {
$row .= $name;
}
$row .= '</a>';
- if ( empty( $args['feed_image'] ) ) {
+ if ( empty( $parsed_args['feed_image'] ) ) {
$row .= ')';
}
}
$return .= $row;
- $return .= ( 'list' === $args['style'] ) ? '</li>' : ', ';
+ $return .= ( 'list' === $parsed_args['style'] ) ? '</li>' : ', ';
}
$return = rtrim( $return, ', ' );
- if ( ! $args['echo'] ) {
+ if ( ! $parsed_args['echo'] ) {
return $return;
}
echo $return;
@@ -935,7 +982,7 @@
if ( ! is_multisite() ) {
$site_id = get_current_blog_id();
- $sites = array( $site_id => new stdClass );
+ $sites = array( $site_id => new stdClass() );
$sites[ $site_id ]->userblog_id = $site_id;
$sites[ $site_id ]->blogname = get_option( 'blogname' );
$sites[ $site_id ]->domain = '';
@@ -958,10 +1005,10 @@
$keys = array_keys( $keys );
foreach ( $keys as $key ) {
- if ( 'capabilities' !== substr( $key, -12 ) ) {
+ if ( ! str_ends_with( $key, 'capabilities' ) ) {
continue;
}
- if ( $wpdb->base_prefix && 0 !== strpos( $key, $wpdb->base_prefix ) ) {
+ if ( $wpdb->base_prefix && ! str_starts_with( $key, $wpdb->base_prefix ) ) {
continue;
}
$site_id = str_replace( array( $wpdb->base_prefix, '_capabilities' ), '', $key );
@@ -976,9 +1023,8 @@
if ( ! empty( $site_ids ) ) {
$args = array(
- 'number' => '',
- 'site__in' => $site_ids,
- 'update_site_meta_cache' => false,
+ 'number' => '',
+ 'site__in' => $site_ids,
);
if ( ! $all ) {
$args['archived'] = 0;
@@ -1038,8 +1084,10 @@
$user_id = get_current_user_id();
}
- // Technically not needed, but does save calls to get_site() and get_user_meta()
- // in the event that the function is called when a user isn't logged in.
+ /*
+ * Technically not needed, but does save calls to get_site() and get_user_meta()
+ * in the event that the function is called when a user isn't logged in.
+ */
if ( empty( $user_id ) ) {
return false;
} else {
@@ -1072,7 +1120,7 @@
$base_capabilities_key = $wpdb->base_prefix . 'capabilities';
$site_capabilities_key = $wpdb->base_prefix . $blog_id . '_capabilities';
- if ( isset( $keys[ $base_capabilities_key ] ) && 1 == $blog_id ) {
+ if ( isset( $keys[ $base_capabilities_key ] ) && 1 === $blog_id ) {
return true;
}
@@ -1208,10 +1256,10 @@
*
* @since 5.1.0
*
- * @param null|string $result The value to return instead. Default null to continue with the query.
- * @param string $strategy Optional. The computational strategy to use when counting the users.
- * Accepts either 'time' or 'memory'. Default 'time'.
- * @param int|null $site_id Optional. The site ID to count users for. Defaults to the current site.
+ * @param null|array $result The value to return instead. Default null to continue with the query.
+ * @param string $strategy Optional. The computational strategy to use when counting the users.
+ * Accepts either 'time' or 'memory'. Default 'time'.
+ * @param int $site_id The site ID to count users for.
*/
$pre = apply_filters( 'pre_count_users', null, $strategy, $site_id );
@@ -1223,7 +1271,7 @@
$result = array();
if ( 'time' === $strategy ) {
- if ( is_multisite() && get_current_blog_id() != $site_id ) {
+ if ( is_multisite() && get_current_blog_id() !== $site_id ) {
switch_to_blog( $site_id );
$avail_roles = wp_roles()->get_names();
restore_current_blog();
@@ -1287,11 +1335,11 @@
continue;
}
if ( empty( $b_roles ) ) {
- $avail_roles['none']++;
+ ++$avail_roles['none'];
}
foreach ( $b_roles as $b_role => $val ) {
if ( isset( $avail_roles[ $b_role ] ) ) {
- $avail_roles[ $b_role ]++;
+ ++$avail_roles[ $b_role ];
} else {
$avail_roles[ $b_role ] = 1;
}
@@ -1510,54 +1558,70 @@
*
* @since 2.3.0
* @since 4.5.0 Added the 'display_name_with_login' value for 'show'.
- * @since 4.7.0 Added the `$role`, `$role__in`, and `$role__not_in` parameters.
+ * @since 4.7.0 Added the 'role', 'role__in', and 'role__not_in' parameters.
+ * @since 5.9.0 Added the 'capability', 'capability__in', and 'capability__not_in' parameters.
+ * Deprecated the 'who' parameter.
*
* @param array|string $args {
* Optional. Array or string of arguments to generate a drop-down of users.
* See WP_User_Query::prepare_query() for additional available arguments.
*
- * @type string $show_option_all Text to show as the drop-down default (all).
- * Default empty.
- * @type string $show_option_none Text to show as the drop-down default when no
- * users were found. Default empty.
- * @type int|string $option_none_value Value to use for $show_option_non when no users
- * were found. Default -1.
- * @type string $hide_if_only_one_author Whether to skip generating the drop-down
- * if only one user was found. Default empty.
- * @type string $orderby Field to order found users by. Accepts user fields.
- * Default 'display_name'.
- * @type string $order Whether to order users in ascending or descending
- * order. Accepts 'ASC' (ascending) or 'DESC' (descending).
- * Default 'ASC'.
- * @type int[]|string $include Array or comma-separated list of user IDs to include.
- * Default empty.
- * @type int[]|string $exclude Array or comma-separated list of user IDs to exclude.
- * Default empty.
- * @type bool|int $multi Whether to skip the ID attribute on the 'select' element.
- * Accepts 1|true or 0|false. Default 0|false.
- * @type string $show User data to display. If the selected item is empty
- * then the 'user_login' will be displayed in parentheses.
- * Accepts any user field, or 'display_name_with_login' to show
- * the display name with user_login in parentheses.
- * Default 'display_name'.
- * @type int|bool $echo Whether to echo or return the drop-down. Accepts 1|true (echo)
- * or 0|false (return). Default 1|true.
- * @type int $selected Which user ID should be selected. Default 0.
- * @type bool $include_selected Whether to always include the selected user ID in the drop-
- * down. Default false.
- * @type string $name Name attribute of select element. Default 'user'.
- * @type string $id ID attribute of the select element. Default is the value of $name.
- * @type string $class Class attribute of the select element. Default empty.
- * @type int $blog_id ID of blog (Multisite only). Default is ID of the current blog.
- * @type string $who Which type of users to query. Accepts only an empty string or
- * 'authors'. Default empty.
- * @type string|array $role An array or a comma-separated list of role names that users must
- * match to be included in results. Note that this is an inclusive
- * list: users must match *each* role. Default empty.
- * @type string[] $role__in An array of role names. Matched users must have at least one of
- * these roles. Default empty array.
- * @type string[] $role__not_in An array of role names to exclude. Users matching one or more of
- * these roles will not be included in results. Default empty array.
+ * @type string $show_option_all Text to show as the drop-down default (all).
+ * Default empty.
+ * @type string $show_option_none Text to show as the drop-down default when no
+ * users were found. Default empty.
+ * @type int|string $option_none_value Value to use for `$show_option_none` when no users
+ * were found. Default -1.
+ * @type string $hide_if_only_one_author Whether to skip generating the drop-down
+ * if only one user was found. Default empty.
+ * @type string $orderby Field to order found users by. Accepts user fields.
+ * Default 'display_name'.
+ * @type string $order Whether to order users in ascending or descending
+ * order. Accepts 'ASC' (ascending) or 'DESC' (descending).
+ * Default 'ASC'.
+ * @type int[]|string $include Array or comma-separated list of user IDs to include.
+ * Default empty.
+ * @type int[]|string $exclude Array or comma-separated list of user IDs to exclude.
+ * Default empty.
+ * @type bool|int $multi Whether to skip the ID attribute on the 'select' element.
+ * Accepts 1|true or 0|false. Default 0|false.
+ * @type string $show User data to display. If the selected item is empty
+ * then the 'user_login' will be displayed in parentheses.
+ * Accepts any user field, or 'display_name_with_login' to show
+ * the display name with user_login in parentheses.
+ * Default 'display_name'.
+ * @type int|bool $echo Whether to echo or return the drop-down. Accepts 1|true (echo)
+ * or 0|false (return). Default 1|true.
+ * @type int $selected Which user ID should be selected. Default 0.
+ * @type bool $include_selected Whether to always include the selected user ID in the drop-
+ * down. Default false.
+ * @type string $name Name attribute of select element. Default 'user'.
+ * @type string $id ID attribute of the select element. Default is the value of `$name`.
+ * @type string $class Class attribute of the select element. Default empty.
+ * @type int $blog_id ID of blog (Multisite only). Default is ID of the current blog.
+ * @type string $who Deprecated, use `$capability` instead.
+ * Which type of users to query. Accepts only an empty string or
+ * 'authors'. Default empty (all users).
+ * @type string|string[] $role An array or a comma-separated list of role names that users
+ * must match to be included in results. Note that this is
+ * an inclusive list: users must match *each* role. Default empty.
+ * @type string[] $role__in An array of role names. Matched users must have at least one
+ * of these roles. Default empty array.
+ * @type string[] $role__not_in An array of role names to exclude. Users matching one or more
+ * of these roles will not be included in results. Default empty array.
+ * @type string|string[] $capability An array or a comma-separated list of capability names that users
+ * must match to be included in results. Note that this is
+ * an inclusive list: users must match *each* capability.
+ * Does NOT work for capabilities not in the database or filtered
+ * via {@see 'map_meta_cap'}. Default empty.
+ * @type string[] $capability__in An array of capability names. Matched users must have at least one
+ * of these capabilities.
+ * Does NOT work for capabilities not in the database or filtered
+ * via {@see 'map_meta_cap'}. Default empty array.
+ * @type string[] $capability__not_in An array of capability names to exclude. Users matching one or more
+ * of these capabilities will not be included in results.
+ * Does NOT work for capabilities not in the database or filtered
+ * via {@see 'map_meta_cap'}. Default empty array.
* }
* @return string HTML dropdown list of users.
*/
@@ -1738,7 +1802,7 @@
return $value;
}
- $prefixed = false !== strpos( $field, 'user_' );
+ $prefixed = str_contains( $field, 'user_' );
if ( 'edit' === $context ) {
if ( $prefixed ) {
@@ -1845,8 +1909,11 @@
wp_cache_add( $user->ID, $user, 'users' );
wp_cache_add( $user->user_login, $user->ID, 'userlogins' );
- wp_cache_add( $user->user_email, $user->ID, 'useremail' );
wp_cache_add( $user->user_nicename, $user->ID, 'userslugs' );
+
+ if ( ! empty( $user->user_email ) ) {
+ wp_cache_add( $user->user_email, $user->ID, 'useremail' );
+ }
}
/**
@@ -1854,15 +1921,11 @@
*
* @since 3.0.0
* @since 4.4.0 'clean_user_cache' action was added.
- * @since 5.8.0 Refreshes the global user instance if cleaning the user cache for the current user.
- *
- * @global WP_User $current_user The current user object which holds the user data.
+ * @since 6.2.0 User metadata caches are now cleared.
*
* @param WP_User|int $user User object or ID to be cleaned from the cache
*/
function clean_user_cache( $user ) {
- global $current_user;
-
if ( is_numeric( $user ) ) {
$user = new WP_User( $user );
}
@@ -1873,9 +1936,15 @@
wp_cache_delete( $user->ID, 'users' );
wp_cache_delete( $user->user_login, 'userlogins' );
- wp_cache_delete( $user->user_email, 'useremail' );
wp_cache_delete( $user->user_nicename, 'userslugs' );
+ if ( ! empty( $user->user_email ) ) {
+ wp_cache_delete( $user->user_email, 'useremail' );
+ }
+
+ wp_cache_delete( $user->ID, 'user_meta' );
+ wp_cache_set_users_last_changed();
+
/**
* Fires immediately after the given user's cache is cleaned.
*
@@ -1885,13 +1954,6 @@
* @param WP_User $user User object.
*/
do_action( 'clean_user_cache', $user->ID, $user );
-
- // Refresh the global user instance if the cleaning current user.
- if ( get_current_user_id() === (int) $user->ID ) {
- $user_id = (int) $user->ID;
- $current_user = null;
- wp_set_current_user( $user_id, '' );
- }
}
/**
@@ -1969,7 +2031,7 @@
*/
function validate_username( $username ) {
$sanitized = sanitize_user( $username, true );
- $valid = ( $sanitized == $username && ! empty( $sanitized ) );
+ $valid = ( $sanitized === $username && ! empty( $sanitized ) );
/**
* Filters whether the provided username is valid.
@@ -2005,7 +2067,8 @@
* An array, object, or WP_User object of user data arguments.
*
* @type int $ID User ID. If supplied, the user will be updated.
- * @type string $user_pass The plain-text user password.
+ * @type string $user_pass The plain-text user password for new users.
+ * Hashed password for existing users.
* @type string $user_login The user's login username.
* @type string $user_nicename The URL-friendly user name.
* @type string $user_url The user URL.
@@ -2067,6 +2130,9 @@
return new WP_Error( 'invalid_user_id', __( 'Invalid user ID.' ) );
}
+ // Slash current user email to compare it later with slashed new user email.
+ $old_user_data->user_email = wp_slash( $old_user_data->user_email );
+
// Hashed in wp_update_user(), plaintext if called directly.
$user_pass = ! empty( $userdata['user_pass'] ) ? $userdata['user_pass'] : $old_user_data->user_pass;
} else {
@@ -2149,7 +2215,7 @@
$base_length = 49 - mb_strlen( $suffix );
$alt_user_nicename = mb_substr( $user_nicename, 0, $base_length ) . "-$suffix";
$user_nicename_check = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM $wpdb->users WHERE user_nicename = %s AND user_login != %s LIMIT 1", $alt_user_nicename, $user_login ) );
- $suffix++;
+ ++$suffix;
}
$user_nicename = $alt_user_nicename;
}
@@ -2242,8 +2308,12 @@
if ( $update ) {
$display_name = $user_login;
} elseif ( $meta['first_name'] && $meta['last_name'] ) {
- /* translators: 1: User's first name, 2: Last name. */
- $display_name = sprintf( _x( '%1$s %2$s', 'Display name based on first name and last name' ), $meta['first_name'], $meta['last_name'] );
+ $display_name = sprintf(
+ /* translators: 1: User's first name, 2: Last name. */
+ _x( '%1$s %2$s', 'Display name based on first name and last name' ),
+ $meta['first_name'],
+ $meta['last_name']
+ );
} elseif ( $meta['first_name'] ) {
$display_name = $meta['first_name'];
} elseif ( $meta['last_name'] ) {
@@ -2399,9 +2469,16 @@
$meta = array_merge( $meta, $custom_meta );
- // Update user meta.
- foreach ( $meta as $key => $value ) {
- update_user_meta( $user_id, $key, $value );
+ if ( $update ) {
+ // Update user meta.
+ foreach ( $meta as $key => $value ) {
+ update_user_meta( $user_id, $key, $value );
+ }
+ } else {
+ // Add user meta.
+ foreach ( $meta as $key => $value ) {
+ add_user_meta( $user_id, $key, $value );
+ }
}
foreach ( wp_get_user_contact_methods( $user ) as $key => $value ) {
@@ -2431,8 +2508,8 @@
*/
do_action( 'profile_update', $user_id, $old_user_data, $userdata );
- if ( isset( $userdata['spam'] ) && $userdata['spam'] != $old_user_data->spam ) {
- if ( 1 == $userdata['spam'] ) {
+ if ( isset( $userdata['spam'] ) && $userdata['spam'] !== $old_user_data->spam ) {
+ if ( '1' === $userdata['spam'] ) {
/**
* Fires after the user is marked as a SPAM user.
*
@@ -2491,6 +2568,8 @@
$userdata = $userdata->to_array();
}
+ $userdata_raw = $userdata;
+
$user_id = isset( $userdata['ID'] ) ? (int) $userdata['ID'] : 0;
if ( ! $user_id ) {
return new WP_Error( 'invalid_user_id', __( 'Invalid user ID.' ) );
@@ -2560,7 +2639,7 @@
$switched_locale = false;
if ( ! empty( $send_password_change_email ) || ! empty( $send_email_change_email ) ) {
- $switched_locale = switch_to_locale( get_user_locale( $user_id ) );
+ $switched_locale = switch_to_user_locale( $user_id );
}
if ( ! empty( $send_password_change_email ) ) {
@@ -2687,16 +2766,20 @@
// Update the cookies if the password changed.
$current_user = wp_get_current_user();
- if ( $current_user->ID == $user_id ) {
+ 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.
+ /*
+ * 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.
+ */
$logged_in_cookie = wp_parse_auth_cookie( '', 'logged_in' );
/** This filter is documented in wp-includes/pluggable.php */
$default_cookie_life = apply_filters( 'auth_cookie_expiration', ( 2 * DAY_IN_SECONDS ), $user_id, false );
- $remember = false;
+
+ $remember = false;
+
if ( false !== $logged_in_cookie && ( $logged_in_cookie['expiration'] - time() ) > $default_cookie_life ) {
$remember = true;
}
@@ -2705,6 +2788,17 @@
}
}
+ /**
+ * Fires after the user has been updated and emails have been sent.
+ *
+ * @since 6.3.0
+ *
+ * @param int $user_id The ID of the user that was just updated.
+ * @param array $userdata The array of user data that was updated.
+ * @param array $userdata_raw The unedited array of user data that was updated.
+ */
+ do_action( 'wp_update_user', $user_id, $userdata, $userdata_raw );
+
return $user_id;
}
@@ -2821,7 +2915,7 @@
*
* @since 4.4.0
*
- * @global PasswordHash $wp_hasher Portable PHP password hashing framework.
+ * @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.
@@ -2830,7 +2924,7 @@
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.' ) );
+ return new WP_Error( 'invalidcombo', __( '<strong>Error:</strong> There is no account with that username or email address.' ) );
}
/**
@@ -2854,25 +2948,11 @@
*/
do_action( 'retrieve_password', $user->user_login );
- $allow = true;
- if ( is_multisite() && is_user_spammy( $user ) ) {
- $allow = false;
- }
-
- /**
- * Filters whether to allow a password to be reset.
- *
- * @since 2.7.0
- *
- * @param bool $allow Whether to allow the password to be reset. Default true.
- * @param int $user_id The ID of the user attempting to reset a password.
- */
- $allow = apply_filters( 'allow_password_reset', $allow, $user->ID );
-
- if ( ! $allow ) {
+ $password_reset_allowed = wp_is_password_reset_allowed_for_user( $user );
+ if ( ! $password_reset_allowed ) {
return new WP_Error( 'no_password_reset', __( 'Password reset is not allowed for this user' ) );
- } elseif ( is_wp_error( $allow ) ) {
- return $allow;
+ } elseif ( is_wp_error( $password_reset_allowed ) ) {
+ return $password_reset_allowed;
}
// Generate something random for a password reset key.
@@ -2920,7 +3000,6 @@
*
* @since 3.1.0
*
- * @global wpdb $wpdb WordPress database object for queries.
* @global PasswordHash $wp_hasher Portable PHP password hashing framework instance.
*
* @param string $key Hash to validate sending user's password.
@@ -2928,7 +3007,7 @@
* @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 $wpdb, $wp_hasher;
+ global $wp_hasher;
$key = preg_replace( '/[^a-z0-9]/i', '', $key );
@@ -2960,7 +3039,7 @@
*/
$expiration_duration = apply_filters( 'password_reset_expiration', DAY_IN_SECONDS );
- if ( false !== strpos( $user->user_activation_key, ':' ) ) {
+ if ( str_contains( $user->user_activation_key, ':' ) ) {
list( $pass_request_time, $pass_key ) = explode( ':', $user->user_activation_key, 2 );
$expiration_time = $pass_request_time + $expiration_duration;
} else {
@@ -3008,8 +3087,8 @@
* @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.
+ * @global wpdb $wpdb WordPress database abstraction object.
+ * @global PasswordHash $wp_hasher Portable PHP password hashing framework instance.
*
* @param string $user_login Optional. Username to send a password retrieval email for.
* Defaults to `$_POST['user_login']` if not set.
@@ -3024,15 +3103,22 @@
$user_login = $_POST['user_login'];
}
+ $user_login = trim( wp_unslash( $user_login ) );
+
if ( empty( $user_login ) ) {
- $errors->add( 'empty_username', __( '<strong>Error</strong>: Please enter a username or email address.' ) );
+ $errors->add( 'empty_username', __( '<strong>Error:</strong> Please enter a username or email address.' ) );
} elseif ( strpos( $user_login, '@' ) ) {
- $user_data = get_user_by( 'email', trim( wp_unslash( $user_login ) ) );
+ $user_data = get_user_by( 'email', $user_login );
+
if ( empty( $user_data ) ) {
- $errors->add( 'invalid_email', __( '<strong>Error</strong>: There is no account with that username or email address.' ) );
+ $user_data = get_user_by( 'login', $user_login );
+ }
+
+ if ( empty( $user_data ) ) {
+ $errors->add( 'invalid_email', __( '<strong>Error:</strong> There is no account with that username or email address.' ) );
}
} else {
- $user_data = get_user_by( 'login', trim( wp_unslash( $user_login ) ) );
+ $user_data = get_user_by( 'login', $user_login );
}
/**
@@ -3083,7 +3169,7 @@
}
if ( ! $user_data ) {
- $errors->add( 'invalidcombo', __( '<strong>Error</strong>: There is no account with that username or email address.' ) );
+ $errors->add( 'invalidcombo', __( '<strong>Error:</strong> There is no account with that username or email address.' ) );
return $errors;
}
@@ -3114,7 +3200,7 @@
// Localize password reset message content for user.
$locale = get_user_locale( $user_data );
- $switched_locale = switch_to_locale( $locale );
+ $switched_locale = switch_to_user_locale( $user_data->ID );
if ( is_multisite() ) {
$site_name = get_network()->site_name;
@@ -3205,9 +3291,9 @@
* @type string $message The body of the email.
* @type string $headers The headers of the email.
* }
- * @type string $key The activation key.
- * @type string $user_login The username for the user.
- * @type WP_User $user_data WP_User object.
+ * @param string $key The activation key.
+ * @param string $user_login The username for the user.
+ * @param WP_User $user_data WP_User object.
*/
$notification_email = apply_filters( 'retrieve_password_notification_email', $defaults, $key, $user_login, $user_data );
@@ -3231,8 +3317,8 @@
'retrieve_password_email_failure',
sprintf(
/* translators: %s: Documentation URL. */
- __( '<strong>Error</strong>: The email could not be sent. Your site may not be correctly configured to send emails. <a href="%s">Get support for resetting your password</a>.' ),
- esc_url( __( 'https://wordpress.org/support/article/resetting-your-password/' ) )
+ __( '<strong>Error:</strong> The email could not be sent. Your site may not be correctly configured to send emails. <a href="%s">Get support for resetting your password</a>.' ),
+ esc_url( __( 'https://wordpress.org/documentation/article/reset-your-password/' ) )
)
);
return $errors;
@@ -3298,26 +3384,25 @@
// Check the username.
if ( '' === $sanitized_user_login ) {
- $errors->add( 'empty_username', __( '<strong>Error</strong>: Please enter a username.' ) );
+ $errors->add( 'empty_username', __( '<strong>Error:</strong> Please enter a username.' ) );
} elseif ( ! validate_username( $user_login ) ) {
- $errors->add( 'invalid_username', __( '<strong>Error</strong>: This username is invalid because it uses illegal characters. Please enter a valid username.' ) );
+ $errors->add( 'invalid_username', __( '<strong>Error:</strong> This username is invalid because it uses illegal characters. Please enter a valid username.' ) );
$sanitized_user_login = '';
} elseif ( username_exists( $sanitized_user_login ) ) {
- $errors->add( 'username_exists', __( '<strong>Error</strong>: This username is already registered. Please choose another one.' ) );
-
+ $errors->add( 'username_exists', __( '<strong>Error:</strong> This username is already registered. Please choose another one.' ) );
} else {
/** This filter is documented in wp-includes/user.php */
$illegal_user_logins = (array) apply_filters( 'illegal_user_logins', array() );
if ( in_array( strtolower( $sanitized_user_login ), array_map( 'strtolower', $illegal_user_logins ), true ) ) {
- $errors->add( 'invalid_username', __( '<strong>Error</strong>: Sorry, that username is not allowed.' ) );
+ $errors->add( 'invalid_username', __( '<strong>Error:</strong> Sorry, that username is not allowed.' ) );
}
}
// Check the email address.
if ( '' === $user_email ) {
- $errors->add( 'empty_email', __( '<strong>Error</strong>: Please type your email address.' ) );
+ $errors->add( 'empty_email', __( '<strong>Error:</strong> Please type your email address.' ) );
} elseif ( ! is_email( $user_email ) ) {
- $errors->add( 'invalid_email', __( '<strong>Error</strong>: The email address is not correct.' ) );
+ $errors->add( 'invalid_email', __( '<strong>Error:</strong> The email address is not correct.' ) );
$user_email = '';
} elseif ( email_exists( $user_email ) ) {
$errors->add(
@@ -3372,7 +3457,7 @@
'registerfail',
sprintf(
/* translators: %s: Admin email address. */
- __( '<strong>Error</strong>: Could not register you… please contact the <a href="mailto:%s">site admin</a>!' ),
+ __( '<strong>Error:</strong> Could not register you… please contact the <a href="mailto:%s">site admin</a>!' ),
get_option( 'admin_email' )
)
);
@@ -3484,6 +3569,8 @@
* @since 4.4.0
* @since 4.9.0 The `$site_id` parameter was added to support multisite.
*
+ * @global wpdb $wpdb WordPress database abstraction object.
+ *
* @param int|null $site_id Optional. The site ID to get users with no role for. Defaults to the current site.
* @return string[] Array of user IDs as strings.
*/
@@ -3496,7 +3583,7 @@
$prefix = $wpdb->get_blog_prefix( $site_id );
- if ( is_multisite() && get_current_blog_id() != $site_id ) {
+ if ( is_multisite() && get_current_blog_id() !== $site_id ) {
switch_to_blog( $site_id );
$role_names = wp_roles()->get_names();
restore_current_blog();
@@ -3508,12 +3595,10 @@
$regex = preg_replace( '/[^a-zA-Z_\|-]/', '', $regex );
$users = $wpdb->get_col(
$wpdb->prepare(
- "
- SELECT user_id
- FROM $wpdb->usermeta
- WHERE meta_key = '{$prefix}capabilities'
- AND meta_value NOT REGEXP %s
- ",
+ "SELECT user_id
+ FROM $wpdb->usermeta
+ WHERE meta_key = '{$prefix}capabilities'
+ AND meta_value NOT REGEXP %s",
$regex
)
);
@@ -3607,15 +3692,15 @@
$errors = new WP_Error();
}
- if ( $current_user->ID != $_POST['user_id'] ) {
+ if ( $current_user->ID !== (int) $_POST['user_id'] ) {
return false;
}
- if ( $current_user->user_email != $_POST['email'] ) {
+ if ( $current_user->user_email !== $_POST['email'] ) {
if ( ! is_email( $_POST['email'] ) ) {
$errors->add(
'user_email',
- __( '<strong>Error</strong>: The email address is not correct.' ),
+ __( '<strong>Error:</strong> The email address is not correct.' ),
array(
'form-field' => 'email',
)
@@ -3627,7 +3712,7 @@
if ( email_exists( $_POST['email'] ) ) {
$errors->add(
'user_email',
- __( '<strong>Error</strong>: The email address is already used.' ),
+ __( '<strong>Error:</strong> The email address is already used.' ),
array(
'form-field' => 'email',
)
@@ -3689,7 +3774,7 @@
$content = apply_filters( 'new_user_email_content', $email_text, $new_user_email );
$content = str_replace( '###USERNAME###', $current_user->user_login, $content );
- $content = str_replace( '###ADMIN_URL###', esc_url( admin_url( 'profile.php?newuseremail=' . $hash ) ), $content );
+ $content = str_replace( '###ADMIN_URL###', esc_url( self_admin_url( 'profile.php?newuseremail=' . $hash ) ), $content );
$content = str_replace( '###EMAIL###', $_POST['email'], $content );
$content = str_replace( '###SITENAME###', $sitename, $content );
$content = str_replace( '###SITEURL###', home_url(), $content );
@@ -3716,8 +3801,12 @@
if ( 'profile.php' === $pagenow && isset( $_GET['updated'] ) ) {
$email = get_user_meta( get_current_user_id(), '_new_email', true );
if ( $email ) {
- /* translators: %s: New email address. */
- echo '<div class="notice notice-info"><p>' . sprintf( __( 'Your email address has not been updated yet. Please check your inbox at %s for a confirmation email.' ), '<code>' . esc_html( $email['newemail'] ) . '</code>' ) . '</p></div>';
+ $message = sprintf(
+ /* translators: %s: New email address. */
+ __( 'Your email address has not been updated yet. Please check your inbox at %s for a confirmation email.' ),
+ '<code>' . esc_html( $email['newemail'] ) . '</code>'
+ );
+ wp_admin_notice( $message, array( 'type' => 'info' ) );
}
}
}
@@ -3728,7 +3817,7 @@
* @since 4.9.6
* @access private
*
- * @return array List of core privacy action types.
+ * @return string[] List of core privacy action types.
*/
function _wp_privacy_action_request_types() {
return array(
@@ -3742,8 +3831,8 @@
*
* @since 4.9.6
*
- * @param array $exporters An array of personal data exporters.
- * @return array An array of personal data exporters.
+ * @param array[] $exporters An array of personal data exporters.
+ * @return array[] An array of personal data exporters.
*/
function wp_register_user_personal_data_exporter( $exporters ) {
$exporters['wordpress-user'] = array(
@@ -3762,7 +3851,12 @@
* @since 5.4.0 Added 'Session Tokens' group to the export data.
*
* @param string $email_address The user's email address.
- * @return array An array of personal data.
+ * @return array {
+ * An array of personal data.
+ *
+ * @type array[] $data An array of personal data arrays.
+ * @type bool $done Whether the exporter is finished.
+ * }
*/
function wp_user_personal_data_exporter( $email_address ) {
$email_address = trim( $email_address );
@@ -3849,7 +3943,7 @@
// Remove items that use reserved names.
$extra_data = array_filter(
$_extra_data,
- static function( $item ) use ( $reserved_names ) {
+ static function ( $item ) use ( $reserved_names ) {
return ! in_array( $item['name'], $reserved_names, true );
}
);
@@ -3993,7 +4087,7 @@
function _wp_privacy_send_request_confirmation_notification( $request_id ) {
$request = wp_get_user_request( $request_id );
- if ( ! is_a( $request, 'WP_User_Request' ) || 'request-confirmed' !== $request->status ) {
+ if ( ! ( $request instanceof WP_User_Request ) || 'request-confirmed' !== $request->status ) {
return;
}
@@ -4158,8 +4252,8 @@
$content = str_replace( '###SITENAME###', $email_data['sitename'], $content );
$content = str_replace( '###USER_EMAIL###', $email_data['user_email'], $content );
$content = str_replace( '###DESCRIPTION###', $email_data['description'], $content );
- $content = str_replace( '###MANAGE_URL###', esc_url_raw( $email_data['manage_url'] ), $content );
- $content = str_replace( '###SITEURL###', esc_url_raw( $email_data['siteurl'] ), $content );
+ $content = str_replace( '###MANAGE_URL###', sanitize_url( $email_data['manage_url'] ), $content );
+ $content = str_replace( '###SITEURL###', sanitize_url( $email_data['siteurl'] ), $content );
$headers = '';
@@ -4205,7 +4299,7 @@
function _wp_privacy_send_erasure_fulfillment_notification( $request_id ) {
$request = wp_get_user_request( $request_id );
- if ( ! is_a( $request, 'WP_User_Request' ) || 'request-completed' !== $request->status ) {
+ if ( ! ( $request instanceof WP_User_Request ) || 'request-completed' !== $request->status ) {
return;
}
@@ -4217,12 +4311,10 @@
// Localize message content for user; fallback to site default for visitors.
if ( ! empty( $request->user_id ) ) {
- $locale = get_user_locale( $request->user_id );
+ $switched_locale = switch_to_user_locale( $request->user_id );
} else {
- $locale = get_locale();
- }
-
- $switched_locale = switch_to_locale( $locale );
+ $switched_locale = switch_to_locale( get_locale() );
+ }
/**
* Filters the recipient of the data erasure fulfillment notification.
@@ -4399,7 +4491,7 @@
$content = str_replace( '###SITENAME###', $email_data['sitename'], $content );
$content = str_replace( '###PRIVACY_POLICY_URL###', $email_data['privacy_policy_url'], $content );
- $content = str_replace( '###SITEURL###', esc_url_raw( $email_data['siteurl'] ), $content );
+ $content = str_replace( '###SITEURL###', sanitize_url( $email_data['siteurl'] ), $content );
$headers = '';
@@ -4628,12 +4720,10 @@
// Localize message content for user; fallback to site default for visitors.
if ( ! empty( $request->user_id ) ) {
- $locale = get_user_locale( $request->user_id );
+ $switched_locale = switch_to_user_locale( $request->user_id );
} else {
- $locale = get_locale();
- }
-
- $switched_locale = switch_to_locale( $locale );
+ $switched_locale = switch_to_locale( get_locale() );
+ }
$email_data = array(
'request' => $request,
@@ -4720,10 +4810,10 @@
$content = apply_filters( 'user_request_action_email_content', $content, $email_data );
$content = str_replace( '###DESCRIPTION###', $email_data['description'], $content );
- $content = str_replace( '###CONFIRM_URL###', esc_url_raw( $email_data['confirm_url'] ), $content );
+ $content = str_replace( '###CONFIRM_URL###', sanitize_url( $email_data['confirm_url'] ), $content );
$content = str_replace( '###EMAIL###', $email_data['email'], $content );
$content = str_replace( '###SITENAME###', $email_data['sitename'], $content );
- $content = str_replace( '###SITEURL###', esc_url_raw( $email_data['siteurl'] ), $content );
+ $content = str_replace( '###SITEURL###', sanitize_url( $email_data['siteurl'] ), $content );
$headers = '';
@@ -4767,6 +4857,8 @@
*
* @since 4.9.6
*
+ * @global PasswordHash $wp_hasher Portable PHP password hashing framework instance.
+ *
* @param int $request_id Request ID.
* @return string Confirmation key.
*/
@@ -4798,6 +4890,8 @@
*
* @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.
@@ -4936,3 +5030,91 @@
*/
return apply_filters( 'wp_is_application_passwords_available_for_user', true, $user );
}
+
+/**
+ * Registers the user meta property for persisted preferences.
+ *
+ * This property is used to store user preferences across page reloads and is
+ * currently used by the block editor for preferences like 'fullscreenMode' and
+ * 'fixedToolbar'.
+ *
+ * @since 6.1.0
+ * @access private
+ *
+ * @global wpdb $wpdb WordPress database abstraction object.
+ */
+function wp_register_persisted_preferences_meta() {
+ /*
+ * Create a meta key that incorporates the blog prefix so that each site
+ * on a multisite can have distinct user preferences.
+ */
+ global $wpdb;
+ $meta_key = $wpdb->get_blog_prefix() . 'persisted_preferences';
+
+ register_meta(
+ 'user',
+ $meta_key,
+ array(
+ 'type' => 'object',
+ 'single' => true,
+ 'show_in_rest' => array(
+ 'name' => 'persisted_preferences',
+ 'type' => 'object',
+ 'schema' => array(
+ 'type' => 'object',
+ 'context' => array( 'edit' ),
+ 'properties' => array(
+ '_modified' => array(
+ 'description' => __( 'The date and time the preferences were updated.' ),
+ 'type' => 'string',
+ 'format' => 'date-time',
+ 'readonly' => false,
+ ),
+ ),
+ 'additionalProperties' => true,
+ ),
+ ),
+ )
+ );
+}
+
+/**
+ * Sets the last changed time for the 'users' cache group.
+ *
+ * @since 6.3.0
+ */
+function wp_cache_set_users_last_changed() {
+ wp_cache_set_last_changed( 'users' );
+}
+
+/**
+ * Checks if password reset is allowed for a specific user.
+ *
+ * @since 6.3.0
+ *
+ * @param int|WP_User $user The user to check.
+ * @return bool|WP_Error True if allowed, false or WP_Error otherwise.
+ */
+function wp_is_password_reset_allowed_for_user( $user ) {
+ if ( ! is_object( $user ) ) {
+ $user = get_userdata( $user );
+ }
+
+ if ( ! $user || ! $user->exists() ) {
+ return false;
+ }
+ $allow = true;
+ if ( is_multisite() && is_user_spammy( $user ) ) {
+ $allow = false;
+ }
+
+ /**
+ * Filters whether to allow a password to be reset.
+ *
+ * @since 2.7.0
+ *
+ * @param bool $allow Whether to allow the password to be reset. Default true.
+ * @param int $user_id The ID of the user attempting to reset a password.
+ */
+ return apply_filters( 'allow_password_reset', $allow, $user->ID );
+}