Make WordPress Core

Opened 9 months ago

Closed 3 months ago

#63427 closed defect (bug) (fixed)

User roles property should always be an array, but they sometimes become an object in localized data

Reported by: haruncpi's profile haruncpi Owned by: sergeybiryukov's profile SergeyBiryukov
Milestone: 6.9 Priority: normal
Severity: normal Version: 2.0
Component: Users Keywords: has-patch has-test-info has-unit-tests commit
Focuses: Cc:

Description

When filtering roles via array_filter() in wp-includes/class-wp-user.php, the resulting array ($this->roles) can retain non-sequential numeric keys, if any roles are filtered out. This causes WP_User->roles to be treated as an object when JSON-encoded (e.g., in wp_localize_script()), instead of a proper array, which breaks client-side expectations.

$current_user = wp_get_current_user();

$local_data = array(
    'current_user' => array(
        'id' => $current_user->ID,
        'roles' => $current_user->roles,
    ),
);

wp_localize_script( 'myplugin-frontend', '_myplugin_object', $local_data );

If roles contains keys like [0 => 'editor', 2 => 'seo'] , the resulting JSON becomes:

{ roles: { "0": "editor", "2": "custom" } }

Instead of

{ roles: ["editor", "custom"] }

Attachments (1)

63427.diff (506 bytes) - added by haruncpi 9 months ago.
Adds array reindexing to ensure roles remains a proper indexed array

Download all attachments as: .zip

Change History (27)

@haruncpi
9 months ago

Adds array reindexing to ensure roles remains a proper indexed array

This ticket was mentioned in PR #8790 on WordPress/wordpress-develop by @haruncpi.


9 months ago
#1

When filtering roles via array_filter() in wp-includes/class-wp-user.php, the resulting array ($this->roles) can retain non-sequential numeric keys, if any roles are filtered out. This causes WP_User->roles to be treated as an object when JSON-encoded (e.g., in wp_localize_script()), instead of a proper array, which breaks client-side expectations.

$current_user = wp_get_current_user();

$local_data = array(
    'current_user' => array(
        'id' => $current_user->ID,
        'roles' => $current_user->roles,
    ),
);

wp_localize_script( 'myplugin-frontend', '_myplugin_object', $local_data );

If roles contains keys like [0 => 'editor', 2 => 'seo'], the resulting JSON becomes:

{ roles: { "0": "editor", "2": "custom" } }

Instead of

{ roles: ["editor", "custom"] }

Ticket’s URL
https://core.trac.wordpress.org/ticket/63427

@getsyash commented on PR #8790:


9 months ago
#2

### Test Report

I’ve tested this PR and can confirm that it works as expected. After applying the changes, the $current_user->roles array is properly reindexed using array_values(), ensuring sequential keys. The localized JSON output now correctly produces an array instead of a JavaScript object, which resolves the client-side issue.

Tested with multiple role combinations, including cases where some roles were removed — everything behaves correctly. ✅

### Testing environment:

WordPress Version: 6.8.1
PHP Version: 8.2.27
Theme: TwentyTwentyFive
Plugins: None installed
OS: Windows

Thanks for the Fix

#3 @haruncpi
9 months ago

  • Resolution set to worksforme
  • Status changed from assigned to closed

Could you kindly take a look and review this fix? @SergeyBiryukov @desrosj

#4 @SirLouen
9 months ago

  • Component changed from General to Users
  • Focuses sustainability removed
  • Keywords has-test-info dev-feedback has-unit-tests added
  • Resolution worksforme deleted
  • Status changed from closed to reopened

@haruncpi If you have a patch and a test report, you dont have to close the ticket. Leave it open for review with the patch. Closing with worksforme is used when someone reports a bug, you test it and you see there is no bug.

PS: I have not tested the patch, I'm just informing the user.

#5 @haruncpi
9 months ago

Thank you @SirLouen
Reopened for review.

#6 @SergeyBiryukov
9 months ago

  • Milestone changed from Awaiting Review to 6.9
  • Owner changed from haruncpi to SergeyBiryukov
  • Status changed from reopened to reviewing

#7 @SirLouen
8 months ago

  • Keywords needs-testing added; dev-feedback removed

#8 follow-up: @SirLouen
8 months ago

  • Keywords changes-requested added; needs-testing removed

@haruncpi I've added some code review in your PR. Give it a check when you can.

#9 @SirLouen
8 months ago

  • Keywords reporter-feedback added
  • Version changed from 6.8 to 2.0

<has-code-review>

I've been able to track the introduction of array_keys back to 1.6 [2793], which is not the problem itself, but is the main reason of why this is happening. Because this thing has probably existed historically, from the inception of this function.

The question here is: @haruncpi why you don't simply preprocess the resulting json into an array, before delivering it to wp_localize_script?

Last edited 8 months ago by SirLouen (previous) (diff)

#10 in reply to: ↑ 8 @haruncpi
8 months ago

Replying to SirLouen:

@haruncpi I've added some code review in your PR. Give it a check when you can.

PR is updated

#11 follow-up: @SirLouen
8 months ago

  • Keywords 2nd-opinion added; changes-requested reporter-feedback removed

Still I have the question:

@haruncpi why you don't simply preprocess the resulting json into an array, before delivering it to wp_localize_script?

