diff --git a/src/wp-includes/abilities.php b/src/wp-includes/abilities.php index 4c6db1ed830e0..08c471020f91b 100644 --- a/src/wp-includes/abilities.php +++ b/src/wp-includes/abilities.php @@ -130,57 +130,114 @@ function wp_register_core_abilities(): void { ) ); + $user_info_properties = array( + 'id' => array( + 'type' => 'integer', + 'title' => __( 'User ID' ), + 'description' => __( 'The user ID.' ), + ), + 'display_name' => array( + 'type' => 'string', + 'title' => __( 'Display Name' ), + 'description' => __( 'The display name of the user.' ), + ), + 'user_nicename' => array( + 'type' => 'string', + 'title' => __( 'User Nicename' ), + 'description' => __( 'The URL-friendly name for the user.' ), + ), + 'user_login' => array( + 'type' => 'string', + 'title' => __( 'Username' ), + 'description' => __( 'The login username for the user.' ), + ), + 'roles' => array( + 'type' => 'array', + 'title' => __( 'Roles' ), + 'description' => __( 'The roles assigned to the user.' ), + 'items' => array( + 'type' => 'string', + ), + ), + 'locale' => array( + 'type' => 'string', + 'title' => __( 'Locale' ), + 'description' => __( 'The locale string for the user, such as en_US.' ), + ), + 'first_name' => array( + 'type' => 'string', + 'title' => __( 'First Name' ), + 'description' => __( 'The first name of the user.' ), + ), + 'last_name' => array( + 'type' => 'string', + 'title' => __( 'Last Name' ), + 'description' => __( 'The last name of the user.' ), + ), + 'nickname' => array( + 'type' => 'string', + 'title' => __( 'Nickname' ), + 'description' => __( 'The nickname of the user.' ), + ), + 'description' => array( + 'type' => 'string', + 'title' => __( 'Biographical Info' ), + 'description' => __( 'The biographical description of the user.' ), + ), + 'user_url' => array( + 'type' => 'string', + 'title' => __( 'Website' ), + 'description' => __( 'The URL of the user\'s website.' ), + ), + ); + $user_info_fields = array_keys( $user_info_properties ); + wp_register_ability( 'core/get-user-info', array( 'label' => __( 'Get User Information' ), - 'description' => __( 'Returns basic profile details for the current authenticated user to support personalization, auditing, and access-aware behavior.' ), + 'description' => __( 'Returns profile details for the current authenticated user to support personalization, auditing, and access-aware behavior. By default returns all fields, or optionally a filtered subset.' ), 'category' => $category_user, - 'output_schema' => array( + 'input_schema' => array( 'type' => 'object', - 'required' => array( 'id', 'display_name', 'user_nicename', 'user_login', 'roles', 'locale' ), 'properties' => array( - 'id' => array( - 'type' => 'integer', - 'description' => __( 'The user ID.' ), - ), - 'display_name' => array( - 'type' => 'string', - 'description' => __( 'The display name of the user.' ), - ), - 'user_nicename' => array( - 'type' => 'string', - 'description' => __( 'The URL-friendly name for the user.' ), - ), - 'user_login' => array( - 'type' => 'string', - 'description' => __( 'The login username for the user.' ), - ), - 'roles' => array( + 'fields' => array( 'type' => 'array', - 'description' => __( 'The roles assigned to the user.' ), 'items' => array( 'type' => 'string', + 'enum' => $user_info_fields, ), - ), - 'locale' => array( - 'type' => 'string', - 'description' => __( 'The locale string for the user, such as en_US.' ), + 'description' => __( 'Optional: Limit response to specific fields. If omitted, all fields are returned.' ), ), ), 'additionalProperties' => false, + 'default' => array(), ), - 'execute_callback' => static function (): array { - $current_user = wp_get_current_user(); + 'output_schema' => array( + 'type' => 'object', + 'properties' => $user_info_properties, + 'additionalProperties' => false, + ), + 'execute_callback' => static function ( $input = array() ) use ( $user_info_fields ): array { + $input = is_array( $input ) ? $input : array(); + $requested_fields = ! empty( $input['fields'] ) ? $input['fields'] : $user_info_fields; + $current_user = wp_get_current_user(); - return array( + $all = array( 'id' => $current_user->ID, 'display_name' => $current_user->display_name, 'user_nicename' => $current_user->user_nicename, 'user_login' => $current_user->user_login, - 'roles' => $current_user->roles, + 'roles' => array_values( $current_user->roles ), 'locale' => get_user_locale( $current_user ), + 'first_name' => $current_user->first_name, + 'last_name' => $current_user->last_name, + 'nickname' => $current_user->nickname, + 'description' => $current_user->description, + 'user_url' => $current_user->user_url, ); + + return array_intersect_key( $all, array_flip( $requested_fields ) ); }, 'permission_callback' => static function (): bool { return is_user_logged_in(); diff --git a/tests/phpunit/tests/abilities-api/wpRegisterCoreAbilities.php b/tests/phpunit/tests/abilities-api/wpRegisterCoreAbilities.php index 48cae6efd1dee..ab067fb604db2 100644 --- a/tests/phpunit/tests/abilities-api/wpRegisterCoreAbilities.php +++ b/tests/phpunit/tests/abilities-api/wpRegisterCoreAbilities.php @@ -136,8 +136,13 @@ public function test_core_get_current_user_info_requires_authentication(): void public function test_core_get_current_user_info_returns_user_data(): void { $user_id = self::factory()->user->create( array( - 'role' => 'subscriber', - 'locale' => 'fr_FR', + 'role' => 'subscriber', + 'locale' => 'fr_FR', + 'first_name' => 'Jane', + 'last_name' => 'Doe', + 'nickname' => 'janed', + 'description' => 'Site contributor.', + 'user_url' => 'https://example.com', ) ); @@ -152,6 +157,98 @@ public function test_core_get_current_user_info_returns_user_data(): void { $this->assertSame( 'fr_FR', $result['locale'] ); $this->assertSame( 'subscriber', $result['roles'][0] ); $this->assertSame( get_userdata( $user_id )->display_name, $result['display_name'] ); + + // New profile fields should be present by default. + $this->assertSame( 'Jane', $result['first_name'] ); + $this->assertSame( 'Doe', $result['last_name'] ); + $this->assertSame( 'janed', $result['nickname'] ); + $this->assertSame( 'Site contributor.', $result['description'] ); + $this->assertSame( 'https://example.com', $result['user_url'] ); + } + + /** + * Tests that the `core/get-user-info` ability is registered with the expected schema. + * @ticket 65234 + */ + public function test_core_get_user_info_ability_is_registered(): void { + $ability = wp_get_ability( 'core/get-user-info' ); + + $this->assertInstanceOf( WP_Ability::class, $ability ); + + $input_schema = $ability->get_input_schema(); + $output_schema = $ability->get_output_schema(); + + // Input schema should expose an optional `fields` array with an enum of valid field names. + $this->assertSame( 'object', $input_schema['type'] ); + $this->assertArrayHasKey( 'default', $input_schema ); + $this->assertSame( array(), $input_schema['default'] ); + $this->assertArrayHasKey( 'fields', $input_schema['properties'] ); + $this->assertSame( 'array', $input_schema['properties']['fields']['type'] ); + + $enum = $input_schema['properties']['fields']['items']['enum']; + foreach ( array( 'id', 'display_name', 'first_name', 'last_name', 'nickname', 'description', 'user_url' ) as $field ) { + $this->assertContains( $field, $enum ); + } + + // Output schema should document the original and new profile fields with title + description. + foreach ( array( 'id', 'display_name', 'first_name', 'last_name', 'nickname', 'description', 'user_url' ) as $field ) { + $this->assertArrayHasKey( $field, $output_schema['properties'] ); + $this->assertArrayHasKey( 'title', $output_schema['properties'][ $field ] ); + $this->assertArrayHasKey( 'description', $output_schema['properties'][ $field ] ); + } + } + + /** + * Tests that the `core/get-user-info` ability filters its output by the `fields` input parameter. + * @ticket 65234 + */ + public function test_core_get_user_info_filters_fields(): void { + $user_id = self::factory()->user->create( + array( + 'role' => 'subscriber', + 'first_name' => 'Jane', + 'last_name' => 'Doe', + ) + ); + wp_set_current_user( $user_id ); + + $ability = wp_get_ability( 'core/get-user-info' ); + + $result = $ability->execute( + array( + 'fields' => array( 'display_name', 'first_name', 'last_name' ), + ) + ); + + $this->assertIsArray( $result ); + $this->assertCount( 3, $result ); + $this->assertArrayHasKey( 'display_name', $result ); + $this->assertArrayHasKey( 'first_name', $result ); + $this->assertArrayHasKey( 'last_name', $result ); + $this->assertArrayNotHasKey( 'id', $result ); + $this->assertArrayNotHasKey( 'roles', $result ); + $this->assertSame( 'Jane', $result['first_name'] ); + $this->assertSame( 'Doe', $result['last_name'] ); + } + + /** + * Tests that the `core/get-user-info` ability rejects unknown field names via schema validation. + * @ticket 65234 + */ + public function test_core_get_user_info_rejects_invalid_fields(): void { + $user_id = self::factory()->user->create( array( 'role' => 'subscriber' ) ); + wp_set_current_user( $user_id ); + + $ability = wp_get_ability( 'core/get-user-info' ); + + $result = $ability->execute( + array( + 'fields' => array( 'display_name', 'not_a_real_field' ), + ) + ); + + $this->assertWPError( $result ); + $this->assertSame( 'ability_invalid_input', $result->get_error_code() ); } /**