Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
613527a
feat: add access control settings for features with REST API and UI i…
Infinite-Null Jun 17, 2026
1ca4935
refactor: implement local state for role and user tokens in AccessCon…
Infinite-Null Jun 18, 2026
013f157
refactor: replace Stack with Flex layout in AccessControlSettings and…
Infinite-Null Jun 18, 2026
c1f6160
refactor: replace FormTokenField with CheckboxControl for role select…
Infinite-Null Jun 18, 2026
c134007
feat: implement debounced server-side user search and persistent sele…
Infinite-Null Jun 18, 2026
a0da3db
Merge branch 'develop' into feat/add-role-user-access-controls
Infinite-Null Jun 18, 2026
1e6c0ae
feat: update access control settings input to use 40px default sizing…
Infinite-Null Jun 18, 2026
4832b12
feat: enforce access control checks for experiment registration and u…
Infinite-Null Jun 19, 2026
bd2ba2a
feat: restrict access control settings to non-admin features
Infinite-Null Jun 19, 2026
615d44b
refactor: add type hinting to REST controller and simplify role verif…
Infinite-Null Jun 19, 2026
25ba583
refactor: format AccessControlSettings conditional rendering for bett…
Infinite-Null Jun 19, 2026
7f28d57
refactor: improve Yoda condition readability in user search query logic
Infinite-Null Jun 19, 2026
3f11239
refactor: remove redundant user login check from access verification …
Infinite-Null Jun 19, 2026
d6ad4d7
refactor: clean up AccessControlSettings hook usage and implement log…
Infinite-Null Jun 22, 2026
c37c5bb
style: refactor code formatting and indentation in access control com…
Infinite-Null Jun 22, 2026
e26725c
fix: sync selected user display names and filter current selections f…
Infinite-Null Jun 22, 2026
993bb99
refactor: reformat loop block in AccessControlSettings and update loc…
Infinite-Null Jun 22, 2026
f1e5478
Merge branch 'develop' into feat/add-role-user-access-controls
Infinite-Null Jun 22, 2026
51fa6bf
refactor: update AccessControlSettings layout to use CSS grid and the…
Infinite-Null Jun 29, 2026
30bf0fd
refactor: simplify gridTemplateColumns formatting in AccessControlSet…
Infinite-Null Jun 29, 2026
e995019
Merge branch 'develop' into feat/add-role-user-access-controls
Infinite-Null Jul 14, 2026
40df60f
Add Advanced Settings toggle to Developer Tools menu (#842)
Infinite-Null Jul 10, 2026
e94080d
test: add e2e test for content summarization access controls and intr…
Infinite-Null Jul 15, 2026
e5c207c
test: add e2e test case for user-based access control in Content Summ…
Infinite-Null Jul 16, 2026
2c1f94d
test: add integration tests for the roles-users REST controller
Infinite-Null Jul 17, 2026
bd767b6
refactor: standardize PHP coding style and update AI access control i…
Infinite-Null Aug 8, 2026
a84fb1a
Merge branch 'develop' into feat/add-role-user-access-controls
Infinite-Null Aug 17, 2026
de1d92c
feat: add settings import and export functionality to the AI develope…
Infinite-Null Aug 17, 2026
1249daf
refactor: fix indentation and formatting in AI settings page component
Infinite-Null Aug 17, 2026
2359164
refactor: apply codebase-wide coding standards and formatting improve…
Infinite-Null Aug 17, 2026
c391e6b
refactor: introduce register_infrastructure to decouple global WordPr…
Infinite-Null Aug 17, 2026
6aec265
refactor: replace inline styles with CSS
Infinite-Null Aug 18, 2026
0246755
docs: clarify AccessControlSettings user map seeding behavior and API…
Infinite-Null Aug 18, 2026
45a14d3
feat: wrap AI settings page with RolesUsersProvider for improved acce…
Infinite-Null Aug 18, 2026
55b2b1b
feat: add wpai_user_has_role_access filter to user role verification …
Infinite-Null Aug 18, 2026
cb3fbda
fix: update docblock type hint for current_user parameter to \WP_User
Infinite-Null Aug 18, 2026
3dcf3ac
test: add integration tests for user and role restrictions in current…
Infinite-Null Aug 18, 2026
e052399
refactor: consolidate and modernize Roles_Users_Controller integratio…
Infinite-Null Aug 18, 2026
08e1d30
test: update user deletion to target specific test user ID instead of…
Infinite-Null Aug 18, 2026
a603c6c
refactor: move RequestUtils import to the top level in settings.spec.js
Infinite-Null Aug 18, 2026
ade4fec
test: verify success snackbar and blur input field after saving acces…
Infinite-Null Aug 18, 2026
831ea51
fix: sanitize feature access settings
Infinite-Null Aug 19, 2026
08d2caf
feat: add validation and sanitization callbacks for feature roles and…
Infinite-Null Aug 19, 2026
4ec1a4f
refactor: remove unnecessary Fragment wrapper from AccessControlSetti…
Infinite-Null Aug 19, 2026
8f8eab9
fix: remove conditional spinner from save button to prevent layout shift
Infinite-Null Aug 19, 2026
2b786d1
test: add registration tests and cleanup for roles and users settings
Infinite-Null Aug 19, 2026
e51cd85
feat: reset unrelated changes
Infinite-Null Aug 19, 2026
6062b1f
chore: update @since version tags to x.x.x in Settings_IO_ControllerTest
Infinite-Null Aug 19, 2026
5841ce5
refactor: reset unrelated changes
Infinite-Null Aug 19, 2026
ad722d6
chore: remove unrealated changes
Infinite-Null Aug 19, 2026
bedff1a
refactor: simplify ability unregistration in HelpersTest
Infinite-Null Aug 19, 2026
72b03ec
test: add helper to clear feature access settings and integrate into …
Infinite-Null Aug 20, 2026
b8205a5
Merge branch 'develop' into feat/add-role-user-access-controls
Infinite-Null Aug 20, 2026
b0e10f8
Merge branch 'develop' into feat/add-role-user-access-controls
jeffpaul Aug 26, 2026
0fa70ce
feat: restrict access for subscribers and contributors, and extend ac…
Infinite-Null Aug 29, 2026
f63e445
refactor: simplify formatting of FEATURES_BY_SETTING map definition
Infinite-Null Aug 29, 2026
6d4f76b
test: restrict subscriber and contributor access to features and excl…
Infinite-Null Aug 31, 2026
f08df20
Merge branch 'develop' into feat/add-role-user-access-controls
Infinite-Null Sep 8, 2026
d3312b2
Merge branch 'develop' into feat/add-role-user-access-controls
Infinite-Null Sep 9, 2026
7f2ab27
Merge branch 'develop' into feat/add-role-user-access-controls
dkotter Sep 28, 2026
cf0e6ef
refactor: centralize access control and feature registration in Abstr…
Infinite-Null Sep 29, 2026
d64e926
feat: set default feature access control roles to administrator, edit…
Infinite-Null Sep 30, 2026
048c2a2
refactor: remove ID fallback parsing from label in AccessControlSettings
Infinite-Null Sep 30, 2026
5af7b77
refactor: separate access control settings into a distinct option gro…
Infinite-Null Oct 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 73 additions & 2 deletions includes/Abstracts/Abstract_Feature.php
Original file line number Diff line number Diff line change
Expand Up @@ -319,10 +319,81 @@ final public static function get_field_option_name( string $option_name ): strin
return 'wpai_feature_' . static::get_id() . '_field_' . $option_name;
}

