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