From 8640581621367627f17384ab9fb016c396817791 Mon Sep 17 00:00:00 2001 From: fenn-cs Date: Mon, 22 Apr 2024 09:37:56 +0100 Subject: [PATCH] fix(contactsMenu): Attach user cloud to each contact entry The contact entry should carry cloud information, this can help to fix various issues including: - Displaying correct avatar links for federated users This can also lead to UI improvements in the frontend where, it's federated contacts are shown with a marker or indicator on the UI. Signed-off-by: fenn-cs --- .../Contacts/ContactsMenu/ContactsStore.php | 29 ++++++-- lib/private/Contacts/ContactsMenu/Entry.php | 71 +++++++++++-------- lib/private/Federation/CloudId.php | 12 ++++ lib/private/URLGenerator.php | 13 ++++ 4 files changed, 89 insertions(+), 36 deletions(-) diff --git a/lib/private/Contacts/ContactsMenu/ContactsStore.php b/lib/private/Contacts/ContactsMenu/ContactsStore.php index 3b39cc869a716..4c1b544a1c0fc 100644 --- a/lib/private/Contacts/ContactsMenu/ContactsStore.php +++ b/lib/private/Contacts/ContactsMenu/ContactsStore.php @@ -31,6 +31,7 @@ namespace OC\Contacts\ContactsMenu; +use OC\Federation\CloudId; use OC\KnownUser\KnownUserService; use OC\Profile\ProfileManager; use OCA\UserStatus\Db\UserStatus; @@ -44,6 +45,7 @@ use OCP\IUser; use OCP\IUserManager; use OCP\L10N\IFactory as IL10NFactory; +use Psr\Log\LoggerInterface; use function array_column; use function array_fill_keys; use function array_filter; @@ -62,6 +64,7 @@ public function __construct( private IGroupManager $groupManager, private KnownUserService $knownUserService, private IL10NFactory $l10nFactory, + private LoggerInterface $logger, ) { } @@ -351,19 +354,35 @@ public function findOne(IUser $user, int $shareType, string $shareWith): ?IEntry private function contactArrayToEntry(array $contact): Entry { $entry = new Entry(); - if (!empty($contact['UID'])) { $uid = $contact['UID']; $entry->setId($uid); $entry->setProperty('isUser', false); + $username = ''; + $remoteServer = ''; + + if (isset($contact['CLOUD']) && is_array($contact['CLOUD']) && isset($contact['CLOUD'][0])) { + preg_match('/^(.*?)@(https?:\/\/.*?)$/', $contact['CLOUD'][0], $matches); + if (count($matches) === 3) { + $username = $matches[1]; + $remoteServer = $matches[2]; + $cloud = new CloudId($entry->getId(), $username, $remoteServer); + $entry->setCloudId($cloud); + $this->logger->warning('Set address cloud: ' . json_encode(['username' => $username, 'server' => $remoteServer])); + } else { + $this->logger->warning('Unable to process contact remote server: ' . $contact['CLOUD'][0]); + } + } else { + $this->logger->warning('Invalid remote server data'); + } // overloaded usage so leaving as-is for now if (isset($contact['isLocalSystemBook'])) { $avatar = $this->urlGenerator->linkToRouteAbsolute('core.avatar.getAvatar', ['userId' => $uid, 'size' => 64]); $entry->setProperty('isUser', true); - } elseif (!empty($contact['FN'])) { - $avatar = $this->urlGenerator->linkToRouteAbsolute('core.GuestAvatar.getAvatar', ['guestName' => str_replace('/', ' ', $contact['FN']), 'size' => 64]); + } elseif ($username != '') { + $avatar = $this->urlGenerator->linkToRemoteRouteAbsolute($remoteServer, 'core.avatar.getAvatar', ['userId' => str_replace('/', ' ', $username), 'size' => 64]); } else { - $avatar = $this->urlGenerator->linkToRouteAbsolute('core.GuestAvatar.getAvatar', ['guestName' => str_replace('/', ' ', $uid), 'size' => 64]); + $avatar = $this->urlGenerator->linkToRemoteRouteAbsolute($remoteServer, 'core.avatar.getAvatar', ['userId' => str_replace('/', ' ', $uid), 'size' => 64]); } $entry->setAvatar($avatar); } @@ -374,7 +393,7 @@ private function contactArrayToEntry(array $contact): Entry { $avatarPrefix = "VALUE=uri:"; if (!empty($contact['PHOTO']) && str_starts_with($contact['PHOTO'], $avatarPrefix)) { - $entry->setAvatar(substr($contact['PHOTO'], strlen($avatarPrefix))); + //$entry->setAvatar(substr($contact['PHOTO'], strlen($avatarPrefix))); } if (!empty($contact['EMAIL'])) { diff --git a/lib/private/Contacts/ContactsMenu/Entry.php b/lib/private/Contacts/ContactsMenu/Entry.php index 41fb88b15285c..1951501db5f11 100644 --- a/lib/private/Contacts/ContactsMenu/Entry.php +++ b/lib/private/Contacts/ContactsMenu/Entry.php @@ -27,6 +27,7 @@ namespace OC\Contacts\ContactsMenu; +use OC\Federation\CloudId; use OCP\Contacts\ContactsMenu\IAction; use OCP\Contacts\ContactsMenu\IEntry; use function array_merge; @@ -34,34 +35,32 @@ class Entry implements IEntry { public const PROPERTY_STATUS_MESSAGE_TIMESTAMP = 'statusMessageTimestamp'; - /** @var string|int|null */ - private $id = null; - - private string $fullName = ''; - - /** @var string[] */ - private array $emailAddresses = []; - - private ?string $avatar = null; - - private ?string $profileTitle = null; - - private ?string $profileUrl = null; - - /** @var IAction[] */ - private array $actions = []; - - private array $properties = []; + public function __construct( + private ?string $id = null, + private string $fullName = '', + private array $emailAddresses = [], + private ?string $avatar = null, + private ?string $profileTitle = null, + private ?string $profileUrl = null, + private array $actions = [], + private array $properties = [], + private ?string $status = null, + private ?string $statusMessage = null, + private ?int $statusMessageTimestamp = null, + private ?string $statusIcon = null, + private ?CloudId $cloud = null + ) { + } - private ?string $status = null; - private ?string $statusMessage = null; - private ?int $statusMessageTimestamp = null; - private ?string $statusIcon = null; public function setId(string $id): void { $this->id = $id; } + public function getId(): string { + return $this->id; + } + public function setFullName(string $displayName): void { $this->fullName = $displayName; } @@ -163,8 +162,25 @@ public function getProperty(string $key): mixed { return $this->properties[$key]; } + + public function getStatusMessage(): ?string { + return $this->statusMessage; + } + + public function getStatusMessageTimestamp(): ?int { + return $this->statusMessageTimestamp; + } + + public function setCloudId(CloudId $cloudId) { + $this->cloud = $cloudId; + } + + public function getCloud(): CloudId { + return $this->cloud; + } + /** - * @return array{id: int|string|null, fullName: string, avatar: string|null, topAction: mixed, actions: array, lastMessage: '', emailAddresses: string[], profileTitle: string|null, profileUrl: string|null, status: string|null, statusMessage: null|string, statusMessageTimestamp: null|int, statusIcon: null|string, isUser: bool, uid: mixed} + * @return array{id: int|string|null, fullName: string, avatar: string|null, topAction: mixed, actions: array, lastMessage: '', emailAddresses: string[], profileTitle: string|null, profileUrl: string|null, status: string|null, statusMessage: null|string, statusMessageTimestamp: null|int, statusIcon: null|string, isUser: bool, uid: mixed, cloud: mixed} */ public function jsonSerialize(): array { $topAction = !empty($this->actions) ? $this->actions[0]->jsonSerialize() : null; @@ -188,14 +204,7 @@ public function jsonSerialize(): array { 'statusIcon' => $this->statusIcon, 'isUser' => $this->getProperty('isUser') === true, 'uid' => $this->getProperty('UID'), + 'cloud' => $this->cloud, ]; } - - public function getStatusMessage(): ?string { - return $this->statusMessage; - } - - public function getStatusMessageTimestamp(): ?int { - return $this->statusMessageTimestamp; - } } diff --git a/lib/private/Federation/CloudId.php b/lib/private/Federation/CloudId.php index 50e974831a6fe..ae2d453044b7b 100644 --- a/lib/private/Federation/CloudId.php +++ b/lib/private/Federation/CloudId.php @@ -88,4 +88,16 @@ public function getUser(): string { public function getRemote(): string { return $this->remote; } + + /** + * @return array{id: string, user: string, remote: string, displayName: string|null} + */ + public function jsonSerialize(): array { + return [ + 'id' => $this->id, + 'user' => $this->user, + 'remote' => $this->remote, + 'displayName' => $this->displayName, + ]; + } } diff --git a/lib/private/URLGenerator.php b/lib/private/URLGenerator.php index 4701f3af6a99d..463688766598a 100644 --- a/lib/private/URLGenerator.php +++ b/lib/private/URLGenerator.php @@ -115,6 +115,19 @@ public function linkToRouteAbsolute(string $routeName, array $arguments = []): s return $this->getAbsoluteURL($this->linkToRoute($routeName, $arguments)); } + + public function linkToRemoteRouteAbsolute(string $remote, $routeName, array $arguments = []): string { + return $this->formatAsUrl($remote, $this->linkToRoute($routeName, $arguments)); + } + + private function formatAsUrl(string $baseUrl, string $restUrl): ?string { + $baseUrl = trim($baseUrl); + if (empty($baseUrl) || !filter_var(preg_match("~^(?:f|ht)tps?://~i", $baseUrl) ? $baseUrl : "http://$baseUrl", FILTER_VALIDATE_URL)) { + return null; + } + return filter_var($baseUrl . $restUrl, FILTER_VALIDATE_URL) ? $baseUrl . $restUrl : null; + } + public function linkToOCSRouteAbsolute(string $routeName, array $arguments = []): string { // Returns `/subfolder/index.php/ocsapp/…` with `'htaccess.IgnoreFrontController' => false` in config.php // And `/subfolder/ocsapp/…` with `'htaccess.IgnoreFrontController' => true` in config.php