Commit f65079ad5c for wordpress.org
commit f65079ad5c1855164e571a8f7522ecd9c9c5ea0d
Author: jorgefilipecosta <jorgefilipecosta@git.wordpress.org>
Date: Mon Oct 12 11:37:49 2026 +0000
Core Abilities: Add the `core/settings-get` ability.
Adds a read-only `core/settings-get` ability that returns WordPress settings as a flat map of setting name to value, optionally narrowed by settings group, by setting name, or both. It requires the `manage_options` capability. The code lives in the new private `WP_Abilities_Settings` class.
Settings opt in with the new `show_in_abilities` argument of `register_setting()`, which defaults to `false`. When `true`, a setting is exposed.
Developed in https://github.com/WordPress/ai/pull/691 and https://github.com/WordPress/wordpress-develop/pull/12141.
Props jorgefilipecosta, gziolo, jorbin, justlevine, dkotter, logicrays, jason_the_adams, mukesh27, ovidiu-galatan, jeffpaul.
Fixes #64605.
Built from https://develop.svn.wordpress.org/trunk@64311
git-svn-id: http://core.svn.wordpress.org/trunk@63461 1a063a9b-81f0-0310-95a4-ce76da25c4cd
diff --git a/wp-includes/abilities.php b/wp-includes/abilities.php
index 1386c0deb7..5fa42fea2b 100644
--- a/wp-includes/abilities.php
+++ b/wp-includes/abilities.php
@@ -9,6 +9,8 @@
declare( strict_types = 1 );
+require_once __DIR__ . '/abilities/class-wp-abilities-settings.php';
+
/**
* Registers the core ability categories.
*
@@ -45,6 +47,7 @@ function wp_register_core_ability_categories(): void {
* Registers the default core abilities.
*
* @since 6.9.0
+ * @since 7.2.0 Added the `core/settings-get` ability.
*
* @global wpdb $wpdb WordPress database abstraction object.
*/
@@ -360,4 +363,7 @@ function wp_register_core_abilities(): void {
),
)
);
+
+ // Register the settings abilities (currently the read-only `core/settings-get`).
+ ( new WP_Abilities_Settings() )->register();
}
diff --git a/wp-includes/abilities/class-wp-abilities-settings.php b/wp-includes/abilities/class-wp-abilities-settings.php
new file mode 100644
index 0000000000..f9afdcd195
--- /dev/null
+++ b/wp-includes/abilities/class-wp-abilities-settings.php
@@ -0,0 +1,340 @@
+<?php
+/**
+ * Abilities API: WP_Abilities_Settings class.
+ *
+ * @package WordPress
+ * @subpackage Abilities API
+ * @since 7.2.0
+ */
+
+declare( strict_types = 1 );
+
+/**
+ * Core class used to register settings-related abilities.
+ *
+ * Provides the read-only `core/settings-get` ability and the shared building blocks
+ * (exposed-settings discovery and schema generation) that are intended to also back a
+ * future write-oriented `core/settings-update` ability.
+ *
+ * Unlike the other core abilities, which are self-contained closures registered directly
+ * in wp_register_core_abilities(), the settings abilities live in a dedicated class
+ * because they share state: the set of exposed settings is computed once at registration
+ * and reused by the input schema, the output schema, and the execute callback, and the
+ * same helpers are meant to be shared with the future write ability.
+ *
+ * The exposed settings are captured when the ability registers, the first time the abilities
+ * registry is used in a request. Settings registered later in that request are not exposed.
+ * Core registers its own settings in time, see _wp_register_initial_settings_for_abilities().
+ *
+ * Only register() is public. The ability callbacks are closures that call private
+ * methods, so callers go through the Abilities API, such as
+ * `wp_get_ability( 'core/settings-get' )->execute()`, which validates the input and
+ * checks permissions before running them.
+ *
+ * @since 7.2.0
+ *
+ * @access private
+ */
+final class WP_Abilities_Settings {
+
+ /**
+ * The ability category used for settings abilities.
+ *
+ * @since 7.2.0
+ * @var string
+ */
+ private const CATEGORY = 'site';
+
+ /**
+ * Settings exposed through the Abilities API, computed once at registration.
+ *
+ * @since 7.2.0
+ * @var array<string, array{option: string, group: string, default: mixed, schema: array<string, mixed>}>
+ */
+ private $exposed_settings = array();
+
+ /**
+ * Registers all settings abilities.
+ *
+ * Must run on the `wp_abilities_api_init` hook. Registers nothing when no setting is
+ * exposed to abilities.
+ *
+ * @since 7.2.0
+ */
+ public function register(): void {
+ $this->exposed_settings = $this->get_exposed_settings();
+ if ( empty( $this->exposed_settings ) ) {
+ return;
+ }
+
+ $this->register_settings_get();
+ }
+
+ /**
+ * Registers the read-only `core/settings-get` ability.
+ *
+ * @since 7.2.0
+ */
+ private function register_settings_get(): void {
+ $groups = array_values( array_unique( array_filter( array_column( $this->exposed_settings, 'group' ) ) ) );
+
+ wp_register_ability(
+ 'core/settings-get',
+ array(
+ 'label' => __( 'Get Settings' ),
+ '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, by setting name, or both. A setting whose value does not match its schema is left out. Requires an authenticated user who can manage options.' ),
+ 'category' => self::CATEGORY,
+ 'input_schema' => $this->get_settings_get_input_schema( $groups, array_map( 'strval', array_keys( $this->exposed_settings ) ) ),
+ 'output_schema' => $this->get_settings_get_output_schema(),
+ 'execute_callback' => function ( $input = array() ): array {
+ return $this->execute_settings_get( $input );
+ },
+ 'permission_callback' => function (): bool {
+ return $this->check_permission();
+ },
+ 'meta' => array(
+ 'annotations' => array(
+ 'readonly' => true,
+ 'destructive' => false,
+ 'idempotent' => true,
+ ),
+ 'public' => true,
+ ),
+ )
+ );
+ }
+
+ /**
+ * Executes the `core/settings-get` ability.
+ *
+ * @since 7.2.0
+ *
+ * @param mixed $input Optional. The ability input. Default empty array.
+ * @return array<string, mixed> Map of exposed setting name to current value.
+ */
+ private function execute_settings_get( $input = array() ): array {
+ $input = rest_sanitize_object( $input );
+ $group = isset( $input['group'] ) && is_string( $input['group'] ) ? $input['group'] : '';
+ $fields = rest_sanitize_array( $input['fields'] ?? array() );
+
+ $result = array();
+ foreach ( $this->exposed_settings as $exposed_name => $setting ) {
+ if ( '' !== $group && $setting['group'] !== $group ) {
+ continue;
+ }
+ if ( ! empty( $fields ) && ! in_array( (string) $exposed_name, $fields, true ) ) {
+ continue;
+ }
+
+ /*
+ * As the settings endpoint does, read a setting that was never saved as its registered
+ * default, or leave it out when it has none: get_option() would return false, which a
+ * boolean schema accepts as a value and an array schema reads as an empty list.
+ */
+ $value = get_option( $setting['option'], $setting['default'] );
+ if ( null === $value ) {
+ continue;
+ }
+
+ // WordPress stores false as '', which the boolean schema rejects.
+ if ( '' === $value && 'boolean' === $setting['schema']['type'] ) {
+ $value = false;
+ }
+
+ /*
+ * Leave out a value its schema rejects, before sanitizing (which could make it pass) or
+ * after (which could make it fail), instead of failing output validation for every setting.
+ */
+ if ( is_wp_error( rest_validate_value_from_schema( $value, $setting['schema'] ) ) ) {
+ continue;
+ }
+
+ $value = rest_sanitize_value_from_schema( $value, $setting['schema'] );
+ if ( is_wp_error( rest_validate_value_from_schema( $value, $setting['schema'] ) ) ) {
+ continue;
+ }
+
+ // Object (not array()) so an empty object value is serialized as {}, consistent with type:object.
+ $result[ $exposed_name ] = 'object' === $setting['schema']['type'] ? (object) $value : $value;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Permission callback for the settings abilities.
+ *
+ * The settings abilities read site options, so they are gated on the `manage_options`
+ * capability regardless of the input.
+ *
+ * @since 7.2.0
+ *
+ * @return bool True if the current user can manage options.
+ */
+ private function check_permission(): bool {
+ return current_user_can( 'manage_options' );
+ }
+
+ /**
+ * Builds the input schema for the `core/settings-get` ability: optional filters by group and/or name.
+ *
+ * Both `group` and `fields` are optional; supplying both narrows the response to their
+ * intersection, and supplying neither returns every exposed setting.
+ *
+ * @since 7.2.0
+ *
+ * @param list<string> $groups Available settings groups.
+ * @param list<string> $field_names Available exposed setting names.
+ * @return array<string, mixed> The input JSON Schema.
+ */
+ private function get_settings_get_input_schema( array $groups, array $field_names ): array {
+ return array(
+ 'type' => 'object',
+ 'default' => array(),
+ 'properties' => array(
+ 'group' => array(
+ 'type' => 'string',
+ 'enum' => $groups,
+ 'description' => __( 'Return only settings that belong to this settings group. If omitted, settings of every group are returned.' ),
+ ),
+ 'fields' => array(
+ 'type' => 'array',
+ 'uniqueItems' => true,
+ 'items' => array(
+ 'type' => 'string',
+ 'enum' => $field_names,
+ ),
+ 'description' => __( 'Return only the settings with these names. If omitted or empty, all settings allowed by the group filter are returned. When both filters are given, only settings matching both are returned.' ),
+ ),
+ ),
+ 'additionalProperties' => false,
+ );
+ }
+
+ /**
+ * Builds the output schema for the `core/settings-get` ability.
+ *
+ * No setting is marked required because the `group` and `fields` inputs let the caller
+ * request any subset, and a setting whose value does not match its schema is left out.
+ *
+ * @since 7.2.0
+ *
+ * @return array<string, mixed> The output JSON Schema.
+ */
+ private function get_settings_get_output_schema(): array {
+ return array(
+ 'type' => 'object',
+ 'description' => __( 'A map of setting name to its current value.' ),
+ 'properties' => wp_list_pluck( $this->exposed_settings, 'schema' ),
+ 'additionalProperties' => false,
+ );
+ }
+
+ /**
+ * Returns the settings exposed through the Abilities API.
+ *
+ * Reads {@see get_registered_settings()} and keeps only settings flagged with a truthy
+ * `show_in_abilities` argument, of a type the settings endpoint supports. Each entry is
+ * keyed by its exposed name and carries the underlying option name, the settings group,
+ * the registered default (null when there is none), and a JSON Schema describing the value.
+ *
+ * Two settings exposed under the same name trigger a notice, and the later one is exposed.
+ *
+ * @since 7.2.0
+ *
+ * @return array<string, array{option: string, group: string, default: mixed, schema: array<string, mixed>}> Settings keyed by exposed name.
+ */
+ private function get_exposed_settings(): array {
+ $settings = array();
+
+ foreach ( get_registered_settings() as $option_name => $args ) {
+ if ( empty( $args['show_in_abilities'] ) ) {
+ continue;
+ }
+
+ $show = $this->get_exposure_args( $args );
+
+ $schema = $this->value_schema( $args, $show );
+ if ( ! in_array( $schema['type'], array( 'number', 'integer', 'string', 'boolean', 'array', 'object' ), true ) ) {
+ continue;
+ }
+
+ $option_name = (string) $option_name;
+ $exposed_name = empty( $show['name'] ) ? $option_name : (string) $show['name'];
+
+ if ( isset( $settings[ $exposed_name ] ) ) {
+ _doing_it_wrong(
+ __METHOD__,
+ sprintf(
+ /* translators: 1: Option name, 2: Setting name exposed to abilities, 3: Option name. */
+ __( 'The "%1$s" setting is exposed to abilities as "%2$s", a name already used by the "%3$s" setting, which is no longer exposed.' ),
+ esc_html( $option_name ),
+ esc_html( $exposed_name ),
+ esc_html( $settings[ $exposed_name ]['option'] )
+ ),
+ '7.2.0'
+ );
+ }
+
+ $settings[ $exposed_name ] = array(
+ 'option' => $option_name,
+ 'group' => $args['group'] ?? '',
+ 'default' => $args['default'] ?? null,
+ 'schema' => $schema,
+ );
+ }
+
+ return $settings;
+ }
+
+ /**
+ * Returns the name and schema overrides used to expose a setting to abilities.
+ *
+ * When `show_in_abilities` is `true`, the setting is exposed the same way as in the
+ * REST API: it uses the `name` and `schema` from `show_in_rest`. An array is used
+ * as is.
+ *
+ * @since 7.2.0
+ *
+ * @param array<string, mixed> $args The setting registration arguments.
+ * @return array<string, mixed> The exposure arguments, with optional `name` and `schema` keys.
+ */
+ private function get_exposure_args( array $args ): array {
+ if ( is_array( $args['show_in_abilities'] ) ) {
+ return $args['show_in_abilities'];
+ }
+
+ return is_array( $args['show_in_rest'] ) ? $args['show_in_rest'] : array();
+ }
+
+ /**
+ * Builds the JSON Schema describing a single setting's value.
+ *
+ * As in the settings endpoint, objects in the schema reject properties they do not declare,
+ * unless the schema allows them.
+ *
+ * @since 7.2.0
+ *
+ * @param array<string, mixed> $args The setting registration arguments.
+ * @param array<string, mixed> $show The exposure arguments, see get_exposure_args().
+ * @return array<string, mixed> The value JSON Schema.
+ */
+ private function value_schema( array $args, $show ): array {
+ $schema = array(
+ 'type' => $args['type'],
+ );
+ if ( ! empty( $args['label'] ) ) {
+ $schema['title'] = $args['label'];
+ }
+ if ( ! empty( $args['description'] ) ) {
+ $schema['description'] = $args['description'];
+ }
+ if ( isset( $show['schema'] ) && is_array( $show['schema'] ) ) {
+ /** @var array<string, mixed> $show_schema */
+ $show_schema = $show['schema'];
+ $schema = array_merge( $schema, $show_schema );
+ }
+
+ return rest_default_additional_properties_to_false( $schema );
+ }
+}
diff --git a/wp-includes/default-filters.php b/wp-includes/default-filters.php
index fe93208adf..cdeaa169af 100644
--- a/wp-includes/default-filters.php
+++ b/wp-includes/default-filters.php
@@ -556,6 +556,7 @@ add_action( 'parse_request', 'rest_api_loaded' );
// Abilities API.
add_action( 'wp_abilities_api_categories_init', 'wp_register_core_ability_categories' );
+add_action( 'wp_abilities_api_init', '_wp_register_initial_settings_for_abilities', 1 );
add_action( 'wp_abilities_api_init', 'wp_register_core_abilities' );
// Connectors API.
diff --git a/wp-includes/option.php b/wp-includes/option.php
index 49ecfb6597..874e674d63 100644
--- a/wp-includes/option.php
+++ b/wp-includes/option.php
@@ -2738,24 +2738,26 @@ function set_site_transient( $transient, $value, $expiration = 0 ) {
/**
* Registers default settings available in WordPress.
*
- * The settings registered here are primarily useful for the REST API, so this
- * does not encompass all settings available in WordPress.
+ * The settings registered here are primarily useful for the REST API and the
+ * Abilities API, so this does not encompass all settings available in WordPress.
*
* @since 4.7.0
* @since 6.0.1 The `show_on_front`, `page_on_front`, and `page_for_posts` options were added.
* @since 7.2.0 The `wp_page_for_privacy_policy` option was registered, exposed as `page_for_privacy_policy`.
+ * @since 7.2.0 Added `show_in_abilities` support for the exposed settings.
*/
function register_initial_settings() {
register_setting(
'general',
'blogname',
array(
- 'show_in_rest' => array(
+ 'show_in_rest' => array(
'name' => 'title',
),
- 'type' => 'string',
- 'label' => __( 'Title' ),
- 'description' => __( 'Site title.' ),
+ 'show_in_abilities' => true,
+ 'type' => 'string',
+ 'label' => __( 'Title' ),
+ 'description' => __( 'Site title.' ),
)
);
@@ -2763,12 +2765,13 @@ function register_initial_settings() {
'general',
'blogdescription',
array(
- 'show_in_rest' => array(
+ 'show_in_rest' => array(
'name' => 'description',
),
- 'type' => 'string',
- 'label' => __( 'Tagline' ),
- 'description' => __( 'Site tagline.' ),
+ 'show_in_abilities' => true,
+ 'type' => 'string',
+ 'label' => __( 'Tagline' ),
+ 'description' => __( 'Site tagline.' ),
)
);
@@ -2777,14 +2780,15 @@ function register_initial_settings() {
'general',
'siteurl',
array(
- 'show_in_rest' => array(
+ 'show_in_rest' => array(
'name' => 'url',
'schema' => array(
'format' => 'uri',
),
),
- 'type' => 'string',
- 'description' => __( 'Site URL.' ),
+ 'show_in_abilities' => true,
+ 'type' => 'string',
+ 'description' => __( 'The URL where WordPress core files are served. May differ from the public site URL.' ),
)
);
}
@@ -2794,14 +2798,15 @@ function register_initial_settings() {
'general',
'admin_email',
array(
- 'show_in_rest' => array(
+ 'show_in_rest' => array(
'name' => 'email',
'schema' => array(
'format' => 'email',
),
),
- 'type' => 'string',
- 'description' => __( 'This address is used for admin purposes, like new user notification.' ),
+ 'show_in_abilities' => true,
+ 'type' => 'string',
+ 'description' => __( 'This address is used for admin purposes, like new user notification.' ),
)
);
}
@@ -2810,11 +2815,12 @@ function register_initial_settings() {
'general',
'timezone_string',
array(
- 'show_in_rest' => array(
+ 'show_in_rest' => array(
'name' => 'timezone',
),
- 'type' => 'string',
- 'description' => __( 'A city in the same timezone as you.' ),
+ 'show_in_abilities' => true,
+ 'type' => 'string',
+ 'description' => __( 'A city in the same timezone as you.' ),
)
);
@@ -2822,9 +2828,10 @@ function register_initial_settings() {
'general',
'date_format',
array(
- 'show_in_rest' => true,
- 'type' => 'string',
- 'description' => __( 'A date format for all date strings.' ),
+ 'show_in_rest' => true,
+ 'show_in_abilities' => true,
+ 'type' => 'string',
+ 'description' => __( 'A date format for all date strings.' ),
)
);
@@ -2832,9 +2839,10 @@ function register_initial_settings() {
'general',
'time_format',
array(
- 'show_in_rest' => true,
- 'type' => 'string',
- 'description' => __( 'A time format for all time strings.' ),
+ 'show_in_rest' => true,
+ 'show_in_abilities' => true,
+ 'type' => 'string',
+ 'description' => __( 'A time format for all time strings.' ),
)
);
@@ -2842,9 +2850,10 @@ function register_initial_settings() {
'general',
'start_of_week',
array(
- 'show_in_rest' => true,
- 'type' => 'integer',
- 'description' => __( 'A day number of the week that the week should start on.' ),
+ 'show_in_rest' => true,
+ 'show_in_abilities' => true,
+ 'type' => 'integer',
+ 'description' => __( 'A day number of the week that the week should start on.' ),
)
);
@@ -2852,12 +2861,13 @@ function register_initial_settings() {
'general',
'WPLANG',
array(
- 'show_in_rest' => array(
+ 'show_in_rest' => array(
'name' => 'language',
),
- 'type' => 'string',
- 'description' => __( 'WordPress locale code.' ),
- 'default' => 'en_US',
+ 'show_in_abilities' => true,
+ 'type' => 'string',
+ 'description' => __( 'WordPress locale code.' ),
+ 'default' => 'en_US',
)
);
@@ -2865,10 +2875,11 @@ function register_initial_settings() {
'writing',
'use_smilies',
array(
- 'show_in_rest' => true,
- 'type' => 'boolean',
- 'description' => __( 'Convert emoticons like :-) and :-P to graphics on display.' ),
- 'default' => true,
+ 'show_in_rest' => true,
+ 'show_in_abilities' => true,
+ 'type' => 'boolean',
+ 'description' => __( 'Convert emoticons like :-) and :-P to graphics on display.' ),
+ 'default' => true,
)
);
@@ -2876,9 +2887,10 @@ function register_initial_settings() {
'writing',
'default_category',
array(
- 'show_in_rest' => true,
- 'type' => 'integer',
- 'description' => __( 'Default post category.' ),
+ 'show_in_rest' => true,
+ 'show_in_abilities' => true,
+ 'type' => 'integer',
+ 'description' => __( 'Default post category.' ),
)
);
@@ -2886,9 +2898,10 @@ function register_initial_settings() {
'writing',
'default_post_format',
array(
- 'show_in_rest' => true,
- 'type' => 'string',
- 'description' => __( 'Default post format.' ),
+ 'show_in_rest' => true,
+ 'show_in_abilities' => true,
+ 'type' => 'string',
+ 'description' => __( 'Default post format.' ),
)
);
@@ -2896,11 +2909,12 @@ function register_initial_settings() {
'reading',
'posts_per_page',
array(
- 'show_in_rest' => true,
- 'type' => 'integer',
- 'label' => __( 'Maximum posts per page' ),
- 'description' => __( 'Blog pages show at most.' ),
- 'default' => 10,
+ 'show_in_rest' => true,
+ 'show_in_abilities' => true,
+ 'type' => 'integer',
+ 'label' => __( 'Maximum posts per page' ),
+ 'description' => __( 'Blog pages show at most.' ),
+ 'default' => 10,
)
);
@@ -2908,10 +2922,11 @@ function register_initial_settings() {
'reading',
'show_on_front',
array(
- 'show_in_rest' => true,
- 'type' => 'string',
- 'label' => __( 'Show on front' ),
- 'description' => __( 'What to show on the front page' ),
+ 'show_in_rest' => true,
+ 'show_in_abilities' => true,
+ 'type' => 'string',
+ 'label' => __( 'Show on front' ),
+ 'description' => __( 'What to show on the front page' ),
)
);
@@ -2919,10 +2934,11 @@ function register_initial_settings() {
'reading',
'page_on_front',
array(
- 'show_in_rest' => true,
- 'type' => 'integer',
- 'label' => __( 'Page on front' ),
- 'description' => __( 'The ID of the page that should be displayed on the front page' ),
+ 'show_in_rest' => true,
+ 'show_in_abilities' => true,
+ 'type' => 'integer',
+ 'label' => __( 'Page on front' ),
+ 'description' => __( 'The ID of the page that should be displayed on the front page' ),
)
);
@@ -2930,9 +2946,10 @@ function register_initial_settings() {
'reading',
'page_for_posts',
array(
- 'show_in_rest' => true,
- 'type' => 'integer',
- 'description' => __( 'The ID of the page that should display the latest posts' ),
+ 'show_in_rest' => true,
+ 'show_in_abilities' => true,
+ 'type' => 'integer',
+ 'description' => __( 'The ID of the page that should display the latest posts' ),
)
);
@@ -2940,11 +2957,12 @@ function register_initial_settings() {
'reading',
'wp_page_for_privacy_policy',
array(
- 'show_in_rest' => array(
+ 'show_in_rest' => array(
'name' => 'page_for_privacy_policy',
),
- 'type' => 'integer',
- 'description' => __( 'The ID of the page that should be displayed as the privacy policy page' ),
+ 'show_in_abilities' => true,
+ 'type' => 'integer',
+ 'description' => __( 'The ID of the page that should be displayed as the privacy policy page' ),
)
);
@@ -2952,13 +2970,14 @@ function register_initial_settings() {
'discussion',
'default_ping_status',
array(
- 'show_in_rest' => array(
+ 'show_in_rest' => array(
'schema' => array(
'enum' => array( 'open', 'closed' ),
),
),
- 'type' => 'string',
- 'description' => __( 'Allow link notifications from other blogs (pingbacks and trackbacks) on new articles.' ),
+ 'show_in_abilities' => true,
+ 'type' => 'string',
+ 'description' => __( 'Allow link notifications from other blogs (pingbacks and trackbacks) on new articles.' ),
)
);
@@ -2966,18 +2985,53 @@ function register_initial_settings() {
'discussion',
'default_comment_status',
array(
- 'show_in_rest' => array(
+ 'show_in_rest' => array(
'schema' => array(
'enum' => array( 'open', 'closed' ),
),
),
- 'type' => 'string',
- 'label' => __( 'Allow comments on new posts' ),
- 'description' => __( 'Allow people to submit comments on new posts.' ),
+ 'show_in_abilities' => true,
+ 'type' => 'string',
+ 'label' => __( 'Allow comments on new posts' ),
+ 'description' => __( 'Allow people to submit comments on new posts.' ),
)
);
}
+/**
+ * Registers the default settings when the Abilities API initializes.
+ *
+ * The default settings are registered on `rest_api_init`, which fires lazily and
+ * independently of `wp_abilities_api_init`: on cron, WP-CLI, or any request where
+ * abilities are used before the REST server loads, it may not have fired, or may be
+ * mid-fire at a priority before register_initial_settings() runs. This makes sure the
+ * settings exist before the core abilities read them. Registering them again later on
+ * `rest_api_init` is harmless.
+ *
+ * @since 7.2.0
+ * @access private
+ *
+ * @global array $new_allowed_options
+ */
+function _wp_register_initial_settings_for_abilities(): void {
+ global $new_allowed_options;
+
+ if ( did_action( 'rest_api_init' ) && ! doing_action( 'rest_api_init' ) ) {
+ return;
+ }
+
+ $allowed_options = $new_allowed_options;
+
+ register_initial_settings();
+
+ /*
+ * Registering a setting also allows it on the options screen of its group. Restore
+ * the list, so saving Settings > General does not try to save `admin_email`, which
+ * that screen sends as `new_admin_email`.
+ */
+ $new_allowed_options = $allowed_options;
+}
+
/**
* Registers a setting and its data.
*
@@ -2988,6 +3042,7 @@ function register_initial_settings() {
* @since 5.5.0 `$new_whitelist_options` was renamed to `$new_allowed_options`.
* Please consider writing more inclusive code.
* @since 6.6.0 Added the `label` argument.
+ * @since 7.2.0 Added the `show_in_abilities` argument.
*
* @global array $new_allowed_options
* @global array $wp_registered_settings
@@ -3009,6 +3064,11 @@ function register_initial_settings() {
* @type bool|array $show_in_rest Whether data associated with this setting should be included in the
* REST API. When registering complex settings, this argument may
* optionally be an array with a 'schema' key.
+ * @type bool|array $show_in_abilities Whether this setting should be exposed through the Abilities API.
+ * When true, it uses the same name and schema as `$show_in_rest`.
+ * It may also be an array with 'name' and 'schema' keys, used instead of
+ * `$show_in_rest` rather than merged with it.
+ * Settings registered after abilities initialize are not exposed.
* @type mixed $default Default value when calling `get_option()`.
* }
*/
@@ -3028,6 +3088,7 @@ function register_setting( $option_group, $option_name, $args = array() ) {
'description' => '',
'sanitize_callback' => null,
'show_in_rest' => false,
+ 'show_in_abilities' => false,
);
// Back-compat: old sanitize callback is added.
@@ -3211,6 +3272,7 @@ function unregister_setting( $option_group, $option_name, $deprecated = '' ) {
* Retrieves an array of registered settings.
*
* @since 4.7.0
+ * @since 7.2.0 Registered setting data includes the `show_in_abilities` argument.
*
* @global array $wp_registered_settings
*
@@ -3230,6 +3292,10 @@ function unregister_setting( $option_group, $option_name, $deprecated = '' ) {
* @type bool|array $show_in_rest Whether data associated with this setting should be included in the
* REST API. When registering complex settings, this argument may
* optionally be an array with a 'schema' key.
+ * @type bool|array $show_in_abilities Whether this setting should be exposed through the Abilities API.
+ * When true, it uses the same name and schema as `$show_in_rest`.
+ * It may also be an array with 'name' and 'schema' keys, used
+ * instead of `$show_in_rest` rather than merged with it.
* @type mixed $default Default value when calling `get_option()`. Only present when the
* setting was registered with a default.
* }
diff --git a/wp-includes/version.php b/wp-includes/version.php
index 7c1f4cc855..6a5fe72b3a 100644
--- a/wp-includes/version.php
+++ b/wp-includes/version.php
@@ -16,7 +16,7 @@
*
* @global string $wp_version
*/
-$wp_version = '7.2-alpha-64310';
+$wp_version = '7.2-alpha-64311';
/**
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.