Skip to content

Commit

Permalink
fix(contactsMenu): Attach user cloud to each contact entry
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
nfebe committed Apr 22, 2024
1 parent ea1b0a0 commit 990d244
Show file tree
Hide file tree
Showing 7 changed files with 96 additions and 38 deletions.
5 changes: 3 additions & 2 deletions core/src/components/UnifiedSearch/SearchableList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@
:wide="true"
@click="itemSelected(element)">
<template #icon>
<NcAvatar :user="element.user" :show-user-status="false" :hide-favorite="false" />
<NcAvatar v-if="element.isUser" :user="element.user" :show-user-status="false" :hide-favorite="false" />

Check failure on line 49 in core/src/components/UnifiedSearch/SearchableList.vue

View workflow job for this annotation

GitHub Actions / NPM lint

':hide-favorite' should be on a new line
<NcAvatar v-else :url="element.avatar" :show-user-status="false" :hide-favorite="false" />

Check failure on line 50 in core/src/components/UnifiedSearch/SearchableList.vue

View workflow job for this annotation

GitHub Actions / NPM lint

':hide-favorite' should be on a new line
</template>
{{ element.displayName }}
</NcButton>
Expand Down Expand Up @@ -117,7 +118,6 @@ export default {
})
},
},

methods: {
clearSearch() {
this.searchTerm = ''
Expand All @@ -128,6 +128,7 @@ export default {
this.opened = false
},
searchTermChanged(term) {
console.debug('Users (search)', this.filteredList) // WIP, would remove
this.$emit('search-term-change', term)
},
},
Expand Down
1 change: 1 addition & 0 deletions core/src/services/UnifiedSearchService.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ export async function getContacts({ searchTerm }) {
id: authenticatedUser.uid,
fullName: authenticatedUser.displayName,
emailAddresses: [],
isUser: true,
}
contacts.unshift(authenticatedUser)
return contacts
Expand Down
3 changes: 3 additions & 0 deletions core/src/views/UnifiedSearchModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,7 @@ export default {
},
mapContacts(contacts) {
return contacts.map(contact => {
console.debug('CONTACT VIEW', contact) // WIP would remove
return {
// id: contact.id,
// name: '',
Expand All @@ -391,6 +392,8 @@ export default {
subname: contact.emailAddresses[0] ? contact.emailAddresses[0] : '',
icon: '',
user: contact.id,
isUser: contact.isUser,
avatar: contact.avatar,
}
})
},
Expand Down
29 changes: 24 additions & 5 deletions lib/private/Contacts/ContactsMenu/ContactsStore.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -62,6 +64,7 @@ public function __construct(
private IGroupManager $groupManager,
private KnownUserService $knownUserService,
private IL10NFactory $l10nFactory,
private LoggerInterface $logger,
) {
}

Expand Down Expand Up @@ -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]);

Check failure

Code scanning / Psalm

UndefinedInterfaceMethod Error

Method OCP\IURLGenerator::linkToRemoteRouteAbsolute does not exist

Check failure on line 383 in lib/private/Contacts/ContactsMenu/ContactsStore.php

View workflow job for this annotation

GitHub Actions / static-code-analysis

UndefinedInterfaceMethod

lib/private/Contacts/ContactsMenu/ContactsStore.php:383:36: UndefinedInterfaceMethod: Method OCP\IURLGenerator::linkToRemoteRouteAbsolute does not exist (see https://psalm.dev/181)
} 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]);

Check failure

Code scanning / Psalm

UndefinedInterfaceMethod Error

Method OCP\IURLGenerator::linkToRemoteRouteAbsolute does not exist

Check failure on line 385 in lib/private/Contacts/ContactsMenu/ContactsStore.php

View workflow job for this annotation

GitHub Actions / static-code-analysis

UndefinedInterfaceMethod

lib/private/Contacts/ContactsMenu/ContactsStore.php:385:36: UndefinedInterfaceMethod: Method OCP\IURLGenerator::linkToRemoteRouteAbsolute does not exist (see https://psalm.dev/181)
}
$entry->setAvatar($avatar);
}
Expand All @@ -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'])) {
Expand Down
71 changes: 40 additions & 31 deletions lib/private/Contacts/ContactsMenu/Entry.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,41 +27,40 @@

namespace OC\Contacts\ContactsMenu;

