Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 2 additions & 11 deletions src/wp-admin/user-edit.php
Original file line number Diff line number Diff line change
Expand Up @@ -102,21 +102,12 @@

// Execute confirmed email change. See send_confirmation_on_profile_email().
if ( IS_PROFILE_PAGE && isset( $_GET['newuseremail'] ) && $current_user->ID ) {
$new_email = get_user_meta( $current_user->ID, '_new_email', true );
if ( $new_email && hash_equals( $new_email['hash'], $_GET['newuseremail'] ) ) {
$user = new stdClass();
$user->ID = $current_user->ID;
$user->user_email = esc_html( trim( $new_email['newemail'] ) );
if ( is_multisite() && $wpdb->get_var( $wpdb->prepare( "SELECT user_login FROM {$wpdb->signups} WHERE user_login = %s", $current_user->user_login ) ) ) {
$wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->signups} SET user_email = %s WHERE user_login = %s", $user->user_email, $current_user->user_login ) );
}
wp_update_user( $user );
delete_user_meta( $current_user->ID, '_new_email' );
if ( send_user_email_change_confirmation_process( $current_user->ID, $_GET['newuseremail'] ) ) {
wp_redirect( add_query_arg( array( 'updated' => 'true' ), self_admin_url( 'profile.php' ) ) );
die();
} else {
wp_redirect( add_query_arg( array( 'error' => 'new-email' ), self_admin_url( 'profile.php' ) ) );
}
die();
Comment on lines +105 to +110

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worth noting that this code branch is no longer used, and is here primarily for existing emails that have been sent.

} elseif ( IS_PROFILE_PAGE && ! empty( $_GET['dismiss'] ) && $current_user->ID . '_new_email' === $_GET['dismiss'] ) {
check_admin_referer( 'dismiss-' . $current_user->ID . '_new_email' );
delete_user_meta( $current_user->ID, '_new_email' );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -762,6 +762,14 @@ public function update_item( $request ) {
}
}

// Maybe send the change email confirmation.
if ( ! empty( $request['email'] ) ) {
$email_sent = send_user_email_change_confirmation_email( $user, $request['email'] );
if ( true === $email_sent ) {
unset( $request['email'] );
}
}

$user = $this->prepare_item_for_database( $request );

