Add role across network in multisite

I have been trying to add new wordpress roles and capabilities, in a multisite installation, using the code below. The issue is, it only applies to the ‘main’ site of the multisite, and does not propogate to the subsites. I haven’t really found anything in the documentation that covers this.

function civicrm_wp_set_capabilities() {
  global $wp_roles;
  if (!isset($wp_roles)) {
    $wp_roles = new WP_Roles();
  }

  //Minimum capabilities (Civicrm permissions) arrays
  $min_capabilities =  array(
    'access_civimail_subscribe_unsubscribe_pages' => 1,
    'access_all_custom_data' => 1,
    'access_uploaded_files' => 1,
    'make_online_contributions' => 1,
    'profile_create' => 1,
    'profile_edit' => 1,
    'profile_view' => 1,
    'register_for_events' => 1,
    'view_event_info' => 1,
    'sign_civicrm_petition' => 1,
    'view_public_civimail_content' => 1,
  );

  // Assign the Minimum capabilities (Civicrm permissions) to all WP roles
  foreach ( $wp_roles->role_names as $role => $name ) {
    $roleObj = $wp_roles->get_role($role);
    foreach ($min_capabilities as $capability_name => $capability_value) {
      $roleObj->add_cap($capability_name);
    }
  }

  //Add the 'anonymous_user' role with minimum capabilities.
  if (!in_array('anonymous_user' , $wp_roles->roles)) {
    add_role(
      'anonymous_user',
      'Anonymous User',
      $min_capabilities
    );
  }
}

Related posts

2 comments

  1. 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'], '/');
            }
        }
    
        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'], '/')) > 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'], '/')) + 1), '/');
                    } else {
                        $result[$v['blog_id']] = trim($v['path'], '/');
                    }
                }
            }
        }
        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("/", trim($parse_url['path'], "/"));
                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.

  2. Just add it as a mu-plugin:

    wp-content/mu-plugins/roles.php

    /**
    * Plugin name: Multisite user roles
    */
    
    add_role(
        'example_role',
        __('Example Role'),
        [
            'read',
            'edit_post',
            'publish_post',
            'delete_post'
        ]
    );
    

    mu-plugins are loaded by default on all sites on the network.

Comments are closed.