Skip to content

Add a core/settings ability#691

Merged
dkotter merged 27 commits into
developfrom
add/core-settings-ability
Jun 22, 2026
Merged

Add a core/settings ability#691
dkotter merged 27 commits into
developfrom
add/core-settings-ability

Conversation

@jorgefilipecosta

@jorgefilipecosta jorgefilipecosta commented Jun 9, 2026

Copy link
Copy Markdown
Member

Summary

Part of: #40

Adds the core/settings ability to the plugin, matching the equivalent WordPress core change in WordPress/wordpress-develop#12141. Read-only; returns settings flagged show_in_abilities as a flat name => value map (metadata in the output schema), filterable by group or slugs (mutually exclusive). Requires manage_options.

  • Settings — registers core/settings. Kept near-identical to core's WP_Settings_Abilities (differences marked with // Plugin: comments). It registers at priority 11 and unregisters any core-provided copy first, so the plugin's version wins when both are present.
  • Show_In_Abilities — seeds the show_in_abilities flag onto a curated set of core settings via the register_setting_args filter, so the ability returns data on a stock site before core ships the flag. Object-type-agnostic, for future reuse (post types, meta).

Test plan

Prerequisite: the client-side Abilities API (@wordpress/abilities) is only loaded in the block editor once an AI experiment is enabled — the experiment's editor script declares the @wordpress/abilities + @wordpress/core-abilities modules as dependencies, which adds them to the editor's import map. So first enable an experiment such as Excerpt Generation and open a new post (post-new.php). Then, in the browser console:

const { executeAbility } = await import( '@wordpress/abilities' );

await executeAbility( 'core/settings', {} );
await executeAbility( 'core/settings', { group: 'reading' } );
await executeAbility( 'core/settings', { settings: [ 'blogname' ] } );
  • Flat name => value map with typed values
  • group / slugs filters work; supplying both is rejected
  • Non-admin (no manage_options) is denied

Integration tests added under tests/Integration/Includes/Abilities/. An end-to-end test (tests/e2e/specs/abilities/core-settings.spec.js) drives the ability through the client-side Abilities API from the editor (with an experiment enabled).

Open WordPress Playground Preview

Core PR: WordPress/wordpress-develop#12141

@github-actions

github-actions Bot commented Jun 9, 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: jeffpaul <jeffpaul@git.wordpress.org>
Co-authored-by: jorgefilipecosta <jorgefilipecosta@git.wordpress.org>
Co-authored-by: gziolo <gziolo@git.wordpress.org>
Co-authored-by: dkotter <dkotter@git.wordpress.org>
Co-authored-by: justlevine <justlevine@git.wordpress.org>

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

@jorgefilipecosta jorgefilipecosta force-pushed the add/core-settings-ability branch from 4c74702 to 413c6a4 Compare June 12, 2026 14:58
@gziolo gziolo self-requested a review June 15, 2026 12:31
@jorgefilipecosta jorgefilipecosta force-pushed the add/core-settings-ability branch 2 times, most recently from 52920c8 to a431013 Compare June 15, 2026 19:19
@github-actions

github-actions Bot commented Jun 15, 2026

Copy link
Copy Markdown

✅ WordPress Plugin Check Report

✅ Status: Passed

📊 Report

All checks passed! No errors or warnings found.


🤖 Generated by WordPress Plugin Check Action • Learn more about Plugin Check

Comment thread includes/Abilities/Settings/Settings.php Outdated
Comment thread includes/Abilities/Settings/Settings.php
Comment thread includes/Abilities/Settings/Settings.php Outdated
Comment thread includes/Abilities/Settings/Settings.php Outdated
Comment thread includes/Abilities/Settings/Settings.php
Comment thread tests/Integration/Includes/Abilities/Settings/SettingsTest.php Outdated
@gziolo

gziolo commented Jun 17, 2026

Copy link
Copy Markdown
Member

@jeffpaul or @dkotter, what are the conditions for this new (and other planned) ability to get included in the AI plugin? Code-wise it's shaping up nicely according to what we discussed in #40 and in https://core.trac.wordpress.org/ticket/64605. The idea would be to land it in the plugin first for early testing, and then put it in WordPress core.

