diff --git a/src/wp-includes/abilities/class-wp-settings-abilities.php b/src/wp-includes/abilities/class-wp-settings-abilities.php index 5af7fa48450ee..463efc1fe9293 100644 --- a/src/wp-includes/abilities/class-wp-settings-abilities.php +++ b/src/wp-includes/abilities/class-wp-settings-abilities.php @@ -137,7 +137,7 @@ private static function get_available_slugs(): array { * * 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. + * schema properties from show_in_abilities. * * @since 7.0.0 * @@ -163,6 +163,11 @@ private static function build_output_schema(): array { $setting_schema['description'] = $args['label']; } + // Merge custom schema from show_in_abilities if provided as an array. + if ( is_array( $args['show_in_abilities'] ) && ! empty( $args['show_in_abilities']['schema'] ) ) { + $setting_schema = array_merge( $setting_schema, $args['show_in_abilities']['schema'] ); + } + if ( ! isset( $group_properties[ $group ] ) ) { $group_properties[ $group ] = array( 'type' => 'object', diff --git a/src/wp-includes/option.php b/src/wp-includes/option.php index 8a9a2c3c89ece..fbd650785c208 100644 --- a/src/wp-includes/option.php +++ b/src/wp-includes/option.php @@ -2768,17 +2768,18 @@ function register_initial_settings() { ); if ( ! is_multisite() ) { + $uri_schema = array( 'format' => 'uri' ); register_setting( 'general', 'siteurl', array( 'show_in_rest' => array( 'name' => 'url', - 'schema' => array( - 'format' => 'uri', - ), + 'schema' => $uri_schema, + ), + 'show_in_abilities' => array( + 'schema' => $uri_schema, ), - 'show_in_abilities' => true, 'type' => 'string', 'description' => __( 'Site URL.' ), ) @@ -2786,17 +2787,18 @@ function register_initial_settings() { } if ( ! is_multisite() ) { + $email_schema = array( 'format' => 'email' ); register_setting( 'general', 'admin_email', array( 'show_in_rest' => array( 'name' => 'email', - 'schema' => array( - 'format' => 'email', - ), + 'schema' => $email_schema, + ), + 'show_in_abilities' => array( + 'schema' => $email_schema, ), - 'show_in_abilities' => true, 'type' => 'string', 'description' => __( 'This address is used for admin purposes, like new user notification.' ), ) @@ -2945,16 +2947,18 @@ function register_initial_settings() { ) ); + $open_closed_enum_schema = array( 'enum' => array( 'open', 'closed' ) ); + register_setting( 'discussion', 'default_ping_status', array( 'show_in_rest' => array( - 'schema' => array( - 'enum' => array( 'open', 'closed' ), - ), + 'schema' => $open_closed_enum_schema, + ), + 'show_in_abilities' => array( + 'schema' => $open_closed_enum_schema, ), - 'show_in_abilities' => true, 'type' => 'string', 'description' => __( 'Allow link notifications from other blogs (pingbacks and trackbacks) on new articles.' ), ) @@ -2965,11 +2969,11 @@ function register_initial_settings() { 'default_comment_status', array( 'show_in_rest' => array( - 'schema' => array( - 'enum' => array( 'open', 'closed' ), - ), + 'schema' => $open_closed_enum_schema, + ), + 'show_in_abilities' => array( + 'schema' => $open_closed_enum_schema, ), - 'show_in_abilities' => true, 'type' => 'string', 'label' => __( 'Allow comments on new posts' ), 'description' => __( 'Allow people to submit comments on new posts.' ), @@ -3006,8 +3010,9 @@ 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 $show_in_abilities Whether this setting should be exposed through the Abilities API. - * Default false. + * @type bool|array $show_in_abilities Whether this setting should be exposed through the Abilities API. + * When registering complex settings, this argument may optionally be an + * array with a 'schema' key. Default false. * @type mixed $default Default value when calling `get_option()`. * } */ diff --git a/tests/phpunit/tests/rest-api/wpRestAbilitiesSettingsController.php b/tests/phpunit/tests/rest-api/wpRestAbilitiesSettingsController.php index 198c0c3b8bc69..65d17b0eb3365 100644 --- a/tests/phpunit/tests/rest-api/wpRestAbilitiesSettingsController.php +++ b/tests/phpunit/tests/rest-api/wpRestAbilitiesSettingsController.php @@ -353,4 +353,89 @@ public function test_core_get_settings_returns_correct_values(): void { $this->assertSame( 'Test Site Name', $data['general']['blogname'] ); } + + /** + * Tests that settings with enum schema in show_in_abilities include it in output schema. + * + * @ticket 64605 + */ + public function test_core_get_settings_output_schema_includes_enum(): void { + $ability = wp_get_ability( 'core/get-settings' ); + $output_schema = $ability->get_output_schema(); + + // Check default_ping_status has enum. + $this->assertArrayHasKey( 'discussion', $output_schema['properties'] ); + $this->assertArrayHasKey( 'default_ping_status', $output_schema['properties']['discussion']['properties'] ); + $this->assertArrayHasKey( 'enum', $output_schema['properties']['discussion']['properties']['default_ping_status'] ); + $this->assertSame( array( 'open', 'closed' ), $output_schema['properties']['discussion']['properties']['default_ping_status']['enum'] ); + + // Check default_comment_status has enum. + $this->assertArrayHasKey( 'default_comment_status', $output_schema['properties']['discussion']['properties'] ); + $this->assertArrayHasKey( 'enum', $output_schema['properties']['discussion']['properties']['default_comment_status'] ); + $this->assertSame( array( 'open', 'closed' ), $output_schema['properties']['discussion']['properties']['default_comment_status']['enum'] ); + } + + /** + * Tests that boolean show_in_abilities (true) still works correctly. + * + * @ticket 64605 + */ + public function test_core_get_settings_boolean_show_in_abilities_still_works(): void { + $ability = wp_get_ability( 'core/get-settings' ); + $output_schema = $ability->get_output_schema(); + + // blogname uses show_in_abilities => true (boolean). + $this->assertArrayHasKey( 'general', $output_schema['properties'] ); + $this->assertArrayHasKey( 'blogname', $output_schema['properties']['general']['properties'] ); + $this->assertSame( 'string', $output_schema['properties']['general']['properties']['blogname']['type'] ); + } + + /** + * Tests that custom show_in_abilities schema preserves base schema properties while adding custom ones. + * + * @ticket 64605 + */ + public function test_core_get_settings_output_schema_preserves_base_schema(): void { + $ability = wp_get_ability( 'core/get-settings' ); + $output_schema = $ability->get_output_schema(); + + // default_comment_status has show_in_abilities with schema but also has label and description. + $this->assertArrayHasKey( 'discussion', $output_schema['properties'] ); + $this->assertArrayHasKey( 'default_comment_status', $output_schema['properties']['discussion']['properties'] ); + + $setting_schema = $output_schema['properties']['discussion']['properties']['default_comment_status']; + + // Verify base schema properties are preserved. + $this->assertSame( 'string', $setting_schema['type'] ); + $this->assertArrayHasKey( 'title', $setting_schema ); + $this->assertArrayHasKey( 'description', $setting_schema ); + + // Verify custom schema property (enum) is merged. + $this->assertArrayHasKey( 'enum', $setting_schema ); + $this->assertSame( array( 'open', 'closed' ), $setting_schema['enum'] ); + } + + /** + * Tests that ability returns error when setting value violates schema enum. + * + * @ticket 64605 + */ + public function test_core_get_settings_returns_error_for_invalid_enum_value(): void { + // Set an invalid value for default_ping_status (violates enum: ['open', 'closed']). + update_option( 'default_ping_status', 'invalid_value' ); + + $request = new WP_REST_Request( 'GET', '/wp-abilities/v1/abilities/core/get-settings/run' ); + $request->set_query_params( + array( + 'input' => array( + 'slugs' => array( 'default_ping_status' ), + ), + ) + ); + $response = $this->server->dispatch( $request ); + + $this->assertSame( 500, $response->get_status() ); + $data = $response->get_data(); + $this->assertSame( 'ability_invalid_output', $data['code'] ); + } }