[ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Core User API 4 * 5 * @package WordPress 6 * @subpackage Users 7 */ 8 9 /** 10 * Authenticates and logs a user in with 'remember' capability. 11 * 12 * The credentials is an array that has 'user_login', 'user_password', and 13 * 'remember' indices. If the credentials is not given, then the log in form 14 * will be assumed and used if set. 15 * 16 * The various authentication cookies will be set by this function and will be 17 * set for a longer period depending on if the 'remember' credential is set to 18 * true. 19 * 20 * Note: wp_signon() doesn't handle setting the current user. This means that if the 21 * function is called before the {@see 'init'} hook is fired, is_user_logged_in() will 22 * evaluate as false until that point. If is_user_logged_in() is needed in conjunction 23 * with wp_signon(), wp_set_current_user() should be called explicitly. 24 * 25 * @since 2.5.0 26 * 27 * @global string $auth_secure_cookie 28 * @global wpdb $wpdb WordPress database abstraction object. 29 * 30 * @param array $credentials { 31 * Optional. User info in order to sign on. 32 * 33 * @type string $user_login Username. 34 * @type string $user_password User password. 35 * @type bool $remember Whether to 'remember' the user. Increases the time 36 * that the cookie will be kept. Default false. 37 * } 38 * @param string|bool $secure_cookie Optional. Whether to use secure cookie. 39 * @return WP_User|WP_Error WP_User on success, WP_Error on failure. 40 */ 41 function wp_signon( $credentials = array(), $secure_cookie = '' ) { 42 global $auth_secure_cookie, $wpdb; 43 44 if ( empty( $credentials ) ) { 45 $credentials = array( 46 'user_login' => '', 47 'user_password' => '', 48 'remember' => false, 49 ); 50 51 if ( ! empty( $_POST['log'] ) ) { 52 $credentials['user_login'] = wp_unslash( $_POST['log'] ); 53 } 54 if ( ! empty( $_POST['pwd'] ) ) { 55 $credentials['user_password'] = $_POST['pwd']; 56 } 57 if ( ! empty( $_POST['rememberme'] ) ) { 58 $credentials['remember'] = $_POST['rememberme']; 59 } 60 } 61 62 if ( ! empty( $credentials['remember'] ) ) { 63 $credentials['remember'] = true; 64 } else { 65 $credentials['remember'] = false; 66 } 67 68 /** 69 * Fires before the user is authenticated. 70 * 71 * The variables passed to the callbacks are passed by reference, 72 * and can be modified by callback functions. 73 * 74 * @since 1.5.1 75 * 76 * @todo Decide whether to deprecate the wp_authenticate action. 77 * 78 * @param string $user_login Username (passed by reference). 79 * @param string $user_password User password (passed by reference). 80 */ 81 do_action_ref_array( 'wp_authenticate', array( &$credentials['user_login'], &$credentials['user_password'] ) ); 82 83 if ( '' === $secure_cookie ) { 84 $secure_cookie = is_ssl(); 85 } 86 87 /** 88 * Filters whether to use a secure sign-on cookie. 89 * 90 * @since 3.1.0 91 * 92 * @param bool $secure_cookie Whether to use a secure sign-on cookie. 93 * @param array $credentials { 94 * Array of entered sign-on data. 95 * 96 * @type string $user_login Username. 97 * @type string $user_password Password entered. 98 * @type bool $remember Whether to 'remember' the user. Increases the time 99 * that the cookie will be kept. Default false. 100 * } 101 */ 102 $secure_cookie = apply_filters( 'secure_signon_cookie', $secure_cookie, $credentials ); 103 104 // XXX ugly hack to pass this to wp_authenticate_cookie(). 105 $auth_secure_cookie = $secure_cookie; 106 107 add_filter( 'authenticate', 'wp_authenticate_cookie', 30, 3 ); 108 109 $user = wp_authenticate( $credentials['user_login'], $credentials['user_password'] ); 110 111 if ( is_wp_error( $user ) ) { 112 return $user; 113 } 114 115 wp_set_auth_cookie( $user->ID, $credentials['remember'], $secure_cookie ); 116 117 // Clear `user_activation_key` after a successful login. 118 if ( ! empty( $user->user_activation_key ) ) { 119 $wpdb->update( 120 $wpdb->users, 121 array( 122 'user_activation_key' => '', 123 ), 124 array( 'ID' => $user->ID ) 125 ); 126 127 $user->user_activation_key = ''; 128 } 129 130 /** 131 * Fires after the user has successfully logged in. 132 * 133 * @since 1.5.0 134 * 135 * @param string $user_login Username. 136 * @param WP_User $user WP_User object of the logged-in user. 137 */ 138 do_action( 'wp_login', $user->user_login, $user ); 139 140 return $user; 141 } 142 143 /** 144 * Authenticates a user, confirming the username and password are valid. 145 * 146 * @since 2.8.0 147 * 148 * @param WP_User|WP_Error|null $user WP_User or WP_Error object from a previous callback. Default null. 149 * @param string $username Username for authentication. 150 * @param string $password Password for authentication. 151 * @return WP_User|WP_Error WP_User on success, WP_Error on failure. 152 */ 153 function wp_authenticate_username_password( $user, $username, $password ) { 154 if ( $user instanceof WP_User ) { 155 return $user; 156 } 157 158 if ( empty( $username ) || empty( $password ) ) { 159 if ( is_wp_error( $user ) ) { 160 return $user; 161 } 162 163 $error = new WP_Error(); 164 165 if ( empty( $username ) ) { 166 $error->add( 'empty_username', __( '<strong>Error:</strong> The username field is empty.' ) ); 167 } 168 169 if ( empty( $password ) ) { 170 $error->add( 'empty_password', __( '<strong>Error:</strong> The password field is empty.' ) ); 171 } 172 173 return $error; 174 } 175 176 $user = get_user_by( 'login', $username ); 177 178 if ( ! $user ) { 179 return new WP_Error( 180 'invalid_username', 181 sprintf( 182 /* translators: %s: User name. */ 183 __( '<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.' ), 184 $username 185 ) 186 ); 187 } 188 189 /** 190 * Filters whether the given user can be authenticated with the provided password. 191 * 192 * @since 2.5.0 193 * 194 * @param WP_User|WP_Error $user WP_User or WP_Error object if a previous 195 * callback failed authentication. 196 * @param string $password Password to check against the user. 197 */ 198 $user = apply_filters( 'wp_authenticate_user', $user, $password ); 199 if ( is_wp_error( $user ) ) { 200 return $user; 201 } 202 203 if ( ! wp_check_password( $password, $user->user_pass, $user->ID ) ) { 204 return new WP_Error( 205 'incorrect_password', 206 sprintf( 207 /* translators: %s: User name. */ 208 __( '<strong>Error:</strong> The password you entered for the username %s is incorrect.' ), 209 '<strong>' . $username . '</strong>' 210 ) . 211 ' <a href="' . wp_lostpassword_url() . '">' . 212 __( 'Lost your password?' ) . 213 '</a>' 214 ); 215 } 216 217 return $user; 218 } 219 220 /** 221 * Authenticates a user using the email and password. 222 * 223 * @since 4.5.0 224 * 225 * @param WP_User|WP_Error|null $user WP_User or WP_Error object if a previous 226 * callback failed authentication. 227 * @param string $email Email address for authentication. 228 * @param string $password Password for authentication. 229 * @return WP_User|WP_Error WP_User on success, WP_Error on failure. 230 */ 231 function wp_authenticate_email_password( $user, $email, $password ) { 232 if ( $user instanceof WP_User ) { 233 return $user; 234 } 235 236 if ( empty( $email ) || empty( $password ) ) { 237 if ( is_wp_error( $user ) ) { 238 return $user; 239 } 240 241 $error = new WP_Error(); 242 243 if ( empty( $email ) ) { 244 // Uses 'empty_username' for back-compat with wp_signon(). 245 $error->add( 'empty_username', __( '<strong>Error:</strong> The email field is empty.' ) ); 246 } 247 248 if ( empty( $password ) ) { 249 $error->add( 'empty_password', __( '<strong>Error:</strong> The password field is empty.' ) ); 250 } 251 252 return $error; 253 } 254 255 if ( ! is_email( $email ) ) { 256 return $user; 257 } 258 259 $user = get_user_by( 'email', $email ); 260 261 if ( ! $user ) { 262 return new WP_Error( 263 'invalid_email', 264 __( 'Unknown email address. Check again or try your username.' ) 265 ); 266 } 267 268 /** This filter is documented in wp-includes/user.php */ 269 $user = apply_filters( 'wp_authenticate_user', $user, $password ); 270 271 if ( is_wp_error( $user ) ) { 272 return $user; 273 } 274 275 if ( ! wp_check_password( $password, $user->user_pass, $user->ID ) ) { 276 return new WP_Error( 277 'incorrect_password', 278 sprintf( 279 /* translators: %s: Email address. */ 280 __( '<strong>Error:</strong> The password you entered for the email address %s is incorrect.' ), 281 '<strong>' . $email . '</strong>' 282 ) . 283 ' <a href="' . wp_lostpassword_url() . '">' . 284 __( 'Lost your password?' ) . 285 '</a>' 286 ); 287 } 288 289 return $user; 290 } 291 292 /** 293 * Authenticates the user using the WordPress auth cookie. 294 * 295 * @since 2.8.0 296 * 297 * @global string $auth_secure_cookie 298 * 299 * @param WP_User|WP_Error|null $user WP_User or WP_Error object from a previous callback. Default null. 300 * @param string $username Username. If not empty, cancels the cookie authentication. 301 * @param string $password Password. If not empty, cancels the cookie authentication. 302 * @return WP_User|WP_Error WP_User on success, WP_Error on failure. 303 */ 304 function wp_authenticate_cookie( $user, $username, $password ) { 305 global $auth_secure_cookie; 306 307 if ( $user instanceof WP_User ) { 308 return $user; 309 } 310 311 if ( empty( $username ) && empty( $password ) ) { 312 $user_id = wp_validate_auth_cookie(); 313 if ( $user_id ) { 314 return new WP_User( $user_id ); 315 } 316 317 if ( $auth_secure_cookie ) { 318 $auth_cookie = SECURE_AUTH_COOKIE; 319 } else { 320 $auth_cookie = AUTH_COOKIE; 321 } 322 323 if ( ! empty( $_COOKIE[ $auth_cookie ] ) ) { 324 return new WP_Error( 'expired_session', __( 'Please log in again.' ) ); 325 } 326 327 // If the cookie is not set, be silent. 328 } 329 330 return $user; 331 } 332 333 /** 334 * Authenticates the user using an application password. 335 * 336 * @since 5.6.0 337 * 338 * @param WP_User|WP_Error|null $input_user WP_User or WP_Error object if a previous 339 * callback failed authentication. 340 * @param string $username Username for authentication. 341 * @param string $password Password for authentication. 342 * @return WP_User|WP_Error|null WP_User on success, WP_Error on failure, null if 343 * null is passed in and this isn't an API request. 344 */ 345 function wp_authenticate_application_password( $input_user, $username, $password ) { 346 if ( $input_user instanceof WP_User ) { 347 return $input_user; 348 } 349 350 if ( ! WP_Application_Passwords::is_in_use() ) { 351 return $input_user; 352 } 353 354 // The 'REST_REQUEST' check here may happen too early for the constant to be available. 355 $is_api_request = ( ( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST ) || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ); 356 357 /** 358 * Filters whether this is an API request that Application Passwords can be used on. 359 * 360 * By default, Application Passwords is available for the REST API and XML-RPC. 361 * 362 * @since 5.6.0 363 * 364 * @param bool $is_api_request If this is an acceptable API request. 365 */ 366 $is_api_request = apply_filters( 'application_password_is_api_request', $is_api_request ); 367 368 if ( ! $is_api_request ) { 369 return $input_user; 370 } 371 372 $error = null; 373 $user = get_user_by( 'login', $username ); 374 375 if ( ! $user && is_email( $username ) ) { 376 $user = get_user_by( 'email', $username ); 377 } 378 379 // If the login name is invalid, short circuit. 380 if ( ! $user ) { 381 if ( is_email( $username ) ) { 382 $error = new WP_Error( 383 'invalid_email', 384 __( '<strong>Error:</strong> Unknown email address. Check again or try your username.' ) 385 ); 386 } else { 387 $error = new WP_Error( 388 'invalid_username', 389 __( '<strong>Error:</strong> Unknown username. Check again or try your email address.' ) 390 ); 391 } 392 } elseif ( ! wp_is_application_passwords_available() ) { 393 $error = new WP_Error( 394 'application_passwords_disabled', 395 __( 'Application passwords are not available.' ) 396 ); 397 } elseif ( ! wp_is_application_passwords_available_for_user( $user ) ) { 398 $error = new WP_Error( 399 'application_passwords_disabled_for_user', 400 __( 'Application passwords are not available for your account. Please contact the site administrator for assistance.' ) 401 ); 402 } 403 404 if ( $error ) { 405 /** 406 * Fires when an application password failed to authenticate the user. 407 * 408 * @since 5.6.0 409 * 410 * @param WP_Error $error The authentication error. 411 */ 412 do_action( 'application_password_failed_authentication', $error ); 413 414 return $error; 415 } 416 417 /* 418 * Strips out anything non-alphanumeric. This is so passwords can be used with 419 * or without spaces to indicate the groupings for readability. 420 * 421 * Generated application passwords are exclusively alphanumeric. 422 */ 423 $password = preg_replace( '/[^a-z\d]/i', '', $password ); 424 425 $hashed_passwords = WP_Application_Passwords::get_user_application_passwords( $user->ID ); 426 427 foreach ( $hashed_passwords as $key => $item ) { 428 if ( ! wp_check_password( $password, $item['password'], $user->ID ) ) { 429 continue; 430 } 431 432 $error = new WP_Error(); 433 434 /** 435 * Fires when an application password has been successfully checked as valid. 436 * 437 * This allows for plugins to add additional constraints to prevent an application password from being used. 438 * 439 * @since 5.6.0 440 * 441 * @param WP_Error $error The error object. 442 * @param WP_User $user The user authenticating. 443 * @param array $item The details about the application password. 444 * @param string $password The raw supplied password. 445 */ 446 do_action( 'wp_authenticate_application_password_errors', $error, $user, $item, $password ); 447 448 if ( is_wp_error( $error ) && $error->has_errors() ) { 449 /** This action is documented in wp-includes/user.php */ 450 do_action( 'application_password_failed_authentication', $error ); 451 452 return $error; 453 } 454 455 WP_Application_Passwords::record_application_password_usage( $user->ID, $item['uuid'] ); 456 457 /** 458 * Fires after an application password was used for authentication. 459 * 460 * @since 5.6.0 461 * 462 * @param WP_User $user The user who was authenticated. 463 * @param array $item The application password used. 464 */ 465 do_action( 'application_password_did_authenticate', $user, $item ); 466 467 return $user; 468 } 469 470 $error = new WP_Error( 471 'incorrect_password', 472 __( 'The provided password is an invalid application password.' ) 473 ); 474 475 /** This action is documented in wp-includes/user.php */ 476 do_action( 'application_password_failed_authentication', $error ); 477 478 return $error; 479 } 480 481 /** 482 * Validates the application password credentials passed via Basic Authentication. 483 * 484 * @since 5.6.0 485 * 486 * @param int|false $input_user User ID if one has been determined, false otherwise. 487 * @return int|false The authenticated user ID if successful, false otherwise. 488 */ 489 function wp_validate_application_password( $input_user ) { 490 // Don't authenticate twice. 491 if ( ! empty( $input_user ) ) { 492 return $input_user; 493 } 494 495 if ( ! wp_is_application_passwords_available() ) { 496 return $input_user; 497 } 498 499 // Both $_SERVER['PHP_AUTH_USER'] and $_SERVER['PHP_AUTH_PW'] must be set in order to attempt authentication. 500 if ( ! isset( $_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'] ) ) { 501 return $input_user; 502 } 503 504 $authenticated = wp_authenticate_application_password( null, $_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'] ); 505 506 if ( $authenticated instanceof WP_User ) { 507 return $authenticated->ID; 508 } 509 510 // If it wasn't a user what got returned, just pass on what we had received originally. 511 return $input_user; 512 } 513 514 /** 515 * For Multisite blogs, checks if the authenticated user has been marked as a 516 * spammer, or if the user's primary blog has been marked as spam. 517 * 518 * @since 3.7.0 519 * 520 * @param WP_User|WP_Error|null $user WP_User or WP_Error object from a previous callback. Default null. 521 * @return WP_User|WP_Error WP_User on success, WP_Error if the user is considered a spammer. 522 */ 523 function wp_authenticate_spam_check( $user ) { 524 if ( $user instanceof WP_User && is_multisite() ) { 525 /** 526 * Filters whether the user has been marked as a spammer. 527 * 528 * @since 3.7.0 529 * 530 * @param bool $spammed Whether the user is considered a spammer. 531 * @param WP_User $user User to check against. 532 */ 533 $spammed = apply_filters( 'check_is_user_spammed', is_user_spammy( $user ), $user ); 534 535 if ( $spammed ) { 536 return new WP_Error( 'spammer_account', __( '<strong>Error:</strong> Your account has been marked as a spammer.' ) ); 537 } 538 } 539 return $user; 540 } 541 542 /** 543 * Validates the logged-in cookie. 544 * 545 * Checks the logged-in cookie if the previous auth cookie could not be 546 * validated and parsed. 547 * 548 * This is a callback for the {@see 'determine_current_user'} filter, rather than API. 549 * 550 * @since 3.9.0 551 * 552 * @param int|false $user_id The user ID (or false) as received from 553 * the `determine_current_user` filter. 554 * @return int|false User ID if validated, false otherwise. If a user ID from 555 * an earlier filter callback is received, that value is returned. 556 */ 557 function wp_validate_logged_in_cookie( $user_id ) { 558 if ( $user_id ) { 559 return $user_id; 560 } 561 562 if ( is_blog_admin() || is_network_admin() || empty( $_COOKIE[ LOGGED_IN_COOKIE ] ) ) { 563 return false; 564 } 565 566 return wp_validate_auth_cookie( $_COOKIE[ LOGGED_IN_COOKIE ], 'logged_in' ); 567 } 568 569 /** 570 * Gets the number of posts a user has written. 571 * 572 * @since 3.0.0 573 * @since 4.1.0 Added `$post_type` argument. 574 * @since 4.3.0 Added `$public_only` argument. Added the ability to pass an array 575 * of post types to `$post_type`. 576 * 577 * @global wpdb $wpdb WordPress database abstraction object. 578 * 579 * @param int $userid User ID. 580 * @param array|string $post_type Optional. Single post type or array of post types to count the number of posts for. Default 'post'. 581 * @param bool $public_only Optional. Whether to only return counts for public posts. Default false. 582 * @return string Number of posts the user has written in this post type. 583 */ 584 function count_user_posts( $userid, $post_type = 'post', $public_only = false ) { 585 global $wpdb; 586 587 $where = get_posts_by_author_sql( $post_type, true, $userid, $public_only ); 588 589 $count = $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->posts $where" ); 590 591 /** 592 * Filters the number of posts a user has written. 593 * 594 * @since 2.7.0 595 * @since 4.1.0 Added `$post_type` argument. 596 * @since 4.3.1 Added `$public_only` argument. 597 * 598 * @param int $count The user's post count. 599 * @param int $userid User ID. 600 * @param string|array $post_type Single post type or array of post types to count the number of posts for. 601 * @param bool $public_only Whether to limit counted posts to public posts. 602 */ 603 return apply_filters( 'get_usernumposts', $count, $userid, $post_type, $public_only ); 604 } 605 606 /** 607 * Gets the number of posts written by a list of users. 608 * 609 * @since 3.0.0 610 * 611 * @global wpdb $wpdb WordPress database abstraction object. 612 * 613 * @param int[] $users Array of user IDs. 614 * @param string|string[] $post_type Optional. Single post type or array of post types to check. Defaults to 'post'. 615 * @param bool $public_only Optional. Only return counts for public posts. Defaults to false. 616 * @return string[] Amount of posts each user has written, as strings, keyed by user ID. 617 */ 618 function count_many_users_posts( $users, $post_type = 'post', $public_only = false ) { 619 global $wpdb; 620 621 $count = array(); 622 if ( empty( $users ) || ! is_array( $users ) ) { 623 return $count; 624 } 625 626 $userlist = implode( ',', array_map( 'absint', $users ) ); 627 $where = get_posts_by_author_sql( $post_type, true, null, $public_only ); 628 629 $result = $wpdb->get_results( "SELECT post_author, COUNT(*) FROM $wpdb->posts $where AND post_author IN ($userlist) GROUP BY post_author", ARRAY_N ); 630 foreach ( $result as $row ) { 631 $count[ $row[0] ] = $row[1]; 632 } 633 634 foreach ( $users as $id ) { 635 if ( ! isset( $count[ $id ] ) ) { 636 $count[ $id ] = 0; 637 } 638 } 639 640 return $count; 641 } 642 643 // 644 // User option functions. 645 // 646 647 /** 648 * Gets the current user's ID. 649 * 650 * @since MU (3.0.0) 651 * 652 * @return int The current user's ID, or 0 if no user is logged in. 653 */ 654 function get_current_user_id() { 655 if ( ! function_exists( 'wp_get_current_user' ) ) { 656 return 0; 657 } 658 $user = wp_get_current_user(); 659 return ( isset( $user->ID ) ? (int) $user->ID : 0 ); 660 } 661 662 /** 663 * Retrieves user option that can be either per Site or per Network. 664 * 665 * If the user ID is not given, then the current user will be used instead. If 666 * the user ID is given, then the user data will be retrieved. The filter for 667 * the result, will also pass the original option name and finally the user data 668 * object as the third parameter. 669 * 670 * The option will first check for the per site name and then the per Network name. 671 * 672 * @since 2.0.0 673 * 674 * @global wpdb $wpdb WordPress database abstraction object. 675 * 676 * @param string $option User option name. 677 * @param int $user Optional. User ID. 678 * @param string $deprecated Use get_option() to check for an option in the options table. 679 * @return mixed User option value on success, false on failure. 680 */ 681 function get_user_option( $option, $user = 0, $deprecated = '' ) { 682 global $wpdb; 683 684 if ( ! empty( $deprecated ) ) { 685 _deprecated_argument( __FUNCTION__, '3.0.0' ); 686 } 687 688 if ( empty( $user ) ) { 689 $user = get_current_user_id(); 690 } 691 692 $user = get_userdata( $user ); 693 if ( ! $user ) { 694 return false; 695 } 696 697 $prefix = $wpdb->get_blog_prefix(); 698 if ( $user->has_prop( $prefix . $option ) ) { // Blog-specific. 699 $result = $user->get( $prefix . $option ); 700 } elseif ( $user->has_prop( $option ) ) { // User-specific and cross-blog. 701 $result = $user->get( $option ); 702 } else { 703 $result = false; 704 } 705 706 /** 707 * Filters a specific user option value. 708 * 709 * The dynamic portion of the hook name, `$option`, refers to the user option name. 710 * 711 * @since 2.5.0 712 * 713 * @param mixed $result Value for the user's option. 714 * @param string $option Name of the option being retrieved. 715 * @param WP_User $user WP_User object of the user whose option is being retrieved. 716 */ 717 return apply_filters( "get_user_option_{$option}", $result, $option, $user ); 718 } 719 720 /** 721 * Updates user option with global blog capability. 722 * 723 * User options are just like user metadata except that they have support for 724 * global blog options. If the 'is_global' parameter is false, which it is by default, 725 * it will prepend the WordPress table prefix to the option name. 726 * 727 * Deletes the user option if $newvalue is empty. 728 * 729 * @since 2.0.0 730 * 731 * @global wpdb $wpdb WordPress database abstraction object. 732 * 733 * @param int $user_id User ID. 734 * @param string $option_name User option name. 735 * @param mixed $newvalue User option value. 736 * @param bool $is_global Optional. Whether option name is global or blog specific. 737 * Default false (blog specific). 738 * @return int|bool User meta ID if the option didn't exist, true on successful update, 739 * false on failure. 740 */ 741 function update_user_option( $user_id, $option_name, $newvalue, $is_global = false ) { 742 global $wpdb; 743 744 if ( ! $is_global ) { 745 $option_name = $wpdb->get_blog_prefix() . $option_name; 746 } 747 748 return update_user_meta( $user_id, $option_name, $newvalue ); 749 } 750 751 /** 752 * Deletes user option with global blog capability. 753 * 754 * User options are just like user metadata except that they have support for 755 * global blog options. If the 'is_global' parameter is false, which it is by default, 756 * it will prepend the WordPress table prefix to the option name. 757 * 758 * @since 3.0.0 759 * 760 * @global wpdb $wpdb WordPress database abstraction object. 761 * 762 * @param int $user_id User ID 763 * @param string $option_name User option name. 764 * @param bool $is_global Optional. Whether option name is global or blog specific. 765 * Default false (blog specific). 766 * @return bool True on success, false on failure. 767 */ 768 function delete_user_option( $user_id, $option_name, $is_global = false ) { 769 global $wpdb; 770 771 if ( ! $is_global ) { 772 $option_name = $wpdb->get_blog_prefix() . $option_name; 773 } 774 775 return delete_user_meta( $user_id, $option_name ); 776 } 777 778 /** 779 * Retrieves list of users matching criteria. 780 * 781 * @since 3.1.0 782 * 783 * @see WP_User_Query 784 * 785 * @param array $args Optional. Arguments to retrieve users. See WP_User_Query::prepare_query() 786 * for more information on accepted arguments. 787 * @return array List of users. 788 */ 789 function get_users( $args = array() ) { 790 791 $args = wp_parse_args( $args ); 792 $args['count_total'] = false; 793 794 $user_search = new WP_User_Query( $args ); 795 796 return (array) $user_search->get_results(); 797 } 798 799 /** 800 * Lists all the users of the site, with several options available. 801 * 802 * @since 5.9.0 803 * 804 * @param string|array $args { 805 * Optional. Array or string of default arguments. 806 * 807 * @type string $orderby How to sort the users. Accepts 'nicename', 'email', 'url', 'registered', 808 * 'user_nicename', 'user_email', 'user_url', 'user_registered', 'name', 809 * 'display_name', 'post_count', 'ID', 'meta_value', 'user_login'. Default 'name'. 810 * @type string $order Sorting direction for $orderby. Accepts 'ASC', 'DESC'. Default 'ASC'. 811 * @type int $number Maximum users to return or display. Default empty (all users). 812 * @type bool $exclude_admin Whether to exclude the 'admin' account, if it exists. Default false. 813 * @type bool $show_fullname Whether to show the user's full name. Default false. 814 * @type string $feed If not empty, show a link to the user's feed and use this text as the alt 815 * parameter of the link. Default empty. 816 * @type string $feed_image If not empty, show a link to the user's feed and use this image URL as 817 * clickable anchor. Default empty. 818 * @type string $feed_type The feed type to link to, such as 'rss2'. Defaults to default feed type. 819 * @type bool $echo Whether to output the result or instead return it. Default true. 820 * @type string $style If 'list', each user is wrapped in an `<li>` element, otherwise the users 821 * will be separated by commas. 822 * @type bool $html Whether to list the items in HTML form or plaintext. Default true. 823 * @type string $exclude An array, comma-, or space-separated list of user IDs to exclude. Default empty. 824 * @type string $include An array, comma-, or space-separated list of user IDs to include. Default empty. 825 * } 826 * @return string|null The output if echo is false. Otherwise null. 827 */ 828 function wp_list_users( $args = array() ) { 829 $defaults = array( 830 'orderby' => 'name', 831 'order' => 'ASC', 832 'number' => '', 833 'exclude_admin' => true, 834 'show_fullname' => false, 835 'feed' => '', 836 'feed_image' => '', 837 'feed_type' => '', 838 'echo' => true, 839 'style' => 'list', 840 'html' => true, 841 'exclude' => '', 842 'include' => '', 843 ); 844 845 $parsed_args = wp_parse_args( $args, $defaults ); 846 847 $return = ''; 848 849 $query_args = wp_array_slice_assoc( $parsed_args, array( 'orderby', 'order', 'number', 'exclude', 'include' ) ); 850 $query_args['fields'] = 'ids'; 851 852 /** 853 * Filters the query arguments for the list of all users of the site. 854 * 855 * @since 6.1.0 856 * 857 * @param array $query_args The query arguments for get_users(). 858 * @param array $parsed_args The arguments passed to wp_list_users() combined with the defaults. 859 */ 860 $query_args = apply_filters( 'wp_list_users_args', $query_args, $parsed_args ); 861 862 $users = get_users( $query_args ); 863 864 foreach ( $users as $user_id ) { 865 $user = get_userdata( $user_id ); 866 867 if ( $parsed_args['exclude_admin'] && 'admin' === $user->display_name ) { 868 continue; 869 } 870 871 if ( $parsed_args['show_fullname'] && '' !== $user->first_name && '' !== $user->last_name ) { 872 $name = sprintf( 873 /* translators: 1: User's first name, 2: Last name. */ 874 _x( '%1$s %2$s', 'Display name based on first name and last name' ), 875 $user->first_name, 876 $user->last_name 877 ); 878 } else { 879 $name = $user->display_name; 880 } 881 882 if ( ! $parsed_args['html'] ) { 883 $return .= $name . ', '; 884 885 continue; // No need to go further to process HTML. 886 } 887 888 if ( 'list' === $parsed_args['style'] ) { 889 $return .= '<li>'; 890 } 891 892 $row = $name; 893 894 if ( ! empty( $parsed_args['feed_image'] ) || ! empty( $parsed_args['feed'] ) ) { 895 $row .= ' '; 896 if ( empty( $parsed_args['feed_image'] ) ) { 897 $row .= '('; 898 } 899 900 $row .= '<a href="' . get_author_feed_link( $user->ID, $parsed_args['feed_type'] ) . '"'; 901 902 $alt = ''; 903 if ( ! empty( $parsed_args['feed'] ) ) { 904 $alt = ' alt="' . esc_attr( $parsed_args['feed'] ) . '"'; 905 $name = $parsed_args['feed']; 906 } 907 908 $row .= '>'; 909 910 if ( ! empty( $parsed_args['feed_image'] ) ) { 911 $row .= '<img src="' . esc_url( $parsed_args['feed_image'] ) . '" style="border: none;"' . $alt . ' />'; 912 } else { 913 $row .= $name; 914 } 915 916 $row .= '</a>'; 917 918 if ( empty( $parsed_args['feed_image'] ) ) { 919 $row .= ')'; 920 } 921 } 922 923 $return .= $row; 924 $return .= ( 'list' === $parsed_args['style'] ) ? '</li>' : ', '; 925 } 926 927 $return = rtrim( $return, ', ' ); 928 929 if ( ! $parsed_args['echo'] ) { 930 return $return; 931 } 932 echo $return; 933 } 934 935 /** 936 * Gets the sites a user belongs to. 937 * 938 * @since 3.0.0 939 * @since 4.7.0 Converted to use `get_sites()`. 940 * 941 * @global wpdb $wpdb WordPress database abstraction object. 942 * 943 * @param int $user_id User ID 944 * @param bool $all Whether to retrieve all sites, or only sites that are not 945 * marked as deleted, archived, or spam. 946 * @return object[] A list of the user's sites. An empty array if the user doesn't exist 947 * or belongs to no sites. 948 */ 949 function get_blogs_of_user( $user_id, $all = false ) { 950 global $wpdb; 951 952 $user_id = (int) $user_id; 953 954 // Logged out users can't have sites. 955 if ( empty( $user_id ) ) { 956 return array(); 957 } 958 959 /** 960 * Filters the list of a user's sites before it is populated. 961 * 962 * Returning a non-null value from the filter will effectively short circuit 963 * get_blogs_of_user(), returning that value instead. 964 * 965 * @since 4.6.0 966 * 967 * @param null|object[] $sites An array of site objects of which the user is a member. 968 * @param int $user_id User ID. 969 * @param bool $all Whether the returned array should contain all sites, including 970 * those marked 'deleted', 'archived', or 'spam'. Default false. 971 */ 972 $sites = apply_filters( 'pre_get_blogs_of_user', null, $user_id, $all ); 973 974 if ( null !== $sites ) { 975 return $sites; 976 } 977 978 $keys = get_user_meta( $user_id ); 979 if ( empty( $keys ) ) { 980 return array(); 981 } 982 983 if ( ! is_multisite() ) { 984 $site_id = get_current_blog_id(); 985 $sites = array( $site_id => new stdClass() ); 986 $sites[ $site_id ]->userblog_id = $site_id; 987 $sites[ $site_id ]->blogname = get_option( 'blogname' ); 988 $sites[ $site_id ]->domain = ''; 989 $sites[ $site_id ]->path = ''; 990 $sites[ $site_id ]->site_id = 1; 991 $sites[ $site_id ]->siteurl = get_option( 'siteurl' ); 992 $sites[ $site_id ]->archived = 0; 993 $sites[ $site_id ]->spam = 0; 994 $sites[ $site_id ]->deleted = 0; 995 return $sites; 996 } 997 998 $site_ids = array(); 999 1000 if ( isset( $keys[ $wpdb->base_prefix . 'capabilities' ] ) && defined( 'MULTISITE' ) ) { 1001 $site_ids[] = 1; 1002 unset( $keys[ $wpdb->base_prefix . 'capabilities' ] ); 1003 } 1004 1005 $keys = array_keys( $keys ); 1006 1007 foreach ( $keys as $key ) { 1008 if ( ! str_ends_with( $key, 'capabilities' ) ) { 1009 continue; 1010 } 1011 if ( $wpdb->base_prefix && ! str_starts_with( $key, $wpdb->base_prefix ) ) { 1012 continue; 1013 } 1014 $site_id = str_replace( array( $wpdb->base_prefix, '_capabilities' ), '', $key ); 1015 if ( ! is_numeric( $site_id ) ) { 1016 continue; 1017 } 1018 1019 $site_ids[] = (int) $site_id; 1020 } 1021 1022 $sites = array(); 1023 1024 if ( ! empty( $site_ids ) ) { 1025 $args = array( 1026 'number' => '', 1027 'site__in' => $site_ids, 1028 ); 1029 if ( ! $all ) { 1030 $args['archived'] = 0; 1031 $args['spam'] = 0; 1032 $args['deleted'] = 0; 1033 } 1034 1035 $_sites = get_sites( $args ); 1036 1037 foreach ( $_sites as $site ) { 1038 $sites[ $site->id ] = (object) array( 1039 'userblog_id' => $site->id, 1040 'blogname' => $site->blogname, 1041 'domain' => $site->domain, 1042 'path' => $site->path, 1043 'site_id' => $site->network_id, 1044 'siteurl' => $site->siteurl, 1045 'archived' => $site->archived, 1046 'mature' => $site->mature, 1047 'spam' => $site->spam, 1048 'deleted' => $site->deleted, 1049 ); 1050 } 1051 } 1052 1053 /** 1054 * Filters the list of sites a user belongs to. 1055 * 1056 * @since MU (3.0.0) 1057 * 1058 * @param object[] $sites An array of site objects belonging to the user. 1059 * @param int $user_id User ID. 1060 * @param bool $all Whether the returned sites array should contain all sites, including 1061 * those marked 'deleted', 'archived', or 'spam'. Default false. 1062 */ 1063 return apply_filters( 'get_blogs_of_user', $sites, $user_id, $all ); 1064 } 1065 1066 /** 1067 * Finds out whether a user is a member of a given blog. 1068 * 1069 * @since MU (3.0.0) 1070 * 1071 * @global wpdb $wpdb WordPress database abstraction object. 1072 * 1073 * @param int $user_id Optional. The unique ID of the user. Defaults to the current user. 1074 * @param int $blog_id Optional. ID of the blog to check. Defaults to the current site. 1075 * @return bool 1076 */ 1077 function is_user_member_of_blog( $user_id = 0, $blog_id = 0 ) { 1078 global $wpdb; 1079 1080 $user_id = (int) $user_id; 1081 $blog_id = (int) $blog_id; 1082 1083 if ( empty( $user_id ) ) { 1084 $user_id = get_current_user_id(); 1085 } 1086 1087 /* 1088 * Technically not needed, but does save calls to get_site() and get_user_meta() 1089 * in the event that the function is called when a user isn't logged in. 1090 */ 1091 if ( empty( $user_id ) ) { 1092 return false; 1093 } else { 1094 $user = get_userdata( $user_id ); 1095 if ( ! $user instanceof WP_User ) { 1096 return false; 1097 } 1098 } 1099 1100 if ( ! is_multisite() ) { 1101 return true; 1102 } 1103 1104 if ( empty( $blog_id ) ) { 1105 $blog_id = get_current_blog_id(); 1106 } 1107 1108 $blog = get_site( $blog_id ); 1109 1110 if ( ! $blog || ! isset( $blog->domain ) || $blog->archived || $blog->spam || $blog->deleted ) { 1111 return false; 1112 } 1113 1114 $keys = get_user_meta( $user_id ); 1115 if ( empty( $keys ) ) { 1116 return false; 1117 } 1118 1119 // No underscore before capabilities in $base_capabilities_key. 1120 $base_capabilities_key = $wpdb->base_prefix . 'capabilities'; 1121 $site_capabilities_key = $wpdb->base_prefix . $blog_id . '_capabilities'; 1122 1123 if ( isset( $keys[ $base_capabilities_key ] ) && 1 === $blog_id ) { 1124 return true; 1125 } 1126 1127 if ( isset( $keys[ $site_capabilities_key ] ) ) { 1128 return true; 1129 } 1130 1131 return false; 1132 } 1133 1134 /** 1135 * Adds meta data to a user. 1136 * 1137 * @since 3.0.0 1138 * 1139 * @param int $user_id User ID. 1140 * @param string $meta_key Metadata name. 1141 * @param mixed $meta_value Metadata value. Must be serializable if non-scalar. 1142 * @param bool $unique Optional. Whether the same key should not be added. 1143 * Default false. 1144 * @return int|false Meta ID on success, false on failure. 1145 */ 1146 function add_user_meta( $user_id, $meta_key, $meta_value, $unique = false ) { 1147 return add_metadata( 'user', $user_id, $meta_key, $meta_value, $unique ); 1148 } 1149 1150 /** 1151 * Removes metadata matching criteria from a user. 1152 * 1153 * You can match based on the key, or key and value. Removing based on key and 1154 * value, will keep from removing duplicate metadata with the same key. It also 1155 * allows removing all metadata matching key, if needed. 1156 * 1157 * @since 3.0.0 1158 * 1159 * @link https://developer.wordpress.org/reference/functions/delete_user_meta/ 1160 * 1161 * @param int $user_id User ID 1162 * @param string $meta_key Metadata name. 1163 * @param mixed $meta_value Optional. Metadata value. If provided, 1164 * rows will only be removed that match the value. 1165 * Must be serializable if non-scalar. Default empty. 1166 * @return bool True on success, false on failure. 1167 */ 1168 function delete_user_meta( $user_id, $meta_key, $meta_value = '' ) { 1169 return delete_metadata( 'user', $user_id, $meta_key, $meta_value ); 1170 } 1171 1172 /** 1173 * Retrieves user meta field for a user. 1174 * 1175 * @since 3.0.0 1176 * 1177 * @link https://developer.wordpress.org/reference/functions/get_user_meta/ 1178 * 1179 * @param int $user_id User ID. 1180 * @param string $key Optional. The meta key to retrieve. By default, 1181 * returns data for all keys. 1182 * @param bool $single Optional. Whether to return a single value. 1183 * This parameter has no effect if `$key` is not specified. 1184 * Default false. 1185 * @return mixed An array of values if `$single` is false. 1186 * The value of meta data field if `$single` is true. 1187 * False for an invalid `$user_id` (non-numeric, zero, or negative value). 1188 * An empty string if a valid but non-existing user ID is passed. 1189 */ 1190 function get_user_meta( $user_id, $key = '', $single = false ) { 1191 return get_metadata( 'user', $user_id, $key, $single ); 1192 } 1193 1194 /** 1195 * Updates user meta field based on user ID. 1196 * 1197 * Use the $prev_value parameter to differentiate between meta fields with the 1198 * same key and user ID. 1199 * 1200 * If the meta field for the user does not exist, it will be added. 1201 * 1202 * @since 3.0.0 1203 * 1204 * @link https://developer.wordpress.org/reference/functions/update_user_meta/ 1205 * 1206 * @param int $user_id User ID. 1207 * @param string $meta_key Metadata key. 1208 * @param mixed $meta_value Metadata value. Must be serializable if non-scalar. 1209 * @param mixed $prev_value Optional. Previous value to check before updating. 1210 * If specified, only update existing metadata entries with 1211 * this value. Otherwise, update all entries. Default empty. 1212 * @return int|bool Meta ID if the key didn't exist, true on successful update, 1213 * false on failure or if the value passed to the function 1214 * is the same as the one that is already in the database. 1215 */ 1216 function update_user_meta( $user_id, $meta_key, $meta_value, $prev_value = '' ) { 1217 return update_metadata( 'user', $user_id, $meta_key, $meta_value, $prev_value ); 1218 } 1219 1220 /** 1221 * Counts number of users who have each of the user roles. 1222 * 1223 * Assumes there are neither duplicated nor orphaned capabilities meta_values. 1224 * Assumes role names are unique phrases. Same assumption made by WP_User_Query::prepare_query() 1225 * Using $strategy = 'time' this is CPU-intensive and should handle around 10^7 users. 1226 * Using $strategy = 'memory' this is memory-intensive and should handle around 10^5 users, but see WP Bug #12257. 1227 * 1228 * @since 3.0.0 1229 * @since 4.4.0 The number of users with no role is now included in the `none` element. 1230 * @since 4.9.0 The `$site_id` parameter was added to support multisite. 1231 * 1232 * @global wpdb $wpdb WordPress database abstraction object. 1233 * 1234 * @param string $strategy Optional. The computational strategy to use when counting the users. 1235 * Accepts either 'time' or 'memory'. Default 'time'. 1236 * @param int|null $site_id Optional. The site ID to count users for. Defaults to the current site. 1237 * @return array { 1238 * User counts. 1239 * 1240 * @type int $total_users Total number of users on the site. 1241 * @type int[] $avail_roles Array of user counts keyed by user role. 1242 * } 1243 */ 1244 function count_users( $strategy = 'time', $site_id = null ) { 1245 global $wpdb; 1246 1247 // Initialize. 1248 if ( ! $site_id ) { 1249 $site_id = get_current_blog_id(); 1250 } 1251 1252 /** 1253 * Filters the user count before queries are run. 1254 * 1255 * Return a non-null value to cause count_users() to return early. 1256 * 1257 * @since 5.1.0 1258 * 1259 * @param null|array $result The value to return instead. Default null to continue with the query. 1260 * @param string $strategy Optional. The computational strategy to use when counting the users. 1261 * Accepts either 'time' or 'memory'. Default 'time'. 1262 * @param int $site_id The site ID to count users for. 1263 */ 1264 $pre = apply_filters( 'pre_count_users', null, $strategy, $site_id ); 1265 1266 if ( null !== $pre ) { 1267 return $pre; 1268 } 1269 1270 $blog_prefix = $wpdb->get_blog_prefix( $site_id ); 1271 $result = array(); 1272 1273 if ( 'time' === $strategy ) { 1274 if ( is_multisite() && get_current_blog_id() !== $site_id ) { 1275 switch_to_blog( $site_id ); 1276 $avail_roles = wp_roles()->get_names(); 1277 restore_current_blog(); 1278 } else { 1279 $avail_roles = wp_roles()->get_names(); 1280 } 1281 1282 // Build a CPU-intensive query that will return concise information. 1283 $select_count = array(); 1284 foreach ( $avail_roles as $this_role => $name ) { 1285 $select_count[] = $wpdb->prepare( 'COUNT(NULLIF(`meta_value` LIKE %s, false))', '%' . $wpdb->esc_like( '"' . $this_role . '"' ) . '%' ); 1286 } 1287 $select_count[] = "COUNT(NULLIF(`meta_value` = 'a:0:{}', false))"; 1288 $select_count = implode( ', ', $select_count ); 1289 1290 // Add the meta_value index to the selection list, then run the query. 1291 $row = $wpdb->get_row( 1292 " 1293 SELECT {$select_count}, COUNT(*) 1294 FROM {$wpdb->usermeta} 1295 INNER JOIN {$wpdb->users} ON user_id = ID 1296 WHERE meta_key = '{$blog_prefix}capabilities' 1297 ", 1298 ARRAY_N 1299 ); 1300 1301 // Run the previous loop again to associate results with role names. 1302 $col = 0; 1303 $role_counts = array(); 1304 foreach ( $avail_roles as $this_role => $name ) { 1305 $count = (int) $row[ $col++ ]; 1306 if ( $count > 0 ) { 1307 $role_counts[ $this_role ] = $count; 1308 } 1309 } 1310 1311 $role_counts['none'] = (int) $row[ $col++ ]; 1312 1313 // Get the meta_value index from the end of the result set. 1314 $total_users = (int) $row[ $col ]; 1315 1316 $result['total_users'] = $total_users; 1317 $result['avail_roles'] =& $role_counts; 1318 } else { 1319 $avail_roles = array( 1320 'none' => 0, 1321 ); 1322 1323 $users_of_blog = $wpdb->get_col( 1324 " 1325 SELECT meta_value 1326 FROM {$wpdb->usermeta} 1327 INNER JOIN {$wpdb->users} ON user_id = ID 1328 WHERE meta_key = '{$blog_prefix}capabilities' 1329 " 1330 ); 1331 1332 foreach ( $users_of_blog as $caps_meta ) { 1333 $b_roles = maybe_unserialize( $caps_meta ); 1334 if ( ! is_array( $b_roles ) ) { 1335 continue; 1336 } 1337 if ( empty( $b_roles ) ) { 1338 ++$avail_roles['none']; 1339 } 1340 foreach ( $b_roles as $b_role => $val ) { 1341 if ( isset( $avail_roles[ $b_role ] ) ) { 1342 ++$avail_roles[ $b_role ]; 1343 } else { 1344 $avail_roles[ $b_role ] = 1; 1345 } 1346 } 1347 } 1348 1349 $result['total_users'] = count( $users_of_blog ); 1350 $result['avail_roles'] =& $avail_roles; 1351 } 1352 1353 return $result; 1354 } 1355 1356 /** 1357 * Returns the number of active users in your installation. 1358 * 1359 * Note that on a large site the count may be cached and only updated twice daily. 1360 * 1361 * @since MU (3.0.0) 1362 * @since 4.8.0 The `$network_id` parameter has been added. 1363 * @since 6.0.0 Moved to wp-includes/user.php. 1364 * 1365 * @param int|null $network_id ID of the network. Defaults to the current network. 1366 * @return int Number of active users on the network. 1367 */ 1368 function get_user_count( $network_id = null ) { 1369 if ( ! is_multisite() && null !== $network_id ) { 1370 _doing_it_wrong( 1371 __FUNCTION__, 1372 sprintf( 1373 /* translators: %s: $network_id */ 1374 __( 'Unable to pass %s if not using multisite.' ), 1375 '<code>$network_id</code>' 1376 ), 1377 '6.0.0' 1378 ); 1379 } 1380 1381 return (int) get_network_option( $network_id, 'user_count', -1 ); 1382 } 1383 1384 /** 1385 * Updates the total count of users on the site if live user counting is enabled. 1386 * 1387 * @since 6.0.0 1388 * 1389 * @param int|null $network_id ID of the network. Defaults to the current network. 1390 * @return bool Whether the update was successful. 1391 */ 1392 function wp_maybe_update_user_counts( $network_id = null ) { 1393 if ( ! is_multisite() && null !== $network_id ) { 1394 _doing_it_wrong( 1395 __FUNCTION__, 1396 sprintf( 1397 /* translators: %s: $network_id */ 1398 __( 'Unable to pass %s if not using multisite.' ), 1399 '<code>$network_id</code>' 1400 ), 1401 '6.0.0' 1402 ); 1403 } 1404 1405 $is_small_network = ! wp_is_large_user_count( $network_id ); 1406 /** This filter is documented in wp-includes/ms-functions.php */ 1407 if ( ! apply_filters( 'enable_live_network_counts', $is_small_network, 'users' ) ) { 1408 return false; 1409 } 1410 1411 return wp_update_user_counts( $network_id ); 1412 } 1413 1414 /** 1415 * Updates the total count of users on the site. 1416 * 1417 * @global wpdb $wpdb WordPress database abstraction object. 1418 * @since 6.0.0 1419 * 1420 * @param int|null $network_id ID of the network. Defaults to the current network. 1421 * @return bool Whether the update was successful. 1422 */ 1423 function wp_update_user_counts( $network_id = null ) { 1424 global $wpdb; 1425 1426 if ( ! is_multisite() && null !== $network_id ) { 1427 _doing_it_wrong( 1428 __FUNCTION__, 1429 sprintf( 1430 /* translators: %s: $network_id */ 1431 __( 'Unable to pass %s if not using multisite.' ), 1432 '<code>$network_id</code>' 1433 ), 1434 '6.0.0' 1435 ); 1436 } 1437 1438 $query = "SELECT COUNT(ID) as c FROM $wpdb->users"; 1439 if ( is_multisite() ) { 1440 $query .= " WHERE spam = '0' AND deleted = '0'"; 1441 } 1442 1443 $count = $wpdb->get_var( $query ); 1444 1445 return update_network_option( $network_id, 'user_count', $count ); 1446 } 1447 1448 /** 1449 * Schedules a recurring recalculation of the total count of users. 1450 * 1451 * @since 6.0.0 1452 */ 1453 function wp_schedule_update_user_counts() { 1454 if ( ! is_main_site() ) { 1455 return; 1456 } 1457 1458 if ( ! wp_next_scheduled( 'wp_update_user_counts' ) && ! wp_installing() ) { 1459 wp_schedule_event( time(), 'twicedaily', 'wp_update_user_counts' ); 1460 } 1461 } 1462 1463 /** 1464 * Determines whether the site has a large number of users. 1465 * 1466 * The default criteria for a large site is more than 10,000 users. 1467 * 1468 * @since 6.0.0 1469 * 1470 * @param int|null $network_id ID of the network. Defaults to the current network. 1471 * @return bool Whether the site has a large number of users. 1472 */ 1473 function wp_is_large_user_count( $network_id = null ) { 1474 if ( ! is_multisite() && null !== $network_id ) { 1475 _doing_it_wrong( 1476 __FUNCTION__, 1477 sprintf( 1478 /* translators: %s: $network_id */ 1479 __( 'Unable to pass %s if not using multisite.' ), 1480 '<code>$network_id</code>' 1481 ), 1482 '6.0.0' 1483 ); 1484 } 1485 1486 $count = get_user_count( $network_id ); 1487 1488 /** 1489 * Filters whether the site is considered large, based on its number of users. 1490 * 1491 * @since 6.0.0 1492 * 1493 * @param bool $is_large_user_count Whether the site has a large number of users. 1494 * @param int $count The total number of users. 1495 * @param int|null $network_id ID of the network. `null` represents the current network. 1496 */ 1497 return apply_filters( 'wp_is_large_user_count', $count > 10000, $count, $network_id ); 1498 } 1499 1500 // 1501 // Private helper functions. 1502 // 1503 1504 /** 1505 * Sets up global user vars. 1506 * 1507 * Used by wp_set_current_user() for back compat. Might be deprecated in the future. 1508 * 1509 * @since 2.0.4 1510 * 1511 * @global string $user_login The user username for logging in 1512 * @global WP_User $userdata User data. 1513 * @global int $user_level The level of the user 1514 * @global int $user_ID The ID of the user 1515 * @global string $user_email The email address of the user 1516 * @global string $user_url The url in the user's profile 1517 * @global string $user_identity The display name of the user 1518 * 1519 * @param int $for_user_id Optional. User ID to set up global data. Default 0. 1520 */ 1521 function setup_userdata( $for_user_id = 0 ) { 1522 global $user_login, $userdata, $user_level, $user_ID, $user_email, $user_url, $user_identity; 1523 1524 if ( ! $for_user_id ) { 1525 $for_user_id = get_current_user_id(); 1526 } 1527 $user = get_userdata( $for_user_id ); 1528 1529 if ( ! $user ) { 1530 $user_ID = 0; 1531 $user_level = 0; 1532 $userdata = null; 1533 $user_login = ''; 1534 $user_email = ''; 1535 $user_url = ''; 1536 $user_identity = ''; 1537 return; 1538 } 1539 1540 $user_ID = (int) $user->ID; 1541 $user_level = (int) $user->user_level; 1542 $userdata = $user; 1543 $user_login = $user->user_login; 1544 $user_email = $user->user_email; 1545 $user_url = $user->user_url; 1546 $user_identity = $user->display_name; 1547 } 1548 1549 /** 1550 * Creates dropdown HTML content of users. 1551 * 1552 * The content can either be displayed, which it is by default or retrieved by 1553 * setting the 'echo' argument. The 'include' and 'exclude' arguments do not 1554 * need to be used; all users will be displayed in that case. Only one can be 1555 * used, either 'include' or 'exclude', but not both. 1556 * 1557 * The available arguments are as follows: 1558 * 1559 * @since 2.3.0 1560 * @since 4.5.0 Added the 'display_name_with_login' value for 'show'. 1561 * @since 4.7.0 Added the 'role', 'role__in', and 'role__not_in' parameters. 1562 * @since 5.9.0 Added the 'capability', 'capability__in', and 'capability__not_in' parameters. 1563 * Deprecated the 'who' parameter. 1564 * 1565 * @param array|string $args { 1566 * Optional. Array or string of arguments to generate a drop-down of users. 1567 * See WP_User_Query::prepare_query() for additional available arguments. 1568 * 1569 * @type string $show_option_all Text to show as the drop-down default (all). 1570 * Default empty. 1571 * @type string $show_option_none Text to show as the drop-down default when no 1572 * users were found. Default empty. 1573 * @type int|string $option_none_value Value to use for `$show_option_none` when no users 1574 * were found. Default -1. 1575 * @type string $hide_if_only_one_author Whether to skip generating the drop-down 1576 * if only one user was found. Default empty. 1577 * @type string $orderby Field to order found users by. Accepts user fields. 1578 * Default 'display_name'. 1579 * @type string $order Whether to order users in ascending or descending 1580 * order. Accepts 'ASC' (ascending) or 'DESC' (descending). 1581 * Default 'ASC'. 1582 * @type int[]|string $include Array or comma-separated list of user IDs to include. 1583 * Default empty. 1584 * @type int[]|string $exclude Array or comma-separated list of user IDs to exclude. 1585 * Default empty. 1586 * @type bool|int $multi Whether to skip the ID attribute on the 'select' element. 1587 * Accepts 1|true or 0|false. Default 0|false. 1588 * @type string $show User data to display. If the selected item is empty 1589 * then the 'user_login' will be displayed in parentheses. 1590 * Accepts any user field, or 'display_name_with_login' to show 1591 * the display name with user_login in parentheses. 1592 * Default 'display_name'. 1593 * @type int|bool $echo Whether to echo or return the drop-down. Accepts 1|true (echo) 1594 * or 0|false (return). Default 1|true. 1595 * @type int $selected Which user ID should be selected. Default 0. 1596 * @type bool $include_selected Whether to always include the selected user ID in the drop- 1597 * down. Default false. 1598 * @type string $name Name attribute of select element. Default 'user'. 1599 * @type string $id ID attribute of the select element. Default is the value of `$name`. 1600 * @type string $class Class attribute of the select element. Default empty. 1601 * @type int $blog_id ID of blog (Multisite only). Default is ID of the current blog. 1602 * @type string $who Deprecated, use `$capability` instead. 1603 * Which type of users to query. Accepts only an empty string or 1604 * 'authors'. Default empty (all users). 1605 * @type string|string[] $role An array or a comma-separated list of role names that users 1606 * must match to be included in results. Note that this is 1607 * an inclusive list: users must match *each* role. Default empty. 1608 * @type string[] $role__in An array of role names. Matched users must have at least one 1609 * of these roles. Default empty array. 1610 * @type string[] $role__not_in An array of role names to exclude. Users matching one or more 1611 * of these roles will not be included in results. Default empty array. 1612 * @type string|string[] $capability An array or a comma-separated list of capability names that users 1613 * must match to be included in results. Note that this is 1614 * an inclusive list: users must match *each* capability. 1615 * Does NOT work for capabilities not in the database or filtered 1616 * via {@see 'map_meta_cap'}. Default empty. 1617 * @type string[] $capability__in An array of capability names. Matched users must have at least one 1618 * of these capabilities. 1619 * Does NOT work for capabilities not in the database or filtered 1620 * via {@see 'map_meta_cap'}. Default empty array. 1621 * @type string[] $capability__not_in An array of capability names to exclude. Users matching one or more 1622 * of these capabilities will not be included in results. 1623 * Does NOT work for capabilities not in the database or filtered 1624 * via {@see 'map_meta_cap'}. Default empty array. 1625 * } 1626 * @return string HTML dropdown list of users. 1627 */ 1628 function wp_dropdown_users( $args = '' ) { 1629 $defaults = array( 1630 'show_option_all' => '', 1631 'show_option_none' => '', 1632 'hide_if_only_one_author' => '', 1633 'orderby' => 'display_name', 1634 'order' => 'ASC', 1635 'include' => '', 1636 'exclude' => '', 1637 'multi' => 0, 1638 'show' => 'display_name', 1639 'echo' => 1, 1640 'selected' => 0, 1641 'name' => 'user', 1642 'class' => '', 1643 'id' => '', 1644 'blog_id' => get_current_blog_id(), 1645 'who' => '', 1646 'include_selected' => false, 1647 'option_none_value' => -1, 1648 'role' => '', 1649 'role__in' => array(), 1650 'role__not_in' => array(), 1651 'capability' => '', 1652 'capability__in' => array(), 1653 'capability__not_in' => array(), 1654 ); 1655 1656 $defaults['selected'] = is_author() ? get_query_var( 'author' ) : 0; 1657 1658 $parsed_args = wp_parse_args( $args, $defaults ); 1659 1660 $query_args = wp_array_slice_assoc( 1661 $parsed_args, 1662 array( 1663 'blog_id', 1664 'include', 1665 'exclude', 1666 'orderby', 1667 'order', 1668 'who', 1669 'role', 1670 'role__in', 1671 'role__not_in', 1672 'capability', 1673 'capability__in', 1674 'capability__not_in', 1675 ) 1676 ); 1677 1678 $fields = array( 'ID', 'user_login' ); 1679 1680 $show = ! empty( $parsed_args['show'] ) ? $parsed_args['show'] : 'display_name'; 1681 if ( 'display_name_with_login' === $show ) { 1682 $fields[] = 'display_name'; 1683 } else { 1684 $fields[] = $show; 1685 } 1686 1687 $query_args['fields'] = $fields; 1688 1689 $show_option_all = $parsed_args['show_option_all']; 1690 $show_option_none = $parsed_args['show_option_none']; 1691 $option_none_value = $parsed_args['option_none_value']; 1692 1693 /** 1694 * Filters the query arguments for the list of users in the dropdown. 1695 * 1696 * @since 4.4.0 1697 * 1698 * @param array $query_args The query arguments for get_users(). 1699 * @param array $parsed_args The arguments passed to wp_dropdown_users() combined with the defaults. 1700 */ 1701 $query_args = apply_filters( 'wp_dropdown_users_args', $query_args, $parsed_args ); 1702 1703 $users = get_users( $query_args ); 1704 1705 $output = ''; 1706 if ( ! empty( $users ) && ( empty( $parsed_args['hide_if_only_one_author'] ) || count( $users ) > 1 ) ) { 1707 $name = esc_attr( $parsed_args['name'] ); 1708 if ( $parsed_args['multi'] && ! $parsed_args['id'] ) { 1709 $id = ''; 1710 } else { 1711 $id = $parsed_args['id'] ? " id='" . esc_attr( $parsed_args['id'] ) . "'" : " id='$name'"; 1712 } 1713 $output = "<select name='{$name}'{$id} class='" . $parsed_args['class'] . "'>\n"; 1714 1715 if ( $show_option_all ) { 1716 $output .= "\t<option value='0'>$show_option_all</option>\n"; 1717 } 1718 1719 if ( $show_option_none ) { 1720 $_selected = selected( $option_none_value, $parsed_args['selected'], false ); 1721 $output .= "\t<option value='" . esc_attr( $option_none_value ) . "'$_selected>$show_option_none</option>\n"; 1722 } 1723 1724 if ( $parsed_args['include_selected'] && ( $parsed_args['selected'] > 0 ) ) { 1725 $found_selected = false; 1726 $parsed_args['selected'] = (int) $parsed_args['selected']; 1727 1728 foreach ( (array) $users as $user ) { 1729 $user->ID = (int) $user->ID; 1730 if ( $user->ID === $parsed_args['selected'] ) { 1731 $found_selected = true; 1732 } 1733 } 1734 1735 if ( ! $found_selected ) { 1736 $selected_user = get_userdata( $parsed_args['selected'] ); 1737 if ( $selected_user ) { 1738 $users[] = $selected_user; 1739 } 1740 } 1741 } 1742 1743 foreach ( (array) $users as $user ) { 1744 if ( 'display_name_with_login' === $show ) { 1745 /* translators: 1: User's display name, 2: User login. */ 1746 $display = sprintf( _x( '%1$s (%2$s)', 'user dropdown' ), $user->display_name, $user->user_login ); 1747 } elseif ( ! empty( $user->$show ) ) { 1748 $display = $user->$show; 1749 } else { 1750 $display = '(' . $user->user_login . ')'; 1751 } 1752 1753 $_selected = selected( $user->ID, $parsed_args['selected'], false ); 1754 $output .= "\t<option value='$user->ID'$_selected>" . esc_html( $display ) . "</option>\n"; 1755 } 1756 1757 $output .= '</select>'; 1758 } 1759 1760 /** 1761 * Filters the wp_dropdown_users() HTML output. 1762 * 1763 * @since 2.3.0 1764 * 1765 * @param string $output HTML output generated by wp_dropdown_users(). 1766 */ 1767 $html = apply_filters( 'wp_dropdown_users', $output ); 1768 1769 if ( $parsed_args['echo'] ) { 1770 echo $html; 1771 } 1772 return $html; 1773 } 1774 1775 /** 1776 * Sanitizes user field based on context. 1777 * 1778 * Possible context values are: 'raw', 'edit', 'db', 'display', 'attribute' and 'js'. The 1779 * 'display' context is used by default. 'attribute' and 'js' contexts are treated like 'display' 1780 * when calling filters. 1781 * 1782 * @since 2.3.0 1783 * 1784 * @param string $field The user Object field name. 1785 * @param mixed $value The user Object value. 1786 * @param int $user_id User ID. 1787 * @param string $context How to sanitize user fields. Looks for 'raw', 'edit', 'db', 'display', 1788 * 'attribute' and 'js'. 1789 * @return mixed Sanitized value. 1790 */ 1791 function sanitize_user_field( $field, $value, $user_id, $context ) { 1792 $int_fields = array( 'ID' ); 1793 if ( in_array( $field, $int_fields, true ) ) { 1794 $value = (int) $value; 1795 } 1796 1797 if ( 'raw' === $context ) { 1798 return $value; 1799 } 1800 1801 if ( ! is_string( $value ) && ! is_numeric( $value ) ) { 1802 return $value; 1803 } 1804 1805 $prefixed = str_contains( $field, 'user_' ); 1806 1807 if ( 'edit' === $context ) { 1808 if ( $prefixed ) { 1809 1810 /** This filter is documented in wp-includes/post.php */ 1811 $value = apply_filters( "edit_{$field}", $value, $user_id ); 1812 } else { 1813 1814 /** 1815 * Filters a user field value in the 'edit' context. 1816 * 1817 * The dynamic portion of the hook name, `$field`, refers to the prefixed user 1818 * field being filtered, such as 'user_login', 'user_email', 'first_name', etc. 1819 * 1820 * @since 2.9.0 1821 * 1822 * @param mixed $value Value of the prefixed user field. 1823 * @param int $user_id User ID. 1824 */ 1825 $value = apply_filters( "edit_user_{$field}", $value, $user_id ); 1826 } 1827 1828 if ( 'description' === $field ) { 1829 $value = esc_html( $value ); // textarea_escaped? 1830 } else { 1831 $value = esc_attr( $value ); 1832 } 1833 } elseif ( 'db' === $context ) { 1834 if ( $prefixed ) { 1835 /** This filter is documented in wp-includes/post.php */ 1836 $value = apply_filters( "pre_{$field}", $value ); 1837 } else { 1838 1839 /** 1840 * Filters the value of a user field in the 'db' context. 1841 * 1842 * The dynamic portion of the hook name, `$field`, refers to the prefixed user 1843 * field being filtered, such as 'user_login', 'user_email', 'first_name', etc. 1844 * 1845 * @since 2.9.0 1846 * 1847 * @param mixed $value Value of the prefixed user field. 1848 */ 1849 $value = apply_filters( "pre_user_{$field}", $value ); 1850 } 1851 } else { 1852 // Use display filters by default. 1853 if ( $prefixed ) { 1854 1855 /** This filter is documented in wp-includes/post.php */ 1856 $value = apply_filters( "{$field}", $value, $user_id, $context ); 1857 } else { 1858 1859 /** 1860 * Filters the value of a user field in a standard context. 1861 * 1862 * The dynamic portion of the hook name, `$field`, refers to the prefixed user 1863 * field being filtered, such as 'user_login', 'user_email', 'first_name', etc. 1864 * 1865 * @since 2.9.0 1866 * 1867 * @param mixed $value The user object value to sanitize. 1868 * @param int $user_id User ID. 1869 * @param string $context The context to filter within. 1870 */ 1871 $value = apply_filters( "user_{$field}", $value, $user_id, $context ); 1872 } 1873 } 1874 1875 if ( 'user_url' === $field ) { 1876 $value = esc_url( $value ); 1877 } 1878 1879 if ( 'attribute' === $context ) { 1880 $value = esc_attr( $value ); 1881 } elseif ( 'js' === $context ) { 1882 $value = esc_js( $value ); 1883 } 1884 1885 // Restore the type for integer fields after esc_attr(). 1886 if ( in_array( $field, $int_fields, true ) ) { 1887 $value = (int) $value; 1888 } 1889 1890 return $value; 1891 } 1892 1893 /** 1894 * Updates all user caches. 1895 * 1896 * @since 3.0.0 1897 * 1898 * @param object|WP_User $user User object or database row to be cached 1899 * @return void|false Void on success, false on failure. 1900 */ 1901 function update_user_caches( $user ) { 1902 if ( $user instanceof WP_User ) { 1903 if ( ! $user->exists() ) { 1904 return false; 1905 } 1906 1907 $user = $user->data; 1908 } 1909 1910 wp_cache_add( $user->ID, $user, 'users' ); 1911 wp_cache_add( $user->user_login, $user->ID, 'userlogins' ); 1912 wp_cache_add( $user->user_nicename, $user->ID, 'userslugs' ); 1913 1914 if ( ! empty( $user->user_email ) ) { 1915 wp_cache_add( $user->user_email, $user->ID, 'useremail' ); 1916 } 1917 } 1918 1919 /** 1920 * Cleans all user caches. 1921 * 1922 * @since 3.0.0 1923 * @since 4.4.0 'clean_user_cache' action was added. 1924 * @since 6.2.0 User metadata caches are now cleared. 1925 * 1926 * @param WP_User|int $user User object or ID to be cleaned from the cache 1927 */ 1928 function clean_user_cache( $user ) { 1929 if ( is_numeric( $user ) ) { 1930 $user = new WP_User( $user ); 1931 } 1932 1933 if ( ! $user->exists() ) { 1934 return; 1935 } 1936 1937 wp_cache_delete( $user->ID, 'users' ); 1938 wp_cache_delete( $user->user_login, 'userlogins' ); 1939 wp_cache_delete( $user->user_nicename, 'userslugs' ); 1940 1941 if ( ! empty( $user->user_email ) ) { 1942 wp_cache_delete( $user->user_email, 'useremail' ); 1943 } 1944 1945 wp_cache_delete( $user->ID, 'user_meta' ); 1946 wp_cache_set_users_last_changed(); 1947 1948 /** 1949 * Fires immediately after the given user's cache is cleaned. 1950 * 1951 * @since 4.4.0 1952 * 1953 * @param int $user_id User ID. 1954 * @param WP_User $user User object. 1955 */ 1956 do_action( 'clean_user_cache', $user->ID, $user ); 1957 } 1958 1959 /** 1960 * Determines whether the given username exists. 1961 * 1962 * For more information on this and similar theme functions, check out 1963 * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ 1964 * Conditional Tags} article in the Theme Developer Handbook. 1965 * 1966 * @since 2.0.0 1967 * 1968 * @param string $username The username to check for existence. 1969 * @return int|false The user ID on success, false on failure. 1970 */ 1971 function username_exists( $username ) { 1972 $user = get_user_by( 'login', $username ); 1973 if ( $user ) { 1974 $user_id = $user->ID; 1975 } else { 1976 $user_id = false; 1977 } 1978 1979 /** 1980 * Filters whether the given username exists. 1981 * 1982 * @since 4.9.0 1983 * 1984 * @param int|false $user_id The user ID associated with the username, 1985 * or false if the username does not exist. 1986 * @param string $username The username to check for existence. 1987 */ 1988 return apply_filters( 'username_exists', $user_id, $username ); 1989 } 1990 1991 /** 1992 * Determines whether the given email exists. 1993 * 1994 * For more information on this and similar theme functions, check out 1995 * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ 1996 * Conditional Tags} article in the Theme Developer Handbook. 1997 * 1998 * @since 2.1.0 1999 * 2000 * @param string $email The email to check for existence. 2001 * @return int|false The user ID on success, false on failure. 2002 */ 2003 function email_exists( $email ) { 2004 $user = get_user_by( 'email', $email ); 2005 if ( $user ) { 2006 $user_id = $user->ID; 2007 } else { 2008 $user_id = false; 2009 } 2010 2011 /** 2012 * Filters whether the given email exists. 2013 * 2014 * @since 5.6.0 2015 * 2016 * @param int|false $user_id The user ID associated with the email, 2017 * or false if the email does not exist. 2018 * @param string $email The email to check for existence. 2019 */ 2020 return apply_filters( 'email_exists', $user_id, $email ); 2021 } 2022 2023 /** 2024 * Checks whether a username is valid. 2025 * 2026 * @since 2.0.1 2027 * @since 4.4.0 Empty sanitized usernames are now considered invalid. 2028 * 2029 * @param string $username Username. 2030 * @return bool Whether username given is valid. 2031 */ 2032 function validate_username( $username ) { 2033 $sanitized = sanitize_user( $username, true ); 2034 $valid = ( $sanitized === $username && ! empty( $sanitized ) ); 2035 2036 /** 2037 * Filters whether the provided username is valid. 2038 * 2039 * @since 2.0.1 2040 * 2041 * @param bool $valid Whether given username is valid. 2042 * @param string $username Username to check. 2043 */ 2044 return apply_filters( 'validate_username', $valid, $username ); 2045 } 2046 2047 /** 2048 * Inserts a user into the database. 2049 * 2050 * Most of the `$userdata` array fields have filters associated with the values. Exceptions are 2051 * 'ID', 'rich_editing', 'syntax_highlighting', 'comment_shortcuts', 'admin_color', 'use_ssl', 2052 * 'user_registered', 'user_activation_key', 'spam', and 'role'. The filters have the prefix 2053 * 'pre_user_' followed by the field name. An example using 'description' would have the filter 2054 * called 'pre_user_description' that can be hooked into. 2055 * 2056 * @since 2.0.0 2057 * @since 3.6.0 The `aim`, `jabber`, and `yim` fields were removed as default user contact 2058 * methods for new installations. See wp_get_user_contact_methods(). 2059 * @since 4.7.0 The `locale` field can be passed to `$userdata`. 2060 * @since 5.3.0 The `user_activation_key` field can be passed to `$userdata`. 2061 * @since 5.3.0 The `spam` field can be passed to `$userdata` (Multisite only). 2062 * @since 5.9.0 The `meta_input` field can be passed to `$userdata` to allow addition of user meta data. 2063 * 2064 * @global wpdb $wpdb WordPress database abstraction object. 2065 * 2066 * @param array|object|WP_User $userdata { 2067 * An array, object, or WP_User object of user data arguments. 2068 * 2069 * @type int $ID User ID. If supplied, the user will be updated. 2070 * @type string $user_pass The plain-text user password for new users. 2071 * Hashed password for existing users. 2072 * @type string $user_login The user's login username. 2073 * @type string $user_nicename The URL-friendly user name. 2074 * @type string $user_url The user URL. 2075 * @type string $user_email The user email address. 2076 * @type string $display_name The user's display name. 2077 * Default is the user's username. 2078 * @type string $nickname The user's nickname. 2079 * Default is the user's username. 2080 * @type string $first_name The user's first name. For new users, will be used 2081 * to build the first part of the user's display name 2082 * if `$display_name` is not specified. 2083 * @type string $last_name The user's last name. For new users, will be used 2084 * to build the second part of the user's display name 2085 * if `$display_name` is not specified. 2086 * @type string $description The user's biographical description. 2087 * @type string $rich_editing Whether to enable the rich-editor for the user. 2088 * Accepts 'true' or 'false' as a string literal, 2089 * not boolean. Default 'true'. 2090 * @type string $syntax_highlighting Whether to enable the rich code editor for the user. 2091 * Accepts 'true' or 'false' as a string literal, 2092 * not boolean. Default 'true'. 2093 * @type string $comment_shortcuts Whether to enable comment moderation keyboard 2094 * shortcuts for the user. Accepts 'true' or 'false' 2095 * as a string literal, not boolean. Default 'false'. 2096 * @type string $admin_color Admin color scheme for the user. Default 'fresh'. 2097 * @type bool $use_ssl Whether the user should always access the admin over 2098 * https. Default false. 2099 * @type string $user_registered Date the user registered in UTC. Format is 'Y-m-d H:i:s'. 2100 * @type string $user_activation_key Password reset key. Default empty. 2101 * @type bool $spam Multisite only. Whether the user is marked as spam. 2102 * Default false. 2103 * @type string $show_admin_bar_front Whether to display the Admin Bar for the user 2104 * on the site's front end. Accepts 'true' or 'false' 2105 * as a string literal, not boolean. Default 'true'. 2106 * @type string $role User's role. 2107 * @type string $locale User's locale. Default empty. 2108 * @type array $meta_input Array of custom user meta values keyed by meta key. 2109 * Default empty. 2110 * } 2111 * @return int|WP_Error The newly created user's ID or a WP_Error object if the user could not 2112 * be created. 2113 */ 2114 function wp_insert_user( $userdata ) { 2115 global $wpdb; 2116 2117 if ( $userdata instanceof stdClass ) { 2118 $userdata = get_object_vars( $userdata ); 2119 } elseif ( $userdata instanceof WP_User ) { 2120 $userdata = $userdata->to_array(); 2121 } 2122 2123 // Are we updating or creating? 2124 if ( ! empty( $userdata['ID'] ) ) { 2125 $user_id = (int) $userdata['ID']; 2126 $update = true; 2127 $old_user_data = get_userdata( $user_id ); 2128 2129 if ( ! $old_user_data ) { 2130 return new WP_Error( 'invalid_user_id', __( 'Invalid user ID.' ) ); 2131 } 2132 2133 // Slash current user email to compare it later with slashed new user email. 2134 $old_user_data->user_email = wp_slash( $old_user_data->user_email ); 2135 2136 // Hashed in wp_update_user(), plaintext if called directly. 2137 $user_pass = ! empty( $userdata['user_pass'] ) ? $userdata['user_pass'] : $old_user_data->user_pass; 2138 } else { 2139 $update = false; 2140 // Hash the password. 2141 $user_pass = wp_hash_password( $userdata['user_pass'] ); 2142 } 2143 2144 $sanitized_user_login = sanitize_user( $userdata['user_login'], true ); 2145 2146 /** 2147 * Filters a username after it has been sanitized. 2148 * 2149 * This filter is called before the user is created or updated. 2150 * 2151 * @since 2.0.3 2152 * 2153 * @param string $sanitized_user_login Username after it has been sanitized. 2154 */ 2155 $pre_user_login = apply_filters( 'pre_user_login', $sanitized_user_login ); 2156 2157 // Remove any non-printable chars from the login string to see if we have ended up with an empty username. 2158 $user_login = trim( $pre_user_login ); 2159 2160 // user_login must be between 0 and 60 characters. 2161 if ( empty( $user_login ) ) { 2162 return new WP_Error( 'empty_user_login', __( 'Cannot create a user with an empty login name.' ) ); 2163 } elseif ( mb_strlen( $user_login ) > 60 ) { 2164 return new WP_Error( 'user_login_too_long', __( 'Username may not be longer than 60 characters.' ) ); 2165 } 2166 2167 if ( ! $update && username_exists( $user_login ) ) { 2168 return new WP_Error( 'existing_user_login', __( 'Sorry, that username already exists!' ) ); 2169 } 2170 2171 /** 2172 * Filters the list of disallowed usernames. 2173 * 2174 * @since 4.4.0 2175 * 2176 * @param array $usernames Array of disallowed usernames. 2177 */ 2178 $illegal_logins = (array) apply_filters( 'illegal_user_logins', array() ); 2179 2180 if ( in_array( strtolower( $user_login ), array_map( 'strtolower', $illegal_logins ), true ) ) { 2181 return new WP_Error( 'invalid_username', __( 'Sorry, that username is not allowed.' ) ); 2182 } 2183 2184 /* 2185 * If a nicename is provided, remove unsafe user characters before using it. 2186 * Otherwise build a nicename from the user_login. 2187 */ 2188 if ( ! empty( $userdata['user_nicename'] ) ) { 2189 $user_nicename = sanitize_user( $userdata['user_nicename'], true ); 2190 } else { 2191 $user_nicename = mb_substr( $user_login, 0, 50 ); 2192 } 2193 2194 $user_nicename = sanitize_title( $user_nicename ); 2195 2196 /** 2197 * Filters a user's nicename before the user is created or updated. 2198 * 2199 * @since 2.0.3 2200 * 2201 * @param string $user_nicename The user's nicename. 2202 */ 2203 $user_nicename = apply_filters( 'pre_user_nicename', $user_nicename ); 2204 2205 if ( mb_strlen( $user_nicename ) > 50 ) { 2206 return new WP_Error( 'user_nicename_too_long', __( 'Nicename may not be longer than 50 characters.' ) ); 2207 } 2208 2209 $user_nicename_check = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM $wpdb->users WHERE user_nicename = %s AND user_login != %s LIMIT 1", $user_nicename, $user_login ) ); 2210 2211 if ( $user_nicename_check ) { 2212 $suffix = 2; 2213 while ( $user_nicename_check ) { 2214 // user_nicename allows 50 chars. Subtract one for a hyphen, plus the length of the suffix. 2215 $base_length = 49 - mb_strlen( $suffix ); 2216 $alt_user_nicename = mb_substr( $user_nicename, 0, $base_length ) . "-$suffix"; 2217 $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 ) ); 2218 ++$suffix; 2219 } 2220 $user_nicename = $alt_user_nicename; 2221 } 2222 2223 $raw_user_email = empty( $userdata['user_email'] ) ? '' : $userdata['user_email']; 2224 2225 /** 2226 * Filters a user's email before the user is created or updated. 2227 * 2228 * @since 2.0.3 2229 * 2230 * @param string $raw_user_email The user's email. 2231 */ 2232 $user_email = apply_filters( 'pre_user_email', $raw_user_email ); 2233 2234 /* 2235 * If there is no update, just check for `email_exists`. If there is an update, 2236 * check if current email and new email are the same, and check `email_exists` 2237 * accordingly. 2238 */ 2239 if ( ( ! $update || ( ! empty( $old_user_data ) && 0 !== strcasecmp( $user_email, $old_user_data->user_email ) ) ) 2240 && ! defined( 'WP_IMPORTING' ) 2241 && email_exists( $user_email ) 2242 ) { 2243 return new WP_Error( 'existing_user_email', __( 'Sorry, that email address is already used!' ) ); 2244 } 2245 2246 $raw_user_url = empty( $userdata['user_url'] ) ? '' : $userdata['user_url']; 2247 2248 /** 2249 * Filters a user's URL before the user is created or updated. 2250 * 2251 * @since 2.0.3 2252 * 2253 * @param string $raw_user_url The user's URL. 2254 */ 2255 $user_url = apply_filters( 'pre_user_url', $raw_user_url ); 2256 2257 if ( mb_strlen( $user_url ) > 100 ) { 2258 return new WP_Error( 'user_url_too_long', __( 'User URL may not be longer than 100 characters.' ) ); 2259 } 2260 2261 $user_registered = empty( $userdata['user_registered'] ) ? gmdate( 'Y-m-d H:i:s' ) : $userdata['user_registered']; 2262 2263 $user_activation_key = empty( $userdata['user_activation_key'] ) ? '' : $userdata['user_activation_key']; 2264 2265 if ( ! empty( $userdata['spam'] ) && ! is_multisite() ) { 2266 return new WP_Error( 'no_spam', __( 'Sorry, marking a user as spam is only supported on Multisite.' ) ); 2267 } 2268 2269 $spam = empty( $userdata['spam'] ) ? 0 : (bool) $userdata['spam']; 2270 2271 // Store values to save in user meta. 2272 $meta = array(); 2273 2274 $nickname = empty( $userdata['nickname'] ) ? $user_login : $userdata['nickname']; 2275 2276 /** 2277 * Filters a user's nickname before the user is created or updated. 2278 * 2279 * @since 2.0.3 2280 * 2281 * @param string $nickname The user's nickname. 2282 */ 2283 $meta['nickname'] = apply_filters( 'pre_user_nickname', $nickname ); 2284 2285 $first_name = empty( $userdata['first_name'] ) ? '' : $userdata['first_name']; 2286 2287 /** 2288 * Filters a user's first name before the user is created or updated. 2289 * 2290 * @since 2.0.3 2291 * 2292 * @param string $first_name The user's first name. 2293 */ 2294 $meta['first_name'] = apply_filters( 'pre_user_first_name', $first_name ); 2295 2296 $last_name = empty( $userdata['last_name'] ) ? '' : $userdata['last_name']; 2297 2298 /** 2299 * Filters a user's last name before the user is created or updated. 2300 * 2301 * @since 2.0.3 2302 * 2303 * @param string $last_name The user's last name. 2304 */ 2305 $meta['last_name'] = apply_filters( 'pre_user_last_name', $last_name ); 2306 2307 if ( empty( $userdata['display_name'] ) ) { 2308 if ( $update ) { 2309 $display_name = $user_login; 2310 } elseif ( $meta['first_name'] && $meta['last_name'] ) { 2311 $display_name = sprintf( 2312 /* translators: 1: User's first name, 2: Last name. */ 2313 _x( '%1$s %2$s', 'Display name based on first name and last name' ), 2314 $meta['first_name'], 2315 $meta['last_name'] 2316 ); 2317 } elseif ( $meta['first_name'] ) { 2318 $display_name = $meta['first_name']; 2319 } elseif ( $meta['last_name'] ) { 2320 $display_name = $meta['last_name']; 2321 } else { 2322 $display_name = $user_login; 2323 } 2324 } else { 2325 $display_name = $userdata['display_name']; 2326 } 2327 2328 /** 2329 * Filters a user's display name before the user is created or updated. 2330 * 2331 * @since 2.0.3 2332 * 2333 * @param string $display_name The user's display name. 2334 */ 2335 $display_name = apply_filters( 'pre_user_display_name', $display_name ); 2336 2337 $description = empty( $userdata['description'] ) ? '' : $userdata['description']; 2338 2339 /** 2340 * Filters a user's description before the user is created or updated. 2341 * 2342 * @since 2.0.3 2343 * 2344 * @param string $description The user's description. 2345 */ 2346 $meta['description'] = apply_filters( 'pre_user_description', $description ); 2347 2348 $meta['rich_editing'] = empty( $userdata['rich_editing'] ) ? 'true' : $userdata['rich_editing']; 2349 2350 $meta['syntax_highlighting'] = empty( $userdata['syntax_highlighting'] ) ? 'true' : $userdata['syntax_highlighting']; 2351 2352 $meta['comment_shortcuts'] = empty( $userdata['comment_shortcuts'] ) || 'false' === $userdata['comment_shortcuts'] ? 'false' : 'true'; 2353 2354 $admin_color = empty( $userdata['admin_color'] ) ? 'fresh' : $userdata['admin_color']; 2355 $meta['admin_color'] = preg_replace( '|[^a-z0-9 _.\-@]|i', '', $admin_color ); 2356 2357 $meta['use_ssl'] = empty( $userdata['use_ssl'] ) ? 0 : (bool) $userdata['use_ssl']; 2358 2359 $meta['show_admin_bar_front'] = empty( $userdata['show_admin_bar_front'] ) ? 'true' : $userdata['show_admin_bar_front']; 2360 2361 $meta['locale'] = isset( $userdata['locale'] ) ? $userdata['locale'] : ''; 2362 2363 $compacted = compact( 'user_pass', 'user_nicename', 'user_email', 'user_url', 'user_registered', 'user_activation_key', 'display_name' ); 2364 $data = wp_unslash( $compacted ); 2365 2366 if ( ! $update ) { 2367 $data = $data + compact( 'user_login' ); 2368 } 2369 2370 if ( is_multisite() ) { 2371 $data = $data + compact( 'spam' ); 2372 } 2373 2374 /** 2375 * Filters user data before the record is created or updated. 2376 * 2377 * It only includes data in the users table, not any user metadata. 2378 * 2379 * @since 4.9.0 2380 * @since 5.8.0 The `$userdata` parameter was added. 2381 * 2382 * @param array $data { 2383 * Values and keys for the user. 2384 * 2385 * @type string $user_login The user's login. Only included if $update == false 2386 * @type string $user_pass The user's password. 2387 * @type string $user_email The user's email. 2388 * @type string $user_url The user's url. 2389 * @type string $user_nicename The user's nice name. Defaults to a URL-safe version of user's login. 2390 * @type string $display_name The user's display name. 2391 * @type string $user_registered MySQL timestamp describing the moment when the user registered. Defaults to 2392 * the current UTC timestamp. 2393 * } 2394 * @param bool $update Whether the user is being updated rather than created. 2395 * @param int|null $user_id ID of the user to be updated, or NULL if the user is being created. 2396 * @param array $userdata The raw array of data passed to wp_insert_user(). 2397 */ 2398 $data = apply_filters( 'wp_pre_insert_user_data', $data, $update, ( $update ? $user_id : null ), $userdata ); 2399 2400 if ( empty( $data ) || ! is_array( $data ) ) { 2401 return new WP_Error( 'empty_data', __( 'Not enough data to create this user.' ) ); 2402 } 2403 2404 if ( $update ) { 2405 if ( $user_email !== $old_user_data->user_email || $user_pass !== $old_user_data->user_pass ) { 2406 $data['user_activation_key'] = ''; 2407 } 2408 $wpdb->update( $wpdb->users, $data, array( 'ID' => $user_id ) ); 2409 } else { 2410 $wpdb->insert( $wpdb->users, $data ); 2411 $user_id = (int) $wpdb->insert_id; 2412 } 2413 2414 $user = new WP_User( $user_id ); 2415 2416 /** 2417 * Filters a user's meta values and keys immediately after the user is created or updated 2418 * and before any user meta is inserted or updated. 2419 * 2420 * Does not include contact methods. These are added using `wp_get_user_contact_methods( $user )`. 2421 * 2422 * For custom meta fields, see the {@see 'insert_custom_user_meta'} filter. 2423 * 2424 * @since 4.4.0 2425 * @since 5.8.0 The `$userdata` parameter was added. 2426 * 2427 * @param array $meta { 2428 * Default meta values and keys for the user. 2429 * 2430 * @type string $nickname The user's nickname. Default is the user's username. 2431 * @type string $first_name The user's first name. 2432 * @type string $last_name The user's last name. 2433 * @type string $description The user's description. 2434 * @type string $rich_editing Whether to enable the rich-editor for the user. Default 'true'. 2435 * @type string $syntax_highlighting Whether to enable the rich code editor for the user. Default 'true'. 2436 * @type string $comment_shortcuts Whether to enable keyboard shortcuts for the user. Default 'false'. 2437 * @type string $admin_color The color scheme for a user's admin screen. Default 'fresh'. 2438 * @type int|bool $use_ssl Whether to force SSL on the user's admin area. 0|false if SSL 2439 * is not forced. 2440 * @type string $show_admin_bar_front Whether to show the admin bar on the front end for the user. 2441 * Default 'true'. 2442 * @type string $locale User's locale. Default empty. 2443 * } 2444 * @param WP_User $user User object. 2445 * @param bool $update Whether the user is being updated rather than created. 2446 * @param array $userdata The raw array of data passed to wp_insert_user(). 2447 */ 2448 $meta = apply_filters( 'insert_user_meta', $meta, $user, $update, $userdata ); 2449 2450 $custom_meta = array(); 2451 if ( array_key_exists( 'meta_input', $userdata ) && is_array( $userdata['meta_input'] ) && ! empty( $userdata['meta_input'] ) ) { 2452 $custom_meta = $userdata['meta_input']; 2453 } 2454 2455 /** 2456 * Filters a user's custom meta values and keys immediately after the user is created or updated 2457 * and before any user meta is inserted or updated. 2458 * 2459 * For non-custom meta fields, see the {@see 'insert_user_meta'} filter. 2460 * 2461 * @since 5.9.0 2462 * 2463 * @param array $custom_meta Array of custom user meta values keyed by meta key. 2464 * @param WP_User $user User object. 2465 * @param bool $update Whether the user is being updated rather than created. 2466 * @param array $userdata The raw array of data passed to wp_insert_user(). 2467 */ 2468 $custom_meta = apply_filters( 'insert_custom_user_meta', $custom_meta, $user, $update, $userdata ); 2469 2470 $meta = array_merge( $meta, $custom_meta ); 2471 2472 if ( $update ) { 2473 // Update user meta. 2474 foreach ( $meta as $key => $value ) { 2475 update_user_meta( $user_id, $key, $value ); 2476 } 2477 } else { 2478 // Add user meta. 2479 foreach ( $meta as $key => $value ) { 2480 add_user_meta( $user_id, $key, $value ); 2481 } 2482 } 2483 2484 foreach ( wp_get_user_contact_methods( $user ) as $key => $value ) { 2485 if ( isset( $userdata[ $key ] ) ) { 2486 update_user_meta( $user_id, $key, $userdata[ $key ] ); 2487 } 2488 } 2489 2490 if ( isset( $userdata['role'] ) ) { 2491 $user->set_role( $userdata['role'] ); 2492 } elseif ( ! $update ) { 2493 $user->set_role( get_option( 'default_role' ) ); 2494 } 2495 2496 clean_user_cache( $user_id ); 2497 2498 if ( $update ) { 2499 /** 2500 * Fires immediately after an existing user is updated. 2501 * 2502 * @since 2.0.0 2503 * @since 5.8.0 The `$userdata` parameter was added. 2504 * 2505 * @param int $user_id User ID. 2506 * @param WP_User $old_user_data Object containing user's data prior to update. 2507 * @param array $userdata The raw array of data passed to wp_insert_user(). 2508 */ 2509 do_action( 'profile_update', $user_id, $old_user_data, $userdata ); 2510 2511 if ( isset( $userdata['spam'] ) && $userdata['spam'] !== $old_user_data->spam ) { 2512 if ( '1' === $userdata['spam'] ) { 2513 /** 2514 * Fires after the user is marked as a SPAM user. 2515 * 2516 * @since 3.0.0 2517 * 2518 * @param int $user_id ID of the user marked as SPAM. 2519 */ 2520 do_action( 'make_spam_user', $user_id ); 2521 } else { 2522 /** 2523 * Fires after the user is marked as a HAM user. Opposite of SPAM. 2524 * 2525 * @since 3.0.0 2526 * 2527 * @param int $user_id ID of the user marked as HAM. 2528 */ 2529 do_action( 'make_ham_user', $user_id ); 2530 } 2531 } 2532 } else { 2533 /** 2534 * Fires immediately after a new user is registered. 2535 * 2536 * @since 1.5.0 2537 * @since 5.8.0 The `$userdata` parameter was added. 2538 * 2539 * @param int $user_id User ID. 2540 * @param array $userdata The raw array of data passed to wp_insert_user(). 2541 */ 2542 do_action( 'user_register', $user_id, $userdata ); 2543 } 2544 2545 return $user_id; 2546 } 2547 2548 /** 2549 * Updates a user in the database. 2550 * 2551 * It is possible to update a user's password by specifying the 'user_pass' 2552 * value in the $userdata parameter array. 2553 * 2554 * If current user's password is being updated, then the cookies will be 2555 * cleared. 2556 * 2557 * @since 2.0.0 2558 * 2559 * @see wp_insert_user() For what fields can be set in $userdata. 2560 * 2561 * @param array|object|WP_User $userdata An array of user data or a user object of type stdClass or WP_User. 2562 * @return int|WP_Error The updated user's ID or a WP_Error object if the user could not be updated. 2563 */ 2564 function wp_update_user( $userdata ) { 2565 if ( $userdata instanceof stdClass ) { 2566 $userdata = get_object_vars( $userdata ); 2567 } elseif ( $userdata instanceof WP_User ) { 2568 $userdata = $userdata->to_array(); 2569 } 2570 2571 $userdata_raw = $userdata; 2572 2573 $user_id = isset( $userdata['ID'] ) ? (int) $userdata['ID'] : 0; 2574 if ( ! $user_id ) { 2575 return new WP_Error( 'invalid_user_id', __( 'Invalid user ID.' ) ); 2576 } 2577 2578 // First, get all of the original fields. 2579 $user_obj = get_userdata( $user_id ); 2580 if ( ! $user_obj ) { 2581 return new WP_Error( 'invalid_user_id', __( 'Invalid user ID.' ) ); 2582 } 2583 2584 $user = $user_obj->to_array(); 2585 2586 // Add additional custom fields. 2587 foreach ( _get_additional_user_keys( $user_obj ) as $key ) { 2588 $user[ $key ] = get_user_meta( $user_id, $key, true ); 2589 } 2590 2591 // Escape data pulled from DB. 2592 $user = add_magic_quotes( $user ); 2593 2594 if ( ! empty( $userdata['user_pass'] ) && $userdata['user_pass'] !== $user_obj->user_pass ) { 2595 // If password is changing, hash it now. 2596 $plaintext_pass = $userdata['user_pass']; 2597 $userdata['user_pass'] = wp_hash_password( $userdata['user_pass'] ); 2598 2599 /** 2600 * Filters whether to send the password change email. 2601 * 2602 * @since 4.3.0 2603 * 2604 * @see wp_insert_user() For `$user` and `$userdata` fields. 2605 * 2606 * @param bool $send Whether to send the email. 2607 * @param array $user The original user array. 2608 * @param array $userdata The updated user array. 2609 */ 2610 $send_password_change_email = apply_filters( 'send_password_change_email', true, $user, $userdata ); 2611 } 2612 2613 if ( isset( $userdata['user_email'] ) && $user['user_email'] !== $userdata['user_email'] ) { 2614 /** 2615 * Filters whether to send the email change email. 2616 * 2617 * @since 4.3.0 2618 * 2619 * @see wp_insert_user() For `$user` and `$userdata` fields. 2620 * 2621 * @param bool $send Whether to send the email. 2622 * @param array $user The original user array. 2623 * @param array $userdata The updated user array. 2624 */ 2625 $send_email_change_email = apply_filters( 'send_email_change_email', true, $user, $userdata ); 2626 } 2627 2628 clean_user_cache( $user_obj ); 2629 2630 // Merge old and new fields with new fields overwriting old ones. 2631 $userdata = array_merge( $user, $userdata ); 2632 $user_id = wp_insert_user( $userdata ); 2633 2634 if ( is_wp_error( $user_id ) ) { 2635 return $user_id; 2636 } 2637 2638 $blog_name = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ); 2639 2640 $switched_locale = false; 2641 if ( ! empty( $send_password_change_email ) || ! empty( $send_email_change_email ) ) { 2642 $switched_locale = switch_to_user_locale( $user_id ); 2643 } 2644 2645 if ( ! empty( $send_password_change_email ) ) { 2646 /* translators: Do not translate USERNAME, ADMIN_EMAIL, EMAIL, SITENAME, SITEURL: those are placeholders. */ 2647 $pass_change_text = __( 2648 'Hi ###USERNAME###, 2649 2650 This notice confirms that your password was changed on ###SITENAME###. 2651 2652 If you did not change your password, please contact the Site Administrator at 2653 ###ADMIN_EMAIL### 2654 2655 This email has been sent to ###EMAIL### 2656 2657 Regards, 2658 All at ###SITENAME### 2659 ###SITEURL###' 2660 ); 2661 2662 $pass_change_email = array( 2663 'to' => $user['user_email'], 2664 /* translators: Password change notification email subject. %s: Site title. */ 2665 'subject' => __( '[%s] Password Changed' ), 2666 'message' => $pass_change_text, 2667 'headers' => '', 2668 ); 2669 2670 /** 2671 * Filters the contents of the email sent when the user's password is changed. 2672 * 2673 * @since 4.3.0 2674 * 2675 * @param array $pass_change_email { 2676 * Used to build wp_mail(). 2677 * 2678 * @type string $to The intended recipients. Add emails in a comma separated string. 2679 * @type string $subject The subject of the email. 2680 * @type string $message The content of the email. 2681 * The following strings have a special meaning and will get replaced dynamically: 2682 * - ###USERNAME### The current user's username. 2683 * - ###ADMIN_EMAIL### The admin email in case this was unexpected. 2684 * - ###EMAIL### The user's email address. 2685 * - ###SITENAME### The name of the site. 2686 * - ###SITEURL### The URL to the site. 2687 * @type string $headers Headers. Add headers in a newline (\r\n) separated string. 2688 * } 2689 * @param array $user The original user array. 2690 * @param array $userdata The updated user array. 2691 */ 2692 $pass_change_email = apply_filters( 'password_change_email', $pass_change_email, $user, $userdata ); 2693 2694 $pass_change_email['message'] = str_replace( '###USERNAME###', $user['user_login'], $pass_change_email['message'] ); 2695 $pass_change_email['message'] = str_replace( '###ADMIN_EMAIL###', get_option( 'admin_email' ), $pass_change_email['message'] ); 2696 $pass_change_email['message'] = str_replace( '###EMAIL###', $user['user_email'], $pass_change_email['message'] ); 2697 $pass_change_email['message'] = str_replace( '###SITENAME###', $blog_name, $pass_change_email['message'] ); 2698 $pass_change_email['message'] = str_replace( '###SITEURL###', home_url(), $pass_change_email['message'] ); 2699 2700 wp_mail( $pass_change_email['to'], sprintf( $pass_change_email['subject'], $blog_name ), $pass_change_email['message'], $pass_change_email['headers'] ); 2701 } 2702 2703 if ( ! empty( $send_email_change_email ) ) { 2704 /* translators: Do not translate USERNAME, ADMIN_EMAIL, NEW_EMAIL, EMAIL, SITENAME, SITEURL: those are placeholders. */ 2705 $email_change_text = __( 2706 'Hi ###USERNAME###, 2707 2708 This notice confirms that your email address on ###SITENAME### was changed to ###NEW_EMAIL###. 2709 2710 If you did not change your email, please contact the Site Administrator at 2711 ###ADMIN_EMAIL### 2712 2713 This email has been sent to ###EMAIL### 2714 2715 Regards, 2716 All at ###SITENAME### 2717 ###SITEURL###' 2718 ); 2719 2720 $email_change_email = array( 2721 'to' => $user['user_email'], 2722 /* translators: Email change notification email subject. %s: Site title. */ 2723 'subject' => __( '[%s] Email Changed' ), 2724 'message' => $email_change_text, 2725 'headers' => '', 2726 ); 2727 2728 /** 2729 * Filters the contents of the email sent when the user's email is changed. 2730 * 2731 * @since 4.3.0 2732 * 2733 * @param array $email_change_email { 2734 * Used to build wp_mail(). 2735 * 2736 * @type string $to The intended recipients. 2737 * @type string $subject The subject of the email. 2738 * @type string $message The content of the email. 2739 * The following strings have a special meaning and will get replaced dynamically: 2740 * - ###USERNAME### The current user's username. 2741 * - ###ADMIN_EMAIL### The admin email in case this was unexpected. 2742 * - ###NEW_EMAIL### The new email address. 2743 * - ###EMAIL### The old email address. 2744 * - ###SITENAME### The name of the site. 2745 * - ###SITEURL### The URL to the site. 2746 * @type string $headers Headers. 2747 * } 2748 * @param array $user The original user array. 2749 * @param array $userdata The updated user array. 2750 */ 2751 $email_change_email = apply_filters( 'email_change_email', $email_change_email, $user, $userdata ); 2752 2753 $email_change_email['message'] = str_replace( '###USERNAME###', $user['user_login'], $email_change_email['message'] ); 2754 $email_change_email['message'] = str_replace( '###ADMIN_EMAIL###', get_option( 'admin_email' ), $email_change_email['message'] ); 2755 $email_change_email['message'] = str_replace( '###NEW_EMAIL###', $userdata['user_email'], $email_change_email['message'] ); 2756 $email_change_email['message'] = str_replace( '###EMAIL###', $user['user_email'], $email_change_email['message'] ); 2757 $email_change_email['message'] = str_replace( '###SITENAME###', $blog_name, $email_change_email['message'] ); 2758 $email_change_email['message'] = str_replace( '###SITEURL###', home_url(), $email_change_email['message'] ); 2759 2760 wp_mail( $email_change_email['to'], sprintf( $email_change_email['subject'], $blog_name ), $email_change_email['message'], $email_change_email['headers'] ); 2761 } 2762 2763 if ( $switched_locale ) { 2764 restore_previous_locale(); 2765 } 2766 2767 // Update the cookies if the password changed. 2768 $current_user = wp_get_current_user(); 2769 if ( $current_user->ID === $user_id ) { 2770 if ( isset( $plaintext_pass ) ) { 2771 wp_clear_auth_cookie(); 2772 2773 /* 2774 * Here we calculate the expiration length of the current auth cookie and compare it to the default expiration. 2775 * If it's greater than this, then we know the user checked 'Remember Me' when they logged in. 2776 */ 2777 $logged_in_cookie = wp_parse_auth_cookie( '', 'logged_in' ); 2778 /** This filter is documented in wp-includes/pluggable.php */ 2779 $default_cookie_life = apply_filters( 'auth_cookie_expiration', ( 2 * DAY_IN_SECONDS ), $user_id, false ); 2780 2781 $remember = false; 2782 2783 if ( false !== $logged_in_cookie && ( $logged_in_cookie['expiration'] - time() ) > $default_cookie_life ) { 2784 $remember = true; 2785 } 2786 2787 wp_set_auth_cookie( $user_id, $remember ); 2788 } 2789 } 2790 2791 /** 2792 * Fires after the user has been updated and emails have been sent. 2793 * 2794 * @since 6.3.0 2795 * 2796 * @param int $user_id The ID of the user that was just updated. 2797 * @param array $userdata The array of user data that was updated. 2798 * @param array $userdata_raw The unedited array of user data that was updated. 2799 */ 2800 do_action( 'wp_update_user', $user_id, $userdata, $userdata_raw ); 2801 2802 return $user_id; 2803 } 2804 2805 /** 2806 * Provides a simpler way of inserting a user into the database. 2807 * 2808 * Creates a new user with just the username, password, and email. For more 2809 * complex user creation use wp_insert_user() to specify more information. 2810 * 2811 * @since 2.0.0 2812 * 2813 * @see wp_insert_user() More complete way to create a new user. 2814 * 2815 * @param string $username The user's username. 2816 * @param string $password The user's password. 2817 * @param string $email Optional. The user's email. Default empty. 2818 * @return int|WP_Error The newly created user's ID or a WP_Error object if the user could not 2819 * be created. 2820 */ 2821 function wp_create_user( $username, $password, $email = '' ) { 2822 $user_login = wp_slash( $username ); 2823 $user_email = wp_slash( $email ); 2824 $user_pass = $password; 2825 2826 $userdata = compact( 'user_login', 'user_email', 'user_pass' ); 2827 return wp_insert_user( $userdata ); 2828 } 2829 2830 /** 2831 * Returns a list of meta keys to be (maybe) populated in wp_update_user(). 2832 * 2833 * The list of keys returned via this function are dependent on the presence 2834 * of those keys in the user meta data to be set. 2835 * 2836 * @since 3.3.0 2837 * @access private 2838 * 2839 * @param WP_User $user WP_User instance. 2840 * @return string[] List of user keys to be populated in wp_update_user(). 2841 */ 2842 function _get_additional_user_keys( $user ) { 2843 $keys = array( 'first_name', 'last_name', 'nickname', 'description', 'rich_editing', 'syntax_highlighting', 'comment_shortcuts', 'admin_color', 'use_ssl', 'show_admin_bar_front', 'locale' ); 2844 return array_merge( $keys, array_keys( wp_get_user_contact_methods( $user ) ) ); 2845 } 2846 2847 /** 2848 * Sets up the user contact methods. 2849 * 2850 * Default contact methods were removed in 3.6. A filter dictates contact methods. 2851 * 2852 * @since 3.7.0 2853 * 2854 * @param WP_User|null $user Optional. WP_User object. 2855 * @return string[] Array of contact method labels keyed by contact method. 2856 */ 2857 function wp_get_user_contact_methods( $user = null ) { 2858 $methods = array(); 2859 if ( get_site_option( 'initial_db_version' ) < 23588 ) { 2860 $methods = array( 2861 'aim' => __( 'AIM' ), 2862 'yim' => __( 'Yahoo IM' ), 2863 'jabber' => __( 'Jabber / Google Talk' ), 2864 ); 2865 } 2866 2867 /** 2868 * Filters the user contact methods. 2869 * 2870 * @since 2.9.0 2871 * 2872 * @param string[] $methods Array of contact method labels keyed by contact method. 2873 * @param WP_User|null $user WP_User object or null if none was provided. 2874 */ 2875 return apply_filters( 'user_contactmethods', $methods, $user ); 2876 } 2877 2878 /** 2879 * The old private function for setting up user contact methods. 2880 * 2881 * Use wp_get_user_contact_methods() instead. 2882 * 2883 * @since 2.9.0 2884 * @access private 2885 * 2886 * @param WP_User|null $user Optional. WP_User object. Default null. 2887 * @return string[] Array of contact method labels keyed by contact method. 2888 */ 2889 function _wp_get_user_contactmethods( $user = null ) { 2890 return wp_get_user_contact_methods( $user ); 2891 } 2892 2893 /** 2894 * Gets the text suggesting how to create strong passwords. 2895 * 2896 * @since 4.1.0 2897 * 2898 * @return string The password hint text. 2899 */ 2900 function wp_get_password_hint() { 2901 $hint = __( 'Hint: The password should be at least twelve characters long. To make it stronger, use upper and lower case letters, numbers, and symbols like ! " ? $ % ^ & ).' ); 2902 2903 /** 2904 * Filters the text describing the site's password complexity policy. 2905 * 2906 * @since 4.1.0 2907 * 2908 * @param string $hint The password hint text. 2909 */ 2910 return apply_filters( 'password_hint', $hint ); 2911 } 2912 2913 /** 2914 * Creates, stores, then returns a password reset key for user. 2915 * 2916 * @since 4.4.0 2917 * 2918 * @global PasswordHash $wp_hasher Portable PHP password hashing framework instance. 2919 * 2920 * @param WP_User $user User to retrieve password reset key for. 2921 * @return string|WP_Error Password reset key on success. WP_Error on error. 2922 */ 2923 function get_password_reset_key( $user ) { 2924 global $wp_hasher; 2925 2926 if ( ! ( $user instanceof WP_User ) ) { 2927 return new WP_Error( 'invalidcombo', __( '<strong>Error:</strong> There is no account with that username or email address.' ) ); 2928 } 2929 2930 /** 2931 * Fires before a new password is retrieved. 2932 * 2933 * Use the {@see 'retrieve_password'} hook instead. 2934 * 2935 * @since 1.5.0 2936 * @deprecated 1.5.1 Misspelled. Use {@see 'retrieve_password'} hook instead. 2937 * 2938 * @param string $user_login The user login name. 2939 */ 2940 do_action_deprecated( 'retreive_password', array( $user->user_login ), '1.5.1', 'retrieve_password' ); 2941 2942 /** 2943 * Fires before a new password is retrieved. 2944 * 2945 * @since 1.5.1 2946 * 2947 * @param string $user_login The user login name. 2948 */ 2949 do_action( 'retrieve_password', $user->user_login ); 2950 2951 $password_reset_allowed = wp_is_password_reset_allowed_for_user( $user ); 2952 if ( ! $password_reset_allowed ) { 2953 return new WP_Error( 'no_password_reset', __( 'Password reset is not allowed for this user' ) ); 2954 } elseif ( is_wp_error( $password_reset_allowed ) ) { 2955 return $password_reset_allowed; 2956 } 2957 2958 // Generate something random for a password reset key. 2959 $key = wp_generate_password( 20, false ); 2960 2961 /** 2962 * Fires when a password reset key is generated. 2963 * 2964 * @since 2.5.0 2965 * 2966 * @param string $user_login The username for the user. 2967 * @param string $key The generated password reset key. 2968 */ 2969 do_action( 'retrieve_password_key', $user->user_login, $key ); 2970 2971 // Now insert the key, hashed, into the DB. 2972 if ( empty( $wp_hasher ) ) { 2973 require_once ABSPATH . WPINC . '/class-phpass.php'; 2974 $wp_hasher = new PasswordHash( 8, true ); 2975 } 2976 2977 $hashed = time() . ':' . $wp_hasher->HashPassword( $key ); 2978 2979 $key_saved = wp_update_user( 2980 array( 2981 'ID' => $user->ID, 2982 'user_activation_key' => $hashed, 2983 ) 2984 ); 2985 2986 if ( is_wp_error( $key_saved ) ) { 2987 return $key_saved; 2988 } 2989 2990 return $key; 2991 } 2992 2993 /** 2994 * Retrieves a user row based on password reset key and login. 2995 * 2996 * A key is considered 'expired' if it exactly matches the value of the 2997 * user_activation_key field, rather than being matched after going through the 2998 * hashing process. This field is now hashed; old values are no longer accepted 2999 * but have a different WP_Error code so good user feedback can be provided. 3000 * 3001 * @since 3.1.0 3002 * 3003 * @global PasswordHash $wp_hasher Portable PHP password hashing framework instance. 3004 * 3005 * @param string $key Hash to validate sending user's password. 3006 * @param string $login The user login. 3007 * @return WP_User|WP_Error WP_User object on success, WP_Error object for invalid or expired keys. 3008 */ 3009 function check_password_reset_key( $key, $login ) { 3010 global $wp_hasher; 3011 3012 $key = preg_replace( '/[^a-z0-9]/i', '', $key ); 3013 3014 if ( empty( $key ) || ! is_string( $key ) ) { 3015 return new WP_Error( 'invalid_key', __( 'Invalid key.' ) ); 3016 } 3017 3018 if ( empty( $login ) || ! is_string( $login ) ) { 3019 return new WP_Error( 'invalid_key', __( 'Invalid key.' ) ); 3020 } 3021 3022 $user = get_user_by( 'login', $login ); 3023 3024 if ( ! $user ) { 3025 return new WP_Error( 'invalid_key', __( 'Invalid key.' ) ); 3026 } 3027 3028 if ( empty( $wp_hasher ) ) { 3029 require_once ABSPATH . WPINC . '/class-phpass.php'; 3030 $wp_hasher = new PasswordHash( 8, true ); 3031 } 3032 3033 /** 3034 * Filters the expiration time of password reset keys. 3035 * 3036 * @since 4.3.0 3037 * 3038 * @param int $expiration The expiration time in seconds. 3039 */ 3040 $expiration_duration = apply_filters( 'password_reset_expiration', DAY_IN_SECONDS ); 3041 3042 if ( str_contains( $user->user_activation_key, ':' ) ) { 3043 list( $pass_request_time, $pass_key ) = explode( ':', $user->user_activation_key, 2 ); 3044 $expiration_time = $pass_request_time + $expiration_duration; 3045 } else { 3046 $pass_key = $user->user_activation_key; 3047 $expiration_time = false; 3048 } 3049 3050 if ( ! $pass_key ) { 3051 return new WP_Error( 'invalid_key', __( 'Invalid key.' ) ); 3052 } 3053 3054 $hash_is_correct = $wp_hasher->CheckPassword( $key, $pass_key ); 3055 3056 if ( $hash_is_correct && $expiration_time && time() < $expiration_time ) { 3057 return $user; 3058 } elseif ( $hash_is_correct && $expiration_time ) { 3059 // Key has an expiration time that's passed. 3060 return new WP_Error( 'expired_key', __( 'Invalid key.' ) ); 3061 } 3062 3063 if ( hash_equals( $user->user_activation_key, $key ) || ( $hash_is_correct && ! $expiration_time ) ) { 3064 $return = new WP_Error( 'expired_key', __( 'Invalid key.' ) ); 3065 $user_id = $user->ID; 3066 3067 /** 3068 * Filters the return value of check_password_reset_key() when an 3069 * old-style key is used. 3070 * 3071 * @since 3.7.0 Previously plain-text keys were stored in the database. 3072 * @since 4.3.0 Previously key hashes were stored without an expiration time. 3073 * 3074 * @param WP_Error $return A WP_Error object denoting an expired key. 3075 * Return a WP_User object to validate the key. 3076 * @param int $user_id The matched user ID. 3077 */ 3078 return apply_filters( 'password_reset_key_expired', $return, $user_id ); 3079 } 3080 3081 return new WP_Error( 'invalid_key', __( 'Invalid key.' ) ); 3082 } 3083 3084 /** 3085 * Handles sending a password retrieval email to a user. 3086 * 3087 * @since 2.5.0 3088 * @since 5.7.0 Added `$user_login` parameter. 3089 * 3090 * @global wpdb $wpdb WordPress database abstraction object. 3091 * @global PasswordHash $wp_hasher Portable PHP password hashing framework instance. 3092 * 3093 * @param string $user_login Optional. Username to send a password retrieval email for. 3094 * Defaults to `$_POST['user_login']` if not set. 3095 * @return true|WP_Error True when finished, WP_Error object on error. 3096 */ 3097 function retrieve_password( $user_login = null ) { 3098 $errors = new WP_Error(); 3099 $user_data = false; 3100 3101 // Use the passed $user_login if available, otherwise use $_POST['user_login']. 3102 if ( ! $user_login && ! empty( $_POST['user_login'] ) ) { 3103 $user_login = $_POST['user_login']; 3104 } 3105 3106 $user_login = trim( wp_unslash( $user_login ) ); 3107 3108 if ( empty( $user_login ) ) { 3109 $errors->add( 'empty_username', __( '<strong>Error:</strong> Please enter a username or email address.' ) ); 3110 } elseif ( strpos( $user_login, '@' ) ) { 3111 $user_data = get_user_by( 'email', $user_login ); 3112 3113 if ( empty( $user_data ) ) { 3114 $user_data = get_user_by( 'login', $user_login ); 3115 } 3116 3117 if ( empty( $user_data ) ) { 3118 $errors->add( 'invalid_email', __( '<strong>Error:</strong> There is no account with that username or email address.' ) ); 3119 } 3120 } else { 3121 $user_data = get_user_by( 'login', $user_login ); 3122 } 3123 3124 /** 3125 * Filters the user data during a password reset request. 3126 * 3127 * Allows, for example, custom validation using data other than username or email address. 3128 * 3129 * @since 5.7.0 3130 * 3131 * @param WP_User|false $user_data WP_User object if found, false if the user does not exist. 3132 * @param WP_Error $errors A WP_Error object containing any errors generated 3133 * by using invalid credentials. 3134 */ 3135 $user_data = apply_filters( 'lostpassword_user_data', $user_data, $errors ); 3136 3137 /** 3138 * Fires before errors are returned from a password reset request. 3139 * 3140 * @since 2.1.0 3141 * @since 4.4.0 Added the `$errors` parameter. 3142 * @since 5.4.0 Added the `$user_data` parameter. 3143 * 3144 * @param WP_Error $errors A WP_Error object containing any errors generated 3145 * by using invalid credentials. 3146 * @param WP_User|false $user_data WP_User object if found, false if the user does not exist. 3147 */ 3148 do_action( 'lostpassword_post', $errors, $user_data ); 3149 3150 /** 3151 * Filters the errors encountered on a password reset request. 3152 * 3153 * The filtered WP_Error object may, for example, contain errors for an invalid 3154 * username or email address. A WP_Error object should always be returned, 3155 * but may or may not contain errors. 3156 * 3157 * If any errors are present in $errors, this will abort the password reset request. 3158 * 3159 * @since 5.5.0 3160 * 3161 * @param WP_Error $errors A WP_Error object containing any errors generated 3162 * by using invalid credentials. 3163 * @param WP_User|false $user_data WP_User object if found, false if the user does not exist. 3164 */ 3165 $errors = apply_filters( 'lostpassword_errors', $errors, $user_data ); 3166 3167 if ( $errors->has_errors() ) { 3168 return $errors; 3169 } 3170 3171 if ( ! $user_data ) { 3172 $errors->add( 'invalidcombo', __( '<strong>Error:</strong> There is no account with that username or email address.' ) ); 3173 return $errors; 3174 } 3175 3176 /** 3177 * Filters whether to send the retrieve password email. 3178 * 3179 * Return false to disable sending the email. 3180 * 3181 * @since 6.0.0 3182 * 3183 * @param bool $send Whether to send the email. 3184 * @param string $user_login The username for the user. 3185 * @param WP_User $user_data WP_User object. 3186 */ 3187 if ( ! apply_filters( 'send_retrieve_password_email', true, $user_login, $user_data ) ) { 3188 return true; 3189 } 3190 3191 // Redefining user_login ensures we return the right case in the email. 3192 $user_login = $user_data->user_login; 3193 $user_email = $user_data->user_email; 3194 $key = get_password_reset_key( $user_data ); 3195 3196 if ( is_wp_error( $key ) ) { 3197 return $key; 3198 } 3199 3200 // Localize password reset message content for user. 3201 $locale = get_user_locale( $user_data ); 3202 3203 $switched_locale = switch_to_user_locale( $user_data->ID ); 3204 3205 if ( is_multisite() ) { 3206 $site_name = get_network()->site_name; 3207 } else { 3208 /* 3209 * The blogname option is escaped with esc_html on the way into the database 3210 * in sanitize_option. We want to reverse this for the plain text arena of emails. 3211 */ 3212 $site_name = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ); 3213 } 3214 3215 $message = __( 'Someone has requested a password reset for the following account:' ) . "\r\n\r\n"; 3216 /* translators: %s: Site name. */ 3217 $message .= sprintf( __( 'Site Name: %s' ), $site_name ) . "\r\n\r\n"; 3218 /* translators: %s: User login. */ 3219 $message .= sprintf( __( 'Username: %s' ), $user_login ) . "\r\n\r\n"; 3220 $message .= __( 'If this was a mistake, ignore this email and nothing will happen.' ) . "\r\n\r\n"; 3221 $message .= __( 'To reset your password, visit the following address:' ) . "\r\n\r\n"; 3222 3223 /* 3224 * Since some user login names end in a period, this could produce ambiguous URLs that 3225 * end in a period. To avoid the ambiguity, ensure that the login is not the last query 3226 * arg in the URL. If moving it to the end, a trailing period will need to be escaped. 3227 * 3228 * @see https://core.trac.wordpress.org/tickets/42957 3229 */ 3230 $message .= network_site_url( 'wp-login.php?login=' . rawurlencode( $user_login ) . "&key=$key&action=rp", 'login' ) . '&wp_lang=' . $locale . "\r\n\r\n"; 3231 3232 if ( ! is_user_logged_in() ) { 3233 $requester_ip = $_SERVER['REMOTE_ADDR']; 3234 if ( $requester_ip ) { 3235 $message .= sprintf( 3236 /* translators: %s: IP address of password reset requester. */ 3237 __( 'This password reset request originated from the IP address %s.' ), 3238 $requester_ip 3239 ) . "\r\n"; 3240 } 3241 } 3242 3243 /* translators: Password reset notification email subject. %s: Site title. */ 3244 $title = sprintf( __( '[%s] Password Reset' ), $site_name ); 3245 3246 /** 3247 * Filters the subject of the password reset email. 3248 * 3249 * @since 2.8.0 3250 * @since 4.4.0 Added the `$user_login` and `$user_data` parameters. 3251 * 3252 * @param string $title Email subject. 3253 * @param string $user_login The username for the user. 3254 * @param WP_User $user_data WP_User object. 3255 */ 3256 $title = apply_filters( 'retrieve_password_title', $title, $user_login, $user_data ); 3257 3258 /** 3259 * Filters the message body of the password reset mail. 3260 * 3261 * If the filtered message is empty, the password reset email will not be sent. 3262 * 3263 * @since 2.8.0 3264 * @since 4.1.0 Added `$user_login` and `$user_data` parameters. 3265 * 3266 * @param string $message Email message. 3267 * @param string $key The activation key. 3268 * @param string $user_login The username for the user. 3269 * @param WP_User $user_data WP_User object. 3270 */ 3271 $message = apply_filters( 'retrieve_password_message', $message, $key, $user_login, $user_data ); 3272 3273 // Short-circuit on falsey $message value for backwards compatibility. 3274 if ( ! $message ) { 3275 return true; 3276 } 3277 3278 /* 3279 * Wrap the single notification email arguments in an array 3280 * to pass them to the retrieve_password_notification_email filter. 3281 */ 3282 $defaults = array( 3283 'to' => $user_email, 3284 'subject' => $title, 3285 'message' => $message, 3286 'headers' => '', 3287 ); 3288 3289 /** 3290 * Filters the contents of the reset password notification email sent to the user. 3291 * 3292 * @since 6.0.0 3293 * 3294 * @param array $defaults { 3295 * The default notification email arguments. Used to build wp_mail(). 3296 * 3297 * @type string $to The intended recipient - user email address. 3298 * @type string $subject The subject of the email. 3299 * @type string $message The body of the email. 3300 * @type string $headers The headers of the email. 3301 * } 3302 * @param string $key The activation key. 3303 * @param string $user_login The username for the user. 3304 * @param WP_User $user_data WP_User object. 3305 */ 3306 $notification_email = apply_filters( 'retrieve_password_notification_email', $defaults, $key, $user_login, $user_data ); 3307 3308 if ( $switched_locale ) { 3309 restore_previous_locale(); 3310 } 3311 3312 if ( is_array( $notification_email ) ) { 3313 // Force key order and merge defaults in case any value is missing in the filtered array. 3314 $notification_email = array_merge( $defaults, $notification_email ); 3315 } else { 3316 $notification_email = $defaults; 3317 } 3318 3319 list( $to, $subject, $message, $headers ) = array_values( $notification_email ); 3320 3321 $subject = wp_specialchars_decode( $subject ); 3322 3323 if ( ! wp_mail( $to, $subject, $message, $headers ) ) { 3324 $errors->add( 3325 'retrieve_password_email_failure', 3326 sprintf( 3327 /* translators: %s: Documentation URL. */ 3328 __( '<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>.' ), 3329 esc_url( __( 'https://wordpress.org/documentation/article/reset-your-password/' ) ) 3330 ) 3331 ); 3332 return $errors; 3333 } 3334 3335 return true; 3336 } 3337 3338 /** 3339 * Handles resetting the user's password. 3340 * 3341 * @since 2.5.0 3342 * 3343 * @param WP_User $user The user 3344 * @param string $new_pass New password for the user in plaintext 3345 */ 3346 function reset_password( $user, $new_pass ) { 3347 /** 3348 * Fires before the user's password is reset. 3349 * 3350 * @since 1.5.0 3351 * 3352 * @param WP_User $user The user. 3353 * @param string $new_pass New user password. 3354 */ 3355 do_action( 'password_reset', $user, $new_pass ); 3356 3357 wp_set_password( $new_pass, $user->ID ); 3358 update_user_meta( $user->ID, 'default_password_nag', false ); 3359 3360 /** 3361 * Fires after the user's password is reset. 3362 * 3363 * @since 4.4.0 3364 * 3365 * @param WP_User $user The user. 3366 * @param string $new_pass New user password. 3367 */ 3368 do_action( 'after_password_reset', $user, $new_pass ); 3369 } 3370 3371 /** 3372 * Handles registering a new user. 3373 * 3374 * @since 2.5.0 3375 * 3376 * @param string $user_login User's username for logging in 3377 * @param string $user_email User's email address to send password and add 3378 * @return int|WP_Error Either user's ID or error on failure. 3379 */ 3380 function register_new_user( $user_login, $user_email ) { 3381 $errors = new WP_Error(); 3382 3383 $sanitized_user_login = sanitize_user( $user_login ); 3384 /** 3385 * Filters the email address of a user being registered. 3386 * 3387 * @since 2.1.0 3388 * 3389 * @param string $user_email The email address of the new user. 3390 */ 3391 $user_email = apply_filters( 'user_registration_email', $user_email ); 3392 3393 // Check the username. 3394 if ( '' === $sanitized_user_login ) { 3395 $errors->add( 'empty_username', __( '<strong>Error:</strong> Please enter a username.' ) ); 3396 } elseif ( ! validate_username( $user_login ) ) { 3397 $errors->add( 'invalid_username', __( '<strong>Error:</strong> This username is invalid because it uses illegal characters. Please enter a valid username.' ) ); 3398 $sanitized_user_login = ''; 3399 } elseif ( username_exists( $sanitized_user_login ) ) { 3400 $errors->add( 'username_exists', __( '<strong>Error:</strong> This username is already registered. Please choose another one.' ) ); 3401 } else { 3402 /** This filter is documented in wp-includes/user.php */ 3403 $illegal_user_logins = (array) apply_filters( 'illegal_user_logins', array() ); 3404 if ( in_array( strtolower( $sanitized_user_login ), array_map( 'strtolower', $illegal_user_logins ), true ) ) { 3405 $errors->add( 'invalid_username', __( '<strong>Error:</strong> Sorry, that username is not allowed.' ) ); 3406 } 3407 } 3408 3409 // Check the email address. 3410 if ( '' === $user_email ) { 3411 $errors->add( 'empty_email', __( '<strong>Error:</strong> Please type your email address.' ) ); 3412 } elseif ( ! is_email( $user_email ) ) { 3413 $errors->add( 'invalid_email', __( '<strong>Error:</strong> The email address is not correct.' ) ); 3414 $user_email = ''; 3415 } elseif ( email_exists( $user_email ) ) { 3416 $errors->add( 3417 'email_exists', 3418 sprintf( 3419 /* translators: %s: Link to the login page. */ 3420 __( '<strong>Error:</strong> This email address is already registered. <a href="%s">Log in</a> with this address or choose another one.' ), 3421 wp_login_url() 3422 ) 3423 ); 3424 } 3425 3426 /** 3427 * Fires when submitting registration form data, before the user is created. 3428 * 3429 * @since 2.1.0 3430 * 3431 * @param string $sanitized_user_login The submitted username after being sanitized. 3432 * @param string $user_email The submitted email. 3433 * @param WP_Error $errors Contains any errors with submitted username and email, 3434 * e.g., an empty field, an invalid username or email, 3435 * or an existing username or email. 3436 */ 3437 do_action( 'register_post', $sanitized_user_login, $user_email, $errors ); 3438 3439 /** 3440 * Filters the errors encountered when a new user is being registered. 3441 * 3442 * The filtered WP_Error object may, for example, contain errors for an invalid 3443 * or existing username or email address. A WP_Error object should always be returned, 3444 * but may or may not contain errors. 3445 * 3446 * If any errors are present in $errors, this will abort the user's registration. 3447 * 3448 * @since 2.1.0 3449 * 3450 * @param WP_Error $errors A WP_Error object containing any errors encountered 3451 * during registration. 3452 * @param string $sanitized_user_login User's username after it has been sanitized. 3453 * @param string $user_email User's email. 3454 */ 3455 $errors = apply_filters( 'registration_errors', $errors, $sanitized_user_login, $user_email ); 3456 3457 if ( $errors->has_errors() ) { 3458 return $errors; 3459 } 3460 3461 $user_pass = wp_generate_password( 12, false ); 3462 $user_id = wp_create_user( $sanitized_user_login, $user_pass, $user_email ); 3463 if ( ! $user_id || is_wp_error( $user_id ) ) { 3464 $errors->add( 3465 'registerfail', 3466 sprintf( 3467 /* translators: %s: Admin email address. */ 3468 __( '<strong>Error:</strong> Could not register you… please contact the <a href="mailto:%s">site admin</a>!' ), 3469 get_option( 'admin_email' ) 3470 ) 3471 ); 3472 return $errors; 3473 } 3474 3475 update_user_meta( $user_id, 'default_password_nag', true ); // Set up the password change nag. 3476 3477 if ( ! empty( $_COOKIE['wp_lang'] ) ) { 3478 $wp_lang = sanitize_text_field( $_COOKIE['wp_lang'] ); 3479 if ( in_array( $wp_lang, get_available_languages(), true ) ) { 3480 update_user_meta( $user_id, 'locale', $wp_lang ); // Set user locale if defined on registration. 3481 } 3482 } 3483 3484 /** 3485 * Fires after a new user registration has been recorded. 3486 * 3487 * @since 4.4.0 3488 * 3489 * @param int $user_id ID of the newly registered user. 3490 */ 3491 do_action( 'register_new_user', $user_id ); 3492 3493 return $user_id; 3494 } 3495 3496 /** 3497 * Initiates email notifications related to the creation of new users. 3498 * 3499 * Notifications are sent both to the site admin and to the newly created user. 3500 * 3501 * @since 4.4.0 3502 * @since 4.6.0 Converted the `$notify` parameter to accept 'user' for sending 3503 * notifications only to the user created. 3504 * 3505 * @param int $user_id ID of the newly created user. 3506 * @param string $notify Optional. Type of notification that should happen. Accepts 'admin' 3507 * or an empty string (admin only), 'user', or 'both' (admin and user). 3508 * Default 'both'. 3509 */ 3510 function wp_send_new_user_notifications( $user_id, $notify = 'both' ) { 3511 wp_new_user_notification( $user_id, null, $notify ); 3512 } 3513 3514 /** 3515 * Retrieves the current session token from the logged_in cookie. 3516 * 3517 * @since 4.0.0 3518 * 3519 * @return string Token. 3520 */ 3521 function wp_get_session_token() { 3522 $cookie = wp_parse_auth_cookie( '', 'logged_in' ); 3523 return ! empty( $cookie['token'] ) ? $cookie['token'] : ''; 3524 } 3525 3526 /** 3527 * Retrieves a list of sessions for the current user. 3528 * 3529 * @since 4.0.0 3530 * 3531 * @return array Array of sessions. 3532 */ 3533 function wp_get_all_sessions() { 3534 $manager = WP_Session_Tokens::get_instance( get_current_user_id() ); 3535 return $manager->get_all(); 3536 } 3537 3538 /** 3539 * Removes the current session token from the database. 3540 * 3541 * @since 4.0.0 3542 */ 3543 function wp_destroy_current_session() { 3544 $token = wp_get_session_token(); 3545 if ( $token ) { 3546 $manager = WP_Session_Tokens::get_instance( get_current_user_id() ); 3547 $manager->destroy( $token ); 3548 } 3549 } 3550 3551 /** 3552 * Removes all but the current session token for the current user for the database. 3553 * 3554 * @since 4.0.0 3555 */ 3556 function wp_destroy_other_sessions() { 3557 $token = wp_get_session_token(); 3558 if ( $token ) { 3559 $manager = WP_Session_Tokens::get_instance( get_current_user_id() ); 3560 $manager->destroy_others( $token ); 3561 } 3562 } 3563 3564 /** 3565 * Removes all session tokens for the current user from the database. 3566 * 3567 * @since 4.0.0 3568 */ 3569 function wp_destroy_all_sessions() { 3570 $manager = WP_Session_Tokens::get_instance( get_current_user_id() ); 3571 $manager->destroy_all(); 3572 } 3573 3574 /** 3575 * Gets the user IDs of all users with no role on this site. 3576 * 3577 * @since 4.4.0 3578 * @since 4.9.0 The `$site_id` parameter was added to support multisite. 3579 * 3580 * @global wpdb $wpdb WordPress database abstraction object. 3581 * 3582 * @param int|null $site_id Optional. The site ID to get users with no role for. Defaults to the current site. 3583 * @return string[] Array of user IDs as strings. 3584 */ 3585 function wp_get_users_with_no_role( $site_id = null ) { 3586 global $wpdb; 3587 3588 if ( ! $site_id ) { 3589 $site_id = get_current_blog_id(); 3590 } 3591 3592 $prefix = $wpdb->get_blog_prefix( $site_id ); 3593 3594 if ( is_multisite() && get_current_blog_id() !== $site_id ) { 3595 switch_to_blog( $site_id ); 3596 $role_names = wp_roles()->get_names(); 3597 restore_current_blog(); 3598 } else { 3599 $role_names = wp_roles()->get_names(); 3600 } 3601 3602 $regex = implode( '|', array_keys( $role_names ) ); 3603 $regex = preg_replace( '/[^a-zA-Z_\|-]/', '', $regex ); 3604 $users = $wpdb->get_col( 3605 $wpdb->prepare( 3606 "SELECT user_id 3607 FROM $wpdb->usermeta 3608 WHERE meta_key = '{$prefix}capabilities' 3609 AND meta_value NOT REGEXP %s", 3610 $regex 3611 ) 3612 ); 3613 3614 return $users; 3615 } 3616 3617 /** 3618 * Retrieves the current user object. 3619 * 3620 * Will set the current user, if the current user is not set. The current user 3621 * will be set to the logged-in person. If no user is logged-in, then it will 3622 * set the current user to 0, which is invalid and won't have any permissions. 3623 * 3624 * This function is used by the pluggable functions wp_get_current_user() and 3625 * get_currentuserinfo(), the latter of which is deprecated but used for backward 3626 * compatibility. 3627 * 3628 * @since 4.5.0 3629 * @access private 3630 * 3631 * @see wp_get_current_user() 3632 * @global WP_User $current_user Checks if the current user is set. 3633 * 3634 * @return WP_User Current WP_User instance. 3635 */ 3636 function _wp_get_current_user() { 3637 global $current_user; 3638 3639 if ( ! empty( $current_user ) ) { 3640 if ( $current_user instanceof WP_User ) { 3641 return $current_user; 3642 } 3643 3644 // Upgrade stdClass to WP_User. 3645 if ( is_object( $current_user ) && isset( $current_user->ID ) ) { 3646 $cur_id = $current_user->ID; 3647 $current_user = null; 3648 wp_set_current_user( $cur_id ); 3649 return $current_user; 3650 } 3651 3652 // $current_user has a junk value. Force to WP_User with ID 0. 3653 $current_user = null; 3654 wp_set_current_user( 0 ); 3655 return $current_user; 3656 } 3657 3658 if ( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST ) { 3659 wp_set_current_user( 0 ); 3660 return $current_user; 3661 } 3662 3663 /** 3664 * Filters the current user. 3665 * 3666 * The default filters use this to determine the current user from the 3667 * request's cookies, if available. 3668 * 3669 * Returning a value of false will effectively short-circuit setting 3670 * the current user. 3671 * 3672 * @since 3.9.0 3673 * 3674 * @param int|false $user_id User ID if one has been determined, false otherwise. 3675 */ 3676 $user_id = apply_filters( 'determine_current_user', false ); 3677 if ( ! $user_id ) { 3678 wp_set_current_user( 0 ); 3679 return $current_user; 3680 } 3681 3682 wp_set_current_user( $user_id ); 3683 3684 return $current_user; 3685 } 3686 3687 /** 3688 * Sends a confirmation request email when a change of user email address is attempted. 3689 * 3690 * @since 3.0.0 3691 * @since 4.9.0 This function was moved from wp-admin/includes/ms.php so it's no longer Multisite specific. 3692 * 3693 * @global WP_Error $errors WP_Error object. 3694 */ 3695 function send_confirmation_on_profile_email() { 3696 global $errors; 3697 3698 $current_user = wp_get_current_user(); 3699 if ( ! is_object( $errors ) ) { 3700 $errors = new WP_Error(); 3701 } 3702 3703 if ( $current_user->ID !== (int) $_POST['user_id'] ) { 3704 return false; 3705 } 3706 3707 if ( $current_user->user_email !== $_POST['email'] ) { 3708 if ( ! is_email( $_POST['email'] ) ) { 3709 $errors->add( 3710 'user_email', 3711 __( '<strong>Error:</strong> The email address is not correct.' ), 3712 array( 3713 'form-field' => 'email', 3714 ) 3715 ); 3716 3717 return; 3718 } 3719 3720 if ( email_exists( $_POST['email'] ) ) { 3721 $errors->add( 3722 'user_email', 3723 __( '<strong>Error:</strong> The email address is already used.' ), 3724 array( 3725 'form-field' => 'email', 3726 ) 3727 ); 3728 delete_user_meta( $current_user->ID, '_new_email' ); 3729 3730 return; 3731 } 3732 3733 $hash = md5( $_POST['email'] . time() . wp_rand() ); 3734 $new_user_email = array( 3735 'hash' => $hash, 3736 'newemail' => $_POST['email'], 3737 ); 3738 update_user_meta( $current_user->ID, '_new_email', $new_user_email ); 3739 3740 $sitename = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ); 3741 3742 /* translators: Do not translate USERNAME, ADMIN_URL, EMAIL, SITENAME, SITEURL: those are placeholders. */ 3743 $email_text = __( 3744 'Howdy ###USERNAME###, 3745 3746 You recently requested to have the email address on your account changed. 3747 3748 If this is correct, please click on the following link to change it: 3749 ###ADMIN_URL### 3750 3751 You can safely ignore and delete this email if you do not want to 3752 take this action. 3753 3754 This email has been sent to ###EMAIL### 3755 3756 Regards, 3757 All at ###SITENAME### 3758 ###SITEURL###' 3759 ); 3760 3761 /** 3762 * Filters the text of the email sent when a change of user email address is attempted. 3763 * 3764 * The following strings have a special meaning and will get replaced dynamically: 3765 * - ###USERNAME### The current user's username. 3766 * - ###ADMIN_URL### The link to click on to confirm the email change. 3767 * - ###EMAIL### The new email. 3768 * - ###SITENAME### The name of the site. 3769 * - ###SITEURL### The URL to the site. 3770 * 3771 * @since MU (3.0.0) 3772 * @since 4.9.0 This filter is no longer Multisite specific. 3773 * 3774 * @param string $email_text Text in the email. 3775 * @param array $new_user_email { 3776 * Data relating to the new user email address. 3777 * 3778 * @type string $hash The secure hash used in the confirmation link URL. 3779 * @type string $newemail The proposed new email address. 3780 * } 3781 */ 3782 $content = apply_filters( 'new_user_email_content', $email_text, $new_user_email ); 3783 3784 $content = str_replace( '###USERNAME###', $current_user->user_login, $content ); 3785 $content = str_replace( '###ADMIN_URL###', esc_url( self_admin_url( 'profile.php?newuseremail=' . $hash ) ), $content ); 3786 $content = str_replace( '###EMAIL###', $_POST['email'], $content ); 3787 $content = str_replace( '###SITENAME###', $sitename, $content ); 3788 $content = str_replace( '###SITEURL###', home_url(), $content ); 3789 3790 /* translators: New email address notification email subject. %s: Site title. */ 3791 wp_mail( $_POST['email'], sprintf( __( '[%s] Email Change Request' ), $sitename ), $content ); 3792 3793 $_POST['email'] = $current_user->user_email; 3794 } 3795 } 3796 3797 /** 3798 * Adds an admin notice alerting the user to check for confirmation request email 3799 * after email address change. 3800 * 3801 * @since 3.0.0 3802 * @since 4.9.0 This function was moved from wp-admin/includes/ms.php so it's no longer Multisite specific. 3803 * 3804 * @global string $pagenow The filename of the current screen. 3805 */ 3806 function new_user_email_admin_notice() { 3807 global $pagenow; 3808 3809 if ( 'profile.php' === $pagenow && isset( $_GET['updated'] ) ) { 3810 $email = get_user_meta( get_current_user_id(), '_new_email', true ); 3811 if ( $email ) { 3812 $message = sprintf( 3813 /* translators: %s: New email address. */ 3814 __( 'Your email address has not been updated yet. Please check your inbox at %s for a confirmation email.' ), 3815 '<code>' . esc_html( $email['newemail'] ) . '</code>' 3816 ); 3817 wp_admin_notice( $message, array( 'type' => 'info' ) ); 3818 } 3819 } 3820 } 3821 3822 /** 3823 * Gets all personal data request types. 3824 * 3825 * @since 4.9.6 3826 * @access private 3827 * 3828 * @return string[] List of core privacy action types. 3829 */ 3830 function _wp_privacy_action_request_types() { 3831 return array( 3832 'export_personal_data', 3833 'remove_personal_data', 3834 ); 3835 } 3836 3837 /** 3838 * Registers the personal data exporter for users. 3839 * 3840 * @since 4.9.6 3841 * 3842 * @param array[] $exporters An array of personal data exporters. 3843 * @return array[] An array of personal data exporters. 3844 */ 3845 function wp_register_user_personal_data_exporter( $exporters ) { 3846 $exporters['wordpress-user'] = array( 3847 'exporter_friendly_name' => __( 'WordPress User' ), 3848 'callback' => 'wp_user_personal_data_exporter', 3849 ); 3850 3851 return $exporters; 3852 } 3853 3854 /** 3855 * Finds and exports personal data associated with an email address from the user and user_meta table. 3856 * 3857 * @since 4.9.6 3858 * @since 5.4.0 Added 'Community Events Location' group to the export data. 3859 * @since 5.4.0 Added 'Session Tokens' group to the export data. 3860 * 3861 * @param string $email_address The user's email address. 3862 * @return array { 3863 * An array of personal data. 3864 * 3865 * @type array[] $data An array of personal data arrays. 3866 * @type bool $done Whether the exporter is finished. 3867 * } 3868 */ 3869 function wp_user_personal_data_exporter( $email_address ) { 3870 $email_address = trim( $email_address ); 3871 3872 $data_to_export = array(); 3873 3874 $user = get_user_by( 'email', $email_address ); 3875 3876 if ( ! $user ) { 3877 return array( 3878 'data' => array(), 3879 'done' => true, 3880 ); 3881 } 3882 3883 $user_meta = get_user_meta( $user->ID ); 3884 3885 $user_props_to_export = array( 3886 'ID' => __( 'User ID' ), 3887 'user_login' => __( 'User Login Name' ), 3888 'user_nicename' => __( 'User Nice Name' ), 3889 'user_email' => __( 'User Email' ), 3890 'user_url' => __( 'User URL' ), 3891 'user_registered' => __( 'User Registration Date' ), 3892 'display_name' => __( 'User Display Name' ), 3893 'nickname' => __( 'User Nickname' ), 3894 'first_name' => __( 'User First Name' ), 3895 'last_name' => __( 'User Last Name' ), 3896 'description' => __( 'User Description' ), 3897 ); 3898 3899 $user_data_to_export = array(); 3900 3901 foreach ( $user_props_to_export as $key => $name ) { 3902 $value = ''; 3903 3904 switch ( $key ) { 3905 case 'ID': 3906 case 'user_login': 3907 case 'user_nicename': 3908 case 'user_email': 3909 case 'user_url': 3910 case 'user_registered': 3911 case 'display_name': 3912 $value = $user->data->$key; 3913 break; 3914 case 'nickname': 3915 case 'first_name': 3916 case 'last_name': 3917 case 'description': 3918 $value = $user_meta[ $key ][0]; 3919 break; 3920 } 3921 3922 if ( ! empty( $value ) ) { 3923 $user_data_to_export[] = array( 3924 'name' => $name, 3925 'value' => $value, 3926 ); 3927 } 3928 } 3929 3930 // Get the list of reserved names. 3931 $reserved_names = array_values( $user_props_to_export ); 3932 3933 /** 3934 * Filters the user's profile data for the privacy exporter. 3935 * 3936 * @since 5.4.0 3937 * 3938 * @param array $additional_user_profile_data { 3939 * An array of name-value pairs of additional user data items. Default empty array. 3940 * 3941 * @type string $name The user-facing name of an item name-value pair,e.g. 'IP Address'. 3942 * @type string $value The user-facing value of an item data pair, e.g. '50.60.70.0'. 3943 * } 3944 * @param WP_User $user The user whose data is being exported. 3945 * @param string[] $reserved_names An array of reserved names. Any item in `$additional_user_data` 3946 * that uses one of these for its `name` will not be included in the export. 3947 */ 3948 $_extra_data = apply_filters( 'wp_privacy_additional_user_profile_data', array(), $user, $reserved_names ); 3949 3950 if ( is_array( $_extra_data ) && ! empty( $_extra_data ) ) { 3951 // Remove items that use reserved names. 3952 $extra_data = array_filter( 3953 $_extra_data, 3954 static function ( $item ) use ( $reserved_names ) { 3955 return ! in_array( $item['name'], $reserved_names, true ); 3956 } 3957 ); 3958 3959 if ( count( $extra_data ) !== count( $_extra_data ) ) { 3960 _doing_it_wrong( 3961 __FUNCTION__, 3962 sprintf( 3963 /* translators: %s: wp_privacy_additional_user_profile_data */ 3964 __( 'Filter %s returned items with reserved names.' ), 3965 '<code>wp_privacy_additional_user_profile_data</code>' 3966 ), 3967 '5.4.0' 3968 ); 3969 } 3970 3971 if ( ! empty( $extra_data ) ) { 3972 $user_data_to_export = array_merge( $user_data_to_export, $extra_data ); 3973 } 3974 } 3975 3976 $data_to_export[] = array( 3977 'group_id' => 'user', 3978 'group_label' => __( 'User' ), 3979 'group_description' => __( 'User’s profile data.' ), 3980 'item_id' => "user-{$user->ID}", 3981 'data' => $user_data_to_export, 3982 ); 3983 3984 if ( isset( $user_meta['community-events-location'] ) ) { 3985 $location = maybe_unserialize( $user_meta['community-events-location'][0] ); 3986 3987 $location_props_to_export = array( 3988 'description' => __( 'City' ), 3989 'country' => __( 'Country' ), 3990 'latitude' => __( 'Latitude' ), 3991 'longitude' => __( 'Longitude' ), 3992 'ip' => __( 'IP' ), 3993 ); 3994 3995 $location_data_to_export = array(); 3996 3997 foreach ( $location_props_to_export as $key => $name ) { 3998 if ( ! empty( $location[ $key ] ) ) { 3999 $location_data_to_export[] = array( 4000 'name' => $name, 4001 'value' => $location[ $key ], 4002 ); 4003 } 4004 } 4005 4006 $data_to_export[] = array( 4007 'group_id' => 'community-events-location', 4008 'group_label' => __( 'Community Events Location' ), 4009 'group_description' => __( 'User’s location data used for the Community Events in the WordPress Events and News dashboard widget.' ), 4010 'item_id' => "community-events-location-{$user->ID}", 4011 'data' => $location_data_to_export, 4012 ); 4013 } 4014 4015 if ( isset( $user_meta['session_tokens'] ) ) { 4016 $session_tokens = maybe_unserialize( $user_meta['session_tokens'][0] ); 4017 4018 $session_tokens_props_to_export = array( 4019 'expiration' => __( 'Expiration' ), 4020 'ip' => __( 'IP' ), 4021 'ua' => __( 'User Agent' ), 4022 'login' => __( 'Last Login' ), 4023 ); 4024 4025 foreach ( $session_tokens as $token_key => $session_token ) { 4026 $session_tokens_data_to_export = array(); 4027 4028 foreach ( $session_tokens_props_to_export as $key => $name ) { 4029 if ( ! empty( $session_token[ $key ] ) ) { 4030 $value = $session_token[ $key ]; 4031 if ( in_array( $key, array( 'expiration', 'login' ), true ) ) { 4032 $value = date_i18n( 'F d, Y H:i A', $value ); 4033 } 4034 $session_tokens_data_to_export[] = array( 4035 'name' => $name, 4036 'value' => $value, 4037 ); 4038 } 4039 } 4040 4041 $data_to_export[] = array( 4042 'group_id' => 'session-tokens', 4043 'group_label' => __( 'Session Tokens' ), 4044 'group_description' => __( 'User’s Session Tokens data.' ), 4045 'item_id' => "session-tokens-{$user->ID}-{$token_key}", 4046 'data' => $session_tokens_data_to_export, 4047 ); 4048 } 4049 } 4050 4051 return array( 4052 'data' => $data_to_export, 4053 'done' => true, 4054 ); 4055 } 4056 4057 /** 4058 * Updates log when privacy request is confirmed. 4059 * 4060 * @since 4.9.6 4061 * @access private 4062 * 4063 * @param int $request_id ID of the request. 4064 */ 4065 function _wp_privacy_account_request_confirmed( $request_id ) { 4066 $request = wp_get_user_request( $request_id ); 4067 4068 if ( ! $request ) { 4069 return; 4070 } 4071 4072 if ( ! in_array( $request->status, array( 'request-pending', 'request-failed' ), true ) ) { 4073 return; 4074 } 4075 4076 update_post_meta( $request_id, '_wp_user_request_confirmed_timestamp', time() ); 4077 wp_update_post( 4078 array( 4079 'ID' => $request_id, 4080 'post_status' => 'request-confirmed', 4081 ) 4082 ); 4083 } 4084 4085 /** 4086 * Notifies the site administrator via email when a request is confirmed. 4087 * 4088 * Without this, the admin would have to manually check the site to see if any 4089 * action was needed on their part yet. 4090 * 4091 * @since 4.9.6 4092 * 4093 * @param int $request_id The ID of the request. 4094 */ 4095 function _wp_privacy_send_request_confirmation_notification( $request_id ) { 4096 $request = wp_get_user_request( $request_id ); 4097 4098 if ( ! ( $request instanceof WP_User_Request ) || 'request-confirmed' !== $request->status ) { 4099 return; 4100 } 4101 4102 $already_notified = (bool) get_post_meta( $request_id, '_wp_admin_notified', true ); 4103 4104 if ( $already_notified ) { 4105 return; 4106 } 4107 4108 if ( 'export_personal_data' === $request->action_name ) { 4109 $manage_url = admin_url( 'export-personal-data.php' ); 4110 } elseif ( 'remove_personal_data' === $request->action_name ) { 4111 $manage_url = admin_url( 'erase-personal-data.php' ); 4112 } 4113 $action_description = wp_user_request_action_description( $request->action_name ); 4114 4115 /** 4116 * Filters the recipient of the data request confirmation notification. 4117 * 4118 * In a Multisite environment, this will default to the email address of the 4119 * network admin because, by default, single site admins do not have the 4120 * capabilities required to process requests. Some networks may wish to 4121 * delegate those capabilities to a single-site admin, or a dedicated person 4122 * responsible for managing privacy requests. 4123 * 4124 * @since 4.9.6 4125 * 4126 * @param string $admin_email The email address of the notification recipient. 4127 * @param WP_User_Request $request The request that is initiating the notification. 4128 */ 4129 $admin_email = apply_filters( 'user_request_confirmed_email_to', get_site_option( 'admin_email' ), $request ); 4130 4131 $email_data = array( 4132 'request' => $request, 4133 'user_email' => $request->email, 4134 'description' => $action_description, 4135 'manage_url' => $manage_url, 4136 'sitename' => wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ), 4137 'siteurl' => home_url(), 4138 'admin_email' => $admin_email, 4139 ); 4140 4141 $subject = sprintf( 4142 /* translators: Privacy data request confirmed notification email subject. 1: Site title, 2: Name of the confirmed action. */ 4143 __( '[%1$s] Action Confirmed: %2$s' ), 4144 $email_data['sitename'], 4145 $action_description 4146 ); 4147 4148 /** 4149 * Filters the subject of the user request confirmation email. 4150 * 4151 * @since 4.9.8 4152 * 4153 * @param string $subject The email subject. 4154 * @param string $sitename The name of the site. 4155 * @param array $email_data { 4156 * Data relating to the account action email. 4157 * 4158 * @type WP_User_Request $request User request object. 4159 * @type string $user_email The email address confirming a request. 4160 * @type string $description Description of the action being performed so the user knows what the email is for. 4161 * @type string $manage_url The link to click manage privacy requests of this type. 4162 * @type string $sitename The site name sending the mail. 4163 * @type string $siteurl The site URL sending the mail. 4164 * @type string $admin_email The administrator email receiving the mail. 4165 * } 4166 */ 4167 $subject = apply_filters( 'user_request_confirmed_email_subject', $subject, $email_data['sitename'], $email_data ); 4168 4169 /* translators: Do not translate SITENAME, USER_EMAIL, DESCRIPTION, MANAGE_URL, SITEURL; those are placeholders. */ 4170 $content = __( 4171 'Howdy, 4172 4173 A user data privacy request has been confirmed on ###SITENAME###: 4174 4175 User: ###USER_EMAIL### 4176 Request: ###DESCRIPTION### 4177 4178 You can view and manage these data privacy requests here: 4179 4180 ###MANAGE_URL### 4181 4182 Regards, 4183 All at ###SITENAME### 4184 ###SITEURL###' 4185 ); 4186 4187 /** 4188 * Filters the body of the user request confirmation email. 4189 * 4190 * The email is sent to an administrator when a user request is confirmed. 4191 * 4192 * The following strings have a special meaning and will get replaced dynamically: 4193 * 4194 * ###SITENAME### The name of the site. 4195 * ###USER_EMAIL### The user email for the request. 4196 * ###DESCRIPTION### Description of the action being performed so the user knows what the email is for. 4197 * ###MANAGE_URL### The URL to manage requests. 4198 * ###SITEURL### The URL to the site. 4199 * 4200 * @since 4.9.6 4201 * @deprecated 5.8.0 Use {@see 'user_request_confirmed_email_content'} instead. 4202 * For user erasure fulfillment email content 4203 * use {@see 'user_erasure_fulfillment_email_content'} instead. 4204 * 4205 * @param string $content The email content. 4206 * @param array $email_data { 4207 * Data relating to the account action email. 4208 * 4209 * @type WP_User_Request $request User request object. 4210 * @type string $user_email The email address confirming a request. 4211 * @type string $description Description of the action being performed 4212 * so the user knows what the email is for. 4213 * @type string $manage_url The link to click manage privacy requests of this type. 4214 * @type string $sitename The site name sending the mail. 4215 * @type string $siteurl The site URL sending the mail. 4216 * @type string $admin_email The administrator email receiving the mail. 4217 * } 4218 */ 4219 $content = apply_filters_deprecated( 4220 'user_confirmed_action_email_content', 4221 array( $content, $email_data ), 4222 '5.8.0', 4223 sprintf( 4224 /* translators: 1 & 2: Deprecation replacement options. */ 4225 __( '%1$s or %2$s' ), 4226 'user_request_confirmed_email_content', 4227 'user_erasure_fulfillment_email_content' 4228 ) 4229 ); 4230 4231 /** 4232 * Filters the body of the user request confirmation email. 4233 * 4234 * The email is sent to an administrator when a user request is confirmed. 4235 * The following strings have a special meaning and will get replaced dynamically: 4236 * 4237 * ###SITENAME### The name of the site. 4238 * ###USER_EMAIL### The user email for the request. 4239 * ###DESCRIPTION### Description of the action being performed so the user knows what the email is for. 4240 * ###MANAGE_URL### The URL to manage requests. 4241 * ###SITEURL### The URL to the site. 4242 * 4243 * @since 5.8.0 4244 * 4245 * @param string $content The email content. 4246 * @param array $email_data { 4247 * Data relating to the account action email. 4248 * 4249 * @type WP_User_Request $request User request object. 4250 * @type string $user_email The email address confirming a request. 4251 * @type string $description Description of the action being performed so the user knows what the email is for. 4252 * @type string $manage_url The link to click manage privacy requests of this type. 4253 * @type string $sitename The site name sending the mail. 4254 * @type string $siteurl The site URL sending the mail. 4255 * @type string $admin_email The administrator email receiving the mail. 4256 * } 4257 */ 4258 $content = apply_filters( 'user_request_confirmed_email_content', $content, $email_data ); 4259 4260 $content = str_replace( '###SITENAME###', $email_data['sitename'], $content ); 4261 $content = str_replace( '###USER_EMAIL###', $email_data['user_email'], $content ); 4262 $content = str_replace( '###DESCRIPTION###', $email_data['description'], $content ); 4263 $content = str_replace( '###MANAGE_URL###', sanitize_url( $email_data['manage_url'] ), $content ); 4264 $content = str_replace( '###SITEURL###', sanitize_url( $email_data['siteurl'] ), $content ); 4265 4266 $headers = ''; 4267 4268 /** 4269 * Filters the headers of the user request confirmation email. 4270 * 4271 * @since 5.4.0 4272 * 4273 * @param string|array $headers The email headers. 4274 * @param string $subject The email subject. 4275 * @param string $content The email content. 4276 * @param int $request_id The request ID. 4277 * @param array $email_data { 4278 * Data relating to the account action email. 4279 * 4280 * @type WP_User_Request $request User request object. 4281 * @type string $user_email The email address confirming a request. 4282 * @type string $description Description of the action being performed so the user knows what the email is for. 4283 * @type string $manage_url The link to click manage privacy requests of this type. 4284 * @type string $sitename The site name sending the mail. 4285 * @type string $siteurl The site URL sending the mail. 4286 * @type string $admin_email The administrator email receiving the mail. 4287 * } 4288 */ 4289 $headers = apply_filters( 'user_request_confirmed_email_headers', $headers, $subject, $content, $request_id, $email_data ); 4290 4291 $email_sent = wp_mail( $email_data['admin_email'], $subject, $content, $headers ); 4292 4293 if ( $email_sent ) { 4294 update_post_meta( $request_id, '_wp_admin_notified', true ); 4295 } 4296 } 4297 4298 /** 4299 * Notifies the user when their erasure request is fulfilled. 4300 * 4301 * Without this, the user would never know if their data was actually erased. 4302 * 4303 * @since 4.9.6 4304 * 4305 * @param int $request_id The privacy request post ID associated with this request. 4306 */ 4307 function _wp_privacy_send_erasure_fulfillment_notification( $request_id ) { 4308 $request = wp_get_user_request( $request_id ); 4309 4310 if ( ! ( $request instanceof WP_User_Request ) || 'request-completed' !== $request->status ) { 4311 return; 4312 } 4313 4314 $already_notified = (bool) get_post_meta( $request_id, '_wp_user_notified', true ); 4315 4316 if ( $already_notified ) { 4317 return; 4318 } 4319 4320 // Localize message content for user; fallback to site default for visitors. 4321 if ( ! empty( $request->user_id ) ) { 4322 $switched_locale = switch_to_user_locale( $request->user_id ); 4323 } else { 4324 $switched_locale = switch_to_locale( get_locale() ); 4325 } 4326 4327 /** 4328 * Filters the recipient of the data erasure fulfillment notification. 4329 * 4330 * @since 4.9.6 4331 * 4332 * @param string $user_email The email address of the notification recipient. 4333 * @param WP_User_Request $request The request that is initiating the notification. 4334 */ 4335 $user_email = apply_filters( 'user_erasure_fulfillment_email_to', $request->email, $request ); 4336 4337 $email_data = array( 4338 'request' => $request, 4339 'message_recipient' => $user_email, 4340 'privacy_policy_url' => get_privacy_policy_url(), 4341 'sitename' => wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ), 4342 'siteurl' => home_url(), 4343 ); 4344 4345 $subject = sprintf( 4346 /* translators: Erasure request fulfilled notification email subject. %s: Site title. */ 4347 __( '[%s] Erasure Request Fulfilled' ), 4348 $email_data['sitename'] 4349 ); 4350 4351 /** 4352 * Filters the subject of the email sent when an erasure request is completed. 4353 * 4354 * @since 4.9.8 4355 * @deprecated 5.8.0 Use {@see 'user_erasure_fulfillment_email_subject'} instead. 4356 * 4357 * @param string $subject The email subject. 4358 * @param string $sitename The name of the site. 4359 * @param array $email_data { 4360 * Data relating to the account action email. 4361 * 4362 * @type WP_User_Request $request User request object. 4363 * @type string $message_recipient The address that the email will be sent to. Defaults 4364 * to the value of `$request->email`, but can be changed 4365 * by the `user_erasure_fulfillment_email_to` filter. 4366 * @type string $privacy_policy_url Privacy policy URL. 4367 * @type string $sitename The site name sending the mail. 4368 * @type string $siteurl The site URL sending the mail. 4369 * } 4370 */ 4371 $subject = apply_filters_deprecated( 4372 'user_erasure_complete_email_subject', 4373 array( $subject, $email_data['sitename'], $email_data ), 4374 '5.8.0', 4375 'user_erasure_fulfillment_email_subject' 4376 ); 4377 4378 /** 4379 * Filters the subject of the email sent when an erasure request is completed. 4380 * 4381 * @since 5.8.0 4382 * 4383 * @param string $subject The email subject. 4384 * @param string $sitename The name of the site. 4385 * @param array $email_data { 4386 * Data relating to the account action email. 4387 * 4388 * @type WP_User_Request $request User request object. 4389 * @type string $message_recipient The address that the email will be sent to. Defaults 4390 * to the value of `$request->email`, but can be changed 4391 * by the `user_erasure_fulfillment_email_to` filter. 4392 * @type string $privacy_policy_url Privacy policy URL. 4393 * @type string $sitename The site name sending the mail. 4394 * @type string $siteurl The site URL sending the mail. 4395 * } 4396 */ 4397 $subject = apply_filters( 'user_erasure_fulfillment_email_subject', $subject, $email_data['sitename'], $email_data ); 4398 4399 /* translators: Do not translate SITENAME, SITEURL; those are placeholders. */ 4400 $content = __( 4401 'Howdy, 4402 4403 Your request to erase your personal data on ###SITENAME### has been completed. 4404 4405 If you have any follow-up questions or concerns, please contact the site administrator. 4406 4407 Regards, 4408 All at ###SITENAME### 4409 ###SITEURL###' 4410 ); 4411 4412 if ( ! empty( $email_data['privacy_policy_url'] ) ) { 4413 /* translators: Do not translate SITENAME, SITEURL, PRIVACY_POLICY_URL; those are placeholders. */ 4414 $content = __( 4415 'Howdy, 4416 4417 Your request to erase your personal data on ###SITENAME### has been completed. 4418 4419 If you have any follow-up questions or concerns, please contact the site administrator. 4420 4421 For more information, you can also read our privacy policy: ###PRIVACY_POLICY_URL### 4422 4423 Regards, 4424 All at ###SITENAME### 4425 ###SITEURL###' 4426 ); 4427 } 4428 4429 /** 4430 * Filters the body of the data erasure fulfillment notification. 4431 * 4432 * The email is sent to a user when their data erasure request is fulfilled 4433 * by an administrator. 4434 * 4435 * The following strings have a special meaning and will get replaced dynamically: 4436 * 4437 * ###SITENAME### The name of the site. 4438 * ###PRIVACY_POLICY_URL### Privacy policy page URL. 4439 * ###SITEURL### The URL to the site. 4440 * 4441 * @since 4.9.6 4442 * @deprecated 5.8.0 Use {@see 'user_erasure_fulfillment_email_content'} instead. 4443 * For user request confirmation email content 4444 * use {@see 'user_request_confirmed_email_content'} instead. 4445 * 4446 * @param string $content The email content. 4447 * @param array $email_data { 4448 * Data relating to the account action email. 4449 * 4450 * @type WP_User_Request $request User request object. 4451 * @type string $message_recipient The address that the email will be sent to. Defaults 4452 * to the value of `$request->email`, but can be changed 4453 * by the `user_erasure_fulfillment_email_to` filter. 4454 * @type string $privacy_policy_url Privacy policy URL. 4455 * @type string $sitename The site name sending the mail. 4456 * @type string $siteurl The site URL sending the mail. 4457 * } 4458 */ 4459 $content = apply_filters_deprecated( 4460 'user_confirmed_action_email_content', 4461 array( $content, $email_data ), 4462 '5.8.0', 4463 sprintf( 4464 /* translators: 1 & 2: Deprecation replacement options. */ 4465 __( '%1$s or %2$s' ), 4466 'user_erasure_fulfillment_email_content', 4467 'user_request_confirmed_email_content' 4468 ) 4469 ); 4470 4471 /** 4472 * Filters the body of the data erasure fulfillment notification. 4473 * 4474 * The email is sent to a user when their data erasure request is fulfilled 4475 * by an administrator. 4476 * 4477 * The following strings have a special meaning and will get replaced dynamically: 4478 * 4479 * ###SITENAME### The name of the site. 4480 * ###PRIVACY_POLICY_URL### Privacy policy page URL. 4481 * ###SITEURL### The URL to the site. 4482 * 4483 * @since 5.8.0 4484 * 4485 * @param string $content The email content. 4486 * @param array $email_data { 4487 * Data relating to the account action email. 4488 * 4489 * @type WP_User_Request $request User request object. 4490 * @type string $message_recipient The address that the email will be sent to. Defaults 4491 * to the value of `$request->email`, but can be changed 4492 * by the `user_erasure_fulfillment_email_to` filter. 4493 * @type string $privacy_policy_url Privacy policy URL. 4494 * @type string $sitename The site name sending the mail. 4495 * @type string $siteurl The site URL sending the mail. 4496 * } 4497 */ 4498 $content = apply_filters( 'user_erasure_fulfillment_email_content', $content, $email_data ); 4499 4500 $content = str_replace( '###SITENAME###', $email_data['sitename'], $content ); 4501 $content = str_replace( '###PRIVACY_POLICY_URL###', $email_data['privacy_policy_url'], $content ); 4502 $content = str_replace( '###SITEURL###', sanitize_url( $email_data['siteurl'] ), $content ); 4503 4504 $headers = ''; 4505 4506 /** 4507 * Filters the headers of the data erasure fulfillment notification. 4508 * 4509 * @since 5.4.0 4510 * @deprecated 5.8.0 Use {@see 'user_erasure_fulfillment_email_headers'} instead. 4511 * 4512 * @param string|array $headers The email headers. 4513 * @param string $subject The email subject. 4514 * @param string $content The email content. 4515 * @param int $request_id The request ID. 4516 * @param array $email_data { 4517 * Data relating to the account action email. 4518 * 4519 * @type WP_User_Request $request User request object. 4520 * @type string $message_recipient The address that the email will be sent to. Defaults 4521 * to the value of `$request->email`, but can be changed 4522 * by the `user_erasure_fulfillment_email_to` filter. 4523 * @type string $privacy_policy_url Privacy policy URL. 4524 * @type string $sitename The site name sending the mail. 4525 * @type string $siteurl The site URL sending the mail. 4526 * } 4527 */ 4528 $headers = apply_filters_deprecated( 4529 'user_erasure_complete_email_headers', 4530 array( $headers, $subject, $content, $request_id, $email_data ), 4531 '5.8.0', 4532 'user_erasure_fulfillment_email_headers' 4533 ); 4534 4535 /** 4536 * Filters the headers of the data erasure fulfillment notification. 4537 * 4538 * @since 5.8.0 4539 * 4540 * @param string|array $headers The email headers. 4541 * @param string $subject The email subject. 4542 * @param string $content The email content. 4543 * @param int $request_id The request ID. 4544 * @param array $email_data { 4545 * Data relating to the account action email. 4546 * 4547 * @type WP_User_Request $request User request object. 4548 * @type string $message_recipient The address that the email will be sent to. Defaults 4549 * to the value of `$request->email`, but can be changed 4550 * by the `user_erasure_fulfillment_email_to` filter. 4551 * @type string $privacy_policy_url Privacy policy URL. 4552 * @type string $sitename The site name sending the mail. 4553 * @type string $siteurl The site URL sending the mail. 4554 * } 4555 */ 4556 $headers = apply_filters( 'user_erasure_fulfillment_email_headers', $headers, $subject, $content, $request_id, $email_data ); 4557 4558 $email_sent = wp_mail( $user_email, $subject, $content, $headers ); 4559 4560 if ( $switched_locale ) { 4561 restore_previous_locale(); 4562 } 4563 4564 if ( $email_sent ) { 4565 update_post_meta( $request_id, '_wp_user_notified', true ); 4566 } 4567 } 4568 4569 /** 4570 * Returns request confirmation message HTML. 4571 * 4572 * @since 4.9.6 4573 * @access private 4574 * 4575 * @param int $request_id The request ID being confirmed. 4576 * @return string The confirmation message. 4577 */ 4578 function _wp_privacy_account_request_confirmed_message( $request_id ) { 4579 $request = wp_get_user_request( $request_id ); 4580 4581 $message = '<p class="success">' . __( 'Action has been confirmed.' ) . '</p>'; 4582 $message .= '<p>' . __( 'The site administrator has been notified and will fulfill your request as soon as possible.' ) . '</p>'; 4583 4584 if ( $request && in_array( $request->action_name, _wp_privacy_action_request_types(), true ) ) { 4585 if ( 'export_personal_data' === $request->action_name ) { 4586 $message = '<p class="success">' . __( 'Thanks for confirming your export request.' ) . '</p>'; 4587 $message .= '<p>' . __( 'The site administrator has been notified. You will receive a link to download your export via email when they fulfill your request.' ) . '</p>'; 4588 } elseif ( 'remove_personal_data' === $request->action_name ) { 4589 $message = '<p class="success">' . __( 'Thanks for confirming your erasure request.' ) . '</p>'; 4590 $message .= '<p>' . __( 'The site administrator has been notified. You will receive an email confirmation when they erase your data.' ) . '</p>'; 4591 } 4592 } 4593 4594 /** 4595 * Filters the message displayed to a user when they confirm a data request. 4596 * 4597 * @since 4.9.6 4598 * 4599 * @param string $message The message to the user. 4600 * @param int $request_id The ID of the request being confirmed. 4601 */ 4602 $message = apply_filters( 'user_request_action_confirmed_message', $message, $request_id ); 4603 4604 return $message; 4605 } 4606 4607 /** 4608 * Creates and logs a user request to perform a specific action. 4609 * 4610 * Requests are stored inside a post type named `user_request` since they can apply to both 4611 * users on the site, or guests without a user account. 4612 * 4613 * @since 4.9.6 4614 * @since 5.7.0 Added the `$status` parameter. 4615 * 4616 * @param string $email_address User email address. This can be the address of a registered 4617 * or non-registered user. 4618 * @param string $action_name Name of the action that is being confirmed. Required. 4619 * @param array $request_data Misc data you want to send with the verification request and pass 4620 * to the actions once the request is confirmed. 4621 * @param string $status Optional request status (pending or confirmed). Default 'pending'. 4622 * @return int|WP_Error Returns the request ID if successful, or a WP_Error object on failure. 4623 */ 4624 function wp_create_user_request( $email_address = '', $action_name = '', $request_data = array(), $status = 'pending' ) { 4625 $email_address = sanitize_email( $email_address ); 4626 $action_name = sanitize_key( $action_name ); 4627 4628 if ( ! is_email( $email_address ) ) { 4629 return new WP_Error( 'invalid_email', __( 'Invalid email address.' ) ); 4630 } 4631 4632 if ( ! in_array( $action_name, _wp_privacy_action_request_types(), true ) ) { 4633 return new WP_Error( 'invalid_action', __( 'Invalid action name.' ) ); 4634 } 4635 4636 if ( ! in_array( $status, array( 'pending', 'confirmed' ), true ) ) { 4637 return new WP_Error( 'invalid_status', __( 'Invalid request status.' ) ); 4638 } 4639 4640 $user = get_user_by( 'email', $email_address ); 4641 $user_id = $user && ! is_wp_error( $user ) ? $user->ID : 0; 4642 4643 // Check for duplicates. 4644 $requests_query = new WP_Query( 4645 array( 4646 'post_type' => 'user_request', 4647 'post_name__in' => array( $action_name ), // Action name stored in post_name column. 4648 'title' => $email_address, // Email address stored in post_title column. 4649 'post_status' => array( 4650 'request-pending', 4651 'request-confirmed', 4652 ), 4653 'fields' => 'ids', 4654 ) 4655 ); 4656 4657 if ( $requests_query->found_posts ) { 4658 return new WP_Error( 'duplicate_request', __( 'An incomplete personal data request for this email address already exists.' ) ); 4659 } 4660 4661 $request_id = wp_insert_post( 4662 array( 4663 'post_author' => $user_id, 4664 'post_name' => $action_name, 4665 'post_title' => $email_address, 4666 'post_content' => wp_json_encode( $request_data ), 4667 'post_status' => 'request-' . $status, 4668 'post_type' => 'user_request', 4669 'post_date' => current_time( 'mysql', false ), 4670 'post_date_gmt' => current_time( 'mysql', true ), 4671 ), 4672 true 4673 ); 4674 4675 return $request_id; 4676 } 4677 4678 /** 4679 * Gets action description from the name and return a string. 4680 * 4681 * @since 4.9.6 4682 * 4683 * @param string $action_name Action name of the request. 4684 * @return string Human readable action name. 4685 */ 4686 function wp_user_request_action_description( $action_name ) { 4687 switch ( $action_name ) { 4688 case 'export_personal_data': 4689 $description = __( 'Export Personal Data' ); 4690 break; 4691 case 'remove_personal_data': 4692 $description = __( 'Erase Personal Data' ); 4693 break; 4694 default: 4695 /* translators: %s: Action name. */ 4696 $description = sprintf( __( 'Confirm the "%s" action' ), $action_name ); 4697 break; 4698 } 4699 4700 /** 4701 * Filters the user action description. 4702 * 4703 * @since 4.9.6 4704 * 4705 * @param string $description The default description. 4706 * @param string $action_name The name of the request. 4707 */ 4708 return apply_filters( 'user_request_action_description', $description, $action_name ); 4709 } 4710 4711 /** 4712 * Send a confirmation request email to confirm an action. 4713 * 4714 * If the request is not already pending, it will be updated. 4715 * 4716 * @since 4.9.6 4717 * 4718 * @param string $request_id ID of the request created via wp_create_user_request(). 4719 * @return true|WP_Error True on success, `WP_Error` on failure. 4720 */ 4721 function wp_send_user_request( $request_id ) { 4722 $request_id = absint( $request_id ); 4723 $request = wp_get_user_request( $request_id ); 4724 4725 if ( ! $request ) { 4726 return new WP_Error( 'invalid_request', __( 'Invalid personal data request.' ) ); 4727 } 4728 4729 // Localize message content for user; fallback to site default for visitors. 4730 if ( ! empty( $request->user_id ) ) { 4731 $switched_locale = switch_to_user_locale( $request->user_id ); 4732 } else { 4733 $switched_locale = switch_to_locale( get_locale() ); 4734 } 4735 4736 $email_data = array( 4737 'request' => $request, 4738 'email' => $request->email, 4739 'description' => wp_user_request_action_description( $request->action_name ), 4740 'confirm_url' => add_query_arg( 4741 array( 4742 'action' => 'confirmaction', 4743 'request_id' => $request_id, 4744 'confirm_key' => wp_generate_user_request_key( $request_id ), 4745 ), 4746 wp_login_url() 4747 ), 4748 'sitename' => wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ), 4749 'siteurl' => home_url(), 4750 ); 4751 4752 /* translators: Confirm privacy data request notification email subject. 1: Site title, 2: Name of the action. */ 4753 $subject = sprintf( __( '[%1$s] Confirm Action: %2$s' ), $email_data['sitename'], $email_data['description'] ); 4754 4755 /** 4756 * Filters the subject of the email sent when an account action is attempted. 4757 * 4758 * @since 4.9.6 4759 * 4760 * @param string $subject The email subject. 4761 * @param string $sitename The name of the site. 4762 * @param array $email_data { 4763 * Data relating to the account action email. 4764 * 4765 * @type WP_User_Request $request User request object. 4766 * @type string $email The email address this is being sent to. 4767 * @type string $description Description of the action being performed so the user knows what the email is for. 4768 * @type string $confirm_url The link to click on to confirm the account action. 4769 * @type string $sitename The site name sending the mail. 4770 * @type string $siteurl The site URL sending the mail. 4771 * } 4772 */ 4773 $subject = apply_filters( 'user_request_action_email_subject', $subject, $email_data['sitename'], $email_data ); 4774 4775 /* translators: Do not translate DESCRIPTION, CONFIRM_URL, SITENAME, SITEURL: those are placeholders. */ 4776 $content = __( 4777 'Howdy, 4778 4779 A request has been made to perform the following action on your account: 4780 4781 ###DESCRIPTION### 4782 4783 To confirm this, please click on the following link: 4784 ###CONFIRM_URL### 4785 4786 You can safely ignore and delete this email if you do not want to 4787 take this action. 4788 4789 Regards, 4790 All at ###SITENAME### 4791 ###SITEURL###' 4792 ); 4793 4794 /** 4795 * Filters the text of the email sent when an account action is attempted. 4796 * 4797 * The following strings have a special meaning and will get replaced dynamically: 4798 * 4799 * ###DESCRIPTION### Description of the action being performed so the user knows what the email is for. 4800 * ###CONFIRM_URL### The link to click on to confirm the account action. 4801 * ###SITENAME### The name of the site. 4802 * ###SITEURL### The URL to the site. 4803 * 4804 * @since 4.9.6 4805 * 4806 * @param string $content Text in the email. 4807 * @param array $email_data { 4808 * Data relating to the account action email. 4809 * 4810 * @type WP_User_Request $request User request object. 4811 * @type string $email The email address this is being sent to. 4812 * @type string $description Description of the action being performed so the user knows what the email is for. 4813 * @type string $confirm_url The link to click on to confirm the account action. 4814 * @type string $sitename The site name sending the mail. 4815 * @type string $siteurl The site URL sending the mail. 4816 * } 4817 */ 4818 $content = apply_filters( 'user_request_action_email_content', $content, $email_data ); 4819 4820 $content = str_replace( '###DESCRIPTION###', $email_data['description'], $content ); 4821 $content = str_replace( '###CONFIRM_URL###', sanitize_url( $email_data['confirm_url'] ), $content ); 4822 $content = str_replace( '###EMAIL###', $email_data['email'], $content ); 4823 $content = str_replace( '###SITENAME###', $email_data['sitename'], $content ); 4824 $content = str_replace( '###SITEURL###', sanitize_url( $email_data['siteurl'] ), $content ); 4825 4826 $headers = ''; 4827 4828 /** 4829 * Filters the headers of the email sent when an account action is attempted. 4830 * 4831 * @since 5.4.0 4832 * 4833 * @param string|array $headers The email headers. 4834 * @param string $subject The email subject. 4835 * @param string $content The email content. 4836 * @param int $request_id The request ID. 4837 * @param array $email_data { 4838 * Data relating to the account action email. 4839 * 4840 * @type WP_User_Request $request User request object. 4841 * @type string $email The email address this is being sent to. 4842 * @type string $description Description of the action being performed so the user knows what the email is for. 4843 * @type string $confirm_url The link to click on to confirm the account action. 4844 * @type string $sitename The site name sending the mail. 4845 * @type string $siteurl The site URL sending the mail. 4846 * } 4847 */ 4848 $headers = apply_filters( 'user_request_action_email_headers', $headers, $subject, $content, $request_id, $email_data ); 4849 4850 $email_sent = wp_mail( $email_data['email'], $subject, $content, $headers ); 4851 4852 if ( $switched_locale ) { 4853 restore_previous_locale(); 4854 } 4855 4856 if ( ! $email_sent ) { 4857 return new WP_Error( 'privacy_email_error', __( 'Unable to send personal data export confirmation email.' ) ); 4858 } 4859 4860 return true; 4861 } 4862 4863 /** 4864 * Returns a confirmation key for a user action and stores the hashed version for future comparison. 4865 * 4866 * @since 4.9.6 4867 * 4868 * @global PasswordHash $wp_hasher Portable PHP password hashing framework instance. 4869 * 4870 * @param int $request_id Request ID. 4871 * @return string Confirmation key. 4872 */ 4873 function wp_generate_user_request_key( $request_id ) { 4874 global $wp_hasher; 4875 4876 // Generate something random for a confirmation key. 4877 $key = wp_generate_password( 20, false ); 4878 4879 // Return the key, hashed. 4880 if ( empty( $wp_hasher ) ) { 4881 require_once ABSPATH . WPINC . '/class-phpass.php'; 4882 $wp_hasher = new PasswordHash( 8, true ); 4883 } 4884 4885 wp_update_post( 4886 array( 4887 'ID' => $request_id, 4888 'post_status' => 'request-pending', 4889 'post_password' => $wp_hasher->HashPassword( $key ), 4890 ) 4891 ); 4892 4893 return $key; 4894 } 4895 4896 /** 4897 * Validates a user request by comparing the key with the request's key. 4898 * 4899 * @since 4.9.6 4900 * 4901 * @global PasswordHash $wp_hasher Portable PHP password hashing framework instance. 4902 * 4903 * @param string $request_id ID of the request being confirmed. 4904 * @param string $key Provided key to validate. 4905 * @return true|WP_Error True on success, WP_Error on failure. 4906 */ 4907 function wp_validate_user_request_key( $request_id, $key ) { 4908 global $wp_hasher; 4909 4910 $request_id = absint( $request_id ); 4911 $request = wp_get_user_request( $request_id ); 4912 $saved_key = $request->confirm_key; 4913 $key_request_time = $request->modified_timestamp; 4914 4915 if ( ! $request || ! $saved_key || ! $key_request_time ) { 4916 return new WP_Error( 'invalid_request', __( 'Invalid personal data request.' ) ); 4917 } 4918 4919 if ( ! in_array( $request->status, array( 'request-pending', 'request-failed' ), true ) ) { 4920 return new WP_Error( 'expired_request', __( 'This personal data request has expired.' ) ); 4921 } 4922 4923 if ( empty( $key ) ) { 4924 return new WP_Error( 'missing_key', __( 'The confirmation key is missing from this personal data request.' ) ); 4925 } 4926 4927 if ( empty( $wp_hasher ) ) { 4928 require_once ABSPATH . WPINC . '/class-phpass.php'; 4929 $wp_hasher = new PasswordHash( 8, true ); 4930 } 4931 4932 /** 4933 * Filters the expiration time of confirm keys. 4934 * 4935 * @since 4.9.6 4936 * 4937 * @param int $expiration The expiration time in seconds. 4938 */ 4939 $expiration_duration = (int) apply_filters( 'user_request_key_expiration', DAY_IN_SECONDS ); 4940 $expiration_time = $key_request_time + $expiration_duration; 4941 4942 if ( ! $wp_hasher->CheckPassword( $key, $saved_key ) ) { 4943 return new WP_Error( 'invalid_key', __( 'The confirmation key is invalid for this personal data request.' ) ); 4944 } 4945 4946 if ( ! $expiration_time || time() > $expiration_time ) { 4947 return new WP_Error( 'expired_key', __( 'The confirmation key has expired for this personal data request.' ) ); 4948 } 4949 4950 return true; 4951 } 4952 4953 /** 4954 * Returns the user request object for the specified request ID. 4955 * 4956 * @since 4.9.6 4957 * 4958 * @param int $request_id The ID of the user request. 4959 * @return WP_User_Request|false 4960 */ 4961 function wp_get_user_request( $request_id ) { 4962 $request_id = absint( $request_id ); 4963 $post = get_post( $request_id ); 4964 4965 if ( ! $post || 'user_request' !== $post->post_type ) { 4966 return false; 4967 } 4968 4969 return new WP_User_Request( $post ); 4970 } 4971 4972 /** 4973 * Checks if Application Passwords is supported. 4974 * 4975 * Application Passwords is supported only by sites using SSL or local environments 4976 * but may be made available using the {@see 'wp_is_application_passwords_available'} filter. 4977 * 4978 * @since 5.9.0 4979 * 4980 * @return bool 4981 */ 4982 function wp_is_application_passwords_supported() { 4983 return is_ssl() || 'local' === wp_get_environment_type(); 4984 } 4985 4986 /** 4987 * Checks if Application Passwords is globally available. 4988 * 4989 * By default, Application Passwords is available to all sites using SSL or to local environments. 4990 * Use the {@see 'wp_is_application_passwords_available'} filter to adjust its availability. 4991 * 4992 * @since 5.6.0 4993 * 4994 * @return bool 4995 */ 4996 function wp_is_application_passwords_available() { 4997 /** 4998 * Filters whether Application Passwords is available. 4999 * 5000 * @since 5.6.0 5001 * 5002 * @param bool $available True if available, false otherwise. 5003 */ 5004 return apply_filters( 'wp_is_application_passwords_available', wp_is_application_passwords_supported() ); 5005 } 5006 5007 /** 5008 * Checks if Application Passwords is available for a specific user. 5009 * 5010 * By default all users can use Application Passwords. Use {@see 'wp_is_application_passwords_available_for_user'} 5011 * to restrict availability to certain users. 5012 * 5013 * @since 5.6.0 5014 * 5015 * @param int|WP_User $user The user to check. 5016 * @return bool 5017 */ 5018 function wp_is_application_passwords_available_for_user( $user ) { 5019 if ( ! wp_is_application_passwords_available() ) { 5020 return false; 5021 } 5022 5023 if ( ! is_object( $user ) ) { 5024 $user = get_userdata( $user ); 5025 } 5026 5027 if ( ! $user || ! $user->exists() ) { 5028 return false; 5029 } 5030 5031 /** 5032 * Filters whether Application Passwords is available for a specific user. 5033 * 5034 * @since 5.6.0 5035 * 5036 * @param bool $available True if available, false otherwise. 5037 * @param WP_User $user The user to check. 5038 */ 5039 return apply_filters( 'wp_is_application_passwords_available_for_user', true, $user ); 5040 } 5041 5042 /** 5043 * Registers the user meta property for persisted preferences. 5044 * 5045 * This property is used to store user preferences across page reloads and is 5046 * currently used by the block editor for preferences like 'fullscreenMode' and 5047 * 'fixedToolbar'. 5048 * 5049 * @since 6.1.0 5050 * @access private 5051 * 5052 * @global wpdb $wpdb WordPress database abstraction object. 5053 */ 5054 function wp_register_persisted_preferences_meta() { 5055 /* 5056 * Create a meta key that incorporates the blog prefix so that each site 5057 * on a multisite can have distinct user preferences. 5058 */ 5059 global $wpdb; 5060 $meta_key = $wpdb->get_blog_prefix() . 'persisted_preferences'; 5061 5062 register_meta( 5063 'user', 5064 $meta_key, 5065 array( 5066 'type' => 'object', 5067 'single' => true, 5068 'show_in_rest' => array( 5069 'name' => 'persisted_preferences', 5070 'type' => 'object', 5071 'schema' => array( 5072 'type' => 'object', 5073 'context' => array( 'edit' ), 5074 'properties' => array( 5075 '_modified' => array( 5076 'description' => __( 'The date and time the preferences were updated.' ), 5077 'type' => 'string', 5078 'format' => 'date-time', 5079 'readonly' => false, 5080 ), 5081 ), 5082 'additionalProperties' => true, 5083 ), 5084 ), 5085 ) 5086 ); 5087 } 5088 5089 /** 5090 * Sets the last changed time for the 'users' cache group. 5091 * 5092 * @since 6.3.0 5093 */ 5094 function wp_cache_set_users_last_changed() { 5095 wp_cache_set_last_changed( 'users' ); 5096 } 5097 5098 /** 5099 * Checks if password reset is allowed for a specific user. 5100 * 5101 * @since 6.3.0 5102 * 5103 * @param int|WP_User $user The user to check. 5104 * @return bool|WP_Error True if allowed, false or WP_Error otherwise. 5105 */ 5106 function wp_is_password_reset_allowed_for_user( $user ) { 5107 if ( ! is_object( $user ) ) { 5108 $user = get_userdata( $user ); 5109 } 5110 5111 if ( ! $user || ! $user->exists() ) { 5112 return false; 5113 } 5114 $allow = true; 5115 if ( is_multisite() && is_user_spammy( $user ) ) { 5116 $allow = false; 5117 } 5118 5119 /** 5120 * Filters whether to allow a password to be reset. 5121 * 5122 * @since 2.7.0 5123 * 5124 * @param bool $allow Whether to allow the password to be reset. Default true. 5125 * @param int $user_id The ID of the user attempting to reset a password. 5126 */ 5127 return apply_filters( 'allow_password_reset', $allow, $user->ID ); 5128 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated : Fri Jul 26 08:20:01 2024 | Cross-referenced by PHPXref |