/**
* Registers WordPress infrastructure that must run for all users.
*
* Example use cases:
* - Registering post/comment meta via register_meta() or register_post_meta()
* so the REST API schema is always available.
* - Attaching plugin-deactivation hooks to clear transient caches.
*
* @since x.x.x
*
* @return void
*/
protected function register_infrastructure(): void {
// Default implementation is a no-op.
}

/**
* Checks whether the current user can access this feature.
*
* @since x.x.x
*
* @return bool True if current user has access, false otherwise.
*/
public function current_user_can_access(): bool {
if ( ! $this->supports_access_control() ) {
return true;
}

return \WordPress\AI\current_user_can_access_feature( static::get_id() );
}

/**
* Checks whether access control applies to this feature.
*
* Admin-category features do not have access controls by default,
* except for features explicitly allowing it (e.g. comment-moderation, suggest-reply).
*
* @since x.x.x
*
* @return bool True if feature supports access control, false otherwise.
*/
public function supports_access_control(): bool {
return 'admin' !== $this->category
|| in_array( static::get_id(), array( 'comment-moderation', 'suggest-reply' ), true );
}

/**
* Registers user-facing feature hooks and functionality.
*
* Subclasses should override this method to register abilities,
* scripts, UI elements, and actions that require user access.
*
* @since x.x.x
*
* @return void
*/
protected function register_feature(): void {
// Default implementation is a no-op.
}

