Skip to content

Commit cfa86a0

Browse files
andrey18106julien-nc
authored andcommitted
feat(picker): Move users_picker profile custom picker to contacts
Signed-off-by: Julien Veyssier <[email protected]>
1 parent 430505e commit cfa86a0

11 files changed

+958
-0
lines changed

img/LICENSES.md

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Licenses
2+
3+
## profile.svg, profile-dark.svg
4+
5+
* Created by: Google
6+
* License: Apache License version 2.0
7+
* Link: https://pictogrammers.com/library/mdi/icon/account/
8+
*

img/profile-dark.svg

+1
Loading

img/profile.svg

+1
Loading

lib/AppInfo/Application.php

+6
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@
77

88
use OCA\Contacts\Dav\PatchPlugin;
99
use OCA\Contacts\Listener\LoadContactsFilesActions;
10+
use OCA\Contacts\Listener\ProfilePickerReferenceListener;
11+
use OCA\Contacts\Reference\ProfilePickerReferenceProvider;
1012
use OCA\Files\Event\LoadAdditionalScriptsEvent;
1113
use OCP\AppFramework\App;
1214
use OCP\AppFramework\Bootstrap\IBootContext;
1315
use OCP\AppFramework\Bootstrap\IBootstrap;
1416
use OCP\AppFramework\Bootstrap\IRegistrationContext;
17+
use OCP\Collaboration\Reference\RenderReferenceEvent;
1518
use OCP\EventDispatcher\IEventDispatcher;
1619
use OCP\SabrePluginEvent;
1720

