Abilities API: Add a core/read-settings ability#12141
Abilities API: Add a core/read-settings ability#12141jorgefilipecosta wants to merge 16 commits into
Conversation
|
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 Core Committers: Use this line as a base for the props when committing in SVN: To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
Test using WordPress PlaygroundThe changes in this pull request can previewed and tested using a WordPress Playground instance. WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser. Some things to be aware of
For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation. |
|
@jorgefilipecosta can you link this to the trac ticket please? I don't have edit perms in this repo. |
073df67 to
c948c7a
Compare
Nice catch the ticket mention was added. |
c948c7a to
a28c976
Compare
|
Let's run development, review, and testing through WordPress/ai#691, then sync all agreed refinements here. |
24bee86 to
6419a95
Compare
gziolo
left a comment
There was a problem hiding this comment.
I left my feedback.
Two questions regarding core/get-site-info:
- It covers some similar settings but is scoped to the site. Should it also respect the
show_in_abilitiescheck wherever applicable? - Should it get the default value in the schema aligned to
(object) array()as here?
| */ | ||
| private function register_get_settings(): void { | ||
| // Compute once; execute_get_settings() reuses this exact structure. | ||
| $this->exposed_settings = $this->get_exposed_settings(); |
There was a problem hiding this comment.
I noticed that Core settings get registered on the rest_api_init hook:
add_action( 'rest_api_init', 'register_initial_settings', 10 );It's worth double-checking whether there won't be a race condition with wp_abilities_api_init.
The long-term question is whether get_exposed_settings() could be computed lazily inside the execute/schema callbacks instead of being cached at registration? That would remove the ordering coupling and match the sibling's live‑compute model.
There was a problem hiding this comment.
Good catch on the ordering. wp_abilities_api_init fires lazily and isn't ordered relative to rest_api_init, so register() now calls register_initial_settings() before computing the snapshot whenever rest_api_init hasn't fired (or is mid-fire before priority 10). Added the guard in ae862f0 and documented the timing in 59e1819; re-registering later on rest_api_init is harmless.
On computing it lazily: I kept the snapshot cached at registration on purpose. The input schema, output schema, and execute callback all have to derive from the exact same set of exposed settings, so computing it once avoids walking get_registered_settings() three times per request and any risk of the schema and the output drifting apart. With the ordering handled explicitly there's no coupling left to remove, so I dropped the unreachable recompute branch in 13f4d31.
Add a read-only core/settings ability that returns WordPress settings as a flat name => value map. Only settings flagged with the new show_in_abilities registration arg are exposed; callers can filter by settings group or by name (mutually exclusive). Requires the manage_options capability. The logic lives in a new internal WP_Settings_Abilities class, structured so a future core/manage-settings write ability can reuse its helpers.
'settings' reads more naturally than 'slugs' for filtering an abilities-exposed settings map by name.
… tests Register a setting with show_in_abilities and assert it is exposed in the ability's input enum, output schema, and execute output.
- Simplify the input schema: replace the group-XOR-name `oneOf` with optional,
combinable `group` and `fields` filters (rename `slugs` -> `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().
- Harden value handling against loosely-typed registration data.
- Tests: assert keys order-insensitively and cover combined group+fields filtering.
…cblock The $groups and $field_names params are sequential string lists (array_keys() and an appended array), so list<string> is the precise type rather than string[].
…ettings() The exposed-settings cache is always populated in register_get_settings() before the ability is registered, so the recompute fallback was dead code. Read the cache directly and keep a defensive null guard so the unexpected case is explicit rather than silently recomputing.
…tract The core/settings ability snapshots the exposed settings when it registers on wp_abilities_api_init, so a setting must be registered before that hook fires to be exposed. Note this on the show_in_abilities register_setting() argument so integrators know their register_setting() has to run earlier.
Make WP_Settings_Abilities final and tighten member visibility so the intended surface is enforced rather than only documented via @access private. Only the externally-invoked entry points remain public (register(), execute_get_settings(), has_permission()); register_get_settings() and the shared helpers become private, and CATEGORY becomes a private const. Method bodies and the static cache are unchanged.
Move register() and the ability callbacks (execute_get_settings(), has_permission()) from public static to public methods, and the internal helpers to private methods; the exposed-settings cache becomes an instance property. wp_register_core_abilities() now registers the ability via ( new WP_Settings_Abilities() )->register(), reducing the static surface of this @access private class.
…y.php Co-authored-by: Greg Ziółkowski <[email protected]>
8469c64 to
6f4b639
Compare
Syncs the core PR with the WordPress/ai plugin, where the ability was renamed in WordPress/ai#806. Renames the ability, its label (Get Settings -> Read Settings), the docblock references in option.php and abilities.php, and the test file, and adds the plugin's new assertion on the registered ability name.
…ass. Per review, spell out in the class docblock why WP_Settings_Abilities departs from the self-contained closures in wp_register_core_abilities(): the exposed-settings snapshot is shared between the schemas and the execute callback, and the helpers are meant to back a future core/manage-settings write ability. Also documents the snapshot timing contract with register_initial_settings() on rest_api_init.
Aligns the core/get-site-info, core/get-user-info, and
core/get-environment-info input schema defaults with core/read-settings:
(object) array() instead of array(), so the serialized default is {},
consistent with type:object.
Per review, all the tests added by this PR should reference the ticket that introduces the ability.
…apshots them. The wp_abilities_api_init hook fires lazily on first use of the abilities registry, which is not ordered relative to rest_api_init (where core registers its initial settings) and can happen without it entirely, e.g. on cron or WP-CLI. When the registry initialized first, core/read-settings captured an empty settings snapshot for the rest of the request. Ensure register_initial_settings() has run before the snapshot is computed, and register the ability under the previously broken ordering in the tests so they cover the regression.
Hi @gziolo I think the answer is yes to both, but I would prefer to do that in a separate PR to avoid this one being too huge. |
|
All the reviews comments were applied this is ready for another look. |
…t their schema. Run each setting value through rest_validate_value_from_schema() and rest_sanitize_value_from_schema() instead of a type-only cast, mirroring WP_REST_Settings_Controller::prepare_value(). A value that does not conform to its setting's schema (for example an enum or format-constrained setting whose stored option is absent or has drifted) is now dropped from the response rather than left to fail output validation for the entire core/read-settings call, so one bad value can no longer make every setting unreadable. Removes the bespoke cast_value() helper.
Part of: WordPress/ai#40
Ticket: https://core.trac.wordpress.org/ticket/64605
Summary
Adds a read-only
core/read-settingsability to the Abilities API. It returns WordPress settings — those flagged with a newshow_in_abilitiesregistration arg — as a flatname => valuemap, with per-setting metadata (type, title, description, default) carried in the output schema. Callers can optionally filter by settingsgroup, by setting names (fields), or both (narrowing to their intersection). Requiresmanage_options.This is a flat-output alternative to #10747. The logic lives in a new internal
WP_Settings_Abilitiesclass (src/wp-includes/abilities/class-wp-settings-abilities.php), structured so a futurecore/manage-settingswrite ability can reuse its helpers (get_exposed_settings(),value_schema(),cast_value()).AI PRs: WordPress/ai#691, WordPress/ai#806
Test plan
In wp-admin, open the browser console and call the ability's run endpoint with
wp.apiFetch:name => valuemap with typed values (e.g.posts_per_pageis an int,use_smiliesa bool)groupandfieldsfilters narrow the result; supplying both returns their intersectionmanage_options) is deniedPHPUnit coverage added in
tests/phpunit/tests/abilities-api/wpRegisterCoreReadSettingsAbility.php(including a test that a setting registered withshow_in_abilitiesis exposed by the ability).