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.