Skip to content

Commit 073df67

Browse files
Abilities API: Add a core/settings ability
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.
1 parent 46329b6 commit 073df67

4 files changed

Lines changed: 572 additions & 63 deletions

File tree

src/wp-includes/abilities.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
declare( strict_types = 1 );
1111

12+
require_once __DIR__ . '/abilities/class-wp-settings-abilities.php';
13+
1214
/**
1315
* Registers the core ability categories.
1416
*
@@ -351,4 +353,7 @@ function wp_register_core_abilities(): void {
351353
),
352354
)
353355
);
356+
357+
// Register the settings abilities (currently the read-only `core/settings`).
358+
WP_Settings_Abilities::register();
354359
}
Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
<?php
2+
/**
3+
* Abilities API: WP_Settings_Abilities class.
4+
*
5+
* @package WordPress
6+
* @subpackage Abilities API
7+
* @since 7.1.0
8+
*/
9+
10+
declare( strict_types = 1 );
11+
12+
/**
13+
* Core class used to register settings-related abilities.
14+
*
15+
* Provides the read-only `core/settings` ability and the shared building blocks
16+
* (exposed-settings discovery, schema generation, value casting) that are intended to
17+
* also back a future write-oriented `core/manage-settings` ability.
18+
*
19+
* This class is part of WordPress' internal implementation of the core abilities and is
20+
* not part of the public API. It may be changed or removed at any time without notice.
21+
* Do not use it directly or rely on its existence.
22+
*
23+
* @since 7.1.0
24+
*
25+
* @access private
26+
*/
27+
class WP_Settings_Abilities {
28+
29+
/**
30+
* The ability category used for settings abilities.
31+
*
32+
* @since 7.1.0
33+
* @var string
34+
*/
35+
const CATEGORY = 'site';
36+
37+
/**
38+
* Registers all settings abilities.
39+
*
40+
* Must run on the `wp_abilities_api_init` hook.
41+
*
42+
* @since 7.1.0
43+
*/
44+
public static function register(): void {
45+
self::register_get_settings();
46+
47+
/*
48+
* A future write-oriented ability can be registered here, reusing the shared
49+
* helpers below (get_exposed_settings(), value_schema(), cast_value()):
50+
*
51+
* self::register_manage_settings();
52+
*/
53+
}
54+
55+
/**
56+
* Registers the read-only `core/settings` ability.
57+
*
58+
* @since 7.1.0
59+
*/
60+
public static function register_get_settings(): void {
61+
$settings = self::get_exposed_settings();
62+
$groups = array_values( array_unique( array_filter( wp_list_pluck( $settings, 'group' ) ) ) );
63+
$slugs = array_keys( $settings );
64+
$properties = array();
65+
foreach ( $settings as $exposed_name => $setting ) {
66+
$properties[ $exposed_name ] = $setting['schema'];
67+
}
68+
69+
wp_register_ability(
70+
'core/settings',
71+
array(
72+
'label' => __( 'Get Settings' ),
73+
'description' => __( 'Returns WordPress settings as a flat map of setting name to value. By default returns all settings exposed to abilities, or optionally a subset filtered by settings group or by setting name.' ),
74+
'category' => self::CATEGORY,
75+
'input_schema' => self::get_settings_input_schema( $groups, $slugs ),
76+
'output_schema' => array(
77+
'type' => 'object',
78+
'description' => __( 'A map of setting name to its current value.' ),
79+
'properties' => $properties,
80+
'additionalProperties' => false,
81+
),
82+
'execute_callback' => array( self::class, 'execute_get_settings' ),
83+
'permission_callback' => array( self::class, 'has_permission' ),
84+
'meta' => array(
85+
'annotations' => array(
86+
'readonly' => true,
87+
'destructive' => false,
88+
'idempotent' => true,
89+
),
90+
'show_in_rest' => true,
91+
),
92+
)
93+
);
94+
}
95+
96+
/**
97+
* Executes the `core/settings` ability.
98+
*
99+
* @since 7.1.0
100+
*
101+
* @param mixed $input Optional. The ability input. Default empty array.
102+
* @return array<string, mixed> Map of exposed setting name to current value.
103+
*/
104+
public static function execute_get_settings( $input = array() ): array {
105+
$input = is_array( $input ) ? $input : array();
106+
107+
$settings = self::get_exposed_settings();
108+
$group = isset( $input['group'] ) ? (string) $input['group'] : '';
109+
$slugs = isset( $input['slugs'] ) && is_array( $input['slugs'] ) ? $input['slugs'] : array();
110+
111+
$result = array();
112+
foreach ( $settings as $exposed_name => $setting ) {
113+
if ( '' !== $group && $setting['group'] !== $group ) {
114+
continue;
115+
}
116+
if ( ! empty( $slugs ) && ! in_array( $exposed_name, $slugs, true ) ) {
117+
continue;
118+
}
119+
120+
$type = isset( $setting['schema']['type'] ) ? (string) $setting['schema']['type'] : 'string';
121+
$default = array_key_exists( 'default', $setting['schema'] ) ? $setting['schema']['default'] : false;
122+
$value = get_option( $setting['option'], $default );
123+
124+
$result[ $exposed_name ] = self::cast_value( $value, $type );
125+
}
126+
127+
return $result;
128+
}
129+
130+
/**
131+
* Checks whether the current user may use the settings abilities.
132+
*
133+
* @since 7.1.0
134+
*
135+
* @return bool True if the current user can manage options.
136+
*/
137+
public static function has_permission(): bool {
138+
return current_user_can( 'manage_options' );
139+
}
140+
141+
/**
142+
* Builds the input schema for the get ability: filter by group XOR by name.
143+
*
144+
* @since 7.1.0
145+
*
146+
* @param string[] $groups Available settings groups.
147+
* @param string[] $slugs Available exposed setting names.
148+
* @return array<string, mixed> The input JSON Schema.
149+
*/
150+
protected static function get_settings_input_schema( array $groups, array $slugs ): array {
151+
return array(
152+
'type' => 'object',
153+
'default' => array(),
154+
// Filter by group OR by name, but not both at once.
155+
'oneOf' => array(
156+
array(
157+
'title' => __( 'All settings' ),
158+
'type' => 'object',
159+
'additionalProperties' => false,
160+
),
161+
array(
162+
'title' => __( 'Filter by group' ),
163+
'type' => 'object',
164+
'required' => array( 'group' ),
165+
'properties' => array(
166+
'group' => array(
167+
'type' => 'string',
168+
'enum' => $groups,
169+
'description' => __( 'Return only settings that belong to this settings group.' ),
170+
),
171+
),
172+
'additionalProperties' => false,
173+
),
174+
array(
175+
'title' => __( 'Filter by name' ),
176+
'type' => 'object',
177+
'required' => array( 'slugs' ),
178+
'properties' => array(
179+
'slugs' => array(
180+
'type' => 'array',
181+
'items' => array(
182+
'type' => 'string',
183+
'enum' => $slugs,
184+
),
185+
'description' => __( 'Return only the settings with these names.' ),
186+
),
187+
),
188+
'additionalProperties' => false,
189+
),
190+
),
191+
);
192+
}
193+
194+
/**
195+
* Returns the settings exposed through the Abilities API.
196+
*
197+
* Reads {@see get_registered_settings()} and keeps only settings flagged with a truthy
198+
* `show_in_abilities` argument. Each entry is keyed by its exposed name and carries the
199+
* underlying option name, the settings group, and a JSON Schema describing the value.
200+
*
201+
* @since 7.1.0
202+
*
203+
* @return array<string, array{option: string, group: string, schema: array<string, mixed>}> Settings keyed by exposed name.
204+
*/
205+
protected static function get_exposed_settings(): array {
206+
$settings = array();
207+
208+
foreach ( get_registered_settings() as $option_name => $args ) {
209+
$show = $args['show_in_abilities'] ?? false;
210+
if ( empty( $show ) ) {
211+
continue;
212+
}
213+
214+
$option_name = (string) $option_name;
215+
$exposed_name = is_array( $show ) && ! empty( $show['name'] ) ? (string) $show['name'] : $option_name;
216+
217+
$settings[ $exposed_name ] = array(
218+
'option' => $option_name,
219+
'group' => isset( $args['group'] ) ? (string) $args['group'] : '',
220+
'schema' => self::value_schema( $args, $show ),
221+
);
222+
}
223+
224+
return $settings;
225+
}
226+
227+
/**
228+
* Builds the JSON Schema describing a single setting's value.
229+
*
230+
* @since 7.1.0
231+
*
232+
* @param array<string, mixed> $args The setting registration arguments.
233+
* @param bool|array<string, mixed> $show The setting's `show_in_abilities` value.
234+
* @return array<string, mixed> The value JSON Schema.
235+
*/
236+
protected static function value_schema( array $args, $show ): array {
237+
$schema = array(
238+
'type' => isset( $args['type'] ) ? (string) $args['type'] : 'string',
239+
);
240+
if ( ! empty( $args['label'] ) ) {
241+
$schema['title'] = $args['label'];
242+
}
243+
if ( ! empty( $args['description'] ) ) {
244+
$schema['description'] = $args['description'];
245+
}
246+
if ( array_key_exists( 'default', $args ) ) {
247+
$schema['default'] = $args['default'];
248+
}
249+
if ( is_array( $show ) && isset( $show['schema'] ) && is_array( $show['schema'] ) ) {
250+
$schema = array_merge( $schema, $show['schema'] );
251+
}
252+
253+
return $schema;
254+
}
255+
256+
/**
257+
* Casts a stored option value to the type declared in its settings registration.
258+
*
259+
* @since 7.1.0
260+
*
261+
* @param mixed $value The raw option value.
262+
* @param string $type The registered setting type.
263+
* @return mixed The value cast to the declared type.
264+
*/
265+
protected static function cast_value( $value, string $type ) {
266+
switch ( $type ) {
267+
case 'boolean':
268+
return (bool) $value;
269+
case 'integer':
270+
return (int) $value;
271+
case 'number':
272+
return (float) $value;
273+
case 'array':
274+
case 'object':
275+
return is_array( $value ) ? $value : array();
276+
default:
277+
return is_scalar( $value ) ? (string) $value : $value;
278+
}
279+
}
280+
}

0 commit comments

Comments
 (0)