Comment thread includes/Abilities/Settings/Settings.php Outdated
Comment thread includes/Abilities/Settings/Settings.php Outdated
*/
public static function settings_map(): array {
return array(
// General.

@gziolo gziolo Jun 17, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Noting that some settings will get duplicated between core/get-site-info and core/settings.

It seems like we will have to follow up in WP core to replicate show_in_abilities check inside core/get-site-info once that concept is there.

More broadly, show_in_abilities might probably need more variety, for example:

`show_in_abilities` => true,
`show_in_abilities` => false,
`show_in_abilities` => 'read',
`show_in_abilities` => 'write',

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yah show_in_abilities will pass by some evolution in some cases like show_in_rest it may also need to pass schema information, and read/write distinction may also be a good approach. Would you want me to already include read/write distinction as part of this PR or do you think this is something for the future?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

For this PR, we don't need to do anything yet, as true is more than enough. However, I can see how core/manage-settings might benefit from a more granular decision process of what should be editable. Some settings should not be changed in isolation, as that could break the site.

@jorgefilipecosta jorgefilipecosta force-pushed the add/core-settings-ability branch from a9811cb to 66c05ed Compare June 17, 2026 13:25
jorgefilipecosta added a commit that referenced this pull request Jun 17, 2026
Mirrors the fixes from the core/settings review (#691) that also apply here:
- Memoize the exposed post types so the input schema and the permission/execute
  callbacks derive from a single walk of the registered post types.
- Default the input schema to an empty object so the type:object default serializes as {}.
- Use __() instead of esc_html__(), and @SInCE x.x.x per CONTRIBUTING.md.
- Harden input/value handling (type guards, capability resolver, non-negative int
  helper) so the new code passes PHPStan at the strictest level.

The `content` ability-category fallback is kept on purpose: unlike `site`, `content`
is a new category not present on the plugin's minimum WordPress (7.0).
@jorgefilipecosta

Copy link
Copy Markdown
Member Author

Hi @gziolo, thank you a lot for the review I think I applied/answered all the feedback.

jorgefilipecosta and others added 4 commits June 19, 2026 16:15
'settings' reads more naturally than 'slugs' for an ability about settings:
executeAbility( 'core/settings', { settings: [ 'blogname' ] } ).
Add an e2e sample plugin that registers a setting with show_in_abilities, map it
in .wp-env.test.json, and assert the core/settings ability returns it.
- Simplify the input schema: replace the group-XOR-name `oneOf` with optional,
  combinable `group` and `fields` filters (rename `settings` -> `fields`, matching
  core/get-site-info). Default to an empty object so the type:object schema default
  serializes as {}.
- Memoize the exposed settings so the input/output schema and execute() derive from a
  single walk of get_registered_settings().
- Cast object-typed values to objects so they serialize as {} (not []) and satisfy the
  output schema validated by execute().
- Drop the redundant `site` ability-category fallback (core registers it and the plugin
  requires WP 7.0).
- Use __() instead of esc_html__() for ability strings, and @SInCE x.x.x per CONTRIBUTING.md.
- Harden value handling so the new code passes PHPStan at the strictest level.
- Tests: assert keys order-insensitively and cover combined group+fields filtering.
Co-authored-by: Darin Kotter <darin.kotter@gmail.com>
@jorgefilipecosta jorgefilipecosta force-pushed the add/core-settings-ability branch from 7761b51 to fe8c56a Compare June 19, 2026 15:15
…dation

- Correct the get_settings_input_schema() @param annotations from list[]
  to list<string> ($groups and $field_names are lists of strings).
- Fold the show_in_abilities sample setting into the existing
  e2e-request-mocking plugin instead of a separate e2e-sample-settings
  plugin, and drop the now-unused plugin from .wp-env.test.json.
Generalize the former e2e-request-mocking plugin into a shared E2E
support plugin now that it also registers test fixtures:

- Rename the directory and main file to tests/e2e-testing/ and update
  the plugin header name to "E2E Testing".
- Point .wp-env.test.json and the architecture doc at the new path.
- Update the activate/deactivate slug (e2e-test-request-mocking ->
  e2e-testing) in the global setup and specs, plus fixture-path comments.
- Document the show_in_abilities timing contract: the core/settings
  ability snapshots the exposed set at wp_abilities_api_init, so a
  setting must be flagged before that hook to be picked up.
- Drop the unreachable recompute branch in execute_get_settings(); the
  cache is always populated at registration. Keep a defensive null guard
  so the unexpected case is explicit and stays PHPStan-max clean.
@jorgefilipecosta

Copy link
Copy Markdown
Member Author

Hi @gziolo your feedback was applied.

I went through all the changes applied, and everything's addressed. Thank you!

Two small, non-blocking follow-ups:

  • Document the "register setting before wp_abilities_api_init" contract. The exposed set is snapshotted at registration, so a setting flagged with show_in_abilities that registers after that hook won't show up. Reasonable constraint worth a sentence in the show_in_abilities docs so integrators know their register_setting() has to run before abilities init.

It was documented on Show_In_Abilities class.

  • Drop the fallback in execute_get_settings().
    $settings = self::$exposed_settings ?? self::get_exposed_settings();
    

A registered ability always has the cache set, so the recompute branch is unreachable. $settings = self::$exposed_settings; is enough. If it's ever null you'd want to know and act on it.

Fixed 👍

@jorgefilipecosta

Copy link
Copy Markdown
Member Author

Hi @dkotter all your feedback was applied. Thank your for the review!

Comment thread includes/Abilities/Show_In_Abilities.php Outdated
Comment thread includes/Abilities/Show_In_Abilities.php Outdated
Comment thread includes/Abilities/Show_In_Abilities.php Outdated
Comment thread includes/Abilities/Settings/Settings.php
Comment thread includes/Abilities/Settings/Settings.php
jorgefilipecosta and others added 3 commits June 22, 2026 09:45
Co-authored-by: Dovid Levine <justlevine@gmail.com>
Address review feedback: make Show_In_Abilities instance-based instead
of all public static, matching the rest of the plugin (e.g. the Posts
ability). The class is now final; register() and mark_setting() are
instance methods (mark_setting stays public as a register_setting_args
callback), and settings_map() is private. Main bootstraps it via
( new Show_In_Abilities() )->register(), and the tests hold the instance
so the filter is detached through the same object on tear down.
Address review feedback: make Settings final and tighten member
visibility so the intended surface is enforced rather than only
documented via @internal. Only the externally-invoked entry points stay
public static (init(), register(), execute_get_settings(),
has_permission()); register_get_settings() and the shared helpers become
private static, and CATEGORY becomes a private const. Method bodies and
the static cache are unchanged, keeping parity with the core
WP_Settings_Abilities class (mirrored there in lockstep).
@jorgefilipecosta

Copy link
Copy Markdown
Member Author

Thank you a lot for the review @justlevine and raising good points I applied/answered your feedback 👍

Per review feedback, move register() and the ability callbacks
(execute_get_settings(), has_permission()) from public static to public
instance methods, and the internal helpers to private instance methods.
The exposed-settings cache is now an instance property. init() stays a
public static entry point and hooks a fresh instance. Mirrored in the core PR.
Drop the static init() entry point; Main now bootstraps the ability with
( new Settings_Ability() )->init(), matching the rest of the plugin's
instance-based wiring. The class is now fully instance-based apart from
the CATEGORY constant.
@jorgefilipecosta

Copy link
Copy Markdown
Member Author

@justlevine I think I applied the remaining point of removing static from settings.

Bring SettingsTest in line with core's wpRegisterCoreSettingsAbility:
adopt the core test_core_settings_* method names, add the assertions core
makes but the plugin was missing (show_in_rest + destructive on the
registration test; schema type, the default key, and the general/
posts_per_page enum values on the input-schema test), and add a test that
a custom show_in_abilities-flagged setting is exposed in the input fields
enum, the output schema, and execute() output (previously the plugin had
no output_schema coverage at all).

Also document the settings_map() <-> core register_initial_settings()
sync contract in Show_In_Abilities.
@codecov

codecov Bot commented Jun 22, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.89041% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 76.59%. Comparing base (77f116e) to head (a28f881).
⚠️ Report is 57 commits behind head on develop.

Files with missing lines Patch % Lines
includes/Abilities/Settings/Settings.php 94.87% 6 Missing ⚠️
Additional details and impacted files
@@              Coverage Diff              @@
##             develop     #691      +/-   ##
=============================================
+ Coverage      74.45%   76.59%   +2.14%     
- Complexity      1740     1822      +82     
=============================================
  Files             85       87       +2     
  Lines           7521     7735     +214     
=============================================
+ Hits            5600     5925     +325     
+ Misses          1921     1810     -111     
Flag Coverage Δ
unit 76.59% <95.89%> (+2.14%) ⬆️

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.

@justlevine

Copy link
Copy Markdown
Contributor

@justlevine I think I applied the remaining point of removing static from settings.

Yup. Codewise LGTM, I have some upstream concerns regarding the naming aspects of core/settings and core/manage-settings, but that shouldn't stop merging in the Experiment here 👍

@dkotter dkotter merged commit 27e202b into develop Jun 22, 2026
24 checks passed
@dkotter dkotter deleted the add/core-settings-ability branch June 22, 2026 20:24
}

$group = isset( $input['group'] ) && is_string( $input['group'] ) ? $input['group'] : '';
$fields = isset( $input['fields'] ) && is_array( $input['fields'] ) ? $input['fields'] : array();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I was checking WP CLI and their wp option list command. Options are a lower-level concept than settings, but they have some things in common. I have some second thoughts about using fields in this specific context, as an individual setting has more fields than a value, for example: group.

On one hand, the current approach aligns closely with core/get-site-info and how it operates, but that's a curated set of site settings.

On the other hand, if we look at #739 and core/content, it's evident that fields are used there for individual rows rather than filtering which rows to pick.

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.

5 participants