Skip to content

Feat/add role user access controls - #749

Open
Infinite-Null wants to merge 63 commits into
WordPress:developfrom
Infinite-Null:feat/add-role-user-access-controls
Open

Infinite-Null wants to merge 63 commits into
WordPress:developfrom
Infinite-Null:feat/add-role-user-access-controls

Conversation

@Infinite-Null

@Infinite-Null Infinite-Null commented Jun 19, 2026 •

Copy link
Copy Markdown
Contributor

Closes: #736

Description

This PR introduces Feature-Level Access Controls exclusively for Editor Experiments, allowing site administrators to restrict access to individual AI-powered editor features based on specific WordPress roles or individual users.

Testing Instructions

  1. Navigate to AI Settings and enable Enable AI.
  2. Open the Developer Tools menu (three dots) and select Access controls.
  3. Under the "Editor Experiments" section, verify that the Roles and Users configuration panel appears beneath the feature toggles.
  4. Check a few roles (e.g., Editor, Author) for a specific feature and click Save. Verify the save is successful.
  5. In the Users field, search for a specific user and select them from the dropdown. Click Save.
  6. Refresh the page and verify that your selected roles and users persisted and display their correct names.
  7. Turn off "Access controls" from the Developer Tools menu, and verify that all selections are cleared.
  8. Verify Enforcements: Log in to the site as a user who does not possess the selected role and is not the selected user. Open the block editor and verify that the restricted experiment is not accessible or visible to them.
  9. Log in as the specifically selected user or a user with the granted role, and verify the feature is fully accessible.
  10. Turn off "Access controls" from the Developer Tools menu, and verify that all selections are cleared and the feature becomes globally available again.

Screencast

User: Ankit Shah | Role: Editor

Screen.Recording.2026-06-22.at.1.35.17.PM-compressed.mp4

Use of AI Tools

AI assistance: Yes
Tool(s): Claude Code
Model(s): Sonnet 4.6
Used for: Validating bug, suggesting a fix.

Changelog Entry

Add - Role and User based access controls for experiments

Open WordPress Playground Preview

@Infinite-Null
Infinite-Null marked this pull request as draft June 19, 2026 13:05
@github-actions

github-actions Bot commented Jun 19, 2026 •

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: Infinite-Null <ankitkumarshah@git.wordpress.org>
Co-authored-by: jeffpaul <jeffpaul@git.wordpress.org>
Co-authored-by: dkotter <dkotter@git.wordpress.org>
Co-authored-by: henryperkins <htperkins@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@Infinite-Null
Infinite-Null marked this pull request as ready for review June 22, 2026 08:12
@codecov

codecov Bot commented Jun 29, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.61290% with 13 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.61%. Comparing base (3d08887) to head (cf0e6ef).
⚠️ Report is 3 commits behind head on develop.

Files with missing lines Patch % Lines
...iments/Alt_Text_Generation/Alt_Text_Generation.php 14.28% 6 Missing ⚠️
includes/Settings/Settings_Registration.php 92.30% 4 Missing ⚠️
includes/Abstracts/Abstract_Feature.php 78.57% 3 Missing ⚠️
Additional details and impacted files
@@              Coverage Diff              @@
##             develop     #749      +/-   ##
=============================================
+ Coverage      80.87%   81.61%   +0.73%     
- Complexity      2998     3089      +91     
=============================================
  Files            124      130       +6     
  Lines          11987    12368     +381     