This not sequential order has been for ages, and I'm not sure if it's a bug or just something expected. After checking the history of this function, this has been there for easily 15+ years (since WP 1.6 when they introduced array_keys). Not saying that everything old is correct, but I'm still trying to find an useful reason.

#12 in reply to: ↑ 11 ; follow-up: @haruncpi
8 months ago

Replying to SirLouen:

Still I have the question:

@haruncpi why you don't simply preprocess the resulting json into an array, before delivering it to wp_localize_script?

This not sequential order has been for ages, and I'm not sure if it's a bug or just something expected. After checking the history of this function, this has been there for easily 15+ years (since WP 1.6 when they introduced array_keys). Not saying that everything old is correct, but I'm still trying to find an useful reason.

Thanks for the question!

The issue isn’t really about whether this behavior has existed since early WordPress versions. It likely wasn’t noticed, triggered, or was often preprocessed by developers before reaching wp_localize_script.

However, delivering correct, consistent data directly from core is always the better approach. It ensures data integrity at the source and removes the need for extra preprocessing by developers. This makes the data safer and more predictable for all use cases.

Additionally, the process is now optimized to linear time, preventing potential cubic complexity.

#13 in reply to: ↑ 12 @SirLouen
8 months ago

Replying to haruncpi:

However, delivering correct, consistent data directly from core is always the better approach. It ensures data integrity at the source and removes the need for extra preprocessing by developers. This makes the data safer and more predictable for all use cases.

I understand your concern which from my point of view is valid

I think that for this reason, its required to use the maybe_unserialize WP function.

$current_user = wp_get_current_user();

$local_data = array(
    'current_user' => array(
        'id' => $current_user->ID,
        'roles' => maybe_unserialize( $current_user->roles ),
    ),
);

wp_localize_script( 'myplugin-frontend', '_myplugin_object', $local_data );

Probably due to this kind of issues all over the code.

Still I agree with you that this could/should be fixed to provide a more consistent data and also now I feel that with a simplified complexity (not from cubic, but cuadratic because original there were only two iterations), now becomes linear so it also provides some performance improvement.

#14 @SirLouen
8 months ago

  • Keywords 2nd-opinion removed

At this point, I think it is ready to be shipped
cc @SergeyBiryukov

#15 follow-up: @wildworks
3 months ago

@haruncpi @SirLouen Can you rebase the pull request on top of the trunk branch to run CI and ensure that the tests don't fail?

#16 in reply to: ↑ 15 @haruncpi
3 months ago

I have merged the latest changes from WordPress:trunk into my PR branch.
I ran the tests/phpunit/tests/user.php suite, and all tests passed successfully.
Additionally, the test_user_roles_property_is_sequential_array test ran without any failures.

Replying to wildworks:

@haruncpi @SirLouen Can you rebase the pull request on top of the trunk branch to run CI and ensure that the tests don't fail?

Last edited 3 months ago by haruncpi (previous) (diff)

#17 @haruncpi
3 months ago

Hi @SergeyBiryukov @wildworks,
This was completed and tested about 5 months ago, and @SirLouen confirmed it’s ready to ship.
When you have a moment, please review and merge the PR.

#18 @wildworks
3 months ago

@haruncpi Thanks for the update!

@SergeyBiryukov, Do you have the bandwidth to commit to PR 8790 before the RC1 release?

#19 @wildworks
3 months ago

  • Keywords commit added

#20 @wildworks
3 months ago

  • Keywords changes-requested added; commit removed

I will change the keywords because new feedback has been added to the pull request.

#21 @haruncpi
3 months ago

  • Keywords changes-requested removed

@wildworks
Thanks! I’ve updated the PR with all the requested changes except one.
I’ve also added an explanation for why a simple foreach loop was used instead of array_values() — mainly for performance and clarity reasons, as suggested by @SirLouen in this commit https://github.com/WordPress/wordpress-develop/pull/8790/commits/4e32ecf82553567ffc7598296a04490dda85c95b

@wildworks commented on PR #8790:


3 months ago
#22

@haruncpi It appears the unit tests are failing. Could you investigate whether the problem lies in the logic itself or if the tests need to be updated?

@haruncpi commented on PR #8790:


3 months ago
#23

@t-hamano unit test case updated, now it's working fine.

#24 @johnjamesjacoby
3 months ago

Just reviewed & approved the PR.

If Sergey doesn't commit this, I'm happy to.

#25 @wildworks
3 months ago

  • Keywords commit added

@johnjamesjacoby Please do :)

#26 @SergeyBiryukov
3 months ago

  • Resolution set to fixed
  • Status changed from reviewing to closed

In 61210:

Users: Initialize the WP_User::$roles property as a sequential array.

Previously, if any roles were filtered out via array_filter() when assigning the WP_User::$roles property in WP_User::get_role_caps(), the resulting array could contain non-sequential numeric keys, which would then cause it to be treated as an object when JSON-encoded, e.g. in wp_localize_script(), instead of a proper array, breaking client-side expectations.

This commit ensures that the WP_User::$roles property is always treated as an array.

Follow-up to [2703], [2793], [22118].

Props haruncpi, peterwilsoncc, SirLouen, getsyash, wildworks, johnjamesjacoby, SergeyBiryukov.
Fixes #63427.

Note: See TracTickets for help on using tickets.