Completed
Push — master ( 15b459...8e5ae5 )
by
unknown
26:31
created
build/integration/features/bootstrap/ShareesContext.php 1 patch
Indentation   +9 added lines, -9 removed lines patch added patch discarded remove patch
@@ -15,14 +15,14 @@
 block discarded – undo
15 15
  * Features context.
16 16
  */
17 17
 class ShareesContext implements Context, SnippetAcceptingContext {
18
-	use Sharing;
19
-	use AppConfiguration;
18
+    use Sharing;
19
+    use AppConfiguration;
20 20
 
21
-	protected function resetAppConfigs() {
22
-		$this->deleteServerConfig('core', 'shareapi_only_share_with_group_members');
23
-		$this->deleteServerConfig('core', 'shareapi_allow_share_dialog_user_enumeration');
24
-		$this->deleteServerConfig('core', 'shareapi_allow_group_sharing');
25
-		$this->deleteServerConfig('core', 'shareapi_exclude_groups');
26
-		$this->deleteServerConfig('core', 'shareapi_exclude_groups_list');
27
-	}
21
+    protected function resetAppConfigs() {
22
+        $this->deleteServerConfig('core', 'shareapi_only_share_with_group_members');
23
+        $this->deleteServerConfig('core', 'shareapi_allow_share_dialog_user_enumeration');
24
+        $this->deleteServerConfig('core', 'shareapi_allow_group_sharing');
25
+        $this->deleteServerConfig('core', 'shareapi_exclude_groups');
26
+        $this->deleteServerConfig('core', 'shareapi_exclude_groups_list');
27
+    }
28 28
 }
Please login to merge, or discard this patch.
build/integration/features/bootstrap/FeatureContext.php 1 patch
Indentation   +13 added lines, -13 removed lines patch added patch discarded remove patch
@@ -14,18 +14,18 @@
 block discarded – undo
14 14
  * Features context.
15 15
  */
16 16
 class FeatureContext implements Context, SnippetAcceptingContext {
17
-	use AppConfiguration;
18
-	use ContactsMenu;
19
-	use ExternalStorage;
20
-	use Search;
21
-	use WebDav;
22
-	use Trashbin;
17
+    use AppConfiguration;
18
+    use ContactsMenu;
19
+    use ExternalStorage;
20
+    use Search;
21
+    use WebDav;
22
+    use Trashbin;
23 23
 
24
-	protected function resetAppConfigs(): void {
25
-		$this->deleteServerConfig('bruteForce', 'whitelist_0');
26
-		$this->deleteServerConfig('bruteForce', 'whitelist_1');
27
-		$this->deleteServerConfig('bruteforcesettings', 'apply_allowlist_to_ratelimit');
28
-		$this->deleteServerConfig('core', 'shareapi_exclude_groups');
29
-		$this->deleteServerConfig('core', 'shareapi_exclude_groups_list');
30
-	}
24
+    protected function resetAppConfigs(): void {
25
+        $this->deleteServerConfig('bruteForce', 'whitelist_0');
26
+        $this->deleteServerConfig('bruteForce', 'whitelist_1');
27
+        $this->deleteServerConfig('bruteforcesettings', 'apply_allowlist_to_ratelimit');
28
+        $this->deleteServerConfig('core', 'shareapi_exclude_groups');
29
+        $this->deleteServerConfig('core', 'shareapi_exclude_groups_list');
30
+    }
31 31
 }
Please login to merge, or discard this patch.
lib/private/Contacts/ContactsMenu/ContactsStore.php 1 patch
Indentation   +348 added lines, -348 removed lines patch added patch discarded remove patch
@@ -28,352 +28,352 @@
 block discarded – undo
28 28
 use function count;
29 29
 