=============================================
+ Hits            9695    10094     +399     
+ Misses          2292     2274      -18     
Flag Coverage Δ
unit 81.61% <91.61%> (+0.73%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@jeffpaul

Copy link
Copy Markdown
Member

@Infinite-Null FYI some merge conflicts to resolve to keep this moving along

@jeffpaul jeffpaul mentioned this pull request Jul 13, 2026
27 of 28 tasks
@Infinite-Null
Infinite-Null requested a review from a team as a code owner July 14, 2026 13:11
@Infinite-Null

Copy link
Copy Markdown
Contributor Author

I’ve addressed all the feedback and updated the PR accordingly. Thank you so much for taking the time to review it and for the detailed feedback.

Whenever you get a chance, could you please take another look.

cc: @jeffpaul @dkotter

@Infinite-Null
Infinite-Null requested a review from dkotter August 20, 2026 09:19
@jeffpaul

Copy link
Copy Markdown
Member
  1. I'm not seeing this as available on the Image Generation and Editing feature.
  2. I think we can remove Subscriber as a role listed as none of these would be exposed for those folks anyway (perhaps Contributor as well?).
  3. I think we should probably expose these for the Comment Moderation and Suggest Reply experiments as well. Otherwise maybe add a note to the Admin Experiments text that notes that all experiments unless otherwise specified are scoped to admins-only?

@Infinite-Null

Copy link
Copy Markdown
Contributor Author

Hi @jeffpaul, Thanks for the feedback! I’ve updated the PR accordingly. I’ve also removed both Subscriber and Contributor from the listed roles.

@Infinite-Null

Copy link
Copy Markdown
Contributor Author

The failing tests seem to be unrelated to the changes introduced in this PR.

@dkotter dkotter left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Handful of things that still need addressed here.

I've tested and things appear to work fine though I do have a question/concern on the default state.

When I first toggle on Access controls no user roles are checked. This makes it appear like no one should have access but our default here is if nothing is set, everyone has access. I wonder if that should be the default?

Either we need to make that more clear in the UI (maybe you need to turn on Access controls and then separately toggle that on for each individual feature? Or just better messaging here if nothing is selected it tells you everyone has access?).

Or we need some smart defaults (maybe have Administrator selected by default for all of these). And if someone deselects all options, that feature is now no longer accessible to anyone.

Otherwise the default state doesn't match user expectations and you also have the state where someone checks Administrator and later unchecks that and they'll assume no one has access but in reality, everyone has access.


printf(
'<div class="ai-alt-text-media-actions" style="margin-top: 16px;">' .
'<button id="ai-alt-text-generate-button" class="button button-secondary" type="button" data-attachment-id="%1$d">%2$s</button>' .

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Any reason for these spacing changes? I'm assuming just some automated linting but would keep diffs cleaner to revert that

* {@inheritDoc}
*/
public function register(): void {
if ( ! \WordPress\AI\current_user_can_access_feature( $this->get_id() ) ) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Wondering if there's a way we could implement this in the Abstract_Feature class instead of having to duplicate this in every individual feature class? Just seems like a lot of duplicated code that could maybe be handled differently

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

As a practical example here, there are a number of features that never call current_user_can_access_feature right now, even though we show those settings in the UI.

Seems we're missing:

  • Slug generation
  • Type Ahead
  • Content Translation
  • Image Generation

* {@inheritDoc}
*/
public function register(): void {
if ( ! \WordPress\AI\current_user_can_access_feature( $this->get_id() ) ) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If we do keep this here, we should follow the approach of doing

use function WordPress\AI\current_user_can_access_feature;

So this can then be simplified to just current_user_can_access_feature()

}

/**
* Registers experiment infrastructure.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
* Registers experiment infrastructure.
* {@inheritDoc}

* @since 1.2.0
*/
public function register(): void {
$this->register_infrastructure();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is this needed? Doesn't seem we override that here

}, [ effectiveUsers, selectedUserMap ] );

// Seed selectedUserMap with users returned from the API (capped at
// MAX_USERS i.e. 10 at a time). If more than

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

So there may be a bug here if someone actually wants to set more than 10 users. I haven't verified directly but looking at the code, it seems anything set over 10 will be dropped on save

}

$users = array();
$wp_users = get_users( $get_users_args );

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We have a few restrictions in place on contributor and subscriber roles but we don't apply that here, meaning I can select a user that is a subscriber even though that role is rejected in other places. I think the user query here needs to remove those users

Comment thread includes/helpers.php
Comment on lines +977 to +979
if ( array_intersect( $current_user->roles, array( 'subscriber', 'contributor' ) ) ) {
return false;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What if a site wants to add subscribers or contributors and give them access? At the moment, that's not possible. Wondering if we need to rework this function so all of our return values pass through the wpai_user_has_role_access filter?

* @since x.x.x
*/
protected function register_infrastructure(): void {
$this->register_post_meta();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think enqueue_block_assets needs to go here as well as that loads front-end assets


register_setting(
self::OPTION_GROUP,
"wpai_feature_{$feature_id}_users",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This setting will be included in our settings export. That may be fine but this means if someone exports from one site to another and they've given specific users access, the user ID's are likely not the same on the site they are importing too and could result in them giving access to users they don't intend to. May want to consider not exporting this data

@jeffpaul jeffpaul mentioned this pull request Sep 29, 2026
2 of 24 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Expose role/user access controls per feature/experiment

4 participants