use OC\Federation\CloudId;
use OCP\Contacts\ContactsMenu\IAction;
use OCP\Contacts\ContactsMenu\IEntry;
use function array_merge;

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 {

Check failure

Code scanning / Psalm

InvalidNullableReturnType Error

The declared return type 'string' for OC\Contacts\ContactsMenu\Entry::getId is not nullable, but 'null|string' contains null

Check failure on line 60 in lib/private/Contacts/ContactsMenu/Entry.php

View workflow job for this annotation

GitHub Actions / static-code-analysis

InvalidNullableReturnType

lib/private/Contacts/ContactsMenu/Entry.php:60:27: InvalidNullableReturnType: The declared return type 'string' for OC\Contacts\ContactsMenu\Entry::getId is not nullable, but 'null|string' contains null (see https://psalm.dev/144)
return $this->id;

Check failure

Code scanning / Psalm

NullableReturnStatement Error

The declared return type 'string' for OC\Contacts\ContactsMenu\Entry::getId is not nullable, but the function returns 'null|string'

Check failure on line 61 in lib/private/Contacts/ContactsMenu/Entry.php

View workflow job for this annotation

GitHub Actions / static-code-analysis

NullableReturnStatement

lib/private/Contacts/ContactsMenu/Entry.php:61:10: NullableReturnStatement: The declared return type 'string' for OC\Contacts\ContactsMenu\Entry::getId is not nullable, but the function returns 'null|string' (see https://psalm.dev/139)
}

public function setFullName(string $displayName): void {
$this->fullName = $displayName;
}
Expand Down Expand Up @@ -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 {

Check failure

Code scanning / Psalm

InvalidNullableReturnType Error

The declared return type 'OC\Federation\CloudId' for OC\Contacts\ContactsMenu\Entry::getCloud is not nullable, but 'OC\Federation\CloudId|null' contains null

Check failure on line 178 in lib/private/Contacts/ContactsMenu/Entry.php

View workflow job for this annotation

GitHub Actions / static-code-analysis

InvalidNullableReturnType

lib/private/Contacts/ContactsMenu/Entry.php:178:30: InvalidNullableReturnType: The declared return type 'OC\Federation\CloudId' for OC\Contacts\ContactsMenu\Entry::getCloud is not nullable, but 'OC\Federation\CloudId|null' contains null (see https://psalm.dev/144)
return $this->cloud;

Check failure

Code scanning / Psalm

NullableReturnStatement Error

The declared return type 'OC\Federation\CloudId' for OC\Contacts\ContactsMenu\Entry::getCloud is not nullable, but the function returns 'OC\Federation\CloudId|null'

Check failure on line 179 in lib/private/Contacts/ContactsMenu/Entry.php

View workflow job for this annotation

GitHub Actions / static-code-analysis

NullableReturnStatement

lib/private/Contacts/ContactsMenu/Entry.php:179:10: NullableReturnStatement: The declared return type 'OC\Federation\CloudId' for OC\Contacts\ContactsMenu\Entry::getCloud is not nullable, but the function returns 'OC\Federation\CloudId|null' (see https://psalm.dev/139)
}

/**
* @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;
Expand All @@ -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;
}
}
12 changes: 12 additions & 0 deletions lib/private/Federation/CloudId.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
];
}
}
13 changes: 13 additions & 0 deletions lib/private/URLGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Check failure

Code scanning / Psalm

InvalidNullableReturnType Error

The declared return type 'string' for OC\URLGenerator::linkToRemoteRouteAbsolute is not nullable, but 'null|string' contains null

Check failure on line 119 in lib/private/URLGenerator.php

View workflow job for this annotation

GitHub Actions / static-code-analysis

InvalidNullableReturnType

lib/private/URLGenerator.php:119:96: InvalidNullableReturnType: The declared return type 'string' for OC\URLGenerator::linkToRemoteRouteAbsolute is not nullable, but 'null|string' contains null (see https://psalm.dev/144)
return $this->formatAsUrl($remote, $this->linkToRoute($routeName, $arguments));

Check failure

Code scanning / Psalm

NullableReturnStatement Error

The declared return type 'string' for OC\URLGenerator::linkToRemoteRouteAbsolute is not nullable, but the function returns 'null|string'

Check failure on line 120 in lib/private/URLGenerator.php

View workflow job for this annotation

GitHub Actions / static-code-analysis

NullableReturnStatement

lib/private/URLGenerator.php:120:10: NullableReturnStatement: The declared return type 'string' for OC\URLGenerator::linkToRemoteRouteAbsolute is not nullable, but the function returns 'null|string' (see https://psalm.dev/139)
}

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
Expand Down

0 comments on commit 990d244

Please sign in to comment.