diff options
author | Joas Schilling <coding@schilljs.com> | 2025-04-23 10:16:06 +0200 |
---|---|---|
committer | Andy Scherzinger <info@andy-scherzinger.de> | 2025-04-24 00:50:11 +0200 |
commit | 3923eaba04acee4b1026f41c73d5a1f44971929d (patch) | |
tree | b1c59f917a9e18195f40d03683fcf0dfba82ed1b | |
parent | b41ba29d19e32c2b78f11169aad91ae1f1547a1f (diff) | |
download | nextcloud-server-backport/52366/stable31.tar.gz nextcloud-server-backport/52366/stable31.zip |
feat(profile): Add an API to get the profile field databackport/52366/stable31
Signed-off-by: Joas Schilling <coding@schilljs.com>
-rw-r--r-- | build/integration/features/bootstrap/Provisioning.php | 79 | ||||
-rw-r--r-- | build/integration/features/provisioning-v1.feature | 17 | ||||
-rw-r--r-- | core/Controller/ProfileApiController.php | 74 | ||||
-rw-r--r-- | core/ResponseDefinitions.php | 25 | ||||
-rw-r--r-- | core/openapi-full.json | 224 | ||||
-rw-r--r-- | core/openapi.json | 224 | ||||
-rw-r--r-- | lib/private/Profile/ProfileManager.php | 6 | ||||
-rw-r--r-- | lib/public/Profile/IProfileManager.php | 4 |
8 files changed, 622 insertions, 31 deletions
diff --git a/build/integration/features/bootstrap/Provisioning.php b/build/integration/features/bootstrap/Provisioning.php index 9e3e8ccfd8b..446ffc0318e 100644 --- a/build/integration/features/bootstrap/Provisioning.php +++ b/build/integration/features/bootstrap/Provisioning.php @@ -4,6 +4,8 @@ * SPDX-FileCopyrightText: 2016 ownCloud, Inc. * SPDX-License-Identifier: AGPL-3.0-or-later */ + +use Behat\Gherkin\Node\TableNode; use GuzzleHttp\Client; use GuzzleHttp\Message\ResponseInterface; use PHPUnit\Framework\Assert; @@ -124,7 +126,7 @@ trait Provisioning { * @Then /^user "([^"]*)" has$/ * * @param string $user - * @param \Behat\Gherkin\Node\TableNode|null $settings + * @param TableNode|null $settings */ public function userHasSetting($user, $settings) { $fullUrl = $this->baseUrl . "v{$this->apiVersion}.php/cloud/users/$user"; @@ -145,12 +147,43 @@ trait Provisioning { if (isset($value['element']) && in_array($setting[0], ['additional_mail', 'additional_mailScope'], true)) { $expectedValues = explode(';', $setting[1]); foreach ($expectedValues as $expected) { - Assert::assertTrue(in_array($expected, $value['element'], true)); + Assert::assertTrue(in_array($expected, $value['element'], true), 'Data wrong for field: ' . $setting[0]); } } elseif (isset($value[0])) { - Assert::assertEqualsCanonicalizing($setting[1], $value[0]); + Assert::assertEqualsCanonicalizing($setting[1], $value[0], 'Data wrong for field: ' . $setting[0]); } else { - Assert::assertEquals('', $setting[1]); + Assert::assertEquals('', $setting[1], 'Data wrong for field: ' . $setting[0]); + } + } + } + + /** + * @Then /^user "([^"]*)" has the following profile data$/ + */ + public function userHasProfileData(string $user, ?TableNode $settings): void { + $fullUrl = $this->baseUrl . "v{$this->apiVersion}.php/profile/$user"; + $client = new Client(); + $options = []; + if ($this->currentUser === 'admin') { + $options['auth'] = $this->adminUser; + } else { + $options['auth'] = [$this->currentUser, $this->regularUser]; + } + $options['headers'] = [ + 'OCS-APIREQUEST' => 'true', + 'Accept' => 'application/json', + ]; + + $response = $client->get($fullUrl, $options); + $body = $response->getBody()->getContents(); + $data = json_decode($body, true); + $data = $data['ocs']['data']; + foreach ($settings->getRows() as $setting) { + Assert::assertArrayHasKey($setting[0], $data, 'Profile data field missing: ' . $setting[0]); + if ($setting[1] === 'NULL') { + Assert::assertNull($data[$setting[0]], 'Profile data wrong for field: ' . $setting[0]); + } else { + Assert::assertEquals($setting[1], $data[$setting[0]], 'Profile data wrong for field: ' . $setting[0]); } } } @@ -159,7 +192,7 @@ trait Provisioning { * @Then /^group "([^"]*)" has$/ * * @param string $user - * @param \Behat\Gherkin\Node\TableNode|null $settings + * @param TableNode|null $settings */ public function groupHasSetting($group, $settings) { $fullUrl = $this->baseUrl . "v{$this->apiVersion}.php/cloud/groups/details?search=$group"; @@ -191,7 +224,7 @@ trait Provisioning { * @Then /^user "([^"]*)" has editable fields$/ * * @param string $user - * @param \Behat\Gherkin\Node\TableNode|null $fields + * @param TableNode|null $fields */ public function userHasEditableFields($user, $fields) { $fullUrl = $this->baseUrl . "v{$this->apiVersion}.php/cloud/user/fields"; @@ -221,9 +254,9 @@ trait Provisioning { * @Then /^search users by phone for region "([^"]*)" with$/ * * @param string $user - * @param \Behat\Gherkin\Node\TableNode|null $settings + * @param TableNode|null $settings */ - public function searchUserByPhone($region, \Behat\Gherkin\Node\TableNode $searchTable) { + public function searchUserByPhone($region, TableNode $searchTable) { $fullUrl = $this->baseUrl . "v{$this->apiVersion}.php/cloud/users/search/by-phone"; $client = new Client(); $options = []; @@ -624,10 +657,10 @@ trait Provisioning { /** * @Then /^users returned are$/ - * @param \Behat\Gherkin\Node\TableNode|null $usersList + * @param TableNode|null $usersList */ public function theUsersShouldBe($usersList) { - if ($usersList instanceof \Behat\Gherkin\Node\TableNode) { + if ($usersList instanceof TableNode) { $users = $usersList->getRows(); $usersSimplified = $this->simplifyArray($users); $respondedArray = $this->getArrayOfUsersResponded($this->response); @@ -637,10 +670,10 @@ trait Provisioning { /** * @Then /^phone matches returned are$/ - * @param \Behat\Gherkin\Node\TableNode|null $usersList + * @param TableNode|null $usersList */ public function thePhoneUsersShouldBe($usersList) { - if ($usersList instanceof \Behat\Gherkin\Node\TableNode) { + if ($usersList instanceof TableNode) { $users = $usersList->getRowsHash(); $listCheckedElements = simplexml_load_string($this->response->getBody())->data; $respondedArray = json_decode(json_encode($listCheckedElements), true); @@ -650,10 +683,10 @@ trait Provisioning { /** * @Then /^detailed users returned are$/ - * @param \Behat\Gherkin\Node\TableNode|null $usersList + * @param TableNode|null $usersList */ public function theDetailedUsersShouldBe($usersList) { - if ($usersList instanceof \Behat\Gherkin\Node\TableNode) { + if ($usersList instanceof TableNode) { $users = $usersList->getRows(); $usersSimplified = $this->simplifyArray($users); $respondedArray = $this->getArrayOfDetailedUsersResponded($this->response); @@ -664,10 +697,10 @@ trait Provisioning { /** * @Then /^groups returned are$/ - * @param \Behat\Gherkin\Node\TableNode|null $groupsList + * @param TableNode|null $groupsList */ public function theGroupsShouldBe($groupsList) { - if ($groupsList instanceof \Behat\Gherkin\Node\TableNode) { + if ($groupsList instanceof TableNode) { $groups = $groupsList->getRows(); $groupsSimplified = $this->simplifyArray($groups); $respondedArray = $this->getArrayOfGroupsResponded($this->response); @@ -677,10 +710,10 @@ trait Provisioning { /** * @Then /^subadmin groups returned are$/ - * @param \Behat\Gherkin\Node\TableNode|null $groupsList + * @param TableNode|null $groupsList */ public function theSubadminGroupsShouldBe($groupsList) { - if ($groupsList instanceof \Behat\Gherkin\Node\TableNode) { + if ($groupsList instanceof TableNode) { $groups = $groupsList->getRows(); $groupsSimplified = $this->simplifyArray($groups); $respondedArray = $this->getArrayOfSubadminsResponded($this->response); @@ -690,10 +723,10 @@ trait Provisioning { /** * @Then /^apps returned are$/ - * @param \Behat\Gherkin\Node\TableNode|null $appList + * @param TableNode|null $appList */ public function theAppsShouldBe($appList) { - if ($appList instanceof \Behat\Gherkin\Node\TableNode) { + if ($appList instanceof TableNode) { $apps = $appList->getRows(); $appsSimplified = $this->simplifyArray($apps); $respondedArray = $this->getArrayOfAppsResponded($this->response); @@ -703,7 +736,7 @@ trait Provisioning { /** * @Then /^subadmin users returned are$/ - * @param \Behat\Gherkin\Node\TableNode|null $groupsList + * @param TableNode|null $groupsList */ public function theSubadminUsersShouldBe($groupsList) { $this->theSubadminGroupsShouldBe($groupsList); @@ -882,7 +915,7 @@ trait Provisioning { * @param string $quota */ public function userHasAQuotaOf($user, $quota) { - $body = new \Behat\Gherkin\Node\TableNode([ + $body = new TableNode([ 0 => ['key', 'quota'], 1 => ['value', $quota], ]); @@ -950,7 +983,7 @@ trait Provisioning { /** * @Then /^user "([^"]*)" has not$/ */ - public function userHasNotSetting($user, \Behat\Gherkin\Node\TableNode $settings) { + public function userHasNotSetting($user, TableNode $settings) { $fullUrl = $this->baseUrl . "v{$this->apiVersion}.php/cloud/users/$user"; $client = new Client(); $options = []; diff --git a/build/integration/features/provisioning-v1.feature b/build/integration/features/provisioning-v1.feature index 32dfebb5c7d..89fe12eaa1b 100644 --- a/build/integration/features/provisioning-v1.feature +++ b/build/integration/features/provisioning-v1.feature @@ -169,6 +169,23 @@ Feature: provisioning | address | Foo Bar Town | | website | https://nextcloud.com | | twitter | Nextcloud | + And sending "PUT" to "/cloud/users/brand-new-user" with + | key | organisation | + | value | Nextcloud GmbH | + And sending "PUT" to "/cloud/users/brand-new-user" with + | key | role | + | value | Engineer | + And the OCS status code should be "100" + And the HTTP status code should be "200" + Then user "brand-new-user" has the following profile data + | userId | brand-new-user | + | displayname | Brand New User | + | organisation | Nextcloud GmbH | + | role | Engineer | + | address | Foo Bar Town | + | timezone | UTC | + | timezoneOffset | 0 | + | pronouns | NULL | Scenario: Edit a user account properties scopes Given user "brand-new-user" exists diff --git a/core/Controller/ProfileApiController.php b/core/Controller/ProfileApiController.php index c807ecb72d4..02979cb1649 100644 --- a/core/Controller/ProfileApiController.php +++ b/core/Controller/ProfileApiController.php @@ -10,9 +10,11 @@ declare(strict_types=1); namespace OC\Core\Controller; use OC\Core\Db\ProfileConfigMapper; +use OC\Core\ResponseDefinitions; use OC\Profile\ProfileManager; use OCP\AppFramework\Http; use OCP\AppFramework\Http\Attribute\ApiRoute; +use OCP\AppFramework\Http\Attribute\BruteForceProtection; use OCP\AppFramework\Http\Attribute\NoAdminRequired; use OCP\AppFramework\Http\Attribute\PasswordConfirmationRequired; use OCP\AppFramework\Http\Attribute\UserRateLimit; @@ -21,17 +23,27 @@ use OCP\AppFramework\OCS\OCSBadRequestException; use OCP\AppFramework\OCS\OCSForbiddenException; use OCP\AppFramework\OCS\OCSNotFoundException; use OCP\AppFramework\OCSController; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\IConfig; use OCP\IRequest; +use OCP\IUser; use OCP\IUserManager; use OCP\IUserSession; +use OCP\Share\IManager; +/** + * @psalm-import-type CoreProfileData from ResponseDefinitions + */ class ProfileApiController extends OCSController { public function __construct( IRequest $request, + private IConfig $config, + private ITimeFactory $timeFactory, private ProfileConfigMapper $configMapper, private ProfileManager $profileManager, private IUserManager $userManager, private IUserSession $userSession, + private IManager $shareManager, ) { parent::__construct('core', $request); } @@ -57,14 +69,13 @@ class ProfileApiController extends OCSController { #[ApiRoute(verb: 'PUT', url: '/{targetUserId}', root: '/profile')] public function setVisibility(string $targetUserId, string $paramId, string $visibility): DataResponse { $requestingUser = $this->userSession->getUser(); - $targetUser = $this->userManager->get($targetUserId); - - if (!$this->userManager->userExists($targetUserId)) { - throw new OCSNotFoundException('Account does not exist'); + if ($requestingUser->getUID() !== $targetUserId) { + throw new OCSForbiddenException('People can only edit their own visibility settings'); } - if ($requestingUser !== $targetUser) { - throw new OCSForbiddenException('People can only edit their own visibility settings'); + $targetUser = $this->userManager->get($targetUserId); + if (!$targetUser instanceof IUser) { + throw new OCSNotFoundException('Account does not exist'); } // Ensure that a profile config is created in the database @@ -80,4 +91,55 @@ class ProfileApiController extends OCSController { return new DataResponse(); } + + /** + * Get profile fields for another user + * + * @param string $targetUserId ID of the user + * @return DataResponse<Http::STATUS_OK, CoreProfileData, array{}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_NOT_FOUND, null, array{}> + * + * 200: Profile data returned successfully + * 400: Profile is disabled + * 404: Account not found or disabled + */ + #[NoAdminRequired] + #[ApiRoute(verb: 'GET', url: '/{targetUserId}', root: '/profile')] + #[BruteForceProtection(action: 'user')] + #[UserRateLimit(limit: 30, period: 120)] + public function getProfileFields(string $targetUserId): DataResponse { + $targetUser = $this->userManager->get($targetUserId); + if (!$targetUser instanceof IUser) { + $response = new DataResponse(null, Http::STATUS_NOT_FOUND); + $response->throttle(); + return $response; + } + if (!$targetUser->isEnabled()) { + return new DataResponse(null, Http::STATUS_NOT_FOUND); + } + + if (!$this->profileManager->isProfileEnabled($targetUser)) { + return new DataResponse(null, Http::STATUS_BAD_REQUEST); + } + + $requestingUser = $this->userSession->getUser(); + if ($targetUser !== $requestingUser) { + if (!$this->shareManager->currentUserCanEnumerateTargetUser($requestingUser, $targetUser)) { + return new DataResponse(null, Http::STATUS_NOT_FOUND); + } + } + + $profileFields = $this->profileManager->getProfileFields($targetUser, $requestingUser); + + // Extend the profile information with timezone of the user + $timezoneStringTarget = $this->config->getUserValue($targetUser->getUID(), 'core', 'timezone') ?: $this->config->getSystemValueString('default_timezone', 'UTC'); + try { + $timezoneTarget = new \DateTimeZone($timezoneStringTarget); + } catch (\Throwable) { + $timezoneTarget = new \DateTimeZone('UTC'); + } + $profileFields['timezone'] = $timezoneTarget->getName(); // E.g. Europe/Berlin + $profileFields['timezoneOffset'] = $timezoneTarget->getOffset($this->timeFactory->now()); // In seconds E.g. 7200 + + return new DataResponse($profileFields); + } } diff --git a/core/ResponseDefinitions.php b/core/ResponseDefinitions.php index 3b344e6af99..55e27fd927d 100644 --- a/core/ResponseDefinitions.php +++ b/core/ResponseDefinitions.php @@ -202,6 +202,31 @@ namespace OC\Core; * endedAt: ?int, * } * + * @psalm-type CoreProfileAction = array{ + * id: string, + * icon: string, + * title: string, + * target: ?string, + * } + * + * @psalm-type CoreProfileFields = array{ + * userId: string, + * address?: string|null, + * biography?: string|null, + * displayname?: string|null, + * headline?: string|null, + * isUserAvatarVisible?: bool, + * organisation?: string|null, + * pronouns?: string|null, + * role?: string|null, + * actions: list<CoreProfileAction>, + * } + * + * @psalm-type CoreProfileData = CoreProfileFields&array{ + * timezone: string, + * timezoneOffset: int, + * } + * */ class ResponseDefinitions { } diff --git a/core/openapi-full.json b/core/openapi-full.json index 78bf7336ce8..3d6a28f9251 100644 --- a/core/openapi-full.json +++ b/core/openapi-full.json @@ -317,6 +317,102 @@ } } }, + "ProfileAction": { + "type": "object", + "required": [ + "id", + "icon", + "title", + "target" + ], + "properties": { + "id": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "title": { + "type": "string" + }, + "target": { + "type": "string", + "nullable": true + } + } + }, + "ProfileData": { + "allOf": [ + { + "$ref": "#/components/schemas/ProfileFields" + }, + { + "type": "object", + "required": [ + "timezone", + "timezoneOffset" + ], + "properties": { + "timezone": { + "type": "string" + }, + "timezoneOffset": { + "type": "integer", + "format": "int64" + } + } + } + ] + }, + "ProfileFields": { + "type": "object", + "required": [ + "userId", + "actions" + ], + "properties": { + "userId": { + "type": "string" + }, + "address": { + "type": "string", + "nullable": true + }, + "biography": { + "type": "string", + "nullable": true + }, + "displayname": { + "type": "string", + "nullable": true + }, + "headline": { + "type": "string", + "nullable": true + }, + "isUserAvatarVisible": { + "type": "boolean" + }, + "organisation": { + "type": "string", + "nullable": true + }, + "pronouns": { + "type": "string", + "nullable": true + }, + "role": { + "type": "string", + "nullable": true + }, + "actions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ProfileAction" + } + } + } + }, "PublicCapabilities": { "type": "object", "required": [ @@ -3095,6 +3191,134 @@ } } } + }, + "get": { + "operationId": "profile_api-get-profile-fields", + "summary": "Get profile fields for another user", + "tags": [ + "profile_api" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "targetUserId", + "in": "path", + "description": "ID of the user", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Profile data returned successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "$ref": "#/components/schemas/ProfileData" + } + } + } + } + } + } + } + }, + "400": { + "description": "Profile is disabled", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "nullable": true + } + } + } + } + } + } + } + }, + "404": { + "description": "Account not found or disabled", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "nullable": true + } + } + } + } + } + } + } + } + } } }, "/ocs/v2.php/references/extract": { diff --git a/core/openapi.json b/core/openapi.json index 08631a19118..9a381da7e22 100644 --- a/core/openapi.json +++ b/core/openapi.json @@ -317,6 +317,102 @@ } } }, + "ProfileAction": { + "type": "object", + "required": [ + "id", + "icon", + "title", + "target" + ], + "properties": { + "id": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "title": { + "type": "string" + }, + "target": { + "type": "string", + "nullable": true + } + } + }, + "ProfileData": { + "allOf": [ + { + "$ref": "#/components/schemas/ProfileFields" + }, + { + "type": "object", + "required": [ + "timezone", + "timezoneOffset" + ], + "properties": { + "timezone": { + "type": "string" + }, + "timezoneOffset": { + "type": "integer", + "format": "int64" + } + } + } + ] + }, + "ProfileFields": { + "type": "object", + "required": [ + "userId", + "actions" + ], + "properties": { + "userId": { + "type": "string" + }, + "address": { + "type": "string", + "nullable": true + }, + "biography": { + "type": "string", + "nullable": true + }, + "displayname": { + "type": "string", + "nullable": true + }, + "headline": { + "type": "string", + "nullable": true + }, + "isUserAvatarVisible": { + "type": "boolean" + }, + "organisation": { + "type": "string", + "nullable": true + }, + "pronouns": { + "type": "string", + "nullable": true + }, + "role": { + "type": "string", + "nullable": true + }, + "actions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ProfileAction" + } + } + } + }, "PublicCapabilities": { "type": "object", "required": [ @@ -3095,6 +3191,134 @@ } } } + }, + "get": { + "operationId": "profile_api-get-profile-fields", + "summary": "Get profile fields for another user", + "tags": [ + "profile_api" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "targetUserId", + "in": "path", + "description": "ID of the user", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Profile data returned successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "$ref": "#/components/schemas/ProfileData" + } + } + } + } + } + } + } + }, + "400": { + "description": "Profile is disabled", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "nullable": true + } + } + } + } + } + } + } + }, + "404": { + "description": "Account not found or disabled", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "nullable": true + } + } + } + } + } + } + } + } + } } }, "/ocs/v2.php/references/extract": { diff --git a/lib/private/Profile/ProfileManager.php b/lib/private/Profile/ProfileManager.php index 9d3d94fab21..13860286356 100644 --- a/lib/private/Profile/ProfileManager.php +++ b/lib/private/Profile/ProfileManager.php @@ -12,6 +12,7 @@ namespace OC\Profile; use OC\AppFramework\Bootstrap\Coordinator; use OC\Core\Db\ProfileConfig; use OC\Core\Db\ProfileConfigMapper; +use OC\Core\ResponseDefinitions; use OC\KnownUser\KnownUserService; use OC\Profile\Actions\EmailAction; use OC\Profile\Actions\FediverseAction; @@ -33,6 +34,9 @@ use Psr\Log\LoggerInterface; use function array_flip; use function usort; +/** + * @psalm-import-type CoreProfileFields from ResponseDefinitions + */ class ProfileManager implements IProfileManager { /** @var ILinkAction[] */ private array $actions = []; @@ -223,7 +227,7 @@ class ProfileManager implements IProfileManager { /** * Return the profile parameters of the target user that are visible to the visiting user * in an associative array - * @return array{userId: string, address?: string|null, biography?: string|null, displayname?: string|null, headline?: string|null, isUserAvatarVisible?: bool, organisation?: string|null, pronouns?: string|null, role?: string|null, actions: list<array{id: string, icon: string, title: string, target: ?string}>} + * @psalm-return CoreProfileFields */ public function getProfileFields(IUser $targetUser, ?IUser $visitingUser): array { $account = $this->accountManager->getAccount($targetUser); diff --git a/lib/public/Profile/IProfileManager.php b/lib/public/Profile/IProfileManager.php index 0a1e6733e58..f4e90e39d12 100644 --- a/lib/public/Profile/IProfileManager.php +++ b/lib/public/Profile/IProfileManager.php @@ -9,10 +9,12 @@ declare(strict_types=1); namespace OCP\Profile; +use OC\Core\ResponseDefinitions; use OCP\Accounts\IAccountManager; use OCP\IUser; /** + * @psalm-import-type CoreProfileFields from ResponseDefinitions * @since 28.0.0 */ interface IProfileManager { @@ -83,7 +85,7 @@ interface IProfileManager { * Return the profile parameters of the target user that are visible to the visiting user * in an associative array * - * @return array{userId: string, address?: string|null, biography?: string|null, displayname?: string|null, headline?: string|null, isUserAvatarVisible?: bool, organisation?: string|null, pronouns?: string|null, role?: string|null, actions: list<array{id: string, icon: string, title: string, target: ?string}>} + * @psalm-return CoreProfileFields * @since 28.0.0 */ public function getProfileFields(IUser $targetUser, ?IUser $visitingUser): array; |