Add role across network in multisite

This is my experience.

I had to add role for each site in WordPress, I developed an add page in dashboard so the site administrator can add the self-defined role/capabilities.

But I found the $wp_roles->add_role, add_cap only works the subsite. So I did some fix,

I’ve made a plugin, the super-admin(not site administrator but the “admin”) can “Network Activate” in dashboard

xxx/wp-admin/network/plugins.php

the self-defined role/capabilities was saved in role.ini file, the plugin can create two table named wp_s_role and wp_s_cap, The pre-self-defined role/capabilities will insert into the table.

Then the $wp_roles->add_role and add_cap can across the network and insert the role/capabilities to all subsites.

But the subsite administrator must add the self-defined role/capabilities and make it works in all subsites, so I create a trigger for

    register_activation_hook(__FILE__, array($s_role, 'install'));
    register_deactivation_hook(__FILE__, array($s_role, 'uninstall'));

Then the uninstall function works, all the two tables data will be saved into the role.ini file again, after that, the install function will works again. and all self-defined role/capabilities was added into all subsites.

As you see, I made a trigger in order to add the self-defined role into all subsites, but the effectivity is very low, the plugin restart will take more than 5 seconds. So I’ve improve the methods, This time, when the add_role function is done, I copied the subsite role/capabilities into the others subsites.

Some steps:

  • In subsites, when the administrator add the role, I use $blog_id and $table_prefix to get a subsite table wp_2_options contents. (We assumed the blog_id is 2 and the table_prefix is wp).

  • I select the option_name=wp_2_user_roles

    result($wpdb->get_row("select `option_value` from `wp_2_options` where `option_name`='wp_2_user_roles'", ARRAY_A);), 
    

then I foreach the blogs and get the subsite table. So I insert the select result into each subsite table(wp_n_options) and the main table(wp_options), muhaha, am I smart enough? : )

The below function is how to remove the self-defined roles in all subsites. I think it’s would helpful with you.

public function reset_subsite_role_cap()
{
    //select * from `wp_options` where `option_name` = 'wp_user_roles';
    //select * from `wp_options` where `option_name` = 'wp_backup_user_roles';
    //select * from `wp_3_options` where `option_name` = 'wp_3_user_roles';
    //select * from `wp_4_options` where `option_name` = 'wp_4_user_roles';
    //select * from `wp_5_options` where `option_name` = 'wp_5_user_roles';
    global $wpdb;
    $sql = "select * from ".$wpdb->blogs;
    $multisite_info = $wpdb->get_results($sql, ARRAY_A);
    $site_result = array();
    if (!empty($multisite_info) && is_array($multisite_info)) {
        foreach ($multisite_info as $k => $v) {
            $site_result[$v['blog_id']] = trim($v['path'], "https://wordpress.stackexchange.com/");
        }
    }

    global $table_prefix;
    $tp_arr = explode('_', $table_prefix);
    $table_arr = array();
    foreach ($site_result as $blog_id => $site_name) {
        if ($blog_id !== 1) {
            $table_arr[$tp_arr[0].'_'.$blog_id.'_options'] = $tp_arr[0].'_'.$blog_id.'_user_roles';
        } else {
            $table_arr[$tp_arr[0].'_options'] = $tp_arr[0].'_user_roles';
        }
    }

    // get the backup user roles.
    $backup_roles_result = $wpdb->get_row("select `option_value` from `".$tp_arr[0]."_options` where `option_name`='wp_backup_user_roles'", ARRAY_A);

    // clean the others role cap
    if (isset($table_arr) && is_array($table_arr)) {
        foreach ($table_arr as $table_role_cap_name => $table_role_cap_filed) {
            $wpdb->update(
                $table_role_cap_name,
                array('option_value' => $backup_roles_result['option_value']),
                array('option_name' => $table_role_cap_filed)
            );
        }
    }
    return true;
}

For the $site_result, I have another function to fetch all exact site info.

/**
 * Get the all site info.
 *
 * @param integer $id BlogID.
 *
 * @return array array('blog_id' => 'path', '1' => 'printsolv', '2' => 'govsolv')
 */