/**
* {@inheritDoc}
*
* Must be implemented by child classes to set up hooks and functionality.
* Runs infrastructure setup for all users, then registers feature
* hooks if the current user has access to this feature.
*
* @since 0.6.0
*/
abstract public function register(): void;
public function register(): void {
$this->register_infrastructure();

if ( ! $this->current_user_can_access() ) {
return;
}

$this->register_feature();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* @package WordPress\AI
*/

declare( strict_types=1 );
declare(strict_types=1);

namespace WordPress\AI\Experiments\Alt_Text_Generation;

Expand Down Expand Up @@ -78,7 +78,7 @@ protected function load_metadata(): array {
/**
* {@inheritDoc}
*/
public function register(): void {
protected function register_feature(): void {
add_action( 'wp_abilities_api_init', array( $this, 'register_abilities' ) );
add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_editor_assets' ) );
add_action( 'wp_enqueue_media', array( $this, 'enqueue_media_frame_assets' ) );
Expand Down Expand Up @@ -253,9 +253,9 @@ public function render_attachment_meta_box( \WP_Post $post ): void {

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

'<span class="spinner" aria-hidden="true" style="margin-inline-start: 8px; float: none;"></span>' .
'<p class="description" aria-live="polite" style="margin-top: 10px; line-height: 1.3;"></p>' .
'<button id="ai-alt-text-generate-button" class="button button-secondary" type="button" data-attachment-id="%1$d">%2$s</button>' .
'<span class="spinner" aria-hidden="true" style="margin-inline-start: 8px; float: none;"></span>' .
'<p class="description" aria-live="polite" style="margin-top: 10px; line-height: 1.3;"></p>' .
'</div>',
absint( $post->ID ),
esc_html( $button_text )
Expand Down Expand Up @@ -409,9 +409,9 @@ public function add_button_to_media_modal( array $fields, ?\WP_Post $post ): arr
'show_in_edit' => false,
'html' => sprintf(
'<div class="ai-alt-text-media-actions">' .
'<button id="ai-alt-text-generate-button" class="button button-secondary" type="button" data-attachment-id="%1$d">%2$s</button>' .
'<span class="spinner" aria-hidden="true" style="margin-inline-start: 8px; float: none;"></span>' .
'<p class="description" aria-live="polite" style="margin-top: 6px; font-size: 12px;"></p>' .
'<button id="ai-alt-text-generate-button" class="button button-secondary" type="button" data-attachment-id="%1$d">%2$s</button>' .
'<span class="spinner" aria-hidden="true" style="margin-inline-start: 8px; float: none;"></span>' .
'<p class="description" aria-live="polite" style="margin-top: 6px; font-size: 12px;"></p>' .
'</div>',
absint( $post->ID ),
esc_html( $button_text )
Expand Down
15 changes: 11 additions & 4 deletions includes/Experiments/Comment_Moderation/Comment_Moderation.php
Original file line number Diff line number Diff line change
Expand Up @@ -307,18 +307,25 @@ protected function load_metadata(): array {
);
}

/**
* {@inheritDoc}
*
* @since x.x.x
*/
protected function register_infrastructure(): void {
// Moderate new comments automatically in the background.
add_action( 'wp_insert_comment', array( $this, 'moderate_comment' ) );
}

/**
* {@inheritDoc}
*
* @since 0.9.0
*/
public function register(): void {
protected function register_feature(): void {
// Register abilities.
add_action( 'wp_abilities_api_init', array( $this, 'register_abilities' ) );

// Moderate new comments.
add_action( 'wp_insert_comment', array( $this, 'moderate_comment' ) );

// Add columns to comments list table.
add_filter( 'manage_edit-comments_columns', array( $this, 'add_columns' ) );
add_action( 'manage_comments_custom_column', array( $this, 'render_column' ), 10, 2 );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* @package WordPress\AI
*/

declare( strict_types=1 );
declare(strict_types=1);

namespace WordPress\AI\Experiments\Content_Classification;

Expand All @@ -31,6 +31,7 @@
*/
class Content_Classification extends Abstract_Feature {


/**
* The default taxonomy strategy.
*
Expand Down Expand Up @@ -97,7 +98,7 @@ protected function load_metadata(): array {
/**
* {@inheritDoc}
*/
public function register(): void {
protected function register_feature(): void {
add_action( 'wp_abilities_api_init', array( $this, 'register_abilities' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) );
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ protected function load_metadata(): array {
/**
* {@inheritDoc}
*/
public function register(): void {
protected function register_feature(): void {
add_action( 'wp_abilities_api_init', array( $this, 'register_abilities' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) );
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ protected function load_metadata(): array {
*
* @since 1.3.0
*/
public function register(): void {
protected function register_feature(): void {
add_action( 'wp_abilities_api_init', array( $this, 'register_abilities' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) );
add_action( 'enqueue_block_assets', array( $this, 'enqueue_block_assets' ) );
Expand Down
15 changes: 13 additions & 2 deletions includes/Experiments/Editorial_Notes/Editorial_Notes.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* @package WordPress\AI
*/

declare( strict_types=1 );
declare(strict_types=1);

namespace WordPress\AI\Experiments\Editorial_Notes;

Expand All @@ -31,6 +31,7 @@
*/
class Editorial_Notes extends Abstract_Feature {


/**
* {@inheritDoc}
*/
Expand All @@ -52,9 +53,19 @@ protected function load_metadata(): array {
/**
* {@inheritDoc}
*/
public function register(): void {
protected function register_feature(): void {
add_action( 'wp_abilities_api_init', array( $this, 'register_abilities' ) );
add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_assets' ) );
}

/**
* {@inheritDoc}
*
* Registers comment meta and the REST insert filter.
*
* @since x.x.x
*/
protected function register_infrastructure(): void {
add_filter( 'rest_pre_insert_comment', array( $this, 'maybe_set_ai_author' ), 10, 2 );

register_meta(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* @package WordPress\AI
*/

declare( strict_types=1 );
declare(strict_types=1);

namespace WordPress\AI\Experiments\Editorial_Updates;

Expand All @@ -29,6 +29,7 @@
*/
class Editorial_Updates extends Abstract_Feature {


/**
* {@inheritDoc}
*/
Expand All @@ -54,7 +55,7 @@ protected function load_metadata(): array {
*
* @since 0.8.0
*/
public function register(): void {
protected function register_feature(): void {
add_action( 'wp_abilities_api_init', array( $this, 'register_abilities' ) );
add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_assets' ) );
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* @package WordPress\AI
*/

declare( strict_types=1 );
declare(strict_types=1);

namespace WordPress\AI\Experiments\Excerpt_Generation;

Expand All @@ -27,6 +27,7 @@
*/
class Excerpt_Generation extends Abstract_Feature {


/**
* {@inheritDoc}
*/
Expand All @@ -48,7 +49,7 @@ protected function load_metadata(): array {
/**
* {@inheritDoc}
*/
public function register(): void {
protected function register_feature(): void {
add_action( 'wp_abilities_api_init', array( $this, 'register_abilities' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) );
}
Expand Down
18 changes: 14 additions & 4 deletions includes/Experiments/Meta_Description/Meta_Description.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* @package WordPress\AI
*/

declare( strict_types=1 );
declare(strict_types=1);

namespace WordPress\AI\Experiments\Meta_Description;

Expand All @@ -31,6 +31,7 @@
*/
class Meta_Description extends Abstract_Feature {


/**
* {@inheritDoc}
*/
Expand All @@ -54,13 +55,22 @@ protected function load_metadata(): array {
*
* @since 0.7.0
*/
public function register(): void {
protected function register_feature(): void {
add_action( 'wp_abilities_api_init', array( $this, 'register_abilities' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) );
add_action( 'deactivated_plugin', array( $this, 'clear_active_plugin_cache' ) );
}

$this->maybe_output_meta_description();
/**
* {@inheritDoc}
*
* Registers post meta, the deactivated_plugin cache hook, and frontend meta description output.
*
* @since x.x.x
*/
protected function register_infrastructure(): void {
$this->register_post_meta();
$this->maybe_output_meta_description();
add_action( 'deactivated_plugin', array( $this, 'clear_active_plugin_cache' ) );

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 we want to run maybe_output_meta_description in here as well, otherwise turning on user restriction will mean those descriptions never get output on the front-end

}

/**
Expand Down
2 changes: 1 addition & 1 deletion includes/Experiments/Slug_Generation/Slug_Generation.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ protected function load_metadata(): array {
*
* @since 1.3.0
*/
public function register(): void {
protected function register_feature(): void {
add_action( 'wp_abilities_api_init', array( $this, 'register_abilities' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) );
}
Expand Down
2 changes: 1 addition & 1 deletion includes/Experiments/Suggest_Reply/Suggest_Reply.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ protected function load_metadata(): array {
*
* @since 1.2.0
*/
public function register(): void {
protected function register_feature(): void {
add_action( 'wp_abilities_api_init', array( $this, 'register_abilities' ) );
add_filter( 'comment_row_actions', array( $this, 'add_row_action' ), 10, 2 );
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) );
Expand Down
18 changes: 14 additions & 4 deletions includes/Experiments/Summarization/Summarization.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* @package WordPress\AI
*/

declare( strict_types=1 );
declare(strict_types=1);

namespace WordPress\AI\Experiments\Summarization;

Expand Down Expand Up @@ -69,11 +69,9 @@ protected function load_metadata(): array {
/**
* {@inheritDoc}
*/
public function register(): void {
$this->register_post_meta();
protected function register_feature(): void {
add_action( 'wp_abilities_api_init', array( $this, 'register_abilities' ) );
add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_assets' ), 5 );
add_action( 'enqueue_block_assets', array( $this, 'enqueue_block_assets' ) );

add_action( 'load-edit.php', array( $this, 'register_bulk_action_hooks_for_screen' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'maybe_enqueue_bulk_assets' ) );
Expand Down Expand Up @@ -121,6 +119,18 @@ public function register_bulk_action_hooks_for_screen(): void {
add_filter( "handle_bulk_actions-edit-{$post_type}", array( $this, 'handle_bulk_action' ), 10, 3 );
}

/**
* {@inheritDoc}
*
* Registers post meta.
*
* @since x.x.x
*/
protected function register_infrastructure(): void {
add_action( 'enqueue_block_assets', array( $this, 'enqueue_block_assets' ) );
$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 any needed post meta.
*
Expand Down
Loading