
read user capability
“Dashboard”-“Home”, “Dashboard”-“My Sites” (for multisite WP installation) and “Profile”-“Your Profile”.
Thus if you revoke ‘read’ capability from some user, she could not access to her profile then. Such user will get error message from WordPress just after login: “You do not have sufficient permissions to access this page”.
WordPress Codex page declares that:
‘read’ capability was introduced since WorPress version 2.0, it allows access to Administration Panel options: Dashboard, Users > Your Profile
and used nowhere in the core code except the menu.php
. While it is absolute true about menu items, source code information is outdated a little. Let’s dive together inside WordPress core source code and look for the reason to such capability (‘read’) was implemented.
WordPress read capability at PHP files
‘read’ user capability is found at the next .php files from WordPress core source code:
- wp-login.php;
- wp-includes/capabilities.php;
- wp-includes/post.php;
- wp-admin/menu.php;
- wp-admin/network/menu.php
- wp-admin/my-sites.php
- wp-admin/includes/schema.php
Source Code
wp-login.php: The only place where ‘read’ capability is met here is block of code started from line 631:
631 632 633 634 635 636 637 638 639 640 641 | if ( ( empty( $redirect_to ) || $redirect_to == 'wp-admin/' || $redirect_to == admin_url() ) ) { // If the user doesn't belong to a blog, send them to user admin. If the user can't edit posts, send them to their profile. if ( is_multisite() && !get_active_blog_for_user($user->ID) && !is_super_admin( $user->ID ) ) $redirect_to = user_admin_url(); elseif ( is_multisite() && !$user->has_cap('read') ) $redirect_to = get_dashboard_url( $user->ID ); elseif ( !$user->has_cap('edit_posts') ) $redirect_to = admin_url('profile.php'); } wp_safe_redirect($redirect_to); exit(); |
As you can see at line 636 if user has not ‘read’ capability under multi-site environment she will be redirected to her’s dashboard. get_dashboard_url()
function works this way: If a user does not belong to any site, the global user dashboard is used. If the user belongs to the current site, the dashboard for the current site is returned. If the user cannot edit the current site, the dashboard to the user’s primary blog is returned. Otherwise, that is if user is under single-site environment or user has ‘read’ capability, WordPress redirects her to her’s profile page.
wp-includes/capabilities.php: There is map_meta_cap()
function in this file, which “map meta capabilities to primitive capabilities” according to source code comments. That is for our case this function maps ‘read_post’, or ‘read_page’ meta capabilities to the ‘read’ capability for standard public posts and pages (line #1133) post types or for the custom defined one (if exists) for public post of custom post type:
1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 | case 'read_post': case 'read_page': $post = get_post( $args[0] ); if ( 'revision' == $post->post_type ) { $post = get_post( $post->post_parent ); } $post_type = get_post_type_object( $post->post_type ); if ( ! $post_type->map_meta_cap ) { $caps[] = $post_type->cap->$cap; // Prior to 3.1 we would re-call map_meta_cap here. if ( 'read_post' == $cap ) $cap = $post_type->cap->$cap; break; } $status_obj = get_post_status_object( $post->post_status ); if ( $status_obj->public ) { $caps[] = $post_type->cap->read; break; } |
wp-includes/post.php: This file uses ‘read’ capability just in case of mapping to the same ‘read’ one for the standart ‘post’ post type, look at line 1430:
1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 | // Primitive capabilities used within map_meta_cap(): if ( $args->map_meta_cap ) { $default_capabilities_for_mapping = array( 'read' => 'read', 'delete_posts' => 'delete_' . $plural_base, 'delete_private_posts' => 'delete_private_' . $plural_base, 'delete_published_posts' => 'delete_published_' . $plural_base, 'delete_others_posts' => 'delete_others_' . $plural_base, 'edit_private_posts' => 'edit_private_' . $plural_base, 'edit_published_posts' => 'edit_published_' . $plural_base, ); $default_capabilities = array_merge( $default_capabilities, $default_capabilities_for_mapping ); } |
But while ‘read’ capability is defined as one of the key capability to access post content, I don’t find in the code any limitation for access to the public posts for unregistered users or registered and logged-in users without ‘read’ capability. Such user lose access to the WordPress dashboard and user profile in that case only. Following investigation of wp-admin/menu.php
file just confirms that.
wp-admin/menu.php: This file uses ‘read’ capability to limit access for selected menus and menu items at WordPress admin dashboard:
25 26 27 28 29 30 31 | $menu[2] = array( __('Dashboard'), 'read', 'index.php', '', 'menu-top menu-top-first menu-icon-dashboard', 'menu-dashboard', 'none' ); $submenu[ 'index.php' ][0] = array( __('Home'), 'read', 'index.php' ); if ( is_multisite() ) { $submenu[ 'index.php' ][5] = array( __('My Sites'), 'read', 'my-sites.php' ); } |
As you see above such menu items are: ‘Dashboard’, ‘Home’ and ‘My Sites’ for multi-site WordPress installation.
The next ‘read’ capability occurrence in this file is line #42 where it is used to check access to … It seems a joke but its true – line separator between different menus :).
42 | $menu[4] = array( '', 'read', 'separator1', '', 'wp-menu-separator' ); |
The same code could be found at line #137
137 | $menu[59] = array( '', 'read', 'separator2', '', 'wp-menu-separator' ); |
This shows us the real level of responsibility for the ‘read’ user capability. It could be available for any registered WordPress user. The only case when you may to wish revoke this capability when you plan to prohibit users access to their user profile. Look at the code below. Lines 181, 191 and 194 are the subject of our interest here:
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 | if ( current_user_can('list_users') ) $menu[70] = array( __('Users'), 'list_users', 'users.php', '', 'menu-top menu-icon-users', 'menu-users', 'none' ); else $menu[70] = array( __('Profile'), 'read', 'profile.php', '', 'menu-top menu-icon-users', 'menu-users', 'none' ); if ( current_user_can('list_users') ) { $_wp_real_parent_file['profile.php'] = 'users.php'; // Back-compat for plugins adding submenus to profile.php. $submenu['users.php'][5] = array(__('All Users'), 'list_users', 'users.php'); if ( current_user_can('create_users') ) $submenu['users.php'][10] = array(_x('Add New', 'user'), 'create_users', 'user-new.php'); else $submenu['users.php'][10] = array(_x('Add New', 'user'), 'promote_users', 'user-new.php'); $submenu['users.php'][15] = array(__('Your Profile'), 'read', 'profile.php'); } else { $_wp_real_parent_file['users.php'] = 'profile.php'; $submenu['profile.php'][5] = array(__('Your Profile'), 'read', 'profile.php'); if ( current_user_can('create_users') ) $submenu['profile.php'][10] = array(__('Add New User'), 'create_users', 'user-new.php'); else $submenu['profile.php'][10] = array(__('Add New User'), 'promote_users', 'user-new.php'); } |
Just remember that user could not change her password manually in case she has not access to her user profile.
wp-admin/network/menu.php: This file uses ‘read’ capability to decide show or not separator at the Network admin menu:
13 | $menu[4] = array( '', 'read', 'separator1', '', 'wp-menu-separator' ); |
69 | $menu[99] = array( '', 'read', 'separator-last', '', 'wp-menu-separator-last' ); |
Thus, it doesn’t fulfill any security function here.
wp-admin/mysites.php: This page shows an individual user all of his or her sites in this network, and also allows that user to set a primary site. He or she can use the links under each site to visit either the frontend or the dashboard for that site. If user has not ‘read’ capability access to this page is prohibited.
15 16 | if ( ! current_user_can('read') ) wp_die( __( 'You do not have sufficient permissions to view this page.' ) ); |
wp-admin/includes/schema.php: This file contains code to create ‘read’ capability. At line 569 you can find the populate_roles_160()
function:
564 565 566 567 568 569 | /** * Create the roles for WordPress 2.0 * * @since 2.0.0 */ function populate_roles_160() { |
with this line of code:
610 | $role->add_cap('read'); |
which introduced the ‘read’ user capability together with others 1st time since WordPress version 2.0.
This article uses WordPress 3.5 RC2 source code.