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 8640581
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 36 deletions.
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]);
} 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);
}
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 {
return $this->id;
}

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

0 comments on commit 8640581

Please sign in to comment.