Email user when password is reset by admin

Here’s one way to add this feature using the following flowline :

The admin updates the user option page:

   -> edit_user_profile_update or personal_options_update hooks activated
   -> edit_user() function is called
   -> wp_update_user() function is called within edit_user()
   -> wp_insert_user() function is called within wp_update_user()
   -> profile_update hook activated within wp_insert_user() 
      for user updates, not user inserts
   -> wp_redirect() function called on successful user updates
   -> wp_redirect filter activated
   -> The page reloads
   -> admin_notices hook activated

Step #1 – The HTML checkbox:

First we add the Send a notification checkbox above the password text fields and it’s only visible to the administrator:

Checkbox

by hijacking the show_password_fields filter:

add_filter( 'show_password_fields', 'wpse_notification_html' );

function wpse_notification_html( $show )
{
    if( current_user_can( 'manage_options' ) ):
?>
    <tr>
        <th scope="row">
            <label for="wpse_send_notification"><?php _e('Send a notification?') ?></label>
        </th>
    <td>
            <label for="wpse_send_notification">
                <input type="checkbox" name="wpse_send_notification" id="wpse_send_notification" value="1" />
                <?php _e( 'Send an email to user and notify that the password has changed.' ); ?>
            </label>
        </td>
    </tr>
<?php
    endif;
    return $show;
}

Step #2 – The main controller:

Next we want to hook into the profile_update from the user options page:

add_action( 'edit_user_profile_update', 'wpse_user_update' );
add_action( 'personal_options_update',  'wpse_user_update' );

function wpse_user_update( $user_id )
{
    if( current_user_can( 'manage_options' ) )
        add_action( 'profile_update', 'wpse_controller', 10, 2 );
}

where the main logic is in the wpse_controller() function:

function wpse_controller( $user_id, $old_user_data )
{
    // Input:
    $pass1  = filter_input( INPUT_POST, 'pass1' );
    $pass2  = filter_input( INPUT_POST, 'pass2' );
    $send   = filter_input( INPUT_POST, 'wpse_send_notification', FILTER_SANITIZE_NUMBER_INT );

    // Run this action only once:
    remove_action( current_action(), __FUNCTION__ );

    // Send the notification:
    if( 1 == $send )
    {
       if( ! empty( $pass1 )
           && $pass1 === $pass2
           && sanitize_text_field( $pass1 ) === $pass1
       ):
            if( wpse_user_password_notification( $user_id, wp_unslash( sanitize_text_field( $pass1 ) ) ) )
                add_filter( 'wp_redirect', 'wpse_redirect_notification_success' );
            else
                add_filter( 'wp_redirect', 'wpse_redirect_notification_error' );
       else:
                add_filter( 'wp_redirect', 'wpse_redirect_pass_validation_error' );
       endif;
    }
}

because then all the validations have taken place.

It’s a little bit tricky to display error/success messages, because WordPress uses a redirect when the user options page is updated. Therefore we use a trick with the wp_redirect filter to add the relevant query variable so we can determine the message from the admin_notices hook:

function wpse_redirect_notification_success( $location )
{
    return add_query_arg( 'wpse_notification', 'mail_success', $location );
}

function wpse_redirect_notification_error( $location )
{
    return add_query_arg( 'wpse_notification', 'mail_error', $location );
}

function wpse_redirect_pass_validation_error( $location )
{
    return add_query_arg( 'wpse_notification', 'pass_validation_error', $location );
}

Step #3 – Send the email:

We use a modification of the core wp_new_user_notification() function to send the email:

function wpse_user_password_notification( $user_id, $plaintext_pass="" )
{
    if ( empty( $plaintext_pass ) )
       return false;

    $user = get_userdata( $user_id );
    $blogname = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES );
    $message  = sprintf( __( 'Username: %s' ),     $user->user_login ) . "\r\n";
    $message .= sprintf( __( 'New Password: %s' ), $plaintext_pass   ) . "\r\n";
    $message .= wp_login_url() . "\r\n";
    return wp_mail( $user->user_email, sprintf(__('[%s] Your username and new password'), $blogname), $message );
}

Step #4 – Admin notices for success or errors:

We use the following admin notices, determined by the wp_notices query variable added in the wp_redirect filter:

Success
Error 1
Error 2

add_action( 'admin_notices', 'wpse_admin_notices' );

function wpse_admin_notices()
{
    $status = filter_input( INPUT_GET, 'wpse_notification', FILTER_SANITIZE_STRING );

    switch ( $status )
    {
        case 'mail_success':
            ?><div id="message" class="updated"><p><strong>Notification Sent!</strong>: Notification email successfully sent to the user</p></div><?php
            break;  
        case 'mail_error':
            ?><div class="error"><p><strong>ERROR</strong>: Notification email not sent to the user</p></div><?php
            break;
        case 'pass_validation_error':
            ?><div class="error"><p><strong>ERROR</strong>: Notification email not sent to the user, because of symbol chars in the password </p></div><?php
            break;
    } // end switch
}

How to sanitize the password?

Notice that I use the sanitize_text_field() to sanitize the password in the email. I’m not sure what’s the best way to do it. At least I didn’t want to send it raw in the email. That’s the reason for this extra error message:

Error 1

just so we can handle the case where the password contains some characters that get stripped by the sanitize_text_field() function. Any other ideas are welcomed.

PS: This is experimental and might need some adjustments. The code above is all procedural and without any anonymous functions. It might get much simpler by smearing some OOP butter on it 😉

Leave a Comment