From 7215148a242815a5064ce5d00a387c634dc936f3 Mon Sep 17 00:00:00 2001 From: Christopher Ng Date: Thu, 14 Oct 2021 08:05:17 +0000 Subject: Add new account properties - New properties - Organisation - Role - Headline - Biography - Profile Enabled property - Fix errors with building default account properties - Fix L10N factory method `getLanguage` not public error - Update tests Signed-off-by: Christopher Ng --- apps/provisioning_api/lib/Controller/AUserData.php | 5 + .../lib/Controller/UsersController.php | 191 ++++++++++++++------- .../tests/Controller/UsersControllerTest.php | 94 ++++++++-- 3 files changed, 222 insertions(+), 68 deletions(-) (limited to 'apps/provisioning_api') diff --git a/apps/provisioning_api/lib/Controller/AUserData.php b/apps/provisioning_api/lib/Controller/AUserData.php index 5bb62f2b7dc..f2fbea7b04f 100644 --- a/apps/provisioning_api/lib/Controller/AUserData.php +++ b/apps/provisioning_api/lib/Controller/AUserData.php @@ -181,6 +181,11 @@ abstract class AUserData extends OCSController { IAccountManager::PROPERTY_ADDRESS, IAccountManager::PROPERTY_WEBSITE, IAccountManager::PROPERTY_TWITTER, + IAccountManager::PROPERTY_ORGANISATION, + IAccountManager::PROPERTY_ROLE, + IAccountManager::PROPERTY_HEADLINE, + IAccountManager::PROPERTY_BIOGRAPHY, + IAccountManager::PROPERTY_PROFILE_ENABLED, ] as $propertyName) { $property = $userAccount->getProperty($propertyName); $data[$propertyName] = $property->getValue(); diff --git a/apps/provisioning_api/lib/Controller/UsersController.php b/apps/provisioning_api/lib/Controller/UsersController.php index dd8397a8a89..714759ccc0f 100644 --- a/apps/provisioning_api/lib/Controller/UsersController.php +++ b/apps/provisioning_api/lib/Controller/UsersController.php @@ -40,6 +40,7 @@ declare(strict_types=1); * along with this program. If not, see * */ + namespace OCA\Provisioning_API\Controller; use InvalidArgumentException; @@ -94,29 +95,33 @@ class UsersController extends AUserData { /** @var IEventDispatcher */ private $eventDispatcher; - public function __construct(string $appName, - IRequest $request, - IUserManager $userManager, - IConfig $config, - IGroupManager $groupManager, - IUserSession $userSession, - IAccountManager $accountManager, - IURLGenerator $urlGenerator, - LoggerInterface $logger, - IFactory $l10nFactory, - NewUserMailHelper $newUserMailHelper, - ISecureRandom $secureRandom, - RemoteWipe $remoteWipe, - KnownUserService $knownUserService, - IEventDispatcher $eventDispatcher) { - parent::__construct($appName, - $request, - $userManager, - $config, - $groupManager, - $userSession, - $accountManager, - $l10nFactory); + public function __construct( + string $appName, + IRequest $request, + IUserManager $userManager, + IConfig $config, + IGroupManager $groupManager, + IUserSession $userSession, + IAccountManager $accountManager, + IURLGenerator $urlGenerator, + LoggerInterface $logger, + IFactory $l10nFactory, + NewUserMailHelper $newUserMailHelper, + ISecureRandom $secureRandom, + RemoteWipe $remoteWipe, + KnownUserService $knownUserService, + IEventDispatcher $eventDispatcher + ) { + parent::__construct( + $appName, + $request, + $userManager, + $config, + $groupManager, + $userSession, + $accountManager, + $l10nFactory + ); $this->urlGenerator = $urlGenerator; $this->logger = $logger; @@ -325,14 +330,16 @@ class UsersController extends AUserData { * @return DataResponse * @throws OCSException */ - public function addUser(string $userid, - string $password = '', - string $displayName = '', - string $email = '', - array $groups = [], - array $subadmin = [], - string $quota = '', - string $language = ''): DataResponse { + public function addUser( + string $userid, + string $password = '', + string $displayName = '', + string $email = '', + array $groups = [], + array $subadmin = [], + string $quota = '', + string $language = '' + ): DataResponse { $user = $this->userSession->getUser(); $isAdmin = $this->groupManager->isAdmin($user->getUID()); $subAdminManager = $this->groupManager->getSubAdmin(); @@ -349,10 +356,10 @@ class UsersController extends AUserData { if ($groups !== []) { foreach ($groups as $group) { if (!$this->groupManager->groupExists($group)) { - throw new OCSException('group '.$group.' does not exist', 104); + throw new OCSException('group ' . $group . ' does not exist', 104); } if (!$isAdmin && !$subAdminManager->isSubAdminOfGroup($user, $this->groupManager->get($group))) { - throw new OCSException('insufficient privileges for group '. $group, 105); + throw new OCSException('insufficient privileges for group ' . $group, 105); } } } else { @@ -440,7 +447,8 @@ class UsersController extends AUserData { } catch (\Exception $e) { // Mail could be failing hard or just be plain not configured // Logging error as it is the hardest of the two - $this->logger->error("Unable to send the invitation mail to $email", + $this->logger->error( + "Unable to send the invitation mail to $email", [ 'app' => 'ocs_api', 'exception' => $e, @@ -452,7 +460,8 @@ class UsersController extends AUserData { return new DataResponse(['id' => $userid]); } catch (HintException $e) { - $this->logger->warning('Failed addUser attempt with hint exception.', + $this->logger->warning( + 'Failed addUser attempt with hint exception.', [ 'app' => 'ocs_api', 'exception' => $e, @@ -460,7 +469,8 @@ class UsersController extends AUserData { ); throw new OCSException($e->getHint(), 107); } catch (OCSException $e) { - $this->logger->warning('Failed addUser attempt with ocs exeption.', + $this->logger->warning( + 'Failed addUser attempt with ocs exeption.', [ 'app' => 'ocs_api', 'exception' => $e, @@ -468,7 +478,8 @@ class UsersController extends AUserData { ); throw $e; } catch (InvalidArgumentException $e) { - $this->logger->error('Failed addUser attempt with invalid argument exeption.', + $this->logger->error( + 'Failed addUser attempt with invalid argument exeption.', [ 'app' => 'ocs_api', 'exception' => $e, @@ -476,7 +487,8 @@ class UsersController extends AUserData { ); throw new OCSException($e->getMessage(), 101); } catch (\Exception $e) { - $this->logger->error('Failed addUser attempt with exception.', + $this->logger->error( + 'Failed addUser attempt with exception.', [ 'app' => 'ocs_api', 'exception' => $e @@ -573,8 +585,10 @@ class UsersController extends AUserData { } $subAdminManager = $this->groupManager->getSubAdmin(); - if (!$this->groupManager->isAdmin($currentLoggedInUser->getUID()) - && !$subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser)) { + if ( + !$this->groupManager->isAdmin($currentLoggedInUser->getUID()) + && !$subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser) + ) { throw new OCSException('', OCSController::RESPOND_NOT_FOUND); } } else { @@ -583,8 +597,10 @@ class UsersController extends AUserData { // Editing self (display, email) if ($this->config->getSystemValue('allow_user_to_change_display_name', true) !== false) { - if ($targetUser->getBackend() instanceof ISetDisplayNameBackend - || $targetUser->getBackend()->implementsActions(Backend::SET_DISPLAYNAME)) { + if ( + $targetUser->getBackend() instanceof ISetDisplayNameBackend + || $targetUser->getBackend()->implementsActions(Backend::SET_DISPLAYNAME) + ) { $permittedFields[] = IAccountManager::PROPERTY_DISPLAYNAME; } $permittedFields[] = IAccountManager::PROPERTY_EMAIL; @@ -595,6 +611,11 @@ class UsersController extends AUserData { $permittedFields[] = IAccountManager::PROPERTY_ADDRESS; $permittedFields[] = IAccountManager::PROPERTY_WEBSITE; $permittedFields[] = IAccountManager::PROPERTY_TWITTER; + $permittedFields[] = IAccountManager::PROPERTY_ORGANISATION; + $permittedFields[] = IAccountManager::PROPERTY_ROLE; + $permittedFields[] = IAccountManager::PROPERTY_HEADLINE; + $permittedFields[] = IAccountManager::PROPERTY_BIOGRAPHY; + $permittedFields[] = IAccountManager::PROPERTY_PROFILE_ENABLED; return new DataResponse($permittedFields); } @@ -700,11 +721,11 @@ class UsersController extends AUserData { * * @param string $userId * @param string $key - * @param string $value + * @param string|bool $value * @return DataResponse * @throws OCSException */ - public function editUser(string $userId, string $key, string $value): DataResponse { + public function editUser(string $userId, string $key, $value): DataResponse { $currentLoggedInUser = $this->userSession->getUser(); $targetUser = $this->userManager->get($userId); @@ -716,8 +737,10 @@ class UsersController extends AUserData { if ($targetUser->getUID() === $currentLoggedInUser->getUID()) { // Editing self (display, email) if ($this->config->getSystemValue('allow_user_to_change_display_name', true) !== false) { - if ($targetUser->getBackend() instanceof ISetDisplayNameBackend - || $targetUser->getBackend()->implementsActions(Backend::SET_DISPLAYNAME)) { + if ( + $targetUser->getBackend() instanceof ISetDisplayNameBackend + || $targetUser->getBackend()->implementsActions(Backend::SET_DISPLAYNAME) + ) { $permittedFields[] = self::USER_FIELD_DISPLAYNAME; $permittedFields[] = IAccountManager::PROPERTY_DISPLAYNAME; } @@ -731,13 +754,17 @@ class UsersController extends AUserData { $permittedFields[] = self::USER_FIELD_PASSWORD; $permittedFields[] = self::USER_FIELD_NOTIFICATION_EMAIL; - if ($this->config->getSystemValue('force_language', false) === false || - $this->groupManager->isAdmin($currentLoggedInUser->getUID())) { + if ( + $this->config->getSystemValue('force_language', false) === false || + $this->groupManager->isAdmin($currentLoggedInUser->getUID()) + ) { $permittedFields[] = self::USER_FIELD_LANGUAGE; } - if ($this->config->getSystemValue('force_locale', false) === false || - $this->groupManager->isAdmin($currentLoggedInUser->getUID())) { + if ( + $this->config->getSystemValue('force_locale', false) === false || + $this->groupManager->isAdmin($currentLoggedInUser->getUID()) + ) { $permittedFields[] = self::USER_FIELD_LOCALE; } @@ -745,10 +772,20 @@ class UsersController extends AUserData { $permittedFields[] = IAccountManager::PROPERTY_ADDRESS; $permittedFields[] = IAccountManager::PROPERTY_WEBSITE; $permittedFields[] = IAccountManager::PROPERTY_TWITTER; + $permittedFields[] = IAccountManager::PROPERTY_ORGANISATION; + $permittedFields[] = IAccountManager::PROPERTY_ROLE; + $permittedFields[] = IAccountManager::PROPERTY_HEADLINE; + $permittedFields[] = IAccountManager::PROPERTY_BIOGRAPHY; + $permittedFields[] = IAccountManager::PROPERTY_PROFILE_ENABLED; $permittedFields[] = IAccountManager::PROPERTY_PHONE . self::SCOPE_SUFFIX; $permittedFields[] = IAccountManager::PROPERTY_ADDRESS . self::SCOPE_SUFFIX; $permittedFields[] = IAccountManager::PROPERTY_WEBSITE . self::SCOPE_SUFFIX; $permittedFields[] = IAccountManager::PROPERTY_TWITTER . self::SCOPE_SUFFIX; + $permittedFields[] = IAccountManager::PROPERTY_ORGANISATION . self::SCOPE_SUFFIX; + $permittedFields[] = IAccountManager::PROPERTY_ROLE . self::SCOPE_SUFFIX; + $permittedFields[] = IAccountManager::PROPERTY_HEADLINE . self::SCOPE_SUFFIX; + $permittedFields[] = IAccountManager::PROPERTY_BIOGRAPHY . self::SCOPE_SUFFIX; + $permittedFields[] = IAccountManager::PROPERTY_PROFILE_ENABLED . self::SCOPE_SUFFIX; $permittedFields[] = IAccountManager::PROPERTY_AVATAR . self::SCOPE_SUFFIX; @@ -759,11 +796,15 @@ class UsersController extends AUserData { } else { // Check if admin / subadmin $subAdminManager = $this->groupManager->getSubAdmin(); - if ($this->groupManager->isAdmin($currentLoggedInUser->getUID()) - || $subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser)) { + if ( + $this->groupManager->isAdmin($currentLoggedInUser->getUID()) + || $subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser) + ) { // They have permissions over the user - if ($targetUser->getBackend() instanceof ISetDisplayNameBackend - || $targetUser->getBackend()->implementsActions(Backend::SET_DISPLAYNAME)) { + if ( + $targetUser->getBackend() instanceof ISetDisplayNameBackend + || $targetUser->getBackend()->implementsActions(Backend::SET_DISPLAYNAME) + ) { $permittedFields[] = self::USER_FIELD_DISPLAYNAME; $permittedFields[] = IAccountManager::PROPERTY_DISPLAYNAME; } @@ -776,6 +817,11 @@ class UsersController extends AUserData { $permittedFields[] = IAccountManager::PROPERTY_ADDRESS; $permittedFields[] = IAccountManager::PROPERTY_WEBSITE; $permittedFields[] = IAccountManager::PROPERTY_TWITTER; + $permittedFields[] = IAccountManager::PROPERTY_ORGANISATION; + $permittedFields[] = IAccountManager::PROPERTY_ROLE; + $permittedFields[] = IAccountManager::PROPERTY_HEADLINE; + $permittedFields[] = IAccountManager::PROPERTY_BIOGRAPHY; + $permittedFields[] = IAccountManager::PROPERTY_PROFILE_ENABLED; $permittedFields[] = self::USER_FIELD_QUOTA; $permittedFields[] = self::USER_FIELD_NOTIFICATION_EMAIL; } else { @@ -802,7 +848,7 @@ class UsersController extends AUserData { $quota = \OCP\Util::computerFileSize($quota); } if ($quota === false) { - throw new OCSException('Invalid quota value '.$value, 102); + throw new OCSException('Invalid quota value ' . $value, 102); } if ($quota === -1) { $quota = 'none'; @@ -892,6 +938,10 @@ class UsersController extends AUserData { case IAccountManager::PROPERTY_ADDRESS: case IAccountManager::PROPERTY_WEBSITE: case IAccountManager::PROPERTY_TWITTER: + case IAccountManager::PROPERTY_ORGANISATION: + case IAccountManager::PROPERTY_ROLE: + case IAccountManager::PROPERTY_HEADLINE: + case IAccountManager::PROPERTY_BIOGRAPHY: $userAccount = $this->accountManager->getAccount($targetUser); try { $userProperty = $userAccount->getProperty($key); @@ -910,12 +960,34 @@ class UsersController extends AUserData { } $this->accountManager->updateAccount($userAccount); break; + case IAccountManager::PROPERTY_PROFILE_ENABLED: + if (!is_bool($value)) { + throw new OCSException('Invalid value, value must be a boolean', 102); + } + $value = $value === true ? '1' : '0'; + + $userAccount = $this->accountManager->getAccount($targetUser); + try { + $userProperty = $userAccount->getProperty($key); + if ($userProperty->getValue() !== $value) { + $userProperty->setValue($value); + } + } catch (PropertyDoesNotExistException $e) { + $userAccount->setProperty($key, $value, IAccountManager::SCOPE_LOCAL, IAccountManager::NOT_VERIFIED); + } + $this->accountManager->updateAccount($userAccount); + break; case IAccountManager::PROPERTY_DISPLAYNAME . self::SCOPE_SUFFIX: case IAccountManager::PROPERTY_EMAIL . self::SCOPE_SUFFIX: case IAccountManager::PROPERTY_PHONE . self::SCOPE_SUFFIX: case IAccountManager::PROPERTY_ADDRESS . self::SCOPE_SUFFIX: case IAccountManager::PROPERTY_WEBSITE . self::SCOPE_SUFFIX: case IAccountManager::PROPERTY_TWITTER . self::SCOPE_SUFFIX: + case IAccountManager::PROPERTY_ORGANISATION . self::SCOPE_SUFFIX: + case IAccountManager::PROPERTY_ROLE . self::SCOPE_SUFFIX: + case IAccountManager::PROPERTY_HEADLINE . self::SCOPE_SUFFIX: + case IAccountManager::PROPERTY_BIOGRAPHY . self::SCOPE_SUFFIX: + case IAccountManager::PROPERTY_PROFILE_ENABLED . self::SCOPE_SUFFIX: case IAccountManager::PROPERTY_AVATAR . self::SCOPE_SUFFIX: $propertyName = substr($key, 0, strlen($key) - strlen(self::SCOPE_SUFFIX)); $userAccount = $this->accountManager->getAccount($targetUser); @@ -1300,8 +1372,10 @@ class UsersController extends AUserData { // Check if admin / subadmin $subAdminManager = $this->groupManager->getSubAdmin(); - if (!$subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser) - && !$this->groupManager->isAdmin($currentLoggedInUser->getUID())) { + if ( + !$subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser) + && !$this->groupManager->isAdmin($currentLoggedInUser->getUID()) + ) { // No rights throw new OCSException('', OCSController::RESPOND_NOT_FOUND); } @@ -1315,7 +1389,8 @@ class UsersController extends AUserData { $emailTemplate = $this->newUserMailHelper->generateTemplate($targetUser, false); $this->newUserMailHelper->sendMail($targetUser, $emailTemplate); } catch (\Exception $e) { - $this->logger->error("Can't send new user mail to $email", + $this->logger->error( + "Can't send new user mail to $email", [ 'app' => 'settings', 'exception' => $e, diff --git a/apps/provisioning_api/tests/Controller/UsersControllerTest.php b/apps/provisioning_api/tests/Controller/UsersControllerTest.php index 7ae5d0c245f..2ce5cadb57d 100644 --- a/apps/provisioning_api/tests/Controller/UsersControllerTest.php +++ b/apps/provisioning_api/tests/Controller/UsersControllerTest.php @@ -1,4 +1,5 @@ * */ + namespace OCA\Provisioning_API\Tests\Controller; use Exception; @@ -165,11 +167,12 @@ class UsersControllerTest extends TestCase { ->with('MyCustomSearch') ->willReturn(['Admin' => [], 'Foo' => [], 'Bar' => []]); - $expected = ['users' => [ - 'Admin', - 'Foo', - 'Bar', - ], + $expected = [ + 'users' => [ + 'Admin', + 'Foo', + 'Bar', + ], ]; $this->assertEquals($expected, $this->api->getUsers('MyCustomSearch')->getData()); } @@ -687,7 +690,7 @@ class UsersControllerTest extends TestCase { $this->assertTrue(key_exists( 'id', - $this->api->addUser('NewUser', 'PasswordOfTheNewUser', '', '', ['ExistingGroup'])->getData() + $this->api->addUser('NewUser', 'PasswordOfTheNewUser', '', '', ['ExistingGroup'])->getData() )); } @@ -711,7 +714,8 @@ class UsersControllerTest extends TestCase { $this->logger ->expects($this->once()) ->method('error') - ->with('Failed addUser attempt with exception.', + ->with( + 'Failed addUser attempt with exception.', [ 'app' => 'ocs_api', 'exception' => $exception @@ -998,6 +1002,11 @@ class UsersControllerTest extends TestCase { IAccountManager::PROPERTY_PHONE => ['value' => 'phone'], IAccountManager::PROPERTY_TWITTER => ['value' => 'twitter'], IAccountManager::PROPERTY_WEBSITE => ['value' => 'website'], + IAccountManager::PROPERTY_ORGANISATION => ['value' => 'organisation'], + IAccountManager::PROPERTY_ROLE => ['value' => 'role'], + IAccountManager::PROPERTY_HEADLINE => ['value' => 'headline'], + IAccountManager::PROPERTY_BIOGRAPHY => ['value' => 'biography'], + IAccountManager::PROPERTY_PROFILE_ENABLED => ['value' => '1'], ]); $this->config ->expects($this->at(0)) @@ -1067,6 +1076,11 @@ class UsersControllerTest extends TestCase { 'setPassword' => true, ], 'additional_mail' => [], + 'organisation' => 'organisation', + 'role' => 'role', + 'headline' => 'headline', + 'biography' => 'biography', + 'profile_enabled' => '1', 'notify_email' => null, ]; $this->assertEquals($expected, $this->invokePrivate($this->api, 'getUserData', ['UID'])); @@ -1166,6 +1180,11 @@ class UsersControllerTest extends TestCase { IAccountManager::PROPERTY_PHONE => ['value' => 'phone'], IAccountManager::PROPERTY_TWITTER => ['value' => 'twitter'], IAccountManager::PROPERTY_WEBSITE => ['value' => 'website'], + IAccountManager::PROPERTY_ORGANISATION => ['value' => 'organisation'], + IAccountManager::PROPERTY_ROLE => ['value' => 'role'], + IAccountManager::PROPERTY_HEADLINE => ['value' => 'headline'], + IAccountManager::PROPERTY_BIOGRAPHY => ['value' => 'biography'], + IAccountManager::PROPERTY_PROFILE_ENABLED => ['value' => '1'], ]); $this->l10nFactory @@ -1196,6 +1215,11 @@ class UsersControllerTest extends TestCase { 'setPassword' => true, ], 'additional_mail' => [], + 'organisation' => 'organisation', + 'role' => 'role', + 'headline' => 'headline', + 'biography' => 'biography', + 'profile_enabled' => '1', 'notify_email' => null, ]; $this->assertEquals($expected, $this->invokePrivate($this->api, 'getUserData', ['UID'])); @@ -1334,6 +1358,11 @@ class UsersControllerTest extends TestCase { IAccountManager::PROPERTY_PHONE => ['value' => 'phone'], IAccountManager::PROPERTY_TWITTER => ['value' => 'twitter'], IAccountManager::PROPERTY_WEBSITE => ['value' => 'website'], + IAccountManager::PROPERTY_ORGANISATION => ['value' => 'organisation'], + IAccountManager::PROPERTY_ROLE => ['value' => 'role'], + IAccountManager::PROPERTY_HEADLINE => ['value' => 'headline'], + IAccountManager::PROPERTY_BIOGRAPHY => ['value' => 'biography'], + IAccountManager::PROPERTY_PROFILE_ENABLED => ['value' => '1'], ]); $this->l10nFactory @@ -1363,6 +1392,11 @@ class UsersControllerTest extends TestCase { 'setPassword' => false, ], 'additional_mail' => [], + 'organisation' => 'organisation', + 'role' => 'role', + 'headline' => 'headline', + 'biography' => 'biography', + 'profile_enabled' => '1', 'notify_email' => null, ]; $this->assertEquals($expected, $this->invokePrivate($this->api, 'getUserData', ['UID'])); @@ -1543,6 +1577,11 @@ class UsersControllerTest extends TestCase { [IAccountManager::PROPERTY_PHONE, '1234', '12345'], [IAccountManager::PROPERTY_ADDRESS, 'Something street 2', 'Another street 3'], [IAccountManager::PROPERTY_WEBSITE, 'https://examplesite1', 'https://examplesite2'], + [IAccountManager::PROPERTY_ORGANISATION, 'Organisation A', 'Organisation B'], + [IAccountManager::PROPERTY_ROLE, 'Human', 'Alien'], + [IAccountManager::PROPERTY_HEADLINE, 'Hi', 'Hello'], + [IAccountManager::PROPERTY_BIOGRAPHY, 'A biography', 'Another biography'], + [IAccountManager::PROPERTY_PROFILE_ENABLED, '1', '0'], ]; } @@ -1614,6 +1653,11 @@ class UsersControllerTest extends TestCase { [IAccountManager::PROPERTY_PHONE, IAccountManager::SCOPE_LOCAL, IAccountManager::SCOPE_FEDERATED], [IAccountManager::PROPERTY_ADDRESS, IAccountManager::SCOPE_LOCAL, IAccountManager::SCOPE_FEDERATED], [IAccountManager::PROPERTY_WEBSITE, IAccountManager::SCOPE_LOCAL, IAccountManager::SCOPE_FEDERATED], + [IAccountManager::PROPERTY_ORGANISATION, IAccountManager::SCOPE_LOCAL, IAccountManager::SCOPE_FEDERATED], + [IAccountManager::PROPERTY_ROLE, IAccountManager::SCOPE_LOCAL, IAccountManager::SCOPE_FEDERATED], + [IAccountManager::PROPERTY_HEADLINE, IAccountManager::SCOPE_LOCAL, IAccountManager::SCOPE_FEDERATED], + [IAccountManager::PROPERTY_BIOGRAPHY, IAccountManager::SCOPE_LOCAL, IAccountManager::SCOPE_FEDERATED], + [IAccountManager::PROPERTY_PROFILE_ENABLED, IAccountManager::SCOPE_LOCAL, IAccountManager::SCOPE_FEDERATED], ]; } @@ -3490,7 +3534,12 @@ class UsersControllerTest extends TestCase { 'phone' => 'phone', 'address' => 'address', 'website' => 'website', - 'twitter' => 'twitter' + 'twitter' => 'twitter', + 'organisation' => 'organisation', + 'role' => 'role', + 'headline' => 'headline', + 'biography' => 'biography', + 'profile_enabled' => '1' ] ); @@ -3503,6 +3552,11 @@ class UsersControllerTest extends TestCase { 'address' => 'address', 'website' => 'website', 'twitter' => 'twitter', + 'organisation' => 'organisation', + 'role' => 'role', + 'headline' => 'headline', + 'biography' => 'biography', + 'profile_enabled' => '1', 'display-name' => 'Demo User' ]; @@ -3560,7 +3614,12 @@ class UsersControllerTest extends TestCase { 'address' => 'address', 'website' => 'website', 'twitter' => 'twitter', - 'displayname' => 'Demo User' + 'displayname' => 'Demo User', + 'organisation' => 'organisation', + 'role' => 'role', + 'headline' => 'headline', + 'biography' => 'biography', + 'profile_enabled' => '1' ]; $api->expects($this->at(0))->method('getUserData') @@ -3878,6 +3937,11 @@ class UsersControllerTest extends TestCase { IAccountManager::PROPERTY_ADDRESS, IAccountManager::PROPERTY_WEBSITE, IAccountManager::PROPERTY_TWITTER, + IAccountManager::PROPERTY_ORGANISATION, + IAccountManager::PROPERTY_ROLE, + IAccountManager::PROPERTY_HEADLINE, + IAccountManager::PROPERTY_BIOGRAPHY, + IAccountManager::PROPERTY_PROFILE_ENABLED, ]], [true, ISetDisplayNameBackend::class, [ IAccountManager::PROPERTY_DISPLAYNAME, @@ -3887,6 +3951,11 @@ class UsersControllerTest extends TestCase { IAccountManager::PROPERTY_ADDRESS, IAccountManager::PROPERTY_WEBSITE, IAccountManager::PROPERTY_TWITTER, + IAccountManager::PROPERTY_ORGANISATION, + IAccountManager::PROPERTY_ROLE, + IAccountManager::PROPERTY_HEADLINE, + IAccountManager::PROPERTY_BIOGRAPHY, + IAccountManager::PROPERTY_PROFILE_ENABLED, ]], [true, UserInterface::class, [ IAccountManager::PROPERTY_EMAIL, @@ -3895,6 +3964,11 @@ class UsersControllerTest extends TestCase { IAccountManager::PROPERTY_ADDRESS, IAccountManager::PROPERTY_WEBSITE, IAccountManager::PROPERTY_TWITTER, + IAccountManager::PROPERTY_ORGANISATION, + IAccountManager::PROPERTY_ROLE, + IAccountManager::PROPERTY_HEADLINE, + IAccountManager::PROPERTY_BIOGRAPHY, + IAccountManager::PROPERTY_PROFILE_ENABLED, ]], ]; } @@ -3941,7 +4015,7 @@ class UsersControllerTest extends TestCase { $account = $this->createMock(IAccount::class); $account->method('getProperty') - ->will($this->returnValueMap($mockedProperties)); + ->will($this->returnValueMap($mockedProperties)); $this->accountManager->expects($this->any())->method('getAccount') ->with($targetUser) -- cgit v1.2.3 From 309354852f12ae88d5eef05d311d6ebcba8ee762 Mon Sep 17 00:00:00 2001 From: Christopher Ng Date: Thu, 14 Oct 2021 08:19:40 +0000 Subject: Profile backend Signed-off-by: Christopher Ng --- .../lib/Controller/UsersController.php | 9 +- .../lib/Settings/Personal/PersonalInfo.php | 143 +++++++-- .../Listener/BeforeTemplateRenderedListener.php | 32 +- core/Controller/ProfileApiController.php | 98 ++++++ core/Controller/ProfilePageController.php | 131 ++++++++ core/Db/ProfileConfig.php | 172 +++++++++++ core/Db/ProfileConfigMapper.php | 48 +++ core/Migrations/Version23000Date20210930122352.php | 69 +++++ core/img/actions/phone.svg | 1 + core/img/actions/profile.svg | 1 + core/img/actions/timezone.svg | 4 +- core/img/actions/twitter.svg | 1 + core/routes.php | 4 + lib/composer/composer/autoload_classmap.php | 14 + lib/composer/composer/autoload_static.php | 14 + lib/private/AppFramework/Bootstrap/Coordinator.php | 21 +- .../AppFramework/Bootstrap/RegistrationContext.php | 28 +- .../Contacts/ContactsMenu/ActionProviderStore.php | 6 +- .../Contacts/ContactsMenu/ContactsStore.php | 54 +++- lib/private/Contacts/ContactsMenu/Entry.php | 37 +++ .../ContactsMenu/Providers/ProfileProvider.php | 93 ++++++ lib/private/Profile/Actions/EmailAction.php | 94 ++++++ lib/private/Profile/Actions/PhoneAction.php | 94 ++++++ lib/private/Profile/Actions/TwitterAction.php | 97 ++++++ lib/private/Profile/Actions/WebsiteAction.php | 94 ++++++ lib/private/Profile/ProfileManager.php | 333 +++++++++++++++++++++ lib/private/Profile/TProfileHelper.php | 46 +++ lib/private/Setup.php | 3 + lib/private/Template/SCSSCacher.php | 9 +- lib/public/Accounts/IAccountManager.php | 26 ++ .../Bootstrap/IRegistrationContext.php | 13 + lib/public/Profile/ILinkAction.php | 115 +++++++ .../Profile/ParameterDoesNotExistException.php | 40 +++ tests/acceptance/features/header.feature | 4 +- .../ContactsMenu/ActionProviderStoreTest.php | 31 +- .../Contacts/ContactsMenu/ContactsStoreTest.php | 17 +- tests/lib/Contacts/ContactsMenu/EntryTest.php | 4 +- version.php | 2 +- 38 files changed, 1921 insertions(+), 81 deletions(-) create mode 100644 core/Controller/ProfileApiController.php create mode 100644 core/Controller/ProfilePageController.php create mode 100644 core/Db/ProfileConfig.php create mode 100644 core/Db/ProfileConfigMapper.php create mode 100644 core/Migrations/Version23000Date20210930122352.php create mode 100644 core/img/actions/phone.svg create mode 100644 core/img/actions/profile.svg create mode 100644 core/img/actions/twitter.svg create mode 100644 lib/private/Contacts/ContactsMenu/Providers/ProfileProvider.php create mode 100644 lib/private/Profile/Actions/EmailAction.php create mode 100644 lib/private/Profile/Actions/PhoneAction.php create mode 100644 lib/private/Profile/Actions/TwitterAction.php create mode 100644 lib/private/Profile/Actions/WebsiteAction.php create mode 100644 lib/private/Profile/ProfileManager.php create mode 100644 lib/private/Profile/TProfileHelper.php create mode 100644 lib/public/Profile/ILinkAction.php create mode 100644 lib/public/Profile/ParameterDoesNotExistException.php (limited to 'apps/provisioning_api') diff --git a/apps/provisioning_api/lib/Controller/UsersController.php b/apps/provisioning_api/lib/Controller/UsersController.php index 714759ccc0f..38d51857ffc 100644 --- a/apps/provisioning_api/lib/Controller/UsersController.php +++ b/apps/provisioning_api/lib/Controller/UsersController.php @@ -721,11 +721,11 @@ class UsersController extends AUserData { * * @param string $userId * @param string $key - * @param string|bool $value + * @param string $value * @return DataResponse * @throws OCSException */ - public function editUser(string $userId, string $key, $value): DataResponse { + public function editUser(string $userId, string $key, string $value): DataResponse { $currentLoggedInUser = $this->userSession->getUser(); $targetUser = $this->userManager->get($userId); @@ -961,11 +961,6 @@ class UsersController extends AUserData { $this->accountManager->updateAccount($userAccount); break; case IAccountManager::PROPERTY_PROFILE_ENABLED: - if (!is_bool($value)) { - throw new OCSException('Invalid value, value must be a boolean', 102); - } - $value = $value === true ? '1' : '0'; - $userAccount = $this->accountManager->getAccount($targetUser); try { $userProperty = $userAccount->getProperty($key); diff --git a/apps/settings/lib/Settings/Personal/PersonalInfo.php b/apps/settings/lib/Settings/Personal/PersonalInfo.php index b56498fa585..40ce3b62e11 100644 --- a/apps/settings/lib/Settings/Personal/PersonalInfo.php +++ b/apps/settings/lib/Settings/Personal/PersonalInfo.php @@ -33,13 +33,16 @@ declare(strict_types=1); * along with this program. If not, see . * */ + namespace OCA\Settings\Settings\Personal; use OCA\FederatedFileSharing\FederatedShareProvider; use OCP\Accounts\IAccount; use OCP\Accounts\IAccountManager; +use OCP\Accounts\IAccountProperty; use OCP\App\IAppManager; use OCP\AppFramework\Http\TemplateResponse; +use OCP\AppFramework\Services\IInitialState; use OCP\Files\FileInfo; use OCP\IConfig; use OCP\IGroup; @@ -48,26 +51,36 @@ use OCP\IL10N; use OCP\IUser; use OCP\IUserManager; use OCP\L10N\IFactory; +use OC\Profile\ProfileManager; use OCP\Settings\ISettings; -use OCP\Accounts\IAccountProperty; -use OCP\AppFramework\Services\IInitialState; class PersonalInfo implements ISettings { + use \OC\Profile\TProfileHelper; /** @var IConfig */ private $config; + /** @var IUserManager */ private $userManager; + /** @var IAccountManager */ private $accountManager; + + /** @var ProfileManager */ + private $profileManager; + /** @var IGroupManager */ private $groupManager; + /** @var IAppManager */ private $appManager; + /** @var IFactory */ private $l10nFactory; + /** @var IL10N */ private $l; + /** @var IInitialState */ private $initialStateService; @@ -76,6 +89,7 @@ class PersonalInfo implements ISettings { IUserManager $userManager, IGroupManager $groupManager, IAccountManager $accountManager, + ProfileManager $profileManager, IAppManager $appManager, IFactory $l10nFactory, IL10N $l, @@ -84,6 +98,7 @@ class PersonalInfo implements ISettings { $this->config = $config; $this->userManager = $userManager; $this->accountManager = $accountManager; + $this->profileManager = $profileManager; $this->groupManager = $groupManager; $this->appManager = $appManager; $this->l10nFactory = $l10nFactory; @@ -114,7 +129,7 @@ class PersonalInfo implements ISettings { $totalSpace = \OC_Helper::humanFileSize($storageInfo['total']); } - $languageParameters = $this->getLanguages($user); + $languageParameters = $this->getLanguageMap($user); $localeParameters = $this->getLocales($user); $messageParameters = $this->getMessageParameters($account); @@ -146,9 +161,15 @@ class PersonalInfo implements ISettings { ] + $messageParameters + $languageParameters + $localeParameters; $personalInfoParameters = [ - 'displayNames' => $this->getDisplayNames($account), - 'emails' => $this->getEmails($account), - 'languages' => $this->getLanguages($user), + 'userId' => $uid, + 'displayNameMap' => $this->getDisplayNameMap($account), + 'emailMap' => $this->getEmailMap($account), + 'languageMap' => $this->getLanguageMap($user), + 'profileEnabled' => $this->isProfileEnabled($account), + 'organisationMap' => $this->getOrganisationMap($account), + 'roleMap' => $this->getRoleMap($account), + 'headlineMap' => $this->getHeadlineMap($account), + 'biographyMap' => $this->getBiographyMap($account), ]; $accountParameters = [ @@ -156,14 +177,91 @@ class PersonalInfo implements ISettings { 'lookupServerUploadEnabled' => $lookupServerUploadEnabled, ]; + $profileParameters = [ + 'profileConfig' => $this->profileManager->getProfileConfig($user, $user), + ]; + $this->initialStateService->provideInitialState('personalInfoParameters', $personalInfoParameters); $this->initialStateService->provideInitialState('accountParameters', $accountParameters); + $this->initialStateService->provideInitialState('profileParameters', $profileParameters); return new TemplateResponse('settings', 'settings/personal/personal.info', $parameters, ''); } /** - * @return string the section ID, e.g. 'sharing' + * returns the primary biography in an + * associative array + */ + private function getBiographyMap(IAccount $account): array { + $primaryBiography = [ + 'value' => $account->getProperty(IAccountManager::PROPERTY_BIOGRAPHY)->getValue(), + 'scope' => $account->getProperty(IAccountManager::PROPERTY_BIOGRAPHY)->getScope(), + 'verified' => $account->getProperty(IAccountManager::PROPERTY_BIOGRAPHY)->getVerified(), + ]; + + $biographyMap = [ + 'primaryBiography' => $primaryBiography, + ]; + + return $biographyMap; + } + + /** + * returns the primary organisation in an + * associative array + */ + private function getOrganisationMap(IAccount $account): array { + $primaryOrganisation = [ + 'value' => $account->getProperty(IAccountManager::PROPERTY_ORGANISATION)->getValue(), + 'scope' => $account->getProperty(IAccountManager::PROPERTY_ORGANISATION)->getScope(), + 'verified' => $account->getProperty(IAccountManager::PROPERTY_ORGANISATION)->getVerified(), + ]; + + $organisationMap = [ + 'primaryOrganisation' => $primaryOrganisation, + ]; + + return $organisationMap; + } + + /** + * returns the primary headline in an + * associative array + */ + private function getHeadlineMap(IAccount $account): array { + $primaryHeadline = [ + 'value' => $account->getProperty(IAccountManager::PROPERTY_HEADLINE)->getValue(), + 'scope' => $account->getProperty(IAccountManager::PROPERTY_HEADLINE)->getScope(), + 'verified' => $account->getProperty(IAccountManager::PROPERTY_HEADLINE)->getVerified(), + ]; + + $headlineMap = [ + 'primaryHeadline' => $primaryHeadline, + ]; + + return $headlineMap; + } + + /** + * returns the primary role in an + * associative array + */ + private function getRoleMap(IAccount $account): array { + $primaryRole = [ + 'value' => $account->getProperty(IAccountManager::PROPERTY_ROLE)->getValue(), + 'scope' => $account->getProperty(IAccountManager::PROPERTY_ROLE)->getScope(), + 'verified' => $account->getProperty(IAccountManager::PROPERTY_ROLE)->getVerified(), + ]; + + $roleMap = [ + 'primaryRole' => $primaryRole, + ]; + + return $roleMap; + } + + /** + * returns the section ID string, e.g. 'sharing' * @since 9.1 */ public function getSection(): string { @@ -184,9 +282,6 @@ class PersonalInfo implements ISettings { /** * returns a sorted list of the user's group GIDs - * - * @param IUser $user - * @return array */ private function getGroups(IUser $user): array { $groups = array_map( @@ -205,32 +300,26 @@ class PersonalInfo implements ISettings { * associative array * * NOTE may be extended to provide additional display names (i.e. aliases) in the future - * - * @param IAccount $account - * @return array */ - private function getDisplayNames(IAccount $account): array { + private function getDisplayNameMap(IAccount $account): array { $primaryDisplayName = [ 'value' => $account->getProperty(IAccountManager::PROPERTY_DISPLAYNAME)->getValue(), 'scope' => $account->getProperty(IAccountManager::PROPERTY_DISPLAYNAME)->getScope(), 'verified' => $account->getProperty(IAccountManager::PROPERTY_DISPLAYNAME)->getVerified(), ]; - $displayNames = [ + $displayNameMap = [ 'primaryDisplayName' => $primaryDisplayName, ]; - return $displayNames; + return $displayNameMap; } /** * returns the primary email and additional emails in an * associative array - * - * @param IAccount $account - * @return array */ - private function getEmails(IAccount $account): array { + private function getEmailMap(IAccount $account): array { $systemEmail = [ 'value' => $account->getProperty(IAccountManager::PROPERTY_EMAIL)->getValue(), 'scope' => $account->getProperty(IAccountManager::PROPERTY_EMAIL)->getScope(), @@ -246,26 +335,23 @@ class PersonalInfo implements ISettings { 'locallyVerified' => $property->getLocallyVerified(), ]; }, - $account->getPropertyCollection(IAccountManager::COLLECTION_EMAIL)->getProperties() + $account->getPropertyCollection(IAccountManager::COLLECTION_EMAIL)->getProperties(), ); - $emails = [ + $emailMap = [ 'primaryEmail' => $systemEmail, 'additionalEmails' => $additionalEmails, 'notificationEmail' => (string)$account->getUser()->getPrimaryEMailAddress(), ]; - return $emails; + return $emailMap; } /** * returns the user's active language, common languages, and other languages in an * associative array - * - * @param IUser $user - * @return array */ - private function getLanguages(IUser $user): array { + private function getLanguageMap(IUser $user): array { $forceLanguage = $this->config->getSystemValue('force_language', false); if ($forceLanguage !== false) { return []; @@ -340,8 +426,7 @@ class PersonalInfo implements ISettings { } /** - * @param IAccount $account - * @return array + * returns the message parameters */ private function getMessageParameters(IAccount $account): array { $needVerifyMessage = [IAccountManager::PROPERTY_EMAIL, IAccountManager::PROPERTY_WEBSITE, IAccountManager::PROPERTY_TWITTER]; diff --git a/apps/user_status/lib/Listener/BeforeTemplateRenderedListener.php b/apps/user_status/lib/Listener/BeforeTemplateRenderedListener.php index 86e151affdf..24090188170 100644 --- a/apps/user_status/lib/Listener/BeforeTemplateRenderedListener.php +++ b/apps/user_status/lib/Listener/BeforeTemplateRenderedListener.php @@ -24,17 +24,27 @@ declare(strict_types=1); * along with this program. If not, see . * */ + namespace OCA\UserStatus\Listener; use OCA\UserStatus\AppInfo\Application; use OCA\UserStatus\Service\JSDataService; +use OCP\Accounts\IAccountManager; use OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent; use OCP\AppFramework\Http\TemplateResponse; use OCP\EventDispatcher\Event; use OCP\EventDispatcher\IEventListener; use OCP\IInitialStateService; +use OCP\IUserSession; class BeforeTemplateRenderedListener implements IEventListener { + use \OC\Profile\TProfileHelper; + + /** @var IAccountManager */ + private $accountManager; + + /** @var IUserSession */ + private $userSession; /** @var IInitialStateService */ private $initialState; @@ -45,11 +55,19 @@ class BeforeTemplateRenderedListener implements IEventListener { /** * BeforeTemplateRenderedListener constructor. * + * @param IAccountManager $accountManager + * @param IUserSession $userSession * @param IInitialStateService $initialState * @param JSDataService $jsDataService */ - public function __construct(IInitialStateService $initialState, - JSDataService $jsDataService) { + public function __construct( + IAccountManager $accountManager, + IUserSession $userSession, + IInitialStateService $initialState, + JSDataService $jsDataService + ) { + $this->accountManager = $accountManager; + $this->userSession = $userSession; $this->initialState = $initialState; $this->jsDataService = $jsDataService; } @@ -58,6 +76,12 @@ class BeforeTemplateRenderedListener implements IEventListener { * @inheritDoc */ public function handle(Event $event): void { + $user = $this->userSession->getUser(); + if ($user === null) { + return; + } + $account = $this->accountManager->getAccount($user); + if (!($event instanceof BeforeTemplateRenderedEvent)) { // Unrelated return; @@ -71,6 +95,10 @@ class BeforeTemplateRenderedListener implements IEventListener { return $this->jsDataService; }); + $this->initialState->provideLazyInitialState(Application::APP_ID, 'profileEnabled', function () use ($account) { + return ['profileEnabled' => $this->isProfileEnabled($account)]; + }); + \OCP\Util::addScript('user_status', 'user-status-menu'); \OCP\Util::addStyle('user_status', 'user-status-menu'); } diff --git a/core/Controller/ProfileApiController.php b/core/Controller/ProfileApiController.php new file mode 100644 index 00000000000..d9e20701eaa --- /dev/null +++ b/core/Controller/ProfileApiController.php @@ -0,0 +1,98 @@ + + * + * @author Christopher Ng + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Core\Controller; + +use OC\Core\Db\ProfileConfigMapper; +use OCP\AppFramework\Http\DataResponse; +use OCP\AppFramework\OCS\OCSBadRequestException; +use OCP\AppFramework\OCS\OCSForbiddenException; +use OCP\AppFramework\OCS\OCSNotFoundException; +use OCP\AppFramework\OCSController; +use OCP\IRequest; +use OCP\IUserManager; +use OCP\IUserSession; +use OC\Profile\ProfileManager; + +class ProfileApiController extends OCSController { + + /** @var ProfileConfigMapper */ + private $configMapper; + + /** @var ProfileManager */ + private $profileManager; + + /** @var IUserManager */ + private $userManager; + + /** @var IUserSession */ + private $userSession; + + public function __construct( + IRequest $request, + ProfileConfigMapper $configMapper, + ProfileManager $profileManager, + IUserManager $userManager, + IUserSession $userSession + ) { + parent::__construct('core', $request); + $this->configMapper = $configMapper; + $this->profileManager = $profileManager; + $this->userManager = $userManager; + $this->userSession = $userSession; + } + + /** + * @NoAdminRequired + * @NoSubAdminRequired + * @PasswordConfirmationRequired + */ + 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('User does not exist'); + } + + if ($requestingUser !== $targetUser) { + throw new OCSForbiddenException('Users can only edit their own visibility settings'); + } + + // Ensure that a profile config is created in the database + $this->profileManager->getProfileConfig($targetUser, $targetUser); + $config = $this->configMapper->get($targetUserId); + + if (!in_array($paramId, array_keys($config->getVisibilityMap()), true)) { + throw new OCSBadRequestException('User does not have a profile parameter with ID: ' . $paramId); + } + + $config->setVisibility($paramId, $visibility); + $this->configMapper->update($config); + + return new DataResponse(); + } +} diff --git a/core/Controller/ProfilePageController.php b/core/Controller/ProfilePageController.php new file mode 100644 index 00000000000..a7ceb404fbc --- /dev/null +++ b/core/Controller/ProfilePageController.php @@ -0,0 +1,131 @@ + + * + * @author Christopher Ng + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Core\Controller; + +use OCP\Accounts\IAccountManager; +use OCP\AppFramework\Controller; +use OCP\AppFramework\Http\TemplateResponse; +use OCP\AppFramework\Services\IInitialState; +use OCP\IRequest; +use OCP\IUserManager; +use OCP\IUserSession; +use OC\Profile\ProfileManager; +use OCP\UserStatus\IManager as IUserStatusManager; + +class ProfilePageController extends Controller { + use \OC\Profile\TProfileHelper; + + /** @var IInitialState */ + private $initialStateService; + + /** @var IAccountManager */ + private $accountManager; + + /** @var ProfileManager */ + private $profileManager; + + /** @var IUserManager */ + private $userManager; + + /** @var IUserSession */ + private $userSession; + + /** @var IUserStatusManager */ + private $userStatusManager; + + public function __construct( + $appName, + IRequest $request, + IInitialState $initialStateService, + IAccountManager $accountManager, + ProfileManager $profileManager, + IUserManager $userManager, + IUserSession $userSession, + IUserStatusManager $userStatusManager + ) { + parent::__construct($appName, $request); + $this->initialStateService = $initialStateService; + $this->accountManager = $accountManager; + $this->profileManager = $profileManager; + $this->userManager = $userManager; + $this->userSession = $userSession; + $this->userStatusManager = $userStatusManager; + } + + /** + * @PublicPage + * @NoCSRFRequired + * @NoAdminRequired + * @NoSubAdminRequired + */ + public function index(string $targetUserId): TemplateResponse { + if (!$this->userManager->userExists($targetUserId)) { + return new TemplateResponse( + 'core', + '404-profile', + [], + TemplateResponse::RENDER_AS_GUEST, + ); + } + + $visitingUser = $this->userSession->getUser(); + $targetUser = $this->userManager->get($targetUserId); + $targetAccount = $this->accountManager->getAccount($targetUser); + + if (!$this->isProfileEnabled($targetAccount)) { + return new TemplateResponse( + 'core', + '404-profile', + [], + TemplateResponse::RENDER_AS_GUEST, + ); + } + + $userStatuses = $this->userStatusManager->getUserStatuses([$targetUserId]); + $status = array_shift($userStatuses); + if (!empty($status)) { + $this->initialStateService->provideInitialState('status', [ + 'icon' => $status->getIcon(), + 'message' => $status->getMessage(), + ]); + } + + $this->initialStateService->provideInitialState( + 'profileParameters', + $this->profileManager->getProfileParams($targetUser, $visitingUser), + ); + + \OCP\Util::addScript('core', 'dist/profile'); + + return new TemplateResponse( + 'core', + 'profile', + [], + $this->userSession->isLoggedIn() ? TemplateResponse::RENDER_AS_USER : TemplateResponse::RENDER_AS_PUBLIC, + ); + } +} diff --git a/core/Db/ProfileConfig.php b/core/Db/ProfileConfig.php new file mode 100644 index 00000000000..eb50da37a64 --- /dev/null +++ b/core/Db/ProfileConfig.php @@ -0,0 +1,172 @@ + + * + * @author Christopher Ng + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Core\Db; + +use function Safe\json_decode; +use function Safe\json_encode; +use \JsonSerializable; +use OCP\Accounts\IAccountManager; +use OCP\AppFramework\Db\Entity; +use OCP\Profile\ParameterDoesNotExistException; + +/** + * @method string getUserId() + * @method void setUserId(string $userId) + * @method string getConfig() + * @method void setConfig(string $config) + */ +class ProfileConfig extends Entity implements JsonSerializable { + + /** + * Visible to users, guests, and public access + * + * @since 23.0.0 + */ + public const VISIBILITY_SHOW = 'show'; + + /** + * Visible to users and guests + * + * @since 23.0.0 + */ + public const VISIBILITY_SHOW_USERS_ONLY = 'show_users_only'; + + /** + * Visible to nobody + * + * @since 23.0.0 + */ + public const VISIBILITY_HIDE = 'hide'; + + /** + * Default account property visibility + * + * @since 23.0.0 + */ + public const DEFAULT_PROPERTY_VISIBILITY = [ + IAccountManager::PROPERTY_ADDRESS => self::VISIBILITY_SHOW_USERS_ONLY, + IAccountManager::PROPERTY_AVATAR => self::VISIBILITY_SHOW, + IAccountManager::PROPERTY_BIOGRAPHY => self::VISIBILITY_SHOW, + IAccountManager::PROPERTY_DISPLAYNAME => self::VISIBILITY_SHOW, + IAccountManager::PROPERTY_HEADLINE => self::VISIBILITY_SHOW, + IAccountManager::PROPERTY_ORGANISATION => self::VISIBILITY_SHOW, + IAccountManager::PROPERTY_ROLE => self::VISIBILITY_SHOW, + IAccountManager::PROPERTY_EMAIL => self::VISIBILITY_SHOW_USERS_ONLY, + IAccountManager::PROPERTY_PHONE => self::VISIBILITY_SHOW_USERS_ONLY, + IAccountManager::PROPERTY_TWITTER => self::VISIBILITY_SHOW, + IAccountManager::PROPERTY_WEBSITE => self::VISIBILITY_SHOW, + ]; + + /** + * Default visibility + * + * @since 23.0.0 + */ + public const DEFAULT_VISIBILITY = self::VISIBILITY_SHOW_USERS_ONLY; + + /** @var string */ + protected $userId; + + /** @var string */ + protected $config; + + public function __construct() { + $this->addType('userId', 'string'); + $this->addType('config', 'string'); + } + + /** + * Returns the config in an associative array + */ + public function getConfigArray(): array { + return json_decode($this->config, true); + } + + /** + * Set the config + */ + public function setConfigArray(array $config): void { + $this->setConfig(json_encode($config)); + } + + /** + * Returns the visibility map in an associative array + */ + public function getVisibilityMap(): array { + $config = $this->getConfigArray(); + $visibilityMap = []; + foreach ($config as $paramId => $paramConfig) { + $visibilityMap[$paramId] = $paramConfig['visibility']; + } + + return $visibilityMap; + } + + /** + * Set the visibility map + */ + public function setVisibilityMap(array $visibilityMap): void { + $config = $this->getConfigArray(); + foreach ($visibilityMap as $paramId => $visibility) { + $config[$paramId] = array_merge( + $config[$paramId] ?: [], + ['visibility' => $visibility], + ); + } + + $this->setConfigArray($config); + } + + /** + * Returns the visibility of the parameter + * + * @throws ParameterDoesNotExistException + */ + public function getVisibility(string $paramId): string { + $visibilityMap = $this->getVisibilityMap(); + if (isset($visibilityMap[$paramId])) { + return $visibilityMap[$paramId]; + } + throw new ParameterDoesNotExistException($paramId); + } + + /** + * Set the visibility of the parameter + */ + public function setVisibility(string $paramId, string $visibility): void { + $visibilityMap = $this->getVisibilityMap(); + $visibilityMap[$paramId] = $visibility; + $this->setVisibilityMap($visibilityMap); + } + + public function jsonSerialize(): array { + return [ + 'userId' => $this->userId, + 'config' => $this->getConfigArray(), + ]; + } +} diff --git a/core/Db/ProfileConfigMapper.php b/core/Db/ProfileConfigMapper.php new file mode 100644 index 00000000000..a8b1e35f747 --- /dev/null +++ b/core/Db/ProfileConfigMapper.php @@ -0,0 +1,48 @@ + + * + * @author Christopher Ng + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Core\Db; + +use OCP\AppFramework\Db\QBMapper; +use OCP\IDBConnection; + +class ProfileConfigMapper extends QBMapper { + public function __construct(IDBConnection $db) { + parent::__construct($db, 'profile_config', ProfileConfig::class); + } + + public function get(string $userId): ProfileConfig { + $qb = $this->db->getQueryBuilder(); + $qb->select('*') + ->from($this->getTableName()) + ->where($qb->expr()->eq('user_id', $qb->createNamedParameter($userId))); + return $this->findEntity($qb); + } + + public function getArray(string $userId): array { + return $this->get($userId)->getConfigArray(); + } +} diff --git a/core/Migrations/Version23000Date20210930122352.php b/core/Migrations/Version23000Date20210930122352.php new file mode 100644 index 00000000000..e3eb9af7eba --- /dev/null +++ b/core/Migrations/Version23000Date20210930122352.php @@ -0,0 +1,69 @@ + + * + * @author Christopher Ng + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Core\Migrations; + +use Closure; +use OCP\DB\ISchemaWrapper; +use OCP\DB\Types; +use OCP\Migration\IOutput; +use OCP\Migration\SimpleMigrationStep; + +class Version23000Date20210930122352 extends SimpleMigrationStep { + private const TABLE_NAME = 'profile_config'; + + /** + * @param IOutput $output + * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` + * @param array $options + * @return null|ISchemaWrapper + */ + public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper { + /** @var ISchemaWrapper $schema */ + $schema = $schemaClosure(); + + $hasTable = $schema->hasTable(self::TABLE_NAME); + if (!$hasTable) { + $table = $schema->createTable(self::TABLE_NAME); + $table->addColumn('id', Types::INTEGER, [ + 'autoincrement' => true, + 'notnull' => true, + ]); + $table->addColumn('user_id', Types::STRING, [ + 'notnull' => true, + 'length' => 64, + ]); + $table->addColumn('config', Types::TEXT, [ + 'notnull' => true, + ]); + $table->setPrimaryKey(['id']); + $table->addUniqueIndex(['user_id'], 'user_id'); + return $schema; + } + + return null; + } +} diff --git a/core/img/actions/phone.svg b/core/img/actions/phone.svg new file mode 100644 index 00000000000..79bb9100b13 --- /dev/null +++ b/core/img/actions/phone.svg @@ -0,0 +1 @@ + diff --git a/core/img/actions/profile.svg b/core/img/actions/profile.svg new file mode 100644 index 00000000000..90b95f619aa --- /dev/null +++ b/core/img/actions/profile.svg @@ -0,0 +1 @@ + diff --git a/core/img/actions/timezone.svg b/core/img/actions/timezone.svg index f12c3665749..1da05c67df1 100644 --- a/core/img/actions/timezone.svg +++ b/core/img/actions/timezone.svg @@ -1,3 +1 @@ - - - + diff --git a/core/img/actions/twitter.svg b/core/img/actions/twitter.svg new file mode 100644 index 00000000000..c21a67b32a5 --- /dev/null +++ b/core/img/actions/twitter.svg @@ -0,0 +1 @@ + diff --git a/core/routes.php b/core/routes.php index 0625690b2d2..59988404cd4 100644 --- a/core/routes.php +++ b/core/routes.php @@ -33,6 +33,7 @@ declare(strict_types=1); * along with this program. If not, see * */ + use OC\Core\Application; /** @var Application $application */ @@ -42,6 +43,7 @@ $application->registerRoutes($this, [ ['name' => 'lost#email', 'url' => '/lostpassword/email', 'verb' => 'POST'], ['name' => 'lost#resetform', 'url' => '/lostpassword/reset/form/{token}/{userId}', 'verb' => 'GET'], ['name' => 'lost#setPassword', 'url' => '/lostpassword/set/{token}/{userId}', 'verb' => 'POST'], + ['name' => 'ProfilePage#index', 'url' => '/u/{targetUserId}', 'verb' => 'GET'], ['name' => 'user#getDisplayNames', 'url' => '/displaynames', 'verb' => 'POST'], ['name' => 'avatar#getAvatar', 'url' => '/avatar/{userId}/{size}', 'verb' => 'GET'], ['name' => 'avatar#deleteAvatar', 'url' => '/avatar/', 'verb' => 'DELETE'], @@ -117,6 +119,8 @@ $application->registerRoutes($this, [ ['root' => '/collaboration', 'name' => 'CollaborationResources#getCollectionsByResource', 'url' => '/resources/{resourceType}/{resourceId}', 'verb' => 'GET'], ['root' => '/collaboration', 'name' => 'CollaborationResources#createCollectionOnResource', 'url' => '/resources/{baseResourceType}/{baseResourceId}', 'verb' => 'POST'], + ['root' => '/profile', 'name' => 'ProfileApi#setVisibility', 'url' => '/{targetUserId}', 'verb' => 'PUT'], + // Unified search ['root' => '/search', 'name' => 'UnifiedSearch#getProviders', 'url' => '/providers', 'verb' => 'GET'], ['root' => '/search', 'name' => 'UnifiedSearch#search', 'url' => '/providers/{providerId}/search', 'verb' => 'GET'], diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 343d2ddf027..fa1ef50c41e 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -460,6 +460,8 @@ return array( 'OCP\\Preview\\IProvider' => $baseDir . '/lib/public/Preview/IProvider.php', 'OCP\\Preview\\IProviderV2' => $baseDir . '/lib/public/Preview/IProviderV2.php', 'OCP\\Preview\\IVersionedPreviewFile' => $baseDir . '/lib/public/Preview/IVersionedPreviewFile.php', + 'OCP\\Profile\\ILinkAction' => $baseDir . '/lib/public/Profile/ILinkAction.php', + 'OCP\\Profile\\ParameterDoesNotExistException' => $baseDir . '/lib/public/Profile/ParameterDoesNotExistException.php', 'OCP\\Remote\\Api\\IApiCollection' => $baseDir . '/lib/public/Remote/Api/IApiCollection.php', 'OCP\\Remote\\Api\\IApiFactory' => $baseDir . '/lib/public/Remote/Api/IApiFactory.php', 'OCP\\Remote\\Api\\ICapabilitiesApi' => $baseDir . '/lib/public/Remote/Api/ICapabilitiesApi.php', @@ -804,6 +806,7 @@ return array( 'OC\\Contacts\\ContactsMenu\\Entry' => $baseDir . '/lib/private/Contacts/ContactsMenu/Entry.php', 'OC\\Contacts\\ContactsMenu\\Manager' => $baseDir . '/lib/private/Contacts/ContactsMenu/Manager.php', 'OC\\Contacts\\ContactsMenu\\Providers\\EMailProvider' => $baseDir . '/lib/private/Contacts/ContactsMenu/Providers/EMailProvider.php', + 'OC\\Contacts\\ContactsMenu\\Providers\\ProfileProvider' => $baseDir . '/lib/private/Contacts/ContactsMenu/Providers/ProfileProvider.php', 'OC\\Core\\Application' => $baseDir . '/core/Application.php', 'OC\\Core\\BackgroundJobs\\BackgroundCleanupUpdaterBackupsJob' => $baseDir . '/core/BackgroundJobs/BackgroundCleanupUpdaterBackupsJob.php', 'OC\\Core\\BackgroundJobs\\CheckForUserCertificates' => $baseDir . '/core/BackgroundJobs/CheckForUserCertificates.php', @@ -924,6 +927,8 @@ return array( 'OC\\Core\\Controller\\OCJSController' => $baseDir . '/core/Controller/OCJSController.php', 'OC\\Core\\Controller\\OCSController' => $baseDir . '/core/Controller/OCSController.php', 'OC\\Core\\Controller\\PreviewController' => $baseDir . '/core/Controller/PreviewController.php', + 'OC\\Core\\Controller\\ProfileApiController' => $baseDir . '/core/Controller/ProfileApiController.php', + 'OC\\Core\\Controller\\ProfilePageController' => $baseDir . '/core/Controller/ProfilePageController.php', 'OC\\Core\\Controller\\RecommendedAppsController' => $baseDir . '/core/Controller/RecommendedAppsController.php', 'OC\\Core\\Controller\\SearchController' => $baseDir . '/core/Controller/SearchController.php', 'OC\\Core\\Controller\\SetupController' => $baseDir . '/core/Controller/SetupController.php', @@ -940,6 +945,8 @@ return array( 'OC\\Core\\Data\\LoginFlowV2Tokens' => $baseDir . '/core/Data/LoginFlowV2Tokens.php', 'OC\\Core\\Db\\LoginFlowV2' => $baseDir . '/core/Db/LoginFlowV2.php', 'OC\\Core\\Db\\LoginFlowV2Mapper' => $baseDir . '/core/Db/LoginFlowV2Mapper.php', + 'OC\\Core\\Db\\ProfileConfig' => $baseDir . '/core/Db/ProfileConfig.php', + 'OC\\Core\\Db\\ProfileConfigMapper' => $baseDir . '/core/Db/ProfileConfigMapper.php', 'OC\\Core\\Exception\\LoginFlowV2NotFoundException' => $baseDir . '/core/Exception/LoginFlowV2NotFoundException.php', 'OC\\Core\\Exception\\ResetPasswordException' => $baseDir . '/core/Exception/ResetPasswordException.php', 'OC\\Core\\Middleware\\TwoFactorMiddleware' => $baseDir . '/core/Middleware/TwoFactorMiddleware.php', @@ -980,6 +987,7 @@ return array( 'OC\\Core\\Migrations\\Version22000Date20210216080825' => $baseDir . '/core/Migrations/Version22000Date20210216080825.php', 'OC\\Core\\Migrations\\Version23000Date20210721100600' => $baseDir . '/core/Migrations/Version23000Date20210721100600.php', 'OC\\Core\\Migrations\\Version23000Date20210906132259' => $baseDir . '/core/Migrations/Version23000Date20210906132259.php', + 'OC\\Core\\Migrations\\Version23000Date20210930122352' => $baseDir . '/core/Migrations/Version23000Date20210930122352.php', 'OC\\Core\\Notification\\CoreNotifier' => $baseDir . '/core/Notification/CoreNotifier.php', 'OC\\Core\\Service\\LoginFlowV2Service' => $baseDir . '/core/Service/LoginFlowV2Service.php', 'OC\\DB\\Adapter' => $baseDir . '/lib/private/DB/Adapter.php', @@ -1300,6 +1308,12 @@ return array( 'OC\\Preview\\WatcherConnector' => $baseDir . '/lib/private/Preview/WatcherConnector.php', 'OC\\Preview\\WebP' => $baseDir . '/lib/private/Preview/WebP.php', 'OC\\Preview\\XBitmap' => $baseDir . '/lib/private/Preview/XBitmap.php', + 'OC\\Profile\\Actions\\EmailAction' => $baseDir . '/lib/private/Profile/Actions/EmailAction.php', + 'OC\\Profile\\Actions\\PhoneAction' => $baseDir . '/lib/private/Profile/Actions/PhoneAction.php', + 'OC\\Profile\\Actions\\TwitterAction' => $baseDir . '/lib/private/Profile/Actions/TwitterAction.php', + 'OC\\Profile\\Actions\\WebsiteAction' => $baseDir . '/lib/private/Profile/Actions/WebsiteAction.php', + 'OC\\Profile\\ProfileManager' => $baseDir . '/lib/private/Profile/ProfileManager.php', + 'OC\\Profile\\TProfileHelper' => $baseDir . '/lib/private/Profile/TProfileHelper.php', 'OC\\RedisFactory' => $baseDir . '/lib/private/RedisFactory.php', 'OC\\Remote\\Api\\ApiBase' => $baseDir . '/lib/private/Remote/Api/ApiBase.php', 'OC\\Remote\\Api\\ApiCollection' => $baseDir . '/lib/private/Remote/Api/ApiCollection.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 96ed5a614bc..89892d45ab0 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -489,6 +489,8 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OCP\\Preview\\IProvider' => __DIR__ . '/../../..' . '/lib/public/Preview/IProvider.php', 'OCP\\Preview\\IProviderV2' => __DIR__ . '/../../..' . '/lib/public/Preview/IProviderV2.php', 'OCP\\Preview\\IVersionedPreviewFile' => __DIR__ . '/../../..' . '/lib/public/Preview/IVersionedPreviewFile.php', + 'OCP\\Profile\\ILinkAction' => __DIR__ . '/../../..' . '/lib/public/Profile/ILinkAction.php', + 'OCP\\Profile\\ParameterDoesNotExistException' => __DIR__ . '/../../..' . '/lib/public/Profile/ParameterDoesNotExistException.php', 'OCP\\Remote\\Api\\IApiCollection' => __DIR__ . '/../../..' . '/lib/public/Remote/Api/IApiCollection.php', 'OCP\\Remote\\Api\\IApiFactory' => __DIR__ . '/../../..' . '/lib/public/Remote/Api/IApiFactory.php', 'OCP\\Remote\\Api\\ICapabilitiesApi' => __DIR__ . '/../../..' . '/lib/public/Remote/Api/ICapabilitiesApi.php', @@ -833,6 +835,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OC\\Contacts\\ContactsMenu\\Entry' => __DIR__ . '/../../..' . '/lib/private/Contacts/ContactsMenu/Entry.php', 'OC\\Contacts\\ContactsMenu\\Manager' => __DIR__ . '/../../..' . '/lib/private/Contacts/ContactsMenu/Manager.php', 'OC\\Contacts\\ContactsMenu\\Providers\\EMailProvider' => __DIR__ . '/../../..' . '/lib/private/Contacts/ContactsMenu/Providers/EMailProvider.php', + 'OC\\Contacts\\ContactsMenu\\Providers\\ProfileProvider' => __DIR__ . '/../../..' . '/lib/private/Contacts/ContactsMenu/Providers/ProfileProvider.php', 'OC\\Core\\Application' => __DIR__ . '/../../..' . '/core/Application.php', 'OC\\Core\\BackgroundJobs\\BackgroundCleanupUpdaterBackupsJob' => __DIR__ . '/../../..' . '/core/BackgroundJobs/BackgroundCleanupUpdaterBackupsJob.php', 'OC\\Core\\BackgroundJobs\\CheckForUserCertificates' => __DIR__ . '/../../..' . '/core/BackgroundJobs/CheckForUserCertificates.php', @@ -953,6 +956,8 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OC\\Core\\Controller\\OCJSController' => __DIR__ . '/../../..' . '/core/Controller/OCJSController.php', 'OC\\Core\\Controller\\OCSController' => __DIR__ . '/../../..' . '/core/Controller/OCSController.php', 'OC\\Core\\Controller\\PreviewController' => __DIR__ . '/../../..' . '/core/Controller/PreviewController.php', + 'OC\\Core\\Controller\\ProfileApiController' => __DIR__ . '/../../..' . '/core/Controller/ProfileApiController.php', + 'OC\\Core\\Controller\\ProfilePageController' => __DIR__ . '/../../..' . '/core/Controller/ProfilePageController.php', 'OC\\Core\\Controller\\RecommendedAppsController' => __DIR__ . '/../../..' . '/core/Controller/RecommendedAppsController.php', 'OC\\Core\\Controller\\SearchController' => __DIR__ . '/../../..' . '/core/Controller/SearchController.php', 'OC\\Core\\Controller\\SetupController' => __DIR__ . '/../../..' . '/core/Controller/SetupController.php', @@ -969,6 +974,8 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OC\\Core\\Data\\LoginFlowV2Tokens' => __DIR__ . '/../../..' . '/core/Data/LoginFlowV2Tokens.php', 'OC\\Core\\Db\\LoginFlowV2' => __DIR__ . '/../../..' . '/core/Db/LoginFlowV2.php', 'OC\\Core\\Db\\LoginFlowV2Mapper' => __DIR__ . '/../../..' . '/core/Db/LoginFlowV2Mapper.php', + 'OC\\Core\\Db\\ProfileConfig' => __DIR__ . '/../../..' . '/core/Db/ProfileConfig.php', + 'OC\\Core\\Db\\ProfileConfigMapper' => __DIR__ . '/../../..' . '/core/Db/ProfileConfigMapper.php', 'OC\\Core\\Exception\\LoginFlowV2NotFoundException' => __DIR__ . '/../../..' . '/core/Exception/LoginFlowV2NotFoundException.php', 'OC\\Core\\Exception\\ResetPasswordException' => __DIR__ . '/../../..' . '/core/Exception/ResetPasswordException.php', 'OC\\Core\\Middleware\\TwoFactorMiddleware' => __DIR__ . '/../../..' . '/core/Middleware/TwoFactorMiddleware.php', @@ -1009,6 +1016,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OC\\Core\\Migrations\\Version22000Date20210216080825' => __DIR__ . '/../../..' . '/core/Migrations/Version22000Date20210216080825.php', 'OC\\Core\\Migrations\\Version23000Date20210721100600' => __DIR__ . '/../../..' . '/core/Migrations/Version23000Date20210721100600.php', 'OC\\Core\\Migrations\\Version23000Date20210906132259' => __DIR__ . '/../../..' . '/core/Migrations/Version23000Date20210906132259.php', + 'OC\\Core\\Migrations\\Version23000Date20210930122352' => __DIR__ . '/../../..' . '/core/Migrations/Version23000Date20210930122352.php', 'OC\\Core\\Notification\\CoreNotifier' => __DIR__ . '/../../..' . '/core/Notification/CoreNotifier.php', 'OC\\Core\\Service\\LoginFlowV2Service' => __DIR__ . '/../../..' . '/core/Service/LoginFlowV2Service.php', 'OC\\DB\\Adapter' => __DIR__ . '/../../..' . '/lib/private/DB/Adapter.php', @@ -1329,6 +1337,12 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OC\\Preview\\WatcherConnector' => __DIR__ . '/../../..' . '/lib/private/Preview/WatcherConnector.php', 'OC\\Preview\\WebP' => __DIR__ . '/../../..' . '/lib/private/Preview/WebP.php', 'OC\\Preview\\XBitmap' => __DIR__ . '/../../..' . '/lib/private/Preview/XBitmap.php', + 'OC\\Profile\\Actions\\EmailAction' => __DIR__ . '/../../..' . '/lib/private/Profile/Actions/EmailAction.php', + 'OC\\Profile\\Actions\\PhoneAction' => __DIR__ . '/../../..' . '/lib/private/Profile/Actions/PhoneAction.php', + 'OC\\Profile\\Actions\\TwitterAction' => __DIR__ . '/../../..' . '/lib/private/Profile/Actions/TwitterAction.php', + 'OC\\Profile\\Actions\\WebsiteAction' => __DIR__ . '/../../..' . '/lib/private/Profile/Actions/WebsiteAction.php', + 'OC\\Profile\\ProfileManager' => __DIR__ . '/../../..' . '/lib/private/Profile/ProfileManager.php', + 'OC\\Profile\\TProfileHelper' => __DIR__ . '/../../..' . '/lib/private/Profile/TProfileHelper.php', 'OC\\RedisFactory' => __DIR__ . '/../../..' . '/lib/private/RedisFactory.php', 'OC\\Remote\\Api\\ApiBase' => __DIR__ . '/../../..' . '/lib/private/Remote/Api/ApiBase.php', 'OC\\Remote\\Api\\ApiCollection' => __DIR__ . '/../../..' . '/lib/private/Remote/Api/ApiCollection.php', diff --git a/lib/private/AppFramework/Bootstrap/Coordinator.php b/lib/private/AppFramework/Bootstrap/Coordinator.php index 8ffe54a2575..6e05b7fdc88 100644 --- a/lib/private/AppFramework/Bootstrap/Coordinator.php +++ b/lib/private/AppFramework/Bootstrap/Coordinator.php @@ -27,10 +27,14 @@ declare(strict_types=1); * along with this program. If not, see . * */ + namespace OC\AppFramework\Bootstrap; -use OC\Support\CrashReport\Registry; +use function class_exists; +use function class_implements; +use function in_array; use OC_App; +use OC\Support\CrashReport\Registry; use OCP\AppFramework\App; use OCP\AppFramework\Bootstrap\IBootstrap; use OCP\AppFramework\QueryException; @@ -39,9 +43,6 @@ use OCP\EventDispatcher\IEventDispatcher; use OCP\IServerContainer; use Psr\Log\LoggerInterface; use Throwable; -use function class_exists; -use function class_implements; -use function in_array; class Coordinator { @@ -66,11 +67,13 @@ class Coordinator { /** @var string[] */ private $bootedApps = []; - public function __construct(IServerContainer $container, - Registry $registry, - IManager $dashboardManager, - IEventDispatcher $eventListener, - LoggerInterface $logger) { + public function __construct( + IServerContainer $container, + Registry $registry, + IManager $dashboardManager, + IEventDispatcher $eventListener, + LoggerInterface $logger + ) { $this->serverContainer = $container; $this->registry = $registry; $this->dashboardManager = $dashboardManager; diff --git a/lib/private/AppFramework/Bootstrap/RegistrationContext.php b/lib/private/AppFramework/Bootstrap/RegistrationContext.php index 8eed6a5bde4..c638af94c84 100644 --- a/lib/private/AppFramework/Bootstrap/RegistrationContext.php +++ b/lib/private/AppFramework/Bootstrap/RegistrationContext.php @@ -26,9 +26,11 @@ declare(strict_types=1); * along with this program. If not, see . * */ + namespace OC\AppFramework\Bootstrap; use Closure; +use function array_shift; use OC\Support\CrashReport\Registry; use OCP\AppFramework\App; use OCP\AppFramework\Bootstrap\IRegistrationContext; @@ -43,11 +45,11 @@ use OCP\EventDispatcher\IEventDispatcher; use OCP\Files\Template\ICustomTemplateProvider; use OCP\Http\WellKnown\IHandler; use OCP\Notification\INotifier; +use OCP\Profile\ILinkAction; use OCP\Search\IProvider; use OCP\Support\CrashReport\IReporter; use Psr\Log\LoggerInterface; use Throwable; -use function array_shift; class RegistrationContext { @@ -60,6 +62,9 @@ class RegistrationContext { /** @var ServiceRegistration[] */ private $dashboardPanels = []; + /** @var ServiceRegistration[] */ + private $profileActions = []; + /** @var ServiceFactoryRegistration[] */ private $services = []; @@ -236,6 +241,13 @@ class RegistrationContext { $class ); } + + public function registerProfileAction(string $actionClass): void { + $this->context->registerProfileAction( + $this->appId, + $actionClass + ); + } }; } @@ -315,6 +327,13 @@ class RegistrationContext { $this->calendarProviders[] = new ServiceRegistration($appId, $class); } + /** + * @psalm-param class-string $capability + */ + public function registerProfileAction(string $appId, string $actionClass): void { + $this->profileActions[] = new ServiceRegistration($appId, $actionClass); + } + /** * @param App[] $apps */ @@ -552,4 +571,11 @@ class RegistrationContext { public function getCalendarProviders(): array { return $this->calendarProviders; } + + /** + * @return ServiceRegistration[] + */ + public function getProfileActions(): array { + return $this->profileActions; + } } diff --git a/lib/private/Contacts/ContactsMenu/ActionProviderStore.php b/lib/private/Contacts/ContactsMenu/ActionProviderStore.php index a984f9d6dfb..1db99497a21 100644 --- a/lib/private/Contacts/ContactsMenu/ActionProviderStore.php +++ b/lib/private/Contacts/ContactsMenu/ActionProviderStore.php @@ -24,11 +24,13 @@ declare(strict_types=1); * along with this program. If not, see . * */ + namespace OC\Contacts\ContactsMenu; use Exception; use OC\App\AppManager; use OC\Contacts\ContactsMenu\Providers\EMailProvider; +use OC\Contacts\ContactsMenu\Providers\ProfileProvider; use OCP\AppFramework\QueryException; use OCP\Contacts\ContactsMenu\IProvider; use OCP\IServerContainer; @@ -67,7 +69,8 @@ class ActionProviderStore { try { $providers[] = $this->serverContainer->query($class); } catch (QueryException $ex) { - $this->logger->error('Could not load contacts menu action provider ' . $class, + $this->logger->error( + 'Could not load contacts menu action provider ' . $class, [ 'app' => 'core', 'exception' => $ex, @@ -85,6 +88,7 @@ class ActionProviderStore { */ private function getServerProviderClasses(): array { return [ + ProfileProvider::class, EMailProvider::class, ]; } diff --git a/lib/private/Contacts/ContactsMenu/ContactsStore.php b/lib/private/Contacts/ContactsMenu/ContactsStore.php index 31e13bbe8f2..a4a53bf8774 100644 --- a/lib/private/Contacts/ContactsMenu/ContactsStore.php +++ b/lib/private/Contacts/ContactsMenu/ContactsStore.php @@ -1,4 +1,5 @@ * @copyright 2017 Lukas Reschke @@ -27,18 +28,26 @@ * along with this program. If not, see . * */ + namespace OC\Contacts\ContactsMenu; use OC\KnownUser\KnownUserService; +use OCP\Accounts\IAccountManager; use OCP\Contacts\ContactsMenu\IContactsStore; use OCP\Contacts\ContactsMenu\IEntry; use OCP\Contacts\IManager; use OCP\IConfig; use OCP\IGroupManager; +use OCP\IURLGenerator; use OCP\IUser; use OCP\IUserManager; +use OCP\L10N\IFactory as IL10NFactory; class ContactsStore implements IContactsStore { + use \OC\Profile\TProfileHelper; + + /** @var IAccountManager */ + private $accountManager; /** @var IManager */ private $contactsManager; @@ -49,22 +58,36 @@ class ContactsStore implements IContactsStore { /** @var IUserManager */ private $userManager; + /** @var IURLGenerator */ + private $urlGenerator; + /** @var IGroupManager */ private $groupManager; /** @var KnownUserService */ private $knownUserService; - public function __construct(IManager $contactsManager, - IConfig $config, - IUserManager $userManager, - IGroupManager $groupManager, - KnownUserService $knownUserService) { + /** @var IL10NFactory */ + private $l10nFactory; + + public function __construct( + IAccountManager $accountManager, + IManager $contactsManager, + IConfig $config, + IUserManager $userManager, + IURLGenerator $urlGenerator, + IGroupManager $groupManager, + KnownUserService $knownUserService, + IL10NFactory $l10nFactory + ) { + $this->accountManager = $accountManager; $this->contactsManager = $contactsManager; $this->config = $config; $this->userManager = $userManager; + $this->urlGenerator = $urlGenerator; $this->groupManager = $groupManager; $this->knownUserService = $knownUserService; + $this->l10nFactory = $l10nFactory; } /** @@ -116,9 +139,11 @@ class ContactsStore implements IContactsStore { * @param string $filter * @return Entry[] the filtered contacts */ - private function filterContacts(IUser $self, - array $entries, - $filter) { + private function filterContacts( + IUser $self, + array $entries, + $filter + ) { $disallowEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') !== 'yes'; $restrictEnumerationGroup = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes'; $restrictEnumerationPhone = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_phone', 'no') === 'yes'; @@ -302,6 +327,19 @@ class ContactsStore implements IContactsStore { } } + // Provide profile parameters for core/src/OC/contactsmenu/contact.handlebars template + if (isset($contact['UID']) && isset($contact['FN'])) { + $targetUserId = $contact['UID']; + $user = $this->userManager->get($targetUserId); + if (!empty($user)) { + $account = $this->accountManager->getAccount($user); + if ($this->isProfileEnabled($account)) { + $entry->setProfileTitle($this->l10nFactory->get('core')->t('View profile')); + $entry->setProfileUrl($this->urlGenerator->linkToRouteAbsolute('core.ProfilePage.index', ['targetUserId' => $targetUserId])); + } + } + } + // Attach all other properties to the entry too because some // providers might make use of it. $entry->setProperties($contact); diff --git a/lib/private/Contacts/ContactsMenu/Entry.php b/lib/private/Contacts/ContactsMenu/Entry.php index aea71df2968..915a0434cc8 100644 --- a/lib/private/Contacts/ContactsMenu/Entry.php +++ b/lib/private/Contacts/ContactsMenu/Entry.php @@ -24,6 +24,7 @@ declare(strict_types=1); * along with this program. If not, see . * */ + namespace OC\Contacts\ContactsMenu; use OCP\Contacts\ContactsMenu\IAction; @@ -43,6 +44,12 @@ class Entry implements IEntry { /** @var string|null */ private $avatar; + /** @var string|null */ + private $profileTitle; + + /** @var string|null */ + private $profileUrl; + /** @var IAction[] */ private $actions = []; @@ -98,6 +105,34 @@ class Entry implements IEntry { return $this->avatar; } + /** + * @param string $profileTitle + */ + public function setProfileTitle(string $profileTitle): void { + $this->profileTitle = $profileTitle; + } + + /** + * @return string + */ + public function getProfileTitle(): ?string { + return $this->profileTitle; + } + + /** + * @param string $profileUrl + */ + public function setProfileUrl(string $profileUrl): void { + $this->profileUrl = $profileUrl; + } + + /** + * @return string + */ + public function getProfileUrl(): ?string { + return $this->profileUrl; + } + /** * @param IAction $action */ @@ -166,6 +201,8 @@ class Entry implements IEntry { 'actions' => $otherActions, 'lastMessage' => '', 'emailAddresses' => $this->getEMailAddresses(), + 'profileTitle' => $this->profileTitle, + 'profileUrl' => $this->profileUrl, ]; } } diff --git a/lib/private/Contacts/ContactsMenu/Providers/ProfileProvider.php b/lib/private/Contacts/ContactsMenu/Providers/ProfileProvider.php new file mode 100644 index 00000000000..4882c0ac883 --- /dev/null +++ b/lib/private/Contacts/ContactsMenu/Providers/ProfileProvider.php @@ -0,0 +1,93 @@ + + * + * @author Christoph Wurst + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Contacts\ContactsMenu\Providers; + +use OCP\Accounts\IAccountManager; +use OCP\Contacts\ContactsMenu\IActionFactory; +use OCP\Contacts\ContactsMenu\IEntry; +use OCP\Contacts\ContactsMenu\IProvider; +use OCP\IURLGenerator; +use OCP\IUserManager; +use OCP\L10N\IFactory as IL10NFactory; + +class ProfileProvider implements IProvider { + use \OC\Profile\TProfileHelper; + + /** @var IAccountManager */ + private $accountManager; + + /** @var IActionFactory */ + private $actionFactory; + + /** @var IL10NFactory */ + private $l10nFactory; + + /** @var IURLGenerator */ + private $urlGenerator; + + /** @var IUserManager */ + private $userManager; + + /** + * @param IAccountManager $accountManager + * @param IActionFactory $actionFactory + * @param IL10NFactory $l10nFactory + * @param IURLGenerator $urlGenerator + * @param IUserManager $userManager + */ + public function __construct( + IAccountManager $accountManager, + IActionFactory $actionFactory, + IL10NFactory $l10nFactory, + IURLGenerator $urlGenerator, + IUserManager $userManager + ) { + $this->accountManager = $accountManager; + $this->actionFactory = $actionFactory; + $this->l10nFactory = $l10nFactory; + $this->urlGenerator = $urlGenerator; + $this->userManager = $userManager; + } + + /** + * @param IEntry $entry + */ + public function process(IEntry $entry) { + $targetUserId = $entry->getProperty('UID'); + $targetUser = $this->userManager->get($targetUserId); + if (!empty($targetUser)) { + $account = $this->accountManager->getAccount($targetUser); + if ($this->isProfileEnabled($account)) { + $iconUrl = $this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'actions/profile.svg')); + $profileActionText = $this->l10nFactory->get('core')->t('View profile'); + $profileUrl = $this->urlGenerator->linkToRouteAbsolute('core.ProfilePage.index', ['targetUserId' => $targetUserId]); + $action = $this->actionFactory->newLinkAction($iconUrl, $profileActionText, $profileUrl); + // Set highest priority (by descending order), other actions have the default priority 10 as defined in lib/private/Contacts/ContactsMenu/Actions/LinkAction.php + $action->setPriority(20); + $entry->addAction($action); + } + } + } +} diff --git a/lib/private/Profile/Actions/EmailAction.php b/lib/private/Profile/Actions/EmailAction.php new file mode 100644 index 00000000000..1eef1236630 --- /dev/null +++ b/lib/private/Profile/Actions/EmailAction.php @@ -0,0 +1,94 @@ + + * + * @author Christopher Ng + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Profile\Actions; + +use OCP\Accounts\IAccountManager; +use OCP\IURLGenerator; +use OCP\IUser; +use OCP\L10N\IFactory; +use OCP\Profile\ILinkAction; + +class EmailAction implements ILinkAction { + + /** @var string */ + private $value; + + /** @var IAccountManager */ + private $accountManager; + + /** @var IFactory */ + private $l10nFactory; + + /** @var IUrlGenerator */ + private $urlGenerator; + + public function __construct( + IAccountManager $accountManager, + IFactory $l10nFactory, + IURLGenerator $urlGenerator + ) { + $this->accountManager = $accountManager; + $this->l10nFactory = $l10nFactory; + $this->urlGenerator = $urlGenerator; + } + + public function preload(IUser $targetUser): void { + $account = $this->accountManager->getAccount($targetUser); + $this->value = $account->getProperty(IAccountManager::PROPERTY_EMAIL)->getValue(); + } + + public function getAppId(): string { + return 'core'; + } + + public function getId(): string { + return IAccountManager::PROPERTY_EMAIL; + } + + public function getDisplayId(): string { + return $this->l10nFactory->get('core')->t('Email'); + } + + public function getTitle(): string { + return $this->l10nFactory->get('core')->t('Mail %s', [$this->value]); + } + + public function getPriority(): int { + return 20; + } + + public function getIcon(): string { + return $this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'actions/mail.svg')); + } + + public function getTarget(): ?string { + if (empty($this->value)) { + return null; + } + return 'mailto:' . $this->value; + } +} diff --git a/lib/private/Profile/Actions/PhoneAction.php b/lib/private/Profile/Actions/PhoneAction.php new file mode 100644 index 00000000000..df0e30cd277 --- /dev/null +++ b/lib/private/Profile/Actions/PhoneAction.php @@ -0,0 +1,94 @@ + + * + * @author Christopher Ng + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Profile\Actions; + +use OCP\Accounts\IAccountManager; +use OCP\IURLGenerator; +use OCP\IUser; +use OCP\L10N\IFactory; +use OCP\Profile\ILinkAction; + +class PhoneAction implements ILinkAction { + + /** @var string */ + private $value; + + /** @var IAccountManager */ + private $accountManager; + + /** @var IFactory */ + private $l10nFactory; + + /** @var IUrlGenerator */ + private $urlGenerator; + + public function __construct( + IAccountManager $accountManager, + IFactory $l10nFactory, + IURLGenerator $urlGenerator + ) { + $this->accountManager = $accountManager; + $this->l10nFactory = $l10nFactory; + $this->urlGenerator = $urlGenerator; + } + + public function preload(IUser $targetUser): void { + $account = $this->accountManager->getAccount($targetUser); + $this->value = $account->getProperty(IAccountManager::PROPERTY_PHONE)->getValue(); + } + + public function getAppId(): string { + return 'core'; + } + + public function getId(): string { + return IAccountManager::PROPERTY_PHONE; + } + + public function getDisplayId(): string { + return $this->l10nFactory->get('core')->t('Phone'); + } + + public function getTitle(): string { + return $this->l10nFactory->get('core')->t('Call %s', [$this->value]); + } + + public function getPriority(): int { + return 30; + } + + public function getIcon(): string { + return $this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'actions/phone.svg')); + } + + public function getTarget(): ?string { + if (empty($this->value)) { + return null; + } + return 'tel:' . $this->value; + } +} diff --git a/lib/private/Profile/Actions/TwitterAction.php b/lib/private/Profile/Actions/TwitterAction.php new file mode 100644 index 00000000000..3dcfa8aaf12 --- /dev/null +++ b/lib/private/Profile/Actions/TwitterAction.php @@ -0,0 +1,97 @@ + + * + * @author Christopher Ng + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Profile\Actions; + +use function Safe\substr; +use OCP\Accounts\IAccountManager; +use OCP\IURLGenerator; +use OCP\IUser; +use OCP\L10N\IFactory; +use OCP\Profile\ILinkAction; + +class TwitterAction implements ILinkAction { + + /** @var string */ + private $value; + + /** @var IAccountManager */ + private $accountManager; + + /** @var IFactory */ + private $l10nFactory; + + /** @var IUrlGenerator */ + private $urlGenerator; + + public function __construct( + IAccountManager $accountManager, + IFactory $l10nFactory, + IURLGenerator $urlGenerator + ) { + $this->accountManager = $accountManager; + $this->l10nFactory = $l10nFactory; + $this->urlGenerator = $urlGenerator; + } + + public function preload(IUser $targetUser): void { + $account = $this->accountManager->getAccount($targetUser); + $this->value = $account->getProperty(IAccountManager::PROPERTY_TWITTER)->getValue(); + } + + public function getAppId(): string { + return 'core'; + } + + public function getId(): string { + return IAccountManager::PROPERTY_TWITTER; + } + + public function getDisplayId(): string { + return $this->l10nFactory->get('core')->t('Twitter'); + } + + public function getTitle(): string { + $displayUsername = $this->value[0] === '@' ? $this->value : '@' . $this->value; + return $this->l10nFactory->get('core')->t('View %s on Twitter', [$displayUsername]); + } + + public function getPriority(): int { + return 50; + } + + public function getIcon(): string { + return $this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'actions/twitter.svg')); + } + + public function getTarget(): ?string { + if (empty($this->value)) { + return null; + } + $username = $this->value[0] === '@' ? substr($this->value, 1) : $this->value; + return 'https://twitter.com/' . $username; + } +} diff --git a/lib/private/Profile/Actions/WebsiteAction.php b/lib/private/Profile/Actions/WebsiteAction.php new file mode 100644 index 00000000000..ea1daeee20e --- /dev/null +++ b/lib/private/Profile/Actions/WebsiteAction.php @@ -0,0 +1,94 @@ + + * + * @author Christopher Ng + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Profile\Actions; + +use OCP\Accounts\IAccountManager; +use OCP\IURLGenerator; +use OCP\IUser; +use OCP\L10N\IFactory; +use OCP\Profile\ILinkAction; + +class WebsiteAction implements ILinkAction { + + /** @var string */ + private $value; + + /** @var IAccountManager */ + private $accountManager; + + /** @var IFactory */ + private $l10nFactory; + + /** @var IUrlGenerator */ + private $urlGenerator; + + public function __construct( + IAccountManager $accountManager, + IFactory $l10nFactory, + IURLGenerator $urlGenerator + ) { + $this->accountManager = $accountManager; + $this->l10nFactory = $l10nFactory; + $this->urlGenerator = $urlGenerator; + } + + public function preload(IUser $targetUser): void { + $account = $this->accountManager->getAccount($targetUser); + $this->value = $account->getProperty(IAccountManager::PROPERTY_WEBSITE)->getValue(); + } + + public function getAppId(): string { + return 'core'; + } + + public function getId(): string { + return IAccountManager::PROPERTY_WEBSITE; + } + + public function getDisplayId(): string { + return $this->l10nFactory->get('core')->t('Website'); + } + + public function getTitle(): string { + return $this->l10nFactory->get('core')->t('Visit %s', [$this->value]); + } + + public function getPriority(): int { + return 40; + } + + public function getIcon(): string { + return $this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'actions/timezone.svg')); + } + + public function getTarget(): ?string { + if (empty($this->value)) { + return null; + } + return $this->value; + } +} diff --git a/lib/private/Profile/ProfileManager.php b/lib/private/Profile/ProfileManager.php new file mode 100644 index 00000000000..6a38f4ae16d --- /dev/null +++ b/lib/private/Profile/ProfileManager.php @@ -0,0 +1,333 @@ + + * + * @author Christopher Ng + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Profile; + +use function Safe\usort; + +use OC\AppFramework\Bootstrap\Coordinator; +use OC\Core\Db\ProfileConfig; +use OC\Core\Db\ProfileConfigMapper; +use OC\KnownUser\KnownUserService; +use OC\Profile\Actions\EmailAction; +use OC\Profile\Actions\PhoneAction; +use OC\Profile\Actions\TwitterAction; +use OC\Profile\Actions\WebsiteAction; +use OCP\Accounts\IAccountManager; +use OCP\Accounts\PropertyDoesNotExistException; +use OCP\App\IAppManager; +use OCP\AppFramework\Db\DoesNotExistException; +use OCP\IUser; +use OCP\L10N\IFactory; +use OCP\Profile\ILinkAction; +use Psr\Container\ContainerInterface; +use Psr\Log\LoggerInterface; + +/** + * @inheritDoc + */ +class ProfileManager { + + /** @var IAccountManager */ + private $accountManager; + + /** @var IAppManager */ + private $appManager; + + /** @var ProfileConfigMapper */ + private $configMapper; + + /** @var ContainerInterface */ + private $container; + + /** @var KnownUserService */ + private $knownUserService; + + /** @var IFactory */ + private $l10nFactory; + + /** @var LoggerInterface */ + private $logger; + + /** @var Coordinator */ + private $coordinator; + + /** @var ILinkAction[] */ + private $actions = []; + + private const ACCOUNT_PROPERTY_ACTIONS = [ + EmailAction::class, + PhoneAction::class, + WebsiteAction::class, + TwitterAction::class, + ]; + + /** + * Array of account properties displayed on the profile + */ + private const PROFILE_PROPERTIES = [ + IAccountManager::PROPERTY_ADDRESS, + IAccountManager::PROPERTY_BIOGRAPHY, + IAccountManager::PROPERTY_DISPLAYNAME, + IAccountManager::PROPERTY_HEADLINE, + IAccountManager::PROPERTY_ORGANISATION, + IAccountManager::PROPERTY_ROLE, + ]; + + public function __construct( + IAccountManager $accountManager, + IAppManager $appManager, + ProfileConfigMapper $configMapper, + ContainerInterface $container, + KnownUserService $knownUserService, + IFactory $l10nFactory, + LoggerInterface $logger, + Coordinator $coordinator + ) { + $this->accountManager = $accountManager; + $this->appManager = $appManager; + $this->configMapper = $configMapper; + $this->container = $container; + $this->knownUserService = $knownUserService; + $this->l10nFactory = $l10nFactory; + $this->logger = $logger; + $this->coordinator = $coordinator; + } + + /** + * Register an action for the user + */ + private function registerAction(IUser $targetUser, ?IUser $visitingUser, ILinkAction $action): void { + $action->preload($targetUser); + + if ($action->getTarget() === null) { + // Actions without a target are not registered + return; + } + + if (isset($this->actions[$action->getId()])) { + $this->logger->error('Cannot register duplicate action: ' . $action->getId()); + return; + } + + if ($action->getAppId() !== 'core') { + if (!$this->appManager->isEnabledForUser($action->getAppId(), $targetUser)) { + $this->logger->notice('App: ' . $action->getAppId() . ' cannot register actions as it is not enabled for the user: ' . $targetUser->getUID()); + return; + } + if ($visitingUser === null) { + $this->logger->notice('App: ' . $action->getAppId() . ' cannot register actions as it is not available to non logged in users'); + return; + } + if (!$this->appManager->isEnabledForUser($action->getAppId(), $visitingUser)) { + $this->logger->notice('App: ' . $action->getAppId() . ' cannot register actions as it is not enabled for the visiting user: ' . $visitingUser->getUID()); + return; + } + } + + // Add action to associative array of actions + $this->actions[$action->getId()] = $action; + } + + /** + * Return an array of registered profile actions for the user + * + * @return ILinkAction[] + */ + private function getActions(IUser $targetUser, ?IUser $visitingUser): array { + $context = $this->coordinator->getRegistrationContext(); + if ($context === null) { + return []; + } + + foreach (self::ACCOUNT_PROPERTY_ACTIONS as $actionClass) { + /** @var ILinkAction $provider */ + $provider = $this->container->get($actionClass); + $this->registerAction($targetUser, $visitingUser, $provider); + } + + foreach ($context->getProfileActions() as $registration) { + /** @var ILinkAction $provider */ + $provider = $this->container->get($registration->getService()); + $this->registerAction($targetUser, $visitingUser, $provider); + } + + $actionsClone = $this->actions; + // Sort associative array into indexed array in ascending order of priority + usort($actionsClone, function (ILinkAction $a, ILinkAction $b) { + return $a->getPriority() === $b->getPriority() ? 0 : ($a->getPriority() < $b->getPriority() ? -1 : 1); + }); + return $actionsClone; + } + + /** + * Return whether the profile parameter is visible to the visiting user + */ + private function isParameterVisible(IUser $targetUser, ?IUser $visitingUser, string $paramId): bool { + try { + $account = $this->accountManager->getAccount($targetUser); + $scope = $account->getProperty($paramId)->getScope(); + } catch (PropertyDoesNotExistException $e) { + // Allow the exception as not all profile parameters are account properties + } + + $visibility = $this->getProfileConfig($targetUser, $visitingUser)[$paramId]['visibility']; + // Handle profile visibility and account property scope + switch ($visibility) { + case ProfileConfig::VISIBILITY_HIDE: + return false; + case ProfileConfig::VISIBILITY_SHOW_USERS_ONLY: + if (!empty($scope)) { + switch ($scope) { + case IAccountManager::SCOPE_PRIVATE: + return $visitingUser !== null && $this->knownUserService->isKnownToUser($targetUser->getUID(), $visitingUser->getUID()); + case IAccountManager::SCOPE_LOCAL: + case IAccountManager::SCOPE_FEDERATED: + case IAccountManager::SCOPE_PUBLISHED: + return $visitingUser !== null; + default: + return false; + } + } + return $visitingUser !== null; + case ProfileConfig::VISIBILITY_SHOW: + if (!empty($scope)) { + switch ($scope) { + case IAccountManager::SCOPE_PRIVATE: + return $visitingUser !== null && $this->knownUserService->isKnownToUser($targetUser->getUID(), $visitingUser->getUID()); + case IAccountManager::SCOPE_LOCAL: + case IAccountManager::SCOPE_FEDERATED: + case IAccountManager::SCOPE_PUBLISHED: + return true; + default: + return false; + } + } + return true; + default: + return false; + } + } + + /** + * @inheritDoc + */ + public function getProfileParams(IUser $targetUser, ?IUser $visitingUser): array { + $account = $this->accountManager->getAccount($targetUser); + // Initialize associative array of profile parameters + $profileParameters = [ + 'userId' => $account->getUser()->getUID(), + ]; + + // Add account properties + foreach (self::PROFILE_PROPERTIES as $property) { + $profileParameters[$property] = + $this->isParameterVisible($targetUser, $visitingUser, $property) + // Explicitly set to null when value is empty string + ? ($account->getProperty($property)->getValue() ?: null) + : null; + } + + // Add avatar visibility + $profileParameters['isUserAvatarVisible'] = $this->isParameterVisible($targetUser, $visitingUser, IAccountManager::PROPERTY_AVATAR); + + // Add actions + $profileParameters['actions'] = array_map( + function (ILinkAction $action) { + return [ + 'id' => $action->getId(), + 'icon' => $action->getIcon(), + 'title' => $action->getTitle(), + 'target' => $action->getTarget(), + ]; + }, + // This is needed to reindex the array after filtering + array_values( + array_filter( + $this->getActions($targetUser, $visitingUser), + function (ILinkAction $action) use ($targetUser, $visitingUser) { + return $this->isParameterVisible($targetUser, $visitingUser, $action->getId()); + } + ), + ) + ); + + return $profileParameters; + } + + /** + * @inheritDoc + */ + public function getProfileConfig(IUser $targetUser, ?IUser $visitingUser): array { + try { + $configArray = $this->configMapper->getArray($targetUser->getUID()); + } catch (DoesNotExistException $e) { + $config = new ProfileConfig(); + $config->setUserId($targetUser->getUID()); + + // Map of account properties to display IDs + $propertyDisplayMap = [ + IAccountManager::PROPERTY_ADDRESS => $this->l10nFactory->get('core')->t('Address'), + IAccountManager::PROPERTY_AVATAR => $this->l10nFactory->get('core')->t('Avatar'), + IAccountManager::PROPERTY_BIOGRAPHY => $this->l10nFactory->get('core')->t('About'), + IAccountManager::PROPERTY_DISPLAYNAME => $this->l10nFactory->get('core')->t('Full name'), + IAccountManager::PROPERTY_HEADLINE => $this->l10nFactory->get('core')->t('Headline'), + IAccountManager::PROPERTY_ORGANISATION => $this->l10nFactory->get('core')->t('Organisation'), + IAccountManager::PROPERTY_ROLE => $this->l10nFactory->get('core')->t('Role'), + IAccountManager::PROPERTY_EMAIL => $this->l10nFactory->get('core')->t('Email'), + IAccountManager::PROPERTY_PHONE => $this->l10nFactory->get('core')->t('Phone'), + IAccountManager::PROPERTY_TWITTER => $this->l10nFactory->get('core')->t('Twitter'), + IAccountManager::PROPERTY_WEBSITE => $this->l10nFactory->get('core')->t('Website'), + ]; + + // Contruct the default config for account properties + $propertiesConfig = []; + foreach ($propertyDisplayMap as $property => $displayId) { + $propertiesConfig[$property] = [ + 'displayId' => $displayId, + 'visibility' => ProfileConfig::DEFAULT_PROPERTY_VISIBILITY[$property] ?: ProfileConfig::DEFAULT_VISIBILITY, + ]; + } + + // Contruct the default config for actions + $actionsConfig = []; + /** @var ILinkAction $action */ + foreach ($this->getActions($targetUser, $visitingUser) as $action) { + $actionsConfig[$action->getId()] = [ + 'displayId' => $action->getDisplayId(), + 'visibility' => ProfileConfig::DEFAULT_VISIBILITY, + ]; + } + + // Set the default config + $config->setConfigArray(array_merge($propertiesConfig, $actionsConfig)); + $this->configMapper->insert($config); + $configArray = $config->getConfigArray(); + } + + return $configArray; + } +} diff --git a/lib/private/Profile/TProfileHelper.php b/lib/private/Profile/TProfileHelper.php new file mode 100644 index 00000000000..0d4b5c6286e --- /dev/null +++ b/lib/private/Profile/TProfileHelper.php @@ -0,0 +1,46 @@ + + * + * @author Christopher Ng + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Profile; + +use OCP\Accounts\IAccount; +use OCP\Accounts\IAccountManager; + +trait TProfileHelper { + + /** + * Returns whether the profile is enabled for the account + * + * @since 23.0.0 + */ + protected function isProfileEnabled(IAccount $account): ?bool { + return filter_var( + $account->getProperty(IAccountManager::PROPERTY_PROFILE_ENABLED)->getValue(), + FILTER_VALIDATE_BOOLEAN, + FILTER_NULL_ON_FAILURE, + ); + } +} diff --git a/lib/private/Setup.php b/lib/private/Setup.php index c24d417f8cf..a7f0f190fa2 100644 --- a/lib/private/Setup.php +++ b/lib/private/Setup.php @@ -425,6 +425,9 @@ class Setup { //and we are done $config->setSystemValue('installed', true); + $bootstrapCoordinator = \OC::$server->query(\OC\AppFramework\Bootstrap\Coordinator::class); + $bootstrapCoordinator->runInitialRegistration(); + // Create a session token for the newly created user // The token provider requires a working db, so it's not injected on setup /* @var $userSession User\Session */ diff --git a/lib/private/Template/SCSSCacher.php b/lib/private/Template/SCSSCacher.php index 0543427f997..c1bd556de60 100644 --- a/lib/private/Template/SCSSCacher.php +++ b/lib/private/Template/SCSSCacher.php @@ -32,6 +32,7 @@ namespace OC\Template; use OC\AppConfig; use OC\Files\AppData\Factory; use OC\Memcache\NullCache; +use OCA\Theming\ThemingDefaults; use OCP\AppFramework\Utility\ITimeFactory; use OCP\Files\IAppData; use OCP\Files\NotFoundException; @@ -62,7 +63,7 @@ class SCSSCacher { /** @var IConfig */ protected $config; - /** @var \OC_Defaults */ + /** @var ThemingDefaults */ private $defaults; /** @var string */ @@ -96,7 +97,7 @@ class SCSSCacher { * @param Factory $appDataFactory * @param IURLGenerator $urlGenerator * @param IConfig $config - * @param \OC_Defaults $defaults + * @param ThemingDefaults $defaults * @param string $serverRoot * @param ICacheFactory $cacheFactory * @param IconsCacher $iconsCacher @@ -106,7 +107,7 @@ class SCSSCacher { Factory $appDataFactory, IURLGenerator $urlGenerator, IConfig $config, - \OC_Defaults $defaults, + ThemingDefaults $defaults, $serverRoot, ICacheFactory $cacheFactory, IconsCacher $iconsCacher, @@ -406,7 +407,7 @@ class SCSSCacher { } /** - * @return string SCSS code for variables from OC_Defaults + * @return string SCSS code for variables from ThemingDefaults */ private function getInjectedVariables(string $cache = ''): string { if ($this->injectedVariables !== null) { diff --git a/lib/public/Accounts/IAccountManager.php b/lib/public/Accounts/IAccountManager.php index 9418e07ec97..ae5f6b1e542 100644 --- a/lib/public/Accounts/IAccountManager.php +++ b/lib/public/Accounts/IAccountManager.php @@ -26,6 +26,7 @@ declare(strict_types=1); * along with this program. If not, see . * */ + namespace OCP\Accounts; use OCP\IUser; @@ -96,6 +97,31 @@ interface IAccountManager { public const PROPERTY_ADDRESS = 'address'; public const PROPERTY_TWITTER = 'twitter'; + /** + * @since 23.0.0 + */ + public const PROPERTY_ORGANISATION = 'organisation'; + + /** + * @since 23.0.0 + */ + public const PROPERTY_ROLE = 'role'; + + /** + * @since 23.0.0 + */ + public const PROPERTY_HEADLINE = 'headline'; + + /** + * @since 23.0.0 + */ + public const PROPERTY_BIOGRAPHY = 'biography'; + + /** + * @since 23.0.0 + */ + public const PROPERTY_PROFILE_ENABLED = 'profile_enabled'; + public const COLLECTION_EMAIL = 'additional_mail'; public const NOT_VERIFIED = '0'; diff --git a/lib/public/AppFramework/Bootstrap/IRegistrationContext.php b/lib/public/AppFramework/Bootstrap/IRegistrationContext.php index d396c619923..f75cdb18cfb 100644 --- a/lib/public/AppFramework/Bootstrap/IRegistrationContext.php +++ b/lib/public/AppFramework/Bootstrap/IRegistrationContext.php @@ -26,6 +26,7 @@ declare(strict_types=1); * along with this program. If not, see . * */ + namespace OCP\AppFramework\Bootstrap; use OCP\AppFramework\IAppContainer; @@ -74,6 +75,7 @@ interface IRegistrationContext { * @since 20.0.0 */ public function registerDashboardWidget(string $widgetClass): void; + /** * Register a service * @@ -237,4 +239,15 @@ interface IRegistrationContext { * @since 23.0.0 */ public function registerCalendarProvider(string $class): void; + + /** + * Register an implementation of \OCP\Profile\ILinkAction that + * will handle the implementation of a profile action + * + * @param string $actionClass + * @psalm-param class-string<\OCP\Profile\ILinkAction> $actionClass + * @return void + * @since 23.0.0 + */ + public function registerProfileAction(string $actionClass): void; } diff --git a/lib/public/Profile/ILinkAction.php b/lib/public/Profile/ILinkAction.php new file mode 100644 index 00000000000..67e69a73d9c --- /dev/null +++ b/lib/public/Profile/ILinkAction.php @@ -0,0 +1,115 @@ + + * + * @author Christopher Ng + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCP\Profile; + +use OCP\IUser; + +/** + * @since 23.0.0 + */ +interface ILinkAction { + + /** + * Preload the user specific value required by the action + * + * e.g. the email is loaded for the email action and the userId for the Talk action + * + * @since 23.0.0 + */ + public function preload(IUser $targetUser): void; + + /** + * Returns the app ID of the action + * + * e.g. 'spreed' + * + * @since 23.0.0 + */ + public function getAppId(): string; + + /** + * Returns the unique ID of the action + * + * *For account properties this is the constant defined in lib/public/Accounts/IAccountManager.php* + * + * e.g. 'email' + * + * @since 23.0.0 + */ + public function getId(): string; + + /** + * Returns the translated unique display ID of the action + * + * Should be something short and descriptive of the action + * as this is seen by the end-user when configuring actions + * + * e.g. 'Email' + * + * @since 23.0.0 + */ + public function getDisplayId(): string; + + /** + * Returns the translated title + * + * e.g. 'Mail user@domain.com' + * + * Use the L10N service to translate it + * + * @since 23.0.0 + */ + public function getTitle(): string; + + /** + * Returns the priority + * + * *Actions are sorted in ascending order* + * + * e.g. 60 + * + * @since 23.0.0 + */ + public function getPriority(): int; + + /** + * Returns the URL link to the 16*16 SVG icon + * + * @since 23.0.0 + */ + public function getIcon(): string; + + /** + * Returns the target of the action, + * if null is returned the action won't be registered + * + * e.g. 'mailto:user@domain.com' + * + * @since 23.0.0 + */ + public function getTarget(): ?string; +} diff --git a/lib/public/Profile/ParameterDoesNotExistException.php b/lib/public/Profile/ParameterDoesNotExistException.php new file mode 100644 index 00000000000..543a8edc17a --- /dev/null +++ b/lib/public/Profile/ParameterDoesNotExistException.php @@ -0,0 +1,40 @@ + + * + * @author Christopher Ng + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCP\Profile; + +/** + * @since 23.0.0 + */ +class ParameterDoesNotExistException extends \Exception { + + /** + * @since 23.0.0 + */ + public function __construct($parameter) { + parent::__construct("Parameter $parameter does not exist."); + } +} diff --git a/tests/acceptance/features/header.feature b/tests/acceptance/features/header.feature index 4a8b3cea417..9931bc67436 100644 --- a/tests/acceptance/features/header.feature +++ b/tests/acceptance/features/header.feature @@ -5,7 +5,7 @@ Feature: header Given I am logged in as the admin When I open the Settings menu Then I see that the Settings menu is shown - And I see that the Settings menu has only 6 items + And I see that the Settings menu has only 7 items And I see that the "Set status" item in the Settings menu is shown And I see that the "Settings" item in the Settings menu is shown And I see that the "Apps" item in the Settings menu is shown @@ -17,7 +17,7 @@ Feature: header Given I am logged in When I open the Settings menu Then I see that the Settings menu is shown - And I see that the Settings menu has only 4 items + And I see that the Settings menu has only 5 items And I see that the "Set status" item in the Settings menu is shown And I see that the "Settings" item in the Settings menu is shown And I see that the "Help" item in the Settings menu is shown diff --git a/tests/lib/Contacts/ContactsMenu/ActionProviderStoreTest.php b/tests/lib/Contacts/ContactsMenu/ActionProviderStoreTest.php index 301c55518f6..a3557d7cda6 100644 --- a/tests/lib/Contacts/ContactsMenu/ActionProviderStoreTest.php +++ b/tests/lib/Contacts/ContactsMenu/ActionProviderStoreTest.php @@ -27,6 +27,7 @@ namespace Tests\Contacts\ContactsMenu; use OC\App\AppManager; use OC\Contacts\ContactsMenu\ActionProviderStore; use OC\Contacts\ContactsMenu\Providers\EMailProvider; +use OC\Contacts\ContactsMenu\Providers\ProfileProvider; use OCP\App\IAppManager; use OCP\AppFramework\QueryException; use OCP\Contacts\ContactsMenu\IProvider; @@ -61,8 +62,9 @@ class ActionProviderStoreTest extends TestCase { public function testGetProviders() { $user = $this->createMock(IUser::class); - $provider1 = $this->createMock(EMailProvider::class); - $provider2 = $this->createMock(IProvider::class); + $provider1 = $this->createMock(ProfileProvider::class); + $provider2 = $this->createMock(EMailProvider::class); + $provider3 = $this->createMock(IProvider::class); $this->appManager->expects($this->once()) ->method('getEnabledAppsForUser') @@ -76,22 +78,25 @@ class ActionProviderStoreTest extends TestCase { 'OCA\Contacts\Provider1', ], ]); - $this->serverContainer->expects($this->exactly(2)) + $this->serverContainer->expects($this->exactly(3)) ->method('query') ->willReturnMap([ - [EMailProvider::class, true, $provider1], - ['OCA\Contacts\Provider1', true, $provider2] + [ProfileProvider::class, true, $provider1], + [EMailProvider::class, true, $provider2], + ['OCA\Contacts\Provider1', true, $provider3] ]); $providers = $this->actionProviderStore->getProviders($user); - $this->assertCount(2, $providers); - $this->assertInstanceOf(EMailProvider::class, $providers[0]); + $this->assertCount(3, $providers); + $this->assertInstanceOf(ProfileProvider::class, $providers[0]); + $this->assertInstanceOf(EMailProvider::class, $providers[1]); } public function testGetProvidersOfAppWithIncompleInfo() { $user = $this->createMock(IUser::class); - $provider1 = $this->createMock(EMailProvider::class); + $provider1 = $this->createMock(ProfileProvider::class); + $provider2 = $this->createMock(EMailProvider::class); $this->appManager->expects($this->once()) ->method('getEnabledAppsForUser') @@ -101,16 +106,18 @@ class ActionProviderStoreTest extends TestCase { ->method('getAppInfo') ->with('contacts') ->willReturn([/* Empty info.xml */]); - $this->serverContainer->expects($this->once()) + $this->serverContainer->expects($this->exactly(2)) ->method('query') ->willReturnMap([ - [EMailProvider::class, true, $provider1], + [ProfileProvider::class, true, $provider1], + [EMailProvider::class, true, $provider2], ]); $providers = $this->actionProviderStore->getProviders($user); - $this->assertCount(1, $providers); - $this->assertInstanceOf(EMailProvider::class, $providers[0]); + $this->assertCount(2, $providers); + $this->assertInstanceOf(ProfileProvider::class, $providers[0]); + $this->assertInstanceOf(EMailProvider::class, $providers[1]); } diff --git a/tests/lib/Contacts/ContactsMenu/ContactsStoreTest.php b/tests/lib/Contacts/ContactsMenu/ContactsStoreTest.php index ad201d86a2a..13cc7575f43 100644 --- a/tests/lib/Contacts/ContactsMenu/ContactsStoreTest.php +++ b/tests/lib/Contacts/ContactsMenu/ContactsStoreTest.php @@ -27,42 +27,57 @@ namespace Tests\Contacts\ContactsMenu; use OC\Contacts\ContactsMenu\ContactsStore; use OC\KnownUser\KnownUserService; +use OCP\Accounts\IAccountManager; use OCP\Contacts\IManager; use OCP\IConfig; use OCP\IGroupManager; +use OCP\IURLGenerator; use OCP\IUser; use OCP\IUserManager; +use OCP\L10N\IFactory as IL10NFactory; use PHPUnit\Framework\MockObject\MockObject; use Test\TestCase; class ContactsStoreTest extends TestCase { + /** @var IAccountManager */ + private $accountManager; /** @var ContactsStore */ private $contactsStore; /** @var IManager|\PHPUnit\Framework\MockObject\MockObject */ private $contactsManager; /** @var IUserManager|\PHPUnit\Framework\MockObject\MockObject */ private $userManager; + /** @var IURLGenerator */ + private $urlGenerator; /** @var IGroupManager|\PHPUnit\Framework\MockObject\MockObject */ private $groupManager; /** @var IConfig|\PHPUnit\Framework\MockObject\MockObject */ private $config; /** @var KnownUserService|MockObject */ private $knownUserService; + /** @var IL10NFactory */ + private $l10nFactory; protected function setUp(): void { parent::setUp(); + $this->accountManager = $this->createMock(IAccountManager::class); $this->contactsManager = $this->createMock(IManager::class); $this->userManager = $this->createMock(IUserManager::class); + $this->urlGenerator = $this->createMock(IURLGenerator::class); $this->groupManager = $this->createMock(IGroupManager::class); $this->config = $this->createMock(IConfig::class); $this->knownUserService = $this->createMock(KnownUserService::class); + $this->l10nFactory = $this->createMock(IL10NFactory::class); $this->contactsStore = new ContactsStore( + $this->accountManager, $this->contactsManager, $this->config, $this->userManager, + $this->urlGenerator, $this->groupManager, - $this->knownUserService + $this->knownUserService, + $this->l10nFactory ); } diff --git a/tests/lib/Contacts/ContactsMenu/EntryTest.php b/tests/lib/Contacts/ContactsMenu/EntryTest.php index bce1865f94a..561afcf5dde 100644 --- a/tests/lib/Contacts/ContactsMenu/EntryTest.php +++ b/tests/lib/Contacts/ContactsMenu/EntryTest.php @@ -102,7 +102,9 @@ class EntryTest extends TestCase { 'actions' => [], 'lastMessage' => '', 'avatar' => null, - 'emailAddresses' => ['user@example.com'] + 'emailAddresses' => ['user@example.com'], + 'profileTitle' => null, + 'profileUrl' => null, ]; $this->entry->setId(123); diff --git a/version.php b/version.php index d2bb661dba3..0db75d48f50 100644 --- a/version.php +++ b/version.php @@ -30,7 +30,7 @@ // between betas, final and RCs. This is _not_ the public version number. Reset minor/patchlevel // when updating major/minor version number. -$OC_Version = [23, 0, 0, 2]; +$OC_Version = [23, 0, 0, 3]; // The human readable string $OC_VersionString = '23.0.0 alpha'; -- cgit v1.2.3