// Ensure we're operating on the same user we already checked.
Expand Down
201 changes: 140 additions & 61 deletions src/wp-includes/user.php
Original file line number Diff line number Diff line change
Expand Up @@ -3629,53 +3629,101 @@ function _wp_get_current_user() {
function send_confirmation_on_profile_email() {
global $errors;

$current_user = wp_get_current_user();
if ( ! is_object( $errors ) ) {
$errors = new WP_Error();
}

if ( $current_user->ID != $_POST['user_id'] ) {
return false;
}
$user = get_user_by( 'id', $_POST['user_id'] );
$email_sent = send_user_email_change_confirmation_email( $user, $_POST['email'] );

if ( $current_user->user_email != $_POST['email'] ) {
if ( ! is_email( $_POST['email'] ) ) {
if ( is_wp_error( $email_sent ) ) {
// WP_Error::copy_errors() with the addition of adding data.
foreach ( $email_sent->get_error_codes() as $code ) {
$errors->add(
'user_email',
__( '<strong>Error:</strong> The email address is not correct.' ),
$code,
$email_sent->get_error_message( $code ),
array(
'form-field' => 'email',
)
);

return;
}
}

if ( email_exists( $_POST['email'] ) ) {
$errors->add(
'user_email',
__( '<strong>Error:</strong> The email address is already used.' ),
array(
'form-field' => 'email',
)
);
delete_user_meta( $current_user->ID, '_new_email' );
if ( true === $email_sent ) {
$_POST['email'] = $user->user_email;
}
}

return;
}
/**
* Send the 'confirm your email' email.
*
* @since x.x
*
* @param WP_User $user The user to act upon.
* @param string $email The new email address.
* @return null|true|WP_Error true if email sent, WP_Error on error, and null otherwise.
*/
function send_user_email_change_confirmation_email( $user, $email ) {
if ( $user->user_email == $email ) {
return;
}

$hash = md5( $_POST['email'] . time() . wp_rand() );
$new_user_email = array(
'hash' => $hash,
'newemail' => $_POST['email'],
if ( ! is_email( $email ) ) {
return new WP_Error(
'user_email',
__( '<strong>Error:</strong> The email address is not correct.' )
);
}

if ( email_exists( $email ) ) {
delete_user_meta( $user->ID, '_new_email' );

return new WP_Error(
'user_email',
__( '<strong>Error:</strong> The email address is already used.' )
);
update_user_meta( $current_user->ID, '_new_email', $new_user_email );
}

// The email is only sent if a user changes their own email.
$should_send_email_for_change = ( get_current_user_id() === $user->ID );

/**
* Filters whether a 'confirm your email address' email should be sent.
* If false is returned, the change is made immediately.
*
* @since x.x
*
* @param bool $should_send_email_for_change Whether to use an email confirmation.
* @param WP_User $user The user having their email changed.
* @param string $email The new email address.
*/
$should_send_email_for_change = apply_filters( 'should_send_email_for_email_change', $should_send_email_for_change, $user, $email );

if ( ! $should_send_email_for_change ) {
return;
}

$hash = md5( $email . time() . wp_rand() );
$new_user_email = array(
'hash' => $hash,
'newemail' => $email,
);
update_user_meta( $user->ID, '_new_email', $new_user_email );

$sitename = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES );
$confirm_url = add_query_arg(
array(
'action' => 'confirmemail',
'id' => $user->ID,
'hash' => $hash,
),
wp_login_url()
);

/* translators: Do not translate USERNAME, ADMIN_URL, EMAIL, SITENAME, SITEURL: those are placeholders. */
$email_text = __(
'Howdy ###USERNAME###,
$sitename = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES );

/* translators: Do not translate USERNAME, ADMIN_URL, EMAIL, SITENAME, SITEURL: those are placeholders. */
$email_text = __(
'Howdy ###USERNAME###,

You recently requested to have the email address on your account changed.

Expand All @@ -3690,42 +3738,73 @@ function send_confirmation_on_profile_email() {
Regards,
All at ###SITENAME###
###SITEURL###'
);
);

/**
* Filters the text of the email sent when a change of user email address is attempted.
*
* The following strings have a special meaning and will get replaced dynamically:
* - ###USERNAME### The current user's username.
* - ###ADMIN_URL### The link to click on to confirm the email change.
* - ###EMAIL### The new email.
* - ###SITENAME### The name of the site.
* - ###SITEURL### The URL to the site.
*
* @since MU (3.0.0)
* @since 4.9.0 This filter is no longer Multisite specific.
*
* @param string $email_text Text in the email.
* @param array $new_user_email {
* Data relating to the new user email address.
*
* @type string $hash The secure hash used in the confirmation link URL.
* @type string $newemail The proposed new email address.
* }
*/
$content = apply_filters( 'new_user_email_content', $email_text, $new_user_email );
/**
* Filters the text of the email sent when a change of user email address is attempted.
*
* The following strings have a special meaning and will get replaced dynamically:
* - ###USERNAME### The current user's username.
* - ###ADMIN_URL### The link to click on to confirm the email change.
* - ###EMAIL### The new email.
* - ###SITENAME### The name of the site.
* - ###SITEURL### The URL to the site.
*
* @since MU (3.0.0)
* @since 4.9.0 This filter is no longer Multisite specific.
*
* @param string $email_text Text in the email.
* @param array $new_user_email {
* Data relating to the new user email address.
*
* @type string $hash The secure hash used in the confirmation link URL.
* @type string $newemail The proposed new email address.
* }
*/
$content = apply_filters( 'new_user_email_content', $email_text, $new_user_email );

$content = str_replace( '###USERNAME###', $current_user->user_login, $content );
$content = str_replace( '###ADMIN_URL###', esc_url( admin_url( 'profile.php?newuseremail=' . $hash ) ), $content );
$content = str_replace( '###EMAIL###', $_POST['email'], $content );
$content = str_replace( '###SITENAME###', $sitename, $content );
$content = str_replace( '###SITEURL###', home_url(), $content );
$content = str_replace( '###USERNAME###', $user->user_login, $content );
$content = str_replace( '###ADMIN_URL###', esc_url( $confirm_url ), $content );
$content = str_replace( '###EMAIL###', $email, $content );
$content = str_replace( '###SITENAME###', $sitename, $content );
$content = str_replace( '###SITEURL###', home_url(), $content );

/* translators: New email address notification email subject. %s: Site title. */
wp_mail( $_POST['email'], sprintf( __( '[%s] Email Change Request' ), $sitename ), $content );
/* translators: New email address notification email subject. %s: Site title. */
wp_mail( $email, sprintf( __( '[%s] Email Change Request' ), $sitename ), $content );

return true;
}

$_POST['email'] = $current_user->user_email;
/**
* Process the confirmation of an email change request.
*
* @since x.x
*
* @param int $user_id The User ID being acted upon.
* @param string $email_hash The email hash of the request
*
* @return bool Whether or not the change succeeded.
*/
function send_user_email_change_confirmation_process( $user_id, $email_hash ) {

$new_email = get_user_meta( $user_id, '_new_email', true );
if ( ! $new_email || ! hash_equals( $new_email['hash'], $email_hash ) ) {
return false;
}

$the_user = get_user_by( 'id', $user_id );
$user = new stdClass();
$user->ID = $the_user->ID;
$user->user_email = esc_html( trim( $new_email['newemail'] ) );
if ( is_multisite() && $wpdb->get_var( $wpdb->prepare( "SELECT user_login FROM {$wpdb->signups} WHERE user_login = %s", $the_user->user_login ) ) ) {
$wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->signups} SET user_email = %s WHERE user_login = %s", $user->user_email, $the_user->user_login ) );
}

wp_update_user( $user );

delete_user_meta( $user->ID, '_new_email' );

return true;
}

/**
Expand Down
39 changes: 39 additions & 0 deletions src/wp-login.php
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,7 @@ function wp_login_viewport_meta() {
'register',
'checkemail',
'confirmaction',
'confirmemail',
'login',
WP_Recovery_Mode_Link_Service::LOGIN_ACTION_ENTERED,
);
Expand Down Expand Up @@ -1154,6 +1155,44 @@ function wp_login_viewport_meta() {
login_footer();
break;

case 'confirmemail':
if ( ! isset( $_GET['id'], $_GET['hash'] ) ) {
wp_die( __( 'Missing or invalid key.' ) );
}

if ( ! is_user_logged_in() ) {
wp_safe_redirect( wp_login_url() );
exit;
}

$user_id = wp_unslash( $_GET['id'] );
$email_key = wp_unslash( $_GET['hash'] );

if ( ! current_user_can( 'edit_user', $user_id ) ) {
wp_die( __( 'Missing or invalid key.' ) );
}

$updated = send_user_email_change_confirmation_process( $user_id, $email_key );
if ( ! $updated ) {
wp_die( __( 'Missing or invalid key.' ) );
}

/**
* Fires an action hook when the account email has been confirmed by the user.
*
* @since x.x
*
* @param int $user_id User ID.
*/
do_action( 'user_email_confirmed', $user_id );

login_header(
__( 'Confirm your email' ),
'<p class="success">' . __( 'Your email has been confirmed.' ) . '</p>'
);
login_footer();
break;

case 'confirmaction':
if ( ! isset( $_GET['request_id'] ) ) {
wp_die( __( 'Missing request ID.' ) );
Expand Down
Loading