@@ -28,6 +31,9 @@ public function __construct() {
2831

2932
public function register(IRegistrationContext $context): void {
3033
$context->registerEventListener(LoadAdditionalScriptsEvent::class, LoadContactsFilesActions::class);
34+
35+
$context->registerEventListener(RenderReferenceEvent::class, ProfilePickerReferenceListener::class);
36+
$context->registerReferenceProvider(ProfilePickerReferenceProvider::class);
3137
}
3238

3339
public function boot(IBootContext $context): void {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* @copyright Copyright (c) 2023 Andrey Borysenko <[email protected]>
7+
*
8+
* @author 2023 Andrey Borysenko <[email protected]>
9+
*
10+
* @license AGPL-3.0-or-later
11+
*
12+
* This program is free software: you can redistribute it and/or modify
13+
* it under the terms of the GNU Affero General Public License as
14+
* published by the Free Software Foundation, either version 3 of the
15+
* License, or (at your option) any later version.
16+
*
17+
* This program is distributed in the hope that it will be useful,
18+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
19+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20+
* GNU Affero General Public License for more details.
21+
*
22+
* You should have received a copy of the GNU Affero General Public License
23+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
24+
*
25+
*/
26+
27+
namespace OCA\Contacts\Listener;
28+
29+
use OCA\Contacts\AppInfo\Application;
30+
use OCP\Collaboration\Reference\RenderReferenceEvent;
31+
use OCP\EventDispatcher\Event;
32+
use OCP\EventDispatcher\IEventListener;
33+
use OCP\Util;
34+
35+
class ProfilePickerReferenceListener implements IEventListener {
36+
public function handle(Event $event): void {
37+
if (!$event instanceof RenderReferenceEvent) {
38+
return;
39+
}
40+
41+
Util::addScript(Application::APP_ID, Application::APP_ID . '-reference');
42+
}
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* @copyright Copyright (c) 2023 Andrey Borysenko <[email protected]>
7+
*
8+
* @author 2023 Andrey Borysenko <[email protected]>
9+
*
10+
* @license AGPL-3.0-or-later
11+
*
12+
* This program is free software: you can redistribute it and/or modify
13+
* it under the terms of the GNU Affero General Public License as
14+
* published by the Free Software Foundation, either version 3 of the
15+
* License, or (at your option) any later version.
16+
*
17+
* This program is distributed in the hope that it will be useful,
18+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
19+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20+
* GNU Affero General Public License for more details.
21+
*
22+
* You should have received a copy of the GNU Affero General Public License
23+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
24+
*
25+
*/
26+
27+
namespace OCA\Contacts\Reference;
28+
29+
30+
use OCP\Collaboration\Reference\ADiscoverableReferenceProvider;
31+
use OCP\Collaboration\Reference\Reference;
32+
33+
use OCP\Collaboration\Reference\IReference;
34+
use OCP\IL10N;
35+
use OCP\IURLGenerator;
36+
37+
use OCA\Contacts\AppInfo\Application;
38+
use OCP\Accounts\IAccountManager;
39+
use OCP\IUserManager;
40+
41+
class ProfilePickerReferenceProvider extends ADiscoverableReferenceProvider {
42+
public const RICH_OBJECT_TYPE = 'users_picker_profile';
43+
44+
public function __construct(
45+
private IL10N $l10n,
46+
private IURLGenerator $urlGenerator,
47+
private IUserManager $userManager,
48+
private IAccountManager $accountManager,
49+
private ?string $userId
50+
) {
51+
}
52+
53+
/**
54+
* @inheritDoc
55+
*/
56+
public function getId(): string {
57+
return 'profile_picker';
58+
}
59+
60+
/**
61+
* @inheritDoc
62+
*/
63+
public function getTitle(): string {
64+
return $this->l10n->t('Profile picker');
65+
}
66+
67+
/**
68+
* @inheritDoc
69+
*/
70+
public function getOrder(): int {
71+
return 10;
72+
}
73+
74+
/**
75+
* @inheritDoc
76+
*/
77+
public function getIconUrl(): string {
78+
return $this->urlGenerator->imagePath(Application::APP_ID, 'profile-dark.svg');
79+
}
80+
81+
/**
82+
* @inheritDoc
83+
*/
84+
public function matchReference(string $referenceText): bool {
85+
return $this->getObjectId($referenceText) !== null;
86+
}
87+
88+
/**
89+
* @inheritDoc
90+
*/
91+
public function resolveReference(string $referenceText): ?IReference {
92+
if (!$this->matchReference($referenceText)) {
93+
return null;
94+
}
95+
96+
$userId = $this->getObjectId($referenceText);
97+
$user = $this->userManager->get($userId);
98+
if ($user === null) {
99+
return null;
100+
}
101+
$account = $this->accountManager->getAccount($user);
102+
$profileEnabled = $account->getProperty(IAccountManager::PROPERTY_PROFILE_ENABLED)->getValue() === '1';
103+
if (!$profileEnabled) {
104+
return null;
105+
}
106+
107+
$reference = new Reference($referenceText);
108+
109+
$userDisplayName = $user->getDisplayName();
110+
$userEmail = $user->getEMailAddress();
111+
$userAvatarUrl = $this->urlGenerator->linkToRouteAbsolute('core.avatar.getAvatar', ['userId' => $userId, 'size' => '64']);
112+
113+
$bio = $account->getProperty(IAccountManager::PROPERTY_BIOGRAPHY);
114+
$bio = $bio->getScope() !== IAccountManager::SCOPE_PRIVATE ? $bio->getValue() : null;
115+
$headline = $account->getProperty(IAccountManager::PROPERTY_HEADLINE);
116+
$location = $account->getProperty(IAccountManager::PROPERTY_ADDRESS);
117+
$website = $account->getProperty(IAccountManager::PROPERTY_WEBSITE);
118+
$organisation = $account->getProperty(IAccountManager::PROPERTY_ORGANISATION);
119+
$role = $account->getProperty(IAccountManager::PROPERTY_ROLE);
120+
121+
// for clients who can't render the reference widgets
122+
$reference->setTitle($userDisplayName);
123+
$reference->setDescription($userEmail ?? $userDisplayName);
124+
$reference->setImageUrl($userAvatarUrl);
125+
126+
// for the Vue reference widget
127+
$reference->setRichObject(
128+
self::RICH_OBJECT_TYPE,
129+
[
130+
'user_id' => $userId,
131+
'title' => $userDisplayName,
132+
'subline' => $userEmail ?? $userDisplayName,
133+
'email' => $userEmail,
134+
'bio' => isset($bio) && $bio !== ''
135+
? (mb_strlen($bio) > 80
136+
? (mb_substr($bio, 0, 80) . '...')
137+
: $bio)
138+
: null,
139+
'full_bio' => $bio,
140+
'headline' => $headline->getScope() !== IAccountManager::SCOPE_PRIVATE ? $headline->getValue() : null,
141+
'location' => $location->getScope() !== IAccountManager::SCOPE_PRIVATE ? $location->getValue() : null,
142+
'location_url' => $location->getScope() !== IAccountManager::SCOPE_PRIVATE ? $this->getOpenStreetLocationUrl($location->getValue()) : null,
143+
'website' => $website->getScope() !== IAccountManager::SCOPE_PRIVATE ? $website->getValue() : null,
144+
'organisation' => $organisation->getScope() !== IAccountManager::SCOPE_PRIVATE ? $organisation->getValue() : null,
145+
'role' => $role->getScope() !== IAccountManager::SCOPE_PRIVATE ? $role->getValue() : null,
146+
'url' => $referenceText,
147+
]
148+
);
149+
return $reference;
150+
}
151+
152+
public function getObjectId(string $url): ?string {
153+
$baseUrl = $this->urlGenerator->getBaseUrl();
154+
$baseWithIndex = $baseUrl . '/index.php';
155+
156+
preg_match('/^' . preg_quote($baseUrl, '/') . '\/u\/(\w+)$/', $url, $matches);
157+
if (count($matches) > 1) {
158+
return $matches[1];
159+
}
160+
preg_match('/^' . preg_quote($baseWithIndex, '/') . '\/u\/(\w+)$/', $url, $matches);
161+
if (count($matches) > 1) {
162+
return $matches[1];
163+
}
164+
165+
return null;
166+
}
167+
168+
public function getOpenStreetLocationUrl($location): string {
169+
return 'https://www.openstreetmap.org/search?query=' . urlencode($location);
170+
}
171+
172+
/**
173+
* @inheritDoc
174+
*/
175+
public function getCachePrefix(string $referenceId): string {
176+
return $this->userId ?? '';
177+
}
178+
179+
/**
180+
* @inheritDoc
181+
*/
182+
public function getCacheKey(string $referenceId): ?string {
183+
$objectId = $this->getObjectId($referenceId);
184+
if ($objectId !== null) {
185+
return $objectId;
186+
}
187+
return $referenceId;
188+
}
189+
}

0 commit comments

Comments
 (0)