Wordpress - Add Custom User Capabilities Before or After the Custom User Role has Been Added?

There's a reason why add_role() has the $capabilities as 3rd parameter.

First some insights on what happens, when you use the function.

  1. It calls WP_Roles->add_role() - the class method
  2. The method then does a check if the role already exists. If yes, it aborts.
  3. The next step is, that it adds the role to WP_Roles->roles[] array with the display_name (2nd arg) to the $wpdb->prefix . 'user_roles' option table entry (an array).

Conclusion

So if you don't add the capabilities right when creating the role, then you save a role with empty capabilities.

A note about the Codex

NEVER EVER trust the codex. There simply is no function called add_cap(). There're only class methods inside WP_Roles, WP_Role and WP_User that are named like this. (Will update the Codex entry if I find some time.)


For anyone who may have asked the same question, or thought about the same question at least... Here is how I approached the situation.

Please keep in mind, this scenario involves updating an already existing user base. There were already hundreds upon hundreds of users actively participating in this community website.

We were changing the ranking system of the website. (Ranks were custom user roles.) You could achieve a higher rank by participating in the community more. So once the user achieved their goals, they would be promoted to a new custom user role with more access to the community website.

There were already custom user roles in place, and assigned to each user of the community. We just wanted to change the names of the custom user roles, add a few more, and assign new privileges for these ranks (In WordPress terms, add new capabilities for each custom user role).


Step 1 - Update User Meta

Get all of your users, determine their current custom user role, add the new role, and remove any other existing roles. (We safe-listed a few of the already existing user roles, because we wanted to make sure that those were kept as-is and not modified or removed.)

Note: This step isn't actually adding any custom user roles. It is essentially just updating the user meta of each user so that WordPress knows what role they have, and which registered role it should check permissions or capabilities for later on.

private function update_user_roles(){
    global $wp_roles;   

    /* Don't update users with these roles. */
    $safelisted_roles = array(
        'administrator',
        'editor',
        'author',
        'contributor',
        'subscriber', 
        'bbp_moderator',
        'bbp_participant'
    );

    /* Convert existing user's role to the newly added roles. */
    $users = get_users();
    for($i = 0; $i < count($users); $i++){
        $user = new WP_User($users[$i]->ID);
        /* Skip user, if user has no roles. */
        if(is_array($user->roles) && empty($user->roles) || !is_array($user->roles)){
            continue;
        }
        foreach($user->roles as $role){
            if($role == 'rank_name'){// Existing user role
                $user->add_role('new_rank_name');// New user role
            } else{
                /* Set remaining users as Subscribers, ignoring safe-listed user roles. */
                if(!in_array($role, $safelisted_roles)){
                    $user->add_role('subscriber');// Reset everyone to default role.
                }
            }
            /* Remove the old junky roles from the user, ignoring safe-listed user roles. */
            if(!in_array($role, $safelisted_roles)){
                $user->remove_role($role);
            }
        }
    }
}

So for a recap, basically we went through every user, checked what their current user role was, and decided to give them a new role or not. If the user's role was in the safe-list, then their role remained the same.


Step 2 - Define New User Roles and Capabilities

To keep your code neater and more organized, create a separate method for preparing your new user roles and capabilities. This allows for you to better manage your capabilities for each custom user role and makes adding everything easier, and clutter free.

We stored the data from this method in a class property ($this->user_capabilities), in array format, to be used in the next step.

/* Prepare specific permissions for each new user role. */
public function set_user_capabilities(){
    $this->user_capabilities[] = array(
        'role_key' => 'new_rank_one',// Registered role name
        'role_value' => 'Rank One',// Role display name
        'capabilities' => array(// Permissions
            'new_capability_one' => true,
            'new_capability_two' => false,
            'new_capability_three' => false
        )
    );
    $this->user_capabilities[] = array(
        'role_key' => 'new_rank_two',
        'role_value' => 'Rank Two',
        'capabilities' => array(
            'new_capability_one' => true,
            'new_capability_two' => true,
            'new_capability_three' => false
        )
    );
    $this->user_capabilities[] = array(
        'role_key' => 'new_rank_three',
        'role_value' => 'Rank Three',
        'capabilities' => array(
            'new_capability_one' => true,
            'new_capability_two' => true,
            'new_capability_three' => true
        )
    );
}

Just as a recap, the key of the class property $this->user_capabilities is where you would place the custom user role to add. Inside the array, contains each of the capabilities that role will have. These capabilities grant more access to the site.

Notice: how all three new user roles share the same capabilities? The first user role only inherits permission for the first capability, the second user role inherits permission for the first two capabilities, because they are a higher rank, and the third user role inherits permission for all three capabilities, because that is the top dog rank and has the most permissions.


Step 3 - Define Shared Permissions and Create User Roles

We needed to set some general capabilities that all registered users needed to share. So we just made a separate array of shared capabilities right before we added each new custom user role, and merged them with each specific user role's capabilities created in Step 2, which are stored in the class property ($this->user_capabilities). Once again, we need to respect our safe-listed user roles.

/* Add custom user roles for registered members. */
public function set_user_roles(){
    global $wp_roles;

    /* Global capabilities for all registered users. */
    $user_capabilities = array(
        'bp_groups' => true,
        'bp_groups_create' => true,
        'bp_groups_join' => true,
        'bp_groups_directory' => true,
        'bp_groups_search' => true
    );

    /* Don't update users with these roles. */
    $safelisted_roles = array(
        'administrator',
        'editor',
        'author',
        'contributor',
        'subscriber', 
        'bbp_moderator',
        'bbp_participant'
    );

    /* Remove old junky user roles. */
    foreach($wp_roles->get_names() as $role_name => $display_name){
        if(!in_array($role_name, $safelisted_roles)){
            $wp_roles->remove_role($role_name);
        }
    }

    /* Add new custom user roles. */
    for($i = 0; $i < count($this->user_capabilities); $i++){
        $role = $this->user_capabilities[$i];
        $capabilities = array_merge($user_capabilities, $role['capabilities']);
        $wp_roles->add_role($role['role_key'], $role['role_value'], $capabilities);
    }

}

Final recap... We setup a list of capability permissions that all registered users will need to share, regardless of what rank they are. We then removed every single registered custom user role registered with WordPress (Unless it was safe-listed to not modify). We then proceeded to create each of the new custom user roles, and assigned all capabilities for each new user role. The roles we set in the user's meta from Step 1, will now take effect in WordPress.


Conclusion

This is what worked for me. Update all of your user's meta data with the roles you are planning to add. Then, proceed with actually registering newly created custom user roles with WordPress.