From 824ecaba0e4e6efcf0a5b7b91eadc9f84cc34029 Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Thu, 22 Jan 2026 18:06:41 +0000 Subject: [PATCH 01/17] Add: Get current user ability. --- src/wp-includes/abilities.php | 1 + .../abilities/class-wp-users-abilities.php | 271 ++++++++++++++++++ 2 files changed, 272 insertions(+) create mode 100644 src/wp-includes/abilities/class-wp-users-abilities.php diff --git a/src/wp-includes/abilities.php b/src/wp-includes/abilities.php index 4c6db1ed830e0..ed1cdcee6bf2a 100644 --- a/src/wp-includes/abilities.php +++ b/src/wp-includes/abilities.php @@ -9,6 +9,7 @@ declare( strict_types = 1 ); +require_once __DIR__ . '/abilities/class-wp-users-abilities.php'; /** * Registers the core ability categories. * diff --git a/src/wp-includes/abilities/class-wp-users-abilities.php b/src/wp-includes/abilities/class-wp-users-abilities.php new file mode 100644 index 0000000000000..ac8a81b184123 --- /dev/null +++ b/src/wp-includes/abilities/class-wp-users-abilities.php @@ -0,0 +1,271 @@ + __( 'Get Current User' ), + 'description' => __( 'Returns comprehensive profile details for the current authenticated user including identity, profile fields, and roles.' ), + 'category' => 'user', + 'input_schema' => self::get_current_user_input_schema(), + 'output_schema' => self::get_current_user_output_schema(), + 'execute_callback' => array( __CLASS__, 'execute_get_current_user' ), + 'permission_callback' => static function (): bool { + return is_user_logged_in(); + }, + 'meta' => array( + 'annotations' => array( + 'readonly' => true, + 'destructive' => false, + 'idempotent' => true, + ), + 'show_in_rest' => true, + ), + ) + ); + } + + /** + * Gets the input schema for the get-current-user ability. + * + * @since 6.9.0 + * + * @return array The input schema. + */ + private static function get_current_user_input_schema(): array { + return array( + 'type' => 'object', + 'properties' => array( + 'include_capabilities' => array( + 'type' => 'boolean', + 'description' => __( 'Whether to include the user capabilities in the response.' ), + 'default' => false, + ), + 'include_meta' => array( + 'type' => 'boolean', + 'description' => __( 'Whether to include user meta fields in the response.' ), + 'default' => false, + ), + ), + 'additionalProperties' => false, + 'default' => array(), + ); + } + + /** + * Gets the output schema for the get-current-user ability. + * + * @since 6.9.0 + * + * @return array The output schema. + */ + private static function get_current_user_output_schema(): array { + return array( + 'type' => 'object', + 'required' => array( + 'id', + 'username', + 'email', + 'display_name', + 'first_name', + 'last_name', + 'nickname', + 'description', + 'url', + 'link', + 'slug', + 'registered_date', + 'roles', + 'locale', + 'avatar_urls', + ), + 'properties' => array( + 'id' => array( + 'type' => 'integer', + 'description' => __( 'Unique identifier for the user.' ), + ), + 'username' => array( + 'type' => 'string', + 'description' => __( 'Login name for the user.' ), + ), + 'email' => array( + 'type' => 'string', + 'description' => __( 'The email address for the user.' ), + ), + 'display_name' => array( + 'type' => 'string', + 'description' => __( 'Display name for the user.' ), + ), + 'first_name' => array( + 'type' => 'string', + 'description' => __( 'First name for the user.' ), + ), + 'last_name' => array( + 'type' => 'string', + 'description' => __( 'Last name for the user.' ), + ), + 'nickname' => array( + 'type' => 'string', + 'description' => __( 'The nickname for the user.' ), + ), + 'description' => array( + 'type' => 'string', + 'description' => __( 'Description of the user.' ), + ), + 'url' => array( + 'type' => 'string', + 'description' => __( 'URL of the user.' ), + ), + 'link' => array( + 'type' => 'string', + 'description' => __( 'Author URL of the user.' ), + ), + 'slug' => array( + 'type' => 'string', + 'description' => __( 'An alphanumeric identifier for the user.' ), + ), + 'registered_date' => array( + 'type' => 'string', + 'description' => __( 'Registration date for the user in ISO 8601 format.' ), + ), + 'roles' => array( + 'type' => 'array', + 'description' => __( 'Roles assigned to the user.' ), + 'items' => array( + 'type' => 'string', + ), + ), + 'locale' => array( + 'type' => 'string', + 'description' => __( 'Locale for the user.' ), + ), + 'avatar_urls' => array( + 'type' => 'object', + 'description' => __( 'Avatar URLs for the user at various sizes.' ), + ), + 'capabilities' => array( + 'type' => 'object', + 'description' => __( 'All capabilities assigned to the user. Only included if include_capabilities is true.' ), + ), + 'meta' => array( + 'type' => 'object', + 'description' => __( 'User meta fields. Only included if include_meta is true.' ), + ), + ), + 'additionalProperties' => false, + ); + } + + /** + * Executes the get-current-user ability. + * + * @since 6.9.0 + * + * @param array $input The input parameters. + * @return array The current user data. + */ + public static function execute_get_current_user( array $input = array() ): array { + $input = is_array( $input ) ? $input : array(); + $include_capabilities = ! empty( $input['include_capabilities'] ); + $include_meta = ! empty( $input['include_meta'] ); + + $user = wp_get_current_user(); + + $result = array( + 'id' => $user->ID, + 'username' => $user->user_login, + 'email' => $user->user_email, + 'display_name' => $user->display_name, + 'first_name' => $user->first_name, + 'last_name' => $user->last_name, + 'nickname' => $user->nickname, + 'description' => $user->description, + 'url' => $user->user_url, + 'link' => get_author_posts_url( $user->ID, $user->user_nicename ), + 'slug' => $user->user_nicename, + 'registered_date' => gmdate( 'c', strtotime( $user->user_registered ) ), + 'roles' => array_values( $user->roles ), + 'locale' => get_user_locale( $user ), + 'avatar_urls' => rest_get_avatar_urls( $user ), + ); + + if ( $include_capabilities ) { + $result['capabilities'] = (object) $user->allcaps; + } + + if ( $include_meta ) { + $result['meta'] = self::get_user_meta_for_response( $user->ID ); + } + + return $result; + } + + /** + * Gets user meta fields for the response. + * + * @since 6.9.0 + * + * @param int $user_id The user ID. + * @return array The user meta. + */ + private static function get_user_meta_for_response( int $user_id ): array { + $meta = get_user_meta( $user_id ); + $processed_meta = array(); + + foreach ( $meta as $key => $values ) { + // Skip private meta keys (prefixed with underscore). + if ( str_starts_with( $key, '_' ) ) { + continue; + } + + if ( count( $values ) === 1 ) { + $processed_meta[ $key ] = maybe_unserialize( $values[0] ); + } else { + $processed_meta[ $key ] = array_map( 'maybe_unserialize', $values ); + } + } + + return $processed_meta; + } +} From 7a5cc0d7bea77b25d202deb4cb4597a8b52a18d1 Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Thu, 22 Jan 2026 18:27:08 +0000 Subject: [PATCH 02/17] fix unexpected inclusion of meta or properties --- src/wp-includes/abilities/class-wp-users-abilities.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/abilities/class-wp-users-abilities.php b/src/wp-includes/abilities/class-wp-users-abilities.php index ac8a81b184123..e764d42da1319 100644 --- a/src/wp-includes/abilities/class-wp-users-abilities.php +++ b/src/wp-includes/abilities/class-wp-users-abilities.php @@ -207,8 +207,8 @@ private static function get_current_user_output_schema(): array { */ public static function execute_get_current_user( array $input = array() ): array { $input = is_array( $input ) ? $input : array(); - $include_capabilities = ! empty( $input['include_capabilities'] ); - $include_meta = ! empty( $input['include_meta'] ); + $include_capabilities = ! empty( $input['include_capabilities'] ) && ( $input['include_capabilities'] === true || $input['include_capabilities'] === 'true' ); + $include_meta = ! empty( $input['include_meta'] ) && ( $input['include_meta'] === true || $input['include_meta'] === 'true' ); $user = wp_get_current_user(); From 08bb9683bd9af90d03ad938ec495ed96063f2aab Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Thu, 22 Jan 2026 18:48:19 +0000 Subject: [PATCH 03/17] remove unrequired code --- .../abilities/class-wp-users-abilities.php | 44 +------------------ 1 file changed, 1 insertion(+), 43 deletions(-) diff --git a/src/wp-includes/abilities/class-wp-users-abilities.php b/src/wp-includes/abilities/class-wp-users-abilities.php index e764d42da1319..17a24d84c83bd 100644 --- a/src/wp-includes/abilities/class-wp-users-abilities.php +++ b/src/wp-includes/abilities/class-wp-users-abilities.php @@ -82,11 +82,6 @@ private static function get_current_user_input_schema(): array { 'description' => __( 'Whether to include the user capabilities in the response.' ), 'default' => false, ), - 'include_meta' => array( - 'type' => 'boolean', - 'description' => __( 'Whether to include user meta fields in the response.' ), - 'default' => false, - ), ), 'additionalProperties' => false, 'default' => array(), @@ -188,10 +183,6 @@ private static function get_current_user_output_schema(): array { 'type' => 'object', 'description' => __( 'All capabilities assigned to the user. Only included if include_capabilities is true.' ), ), - 'meta' => array( - 'type' => 'object', - 'description' => __( 'User meta fields. Only included if include_meta is true.' ), - ), ), 'additionalProperties' => false, ); @@ -207,8 +198,7 @@ private static function get_current_user_output_schema(): array { */ public static function execute_get_current_user( array $input = array() ): array { $input = is_array( $input ) ? $input : array(); - $include_capabilities = ! empty( $input['include_capabilities'] ) && ( $input['include_capabilities'] === true || $input['include_capabilities'] === 'true' ); - $include_meta = ! empty( $input['include_meta'] ) && ( $input['include_meta'] === true || $input['include_meta'] === 'true' ); + $include_capabilities = ! empty( $input['include_capabilities'] ); $user = wp_get_current_user(); @@ -234,38 +224,6 @@ public static function execute_get_current_user( array $input = array() ): array $result['capabilities'] = (object) $user->allcaps; } - if ( $include_meta ) { - $result['meta'] = self::get_user_meta_for_response( $user->ID ); - } - return $result; } - - /** - * Gets user meta fields for the response. - * - * @since 6.9.0 - * - * @param int $user_id The user ID. - * @return array The user meta. - */ - private static function get_user_meta_for_response( int $user_id ): array { - $meta = get_user_meta( $user_id ); - $processed_meta = array(); - - foreach ( $meta as $key => $values ) { - // Skip private meta keys (prefixed with underscore). - if ( str_starts_with( $key, '_' ) ) { - continue; - } - - if ( count( $values ) === 1 ) { - $processed_meta[ $key ] = maybe_unserialize( $values[0] ); - } else { - $processed_meta[ $key ] = array_map( 'maybe_unserialize', $values ); - } - } - - return $processed_meta; - } } From 44bb39aac8c55bc53af6c04d9f9dac1a4962245a Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Thu, 22 Jan 2026 18:51:04 +0000 Subject: [PATCH 04/17] bug fix --- src/wp-includes/abilities/class-wp-users-abilities.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/abilities/class-wp-users-abilities.php b/src/wp-includes/abilities/class-wp-users-abilities.php index 17a24d84c83bd..a48a705d23638 100644 --- a/src/wp-includes/abilities/class-wp-users-abilities.php +++ b/src/wp-includes/abilities/class-wp-users-abilities.php @@ -198,7 +198,7 @@ private static function get_current_user_output_schema(): array { */ public static function execute_get_current_user( array $input = array() ): array { $input = is_array( $input ) ? $input : array(); - $include_capabilities = ! empty( $input['include_capabilities'] ); + $include_capabilities = ! empty( $input['include_capabilities'] ) && ( $input['include_capabilities'] === true || $input['include_capabilities'] === 'true' ); $user = wp_get_current_user(); From 7b36b5d6c6d621c61d1e66a2f85314fbba35da1c Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Thu, 22 Jan 2026 19:40:39 +0000 Subject: [PATCH 05/17] renamte get user --- .../abilities/class-wp-users-abilities.php | 110 ++++++++++++++---- 1 file changed, 89 insertions(+), 21 deletions(-) diff --git a/src/wp-includes/abilities/class-wp-users-abilities.php b/src/wp-includes/abilities/class-wp-users-abilities.php index a48a705d23638..f31f1c4515adf 100644 --- a/src/wp-includes/abilities/class-wp-users-abilities.php +++ b/src/wp-includes/abilities/class-wp-users-abilities.php @@ -31,29 +31,27 @@ class WP_Users_Abilities { * @return void */ public static function register(): void { - self::register_get_current_user(); + self::register_get_user(); } /** - * Registers the core/get-current-user ability. + * Registers the core/get-user ability. * * @since 6.9.0 * * @return void */ - private static function register_get_current_user(): void { + private static function register_get_user(): void { wp_register_ability( - 'core/get-current-user', + 'core/get-user', array( - 'label' => __( 'Get Current User' ), - 'description' => __( 'Returns comprehensive profile details for the current authenticated user including identity, profile fields, and roles.' ), + 'label' => __( 'Get User' ), + 'description' => __( 'Returns comprehensive profile details for a user by id, username, or email.' ), 'category' => 'user', - 'input_schema' => self::get_current_user_input_schema(), - 'output_schema' => self::get_current_user_output_schema(), - 'execute_callback' => array( __CLASS__, 'execute_get_current_user' ), - 'permission_callback' => static function (): bool { - return is_user_logged_in(); - }, + 'input_schema' => self::get_user_input_schema(), + 'output_schema' => self::get_user_output_schema(), + 'execute_callback' => array( __CLASS__, 'execute_get_user' ), + 'permission_callback' => array( __CLASS__, 'check_get_user_permission' ), 'meta' => array( 'annotations' => array( 'readonly' => true, @@ -67,16 +65,33 @@ private static function register_get_current_user(): void { } /** - * Gets the input schema for the get-current-user ability. + * Gets the input schema for the get-user ability. * * @since 6.9.0 * * @return array The input schema. */ - private static function get_current_user_input_schema(): array { + private static function get_user_input_schema(): array { return array( 'type' => 'object', + 'oneOf' => array( + array( 'required' => array( 'id' ) ), + array( 'required' => array( 'username' ) ), + array( 'required' => array( 'email' ) ), + ), 'properties' => array( + 'id' => array( + 'type' => 'integer', + 'description' => __( 'User ID.' ), + ), + 'username' => array( + 'type' => 'string', + 'description' => __( 'User login name.' ), + ), + 'email' => array( + 'type' => 'string', + 'description' => __( 'User email address.' ), + ), 'include_capabilities' => array( 'type' => 'boolean', 'description' => __( 'Whether to include the user capabilities in the response.' ), @@ -84,18 +99,17 @@ private static function get_current_user_input_schema(): array { ), ), 'additionalProperties' => false, - 'default' => array(), ); } /** - * Gets the output schema for the get-current-user ability. + * Gets the output schema for the get-user ability. * * @since 6.9.0 * * @return array The output schema. */ - private static function get_current_user_output_schema(): array { + private static function get_user_output_schema(): array { return array( 'type' => 'object', 'required' => array( @@ -189,18 +203,72 @@ private static function get_current_user_output_schema(): array { } /** - * Executes the get-current-user ability. + * Finds a user by id, username, or email from input parameters. + * + * @since 6.9.0 + * + * @param array $input The input parameters. + * @return WP_User|false The user object if found, false otherwise. + */ + private static function find_user( array $input ) { + if ( ! empty( $input['id'] ) ) { + return get_user_by( 'ID', (int) $input['id'] ); + } + + if ( ! empty( $input['username'] ) ) { + return get_user_by( 'login', sanitize_user( $input['username'] ) ); + } + + if ( ! empty( $input['email'] ) ) { + return get_user_by( 'email', sanitize_email( $input['email'] ) ); + } + + return false; + } + + /** + * Permission callback for the get-user ability. + * + * @since 6.9.0 + * + * @param array $input The input parameters. + * @return bool Whether the user has permission. + */ + public static function check_get_user_permission( array $input = array() ): bool { + // Must be logged in. + if ( ! is_user_logged_in() ) { + return false; + } + + $user = self::find_user( $input ); + $target_user_id = $user ? $user->ID : 0; + + // Users can view their own profile. + if ( get_current_user_id() === $target_user_id ) { + return true; + } + + // Otherwise require list_users capability. + return current_user_can( 'list_users' ); + } + + /** + * Executes the get-user ability. * * @since 6.9.0 * * @param array $input The input parameters. - * @return array The current user data. + * @return array|WP_Error The user data or error. */ - public static function execute_get_current_user( array $input = array() ): array { + public static function execute_get_user( array $input = array() ) { $input = is_array( $input ) ? $input : array(); $include_capabilities = ! empty( $input['include_capabilities'] ) && ( $input['include_capabilities'] === true || $input['include_capabilities'] === 'true' ); - $user = wp_get_current_user(); + $user = self::find_user( $input ); + + if ( ! $user || ! $user->exists() ) { + return new WP_Error( 'user_not_found', __( 'User not found.' ), array( 'status' => 404 ) ); + } $result = array( 'id' => $user->ID, From 0985dbf61909c9b91bfba941c68d3bee930edb6f Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Thu, 22 Jan 2026 19:47:40 +0000 Subject: [PATCH 06/17] lint fix --- src/wp-includes/abilities/class-wp-users-abilities.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/abilities/class-wp-users-abilities.php b/src/wp-includes/abilities/class-wp-users-abilities.php index f31f1c4515adf..abc2b7369143e 100644 --- a/src/wp-includes/abilities/class-wp-users-abilities.php +++ b/src/wp-includes/abilities/class-wp-users-abilities.php @@ -262,7 +262,7 @@ public static function check_get_user_permission( array $input = array() ): bool */ public static function execute_get_user( array $input = array() ) { $input = is_array( $input ) ? $input : array(); - $include_capabilities = ! empty( $input['include_capabilities'] ) && ( $input['include_capabilities'] === true || $input['include_capabilities'] === 'true' ); + $include_capabilities = ! empty( $input['include_capabilities'] ) && ( true === $input['include_capabilities'] || 'true' === $input['include_capabilities'] ); $user = self::find_user( $input ); From a0fab0f6704576b1b6389b2685845d39ddd80871 Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Thu, 12 Feb 2026 11:56:18 +0000 Subject: [PATCH 07/17] post rebase fix --- src/wp-includes/abilities.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/wp-includes/abilities.php b/src/wp-includes/abilities.php index ed1cdcee6bf2a..f9a6413250171 100644 --- a/src/wp-includes/abilities.php +++ b/src/wp-includes/abilities.php @@ -258,4 +258,5 @@ function wp_register_core_abilities(): void { ), ) ); + WP_Users_Abilities::register(); } From b933772631df786949f8f8f0240f391284de1ce7 Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Thu, 12 Feb 2026 11:57:14 +0000 Subject: [PATCH 08/17] fix since versions --- .../abilities/class-wp-users-abilities.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/wp-includes/abilities/class-wp-users-abilities.php b/src/wp-includes/abilities/class-wp-users-abilities.php index abc2b7369143e..3a7313071ad6e 100644 --- a/src/wp-includes/abilities/class-wp-users-abilities.php +++ b/src/wp-includes/abilities/class-wp-users-abilities.php @@ -7,7 +7,7 @@ * * @package WordPress * @subpackage Abilities_API - * @since 6.9.0 + * @since 7.0.0 * * @internal This class is not part of the public API. * @access private @@ -18,7 +18,7 @@ /** * Registers core user abilities. * - * @since 6.9.0 + * @since 7.0.0 * @access private */ class WP_Users_Abilities { @@ -26,7 +26,7 @@ class WP_Users_Abilities { /** * Registers all user-related abilities. * - * @since 6.9.0 + * @since 7.0.0 * * @return void */ @@ -37,7 +37,7 @@ public static function register(): void { /** * Registers the core/get-user ability. * - * @since 6.9.0 + * @since 7.0.0 * * @return void */ @@ -67,7 +67,7 @@ private static function register_get_user(): void { /** * Gets the input schema for the get-user ability. * - * @since 6.9.0 + * @since 7.0.0 * * @return array The input schema. */ @@ -105,7 +105,7 @@ private static function get_user_input_schema(): array { /** * Gets the output schema for the get-user ability. * - * @since 6.9.0 + * @since 7.0.0 * * @return array The output schema. */ @@ -205,7 +205,7 @@ private static function get_user_output_schema(): array { /** * Finds a user by id, username, or email from input parameters. * - * @since 6.9.0 + * @since 7.0.0 * * @param array $input The input parameters. * @return WP_User|false The user object if found, false otherwise. @@ -229,7 +229,7 @@ private static function find_user( array $input ) { /** * Permission callback for the get-user ability. * - * @since 6.9.0 + * @since 7.0.0 * * @param array $input The input parameters. * @return bool Whether the user has permission. @@ -255,7 +255,7 @@ public static function check_get_user_permission( array $input = array() ): bool /** * Executes the get-user ability. * - * @since 6.9.0 + * @since 7.0.0 * * @param array $input The input parameters. * @return array|WP_Error The user data or error. From 3171d5aac973a986fcd0721d1a15d29a13b2d959 Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Thu, 12 Feb 2026 12:02:47 +0000 Subject: [PATCH 09/17] minor condition improvement --- src/wp-includes/abilities/class-wp-users-abilities.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/abilities/class-wp-users-abilities.php b/src/wp-includes/abilities/class-wp-users-abilities.php index 3a7313071ad6e..1ccf23abc7c36 100644 --- a/src/wp-includes/abilities/class-wp-users-abilities.php +++ b/src/wp-includes/abilities/class-wp-users-abilities.php @@ -211,7 +211,7 @@ private static function get_user_output_schema(): array { * @return WP_User|false The user object if found, false otherwise. */ private static function find_user( array $input ) { - if ( ! empty( $input['id'] ) ) { + if ( ! empty( $input['id'] ) && $input['id'] > 0 ) { return get_user_by( 'ID', (int) $input['id'] ); } From f18ccaf114a0c0a2a35e7aac20e98b65a419d34b Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Tue, 17 Feb 2026 18:44:53 +0000 Subject: [PATCH 10/17] Improve schemas --- .../abilities/class-wp-users-abilities.php | 119 +++++++++++------- 1 file changed, 71 insertions(+), 48 deletions(-) diff --git a/src/wp-includes/abilities/class-wp-users-abilities.php b/src/wp-includes/abilities/class-wp-users-abilities.php index 1ccf23abc7c36..69f58fbaeb7bd 100644 --- a/src/wp-includes/abilities/class-wp-users-abilities.php +++ b/src/wp-includes/abilities/class-wp-users-abilities.php @@ -88,10 +88,11 @@ private static function get_user_input_schema(): array { 'type' => 'string', 'description' => __( 'User login name.' ), ), - 'email' => array( - 'type' => 'string', - 'description' => __( 'User email address.' ), - ), + 'email' => array( + 'type' => 'string', + 'format' => 'email', + 'description' => __( 'User email address.' ), + ), 'include_capabilities' => array( 'type' => 'boolean', 'description' => __( 'Whether to include the user capabilities in the response.' ), @@ -110,6 +111,26 @@ private static function get_user_input_schema(): array { * @return array The output schema. */ private static function get_user_output_schema(): array { + $avatar_urls_schema = array( + 'type' => 'object', + 'description' => __( 'Avatar URLs for the user.' ), + ); + + if ( get_option( 'show_avatars' ) ) { + $avatar_properties = array(); + + foreach ( rest_get_avatar_sizes() as $size ) { + $avatar_properties[ $size ] = array( + /* translators: %d: Avatar image size in pixels. */ + 'description' => sprintf( __( 'Avatar URL with image size of %d pixels.' ), $size ), + 'type' => 'string', + 'format' => 'uri', + ); + } + + $avatar_urls_schema['properties'] = $avatar_properties; + } + return array( 'type' => 'object', 'required' => array( @@ -129,21 +150,22 @@ private static function get_user_output_schema(): array { 'locale', 'avatar_urls', ), - 'properties' => array( - 'id' => array( - 'type' => 'integer', - 'description' => __( 'Unique identifier for the user.' ), - ), - 'username' => array( - 'type' => 'string', - 'description' => __( 'Login name for the user.' ), - ), - 'email' => array( - 'type' => 'string', - 'description' => __( 'The email address for the user.' ), - ), - 'display_name' => array( - 'type' => 'string', + 'properties' => array( + 'id' => array( + 'type' => 'integer', + 'description' => __( 'Unique identifier for the user.' ), + ), + 'username' => array( + 'type' => 'string', + 'description' => __( 'Login name for the user.' ), + ), + 'email' => array( + 'type' => 'string', + 'format' => 'email', + 'description' => __( 'The email address for the user.' ), + ), + 'display_name' => array( + 'type' => 'string', 'description' => __( 'Display name for the user.' ), ), 'first_name' => array( @@ -162,40 +184,41 @@ private static function get_user_output_schema(): array { 'type' => 'string', 'description' => __( 'Description of the user.' ), ), - 'url' => array( - 'type' => 'string', - 'description' => __( 'URL of the user.' ), - ), - 'link' => array( - 'type' => 'string', - 'description' => __( 'Author URL of the user.' ), - ), - 'slug' => array( - 'type' => 'string', - 'description' => __( 'An alphanumeric identifier for the user.' ), - ), - 'registered_date' => array( - 'type' => 'string', - 'description' => __( 'Registration date for the user in ISO 8601 format.' ), - ), - 'roles' => array( - 'type' => 'array', + 'url' => array( + 'type' => 'string', + 'format' => 'uri', + 'description' => __( 'URL of the user.' ), + ), + 'link' => array( + 'type' => 'string', + 'format' => 'uri', + 'description' => __( 'Author URL of the user.' ), + ), + 'slug' => array( + 'type' => 'string', + 'description' => __( 'An alphanumeric identifier for the user.' ), + ), + 'registered_date' => array( + 'type' => 'string', + 'format' => 'date-time', + 'description' => __( 'Registration date for the user in ISO 8601 format.' ), + ), + 'roles' => array( + 'type' => 'array', 'description' => __( 'Roles assigned to the user.' ), 'items' => array( 'type' => 'string', ), ), - 'locale' => array( - 'type' => 'string', - 'description' => __( 'Locale for the user.' ), - ), - 'avatar_urls' => array( - 'type' => 'object', - 'description' => __( 'Avatar URLs for the user at various sizes.' ), - ), - 'capabilities' => array( - 'type' => 'object', - 'description' => __( 'All capabilities assigned to the user. Only included if include_capabilities is true.' ), + 'locale' => array( + 'type' => 'string', + 'enum' => array_merge( array( '', 'en_US' ), get_available_languages() ), + 'description' => __( 'Locale for the user.' ), + ), + 'avatar_urls' => $avatar_urls_schema, + 'capabilities' => array( + 'type' => 'object', + 'description' => __( 'All capabilities assigned to the user. Only included if include_capabilities is true.' ), ), ), 'additionalProperties' => false, From 4c8e76a56c014466f7732079efdf0d11c6ce5acf Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Tue, 17 Feb 2026 19:04:31 +0000 Subject: [PATCH 11/17] improve permissions --- .../abilities/class-wp-users-abilities.php | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/wp-includes/abilities/class-wp-users-abilities.php b/src/wp-includes/abilities/class-wp-users-abilities.php index 69f58fbaeb7bd..64cb84986df09 100644 --- a/src/wp-includes/abilities/class-wp-users-abilities.php +++ b/src/wp-includes/abilities/class-wp-users-abilities.php @@ -263,16 +263,20 @@ public static function check_get_user_permission( array $input = array() ): bool return false; } - $user = self::find_user( $input ); - $target_user_id = $user ? $user->ID : 0; + $user = self::find_user( $input ); + + // Allow privileged users to query unknown targets and receive not-found responses. + if ( ! $user || ! $user->exists() ) { + return current_user_can( 'edit_users' ); + } // Users can view their own profile. - if ( get_current_user_id() === $target_user_id ) { + if ( get_current_user_id() === $user->ID ) { return true; } - // Otherwise require list_users capability. - return current_user_can( 'list_users' ); + // Otherwise require permission to edit the specific target user. + return current_user_can( 'edit_user', $user->ID ); } /** From 408b78c141e1a3451b6397049add7d7fd11b3514 Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Wed, 18 Feb 2026 11:34:58 +0000 Subject: [PATCH 12/17] Adjust required output fields for core/get-user --- .../abilities/class-wp-users-abilities.php | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/src/wp-includes/abilities/class-wp-users-abilities.php b/src/wp-includes/abilities/class-wp-users-abilities.php index 64cb84986df09..75536ba66acab 100644 --- a/src/wp-includes/abilities/class-wp-users-abilities.php +++ b/src/wp-includes/abilities/class-wp-users-abilities.php @@ -131,24 +131,11 @@ private static function get_user_output_schema(): array { $avatar_urls_schema['properties'] = $avatar_properties; } - return array( - 'type' => 'object', + return array( + 'type' => 'object', 'required' => array( 'id', 'username', - 'email', - 'display_name', - 'first_name', - 'last_name', - 'nickname', - 'description', - 'url', - 'link', - 'slug', - 'registered_date', - 'roles', - 'locale', - 'avatar_urls', ), 'properties' => array( 'id' => array( From 69a38fe405fc9db0d316743e6c9c7fb3e7abce8f Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Wed, 18 Feb 2026 11:36:40 +0000 Subject: [PATCH 13/17] Sort core/get-user output schema properties alphabetically --- .../abilities/class-wp-users-abilities.php | 100 +++++++++--------- 1 file changed, 50 insertions(+), 50 deletions(-) diff --git a/src/wp-includes/abilities/class-wp-users-abilities.php b/src/wp-includes/abilities/class-wp-users-abilities.php index 75536ba66acab..53839c4f8a28e 100644 --- a/src/wp-includes/abilities/class-wp-users-abilities.php +++ b/src/wp-includes/abilities/class-wp-users-abilities.php @@ -131,81 +131,81 @@ private static function get_user_output_schema(): array { $avatar_urls_schema['properties'] = $avatar_properties; } - return array( - 'type' => 'object', + return array( + 'type' => 'object', 'required' => array( 'id', 'username', ), - 'properties' => array( - 'id' => array( - 'type' => 'integer', - 'description' => __( 'Unique identifier for the user.' ), - ), - 'username' => array( - 'type' => 'string', - 'description' => __( 'Login name for the user.' ), - ), - 'email' => array( - 'type' => 'string', - 'format' => 'email', - 'description' => __( 'The email address for the user.' ), - ), - 'display_name' => array( - 'type' => 'string', + 'properties' => array( + 'avatar_urls' => $avatar_urls_schema, + 'capabilities' => array( + 'type' => 'object', + 'description' => __( 'All capabilities assigned to the user. Only included if include_capabilities is true.' ), + ), + 'description' => array( + 'type' => 'string', + 'description' => __( 'Description of the user.' ), + ), + 'display_name' => array( + 'type' => 'string', 'description' => __( 'Display name for the user.' ), ), + 'email' => array( + 'type' => 'string', + 'format' => 'email', + 'description' => __( 'The email address for the user.' ), + ), 'first_name' => array( 'type' => 'string', 'description' => __( 'First name for the user.' ), ), + 'id' => array( + 'type' => 'integer', + 'description' => __( 'Unique identifier for the user.' ), + ), 'last_name' => array( 'type' => 'string', 'description' => __( 'Last name for the user.' ), ), + 'link' => array( + 'type' => 'string', + 'format' => 'uri', + 'description' => __( 'Author URL of the user.' ), + ), + 'locale' => array( + 'type' => 'string', + 'enum' => array_merge( array( '', 'en_US' ), get_available_languages() ), + 'description' => __( 'Locale for the user.' ), + ), 'nickname' => array( 'type' => 'string', 'description' => __( 'The nickname for the user.' ), ), - 'description' => array( + 'registered_date' => array( 'type' => 'string', - 'description' => __( 'Description of the user.' ), + 'format' => 'date-time', + 'description' => __( 'Registration date for the user in ISO 8601 format.' ), ), - 'url' => array( - 'type' => 'string', - 'format' => 'uri', - 'description' => __( 'URL of the user.' ), - ), - 'link' => array( - 'type' => 'string', - 'format' => 'uri', - 'description' => __( 'Author URL of the user.' ), - ), - 'slug' => array( - 'type' => 'string', - 'description' => __( 'An alphanumeric identifier for the user.' ), - ), - 'registered_date' => array( - 'type' => 'string', - 'format' => 'date-time', - 'description' => __( 'Registration date for the user in ISO 8601 format.' ), - ), - 'roles' => array( - 'type' => 'array', + 'roles' => array( + 'type' => 'array', 'description' => __( 'Roles assigned to the user.' ), 'items' => array( 'type' => 'string', ), ), - 'locale' => array( - 'type' => 'string', - 'enum' => array_merge( array( '', 'en_US' ), get_available_languages() ), - 'description' => __( 'Locale for the user.' ), - ), - 'avatar_urls' => $avatar_urls_schema, - 'capabilities' => array( - 'type' => 'object', - 'description' => __( 'All capabilities assigned to the user. Only included if include_capabilities is true.' ), + 'slug' => array( + 'type' => 'string', + 'description' => __( 'An alphanumeric identifier for the user.' ), + ), + 'url' => array( + 'type' => 'string', + 'format' => 'uri', + 'description' => __( 'URL of the user.' ), + ), + 'username' => array( + 'type' => 'string', + 'description' => __( 'Login name for the user.' ), ), ), 'additionalProperties' => false, From 76b6c9a2b7bae97bd2684cfe0bb153a76e8fdf5e Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Wed, 18 Feb 2026 11:54:09 +0000 Subject: [PATCH 14/17] reolver level field filtering --- .../abilities/class-wp-users-abilities.php | 98 ++++++++++++------- 1 file changed, 65 insertions(+), 33 deletions(-) diff --git a/src/wp-includes/abilities/class-wp-users-abilities.php b/src/wp-includes/abilities/class-wp-users-abilities.php index 53839c4f8a28e..f1a9c449bd0cf 100644 --- a/src/wp-includes/abilities/class-wp-users-abilities.php +++ b/src/wp-includes/abilities/class-wp-users-abilities.php @@ -88,11 +88,11 @@ private static function get_user_input_schema(): array { 'type' => 'string', 'description' => __( 'User login name.' ), ), - 'email' => array( - 'type' => 'string', - 'format' => 'email', - 'description' => __( 'User email address.' ), - ), + 'email' => array( + 'type' => 'string', + 'format' => 'email', + 'description' => __( 'User email address.' ), + ), 'include_capabilities' => array( 'type' => 'boolean', 'description' => __( 'Whether to include the user capabilities in the response.' ), @@ -103,6 +103,30 @@ private static function get_user_input_schema(): array { ); } + /** + * Determines whether sensitive fields can be returned for a user. + * + * @since 7.0.0 + * + * @param WP_User $user The target user. + * @return bool Whether sensitive fields can be returned. + */ + private static function can_view_sensitive_user_fields( WP_User $user ): bool { + return get_current_user_id() === $user->ID || current_user_can( 'edit_user', $user->ID ); + } + + /** + * Determines whether roles can be returned for a user. + * + * @since 7.0.0 + * + * @param WP_User $user The target user. + * @return bool Whether roles can be returned. + */ + private static function can_view_user_roles( WP_User $user ): bool { + return current_user_can( 'list_users' ) || current_user_can( 'edit_user', $user->ID ); + } + /** * Gets the output schema for the get-user ability. * @@ -137,12 +161,12 @@ private static function get_user_output_schema(): array { 'id', 'username', ), - 'properties' => array( - 'avatar_urls' => $avatar_urls_schema, - 'capabilities' => array( - 'type' => 'object', - 'description' => __( 'All capabilities assigned to the user. Only included if include_capabilities is true.' ), - ), + 'properties' => array( + 'avatar_urls' => $avatar_urls_schema, + 'capabilities' => array( + 'type' => 'object', + 'description' => __( 'All capabilities assigned to the user. Only included if include_capabilities is true and the current user can view them.' ), + ), 'description' => array( 'type' => 'string', 'description' => __( 'Description of the user.' ), @@ -187,13 +211,13 @@ private static function get_user_output_schema(): array { 'format' => 'date-time', 'description' => __( 'Registration date for the user in ISO 8601 format.' ), ), - 'roles' => array( - 'type' => 'array', - 'description' => __( 'Roles assigned to the user.' ), - 'items' => array( - 'type' => 'string', + 'roles' => array( + 'type' => 'array', + 'description' => __( 'Roles assigned to the user when the current user can view them.' ), + 'items' => array( + 'type' => 'string', + ), ), - ), 'slug' => array( 'type' => 'string', 'description' => __( 'An alphanumeric identifier for the user.' ), @@ -284,25 +308,33 @@ public static function execute_get_user( array $input = array() ) { return new WP_Error( 'user_not_found', __( 'User not found.' ), array( 'status' => 404 ) ); } + $can_view_sensitive_user_fields = self::can_view_sensitive_user_fields( $user ); + $result = array( - 'id' => $user->ID, - 'username' => $user->user_login, - 'email' => $user->user_email, - 'display_name' => $user->display_name, - 'first_name' => $user->first_name, - 'last_name' => $user->last_name, - 'nickname' => $user->nickname, - 'description' => $user->description, - 'url' => $user->user_url, - 'link' => get_author_posts_url( $user->ID, $user->user_nicename ), - 'slug' => $user->user_nicename, - 'registered_date' => gmdate( 'c', strtotime( $user->user_registered ) ), - 'roles' => array_values( $user->roles ), - 'locale' => get_user_locale( $user ), - 'avatar_urls' => rest_get_avatar_urls( $user ), + 'id' => $user->ID, + 'display_name' => $user->display_name, + 'description' => $user->description, + 'url' => $user->user_url, + 'link' => get_author_posts_url( $user->ID, $user->user_nicename ), + 'slug' => $user->user_nicename, + 'avatar_urls' => rest_get_avatar_urls( $user ), ); - if ( $include_capabilities ) { + if ( $can_view_sensitive_user_fields ) { + $result['username'] = $user->user_login; + $result['email'] = $user->user_email; + $result['first_name'] = $user->first_name; + $result['last_name'] = $user->last_name; + $result['nickname'] = $user->nickname; + $result['registered_date'] = gmdate( 'c', strtotime( $user->user_registered ) ); + $result['locale'] = get_user_locale( $user ); + } + + if ( self::can_view_user_roles( $user ) ) { + $result['roles'] = array_values( $user->roles ); + } + + if ( $include_capabilities && $can_view_sensitive_user_fields ) { $result['capabilities'] = (object) $user->allcaps; } From aa40a4af54a562cd05bb359f8d77c25aab63d909 Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Wed, 18 Feb 2026 14:16:24 +0000 Subject: [PATCH 15/17] Abilities: normalize core/get-user output coercion --- .../abilities/class-wp-users-abilities.php | 99 ++++++++++++----- .../abilities-api/wpRegisterCoreAbilities.php | 102 ++++++++++++++++++ 2 files changed, 173 insertions(+), 28 deletions(-) diff --git a/src/wp-includes/abilities/class-wp-users-abilities.php b/src/wp-includes/abilities/class-wp-users-abilities.php index f1a9c449bd0cf..62f825657fb87 100644 --- a/src/wp-includes/abilities/class-wp-users-abilities.php +++ b/src/wp-includes/abilities/class-wp-users-abilities.php @@ -161,12 +161,12 @@ private static function get_user_output_schema(): array { 'id', 'username', ), - 'properties' => array( - 'avatar_urls' => $avatar_urls_schema, - 'capabilities' => array( - 'type' => 'object', - 'description' => __( 'All capabilities assigned to the user. Only included if include_capabilities is true and the current user can view them.' ), - ), + 'properties' => array( + 'avatar_urls' => $avatar_urls_schema, + 'capabilities' => array( + 'type' => 'object', + 'description' => __( 'All capabilities assigned to the user. Only included if include_capabilities is true and the current user can view them.' ), + ), 'description' => array( 'type' => 'string', 'description' => __( 'Description of the user.' ), @@ -211,13 +211,13 @@ private static function get_user_output_schema(): array { 'format' => 'date-time', 'description' => __( 'Registration date for the user in ISO 8601 format.' ), ), - 'roles' => array( - 'type' => 'array', - 'description' => __( 'Roles assigned to the user when the current user can view them.' ), - 'items' => array( - 'type' => 'string', - ), + 'roles' => array( + 'type' => 'array', + 'description' => __( 'Roles assigned to the user when the current user can view them.' ), + 'items' => array( + 'type' => 'string', ), + ), 'slug' => array( 'type' => 'string', 'description' => __( 'An alphanumeric identifier for the user.' ), @@ -236,6 +236,44 @@ private static function get_user_output_schema(): array { ); } + /** + * Normalizes capabilities into an object with boolean values. + * + * @since 7.0.0 + * + * @param mixed $capabilities Capabilities map. + * @return object Normalized capabilities. + */ + private static function normalize_capabilities( $capabilities ): object { + $normalized = array(); + + foreach ( (array) $capabilities as $capability => $granted ) { + $normalized[ (string) $capability ] = rest_sanitize_boolean( $granted ); + } + + return (object) $normalized; + } + + /** + * Normalizes a list of values into an array of strings. + * + * @since 7.0.0 + * + * @param mixed $values Values to normalize. + * @return array Normalized string list. + */ + private static function normalize_string_list( $values ): array { + $normalized = array(); + + foreach ( (array) $values as $value ) { + if ( null === $value || is_scalar( $value ) || ( is_object( $value ) && is_callable( array( $value, '__toString' ) ) ) ) { + $normalized[] = (string) $value; + } + } + + return $normalized; + } + /** * Finds a user by id, username, or email from input parameters. * @@ -300,7 +338,7 @@ public static function check_get_user_permission( array $input = array() ): bool */ public static function execute_get_user( array $input = array() ) { $input = is_array( $input ) ? $input : array(); - $include_capabilities = ! empty( $input['include_capabilities'] ) && ( true === $input['include_capabilities'] || 'true' === $input['include_capabilities'] ); + $include_capabilities = array_key_exists( 'include_capabilities', $input ) ? rest_sanitize_boolean( $input['include_capabilities'] ) : false; $user = self::find_user( $input ); @@ -308,34 +346,39 @@ public static function execute_get_user( array $input = array() ) { return new WP_Error( 'user_not_found', __( 'User not found.' ), array( 'status' => 404 ) ); } + $user_id = (int) $user->ID; $can_view_sensitive_user_fields = self::can_view_sensitive_user_fields( $user ); $result = array( - 'id' => $user->ID, - 'display_name' => $user->display_name, - 'description' => $user->description, - 'url' => $user->user_url, - 'link' => get_author_posts_url( $user->ID, $user->user_nicename ), - 'slug' => $user->user_nicename, + 'id' => $user_id, + 'display_name' => (string) $user->display_name, + 'description' => (string) $user->description, + 'url' => (string) $user->user_url, + 'link' => (string) get_author_posts_url( $user_id, $user->user_nicename ), + 'slug' => (string) $user->user_nicename, 'avatar_urls' => rest_get_avatar_urls( $user ), ); if ( $can_view_sensitive_user_fields ) { - $result['username'] = $user->user_login; - $result['email'] = $user->user_email; - $result['first_name'] = $user->first_name; - $result['last_name'] = $user->last_name; - $result['nickname'] = $user->nickname; - $result['registered_date'] = gmdate( 'c', strtotime( $user->user_registered ) ); - $result['locale'] = get_user_locale( $user ); + $result['username'] = (string) $user->user_login; + $result['email'] = (string) $user->user_email; + $result['first_name'] = (string) $user->first_name; + $result['last_name'] = (string) $user->last_name; + $result['nickname'] = (string) $user->nickname; + $result['locale'] = (string) get_user_locale( $user ); + + $registered_timestamp = strtotime( (string) $user->user_registered ); + if ( false !== $registered_timestamp ) { + $result['registered_date'] = gmdate( 'c', $registered_timestamp ); + } } if ( self::can_view_user_roles( $user ) ) { - $result['roles'] = array_values( $user->roles ); + $result['roles'] = self::normalize_string_list( $user->roles ); } if ( $include_capabilities && $can_view_sensitive_user_fields ) { - $result['capabilities'] = (object) $user->allcaps; + $result['capabilities'] = self::normalize_capabilities( $user->allcaps ); } return $result; diff --git a/tests/phpunit/tests/abilities-api/wpRegisterCoreAbilities.php b/tests/phpunit/tests/abilities-api/wpRegisterCoreAbilities.php index 48cae6efd1dee..2a87696de5e77 100644 --- a/tests/phpunit/tests/abilities-api/wpRegisterCoreAbilities.php +++ b/tests/phpunit/tests/abilities-api/wpRegisterCoreAbilities.php @@ -154,6 +154,108 @@ public function test_core_get_current_user_info_returns_user_data(): void { $this->assertSame( get_userdata( $user_id )->display_name, $result['display_name'] ); } + /** + * Tests boolean-like include_capabilities values for the get-user ability. + * @ticket 64146 + */ + public function test_core_get_user_include_capabilities_accepts_boolean_like_values(): void { + $user_id = self::factory()->user->create( array( 'role' => 'subscriber' ) ); + + wp_set_current_user( $user_id ); + + $ability = wp_get_ability( 'core/get-user' ); + + $result = $ability->execute( + array( + 'id' => $user_id, + 'include_capabilities' => '1', + ) + ); + $this->assertIsArray( $result ); + $this->assertArrayHasKey( 'capabilities', $result ); + + $result = $ability->execute( + array( + 'id' => $user_id, + 'include_capabilities' => 1, + ) + ); + $this->assertIsArray( $result ); + $this->assertArrayHasKey( 'capabilities', $result ); + + $result = $ability->execute( + array( + 'id' => $user_id, + 'include_capabilities' => '0', + ) + ); + $this->assertIsArray( $result ); + $this->assertArrayNotHasKey( 'capabilities', $result ); + } + + /** + * Tests get-user output is normalized to the declared schema types. + * @ticket 64146 + */ + public function test_core_get_user_output_is_normalized_to_schema_types(): void { + $user_id = self::factory()->user->create( array( 'role' => 'subscriber' ) ); + + wp_set_current_user( $user_id ); + + $ability = wp_get_ability( 'core/get-user' ); + + $capability_filter = static function ( $allcaps, $caps, $args, $user ) use ( $user_id ) { + if ( $user_id === (int) $user->ID ) { + $allcaps['custom_capability_flag'] = '1'; + } + + return $allcaps; + }; + add_filter( 'user_has_cap', $capability_filter, 10, 4 ); + + try { + $result = $ability->execute( + array( + 'id' => $user_id, + 'include_capabilities' => 1, + ) + ); + } finally { + remove_filter( 'user_has_cap', $capability_filter, 10 ); + } + + $this->assertIsArray( $result ); + $this->assertIsInt( $result['id'] ); + $this->assertIsString( $result['display_name'] ); + $this->assertIsString( $result['description'] ); + $this->assertIsString( $result['url'] ); + $this->assertIsString( $result['link'] ); + $this->assertIsString( $result['slug'] ); + $this->assertIsString( $result['username'] ); + $this->assertIsString( $result['email'] ); + $this->assertIsString( $result['first_name'] ); + $this->assertIsString( $result['last_name'] ); + $this->assertIsString( $result['nickname'] ); + $this->assertIsString( $result['locale'] ); + $this->assertIsString( $result['registered_date'] ); + $this->assertNotFalse( rest_parse_date( $result['registered_date'] ) ); + + $this->assertIsArray( $result['roles'] ); + foreach ( $result['roles'] as $role ) { + $this->assertIsString( $role ); + } + + $this->assertIsArray( $result['avatar_urls'] ); + foreach ( $result['avatar_urls'] as $avatar_url ) { + $this->assertIsString( $avatar_url ); + } + + $this->assertIsObject( $result['capabilities'] ); + $this->assertObjectHasProperty( 'custom_capability_flag', $result['capabilities'] ); + $this->assertIsBool( $result['capabilities']->custom_capability_flag ); + $this->assertTrue( $result['capabilities']->custom_capability_flag ); + } + /** * Tests executing the environment info ability. * @ticket 64146 From 58931a31220cc02a4e6bca62b854850e6ef84d8e Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Wed, 18 Feb 2026 16:45:32 +0000 Subject: [PATCH 16/17] test fixes --- .../abilities-api/wpRegisterCoreAbilities.php | 41 ++++++++++--------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/tests/phpunit/tests/abilities-api/wpRegisterCoreAbilities.php b/tests/phpunit/tests/abilities-api/wpRegisterCoreAbilities.php index 2a87696de5e77..d97ee859b8873 100644 --- a/tests/phpunit/tests/abilities-api/wpRegisterCoreAbilities.php +++ b/tests/phpunit/tests/abilities-api/wpRegisterCoreAbilities.php @@ -202,27 +202,25 @@ public function test_core_get_user_output_is_normalized_to_schema_types(): void wp_set_current_user( $user_id ); - $ability = wp_get_ability( 'core/get-user' ); - - $capability_filter = static function ( $allcaps, $caps, $args, $user ) use ( $user_id ) { - if ( $user_id === (int) $user->ID ) { - $allcaps['custom_capability_flag'] = '1'; - } - - return $allcaps; - }; - add_filter( 'user_has_cap', $capability_filter, 10, 4 ); + $ability = wp_get_ability( 'core/get-user' ); + $capability_key = $GLOBALS['wpdb']->get_blog_prefix() . 'capabilities'; + update_user_meta( + $user_id, + $capability_key, + array( + 'subscriber' => true, + 'custom_capability_flag' => '1', + 'custom_capability_disabled' => '0', + ) + ); + clean_user_cache( $user_id ); - try { - $result = $ability->execute( - array( - 'id' => $user_id, - 'include_capabilities' => 1, - ) - ); - } finally { - remove_filter( 'user_has_cap', $capability_filter, 10 ); - } + $result = $ability->execute( + array( + 'id' => $user_id, + 'include_capabilities' => 1, + ) + ); $this->assertIsArray( $result ); $this->assertIsInt( $result['id'] ); @@ -252,8 +250,11 @@ public function test_core_get_user_output_is_normalized_to_schema_types(): void $this->assertIsObject( $result['capabilities'] ); $this->assertObjectHasProperty( 'custom_capability_flag', $result['capabilities'] ); + $this->assertObjectHasProperty( 'custom_capability_disabled', $result['capabilities'] ); $this->assertIsBool( $result['capabilities']->custom_capability_flag ); + $this->assertIsBool( $result['capabilities']->custom_capability_disabled ); $this->assertTrue( $result['capabilities']->custom_capability_flag ); + $this->assertFalse( $result['capabilities']->custom_capability_disabled ); } /** From a8218c32d8780e0d07a61a62a47bb727a0f2c980 Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Wed, 18 Feb 2026 18:43:04 +0000 Subject: [PATCH 17/17] remove optinal fields and capabilities --- .../abilities/class-wp-users-abilities.php | 48 +++----------- .../abilities-api/wpRegisterCoreAbilities.php | 63 +------------------ 2 files changed, 10 insertions(+), 101 deletions(-) diff --git a/src/wp-includes/abilities/class-wp-users-abilities.php b/src/wp-includes/abilities/class-wp-users-abilities.php index 62f825657fb87..38544dee98170 100644 --- a/src/wp-includes/abilities/class-wp-users-abilities.php +++ b/src/wp-includes/abilities/class-wp-users-abilities.php @@ -80,24 +80,19 @@ private static function get_user_input_schema(): array { array( 'required' => array( 'email' ) ), ), 'properties' => array( - 'id' => array( + 'email' => array( + 'type' => 'string', + 'format' => 'email', + 'description' => __( 'User email address.' ), + ), + 'id' => array( 'type' => 'integer', 'description' => __( 'User ID.' ), ), - 'username' => array( + 'username' => array( 'type' => 'string', 'description' => __( 'User login name.' ), ), - 'email' => array( - 'type' => 'string', - 'format' => 'email', - 'description' => __( 'User email address.' ), - ), - 'include_capabilities' => array( - 'type' => 'boolean', - 'description' => __( 'Whether to include the user capabilities in the response.' ), - 'default' => false, - ), ), 'additionalProperties' => false, ); @@ -163,10 +158,6 @@ private static function get_user_output_schema(): array { ), 'properties' => array( 'avatar_urls' => $avatar_urls_schema, - 'capabilities' => array( - 'type' => 'object', - 'description' => __( 'All capabilities assigned to the user. Only included if include_capabilities is true and the current user can view them.' ), - ), 'description' => array( 'type' => 'string', 'description' => __( 'Description of the user.' ), @@ -236,24 +227,6 @@ private static function get_user_output_schema(): array { ); } - /** - * Normalizes capabilities into an object with boolean values. - * - * @since 7.0.0 - * - * @param mixed $capabilities Capabilities map. - * @return object Normalized capabilities. - */ - private static function normalize_capabilities( $capabilities ): object { - $normalized = array(); - - foreach ( (array) $capabilities as $capability => $granted ) { - $normalized[ (string) $capability ] = rest_sanitize_boolean( $granted ); - } - - return (object) $normalized; - } - /** * Normalizes a list of values into an array of strings. * @@ -337,8 +310,7 @@ public static function check_get_user_permission( array $input = array() ): bool * @return array|WP_Error The user data or error. */ public static function execute_get_user( array $input = array() ) { - $input = is_array( $input ) ? $input : array(); - $include_capabilities = array_key_exists( 'include_capabilities', $input ) ? rest_sanitize_boolean( $input['include_capabilities'] ) : false; + $input = is_array( $input ) ? $input : array(); $user = self::find_user( $input ); @@ -377,10 +349,6 @@ public static function execute_get_user( array $input = array() ) { $result['roles'] = self::normalize_string_list( $user->roles ); } - if ( $include_capabilities && $can_view_sensitive_user_fields ) { - $result['capabilities'] = self::normalize_capabilities( $user->allcaps ); - } - return $result; } } diff --git a/tests/phpunit/tests/abilities-api/wpRegisterCoreAbilities.php b/tests/phpunit/tests/abilities-api/wpRegisterCoreAbilities.php index d97ee859b8873..7fb0bcf89c9e7 100644 --- a/tests/phpunit/tests/abilities-api/wpRegisterCoreAbilities.php +++ b/tests/phpunit/tests/abilities-api/wpRegisterCoreAbilities.php @@ -154,45 +154,6 @@ public function test_core_get_current_user_info_returns_user_data(): void { $this->assertSame( get_userdata( $user_id )->display_name, $result['display_name'] ); } - /** - * Tests boolean-like include_capabilities values for the get-user ability. - * @ticket 64146 - */ - public function test_core_get_user_include_capabilities_accepts_boolean_like_values(): void { - $user_id = self::factory()->user->create( array( 'role' => 'subscriber' ) ); - - wp_set_current_user( $user_id ); - - $ability = wp_get_ability( 'core/get-user' ); - - $result = $ability->execute( - array( - 'id' => $user_id, - 'include_capabilities' => '1', - ) - ); - $this->assertIsArray( $result ); - $this->assertArrayHasKey( 'capabilities', $result ); - - $result = $ability->execute( - array( - 'id' => $user_id, - 'include_capabilities' => 1, - ) - ); - $this->assertIsArray( $result ); - $this->assertArrayHasKey( 'capabilities', $result ); - - $result = $ability->execute( - array( - 'id' => $user_id, - 'include_capabilities' => '0', - ) - ); - $this->assertIsArray( $result ); - $this->assertArrayNotHasKey( 'capabilities', $result ); - } - /** * Tests get-user output is normalized to the declared schema types. * @ticket 64146 @@ -202,23 +163,11 @@ public function test_core_get_user_output_is_normalized_to_schema_types(): void wp_set_current_user( $user_id ); - $ability = wp_get_ability( 'core/get-user' ); - $capability_key = $GLOBALS['wpdb']->get_blog_prefix() . 'capabilities'; - update_user_meta( - $user_id, - $capability_key, - array( - 'subscriber' => true, - 'custom_capability_flag' => '1', - 'custom_capability_disabled' => '0', - ) - ); - clean_user_cache( $user_id ); + $ability = wp_get_ability( 'core/get-user' ); $result = $ability->execute( array( - 'id' => $user_id, - 'include_capabilities' => 1, + 'id' => $user_id, ) ); @@ -247,14 +196,6 @@ public function test_core_get_user_output_is_normalized_to_schema_types(): void foreach ( $result['avatar_urls'] as $avatar_url ) { $this->assertIsString( $avatar_url ); } - - $this->assertIsObject( $result['capabilities'] ); - $this->assertObjectHasProperty( 'custom_capability_flag', $result['capabilities'] ); - $this->assertObjectHasProperty( 'custom_capability_disabled', $result['capabilities'] ); - $this->assertIsBool( $result['capabilities']->custom_capability_flag ); - $this->assertIsBool( $result['capabilities']->custom_capability_disabled ); - $this->assertTrue( $result['capabilities']->custom_capability_flag ); - $this->assertFalse( $result['capabilities']->custom_capability_disabled ); } /**