Skip to content

Commit 8a84c44

Browse files
Merge pull request #60921 from nextcloud/backport/60728/stable34
[stable34] fix(MailPlugin): Stop applying the offset twice and the limit per wide/exact
2 parents 132929b + f603358 commit 8a84c44

4 files changed

Lines changed: 177 additions & 178 deletions

File tree

lib/private/Collaboration/Collaborators/MailByMailPlugin.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use OCP\Federation\ICloudIdManager;
1414
use OCP\IConfig;
1515
use OCP\IGroupManager;
16+
use OCP\IUserManager;
1617
use OCP\IUserSession;
1718
use OCP\Mail\IEmailValidator;
1819
use OCP\Share\IShare;
@@ -30,6 +31,7 @@ public function __construct(
3031
KnownUserService $knownUserService,
3132
IUserSession $userSession,
3233
IEmailValidator $emailValidator,
34+
IUserManager $userManager,
3335
mixed $shareWithGroupOnlyExcludeGroupsList = [],
3436
) {
3537
parent::__construct(
@@ -40,6 +42,7 @@ public function __construct(
4042
$knownUserService,
4143
$userSession,
4244
$emailValidator,
45+
$userManager,
4346
$shareWithGroupOnlyExcludeGroupsList,
4447
IShare::TYPE_EMAIL,
4548
);

lib/private/Collaboration/Collaborators/MailPlugin.php

Lines changed: 143 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@
1616
use OCP\IConfig;
1717
use OCP\IGroupManager;
1818
use OCP\IUser;
19+
use OCP\IUserManager;
1920
use OCP\IUserSession;
2021
use OCP\Mail\IEmailValidator;
2122
use OCP\Share\IShare;
23+
use RuntimeException;
2224

2325
class MailPlugin implements ISearchPlugin {
2426
protected bool $shareWithGroupOnly;
@@ -41,6 +43,7 @@ public function __construct(
4143
private KnownUserService $knownUserService,
4244
private IUserSession $userSession,
4345
private IEmailValidator $emailValidator,
46+
private IUserManager $userManager,
4447
private mixed $shareWithGroupOnlyExcludeGroupsList,
4548
private int $shareType,
4649
) {
@@ -66,83 +69,125 @@ public function search($search, $limit, $offset, ISearchResult $searchResult): b
6669
}
6770

6871
// Extract the email address from "Foo Bar <foo.bar@example.tld>" and then search with "foo.bar@example.tld" instead
69-
$result = preg_match('/<([^@]+@.+)>$/', $search, $matches);
70-
if ($result && filter_var($matches[1], FILTER_VALIDATE_EMAIL)) {
71-
return $this->search($matches[1], $limit, $offset, $searchResult);
72+
if (preg_match('/<([^@]+@.+)>$/', $search, $matches) && filter_var($matches[1], FILTER_VALIDATE_EMAIL)) {
73+
$search = $matches[1];
7274
}
7375

7476
$currentUserId = $this->userSession->getUser()->getUID();
77+
$userGroups = null;
7578

76-
$result = $userResults = ['wide' => [], 'exact' => []];
77-
$userType = new SearchResultType('users');
78-
$emailType = new SearchResultType('emails');
79+
$hasMore = false;
80+
$count = 0;
81+
$results = ['wide' => [], 'exact' => []];
82+
$type = match ($this->shareType) {
83+
IShare::TYPE_USER => new SearchResultType('users'),
84+
IShare::TYPE_EMAIL => new SearchResultType('emails'),
85+
default => throw new RuntimeException(),
86+
};
7987

8088
// Search in contacts
8189
$addressBookContacts = $this->contactsManager->search(
8290
$search,
8391
['EMAIL', 'FN'],
8492
[
85-
'limit' => $limit,
93+
// We request one more, so we can check if there are more results available
94+
'limit' => $limit + 1,
8695
'offset' => $offset,
8796
'enumeration' => $this->shareeEnumeration,
8897
'fullmatch' => $this->shareeEnumerationFullMatch,
8998
]
9099
);
91100
$lowerSearch = strtolower($search);
92101
foreach ($addressBookContacts as $contact) {
93-
if (isset($contact['EMAIL'])) {
94-
$emailAddresses = $contact['EMAIL'];
95-
if (\is_string($emailAddresses)) {
96-
$emailAddresses = [$emailAddresses];
102+
if (!isset($contact['EMAIL'])) {
103+
continue;
104+
}
105+
106+
$emailAddresses = $contact['EMAIL'];
107+
if (\is_string($emailAddresses)) {
108+
$emailAddresses = [$emailAddresses];
109+
}
110+
foreach ($emailAddresses as $emailAddress) {
111+
$displayName = $emailAddress;
112+
$emailAddressType = null;
113+
if (\is_array($emailAddress)) {
114+
$emailAddressData = $emailAddress;
115+
$emailAddress = $emailAddressData['value'];
116+
$emailAddressType = $emailAddressData['type'];
117+
}
118+
119+
if (!filter_var($emailAddress, FILTER_VALIDATE_EMAIL)) {
120+
continue;
97121
}
98-
foreach ($emailAddresses as $type => $emailAddress) {
99-
$displayName = $emailAddress;
100-
$emailAddressType = null;
101-
if (\is_array($emailAddress)) {
102-
$emailAddressData = $emailAddress;
103-
$emailAddress = $emailAddressData['value'];
104-
$emailAddressType = $emailAddressData['type'];
105-
}
106122

107-
if (!filter_var($emailAddress, FILTER_VALIDATE_EMAIL)) {
123+
if (isset($contact['FN'])) {
124+
$displayName = $contact['FN'] . ' (' . $emailAddress . ')';
125+
}
126+
$exactEmailMatch = strtolower($emailAddress) === $lowerSearch;
127+
128+
if (isset($contact['isLocalSystemBook'])) {
129+
$contactUser = $this->userManager->get($contact['UID']);
130+
if ($contactUser === null) {
108131
continue;
109132
}
110133

111-
if (isset($contact['FN'])) {
112-
$displayName = $contact['FN'] . ' (' . $emailAddress . ')';
134+
$contactGroups = $this->groupManager->getUserGroupIds($contactUser);
135+
if ($this->shareWithGroupOnly) {
136+
$userGroups ??= $this->groupManager->getUserGroupIds($this->userSession->getUser());
137+
if (array_intersect($contactGroups, array_diff($userGroups, $this->shareWithGroupOnlyExcludeGroupsList)) === []) {
138+
continue;
139+
}
113140
}
114-
$exactEmailMatch = strtolower($emailAddress) === $lowerSearch;
115-
116-
if (isset($contact['isLocalSystemBook'])) {
117-
if ($this->shareWithGroupOnly) {
118-
/*
119-
* Check if the user may share with the user associated with the e-mail of the just found contact
120-
*/
121-
$userGroups = $this->groupManager->getUserGroupIds($this->userSession->getUser());
122-
123-
// ShareWithGroupOnly filtering
124-
$userGroups = array_diff($userGroups, $this->shareWithGroupOnlyExcludeGroupsList);
125-
126-
$found = false;
127-
foreach ($userGroups as $userGroup) {
128-
if ($this->groupManager->isInGroup($contact['UID'], $userGroup)) {
129-
$found = true;
130-
break;
131-
}
132-
}
133-
if (!$found) {
134-
continue;
135-
}
141+
142+
if ($exactEmailMatch && $this->shareeEnumerationFullMatch) {
143+
try {
144+
$cloud = $this->cloudIdManager->resolveCloudId($contact['CLOUD'][0] ?? '');
145+
} catch (\InvalidArgumentException $e) {
146+
continue;
147+
}
148+
149+
if ($this->shareType === IShare::TYPE_USER && !$this->isCurrentUser($cloud) && !$searchResult->hasResult($type, $cloud->getUser())) {
150+
$singleResult = [[
151+
'label' => $displayName,
152+
'uuid' => $contact['UID'] ?? $emailAddress,
153+
'name' => $contact['FN'] ?? $displayName,
154+
'value' => [
155+
'shareType' => IShare::TYPE_USER,
156+
'shareWith' => $cloud->getUser(),
157+
],
158+
'shareWithDisplayNameUnique' => !empty($emailAddress) ? $emailAddress : $cloud->getUser()
159+
]];
160+
$searchResult->addResultSet($type, [], $singleResult);
161+
$searchResult->markExactIdMatch($type);
136162
}
137-
if ($exactEmailMatch && $this->shareeEnumerationFullMatch) {
138-
try {
139-
$cloud = $this->cloudIdManager->resolveCloudId($contact['CLOUD'][0] ?? '');
140-
} catch (\InvalidArgumentException $e) {
163+
return false;
164+
}
165+
166+
if ($this->shareeEnumeration && $this->shareType === IShare::TYPE_USER) {
167+
try {
168+
if (!isset($contact['CLOUD'])) {
141169
continue;
142170
}
171+
$cloud = $this->cloudIdManager->resolveCloudId($contact['CLOUD'][0] ?? '');
172+
} catch (\InvalidArgumentException $e) {
173+
continue;
174+
}
175+
$addToWide = !($this->shareeEnumerationInGroupOnly || $this->shareeEnumerationPhone);
143176

144-
if ($this->shareType === IShare::TYPE_USER && !$this->isCurrentUser($cloud) && !$searchResult->hasResult($userType, $cloud->getUser())) {
145-
$singleResult = [[
177+
if (!$addToWide && $this->shareeEnumerationPhone && $this->knownUserService->isKnownToUser($currentUserId, $contact['UID'])) {
178+
$addToWide = true;
179+
}
180+
181+
if (!$addToWide && $this->shareeEnumerationInGroupOnly) {
182+
$userGroups ??= $this->groupManager->getUserGroupIds($this->userSession->getUser());
183+
$addToWide = array_intersect($contactGroups, $userGroups) !== [];
184+
}
185+
186+
if ($addToWide && !$this->isCurrentUser($cloud) && !$searchResult->hasResult($type, $cloud->getUser())) {
187+
if ($count++ >= $limit) {
188+
$hasMore = true;
189+
} else {
190+
$results['wide'][] = [
146191
'label' => $displayName,
147192
'uuid' => $contact['UID'] ?? $emailAddress,
148193
'name' => $contact['FN'] ?? $displayName,
@@ -151,122 +196,69 @@ public function search($search, $limit, $offset, ISearchResult $searchResult): b
151196
'shareWith' => $cloud->getUser(),
152197
],
153198
'shareWithDisplayNameUnique' => !empty($emailAddress) ? $emailAddress : $cloud->getUser()
154-
155-
]];
156-
$searchResult->addResultSet($userType, [], $singleResult);
157-
$searchResult->markExactIdMatch($emailType);
199+
];
158200
}
159-
return false;
160201
}
202+
}
161203

162-
if ($this->shareeEnumeration) {
163-
try {
164-
if (!isset($contact['CLOUD'])) {
165-
continue;
166-
}
167-
$cloud = $this->cloudIdManager->resolveCloudId($contact['CLOUD'][0] ?? '');
168-
} catch (\InvalidArgumentException $e) {
169-
continue;
170-
}
171-
172-
$addToWide = !($this->shareeEnumerationInGroupOnly || $this->shareeEnumerationPhone);
173-
if (!$addToWide && $this->shareeEnumerationPhone && $this->knownUserService->isKnownToUser($currentUserId, $contact['UID'])) {
174-
$addToWide = true;
175-
}
204+
continue;
205+
}
176206

177-
if (!$addToWide && $this->shareeEnumerationInGroupOnly) {
178-
$addToWide = false;
179-
$userGroups = $this->groupManager->getUserGroupIds($this->userSession->getUser());
180-
foreach ($userGroups as $userGroup) {
181-
if ($this->groupManager->isInGroup($contact['UID'], $userGroup)) {
182-
$addToWide = true;
183-
break;
184-
}
185-
}
186-
}
187-
if ($addToWide && !$this->isCurrentUser($cloud) && !$searchResult->hasResult($userType, $cloud->getUser())) {
188-
if ($this->shareType === IShare::TYPE_USER) {
189-
$userResults['wide'][] = [
190-
'label' => $displayName,
191-
'uuid' => $contact['UID'] ?? $emailAddress,
192-
'name' => $contact['FN'] ?? $displayName,
193-
'value' => [
194-
'shareType' => IShare::TYPE_USER,
195-
'shareWith' => $cloud->getUser(),
196-
],
197-
'shareWithDisplayNameUnique' => !empty($emailAddress) ? $emailAddress : $cloud->getUser()
198-
];
199-
}
200-
continue;
201-
}
202-
}
203-
continue;
204-
}
207+
if ($this->shareType !== IShare::TYPE_EMAIL) {
208+
continue;
209+
}
205210

206-
if ($this->shareType !== IShare::TYPE_EMAIL) {
207-
continue;
211+
if ($count++ >= $limit) {
212+
$hasMore = true;
213+
} elseif ($exactEmailMatch || (isset($contact['FN']) && strtolower($contact['FN']) === $lowerSearch)) {
214+
if ($exactEmailMatch) {
215+
$searchResult->markExactIdMatch($type);
208216
}
209217

210-
if ($exactEmailMatch
211-
|| (isset($contact['FN']) && strtolower($contact['FN']) === $lowerSearch)) {
212-
if ($exactEmailMatch) {
213-
$searchResult->markExactIdMatch($emailType);
214-
}
215-
$result['exact'][] = [
216-
'label' => $displayName,
217-
'uuid' => $contact['UID'] ?? $emailAddress,
218-
'name' => $contact['FN'] ?? $displayName,
219-
'type' => $emailAddressType ?? '',
220-
'value' => [
221-
'shareType' => IShare::TYPE_EMAIL,
222-
'shareWith' => $emailAddress,
223-
],
224-
];
225-
} else {
226-
$result['wide'][] = [
227-
'label' => $displayName,
228-
'uuid' => $contact['UID'] ?? $emailAddress,
229-
'name' => $contact['FN'] ?? $displayName,
230-
'type' => $emailAddressType ?? '',
231-
'value' => [
232-
'shareType' => IShare::TYPE_EMAIL,
233-
'shareWith' => $emailAddress,
234-
],
235-
];
236-
}
218+
$results['exact'][] = [
219+
'label' => $displayName,
220+
'uuid' => $contact['UID'] ?? $emailAddress,
221+
'name' => $contact['FN'] ?? $displayName,
222+
'type' => $emailAddressType ?? '',
223+
'value' => [
224+
'shareType' => IShare::TYPE_EMAIL,
225+
'shareWith' => $emailAddress,
226+
],
227+
];
228+
} else {
229+
$results['wide'][] = [
230+
'label' => $displayName,
231+
'uuid' => $contact['UID'] ?? $emailAddress,
232+
'name' => $contact['FN'] ?? $displayName,
233+
'type' => $emailAddressType ?? '',
234+
'value' => [
235+
'shareType' => IShare::TYPE_EMAIL,
236+
'shareWith' => $emailAddress,
237+
],
238+
];
237239
}
238240
}
239241
}
240242

241-
$reachedEnd = true;
242-
if ($this->shareeEnumeration) {
243-
$reachedEnd = (count($result['wide']) < $offset + $limit)
244-
&& (count($userResults['wide']) < $offset + $limit);
245-
246-
$result['wide'] = array_slice($result['wide'], $offset, $limit);
247-
$userResults['wide'] = array_slice($userResults['wide'], $offset, $limit);
248-
}
249-
250243
if ($this->shareType === IShare::TYPE_EMAIL
251-
&& !$searchResult->hasExactIdMatch($emailType) && $this->emailValidator->isValid($search)) {
252-
$result['exact'][] = [
253-
'label' => $search,
254-
'uuid' => $search,
255-
'value' => [
256-
'shareType' => IShare::TYPE_EMAIL,
257-
'shareWith' => $search,
258-
],
259-
];
244+
&& !$searchResult->hasExactIdMatch($type) && $this->emailValidator->isValid($search)) {
245+
if ($count++ >= $limit) {
246+
$hasMore = true;
247+
} else {
248+
$results['exact'][] = [
249+
'label' => $search,
250+
'uuid' => $search,
251+
'value' => [
252+
'shareType' => IShare::TYPE_EMAIL,
253+
'shareWith' => $search,
254+
],
255+
];
256+
}
260257
}
261258

262-
if ($this->shareType === IShare::TYPE_USER && !empty($userResults['wide'])) {
263-
$searchResult->addResultSet($userType, $userResults['wide'], []);
264-
}
265-
if ($this->shareType === IShare::TYPE_EMAIL) {
266-
$searchResult->addResultSet($emailType, $result['wide'], $result['exact']);
267-
}
259+
$searchResult->addResultSet($type, $results['wide'], $results['exact']);
268260

269-
return !$reachedEnd;
261+
return $hasMore;
270262
}
271263

272264
public function isCurrentUser(ICloudId $cloud): bool {

0 commit comments

Comments
 (0)