Commit 9625e340a2 for wordpress.org
commit 9625e340a28281cb12f5d334945fa10d3d612980
Author: jorgefilipecosta <jorgefilipecosta@git.wordpress.org>
Date: Mon Feb 9 16:44:44 2026 +0000
Abilities API: Add core/get-settings ability.
Introduce a new `core/get-settings` ability to the WordPress Abilities API that dynamically discovers and exposes WordPress settings. Settings with `show_in_abilities` enabled are exposed through this ability.
Props jorgefilipecosta, jason_the_adams, mukeshpanchal27, justlevine, ovidiu-galatan.
Fixes #64605.
Built from https://develop.svn.wordpress.org/trunk@61600
git-svn-id: http://core.svn.wordpress.org/trunk@60911 1a063a9b-81f0-0310-95a4-ce76da25c4cd
diff --git a/wp-includes/abilities.php b/wp-includes/abilities.php
index 4c6db1ed83..132ca6fc67 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-settings-abilities.php';
+
/**
* Registers the core ability categories.
*
@@ -257,4 +259,6 @@ function wp_register_core_abilities(): void {
),
)
);
+
+ WP_Settings_Abilities::register();
}
diff --git a/wp-includes/abilities/class-wp-settings-abilities.php b/wp-includes/abilities/class-wp-settings-abilities.php
new file mode 100644
index 0000000000..5af7fa4845
--- /dev/null
+++ b/wp-includes/abilities/class-wp-settings-abilities.php
@@ -0,0 +1,343 @@
+<?php
+/**
+ * Registers core settings abilities.
+ *
+ * This is a utility class to encapsulate the registration of settings-related abilities.
+ * It is not intended to be instantiated or consumed directly by any other code or plugin.
+ *
+ * @package WordPress
+ * @subpackage Abilities_API
+ * @since 7.0.0
+ *
+ * @internal This class is not part of the public API.
+ * @access private
+ */
+
+declare( strict_types=1 );
+
+/**
+ * Registers core settings abilities.
+ *
+ * @since 7.0.0
+ * @access private
+ */
+class WP_Settings_Abilities {
+
+ /**
+ * Available setting groups with show_in_abilities enabled.
+ *
+ * @since 7.0.0
+ * @var string[]
+ */
+ private static $available_groups;
+
+ /**
+ * Dynamic output schema built from registered settings.
+ *
+ * @since 7.0.0
+ * @var array
+ */
+ private static $output_schema;
+
+ /**
+ * Available setting slugs with show_in_abilities enabled.
+ *
+ * @since 7.0.0
+ * @var string[]
+ */
+ private static $available_slugs;
+
+ /**
+ * Registers all settings abilities.
+ *
+ * @since 7.0.0
+ *
+ * @return void
+ */
+ public static function register(): void {
+ self::init();
+ self::register_get_settings();
+ }
+
+ /**
+ * Initializes shared data for settings abilities.
+ *
+ * @since 7.0.0
+ *
+ * @return void
+ */
+ private static function init(): void {
+ self::$available_groups = self::get_available_groups();
+ self::$available_slugs = self::get_available_slugs();
+ self::$output_schema = self::build_output_schema();
+ }
+
+ /**
+ * Gets registered settings that have show_in_abilities enabled.
+ *
+ * @since 7.0.0
+ *
+ * @return array Associative array of option_name => args for allowed settings.
+ */
+ private static function get_allowed_settings(): array {
+ $settings = array();
+
+ foreach ( get_registered_settings() as $option_name => $args ) {
+ if ( ! empty( $args['show_in_abilities'] ) ) {
+ $settings[ $option_name ] = $args;
+ }
+ }
+
+ return $settings;
+ }
+
+ /**
+ * Gets unique setting groups that have show_in_abilities enabled.
+ *
+ * @since 7.0.0
+ *
+ * @return string[] List of unique group names.
+ */
+ private static function get_available_groups(): array {
+ $groups = array();
+
+ foreach ( self::get_allowed_settings() as $args ) {
+ $group = $args['group'] ?? 'general';
+ if ( ! in_array( $group, $groups, true ) ) {
+ $groups[] = $group;
+ }
+ }
+
+ sort( $groups );
+
+ return $groups;
+ }
+
+ /**
+ * Gets unique setting slugs that have show_in_abilities enabled.
+ *
+ * @since 7.0.0
+ *
+ * @return string[] List of unique setting slugs.
+ */
+ private static function get_available_slugs(): array {
+ $slugs = array();
+
+ foreach ( self::get_allowed_settings() as $option_name => $args ) {
+ $slugs[] = $option_name;
+ }
+
+ sort( $slugs );
+
+ return $slugs;
+ }
+
+ /**
+ * Builds a rich output schema from registered settings metadata.
+ *
+ * Creates a JSON Schema that documents each setting group and its settings
+ * with their types, titles, descriptions, defaults, and any additional
+ * schema properties from show_in_rest.
+ *
+ * @since 7.0.0
+ *
+ * @return array JSON Schema for the output.
+ */
+ private static function build_output_schema(): array {
+ $group_properties = array();
+
+ foreach ( self::get_allowed_settings() as $option_name => $args ) {
+ $group = $args['group'] ?? 'general';
+
+ $setting_schema = array(
+ 'type' => $args['type'] ?? 'string',
+ );
+
+ if ( ! empty( $args['label'] ) ) {
+ $setting_schema['title'] = $args['label'];
+ }
+
+ if ( ! empty( $args['description'] ) ) {
+ $setting_schema['description'] = $args['description'];
+ } elseif ( ! empty( $args['label'] ) ) {
+ $setting_schema['description'] = $args['label'];
+ }
+
+ if ( ! isset( $group_properties[ $group ] ) ) {
+ $group_properties[ $group ] = array(
+ 'type' => 'object',
+ 'properties' => array(),
+ 'additionalProperties' => false,
+ );
+ }
+
+ $group_properties[ $group ]['properties'][ $option_name ] = $setting_schema;
+ }
+
+ ksort( $group_properties );
+
+ return array(
+ 'type' => 'object',
+ 'description' => __( 'Settings grouped by registration group. Each group contains settings with their current values.' ),
+ 'properties' => $group_properties,
+ 'additionalProperties' => false,
+ );
+ }
+
+ /**
+ * Registers the core/get-settings ability.
+ *
+ * @since 7.0.0
+ *
+ * @return void
+ */
+ private static function register_get_settings(): void {
+ wp_register_ability(
+ 'core/get-settings',
+ array(
+ 'label' => __( 'Get Settings' ),
+ 'description' => __( 'Returns registered WordPress settings grouped by their registration group. Returns key-value pairs per setting.' ),
+ 'category' => 'site',
+ 'input_schema' => array(
+ 'default' => (object) array(),
+ 'oneOf' => array(
+ // Branch 1: No filter (empty object).
+ array(
+ 'type' => 'object',
+ 'additionalProperties' => false,
+ 'maxProperties' => 0,
+ ),
+ // Branch 2: Filter by group only.
+ array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'group' => array(
+ 'type' => 'string',
+ 'description' => __( 'Filter settings by group name.' ),
+ 'enum' => self::$available_groups,
+ ),
+ ),
+ 'required' => array( 'group' ),
+ 'additionalProperties' => false,
+ ),
+ // Branch 3: Filter by slugs only.
+ array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'slugs' => array(
+ 'type' => 'array',
+ 'description' => __( 'Filter settings by specific setting slugs.' ),
+ 'items' => array(
+ 'type' => 'string',
+ 'enum' => self::$available_slugs,
+ ),
+ ),
+ ),
+ 'required' => array( 'slugs' ),
+ 'additionalProperties' => false,
+ ),
+ ),
+ ),
+ 'output_schema' => self::$output_schema,
+ 'execute_callback' => array( __CLASS__, 'execute_get_settings' ),
+ 'permission_callback' => array( __CLASS__, 'check_manage_options' ),
+ 'meta' => array(
+ 'annotations' => array(
+ 'readonly' => true,
+ 'destructive' => false,
+ 'idempotent' => true,
+ ),
+ 'show_in_rest' => true,
+ ),
+ )
+ );
+ }
+
+ /**
+ * Permission callback for settings abilities.
+ *
+ * @since 7.0.0
+ *
+ * @return bool True if the current user can manage options, false otherwise.
+ */
+ public static function check_manage_options(): bool {
+ return current_user_can( 'manage_options' );
+ }
+
+ /**
+ * Execute callback for core/get-settings ability.
+ *
+ * Retrieves all registered settings that are exposed through the Abilities API,
+ * grouped by their registration group.
+ *
+ * @since 7.0.0
+ *
+ * @param array $input {
+ * Optional. Input parameters.
+ *
+ * @type string $group Optional. Filter settings by group name. Cannot be used with slugs.
+ * @type string[] $slugs Optional. Filter settings by specific setting slugs. Cannot be used with group.
+ * }
+ * @return array Settings grouped by registration group.
+ */
+ public static function execute_get_settings( $input = array() ): array {
+ $input = is_array( $input ) ? $input : array();
+ $filter_group = ! empty( $input['group'] ) ? $input['group'] : null;
+ $filter_slugs = ! empty( $input['slugs'] ) ? $input['slugs'] : null;
+
+ $settings_by_group = array();
+
+ foreach ( self::get_allowed_settings() as $option_name => $args ) {
+ $group = $args['group'] ?? 'general';
+
+ if ( $filter_group && $group !== $filter_group ) {
+ continue;
+ }
+
+ if ( $filter_slugs && ! in_array( $option_name, $filter_slugs, true ) ) {
+ continue;
+ }
+
+ $default = $args['default'] ?? null;
+
+ $value = get_option( $option_name, $default );
+ $value = self::cast_value( $value, $args['type'] ?? 'string' );
+
+ if ( ! isset( $settings_by_group[ $group ] ) ) {
+ $settings_by_group[ $group ] = array();
+ }
+
+ $settings_by_group[ $group ][ $option_name ] = $value;
+ }
+
+ ksort( $settings_by_group );
+
+ return $settings_by_group;
+ }
+
+ /**
+ * Casts a value to the appropriate type based on the setting's registered type.
+ *
+ * @since 7.0.0
+ *
+ * @param mixed $value The value to cast.
+ * @param string $type The registered type (string, boolean, integer, number, array, object).
+ * @return string|bool|int|float|array The cast value.
+ */
+ private static function cast_value( $value, string $type ) {
+ switch ( $type ) {
+ case 'boolean':
+ return (bool) $value;
+ case 'integer':
+ return (int) $value;
+ case 'number':
+ return (float) $value;
+ case 'array':
+ case 'object':
+ return is_array( $value ) ? $value : array();
+ case 'string':
+ default:
+ return (string) $value;
+ }
+ }
+}
diff --git a/wp-includes/option.php b/wp-includes/option.php
index 7979c119a9..8a9a2c3c89 100644
--- a/wp-includes/option.php
+++ b/wp-includes/option.php
@@ -2743,12 +2743,13 @@ function register_initial_settings() {
'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.' ),
)
);
@@ -2756,12 +2757,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.' ),
)
);
@@ -2770,14 +2772,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' => __( 'Site URL.' ),
)
);
}
@@ -2787,14 +2790,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.' ),
)
);
}
@@ -2803,11 +2807,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.' ),
)
);
@@ -2815,9 +2820,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.' ),
)
);
@@ -2825,9 +2831,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.' ),
)
);
@@ -2835,9 +2842,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.' ),
)
);
@@ -2845,12 +2853,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',
)
);
@@ -2858,10 +2867,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,
)
);
@@ -2869,9 +2879,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.' ),
)
);
@@ -2879,9 +2890,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.' ),
)
);
@@ -2889,11 +2901,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,
)
);
@@ -2901,10 +2914,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' ),
)
);
@@ -2912,10 +2926,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' ),
)
);
@@ -2923,9 +2938,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' ),
)
);
@@ -2933,13 +2949,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.' ),
)
);
@@ -2947,14 +2964,15 @@ 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.' ),
)
);
}
@@ -2985,10 +3003,12 @@ function register_initial_settings() {
* @type string $label A label of the data attached to this setting.
* @type string $description A description of the data attached to this setting.
* @type callable $sanitize_callback A callback function that sanitizes the option's value.
- * @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 mixed $default Default value when calling `get_option()`.
+ * @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 $show_in_abilities Whether this setting should be exposed through the Abilities API.
+ * Default false.
+ * @type mixed $default Default value when calling `get_option()`.
* }
*/
function register_setting( $option_group, $option_name, $args = array() ) {
@@ -3001,12 +3021,13 @@ function register_setting( $option_group, $option_name, $args = array() ) {
$GLOBALS['new_whitelist_options'] = &$new_allowed_options;
$defaults = array(
- 'type' => 'string',
- 'group' => $option_group,
- 'label' => '',
- 'description' => '',
- 'sanitize_callback' => null,
- 'show_in_rest' => false,
+ 'type' => 'string',
+ 'group' => $option_group,
+ 'label' => '',
+ 'description' => '',
+ 'sanitize_callback' => null,
+ 'show_in_rest' => false,
+ 'show_in_abilities' => false,
);
// Back-compat: old sanitize callback is added.
diff --git a/wp-includes/version.php b/wp-includes/version.php
index 6033270417..1d1d637b79 100644
--- a/wp-includes/version.php
+++ b/wp-includes/version.php
@@ -16,7 +16,7 @@
*
* @global string $wp_version
*/
-$wp_version = '7.0-alpha-61599';
+$wp_version = '7.0-alpha-61600';
/**
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.