public function s_get_multisite_info($id = null)
{
    global $wpdb;
    $where="1=1";
    if (isset($id)) {
        $sql = "select * from ".$wpdb->blogs." where `blog_id`="."'".$id."'";
    } else {
        $sql = "select * from ".$wpdb->blogs." where 1=1 ";
    }
    $multisite_info = $wpdb->get_results($sql, ARRAY_A);
    $result = array();
    if (!empty($multisite_info) && is_array($multisite_info)) {
        // clean the path, > 1 means not http://site_name/theme.php but http://site_name/path_name/theme.php
        if (isset($id)) {
            $site_info = $wpdb->get_row("select * from ".$wpdb->site." where `id`="."'".$multisite_info[0]['site_id']."'", ARRAY_A);
        } else {
            $site_info = $wpdb->get_row("select * from ".$wpdb->site, ARRAY_A);
        }

        $site_path_status = false; // path= /
        if (isset($site_info['path']) && strlen(trim($site_info['path'], "https://wordpress.stackexchange.com/")) > 1 ) {
            // site have path./xxx/
            $site_path_status = true;
        }

        foreach ($multisite_info as $k => $v) {
            if (isset($site_info['domain']) && $site_info['domain'] == $v['domain']) {
                if ($site_path_status == true) {
                    $result[$v['blog_id']] = trim(substr($v['path'], strlen(trim($site_info['path'], "https://wordpress.stackexchange.com/")) + 1), "https://wordpress.stackexchange.com/");
                } else {
                    $result[$v['blog_id']] = trim($v['path'], "https://wordpress.stackexchange.com/");
                }
            }
        }
    }
    return $result;
}

I have just finished the function and it works perfectly. You can use s_copy_site_role_cap() to call. The function can copy the role into others subsite roles after you add_role. Because the plugin might works in main site or subsite, there will have two ways in network(subdomain, subpath) , so i made another function to get the right current blog_name , so I can get the lastest roles contents from the blog_name information.

/**
 * Copy the subsite role/caps to all subsites and main site..
 *
 * @param string $subsite_name SubSite Name.
 *
 * @return boolean.
 */
public function s_copy_site_role_cap()
{
    global $wpdb;
    // Get all site info 
    $multisite_info = $this->s_get_multisite_info();

    global $table_prefix;
    $tp_arr = explode('_', $table_prefix);
    // Get all site wp_x_options table. and table filed.
    $table_arr = array();
    foreach ($multisite_info as $blog_id => $site_name) {
        if ($blog_id !== 1) {
            $table_arr[$tp_arr[0].'_'.$blog_id.'_options'] = $tp_arr[0].'_'.$blog_id.'_user_roles';
        } else {
            $table_arr[$tp_arr[0].'_options'] = $tp_arr[0].'_user_roles';
        }
    }
    // select the blog id by blog name.
    $subsite_name = $this->s_get_dashboard_site_name();
    $subsite_id = array_search($subsite_name, $multisite_info);
    if ($subsite_id === false) {
        return false;
    }
    $current_site_table_pre = $tp_arr[0].'_'.$subsite_id;
    // get the current subsite roles.
    $subsite_roles = $wpdb->get_row("select `option_value` from `".$current_site_table_pre."_options` where `option_name`='".$current_site_table_pre."_user_roles'", ARRAY_A);

    if (isset($table_arr) && is_array($table_arr)) {
        foreach ($table_arr as $table_role_cap_name => $table_role_cap_filed) {
            if ($table_role_cap_name != $current_site_table_pre.'_options') {
                $wpdb->update(
                    $table_role_cap_name,
                    array('option_value' => $subsite_roles['option_value']),
                    array('option_name' => $table_role_cap_filed)
                );
            }
        }
    }
    return true;
}

/**
 * Get dashboard site name.
 *
 * @return string. SiteName
 */
public function s_get_dashboard_site_name()
{
    $dashboard_url = admin_url();

    //$dashboard_url = "http://xxx/wp-admin/";
    //$dashboard_url = "http://subsite_name.xxx/wp-admin/";
    //$dashboard_url = "http://xxx/subsite_name/wp-admin/";

    // get main site name.
    $site_name = get_current_site()->domain;
    $parse_url = parse_url($dashboard_url);
    if (!is_subdomain_install()) {
        // base path  http://xxx/wp-admin/
        // subsite path http://xxx/subsite_name/wp-admin/
        if ($parse_url['path'] == "/wp-admin/") {
            return $site_name;
        } else {
            $tmp_url = explode("https://wordpress.stackexchange.com/", trim($parse_url['path'], "https://wordpress.stackexchange.com/"));
            return $tmp_url[0];
        }
    } else {
        // base domain  http://xxx/wp-admin/
        // subsite domain  http://subsite_name.xxx/wp-admin/
        if ($parse_url['host'] == $site_name) {
            return $site_name;
        } else {
            $tmp_url = explode(".", $parse_url['host']);
            return $tmp_url[0];
        }
    }

}

Thanks.

Leave a Comment