Skip to content

Fix view_stats capability check for users with multiple roles#47991

Open
yaswanthkumar1995 wants to merge 1 commit intoAutomattic:trunkfrom
yaswanthkumar1995:fix/47258-stats-view-multi-role
Open

Fix view_stats capability check for users with multiple roles#47991
yaswanthkumar1995 wants to merge 1 commit intoAutomattic:trunkfrom
yaswanthkumar1995:fix/47258-stats-view-multi-role

Conversation

@yaswanthkumar1995
Copy link
Copy Markdown
Contributor

Fixes #47258

Proposed changes

  • Fixed view_stats meta-capability mapping to check all user roles instead of only the first one.
  • Replaced array_shift() + in_array() with array_intersect() so multi-role users (e.g. customer + administrator) are correctly granted stats access.
  • Added a test for multi-role users where administrator is not the first role.

Other information

The bug occurs because array_shift($user_roles) only returns the first role in the array. For users who were assigned a non-admin role before being promoted to administrator, the first role (e.g. customer) isn't in the stats allowlist, so they're denied access.

Related product discussion/links

Does this pull request change what data or activity we track or use?

No.

Testing instructions

  • Create a test user with subscriber role
  • Add administrator as a second role: $user->add_role('administrator')
  • Verify user_can($user_id, 'view_stats') returns true
  • Previously this would return false because only the first role (subscriber) was checked

@yaswanthkumar1995
Copy link
Copy Markdown
Contributor Author

/assign

@yaswanthkumar1995
Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 8, 2026

Thank you for your PR!

When contributing to Jetpack, we have a few suggestions that can help us test and review your patch:

  • ✅ Include a description of your PR changes.
  • ✅ Add testing instructions.
  • ✅ Specify whether this PR includes any changes to data or privacy.
  • ✅ Add changelog entries to affected projects

This comment will be updated as you work on your PR and make changes. If you think that some of those checks are not needed for your PR, please explain why you think so. Thanks for cooperation 🤖


@github-actions github-actions bot added the OSS Citizen This Pull Request was opened by an Open Source contributor. label Apr 8, 2026
@jeherve jeherve requested a review from a team April 8, 2026 07:31
@jeherve jeherve added Bug When a feature is broken and / or not performing as intended [Status] Needs Review This PR is ready for review. [Pri] Normal [Feature] Stats Data Feature that enables users to track their site's traffic and gain insights on popular content. labels Apr 8, 2026
The view_stats meta-cap mapping only checked the first role in the
user's roles array via array_shift(). Users with multiple roles (e.g.
customer + administrator) could be denied stats access if their first
role wasn't in the stats roles allowlist.

Use array_intersect() to check all user roles against the allowed
stats roles instead of only the first.

Fixes Automattic#47258
@yaswanthkumar1995 yaswanthkumar1995 force-pushed the fix/47258-stats-view-multi-role branch from aad72de to 439f966 Compare April 10, 2026 15:47
@adamwoodnz
Copy link
Copy Markdown
Contributor

@claude review please. Out of interest, do WordPress roles get added to a user in chronological order?

@claude
Copy link
Copy Markdown
Contributor

claude bot commented Apr 16, 2026

Claude finished @adamwoodnz's task in 1m 4s —— View job


Code Review

  • Gather context
  • Read changed files
  • Review code
  • Answer WordPress role ordering question

Summary

The fix is correct and well-targeted. The original array_shift() call destructively extracted only the first role from the copied array, making capability checks order-dependent. Replacing it with array_intersect() properly handles all roles regardless of order.


projects/packages/stats/src/class-main.php

The fix (lines 130–136):

// Before
$user_roles  = $user->roles;
$user_role   = array_shift( $user_roles );
if ( is_array( $stats_roles ) && in_array( $user_role, $stats_roles, true ) ) {

// After
$user_roles  = (array) $user->roles;
if ( is_array( $stats_roles ) && ! empty( array_intersect( $user_roles, $stats_roles ) ) ) {

Looks good. The new logic is correct, and it's now consistent with the existing should_track() method at line 214, which already used array_intersect() for the parallel count_roles check:

// Line 214 — already used this pattern:
if ( ! is_array( $count_roles ) || ! array_intersect( $current_user->roles, $count_roles ) ) {

Minor note: The (array) cast on $user->roles is redundant — WP_User::$roles is already typed as string[] and will always be an array. It's harmless defensive coding, but not strictly needed.


projects/packages/stats/tests/php/Main_Test.php

The new test test_view_stats_meta_mapping_multi_role (lines 184–198) correctly covers the reported scenario: a user created as subscriber, then promoted to administrator, where the first role in the array isn't in the stats allowlist. This is exactly the edge case that was broken.

One gap worth considering: There's no explicit test verifying that a user with only subscriber (not in the default stats roles) still cannot view stats. That case was working before the change and should still work, but an explicit regression test would be cleaner. Not a blocker.


projects/packages/stats/changelog/fix-stats-view-multi-role

The changelog entry correctly marks this as a patch/fixed. Looks good.


WordPress Role Ordering — Answer to Your Question

do WordPress roles get added to a user in chronological order?

Yes, effectively — but it's an implementation detail, not a documented guarantee.

WordPress stores user capabilities in wp_usermeta as a serialized associative array (e.g. { "administrator": true, "subscriber": true }). When WP_User::add_role() is called, it appends the new capability to that array and saves it back. Since PHP 7.0+, associative arrays maintain insertion order, so $user->roles does reflect the chronological order roles were assigned.

That's precisely why array_shift() was fragile — it assumed the "most important" or "primary" role was always first, but for a user who was a subscriber before becoming an admin, subscriber would come first and the stats access check would fail. The array_intersect() fix removes that assumption entirely.


Overall: this is a clean, minimal fix for a real bug with a good test. Approved pending the minor notes above.

Copy link
Copy Markdown
Contributor

@adamwoodnz adamwoodnz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR @yaswanthkumar1995!

I agree with Claude that a couple more tests would be good. One for a single allowed stats role, and one for a non-allowed, eg. subscriber.

Copy link
Copy Markdown
Contributor

@kangzj kangzj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for fixing this. The changes look good to me!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Bug When a feature is broken and / or not performing as intended [Feature] Stats Data Feature that enables users to track their site's traffic and gain insights on popular content. OSS Citizen This Pull Request was opened by an Open Source contributor. [Package] Stats Data [Pri] Normal [Status] Needs Review This PR is ready for review. [Tests] Includes Tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

4 participants