OVERVIEW
WordPress allows you to add custom security roles and capabilities for users. Unfortunately, the code that updates a user profile deletes all custom roles assigned to a user when saving a user profile. The following example code shows a work-around that restores custom roles after a user profile update.
SAVING CUSTOM ROLES DURING A USER UPDATE
The code that processes a form submission from the User Profile editor page only allows a single user role to be passed to the wp_update_user() function. Every time a user with more than one role save any user profile changes, WordPress deletes all but the primary role.
The work-around uses three hooks to capture and restore custom roles. The ‘edit_user_profile’ and ‘show_user_profile’ action hooks are called when the User Profile editor page is being generated for display. The ‘edit_user_profile’ action fires when someone is editing a user profile that is not their own. The ‘show_user_profile’ action fires when you are viewing your own profile.
The third action hook, ‘user_profile_update_errors’, fires inside WordPress’ edit_user() function just before the list of user roles are overwritten. We will use this to capture and save the list of user roles.
Here is the sequence of events in a typical user profile update with the work-around:
- User Profile editor page starts to load.
- Work-around function user_profile_loading() is called from inside the /wp-admin/user-edit.php file when the User Profile editor form is loading. The user_profile_loading() function checks for a WordPress transient to see if the profile of the user being loaded was just updated. On the first time through, no transient data exist, so the User Profile editor form continues to load normally.
- When the Update button is clicked in the User Profile editor, the form is submitted for processing.
- Then the function edit_user() (located in /wp-admin/includes/user.php) calls the work-around function user_profile_updating(). The user_profile_updating() function saves a copy of all custom roles for the user and saved in a transient.
- WordPress finishes saving the user profile data, destroying our custom role in the process. WordPress then redirects the user back to the User Profile editor page to show the updated user data in the form.
- Work-around function user_profile_loading() executes again. Now, the function finds data in the transient, indicating an update to the user profile occurred. The transient data contains a copy of the user roles originally assigned to that user. The function restores all recognized custom roles and ignores the rest. The function then deletes the transient data for that user.
HOW TO PREVENT DELETION OF CUSTOM ROLES
This example code detects and restores a custom user role named ‘my_custom_role’ during a user profile update.
<?php
/** Add hooks to restore user roles after a user profile update */
add_action( 'edit_user_profile', 'user_profile_loading', 10, 1 );
add_action( 'show_user_profile', 'user_profile_loading', 10, 1 );
add_action( 'user_profile_update_errors', 'user_profile_updating', 10, 3 );
/**
* WordPress function wp_update_user() destroys all but
* one of the assigned user roles during a profile update.
* Restore user roles after profile update, if needed.
*
* This is called on first view of a user profile and
* also after a user profile is updated because of a
* redirect to the user profile view on successful update.
*
* @param WP_User $user WP_User object of user being displayed.
*/
function user_profile_loading( $user ) {
/**
* Get transient data on user profile changes.
*/
$trans = get_transient( 'saved_user_roles' );
if ( empty( $trans ) ) {
return; // No data to act on, return.
}
/**
* Make a copy of the User ID for efficiency.
*
* @var int $uid
*/
$uid = $user->ID;
if ( empty( $trans[ $uid ] ) ) {
return; // No transient data saved for this user, return.
}
/**
* Get a copy of user roles from transient array for efficiency.
*
* @var array $roles
*/
$roles = $trans[ $uid ];
/**
* RESTORE MISSING ROLE IF NEEDED.
*/
if ( in_array( 'my_custom_role', $roles ) ) {
$user->add_role( 'my_custom_role' );
}
/**
* Add more role checking and restoration here.
*/
/**
* Get the latest transient data in case it changed.
* Function is not atomic, try to be as close as possible.
*/
$trans = get_transient( 'saved_user_roles' );
/**
* Delete transient data for the user roles that were restored just now.
*/
unset( $trans[ $uid ] );
/**
* Delete transient if empty.
* Update if other data is still in it.
*/
if ( empty( $trans ) ) {
delete_transient( 'saved_user_roles' );
} else {
set_transient( 'saved_user_roles', $trans );
}
}
/**
* WordPress function wp_update_user() destroys all
* but one of the assigned user roles during a profile update.
* Save plugin user roles during user profile update.
* Restore after redirect back to user profile view on successful update.
*
* @param WP_Error $errors Collection of Errors
* @param boolean $update True if user profile updated, false if new user created.
* @param stdClass $user User profile data, not a WP_User object.
*/
function user_profile_updating( &$errors, $update, &$user ) {
/**
* If this is not an update, return.
*/
if ( !$update ) {
return;
}
/**
* If there are errors, don't get in the way, return.
*/
if ( $errors->has_errors() ) {
return;
}
/**
* Get the unmodified WP_User before it gets mangled.
*/
$uid = $user->ID;
$user_temp = new WP_User( $uid );
/**
* Get our transient user data.
*/
$trans = get_transient( 'saved_user_roles' );
if ( empty( $trans ) ) {
$trans = array(); // Make a new array if it doesn't exist.
}
/**
* Save the information we need to restore the user roles later.
*/
$trans[ $uid ] = $user_temp->roles;
/**
* Save the transient data.
*/
set_transient( 'saved_user_roles', $trans);
}
NOTE
There may be a remote possibility of saved custom roles transient data surviving forever since no expiration is specified. If this is a concern, you could add an expiration time to the set_transient() call to something reasonable. For example, changing set_transient( ‘saved_user_roles’, $trans ); to set_transient( ‘saved_user_roles’, $trans, 86400 ); would automatically delete the saved user role transient data after 24 hours.
CONCLUSION
It would be better if WordPress would properly handle user profile updates for users with multiple security roles. Until it does, the code shown above can work-around the issue.
Share the post "WordPress user profile update deletes custom roles (fixed)"