30 30
 class ContactsStore implements IContactsStore {
31
-	public function __construct(
32
-		private IManager $contactsManager,
33
-		private ?StatusService $userStatusService,
34
-		private IConfig $config,
35
-		private ProfileManager $profileManager,
36
-		private IUserManager $userManager,
37
-		private IURLGenerator $urlGenerator,
38
-		private IGroupManager $groupManager,
39
-		private KnownUserService $knownUserService,
40
-		private IL10NFactory $l10nFactory,
41
-	) {
42
-	}
43
-
44
-	/**
45
-	 * @return IEntry[]
46
-	 */
47
-	public function getContacts(IUser $user, ?string $filter, ?int $limit = null, ?int $offset = null): array {
48
-		$options = [
49
-			'enumeration' => $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes',
50
-			'fullmatch' => $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match', 'yes') === 'yes',
51
-		];
52
-		if ($limit !== null) {
53
-			$options['limit'] = $limit;
54
-		}
55
-		if ($offset !== null) {
56
-			$options['offset'] = $offset;
57
-		}
58
-		// Status integration only works without pagination and filters
59
-		if ($offset === null && ($filter === null || $filter === '')) {
60
-			$recentStatuses = $this->userStatusService?->findAllRecentStatusChanges($limit, $offset) ?? [];
61
-		} else {
62
-			$recentStatuses = [];
63
-		}
64
-
65
-		// Search by status if there is no filter and statuses are available
66
-		if (!empty($recentStatuses)) {
67
-			$allContacts = array_filter(array_map(function (UserStatus $userStatus) use ($options) {
68
-				// UID is ambiguous with federation. We have to use the federated cloud ID to an exact match of
69
-				// A local user
70
-				$user = $this->userManager->get($userStatus->getUserId());
71
-				if ($user === null) {
72
-					return null;
73
-				}
74
-
75
-				$contact = $this->contactsManager->search(
76
-					$user->getCloudId(),
77
-					[
78
-						'CLOUD',
79
-					],
80
-					array_merge(
81
-						$options,
82
-						[
83
-							'limit' => 1,
84
-							'offset' => 0,
85
-						],
86
-					),
87
-				)[0] ?? null;
88
-				if ($contact !== null) {
89
-					$contact[Entry::PROPERTY_STATUS_MESSAGE_TIMESTAMP] = $userStatus->getStatusMessageTimestamp();
90
-				}
91
-				return $contact;
92
-			}, $recentStatuses));
93
-			if ($limit !== null && count($allContacts) < $limit) {
94
-				// More contacts were requested
95
-				$fromContacts = $this->contactsManager->search(
96
-					$filter ?? '',
97
-					[
98
-						'FN',
99
-						'EMAIL'
100
-					],
101
-					array_merge(
102
-						$options,
103
-						[
104
-							'limit' => $limit - count($allContacts),
105
-						],
106
-					),
107
-				);
108
-
109
-				// Create hash map of all status contacts
110
-				$existing = array_fill_keys(array_column($allContacts, 'URI'), null);
111
-				// Append the ones that are new
112
-				$allContacts = array_merge(
113
-					$allContacts,
114
-					array_filter($fromContacts, fn (array $contact): bool => !array_key_exists($contact['URI'], $existing))
115
-				);
116
-			}
117
-		} else {
118
-			$allContacts = $this->contactsManager->search(
119
-				$filter ?? '',
120
-				[
121
-					'FN',
122
-					'EMAIL'
123
-				],
124
-				$options
125
-			);
126
-		}
127
-
128
-		$userId = $user->getUID();
129
-		$contacts = array_filter($allContacts, function ($contact) use ($userId) {
130
-			// When searching for multiple results, we strip out the current user
131
-			if (array_key_exists('UID', $contact)) {
132
-				return $contact['UID'] !== $userId;
133
-			}
134
-			return true;
135
-		});
136
-
137
-		$entries = array_map(function (array $contact) {
138
-			return $this->contactArrayToEntry($contact);
139
-		}, $contacts);
140
-		return $this->filterContacts(
141
-			$user,
142
-			$entries,
143
-			$filter
144
-		);
145
-	}
146
-
147
-	/**
148
-	 * Filters the contacts. Applied filters:
149
-	 *  1. if the `shareapi_allow_share_dialog_user_enumeration` config option is
150
-	 * enabled it will filter all local users
151
-	 *  2. if the `shareapi_exclude_groups` config option is enabled and the
152
-	 * current user is only in excluded groups it will filter all local users.
153
-	 *  3. if the `shareapi_only_share_with_group_members` config option is
154
-	 * enabled it will filter all users which doesn't have a common group
155
-	 * with the current user.
156
-	 * If enabled, the 'shareapi_only_share_with_group_members_exclude_group_list'
157
-	 * config option may specify some groups excluded from the principle of
158
-	 * belonging to the same group.
159
-	 *
160
-	 * @param Entry[] $entries
161
-	 * @return Entry[] the filtered contacts
162
-	 */
163
-	private function filterContacts(
164
-		IUser $self,
165
-		array $entries,
166
-		?string $filter,
167
-	): array {
168
-		$disallowEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') !== 'yes';
169
-		$restrictEnumerationGroup = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes';
170
-		$restrictEnumerationPhone = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_phone', 'no') === 'yes';
171
-		$allowEnumerationFullMatch = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match', 'yes') === 'yes';
172
-		$excludeGroups = $this->config->getAppValue('core', 'shareapi_exclude_groups', 'no');
173
-
174
-		// whether to filter out local users
175
-		$skipLocal = false;
176
-		// whether to filter out all users which don't have a common group as the current user
177
-		$ownGroupsOnly = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes';
178
-
179
-		$selfGroups = $this->groupManager->getUserGroupIds($self);
180
-
181
-		if ($excludeGroups && $excludeGroups !== 'no') {
182
-			$excludedGroups = $this->config->getAppValue('core', 'shareapi_exclude_groups_list', '');
183
-			$decodedExcludeGroups = json_decode($excludedGroups, true);
184
-			$excludeGroupsList = $decodedExcludeGroups ?? [];
185
-
186
-			if ($excludeGroups != 'allow') {
187
-				if (count($selfGroups) > 0 && count(array_diff($selfGroups, $excludeGroupsList)) === 0) {
188
-					// all the groups of the current user are excluded -> filter all local users
189
-					$skipLocal = true;
190
-				}
191
-			} else {
192
-				$skipLocal = true;
193
-				if (count(array_intersect($excludeGroupsList, $selfGroups)) !== 0) {
194
-					// a group of the current user is allowed -> do not filter all local users
195
-					$skipLocal = false;
196
-				}
197
-			}
198
-		}
199
-
200
-		// ownGroupsOnly : some groups may be excluded
201
-		if ($ownGroupsOnly) {
202
-			$excludeGroupsFromOwnGroups = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members_exclude_group_list', '');
203
-			$excludeGroupsFromOwnGroupsList = json_decode($excludeGroupsFromOwnGroups, true) ?? [];
204
-			$selfGroups = array_diff($selfGroups, $excludeGroupsFromOwnGroupsList);
205
-		}
206
-
207
-		$selfUID = $self->getUID();
208
-
209
-		return array_values(array_filter($entries, function (IEntry $entry) use ($skipLocal, $ownGroupsOnly, $selfGroups, $selfUID, $disallowEnumeration, $restrictEnumerationGroup, $restrictEnumerationPhone, $allowEnumerationFullMatch, $filter) {
210
-			if ($entry->getProperty('isLocalSystemBook')) {
211
-				if ($skipLocal) {
212
-					return false;
213
-				}
214
-
215
-				$checkedCommonGroupAlready = false;
216
-
217
-				// Prevent enumerating local users
218
-				if ($disallowEnumeration) {
219
-					if (!$allowEnumerationFullMatch) {
220
-						return false;
221
-					}
222
-
223
-					$filterOutUser = true;
224
-
225
-					$mailAddresses = $entry->getEMailAddresses();
226
-					foreach ($mailAddresses as $mailAddress) {
227
-						if ($mailAddress === $filter) {
228
-							$filterOutUser = false;
229
-							break;
230
-						}
231
-					}
232
-
233
-					if ($entry->getProperty('UID') && $entry->getProperty('UID') === $filter) {
234
-						$filterOutUser = false;
235
-					}
236
-
237
-					if ($filterOutUser) {
238
-						return false;
239
-					}
240
-				} elseif ($restrictEnumerationPhone || $restrictEnumerationGroup) {
241
-					$canEnumerate = false;
242
-					if ($restrictEnumerationPhone) {
243
-						$canEnumerate = $this->knownUserService->isKnownToUser($selfUID, $entry->getProperty('UID'));
244
-					}
245
-
246
-					if (!$canEnumerate && $restrictEnumerationGroup) {
247
-						$user = $this->userManager->get($entry->getProperty('UID'));
248
-
249
-						if ($user === null) {
250
-							return false;
251
-						}
252
-
253
-						$contactGroups = $this->groupManager->getUserGroupIds($user);
254
-						$canEnumerate = !empty(array_intersect($contactGroups, $selfGroups));
255
-						$checkedCommonGroupAlready = true;
256
-					}
257
-
258
-					if (!$canEnumerate) {
259
-						return false;
260
-					}
261
-				}
262
-
263
-				if ($ownGroupsOnly && !$checkedCommonGroupAlready) {
264
-					$user = $this->userManager->get($entry->getProperty('UID'));
265
-
266
-					if (!$user instanceof IUser) {
267
-						return false;
268
-					}
269
-
270
-					$contactGroups = $this->groupManager->getUserGroupIds($user);
271
-					if (empty(array_intersect($contactGroups, $selfGroups))) {
272
-						// no groups in common, so shouldn't see the contact
273
-						return false;
274
-					}
275
-				}
276
-			}
277
-
278
-			return true;
279
-		}));
280
-	}
281
-
282
-	public function findOne(IUser $user, int $shareType, string $shareWith): ?IEntry {
283
-		switch ($shareType) {
284
-			case 0:
285
-			case 6:
286
-				$filter = ['UID'];
287
-				break;
288
-			case 4:
289
-				$filter = ['EMAIL'];
290
-				break;
291
-			default:
292
-				return null;
293
-		}
294
-
295
-		$contacts = $this->contactsManager->search($shareWith, $filter, [
296
-			'strict_search' => true,
297
-		]);
298
-		$match = null;
299
-
300
-		foreach ($contacts as $contact) {
301
-			if ($shareType === 4 && isset($contact['EMAIL'])) {
302
-				if (in_array($shareWith, $contact['EMAIL'])) {
303
-					$match = $contact;
304
-					break;
305
-				}
306
-			}
307
-			if ($shareType === 0 || $shareType === 6) {
308
-				if (($contact['isLocalSystemBook'] ?? false) === true && $contact['UID'] === $shareWith) {
309
-					$match = $contact;
310
-					break;
311
-				}
312
-			}
313
-		}
314
-
315
-		if ($match) {
316
-			$match = $this->filterContacts($user, [$this->contactArrayToEntry($match)], $shareWith);
317
-			if (count($match) === 1) {
318
-				$match = $match[0];
319
-			} else {
320
-				$match = null;
321
-			}
322
-		}
323
-
324
-		return $match;
325
-	}
326
-
327
-	private function contactArrayToEntry(array $contact): Entry {
328
-		$entry = new Entry();
329
-
330
-		if (!empty($contact['UID'])) {
331
-			$uid = $contact['UID'];
332
-			$entry->setId($uid);
333
-			$entry->setProperty('isUser', false);
334
-			// overloaded usage so leaving as-is for now
335
-			if (isset($contact['isLocalSystemBook'])) {
336
-				$avatar = $this->urlGenerator->linkToRouteAbsolute('core.avatar.getAvatar', ['userId' => $uid, 'size' => 64]);
337
-				$entry->setProperty('isUser', true);
338
-			} elseif (!empty($contact['FN'])) {
339
-				$avatar = $this->urlGenerator->linkToRouteAbsolute('core.GuestAvatar.getAvatar', ['guestName' => str_replace('/', ' ', $contact['FN']), 'size' => 64]);
340
-			} else {
341
-				$avatar = $this->urlGenerator->linkToRouteAbsolute('core.GuestAvatar.getAvatar', ['guestName' => str_replace('/', ' ', $uid), 'size' => 64]);
342
-			}
343
-			$entry->setAvatar($avatar);
344
-		}
345
-
346
-		if (!empty($contact['FN'])) {
347
-			$entry->setFullName($contact['FN']);
348
-		}
349
-
350
-		$avatarPrefix = 'VALUE=uri:';
351
-		if (!empty($contact['PHOTO']) && str_starts_with($contact['PHOTO'], $avatarPrefix)) {
352
-			$entry->setAvatar(substr($contact['PHOTO'], strlen($avatarPrefix)));
353
-		}
354
-
355
-		if (!empty($contact['EMAIL'])) {
356
-			foreach ($contact['EMAIL'] as $email) {
357
-				$entry->addEMailAddress($email);
358
-			}
359
-		}
360
-
361
-		// Provide profile parameters for core/src/OC/contactsmenu/contact.handlebars template
362
-		if (!empty($contact['UID']) && !empty($contact['FN'])) {
363
-			$targetUserId = $contact['UID'];
364
-			$targetUser = $this->userManager->get($targetUserId);
365
-			if (!empty($targetUser)) {
366
-				if ($this->profileManager->isProfileEnabled($targetUser)) {
367
-					$entry->setProfileTitle($this->l10nFactory->get('lib')->t('View profile'));
368
-					$entry->setProfileUrl($this->urlGenerator->linkToRouteAbsolute('profile.ProfilePage.index', ['targetUserId' => $targetUserId]));
369
-				}
370
-			}
371
-		}
372
-
373
-		// Attach all other properties to the entry too because some
374
-		// providers might make use of it.
375
-		$entry->setProperties($contact);
376
-
377
-		return $entry;
378
-	}
31
+    public function __construct(
32
+        private IManager $contactsManager,
33
+        private ?StatusService $userStatusService,
34
+        private IConfig $config,
35
+        private ProfileManager $profileManager,
36
+        private IUserManager $userManager,
37
+        private IURLGenerator $urlGenerator,
38
+        private IGroupManager $groupManager,
39
+        private KnownUserService $knownUserService,
40
+        private IL10NFactory $l10nFactory,
41
+    ) {
42
+    }
43
+
44
+    /**
45
+     * @return IEntry[]
46
+     */
47
+    public function getContacts(IUser $user, ?string $filter, ?int $limit = null, ?int $offset = null): array {
48
+        $options = [
49
+            'enumeration' => $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes',
50
+            'fullmatch' => $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match', 'yes') === 'yes',
51
+        ];
52
+        if ($limit !== null) {
53
+            $options['limit'] = $limit;
54
+        }
55
+        if ($offset !== null) {
56
+            $options['offset'] = $offset;
57
+        }
58
+        // Status integration only works without pagination and filters
59
+        if ($offset === null && ($filter === null || $filter === '')) {
60
+            $recentStatuses = $this->userStatusService?->findAllRecentStatusChanges($limit, $offset) ?? [];
61
+        } else {
62
+            $recentStatuses = [];
63
+        }
64
+
65
+        // Search by status if there is no filter and statuses are available
66
+        if (!empty($recentStatuses)) {
67
+            $allContacts = array_filter(array_map(function (UserStatus $userStatus) use ($options) {
68
+                // UID is ambiguous with federation. We have to use the federated cloud ID to an exact match of
69
+                // A local user
70
+                $user = $this->userManager->get($userStatus->getUserId());
71
+                if ($user === null) {
72
+                    return null;
73
+                }
74
+
75
+                $contact = $this->contactsManager->search(
76
+                    $user->getCloudId(),
77
+                    [
78
+                        'CLOUD',
79
+                    ],
80
+                    array_merge(
81
+                        $options,
82
+                        [
83
+                            'limit' => 1,
84
+                            'offset' => 0,
85
+                        ],
86
+                    ),
87
+                )[0] ?? null;
88
+                if ($contact !== null) {
89
+                    $contact[Entry::PROPERTY_STATUS_MESSAGE_TIMESTAMP] = $userStatus->getStatusMessageTimestamp();
90
+                }
91
+                return $contact;
92
+            }, $recentStatuses));
93
+            if ($limit !== null && count($allContacts) < $limit) {
94
+                // More contacts were requested
95
+                $fromContacts = $this->contactsManager->search(
96
+                    $filter ?? '',
97
+                    [
98
+                        'FN',
99
+                        'EMAIL'
100
+                    ],
101
+                    array_merge(
102
+                        $options,
103
+                        [
104
+                            'limit' => $limit - count($allContacts),
105
+                        ],
106
+                    ),
107
+                );
108
+
109
+                // Create hash map of all status contacts
110
+                $existing = array_fill_keys(array_column($allContacts, 'URI'), null);
111
+                // Append the ones that are new
112
+                $allContacts = array_merge(
113
+                    $allContacts,
114
+                    array_filter($fromContacts, fn (array $contact): bool => !array_key_exists($contact['URI'], $existing))
115
+                );
116
+            }
117
+        } else {
118
+            $allContacts = $this->contactsManager->search(
119
+                $filter ?? '',
120
+                [
121
+                    'FN',
122
+                    'EMAIL'
123
+                ],
124
+                $options
125
+            );
126
+        }
127
+
128
+        $userId = $user->getUID();
129
+        $contacts = array_filter($allContacts, function ($contact) use ($userId) {
130
+            // When searching for multiple results, we strip out the current user
131
+            if (array_key_exists('UID', $contact)) {
132
+                return $contact['UID'] !== $userId;
133
+            }
134
+            return true;
135
+        });
136
+
137
+        $entries = array_map(function (array $contact) {
138
+            return $this->contactArrayToEntry($contact);
139
+        }, $contacts);
140
+        return $this->filterContacts(
141
+            $user,
142
+            $entries,
143
+            $filter
144
+        );
145
+    }
146
+
147
+    /**
148
+     * Filters the contacts. Applied filters:
149
+     *  1. if the `shareapi_allow_share_dialog_user_enumeration` config option is
150
+     * enabled it will filter all local users
151
+     *  2. if the `shareapi_exclude_groups` config option is enabled and the
152
+     * current user is only in excluded groups it will filter all local users.
153
+     *  3. if the `shareapi_only_share_with_group_members` config option is
154
+     * enabled it will filter all users which doesn't have a common group
155
+     * with the current user.
156
+     * If enabled, the 'shareapi_only_share_with_group_members_exclude_group_list'
157
+     * config option may specify some groups excluded from the principle of
158
+     * belonging to the same group.
159
+     *
160
+     * @param Entry[] $entries
161
+     * @return Entry[] the filtered contacts
162
+     */
163
+    private function filterContacts(
164
+        IUser $self,
165
+        array $entries,
166
+        ?string $filter,
167
+    ): array {
168
+        $disallowEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') !== 'yes';
169
+        $restrictEnumerationGroup = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes';
170
+        $restrictEnumerationPhone = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_phone', 'no') === 'yes';
171
+        $allowEnumerationFullMatch = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match', 'yes') === 'yes';
172
+        $excludeGroups = $this->config->getAppValue('core', 'shareapi_exclude_groups', 'no');
173
+
174
+        // whether to filter out local users
175
+        $skipLocal = false;
176
+        // whether to filter out all users which don't have a common group as the current user
177
+        $ownGroupsOnly = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes';
178
+
179
+        $selfGroups = $this->groupManager->getUserGroupIds($self);
180
+
181
+        if ($excludeGroups && $excludeGroups !== 'no') {
182
+            $excludedGroups = $this->config->getAppValue('core', 'shareapi_exclude_groups_list', '');
183
+            $decodedExcludeGroups = json_decode($excludedGroups, true);
184
+            $excludeGroupsList = $decodedExcludeGroups ?? [];
185
+
186
+            if ($excludeGroups != 'allow') {
187
+                if (count($selfGroups) > 0 && count(array_diff($selfGroups, $excludeGroupsList)) === 0) {
188
+                    // all the groups of the current user are excluded -> filter all local users
189
+                    $skipLocal = true;
190
+                }
191
+            } else {
192
+                $skipLocal = true;
193
+                if (count(array_intersect($excludeGroupsList, $selfGroups)) !== 0) {
194
+                    // a group of the current user is allowed -> do not filter all local users
195
+                    $skipLocal = false;
196
+                }
197
+            }
198
+        }
199
+
200
+        // ownGroupsOnly : some groups may be excluded
201
+        if ($ownGroupsOnly) {
202
+            $excludeGroupsFromOwnGroups = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members_exclude_group_list', '');
203
+            $excludeGroupsFromOwnGroupsList = json_decode($excludeGroupsFromOwnGroups, true) ?? [];
204
+            $selfGroups = array_diff($selfGroups, $excludeGroupsFromOwnGroupsList);
205
+        }
206
+
207
+        $selfUID = $self->getUID();
208
+
209
+        return array_values(array_filter($entries, function (IEntry $entry) use ($skipLocal, $ownGroupsOnly, $selfGroups, $selfUID, $disallowEnumeration, $restrictEnumerationGroup, $restrictEnumerationPhone, $allowEnumerationFullMatch, $filter) {
210
+            if ($entry->getProperty('isLocalSystemBook')) {
211
+                if ($skipLocal) {
212
+                    return false;
213
+                }
214
+
215
+                $checkedCommonGroupAlready = false;
216
+
217
+                // Prevent enumerating local users
218
+                if ($disallowEnumeration) {
219
+                    if (!$allowEnumerationFullMatch) {
220
+                        return false;
221
+                    }
222
+
223
+                    $filterOutUser = true;
224
+
225
+                    $mailAddresses = $entry->getEMailAddresses();
226
+                    foreach ($mailAddresses as $mailAddress) {
227
+                        if ($mailAddress === $filter) {
228
+                            $filterOutUser = false;
229
+                            break;
230
+                        }
231
+                    }
232
+
233
+                    if ($entry->getProperty('UID') && $entry->getProperty('UID') === $filter) {
234
+                        $filterOutUser = false;
235
+                    }
236
+
237
+                    if ($filterOutUser) {
238
+                        return false;
239
+                    }
240
+                } elseif ($restrictEnumerationPhone || $restrictEnumerationGroup) {
241
+                    $canEnumerate = false;
242
+                    if ($restrictEnumerationPhone) {
243
+                        $canEnumerate = $this->knownUserService->isKnownToUser($selfUID, $entry->getProperty('UID'));
244
+                    }
245
+
246
+                    if (!$canEnumerate && $restrictEnumerationGroup) {
247
+                        $user = $this->userManager->get($entry->getProperty('UID'));
248
+
249
+                        if ($user === null) {
250
+                            return false;
251
+                        }
252
+
253
+                        $contactGroups = $this->groupManager->getUserGroupIds($user);
254
+                        $canEnumerate = !empty(array_intersect($contactGroups, $selfGroups));
255
+                        $checkedCommonGroupAlready = true;
256
+                    }
257
+
258
+                    if (!$canEnumerate) {
259
+                        return false;
260
+                    }
261
+                }
262
+
263
+                if ($ownGroupsOnly && !$checkedCommonGroupAlready) {
264
+                    $user = $this->userManager->get($entry->getProperty('UID'));
265
+
266
+                    if (!$user instanceof IUser) {
267
+                        return false;
268
+                    }
269
+
270
+                    $contactGroups = $this->groupManager->getUserGroupIds($user);
271
+                    if (empty(array_intersect($contactGroups, $selfGroups))) {
272
+                        // no groups in common, so shouldn't see the contact
273
+                        return false;
274
+                    }
275
+                }
276
+            }
277
+
278
+            return true;
279
+        }));
280
+    }
281
+
282
+    public function findOne(IUser $user, int $shareType, string $shareWith): ?IEntry {
283
+        switch ($shareType) {
284
+            case 0:
285
+            case 6:
286
+                $filter = ['UID'];
287
+                break;
288
+            case 4:
289
+                $filter = ['EMAIL'];
290
+                break;
291
+            default:
292
+                return null;
293
+        }
294
+
295
+        $contacts = $this->contactsManager->search($shareWith, $filter, [
296
+            'strict_search' => true,
297
+        ]);
298
+        $match = null;
299
+
300
+        foreach ($contacts as $contact) {
301
+            if ($shareType === 4 && isset($contact['EMAIL'])) {
302
+                if (in_array($shareWith, $contact['EMAIL'])) {
303
+                    $match = $contact;
304
+                    break;
305
+                }
306
+            }
307
+            if ($shareType === 0 || $shareType === 6) {
308
+                if (($contact['isLocalSystemBook'] ?? false) === true && $contact['UID'] === $shareWith) {
309
+                    $match = $contact;
310
+                    break;
311
+                }
312
+            }
313
+        }
314
+
315
+        if ($match) {
316
+            $match = $this->filterContacts($user, [$this->contactArrayToEntry($match)], $shareWith);
317
+            if (count($match) === 1) {
318
+                $match = $match[0];
319
+            } else {
320
+                $match = null;
321
+            }
322
+        }
323
+
324
+        return $match;
325
+    }
326
+
327
+    private function contactArrayToEntry(array $contact): Entry {
328
+        $entry = new Entry();
329
+
330
+        if (!empty($contact['UID'])) {
331
+            $uid = $contact['UID'];
332
+            $entry->setId($uid);
333
+            $entry->setProperty('isUser', false);
334
+            // overloaded usage so leaving as-is for now
335
+            if (isset($contact['isLocalSystemBook'])) {
336
+                $avatar = $this->urlGenerator->linkToRouteAbsolute('core.avatar.getAvatar', ['userId' => $uid, 'size' => 64]);
337
+                $entry->setProperty('isUser', true);
338
+            } elseif (!empty($contact['FN'])) {
339
+                $avatar = $this->urlGenerator->linkToRouteAbsolute('core.GuestAvatar.getAvatar', ['guestName' => str_replace('/', ' ', $contact['FN']), 'size' => 64]);
340
+            } else {
341
+                $avatar = $this->urlGenerator->linkToRouteAbsolute('core.GuestAvatar.getAvatar', ['guestName' => str_replace('/', ' ', $uid), 'size' => 64]);
342
+            }
343
+            $entry->setAvatar($avatar);
344
+        }
345
+
346
+        if (!empty($contact['FN'])) {
347
+            $entry->setFullName($contact['FN']);
348
+        }
349
+
350
+        $avatarPrefix = 'VALUE=uri:';
351
+        if (!empty($contact['PHOTO']) && str_starts_with($contact['PHOTO'], $avatarPrefix)) {
352
+            $entry->setAvatar(substr($contact['PHOTO'], strlen($avatarPrefix)));
353
+        }
354
+
355
+        if (!empty($contact['EMAIL'])) {
356
+            foreach ($contact['EMAIL'] as $email) {
357
+                $entry->addEMailAddress($email);
358
+            }
359
+        }
360
+
361
+        // Provide profile parameters for core/src/OC/contactsmenu/contact.handlebars template
362
+        if (!empty($contact['UID']) && !empty($contact['FN'])) {
363
+            $targetUserId = $contact['UID'];
364
+            $targetUser = $this->userManager->get($targetUserId);
365
+            if (!empty($targetUser)) {
366
+                if ($this->profileManager->isProfileEnabled($targetUser)) {
367
+                    $entry->setProfileTitle($this->l10nFactory->get('lib')->t('View profile'));
368
+                    $entry->setProfileUrl($this->urlGenerator->linkToRouteAbsolute('profile.ProfilePage.index', ['targetUserId' => $targetUserId]));
369
+                }
370
+            }
371
+        }
372
+
373
+        // Attach all other properties to the entry too because some
374
+        // providers might make use of it.
375
+        $entry->setProperties($contact);
376
+
377
+        return $entry;
378
+    }
379 379
 }
Please login to merge, or discard this patch.
tests/lib/Contacts/ContactsMenu/ContactsStoreTest.php 1 patch
Indentation   +1127 added lines, -1127 removed lines patch added patch discarded remove patch
@@ -25,1131 +25,1131 @@
 block discarded – undo
25 25
 use Test\TestCase;
26 26
 
27 27
 class ContactsStoreTest extends TestCase {
28
-	private ContactsStore $contactsStore;
29
-	private StatusService|MockObject $statusService;
30
-	/** @var IManager|MockObject */
31
-	private $contactsManager;
32
-	/** @var ProfileManager */
33
-	private $profileManager;
34
-	/** @var IUserManager|MockObject */
35
-	private $userManager;
36
-	/** @var IURLGenerator */
37
-	private $urlGenerator;
38
-	/** @var IGroupManager|MockObject */
39
-	private $groupManager;
40
-	/** @var IConfig|MockObject */
41
-	private $config;
42
-	/** @var KnownUserService|MockObject */
43
-	private $knownUserService;
44
-	/** @var IL10NFactory */
45
-	private $l10nFactory;
46
-
47
-	protected function setUp(): void {
48
-		parent::setUp();
49
-
50
-		$this->contactsManager = $this->createMock(IManager::class);
51
-		$this->statusService = $this->createMock(StatusService::class);
52
-		$this->userManager = $this->createMock(IUserManager::class);
53
-		$this->profileManager = $this->createMock(ProfileManager::class);
54
-		$this->urlGenerator = $this->createMock(IURLGenerator::class);
55
-		$this->groupManager = $this->createMock(IGroupManager::class);
56
-		$this->config = $this->createMock(IConfig::class);
57
-		$this->knownUserService = $this->createMock(KnownUserService::class);
58
-		$this->l10nFactory = $this->createMock(IL10NFactory::class);
59
-		$this->contactsStore = new ContactsStore(
60
-			$this->contactsManager,
61
-			$this->statusService,
62
-			$this->config,
63
-			$this->profileManager,
64
-			$this->userManager,
65
-			$this->urlGenerator,
66
-			$this->groupManager,
67
-			$this->knownUserService,
68
-			$this->l10nFactory,
69
-		);
70
-	}
71
-
72
-	public function testGetContactsWithoutFilter(): void {
73
-		/** @var IUser|MockObject $user */
74
-		$user = $this->createMock(IUser::class);
75
-		$this->contactsManager->expects($this->once())
76
-			->method('search')
77
-			->with($this->equalTo(''), $this->equalTo(['FN', 'EMAIL']))
78
-			->willReturn([
79
-				[
80
-					'UID' => 123,
81
-				],
82
-				[
83
-					'UID' => 567,
84
-					'FN' => 'Darren Roner',
85
-					'EMAIL' => [
86
-						'[email protected]'
87
-					],
88
-				],
89
-			]);
90
-		$user->expects($this->exactly(2))
91
-			->method('getUID')
92
-			->willReturn('user123');
93
-
94
-		$entries = $this->contactsStore->getContacts($user, '');
95
-
96
-		$this->assertCount(2, $entries);
97
-		$this->assertEquals([
98
-			'[email protected]'
99
-		], $entries[1]->getEMailAddresses());
100
-	}
101
-
102
-	public function testGetContactsHidesOwnEntry(): void {
103
-		/** @var IUser|MockObject $user */
104
-		$user = $this->createMock(IUser::class);
105
-		$this->contactsManager->expects($this->once())
106
-			->method('search')
107
-			->with($this->equalTo(''), $this->equalTo(['FN', 'EMAIL']))
108
-			->willReturn([
109
-				[
110
-					'UID' => 'user123',
111
-				],
112
-				[
113
-					'UID' => 567,
114
-					'FN' => 'Darren Roner',
115
-					'EMAIL' => [
116
-						'[email protected]'
117
-					],
118
-				],
119
-			]);
120
-		$user->expects($this->exactly(2))
121
-			->method('getUID')
122
-			->willReturn('user123');
123
-
124
-		$entries = $this->contactsStore->getContacts($user, '');
125
-
126
-		$this->assertCount(1, $entries);
127
-	}
128
-
129
-	public function testGetContactsWithoutBinaryImage(): void {
130
-		/** @var IUser|MockObject $user */
131
-		$user = $this->createMock(IUser::class);
132
-		$this->urlGenerator->expects($this->any())
133
-			->method('linkToRouteAbsolute')
134
-			->with('core.GuestAvatar.getAvatar', $this->anything())
135
-			->willReturn('https://urlToNcAvatar.test');
136
-		$this->contactsManager->expects($this->once())
137
-			->method('search')
138
-			->with($this->equalTo(''), $this->equalTo(['FN', 'EMAIL']))
139
-			->willReturn([
140
-				[
141
-					'UID' => 123,
142
-				],
143
-				[
144
-					'UID' => 567,
145
-					'FN' => 'Darren Roner',
146
-					'EMAIL' => [
147
-						'[email protected]'
148
-					],
149
-					'PHOTO' => base64_encode('photophotophoto'),
150
-				],
151
-			]);
152
-		$user->expects($this->exactly(2))
153
-			->method('getUID')
154
-			->willReturn('user123');
155
-
156
-		$entries = $this->contactsStore->getContacts($user, '');
157
-
158
-		$this->assertCount(2, $entries);
159
-		$this->assertSame('https://urlToNcAvatar.test', $entries[1]->getAvatar());
160
-	}
161
-
162
-	public function testGetContactsWithoutAvatarURI(): void {
163
-		/** @var IUser|MockObject $user */
164
-		$user = $this->createMock(IUser::class);
165
-		$this->contactsManager->expects($this->once())
166
-			->method('search')
167
-			->with($this->equalTo(''), $this->equalTo(['FN', 'EMAIL']))
168
-			->willReturn([
169
-				[
170
-					'UID' => 123,
171
-				],
172
-				[
173
-					'UID' => 567,
174
-					'FN' => 'Darren Roner',
175
-					'EMAIL' => [
176
-						'[email protected]'
177
-					],
178
-					'PHOTO' => 'VALUE=uri:https://photo',
179
-				],
180
-			]);
181
-		$user->expects($this->exactly(2))
182
-			->method('getUID')
183
-			->willReturn('user123');
184
-
185
-		$entries = $this->contactsStore->getContacts($user, '');
186
-
187
-		$this->assertCount(2, $entries);
188
-		$this->assertEquals('https://photo', $entries[1]->getAvatar());
189
-	}
190
-
191
-	public static function dataGetContactsWhenUserIsInExcludeGroups(): array {
192
-		return [
193
-			['yes', '[]', [], ['user123', 'user12345']],
194
-			['yes', '["excludedGroup1"]', [], ['user123', 'user12345']],
195
-			['yes', '["excludedGroup1"]', ['anotherGroup1'], ['user123', 'user12345']],
196
-			['yes', '["excludedGroup1"]', ['anotherGroup1', 'anotherGroup2', 'anotherGroup3'], ['user123', 'user12345']],
197
-			['yes', '["excludedGroup1"]', ['excludedGroup1'], []],
198
-			['yes', '["excludedGroup1"]', ['anotherGroup1', 'excludedGroup1'], ['user123', 'user12345']],
199
-			['yes', '["excludedGroup1"]', ['excludedGroup1', 'anotherGroup1', 'anotherGroup2', 'anotherGroup3'], ['user123', 'user12345']],
200
-			['yes', '["excludedGroup1", "excludedGroup2", "excludedGroup3"]', [], ['user123', 'user12345']],
201
-			['yes', '["excludedGroup1", "excludedGroup2", "excludedGroup3"]', ['anotherGroup1'], ['user123', 'user12345']],
202
-			['yes', '["excludedGroup1", "excludedGroup2", "excludedGroup3"]', ['anotherGroup1', 'anotherGroup2', 'anotherGroup3'], ['user123', 'user12345']],
203
-			['yes', '["excludedGroup1", "excludedGroup2", "excludedGroup3"]', ['excludedGroup1'], []],
204
-			['yes', '["excludedGroup1", "excludedGroup2", "excludedGroup3"]', ['excludedGroup2'], []],
205
-			['yes', '["excludedGroup1", "excludedGroup2", "excludedGroup3"]', ['excludedGroup3'], []],
206
-			['yes', '["excludedGroup1", "excludedGroup2", "excludedGroup3"]', ['excludedGroup1', 'excludedGroup2', 'excludedGroup3'], []],
207
-			['yes', '["excludedGroup1", "excludedGroup2", "excludedGroup3"]', ['anotherGroup1', 'excludedGroup1'], ['user123', 'user12345']],
208
-			['yes', '["excludedGroup1", "excludedGroup2", "excludedGroup3"]', ['anotherGroup1', 'excludedGroup2', 'anotherGroup2', 'anotherGroup3'], ['user123', 'user12345']],
209
-			['yes', '["excludedGroup1", "excludedGroup2", "excludedGroup3"]', ['excludedGroup3', 'anotherGroup1', 'anotherGroup2', 'anotherGroup3'], ['user123', 'user12345']],
210
-			['allow', '[]', [], []],
211
-			['allow', '["allowedGroup1"]', [], []],
212
-			['allow', '["allowedGroup1"]', ['anotherGroup1'], []],
213
-			['allow', '["allowedGroup1"]', ['anotherGroup1', 'anotherGroup2', 'anotherGroup3'], []],
214
-			['allow', '["allowedGroup1"]', ['allowedGroup1'], ['user123', 'user12345']],
215
-			['allow', '["allowedGroup1"]', ['anotherGroup1', 'allowedGroup1'], ['user123', 'user12345']],
216
-			['allow', '["allowedGroup1"]', ['allowedGroup1', 'anotherGroup1', 'anotherGroup2', 'anotherGroup3'], ['user123', 'user12345']],
217
-			['allow', '["allowedGroup1", "allowedGroup2", "allowedGroup3"]', [], []],
218
-			['allow', '["allowedGroup1", "allowedGroup2", "allowedGroup3"]', ['anotherGroup1'], []],
219
-			['allow', '["allowedGroup1", "allowedGroup2", "allowedGroup3"]', ['anotherGroup1', 'anotherGroup2', 'anotherGroup3'], []],
220
-			['allow', '["allowedGroup1", "allowedGroup2", "allowedGroup3"]', ['allowedGroup1'], ['user123', 'user12345']],
221
-			['allow', '["allowedGroup1", "allowedGroup2", "allowedGroup3"]', ['allowedGroup2'], ['user123', 'user12345']],
222
-			['allow', '["allowedGroup1", "allowedGroup2", "allowedGroup3"]', ['allowedGroup3'], ['user123', 'user12345']],
223
-			['allow', '["allowedGroup1", "allowedGroup2", "allowedGroup3"]', ['allowedGroup1', 'allowedGroup2', 'allowedGroup3'], ['user123', 'user12345']],
224
-			['allow', '["allowedGroup1", "allowedGroup2", "allowedGroup3"]', ['anotherGroup1', 'allowedGroup1'], ['user123', 'user12345']],
225
-			['allow', '["allowedGroup1", "allowedGroup2", "allowedGroup3"]', ['anotherGroup1', 'allowedGroup2', 'anotherGroup2', 'anotherGroup3'], ['user123', 'user12345']],
226
-			['allow', '["allowedGroup1", "allowedGroup2", "allowedGroup3"]', ['allowedGroup3', 'anotherGroup1', 'anotherGroup2', 'anotherGroup3'], ['user123', 'user12345']],
227
-		];
228
-	}
229
-
230
-	#[\PHPUnit\Framework\Attributes\DataProvider('dataGetContactsWhenUserIsInExcludeGroups')]
231
-	public function testGetContactsWhenUserIsInExcludeGroups(string $excludeGroups, string $excludeGroupsList, array $currentUserGroupIds, array $expectedUids): void {
232
-		$this->config
233
-			->method('getAppValue')
234
-			->willReturnMap([
235
-				['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'yes'],
236
-				['core', 'shareapi_restrict_user_enumeration_to_group', 'no', 'no'],
237
-				['core', 'shareapi_restrict_user_enumeration_to_phone', 'no', 'no'],
238
-				['core', 'shareapi_exclude_groups', 'no', $excludeGroups],
239
-				['core', 'shareapi_only_share_with_group_members', 'no', 'no'],
240
-				['core', 'shareapi_exclude_groups_list', '', $excludeGroupsList],
241
-				['core', 'shareapi_only_share_with_group_members_exclude_group_list', '', '[]'],
242
-			]);
243
-
244
-		/** @var IUser|MockObject $currentUser */
245
-		$currentUser = $this->createMock(IUser::class);
246
-		$currentUser->expects($this->exactly(2))
247
-			->method('getUID')
248
-			->willReturn('user001');
249
-
250
-		$this->groupManager->expects($this->once())
251
-			->method('getUserGroupIds')
252
-			->with($this->equalTo($currentUser))
253
-			->willReturn($currentUserGroupIds);
254
-
255
-		$this->contactsManager->expects($this->once())
256
-			->method('search')
257
-			->with($this->equalTo(''), $this->equalTo(['FN', 'EMAIL']))
258
-			->willReturn([
259
-				[
260
-					'UID' => 'user123',
261
-					'isLocalSystemBook' => true
262
-				],
263
-				[
264
-					'UID' => 'user12345',
265
-					'isLocalSystemBook' => true
266
-				],
267
-			]);
268
-
269
-
270
-		$entries = $this->contactsStore->getContacts($currentUser, '');
271
-
272
-		$this->assertCount(count($expectedUids), $entries);
273
-		for ($i = 0; $i < count($expectedUids); $i++) {
274
-			$this->assertEquals($expectedUids[$i], $entries[$i]->getProperty('UID'));
275
-		}
276
-	}
277
-
278
-	public function testGetContactsOnlyShareIfInTheSameGroupWhenUserIsInExcludeGroups(): void {
279
-		$this->config
280
-			->method('getAppValue')
281
-			->willReturnMap([
282
-				['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'yes'],
283
-				['core', 'shareapi_restrict_user_enumeration_to_group', 'no', 'no'],
284
-				['core', 'shareapi_restrict_user_enumeration_to_phone', 'no', 'no'],
285
-				['core', 'shareapi_exclude_groups', 'no', 'yes'],
286
-				['core', 'shareapi_only_share_with_group_members', 'no', 'yes'],
287
-				['core', 'shareapi_exclude_groups_list', '', '["group1", "group5", "group6"]'],
288
-				['core', 'shareapi_only_share_with_group_members_exclude_group_list', '', '[]'],
289
-			]);
290
-
291
-		/** @var IUser|MockObject $currentUser */
292
-		$currentUser = $this->createMock(IUser::class);
293
-		$currentUser->expects($this->exactly(2))
294
-			->method('getUID')
295
-			->willReturn('user001');
296
-
297
-		$this->groupManager->expects($this->once())
298
-			->method('getUserGroupIds')
299
-			->with($this->equalTo($currentUser))
300
-			->willReturn(['group1', 'group2', 'group3']);
301
-
302
-
303
-		$this->contactsManager->expects($this->once())
304
-			->method('search')
305
-			->with($this->equalTo(''), $this->equalTo(['FN', 'EMAIL']))
306
-			->willReturn([
307
-				[
308
-					'UID' => 'user123',
309
-					'isLocalSystemBook' => true
310
-				],
311
-				[
312
-					'UID' => 'user12345',
313
-					'isLocalSystemBook' => true
314
-				],
315
-			]);
316
-
317
-
318
-		$entries = $this->contactsStore->getContacts($currentUser, '');
319
-
320
-		$this->assertCount(0, $entries);
321
-	}
322
-
323
-	public function testGetContactsOnlyShareIfInTheSameGroup(): void {
324
-		$this->config
325
-			->method('getAppValue')
326
-			->willReturnMap([
327
-				['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'yes'],
328
-				['core', 'shareapi_restrict_user_enumeration_to_group', 'no', 'no'],
329
-				['core', 'shareapi_restrict_user_enumeration_to_phone', 'no', 'no'],
330
-				['core', 'shareapi_exclude_groups', 'no', 'no'],
331
-				['core', 'shareapi_only_share_with_group_members', 'no', 'yes'],
332
-				['core', 'shareapi_only_share_with_group_members_exclude_group_list', '', '[]'],
333
-			]);
334
-
335
-		/** @var IUser|MockObject $currentUser */
336
-		$currentUser = $this->createMock(IUser::class);
337
-		$currentUser->expects($this->exactly(2))
338
-			->method('getUID')
339
-			->willReturn('user001');
340
-
341
-		$user1 = $this->createMock(IUser::class);
342
-		$user2 = $this->createMock(IUser::class);
343
-		$user3 = $this->createMock(IUser::class);
344
-
345
-		$calls = [
346
-			[[$currentUser], ['group1', 'group2', 'group3']],
347
-			[[$user1], ['group1']],
348
-			[[$user2], ['group2', 'group3']],
349
-			[[$user3], ['group8', 'group9']],
350
-		];
351
-		$this->groupManager->expects($this->exactly(4))
352
-			->method('getUserGroupIds')
353
-			->willReturnCallback(function () use (&$calls): array {
354
-				$expected = array_shift($calls);
355
-				$this->assertEquals($expected[0], func_get_args());
356
-				return $expected[1];
357
-			});
358
-
359
-		$this->userManager->expects($this->exactly(3))
360
-			->method('get')
361
-			->willReturnMap([
362
-				['user1', $user1],
363
-				['user2', $user2],
364
-				['user3', $user3],
365
-			]);
366
-
367
-		$this->contactsManager->expects($this->once())
368
-			->method('search')
369
-			->with($this->equalTo(''), $this->equalTo(['FN', 'EMAIL']))
370
-			->willReturn([
371
-				[
372
-					'UID' => 'user1',
373
-					'isLocalSystemBook' => true
374
-				],
375
-				[
376
-					'UID' => 'user2',
377
-					'isLocalSystemBook' => true
378
-				],
379
-				[
380
-					'UID' => 'user3',
381
-					'isLocalSystemBook' => true
382
-				],
383
-				[
384
-					'UID' => 'contact',
385
-				],
386
-			]);
387
-
388
-		$entries = $this->contactsStore->getContacts($currentUser, '');
389
-
390
-		$this->assertCount(3, $entries);
391
-		$this->assertEquals('user1', $entries[0]->getProperty('UID'));
392
-		$this->assertEquals('user2', $entries[1]->getProperty('UID'));
393
-		$this->assertEquals('contact', $entries[2]->getProperty('UID'));
394
-	}
395
-
396
-	public function testGetContactsOnlyEnumerateIfInTheSameGroup(): void {
397
-		$this->config
398
-			->method('getAppValue')
399
-			->willReturnMap([
400
-				['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'yes'],
401
-				['core', 'shareapi_restrict_user_enumeration_to_group', 'no', 'yes'],
402
-				['core', 'shareapi_restrict_user_enumeration_to_phone', 'no', 'no'],
403
-				['core', 'shareapi_exclude_groups', 'no', 'no'],
404
-				['core', 'shareapi_only_share_with_group_members', 'no', 'yes'],
405
-				['core', 'shareapi_only_share_with_group_members_exclude_group_list', '', '[]'],
406
-			]);
407
-
408
-		/** @var IUser|MockObject $currentUser */
409
-		$currentUser = $this->createMock(IUser::class);
410
-		$currentUser->expects($this->exactly(2))
411
-			->method('getUID')
412
-			->willReturn('user001');
413
-
414
-		$user1 = $this->createMock(IUser::class);
415
-		$user2 = $this->createMock(IUser::class);
416
-		$user3 = $this->createMock(IUser::class);
417
-
418
-		$calls = [
419
-			[[$currentUser], ['group1', 'group2', 'group3']],
420
-			[[$user1], ['group1']],
421
-			[[$user2], ['group2', 'group3']],
422
-			[[$user3], ['group8', 'group9']],
423
-		];
424
-		$this->groupManager->expects($this->exactly(4))
425
-			->method('getUserGroupIds')
426
-			->willReturnCallback(function () use (&$calls): array {
427
-				$expected = array_shift($calls);
428
-				$this->assertEquals($expected[0], func_get_args());
429
-				return $expected[1];
430
-			});
431
-
432
-		$this->userManager->expects($this->exactly(3))
433
-			->method('get')
434
-			->willReturnMap([
435
-				['user1', $user1],
436
-				['user2', $user2],
437
-				['user3', $user3],
438
-			]);
439
-
440
-		$this->contactsManager->expects($this->once())
441
-			->method('search')
442
-			->with($this->equalTo(''), $this->equalTo(['FN', 'EMAIL']))
443
-			->willReturn([
444
-				[
445
-					'UID' => 'user1',
446
-					'isLocalSystemBook' => true
447
-				],
448
-				[
449
-					'UID' => 'user2',
450
-					'isLocalSystemBook' => true
451
-				],
452
-				[
453
-					'UID' => 'user3',
454
-					'isLocalSystemBook' => true
455
-				],
456
-				[
457
-					'UID' => 'contact',
458
-				],
459
-			]);
460
-
461
-		$entries = $this->contactsStore->getContacts($currentUser, '');
462
-
463
-		$this->assertCount(3, $entries);
464
-		$this->assertEquals('user1', $entries[0]->getProperty('UID'));
465
-		$this->assertEquals('user2', $entries[1]->getProperty('UID'));
466
-		$this->assertEquals('contact', $entries[2]->getProperty('UID'));
467
-	}
468
-
469
-	public function testGetContactsOnlyEnumerateIfPhoneBookMatch(): void {
470
-		$this->config
471
-			->method('getAppValue')
472
-			->willReturnMap([
473
-				['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'yes'],
474
-				['core', 'shareapi_restrict_user_enumeration_to_group', 'no', 'no'],
475
-				['core', 'shareapi_restrict_user_enumeration_to_phone', 'no', 'yes'],
476
-				['core', 'shareapi_exclude_groups', 'no', 'no'],
477
-				['core', 'shareapi_only_share_with_group_members', 'no', 'no'],
478
-			]);
479
-
480
-		/** @var IUser|MockObject $currentUser */
481
-		$currentUser = $this->createMock(IUser::class);
482
-		$currentUser->expects($this->exactly(2))
483
-			->method('getUID')
484
-			->willReturn('user001');
485
-
486
-		$this->groupManager->expects($this->once())
487
-			->method('getUserGroupIds')
488
-			->with($this->equalTo($currentUser))
489
-			->willReturn(['group1', 'group2', 'group3']);
490
-
491
-		$this->knownUserService->method('isKnownToUser')
492
-			->willReturnMap([
493
-				['user001', 'user1', true],
494
-				['user001', 'user2', true],
495
-				['user001', 'user3', false],
496
-			]);
497
-
498
-		$this->contactsManager->expects($this->once())
499
-			->method('search')
500
-			->with($this->equalTo(''), $this->equalTo(['FN', 'EMAIL']))
501
-			->willReturn([
502
-				[
503
-					'UID' => 'user1',
504
-					'isLocalSystemBook' => true
505
-				],
506
-				[
507
-					'UID' => 'user2',
508
-					'isLocalSystemBook' => true
509
-				],
510
-				[
511
-					'UID' => 'user3',
512
-					'isLocalSystemBook' => true
513
-				],
514
-				[
515
-					'UID' => 'contact',
516
-				],
517
-			]);
518
-
519
-		$entries = $this->contactsStore->getContacts($currentUser, '');
520
-
521
-		$this->assertCount(3, $entries);
522
-		$this->assertEquals('user1', $entries[0]->getProperty('UID'));
523
-		$this->assertEquals('user2', $entries[1]->getProperty('UID'));
524
-		$this->assertEquals('contact', $entries[2]->getProperty('UID'));
525
-	}
526
-
527
-	public function testGetContactsOnlyEnumerateIfPhoneBookMatchWithOwnGroupsOnly(): void {
528
-		$this->config
529
-			->method('getAppValue')
530
-			->willReturnMap([
531
-				['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'yes'],
532
-				['core', 'shareapi_restrict_user_enumeration_to_group', 'no', 'no'],
533
-				['core', 'shareapi_restrict_user_enumeration_to_phone', 'no', 'yes'],
534
-				['core', 'shareapi_exclude_groups', 'no', 'no'],
535
-				['core', 'shareapi_only_share_with_group_members', 'no', 'yes'],
536
-				['core', 'shareapi_only_share_with_group_members_exclude_group_list', '', '[]'],
537
-			]);
538
-
539
-		/** @var IUser|MockObject $currentUser */
540
-		$currentUser = $this->createMock(IUser::class);
541
-		$currentUser->expects($this->exactly(2))
542
-			->method('getUID')
543
-			->willReturn('user001');
544
-
545
-		$user1 = $this->createMock(IUser::class);
546
-		$user2 = $this->createMock(IUser::class);
547
-		$user3 = $this->createMock(IUser::class);
548
-
549
-		$calls = [
550
-			[[$currentUser], ['group1', 'group2', 'group3']],
551
-			[[$user1], ['group1']],
552
-			[[$user2], ['group2', 'group3']],
553
-			[[$user3], ['group8', 'group9']],
554
-		];
555
-		$this->groupManager->expects($this->exactly(4))
556
-			->method('getUserGroupIds')
557
-			->willReturnCallback(function () use (&$calls): array {
558
-				$expected = array_shift($calls);
559
-				$this->assertEquals($expected[0], func_get_args());
560
-				return $expected[1];
561
-			});
562
-
563
-		$this->userManager->expects($this->exactly(3))
564
-			->method('get')
565
-			->willReturnMap([
566
-				['user1', $user1],
567
-				['user2', $user2],
568
-				['user3', $user3],
569
-			]);
570
-
571
-		$this->knownUserService->method('isKnownToUser')
572
-			->willReturnMap([
573
-				['user001', 'user1', true],
574
-				['user001', 'user2', true],
575
-				['user001', 'user3', true],
576
-			]);
577
-
578
-		$this->contactsManager->expects($this->once())
579
-			->method('search')
580
-			->with($this->equalTo(''), $this->equalTo(['FN', 'EMAIL']))
581
-			->willReturn([
582
-				[
583
-					'UID' => 'user1',
584
-					'isLocalSystemBook' => true
585
-				],
586
-				[
587
-					'UID' => 'user2',
588
-					'isLocalSystemBook' => true
589
-				],
590
-				[
591
-					'UID' => 'user3',
592
-					'isLocalSystemBook' => true
593
-				],
594
-				[
595
-					'UID' => 'contact',
596
-				],
597
-			]);
598
-
599
-		$entries = $this->contactsStore->getContacts($currentUser, '');
600
-
601
-		$this->assertCount(3, $entries);
602
-		$this->assertEquals('user1', $entries[0]->getProperty('UID'));
603
-		$this->assertEquals('user2', $entries[1]->getProperty('UID'));
604
-		$this->assertEquals('contact', $entries[2]->getProperty('UID'));
605
-	}
606
-
607
-	public function testGetContactsOnlyEnumerateIfPhoneBookOrSameGroup(): void {
608
-		$this->config
609
-			->method('getAppValue')
610
-			->willReturnMap([
611
-				['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'yes'],
612
-				['core', 'shareapi_restrict_user_enumeration_to_group', 'no', 'yes'],
613
-				['core', 'shareapi_restrict_user_enumeration_to_phone', 'no', 'yes'],
614
-				['core', 'shareapi_exclude_groups', 'no', 'no'],
615
-				['core', 'shareapi_only_share_with_group_members', 'no', 'no'],
616
-			]);
617
-
618
-		/** @var IUser|MockObject $currentUser */
619
-		$currentUser = $this->createMock(IUser::class);
620
-		$currentUser->expects($this->exactly(2))
621
-			->method('getUID')
622
-			->willReturn('user001');
623
-
624
-		$user1 = $this->createMock(IUser::class);
625
-
626
-		$calls = [
627
-			[[$currentUser], ['group1', 'group2', 'group3']],
628
-			[[$user1], ['group1']],
629
-		];
630
-		$this->groupManager->expects($this->exactly(2))
631
-			->method('getUserGroupIds')
632
-			->willReturnCallback(function () use (&$calls): array {
633
-				$expected = array_shift($calls);
634
-				$this->assertEquals($expected[0], func_get_args());
635
-				return $expected[1];
636
-			});
637
-
638
-		$this->userManager->expects($this->once())
639
-			->method('get')
640
-			->with('user1')
641
-			->willReturn($user1);
642
-
643
-		$this->knownUserService->method('isKnownToUser')
644
-			->willReturnMap([
645
-				['user001', 'user1', false],
646
-				['user001', 'user2', true],
647
-				['user001', 'user3', true],
648
-			]);
649
-
650
-		$this->contactsManager->expects($this->once())
651
-			->method('search')
652
-			->with($this->equalTo(''), $this->equalTo(['FN', 'EMAIL']))
653
-			->willReturn([
654
-				[
655
-					'UID' => 'user1',
656
-					'isLocalSystemBook' => true
657
-				],
658
-				[
659
-					'UID' => 'user2',
660
-					'isLocalSystemBook' => true
661
-				],
662
-				[
663
-					'UID' => 'user3',
664
-					'isLocalSystemBook' => true
665
-				],
666
-				[
667
-					'UID' => 'contact',
668
-				],
669
-			]);
670
-
671
-		$entries = $this->contactsStore->getContacts($currentUser, '');
672
-
673
-		$this->assertCount(4, $entries);
674
-		$this->assertEquals('user1', $entries[0]->getProperty('UID'));
675
-		$this->assertEquals('user2', $entries[1]->getProperty('UID'));
676
-		$this->assertEquals('user3', $entries[2]->getProperty('UID'));
677
-		$this->assertEquals('contact', $entries[3]->getProperty('UID'));
678
-	}
679
-
680
-	public function testGetContactsOnlyEnumerateIfPhoneBookOrSameGroupInOwnGroupsOnly(): void {
681
-		$this->config
682
-			->method('getAppValue')
683
-			->willReturnMap([
684
-				['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'yes'],
685
-				['core', 'shareapi_restrict_user_enumeration_to_group', 'no', 'yes'],
686
-				['core', 'shareapi_restrict_user_enumeration_to_phone', 'no', 'yes'],
687
-				['core', 'shareapi_exclude_groups', 'no', 'no'],
688
-				['core', 'shareapi_only_share_with_group_members', 'no', 'yes'],
689
-				['core', 'shareapi_only_share_with_group_members_exclude_group_list', '', '[]'],
690
-			]);
691
-
692
-		/** @var IUser|MockObject $currentUser */
693
-		$currentUser = $this->createMock(IUser::class);
694
-		$currentUser->expects($this->exactly(2))
695
-			->method('getUID')
696
-			->willReturn('user001');
697
-
698
-		$user1 = $this->createMock(IUser::class);
699
-		$user2 = $this->createMock(IUser::class);
700
-		$user3 = $this->createMock(IUser::class);
701
-
702
-		$calls = [
703
-			[[$currentUser], ['group1', 'group2', 'group3']],
704
-			[[$user1], ['group1']],
705
-			[[$user2], ['group2', 'group3']],
706
-			[[$user3], ['group8', 'group9']],
707
-		];
708
-		$this->groupManager->expects($this->exactly(4))
709
-			->method('getUserGroupIds')
710
-			->willReturnCallback(function () use (&$calls): array {
711
-				$expected = array_shift($calls);
712
-				$this->assertEquals($expected[0], func_get_args());
713
-				return $expected[1];
714
-			});
715
-
716
-		$this->userManager->expects($this->exactly(3))
717
-			->method('get')
718
-			->willReturnMap([
719
-				['user1', $user1],
720
-				['user2', $user2],
721
-				['user3', $user3],
722
-			]);
723
-
724
-		$this->knownUserService->method('isKnownToUser')
725
-			->willReturnMap([
726
-				['user001', 'user1', false],
727
-				['user001', 'user2', true],
728
-				['user001', 'user3', true],
729
-			]);
730
-
731
-		$this->contactsManager->expects($this->once())
732
-			->method('search')
733
-			->with($this->equalTo(''), $this->equalTo(['FN', 'EMAIL']))
734
-			->willReturn([
735
-				[
736
-					'UID' => 'user1',
737
-					'isLocalSystemBook' => true
738
-				],
739
-				[
740
-					'UID' => 'user2',
741
-					'isLocalSystemBook' => true
742
-				],
743
-				[
744
-					'UID' => 'user3',
745
-					'isLocalSystemBook' => true
746
-				],
747
-				[
748
-					'UID' => 'contact',
749
-				],
750
-			]);
751
-
752
-		$entries = $this->contactsStore->getContacts($currentUser, '');
753
-
754
-		$this->assertCount(3, $entries);
755
-		$this->assertEquals('user1', $entries[0]->getProperty('UID'));
756
-		$this->assertEquals('user2', $entries[1]->getProperty('UID'));
757
-		$this->assertEquals('contact', $entries[2]->getProperty('UID'));
758
-	}
759
-
760
-	public function testGetContactsWithFilter(): void {
761
-		$this->config
762
-			->method('getAppValue')
763
-			->willReturnMap([
764
-				['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'no'],
765
-				['core', 'shareapi_restrict_user_enumeration_full_match', 'yes', 'yes'],
766
-			]);
767
-
768
-		/** @var IUser|MockObject $user */
769
-		$user = $this->createMock(IUser::class);
770
-		$this->contactsManager->expects($this->any())
771
-			->method('search')
772
-			->willReturn([
773
-				[
774
-					'UID' => 'a567',
775
-					'FN' => 'Darren Roner',
776
-					'EMAIL' => [
777
-						'[email protected]',
778
-					],
779
-					'isLocalSystemBook' => true,
780
-				],
781
-				[
782
-					'UID' => 'john',
783
-					'FN' => 'John Doe',
784
-					'EMAIL' => [
785
-						'[email protected]',
786
-					],
787
-					'isLocalSystemBook' => true,
788
-				],
789
-				[
790
-					'FN' => 'Anne D',
791
-					'EMAIL' => [
792
-						'[email protected]',
793
-					],
794
-					'isLocalSystemBook' => false,
795
-				],
796
-			]);
797
-		$user->expects($this->any())
798
-			->method('getUID')
799
-			->willReturn('user123');
800
-
801
-		// Complete match on UID should match
802
-		$entry = $this->contactsStore->getContacts($user, 'a567');
803
-		$this->assertSame(2, count($entry));
804
-		$this->assertEquals([
805
-			'[email protected]'
806
-		], $entry[0]->getEMailAddresses());
807
-
808
-		// Partial match on UID should not match
809
-		$entry = $this->contactsStore->getContacts($user, 'a56');
810
-		$this->assertSame(1, count($entry));
811
-		$this->assertEquals([
812
-			'[email protected]'
813
-		], $entry[0]->getEMailAddresses());
814
-
815
-		// Complete match on email should match
816
-		$entry = $this->contactsStore->getContacts($user, '[email protected]');
817
-		$this->assertSame(2, count($entry));
818
-		$this->assertEquals([
819
-			'[email protected]'
820
-		], $entry[0]->getEMailAddresses());
821
-		$this->assertEquals([
822
-			'[email protected]'
823
-		], $entry[1]->getEMailAddresses());
824
-
825
-		// Partial match on email should not match
826
-		$entry = $this->contactsStore->getContacts($user, '[email protected]');
827
-		$this->assertSame(1, count($entry));
828
-		$this->assertEquals([
829
-			'[email protected]'
830
-		], $entry[0]->getEMailAddresses());
831
-
832
-		// Match on FN should not match
833
-		$entry = $this->contactsStore->getContacts($user, 'Darren Roner');
834
-		$this->assertSame(1, count($entry));
835
-		$this->assertEquals([
836
-			'[email protected]'
837
-		], $entry[0]->getEMailAddresses());
838
-
839
-		// Don't filter users in local addressbook
840
-		$entry = $this->contactsStore->getContacts($user, 'Anne D');
841
-		$this->assertSame(1, count($entry));
842
-		$this->assertEquals([
843
-			'[email protected]'
844
-		], $entry[0]->getEMailAddresses());
845
-	}
846
-
847
-	public function testGetContactsWithFilterWithoutFullMatch(): void {
848
-		$this->config
849
-			->method('getAppValue')
850
-			->willReturnMap([
851
-				['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'no'],
852
-				['core', 'shareapi_restrict_user_enumeration_full_match', 'yes', 'no'],
853
-			]);
854
-
855
-		/** @var IUser|MockObject $user */
856
-		$user = $this->createMock(IUser::class);
857
-		$this->contactsManager->expects($this->any())
858
-			->method('search')
859
-			->willReturn([
860
-				[
861
-					'UID' => 'a567',
862
-					'FN' => 'Darren Roner',
863
-					'EMAIL' => [
864
-						'[email protected]',
865
-					],
866
-					'isLocalSystemBook' => true,
867
-				],
868
-				[
869
-					'UID' => 'john',
870
-					'FN' => 'John Doe',
871
-					'EMAIL' => [
872
-						'[email protected]',
873
-					],
874
-					'isLocalSystemBook' => true,
875
-				],
876
-				[
877
-					'FN' => 'Anne D',
878
-					'EMAIL' => [
879
-						'[email protected]',
880
-					],
881
-					'isLocalSystemBook' => false,
882
-				],
883
-			]);
884
-		$user->expects($this->any())
885
-			->method('getUID')
886
-			->willReturn('user123');
887
-
888
-		// Complete match on UID should not match
889
-		$entry = $this->contactsStore->getContacts($user, 'a567');
890
-		$this->assertSame(1, count($entry));
891
-		$this->assertEquals([
892
-			'[email protected]'
893
-		], $entry[0]->getEMailAddresses());
894
-
895
-		// Partial match on UID should not match
896
-		$entry = $this->contactsStore->getContacts($user, 'a56');
897
-		$this->assertSame(1, count($entry));
898
-		$this->assertEquals([
899
-			'[email protected]'
900
-		], $entry[0]->getEMailAddresses());
901
-
902
-		// Complete match on email should not match
903
-		$entry = $this->contactsStore->getContacts($user, '[email protected]');
904
-		$this->assertSame(1, count($entry));
905
-		$this->assertEquals([
906
-			'[email protected]'
907
-		], $entry[0]->getEMailAddresses());
908
-
909
-		// Partial match on email should not match
910
-		$entry = $this->contactsStore->getContacts($user, '[email protected]');
911
-		$this->assertSame(1, count($entry));
912
-		$this->assertEquals([
913
-			'[email protected]'
914
-		], $entry[0]->getEMailAddresses());
915
-
916
-		// Match on FN should not match
917
-		$entry = $this->contactsStore->getContacts($user, 'Darren Roner');
918
-		$this->assertSame(1, count($entry));
919
-		$this->assertEquals([
920
-			'[email protected]'
921
-		], $entry[0]->getEMailAddresses());
922
-
923
-		// Don't filter users in local addressbook
924
-		$entry = $this->contactsStore->getContacts($user, 'Anne D');
925
-		$this->assertSame(1, count($entry));
926
-		$this->assertEquals([
927
-			'[email protected]'
928
-		], $entry[0]->getEMailAddresses());
929
-	}
930
-
931
-	public function testFindOneUser(): void {
932
-		$this->config
933
-			->method('getAppValue')
934
-			->willReturnMap([
935
-				['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'yes'],
936
-				['core', 'shareapi_restrict_user_enumeration_to_group', 'no', 'no'],
937
-				['core', 'shareapi_restrict_user_enumeration_to_phone', 'no', 'no'],
938
-				['core', 'shareapi_restrict_user_enumeration_full_match', 'yes', 'yes'],
939
-				['core', 'shareapi_exclude_groups', 'no', 'yes'],
940
-				['core', 'shareapi_exclude_groups_list', '', ''],
941
-				['core', 'shareapi_only_share_with_group_members', 'no', 'no'],
942
-			]);
943
-
944
-		/** @var IUser|MockObject $user */
945
-		$user = $this->createMock(IUser::class);
946
-		$this->contactsManager->expects($this->once())
947
-			->method('search')
948
-			->with($this->equalTo('a567'), $this->equalTo(['UID']))
949
-			->willReturn([
950
-				[
951
-					'UID' => 123,
952
-					'isLocalSystemBook' => false
953
-				],
954
-				[
955
-					'UID' => 'a567',
956
-					'FN' => 'Darren Roner',
957
-					'EMAIL' => [
958
-						'[email protected]'
959
-					],
960
-					'isLocalSystemBook' => true
961
-				],
962
-			]);
963
-		$user->expects($this->any())
964
-			->method('getUID')
965
-			->willReturn('user123');
966
-
967
-		$entry = $this->contactsStore->findOne($user, 0, 'a567');
968
-
969
-		$this->assertEquals([
970
-			'[email protected]'
971
-		], $entry->getEMailAddresses());
972
-	}
973
-
974
-	public function testFindOneEMail(): void {
975
-		/** @var IUser|MockObject $user */
976
-		$user = $this->createMock(IUser::class);
977
-		$this->contactsManager->expects($this->once())
978
-			->method('search')
979
-			->with($this->equalTo('[email protected]'), $this->equalTo(['EMAIL']))
980
-			->willReturn([
981
-				[
982
-					'UID' => 123,
983
-					'isLocalSystemBook' => false
984
-				],
985
-				[
986
-					'UID' => 'a567',
987
-					'FN' => 'Darren Roner',
988
-					'EMAIL' => [
989
-						'[email protected]'
990
-					],
991
-					'isLocalSystemBook' => false
992
-				],
993
-			]);
994
-		$user->expects($this->any())
995
-			->method('getUID')
996
-			->willReturn('user123');
997
-
998
-		$entry = $this->contactsStore->findOne($user, 4, '[email protected]');
999
-
1000
-		$this->assertEquals([
1001
-			'[email protected]'
1002
-		], $entry->getEMailAddresses());
1003
-	}
1004
-
1005
-	public function testFindOneNotSupportedType(): void {
1006
-		/** @var IUser|MockObject $user */
1007
-		$user = $this->createMock(IUser::class);
1008
-
1009
-		$entry = $this->contactsStore->findOne($user, 42, '[email protected]');
1010
-
1011
-		$this->assertEquals(null, $entry);
1012
-	}
1013
-
1014
-	public function testFindOneNoMatches(): void {
1015
-		/** @var IUser|MockObject $user */
1016
-		$user = $this->createMock(IUser::class);
1017
-		$this->contactsManager->expects($this->once())
1018
-			->method('search')
1019
-			->with($this->equalTo('a567'), $this->equalTo(['UID']))
1020
-			->willReturn([
1021
-				[
1022
-					'UID' => 123,
1023
-					'isLocalSystemBook' => false
1024
-				],
1025
-				[
1026
-					'UID' => 'a567',
1027
-					'FN' => 'Darren Roner',
1028
-					'EMAIL' => [
1029
-						'[email protected]'
1030
-					],
1031
-					'isLocalSystemBook' => false
1032
-				],
1033
-			]);
1034
-		$user->expects($this->never())
1035
-			->method('getUID');
1036
-
1037
-		$entry = $this->contactsStore->findOne($user, 0, 'a567');
1038
-
1039
-		$this->assertEquals(null, $entry);
1040
-	}
1041
-
1042
-	public function testGetRecentStatusFirst(): void {
1043
-		$user = $this->createMock(IUser::class);
1044
-		$status1 = new UserStatus();
1045
-		$status1->setUserId('user1');
1046
-		$status2 = new UserStatus();
1047
-		$status2->setUserId('user2');
1048
-		$this->statusService->expects(self::once())
1049
-			->method('findAllRecentStatusChanges')
1050
-			->willReturn([
1051
-				$status1,
1052
-				$status2,
1053
-			]);
1054
-		$user1 = $this->createMock(IUser::class);
1055
-		$user1->method('getCloudId')->willReturn('user1@localcloud');
1056
-		$user2 = $this->createMock(IUser::class);
1057
-		$user2->method('getCloudId')->willReturn('user2@localcloud');
1058
-		$this->userManager->expects(self::exactly(2))
1059
-			->method('get')
1060
-			->willReturnCallback(function ($uid) use ($user1, $user2) {
1061
-				return match ($uid) {
1062
-					'user1' => $user1,
1063
-					'user2' => $user2,
1064
-				};
1065
-			});
1066
-		$this->contactsManager
1067
-			->expects(self::exactly(3))
1068
-			->method('search')
1069
-			->willReturnCallback(function ($uid, $searchProps, $options) {
1070
-				return match ([$uid, $options['limit'] ?? null]) {
1071
-					['user1@localcloud', 1] => [
1072
-						[
1073
-							'UID' => 'user1',
1074
-							'URI' => 'user1.vcf',
1075
-						],
1076
-					],
1077
-					['user2@localcloud' => [], 1], // Simulate not found
1078
-					['', 4] => [
1079
-						[
1080
-							'UID' => 'contact1',
1081
-							'URI' => 'contact1.vcf',
1082
-						],
1083
-						[
1084
-							'UID' => 'contact2',
1085
-							'URI' => 'contact2.vcf',
1086
-						],
1087
-					],
1088
-					default => [],
1089
-				};
1090
-			});
1091
-
1092
-		$contacts = $this->contactsStore->getContacts(
1093
-			$user,
1094
-			null,
1095
-			5,
1096
-		);
1097
-
1098
-		self::assertCount(3, $contacts);
1099
-		self::assertEquals('user1', $contacts[0]->getProperty('UID'));
1100
-		self::assertEquals('contact1', $contacts[1]->getProperty('UID'));
1101
-		self::assertEquals('contact2', $contacts[2]->getProperty('UID'));
1102
-	}
1103
-
1104
-	public function testPaginateRecentStatus(): void {
1105
-		$user = $this->createMock(IUser::class);
1106
-		$status1 = new UserStatus();
1107
-		$status1->setUserId('user1');
1108
-		$status2 = new UserStatus();
1109
-		$status2->setUserId('user2');
1110
-		$status3 = new UserStatus();
1111
-		$status3->setUserId('user3');
1112
-		$this->statusService->expects(self::never())
1113
-			->method('findAllRecentStatusChanges');
1114
-		$this->contactsManager
1115
-			->expects(self::exactly(2))
1116
-			->method('search')
1117
-			->willReturnCallback(function ($uid, $searchProps, $options) {
1118
-				return match ([$uid, $options['limit'] ?? null, $options['offset'] ?? null]) {
1119
-					['', 2, 0] => [
1120
-						[
1121
-							'UID' => 'contact1',
1122
-							'URI' => 'contact1.vcf',
1123
-						],
1124
-						[
1125
-							'UID' => 'contact2',
1126
-							'URI' => 'contact2.vcf',
1127
-						],
1128
-					],
1129
-					['', 2, 3] => [
1130
-						[
1131
-							'UID' => 'contact3',
1132
-							'URI' => 'contact3.vcf',
1133
-						],
1134
-					],
1135
-					default => [],
1136
-				};
1137
-			});
1138
-
1139
-		$page1 = $this->contactsStore->getContacts(
1140
-			$user,
1141
-			null,
1142
-			2,
1143
-			0,
1144
-		);
1145
-		$page2 = $this->contactsStore->getContacts(
1146
-			$user,
1147
-			null,
1148
-			2,
1149
-			3,
1150
-		);
1151
-
1152
-		self::assertCount(2, $page1);
1153
-		self::assertCount(1, $page2);
1154
-	}
28
+    private ContactsStore $contactsStore;
29
+    private StatusService|MockObject $statusService;
30
+    /** @var IManager|MockObject */
31
+    private $contactsManager;
32
+    /** @var ProfileManager */
33
+    private $profileManager;
34
+    /** @var IUserManager|MockObject */
35
+    private $userManager;
36
+    /** @var IURLGenerator */
37
+    private $urlGenerator;
38
+    /** @var IGroupManager|MockObject */
39
+    private $groupManager;
40
+    /** @var IConfig|MockObject */
41
+    private $config;
42
+    /** @var KnownUserService|MockObject */
43
+    private $knownUserService;
44
+    /** @var IL10NFactory */
45
+    private $l10nFactory;
46
+
47
+    protected function setUp(): void {
48
+        parent::setUp();
49
+
50
+        $this->contactsManager = $this->createMock(IManager::class);
51
+        $this->statusService = $this->createMock(StatusService::class);
52
+        $this->userManager = $this->createMock(IUserManager::class);
53
+        $this->profileManager = $this->createMock(ProfileManager::class);
54
+        $this->urlGenerator = $this->createMock(IURLGenerator::class);
55
+        $this->groupManager = $this->createMock(IGroupManager::class);
56
+        $this->config = $this->createMock(IConfig::class);
57
+        $this->knownUserService = $this->createMock(KnownUserService::class);
58
+        $this->l10nFactory = $this->createMock(IL10NFactory::class);
59
+        $this->contactsStore = new ContactsStore(
60
+            $this->contactsManager,
61
+            $this->statusService,
62
+            $this->config,
63
+            $this->profileManager,
64
+            $this->userManager,
65
+            $this->urlGenerator,
66
+            $this->groupManager,
67
+            $this->knownUserService,
68
+            $this->l10nFactory,
69
+        );
70
+    }
71
+
72
+    public function testGetContactsWithoutFilter(): void {
73
+        /** @var IUser|MockObject $user */
74
+        $user = $this->createMock(IUser::class);
75
+        $this->contactsManager->expects($this->once())
76
+            ->method('search')
77
+            ->with($this->equalTo(''), $this->equalTo(['FN', 'EMAIL']))
78
+            ->willReturn([
79
+                [
80
+                    'UID' => 123,
81
+                ],
82
+                [
83
+                    'UID' => 567,
84
+                    'FN' => 'Darren Roner',
85
+                    'EMAIL' => [
86
+                        '[email protected]'
87
+                    ],
88
+                ],
89
+            ]);
90
+        $user->expects($this->exactly(2))
91
+            ->method('getUID')
92
+            ->willReturn('user123');
93
+
94
+        $entries = $this->contactsStore->getContacts($user, '');
95
+
96
+        $this->assertCount(2, $entries);
97
+        $this->assertEquals([
98
+            '[email protected]'
99
+        ], $entries[1]->getEMailAddresses());
100
+    }
101
+
102
+    public function testGetContactsHidesOwnEntry(): void {
103
+        /** @var IUser|MockObject $user */
104
+        $user = $this->createMock(IUser::class);
105
+        $this->contactsManager->expects($this->once())
106
+            ->method('search')
107
+            ->with($this->equalTo(''), $this->equalTo(['FN', 'EMAIL']))
108
+            ->willReturn([
109
+                [
110
+                    'UID' => 'user123',
111
+                ],
112
+                [
113
+                    'UID' => 567,
114
+                    'FN' => 'Darren Roner',
115
+                    'EMAIL' => [
116
+                        '[email protected]'
117
+                    ],
118
+                ],
119
+            ]);
120
+        $user->expects($this->exactly(2))
121
+            ->method('getUID')
122
+            ->willReturn('user123');
123
+
124
+        $entries = $this->contactsStore->getContacts($user, '');
125
+
126
+        $this->assertCount(1, $entries);
127
+    }
128
+
129
+    public function testGetContactsWithoutBinaryImage(): void {
130
+        /** @var IUser|MockObject $user */
131
+        $user = $this->createMock(IUser::class);
132
+        $this->urlGenerator->expects($this->any())
133
+            ->method('linkToRouteAbsolute')
134
+            ->with('core.GuestAvatar.getAvatar', $this->anything())
135
+            ->willReturn('https://urlToNcAvatar.test');
136
+        $this->contactsManager->expects($this->once())
137
+            ->method('search')
138
+            ->with($this->equalTo(''), $this->equalTo(['FN', 'EMAIL']))
139
+            ->willReturn([
140
+                [
141
+                    'UID' => 123,
142
+                ],
143
+                [
144
+                    'UID' => 567,
145
+                    'FN' => 'Darren Roner',
146
+                    'EMAIL' => [
147
+                        '[email protected]'
148
+                    ],
149
+                    'PHOTO' => base64_encode('photophotophoto'),
150
+                ],
151
+            ]);
152
+        $user->expects($this->exactly(2))
153
+            ->method('getUID')
154
+            ->willReturn('user123');
155
+
156
+        $entries = $this->contactsStore->getContacts($user, '');
157
+
158
+        $this->assertCount(2, $entries);
159
+        $this->assertSame('https://urlToNcAvatar.test', $entries[1]->getAvatar());
160
+    }
161
+
162
+    public function testGetContactsWithoutAvatarURI(): void {
163
+        /** @var IUser|MockObject $user */
164
+        $user = $this->createMock(IUser::class);
165
+        $this->contactsManager->expects($this->once())
166
+            ->method('search')
167
+            ->with($this->equalTo(''), $this->equalTo(['FN', 'EMAIL']))
168
+            ->willReturn([
169
+                [
170
+                    'UID' => 123,
171
+                ],
172
+                [
173
+                    'UID' => 567,
174
+                    'FN' => 'Darren Roner',
175
+                    'EMAIL' => [
176
+                        '[email protected]'
177
+                    ],
178
+                    'PHOTO' => 'VALUE=uri:https://photo',
179
+                ],
180
+            ]);
181
+        $user->expects($this->exactly(2))
182
+            ->method('getUID')
183
+            ->willReturn('user123');
184
+
185
+        $entries = $this->contactsStore->getContacts($user, '');
186
+
187
+        $this->assertCount(2, $entries);
188
+        $this->assertEquals('https://photo', $entries[1]->getAvatar());
189
+    }
190
+
191
+    public static function dataGetContactsWhenUserIsInExcludeGroups(): array {
192
+        return [
193
+            ['yes', '[]', [], ['user123', 'user12345']],
194
+            ['yes', '["excludedGroup1"]', [], ['user123', 'user12345']],
195
+            ['yes', '["excludedGroup1"]', ['anotherGroup1'], ['user123', 'user12345']],
196
+            ['yes', '["excludedGroup1"]', ['anotherGroup1', 'anotherGroup2', 'anotherGroup3'], ['user123', 'user12345']],
197
+            ['yes', '["excludedGroup1"]', ['excludedGroup1'], []],
198
+            ['yes', '["excludedGroup1"]', ['anotherGroup1', 'excludedGroup1'], ['user123', 'user12345']],
199
+            ['yes', '["excludedGroup1"]', ['excludedGroup1', 'anotherGroup1', 'anotherGroup2', 'anotherGroup3'], ['user123', 'user12345']],
200
+            ['yes', '["excludedGroup1", "excludedGroup2", "excludedGroup3"]', [], ['user123', 'user12345']],
201
+            ['yes', '["excludedGroup1", "excludedGroup2", "excludedGroup3"]', ['anotherGroup1'], ['user123', 'user12345']],
202
+            ['yes', '["excludedGroup1", "excludedGroup2", "excludedGroup3"]', ['anotherGroup1', 'anotherGroup2', 'anotherGroup3'], ['user123', 'user12345']],
203
+            ['yes', '["excludedGroup1", "excludedGroup2", "excludedGroup3"]', ['excludedGroup1'], []],
204
+            ['yes', '["excludedGroup1", "excludedGroup2", "excludedGroup3"]', ['excludedGroup2'], []],
205
+            ['yes', '["excludedGroup1", "excludedGroup2", "excludedGroup3"]', ['excludedGroup3'], []],
206
+            ['yes', '["excludedGroup1", "excludedGroup2", "excludedGroup3"]', ['excludedGroup1', 'excludedGroup2', 'excludedGroup3'], []],
207
+            ['yes', '["excludedGroup1", "excludedGroup2", "excludedGroup3"]', ['anotherGroup1', 'excludedGroup1'], ['user123', 'user12345']],
208
+            ['yes', '["excludedGroup1", "excludedGroup2", "excludedGroup3"]', ['anotherGroup1', 'excludedGroup2', 'anotherGroup2', 'anotherGroup3'], ['user123', 'user12345']],
209
+            ['yes', '["excludedGroup1", "excludedGroup2", "excludedGroup3"]', ['excludedGroup3', 'anotherGroup1', 'anotherGroup2', 'anotherGroup3'], ['user123', 'user12345']],
210
+            ['allow', '[]', [], []],
211
+            ['allow', '["allowedGroup1"]', [], []],
212
+            ['allow', '["allowedGroup1"]', ['anotherGroup1'], []],
213
+            ['allow', '["allowedGroup1"]', ['anotherGroup1', 'anotherGroup2', 'anotherGroup3'], []],
214
+            ['allow', '["allowedGroup1"]', ['allowedGroup1'], ['user123', 'user12345']],
215
+            ['allow', '["allowedGroup1"]', ['anotherGroup1', 'allowedGroup1'], ['user123', 'user12345']],
216
+            ['allow', '["allowedGroup1"]', ['allowedGroup1', 'anotherGroup1', 'anotherGroup2', 'anotherGroup3'], ['user123', 'user12345']],
217
+            ['allow', '["allowedGroup1", "allowedGroup2", "allowedGroup3"]', [], []],
218
+            ['allow', '["allowedGroup1", "allowedGroup2", "allowedGroup3"]', ['anotherGroup1'], []],
219
+            ['allow', '["allowedGroup1", "allowedGroup2", "allowedGroup3"]', ['anotherGroup1', 'anotherGroup2', 'anotherGroup3'], []],
220
+            ['allow', '["allowedGroup1", "allowedGroup2", "allowedGroup3"]', ['allowedGroup1'], ['user123', 'user12345']],
221
+            ['allow', '["allowedGroup1", "allowedGroup2", "allowedGroup3"]', ['allowedGroup2'], ['user123', 'user12345']],
222
+            ['allow', '["allowedGroup1", "allowedGroup2", "allowedGroup3"]', ['allowedGroup3'], ['user123', 'user12345']],
223
+            ['allow', '["allowedGroup1", "allowedGroup2", "allowedGroup3"]', ['allowedGroup1', 'allowedGroup2', 'allowedGroup3'], ['user123', 'user12345']],
224
+            ['allow', '["allowedGroup1", "allowedGroup2", "allowedGroup3"]', ['anotherGroup1', 'allowedGroup1'], ['user123', 'user12345']],
225
+            ['allow', '["allowedGroup1", "allowedGroup2", "allowedGroup3"]', ['anotherGroup1', 'allowedGroup2', 'anotherGroup2', 'anotherGroup3'], ['user123', 'user12345']],
226
+            ['allow', '["allowedGroup1", "allowedGroup2", "allowedGroup3"]', ['allowedGroup3', 'anotherGroup1', 'anotherGroup2', 'anotherGroup3'], ['user123', 'user12345']],
227
+        ];
228
+    }
229
+
230
+    #[\PHPUnit\Framework\Attributes\DataProvider('dataGetContactsWhenUserIsInExcludeGroups')]
231
+    public function testGetContactsWhenUserIsInExcludeGroups(string $excludeGroups, string $excludeGroupsList, array $currentUserGroupIds, array $expectedUids): void {
232
+        $this->config
233
+            ->method('getAppValue')
234
+            ->willReturnMap([
235
+                ['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'yes'],
236
+                ['core', 'shareapi_restrict_user_enumeration_to_group', 'no', 'no'],
237
+                ['core', 'shareapi_restrict_user_enumeration_to_phone', 'no', 'no'],
238
+                ['core', 'shareapi_exclude_groups', 'no', $excludeGroups],
239
+                ['core', 'shareapi_only_share_with_group_members', 'no', 'no'],
240
+                ['core', 'shareapi_exclude_groups_list', '', $excludeGroupsList],
241
+                ['core', 'shareapi_only_share_with_group_members_exclude_group_list', '', '[]'],
242
+            ]);
243
+
244
+        /** @var IUser|MockObject $currentUser */
245
+        $currentUser = $this->createMock(IUser::class);
246
+        $currentUser->expects($this->exactly(2))
247
+            ->method('getUID')
248
+            ->willReturn('user001');
249
+
250
+        $this->groupManager->expects($this->once())
251
+            ->method('getUserGroupIds')
252
+            ->with($this->equalTo($currentUser))
253
+            ->willReturn($currentUserGroupIds);
254
+
255
+        $this->contactsManager->expects($this->once())
256
+            ->method('search')
257
+            ->with($this->equalTo(''), $this->equalTo(['FN', 'EMAIL']))
258
+            ->willReturn([
259
+                [
260
+                    'UID' => 'user123',
261
+                    'isLocalSystemBook' => true
262
+                ],
263
+                [
264
+                    'UID' => 'user12345',
265
+                    'isLocalSystemBook' => true
266
+                ],
267
+            ]);
268
+
269
+
270
+        $entries = $this->contactsStore->getContacts($currentUser, '');
271
+
272
+        $this->assertCount(count($expectedUids), $entries);
273
+        for ($i = 0; $i < count($expectedUids); $i++) {
274
+            $this->assertEquals($expectedUids[$i], $entries[$i]->getProperty('UID'));
275
+        }
276
+    }
277
+
278
+    public function testGetContactsOnlyShareIfInTheSameGroupWhenUserIsInExcludeGroups(): void {
279
+        $this->config
280
+            ->method('getAppValue')
281
+            ->willReturnMap([
282
+                ['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'yes'],
283
+                ['core', 'shareapi_restrict_user_enumeration_to_group', 'no', 'no'],
284
+                ['core', 'shareapi_restrict_user_enumeration_to_phone', 'no', 'no'],
285
+                ['core', 'shareapi_exclude_groups', 'no', 'yes'],
286
+                ['core', 'shareapi_only_share_with_group_members', 'no', 'yes'],
287
+                ['core', 'shareapi_exclude_groups_list', '', '["group1", "group5", "group6"]'],
288
+                ['core', 'shareapi_only_share_with_group_members_exclude_group_list', '', '[]'],
289
+            ]);
290
+
291
+        /** @var IUser|MockObject $currentUser */
292
+        $currentUser = $this->createMock(IUser::class);
293
+        $currentUser->expects($this->exactly(2))
294
+            ->method('getUID')
295
+            ->willReturn('user001');
296
+
297
+        $this->groupManager->expects($this->once())
298
+            ->method('getUserGroupIds')
299
+            ->with($this->equalTo($currentUser))
300
+            ->willReturn(['group1', 'group2', 'group3']);
301
+
302
+
303
+        $this->contactsManager->expects($this->once())
304
+            ->method('search')
305
+            ->with($this->equalTo(''), $this->equalTo(['FN', 'EMAIL']))
306
+            ->willReturn([
307
+                [
308
+                    'UID' => 'user123',
309
+                    'isLocalSystemBook' => true
310
+                ],
311
+                [
312
+                    'UID' => 'user12345',
313
+                    'isLocalSystemBook' => true
314
+                ],
315
+            ]);
316
+
317
+
318
+        $entries = $this->contactsStore->getContacts($currentUser, '');
319
+
320
+        $this->assertCount(0, $entries);
321
+    }
322
+
323
+    public function testGetContactsOnlyShareIfInTheSameGroup(): void {
324
+        $this->config
325
+            ->method('getAppValue')
326
+            ->willReturnMap([
327
+                ['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'yes'],
328
+                ['core', 'shareapi_restrict_user_enumeration_to_group', 'no', 'no'],
329
+                ['core', 'shareapi_restrict_user_enumeration_to_phone', 'no', 'no'],
330
+                ['core', 'shareapi_exclude_groups', 'no', 'no'],
331
+                ['core', 'shareapi_only_share_with_group_members', 'no', 'yes'],
332
+                ['core', 'shareapi_only_share_with_group_members_exclude_group_list', '', '[]'],
333
+            ]);
334
+
335
+        /** @var IUser|MockObject $currentUser */
336
+        $currentUser = $this->createMock(IUser::class);
337
+        $currentUser->expects($this->exactly(2))
338
+            ->method('getUID')
339
+            ->willReturn('user001');
340
+
341
+        $user1 = $this->createMock(IUser::class);
342
+        $user2 = $this->createMock(IUser::class);
343
+        $user3 = $this->createMock(IUser::class);
344
+
345
+        $calls = [
346
+            [[$currentUser], ['group1', 'group2', 'group3']],
347
+            [[$user1], ['group1']],
348
+            [[$user2], ['group2', 'group3']],
349
+            [[$user3], ['group8', 'group9']],
350
+        ];
351
+        $this->groupManager->expects($this->exactly(4))
352
+            ->method('getUserGroupIds')
353
+            ->willReturnCallback(function () use (&$calls): array {
354
+                $expected = array_shift($calls);
355
+                $this->assertEquals($expected[0], func_get_args());
356
+                return $expected[1];
357
+            });
358
+
359
+        $this->userManager->expects($this->exactly(3))
360
+            ->method('get')
361
+            ->willReturnMap([
362
+                ['user1', $user1],
363
+                ['user2', $user2],
364
+                ['user3', $user3],
365
+            ]);
366
+
367
+        $this->contactsManager->expects($this->once())
368
+            ->method('search')
369
+            ->with($this->equalTo(''), $this->equalTo(['FN', 'EMAIL']))
370
+            ->willReturn([
371
+                [
372
+                    'UID' => 'user1',
373
+                    'isLocalSystemBook' => true
374
+                ],
375
+                [
376
+                    'UID' => 'user2',
377
+                    'isLocalSystemBook' => true
378
+                ],
379
+                [
380
+                    'UID' => 'user3',
381
+                    'isLocalSystemBook' => true
382
+                ],
383
+                [
384
+                    'UID' => 'contact',
385
+                ],
386
+            ]);
387
+
388
+        $entries = $this->contactsStore->getContacts($currentUser, '');
389
+
390
+        $this->assertCount(3, $entries);
391
+        $this->assertEquals('user1', $entries[0]->getProperty('UID'));
392
+        $this->assertEquals('user2', $entries[1]->getProperty('UID'));
393
+        $this->assertEquals('contact', $entries[2]->getProperty('UID'));
394
+    }
395
+
396
+    public function testGetContactsOnlyEnumerateIfInTheSameGroup(): void {
397
+        $this->config
398
+            ->method('getAppValue')
399
+            ->willReturnMap([
400
+                ['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'yes'],
401
+                ['core', 'shareapi_restrict_user_enumeration_to_group', 'no', 'yes'],
402
+                ['core', 'shareapi_restrict_user_enumeration_to_phone', 'no', 'no'],
403
+                ['core', 'shareapi_exclude_groups', 'no', 'no'],
404
+                ['core', 'shareapi_only_share_with_group_members', 'no', 'yes'],
405
+                ['core', 'shareapi_only_share_with_group_members_exclude_group_list', '', '[]'],
406
+            ]);
407
+
408
+        /** @var IUser|MockObject $currentUser */
409
+        $currentUser = $this->createMock(IUser::class);
410
+        $currentUser->expects($this->exactly(2))
411
+            ->method('getUID')
412
+            ->willReturn('user001');
413
+
414
+        $user1 = $this->createMock(IUser::class);
415
+        $user2 = $this->createMock(IUser::class);
416
+        $user3 = $this->createMock(IUser::class);
417
+
418
+        $calls = [
419
+            [[$currentUser], ['group1', 'group2', 'group3']],
420
+            [[$user1], ['group1']],
421
+            [[$user2], ['group2', 'group3']],
422
+            [[$user3], ['group8', 'group9']],
423
+        ];
424
+        $this->groupManager->expects($this->exactly(4))
425
+            ->method('getUserGroupIds')
426
+            ->willReturnCallback(function () use (&$calls): array {
427
+                $expected = array_shift($calls);
428
+                $this->assertEquals($expected[0], func_get_args());
429
+                return $expected[1];
430
+            });
431
+
432
+        $this->userManager->expects($this->exactly(3))
433
+            ->method('get')
434
+            ->willReturnMap([
435
+                ['user1', $user1],
436
+                ['user2', $user2],
437
+                ['user3', $user3],
438
+            ]);
439
+
440
+        $this->contactsManager->expects($this->once())
441
+            ->method('search')
442
+            ->with($this->equalTo(''), $this->equalTo(['FN', 'EMAIL']))
443
+            ->willReturn([
444
+                [
445
+                    'UID' => 'user1',
446
+                    'isLocalSystemBook' => true
447
+                ],
448
+                [
449
+                    'UID' => 'user2',
450
+                    'isLocalSystemBook' => true
451
+                ],
452
+                [
453
+                    'UID' => 'user3',
454
+                    'isLocalSystemBook' => true
455
+                ],
456
+                [
457
+                    'UID' => 'contact',
458
+                ],
459
+            ]);
460
+
461
+        $entries = $this->contactsStore->getContacts($currentUser, '');
462
+
463
+        $this->assertCount(3, $entries);
464
+        $this->assertEquals('user1', $entries[0]->getProperty('UID'));
465
+        $this->assertEquals('user2', $entries[1]->getProperty('UID'));
466
+        $this->assertEquals('contact', $entries[2]->getProperty('UID'));
467
+    }
468
+
469
+    public function testGetContactsOnlyEnumerateIfPhoneBookMatch(): void {
470
+        $this->config
471
+            ->method('getAppValue')
472
+            ->willReturnMap([
473
+                ['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'yes'],
474
+                ['core', 'shareapi_restrict_user_enumeration_to_group', 'no', 'no'],
475
+                ['core', 'shareapi_restrict_user_enumeration_to_phone', 'no', 'yes'],
476
+                ['core', 'shareapi_exclude_groups', 'no', 'no'],
477
+                ['core', 'shareapi_only_share_with_group_members', 'no', 'no'],
478
+            ]);
479
+
480
+        /** @var IUser|MockObject $currentUser */
481
+        $currentUser = $this->createMock(IUser::class);
482
+        $currentUser->expects($this->exactly(2))
483
+            ->method('getUID')
484
+            ->willReturn('user001');
485
+
486
+        $this->groupManager->expects($this->once())
487
+            ->method('getUserGroupIds')
488
+            ->with($this->equalTo($currentUser))
489
+            ->willReturn(['group1', 'group2', 'group3']);
490
+
491
+        $this->knownUserService->method('isKnownToUser')
492
+            ->willReturnMap([
493
+                ['user001', 'user1', true],
494
+                ['user001', 'user2', true],
495
+                ['user001', 'user3', false],
496
+            ]);
497
+
498
+        $this->contactsManager->expects($this->once())
499
+            ->method('search')
500
+            ->with($this->equalTo(''), $this->equalTo(['FN', 'EMAIL']))
501
+            ->willReturn([
502
+                [
503
+                    'UID' => 'user1',
504
+                    'isLocalSystemBook' => true
505
+                ],
506
+                [
507
+                    'UID' => 'user2',
508
+                    'isLocalSystemBook' => true
509
+                ],
510
+                [
511
+                    'UID' => 'user3',
512
+                    'isLocalSystemBook' => true
513
+                ],
514
+                [
515
+                    'UID' => 'contact',
516
+                ],
517
+            ]);
518
+
519
+        $entries = $this->contactsStore->getContacts($currentUser, '');
520
+
521
+        $this->assertCount(3, $entries);
522
+        $this->assertEquals('user1', $entries[0]->getProperty('UID'));
523
+        $this->assertEquals('user2', $entries[1]->getProperty('UID'));
524
+        $this->assertEquals('contact', $entries[2]->getProperty('UID'));
525
+    }
526
+
527
+    public function testGetContactsOnlyEnumerateIfPhoneBookMatchWithOwnGroupsOnly(): void {
528
+        $this->config
529
+            ->method('getAppValue')
530
+            ->willReturnMap([
531
+                ['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'yes'],
532
+                ['core', 'shareapi_restrict_user_enumeration_to_group', 'no', 'no'],
533
+                ['core', 'shareapi_restrict_user_enumeration_to_phone', 'no', 'yes'],
534
+                ['core', 'shareapi_exclude_groups', 'no', 'no'],
535
+                ['core', 'shareapi_only_share_with_group_members', 'no', 'yes'],
536
+                ['core', 'shareapi_only_share_with_group_members_exclude_group_list', '', '[]'],
537
+            ]);
538
+
539
+        /** @var IUser|MockObject $currentUser */
540
+        $currentUser = $this->createMock(IUser::class);
541
+        $currentUser->expects($this->exactly(2))
542
+            ->method('getUID')
543
+            ->willReturn('user001');
544
+
545
+        $user1 = $this->createMock(IUser::class);
546
+        $user2 = $this->createMock(IUser::class);
547
+        $user3 = $this->createMock(IUser::class);
548
+
549
+        $calls = [
550
+            [[$currentUser], ['group1', 'group2', 'group3']],
551
+            [[$user1], ['group1']],
552
+            [[$user2], ['group2', 'group3']],
553
+            [[$user3], ['group8', 'group9']],
554
+        ];
555
+        $this->groupManager->expects($this->exactly(4))
556
+            ->method('getUserGroupIds')
557
+            ->willReturnCallback(function () use (&$calls): array {
558
+                $expected = array_shift($calls);
559
+                $this->assertEquals($expected[0], func_get_args());
560
+                return $expected[1];
561
+            });
562
+
563
+        $this->userManager->expects($this->exactly(3))
564
+            ->method('get')
565
+            ->willReturnMap([
566
+                ['user1', $user1],
567
+                ['user2', $user2],
568
+                ['user3', $user3],
569
+            ]);
570
+
571
+        $this->knownUserService->method('isKnownToUser')
572
+            ->willReturnMap([
573
+                ['user001', 'user1', true],
574
+                ['user001', 'user2', true],
575
+                ['user001', 'user3', true],
576
+            ]);
577
+
578
+        $this->contactsManager->expects($this->once())
579
+            ->method('search')
580
+            ->with($this->equalTo(''), $this->equalTo(['FN', 'EMAIL']))
581
+            ->willReturn([
582
+                [
583
+                    'UID' => 'user1',
584
+                    'isLocalSystemBook' => true
585
+                ],
586
+                [
587
+                    'UID' => 'user2',
588
+                    'isLocalSystemBook' => true
589
+                ],
590
+                [
591
+                    'UID' => 'user3',
592
+                    'isLocalSystemBook' => true
593
+                ],
594
+                [
595
+                    'UID' => 'contact',
596
+                ],
597
+            ]);
598
+
599
+        $entries = $this->contactsStore->getContacts($currentUser, '');
600
+
601
+        $this->assertCount(3, $entries);
602
+        $this->assertEquals('user1', $entries[0]->getProperty('UID'));
603
+        $this->assertEquals('user2', $entries[1]->getProperty('UID'));
604
+        $this->assertEquals('contact', $entries[2]->getProperty('UID'));
605
+    }
606
+
607
+    public function testGetContactsOnlyEnumerateIfPhoneBookOrSameGroup(): void {
608
+        $this->config
609
+            ->method('getAppValue')
610
+            ->willReturnMap([
611
+                ['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'yes'],
612
+                ['core', 'shareapi_restrict_user_enumeration_to_group', 'no', 'yes'],
613
+                ['core', 'shareapi_restrict_user_enumeration_to_phone', 'no', 'yes'],
614
+                ['core', 'shareapi_exclude_groups', 'no', 'no'],
615
+                ['core', 'shareapi_only_share_with_group_members', 'no', 'no'],
616
+            ]);
617
+
618
+        /** @var IUser|MockObject $currentUser */
619
+        $currentUser = $this->createMock(IUser::class);
620
+        $currentUser->expects($this->exactly(2))
621
+            ->method('getUID')
622
+            ->willReturn('user001');
623
+
624
+        $user1 = $this->createMock(IUser::class);
625
+
626
+        $calls = [
627
+            [[$currentUser], ['group1', 'group2', 'group3']],
628
+            [[$user1], ['group1']],
629
+        ];
630
+        $this->groupManager->expects($this->exactly(2))
631
+            ->method('getUserGroupIds')
632
+            ->willReturnCallback(function () use (&$calls): array {
633
+                $expected = array_shift($calls);
634
+                $this->assertEquals($expected[0], func_get_args());
635
+                return $expected[1];
636
+            });
637
+
638
+        $this->userManager->expects($this->once())
639
+            ->method('get')
640
+            ->with('user1')
641
+            ->willReturn($user1);
642
+
643
+        $this->knownUserService->method('isKnownToUser')
644
+            ->willReturnMap([
645
+                ['user001', 'user1', false],
646
+                ['user001', 'user2', true],
647
+                ['user001', 'user3', true],
648
+            ]);
649
+
650
+        $this->contactsManager->expects($this->once())
651
+            ->method('search')
652
+            ->with($this->equalTo(''), $this->equalTo(['FN', 'EMAIL']))
653
+            ->willReturn([
654
+                [
655
+                    'UID' => 'user1',
656
+                    'isLocalSystemBook' => true
657
+                ],
658
+                [
659
+                    'UID' => 'user2',
660
+                    'isLocalSystemBook' => true
661
+                ],
662
+                [
663
+                    'UID' => 'user3',
664
+                    'isLocalSystemBook' => true
665
+                ],
666
+                [
667
+                    'UID' => 'contact',
668
+                ],
669
+            ]);
670
+
671
+        $entries = $this->contactsStore->getContacts($currentUser, '');
672
+
673
+        $this->assertCount(4, $entries);
674
+        $this->assertEquals('user1', $entries[0]->getProperty('UID'));
675
+        $this->assertEquals('user2', $entries[1]->getProperty('UID'));
676
+        $this->assertEquals('user3', $entries[2]->getProperty('UID'));
677
+        $this->assertEquals('contact', $entries[3]->getProperty('UID'));
678
+    }
679
+
680
+    public function testGetContactsOnlyEnumerateIfPhoneBookOrSameGroupInOwnGroupsOnly(): void {
681
+        $this->config
682
+            ->method('getAppValue')
683
+            ->willReturnMap([
684
+                ['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'yes'],
685
+                ['core', 'shareapi_restrict_user_enumeration_to_group', 'no', 'yes'],
686
+                ['core', 'shareapi_restrict_user_enumeration_to_phone', 'no', 'yes'],
687
+                ['core', 'shareapi_exclude_groups', 'no', 'no'],
688
+                ['core', 'shareapi_only_share_with_group_members', 'no', 'yes'],
689
+                ['core', 'shareapi_only_share_with_group_members_exclude_group_list', '', '[]'],
690
+            ]);
691
+
692
+        /** @var IUser|MockObject $currentUser */
693
+        $currentUser = $this->createMock(IUser::class);
694
+        $currentUser->expects($this->exactly(2))
695
+            ->method('getUID')
696
+            ->willReturn('user001');
697
+
698
+        $user1 = $this->createMock(IUser::class);
699
+        $user2 = $this->createMock(IUser::class);
700
+        $user3 = $this->createMock(IUser::class);
701
+
702
+        $calls = [
703
+            [[$currentUser], ['group1', 'group2', 'group3']],
704
+            [[$user1], ['group1']],
705
+            [[$user2], ['group2', 'group3']],
706
+            [[$user3], ['group8', 'group9']],
707
+        ];
708
+        $this->groupManager->expects($this->exactly(4))
709
+            ->method('getUserGroupIds')
710
+            ->willReturnCallback(function () use (&$calls): array {
711
+                $expected = array_shift($calls);
712
+                $this->assertEquals($expected[0], func_get_args());
713
+                return $expected[1];
714
+            });
715
+
716
+        $this->userManager->expects($this->exactly(3))
717
+            ->method('get')
718
+            ->willReturnMap([
719
+                ['user1', $user1],
720
+                ['user2', $user2],
721
+                ['user3', $user3],
722
+            ]);
723
+
724
+        $this->knownUserService->method('isKnownToUser')
725
+            ->willReturnMap([
726
+                ['user001', 'user1', false],
727
+                ['user001', 'user2', true],
728
+                ['user001', 'user3', true],
729
+            ]);
730
+
731
+        $this->contactsManager->expects($this->once())
732
+            ->method('search')
733
+            ->with($this->equalTo(''), $this->equalTo(['FN', 'EMAIL']))
734
+            ->willReturn([
735
+                [
736
+                    'UID' => 'user1',
737
+                    'isLocalSystemBook' => true
738
+                ],
739
+                [
740
+                    'UID' => 'user2',
741
+                    'isLocalSystemBook' => true
742
+                ],
743
+                [
744
+                    'UID' => 'user3',
745
+                    'isLocalSystemBook' => true
746
+                ],
747
+                [
748
+                    'UID' => 'contact',
749
+                ],
750
+            ]);
751
+
752
+        $entries = $this->contactsStore->getContacts($currentUser, '');
753
+
754
+        $this->assertCount(3, $entries);
755
+        $this->assertEquals('user1', $entries[0]->getProperty('UID'));
756
+        $this->assertEquals('user2', $entries[1]->getProperty('UID'));
757
+        $this->assertEquals('contact', $entries[2]->getProperty('UID'));
758
+    }
759
+
760
+    public function testGetContactsWithFilter(): void {
761
+        $this->config
762
+            ->method('getAppValue')
763
+            ->willReturnMap([
764
+                ['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'no'],
765
+                ['core', 'shareapi_restrict_user_enumeration_full_match', 'yes', 'yes'],
766
+            ]);
767
+
768
+        /** @var IUser|MockObject $user */
769
+        $user = $this->createMock(IUser::class);
770
+        $this->contactsManager->expects($this->any())
771
+            ->method('search')
772
+            ->willReturn([
773
+                [
774
+                    'UID' => 'a567',
775
+                    'FN' => 'Darren Roner',
776
+                    'EMAIL' => [
777
+                        '[email protected]',
778
+                    ],
779
+                    'isLocalSystemBook' => true,
780
+                ],
781
+                [
782
+                    'UID' => 'john',
783
+                    'FN' => 'John Doe',
784
+                    'EMAIL' => [
785
+                        '[email protected]',
786
+                    ],
787
+                    'isLocalSystemBook' => true,
788
+                ],
789
+                [
790
+                    'FN' => 'Anne D',
791
+                    'EMAIL' => [
792
+                        '[email protected]',
793
+                    ],
794
+                    'isLocalSystemBook' => false,
795
+                ],
796
+            ]);
797
+        $user->expects($this->any())
798
+            ->method('getUID')
799
+            ->willReturn('user123');
800
+
801
+        // Complete match on UID should match
802
+        $entry = $this->contactsStore->getContacts($user, 'a567');
803
+        $this->assertSame(2, count($entry));
804
+        $this->assertEquals([
805
+            '[email protected]'
806
+        ], $entry[0]->getEMailAddresses());
807
+
808
+        // Partial match on UID should not match
809
+        $entry = $this->contactsStore->getContacts($user, 'a56');
810
+        $this->assertSame(1, count($entry));
811
+        $this->assertEquals([
812
+            '[email protected]'
813
+        ], $entry[0]->getEMailAddresses());
814
+
815
+        // Complete match on email should match
816
+        $entry = $this->contactsStore->getContacts($user, '[email protected]');
817
+        $this->assertSame(2, count($entry));
818
+        $this->assertEquals([
819
+            '[email protected]'
820
+        ], $entry[0]->getEMailAddresses());
821
+        $this->assertEquals([
822
+            '[email protected]'
823
+        ], $entry[1]->getEMailAddresses());
824
+
825
+        // Partial match on email should not match
826
+        $entry = $this->contactsStore->getContacts($user, '[email protected]');
827
+        $this->assertSame(1, count($entry));
828
+        $this->assertEquals([
829
+            '[email protected]'
830
+        ], $entry[0]->getEMailAddresses());
831
+
832
+        // Match on FN should not match
833
+        $entry = $this->contactsStore->getContacts($user, 'Darren Roner');
834
+        $this->assertSame(1, count($entry));
835
+        $this->assertEquals([
836
+            '[email protected]'
837
+        ], $entry[0]->getEMailAddresses());
838
+
839
+        // Don't filter users in local addressbook
840
+        $entry = $this->contactsStore->getContacts($user, 'Anne D');
841
+        $this->assertSame(1, count($entry));
842
+        $this->assertEquals([
843
+            '[email protected]'
844
+        ], $entry[0]->getEMailAddresses());
845
+    }
846
+
847
+    public function testGetContactsWithFilterWithoutFullMatch(): void {
848
+        $this->config
849
+            ->method('getAppValue')
850
+            ->willReturnMap([
851
+                ['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'no'],
852
+                ['core', 'shareapi_restrict_user_enumeration_full_match', 'yes', 'no'],
853
+            ]);
854
+
855
+        /** @var IUser|MockObject $user */
856
+        $user = $this->createMock(IUser::class);
857
+        $this->contactsManager->expects($this->any())
858
+            ->method('search')
859
+            ->willReturn([
860
+                [
861
+                    'UID' => 'a567',
862
+                    'FN' => 'Darren Roner',
863
+                    'EMAIL' => [
864
+                        '[email protected]',
865
+                    ],
866
+                    'isLocalSystemBook' => true,
867
+                ],
868
+                [
869
+                    'UID' => 'john',
870
+                    'FN' => 'John Doe',
871
+                    'EMAIL' => [
872
+                        '[email protected]',
873
+                    ],
874
+                    'isLocalSystemBook' => true,
875
+                ],
876
+                [
877
+                    'FN' => 'Anne D',
878
+                    'EMAIL' => [
879
+                        '[email protected]',
880
+                    ],
881
+                    'isLocalSystemBook' => false,
882
+                ],
883
+            ]);
884
+        $user->expects($this->any())
885
+            ->method('getUID')
886
+            ->willReturn('user123');
887
+
888
+        // Complete match on UID should not match
889
+        $entry = $this->contactsStore->getContacts($user, 'a567');
890
+        $this->assertSame(1, count($entry));
891
+        $this->assertEquals([
892
+            '[email protected]'
893
+        ], $entry[0]->getEMailAddresses());
894
+
895
+        // Partial match on UID should not match
896
+        $entry = $this->contactsStore->getContacts($user, 'a56');
897
+        $this->assertSame(1, count($entry));
898
+        $this->assertEquals([
899
+            '[email protected]'
900
+        ], $entry[0]->getEMailAddresses());
901
+
902
+        // Complete match on email should not match
903
+        $entry = $this->contactsStore->getContacts($user, '[email protected]');
904
+        $this->assertSame(1, count($entry));
905
+        $this->assertEquals([
906
+            '[email protected]'
907
+        ], $entry[0]->getEMailAddresses());
908
+
909
+        // Partial match on email should not match
910
+        $entry = $this->contactsStore->getContacts($user, '[email protected]');
911
+        $this->assertSame(1, count($entry));
912
+        $this->assertEquals([
913
+            '[email protected]'
914
+        ], $entry[0]->getEMailAddresses());
915
+
916
+        // Match on FN should not match
917
+        $entry = $this->contactsStore->getContacts($user, 'Darren Roner');
918
+        $this->assertSame(1, count($entry));
919
+        $this->assertEquals([
920
+            '[email protected]'
921
+        ], $entry[0]->getEMailAddresses());
922
+
923
+        // Don't filter users in local addressbook
924
+        $entry = $this->contactsStore->getContacts($user, 'Anne D');
925
+        $this->assertSame(1, count($entry));
926
+        $this->assertEquals([
927
+            '[email protected]'
928
+        ], $entry[0]->getEMailAddresses());
929
+    }
930
+
931
+    public function testFindOneUser(): void {
932
+        $this->config
933
+            ->method('getAppValue')
934
+            ->willReturnMap([
935
+                ['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'yes'],
936
+                ['core', 'shareapi_restrict_user_enumeration_to_group', 'no', 'no'],
937
+                ['core', 'shareapi_restrict_user_enumeration_to_phone', 'no', 'no'],
938
+                ['core', 'shareapi_restrict_user_enumeration_full_match', 'yes', 'yes'],
939
+                ['core', 'shareapi_exclude_groups', 'no', 'yes'],
940
+                ['core', 'shareapi_exclude_groups_list', '', ''],
941
+                ['core', 'shareapi_only_share_with_group_members', 'no', 'no'],
942
+            ]);
943
+
944
+        /** @var IUser|MockObject $user */
945
+        $user = $this->createMock(IUser::class);
946
+        $this->contactsManager->expects($this->once())
947
+            ->method('search')
948
+            ->with($this->equalTo('a567'), $this->equalTo(['UID']))
949
+            ->willReturn([
950
+                [
951
+                    'UID' => 123,
952
+                    'isLocalSystemBook' => false
953
+                ],
954
+                [
955
+                    'UID' => 'a567',
956
+                    'FN' => 'Darren Roner',
957
+                    'EMAIL' => [
958
+                        '[email protected]'
959
+                    ],
960
+                    'isLocalSystemBook' => true
961
+                ],
962
+            ]);
963
+        $user->expects($this->any())
964
+            ->method('getUID')
965
+            ->willReturn('user123');
966
+
967
+        $entry = $this->contactsStore->findOne($user, 0, 'a567');
968
+
969
+        $this->assertEquals([
970
+            '[email protected]'
971
+        ], $entry->getEMailAddresses());
972
+    }
973
+
974
+    public function testFindOneEMail(): void {
975
+        /** @var IUser|MockObject $user */
976
+        $user = $this->createMock(IUser::class);
977
+        $this->contactsManager->expects($this->once())
978
+            ->method('search')
979
+            ->with($this->equalTo('[email protected]'), $this->equalTo(['EMAIL']))
980
+            ->willReturn([
981
+                [
982
+                    'UID' => 123,
983
+                    'isLocalSystemBook' => false
984
+                ],
985
+                [
986
+                    'UID' => 'a567',
987
+                    'FN' => 'Darren Roner',
988
+                    'EMAIL' => [
989
+                        '[email protected]'
990
+                    ],
991
+                    'isLocalSystemBook' => false
992
+                ],
993
+            ]);
994
+        $user->expects($this->any())
995
+            ->method('getUID')
996
+            ->willReturn('user123');
997
+
998
+        $entry = $this->contactsStore->findOne($user, 4, '[email protected]');
999
+
1000
+        $this->assertEquals([
1001
+            '[email protected]'
1002
+        ], $entry->getEMailAddresses());
1003
+    }
1004
+
1005
+    public function testFindOneNotSupportedType(): void {
1006
+        /** @var IUser|MockObject $user */
1007
+        $user = $this->createMock(IUser::class);
1008
+
1009
+        $entry = $this->contactsStore->findOne($user, 42, '[email protected]');
1010
+
1011
+        $this->assertEquals(null, $entry);
1012
+    }
1013
+
1014
+    public function testFindOneNoMatches(): void {
1015
+        /** @var IUser|MockObject $user */
1016
+        $user = $this->createMock(IUser::class);
1017
+        $this->contactsManager->expects($this->once())
1018
+            ->method('search')
1019
+            ->with($this->equalTo('a567'), $this->equalTo(['UID']))
1020
+            ->willReturn([
1021
+                [
1022
+                    'UID' => 123,
1023
+                    'isLocalSystemBook' => false
1024
+                ],
1025
+                [
1026
+                    'UID' => 'a567',
1027
+                    'FN' => 'Darren Roner',
1028
+                    'EMAIL' => [
1029
+                        '[email protected]'
1030
+                    ],
1031
+                    'isLocalSystemBook' => false
1032
+                ],
1033
+            ]);
1034
+        $user->expects($this->never())
1035
+            ->method('getUID');
1036
+
1037
+        $entry = $this->contactsStore->findOne($user, 0, 'a567');
1038
+
1039
+        $this->assertEquals(null, $entry);
1040
+    }
1041
+
1042
+    public function testGetRecentStatusFirst(): void {
1043
+        $user = $this->createMock(IUser::class);
1044
+        $status1 = new UserStatus();
1045
+        $status1->setUserId('user1');
1046
+        $status2 = new UserStatus();
1047
+        $status2->setUserId('user2');
1048
+        $this->statusService->expects(self::once())
1049
+            ->method('findAllRecentStatusChanges')
1050
+            ->willReturn([
1051
+                $status1,
1052
+                $status2,
1053
+            ]);
1054
+        $user1 = $this->createMock(IUser::class);
1055
+        $user1->method('getCloudId')->willReturn('user1@localcloud');
1056
+        $user2 = $this->createMock(IUser::class);
1057
+        $user2->method('getCloudId')->willReturn('user2@localcloud');
1058
+        $this->userManager->expects(self::exactly(2))
1059
+            ->method('get')
1060
+            ->willReturnCallback(function ($uid) use ($user1, $user2) {
1061
+                return match ($uid) {
1062
+                    'user1' => $user1,
1063
+                    'user2' => $user2,
1064
+                };
1065
+            });
1066
+        $this->contactsManager
1067
+            ->expects(self::exactly(3))
1068
+            ->method('search')
1069
+            ->willReturnCallback(function ($uid, $searchProps, $options) {
1070
+                return match ([$uid, $options['limit'] ?? null]) {
1071
+                    ['user1@localcloud', 1] => [
1072
+                        [
1073
+                            'UID' => 'user1',
1074
+                            'URI' => 'user1.vcf',
1075
+                        ],
1076
+                    ],
1077
+                    ['user2@localcloud' => [], 1], // Simulate not found
1078
+                    ['', 4] => [
1079
+                        [
1080
+                            'UID' => 'contact1',
1081
+                            'URI' => 'contact1.vcf',
1082
+                        ],
1083
+                        [
1084
+                            'UID' => 'contact2',
1085
+                            'URI' => 'contact2.vcf',
1086
+                        ],
1087
+                    ],
1088
+                    default => [],
1089
+                };
1090
+            });
1091
+
1092
+        $contacts = $this->contactsStore->getContacts(
1093
+            $user,
1094
+            null,
1095
+            5,
1096
+        );
1097
+
1098
+        self::assertCount(3, $contacts);
1099
+        self::assertEquals('user1', $contacts[0]->getProperty('UID'));
1100
+        self::assertEquals('contact1', $contacts[1]->getProperty('UID'));
1101
+        self::assertEquals('contact2', $contacts[2]->getProperty('UID'));
1102
+    }
1103
+
1104
+    public function testPaginateRecentStatus(): void {
1105
+        $user = $this->createMock(IUser::class);
1106
+        $status1 = new UserStatus();
1107
+        $status1->setUserId('user1');
1108
+        $status2 = new UserStatus();
1109
+        $status2->setUserId('user2');
1110
+        $status3 = new UserStatus();
1111
+        $status3->setUserId('user3');
1112
+        $this->statusService->expects(self::never())
1113
+            ->method('findAllRecentStatusChanges');
1114
+        $this->contactsManager
1115
+            ->expects(self::exactly(2))
1116
+            ->method('search')
1117
+            ->willReturnCallback(function ($uid, $searchProps, $options) {
1118
+                return match ([$uid, $options['limit'] ?? null, $options['offset'] ?? null]) {
1119
+                    ['', 2, 0] => [
1120
+                        [
1121
+                            'UID' => 'contact1',
1122
+                            'URI' => 'contact1.vcf',
1123
+                        ],
1124
+                        [
1125
+                            'UID' => 'contact2',
1126
+                            'URI' => 'contact2.vcf',
1127
+                        ],
1128
+                    ],
1129
+                    ['', 2, 3] => [
1130
+                        [
1131
+                            'UID' => 'contact3',
1132
+                            'URI' => 'contact3.vcf',
1133
+                        ],
1134
+                    ],
1135
+                    default => [],
1136
+                };
1137
+            });
1138
+
1139
+        $page1 = $this->contactsStore->getContacts(
1140
+            $user,
1141
+            null,
1142
+            2,
1143
+            0,
1144
+        );
1145
+        $page2 = $this->contactsStore->getContacts(
1146
+            $user,
1147
+            null,
1148
+            2,
1149
+            3,
1150
+        );
1151
+
1152
+        self::assertCount(2, $page1);
1153
+        self::assertCount(1, $page2);
1154
+    }
1155 1155
 }
Please login to merge, or discard this patch.