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