Skip to content

Jetpack Stats view_stats meta-cap mapping only checks first user role, admins with multiple roles may be unable to view stats #47258

@mdrovdahl

Description

@mdrovdahl

$user_role = array_shift( $user_roles ); // Work with the copy
I was working on a site today that would not show stats to an admin user (below) until I explicitly added view_stats. As best I can tell, that's because the current code only considers the user's first listed role. It may be a rare scenario, but if (as I believe they are) roles are ordered by insertion date/time, in this case the user was a customer before they became an admin.

150648781@www.[redacted]wine.com:~$ wp user meta get 56531 wp_capabilities
array (
  'customer' => true,
  'administrator' => true,
  'view_stats' => true,
)

AI generated text below here:

Summary

On a single-site WordPress install using Jetpack, a user who has the administrator role could not see Jetpack Stats until I explicitly granted the view_stats capability at the user level. This appears to be caused by Jetpack Stats’ view_stats meta-cap mapping only considering the first role in WP_User::$roles (derived from the stored wp_capabilities array order), rather than checking all roles the user has.

Expected behavior

If a user has any role that is permitted to view stats (e.g. administrator is included in the Stats roles allowlist), then current_user_can( 'view_stats' ) should succeed and the Stats UI should be available. Multi-role users should not be blocked simply because administrator is not the “first” listed role.

Suggested fix

Update the view_stats meta-cap mapping to consider all of a user’s roles, not just the first role returned by WP_User::$roles.

Today the code effectively does:

  • $user_role = array_shift( $user->roles );
  • allow if $user_role is in Options::get_option( 'roles' )

This breaks for multi-role users when the first role is not in the allowlist.

Proposed logic:

  • Get $user_roles = (array) $user->roles;
  • Get $stats_roles = (array) Options::get_option( 'roles' );
  • If any role overlaps (array_intersect( $user_roles, $stats_roles ) is non-empty), map view_stats to read.

Pseudo-code:

if ( 'view_stats' === $cap ) {
	$user        = new WP_User( $user_id );
	$user_roles  = (array) $user->roles;
	$stats_roles = Options::get_option( 'roles' );

	if ( is_array( $stats_roles ) && ! empty( array_intersect( $user_roles, $stats_roles ) ) ) {
		$caps = array( 'read' );
	}
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    [Feature] Stats DataFeature that enables users to track their site's traffic and gain insights on popular content.

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions