Passed
Push — master ( d011df...0d46fa )
by Joas
15:48 queued 11s
created

UserPlugin::search()   F

Complexity

Conditions 48
Paths > 20000

Size

Total Lines 192
Code Lines 123

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 48
eloc 123
nc 121968
nop 4
dl 0
loc 192
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * @copyright Copyright (c) 2017 Arthur Schiwon <[email protected]>
4
 *
5
 * @author Arthur Schiwon <[email protected]>
6
 * @author Christoph Wurst <[email protected]>
7
 * @author Georg Ehrke <[email protected]>
8
 * @author Joas Schilling <[email protected]>
9
 * @author John Molakvoæ (skjnldsv) <[email protected]>
10
 * @author Julius Härtl <[email protected]>
11
 * @author Morris Jobke <[email protected]>
12
 * @author Robin Appelman <[email protected]>
13
 * @author Roeland Jago Douma <[email protected]>
14
 * @author Thomas Citharel <[email protected]>
15
 *
16
 * @license GNU AGPL version 3 or any later version
17
 *
18
 * This program is free software: you can redistribute it and/or modify
19
 * it under the terms of the GNU Affero General Public License as
20
 * published by the Free Software Foundation, either version 3 of the
21
 * License, or (at your option) any later version.
22
 *
23
 * This program is distributed in the hope that it will be useful,
24
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
26
 * GNU Affero General Public License for more details.
27
 *
28
 * You should have received a copy of the GNU Affero General Public License
29
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
30
 *
31
 */
32
33
namespace OC\Collaboration\Collaborators;
34
35
use OC\KnownUser\KnownUserService;
36
use OCP\Collaboration\Collaborators\ISearchPlugin;
37
use OCP\Collaboration\Collaborators\ISearchResult;
38
use OCP\Collaboration\Collaborators\SearchResultType;
39
use OCP\IConfig;
40
use OCP\IGroupManager;
41
use OCP\IUser;
42
use OCP\IUserManager;
43
use OCP\IUserSession;
44
use OCP\Share\IShare;
45
use OCP\UserStatus\IManager as IUserStatusManager;
46
47
class UserPlugin implements ISearchPlugin {
48
	/* @var bool */
49
	protected $shareWithGroupOnly;
50
	/* @var bool */
51
	protected $shareeEnumeration;
52
	/* @var bool */
53
	protected $shareeEnumerationInGroupOnly;
54
	/* @var bool */
55
	protected $shareeEnumerationPhone;
56
	/* @var bool */
57
	protected $shareeEnumerationFullMatch;
58
59
	/** @var IConfig */
60
	private $config;
61
	/** @var IGroupManager */
62
	private $groupManager;
63
	/** @var IUserSession */
64
	private $userSession;
65
	/** @var IUserManager */
66
	private $userManager;
67
	/** @var KnownUserService */
68
	private $knownUserService;
69
	/** @var IUserStatusManager */
70
	private $userStatusManager;
71
72
	public function __construct(IConfig $config,
73
								IUserManager $userManager,
74
								IGroupManager $groupManager,
75
								IUserSession $userSession,
76
								KnownUserService $knownUserService,
77
								IUserStatusManager $userStatusManager) {
78
		$this->config = $config;
79
80
		$this->groupManager = $groupManager;
81
		$this->userSession = $userSession;
82
		$this->userManager = $userManager;
83
		$this->knownUserService = $knownUserService;
84
		$this->userStatusManager = $userStatusManager;
85
86
		$this->shareWithGroupOnly = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes';
87
		$this->shareeEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes';
88
		$this->shareeEnumerationInGroupOnly = $this->shareeEnumeration && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes';
89
		$this->shareeEnumerationPhone = $this->shareeEnumeration && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_phone', 'no') === 'yes';
90
		$this->shareeEnumerationFullMatch = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match', 'yes') === 'yes';
91
	}
92
93
	public function search($search, $limit, $offset, ISearchResult $searchResult) {
94
		$result = ['wide' => [], 'exact' => []];
95
		$users = [];
96
		$hasMoreResults = false;
97
98
		$currentUserId = $this->userSession->getUser()->getUID();
99
		$currentUserGroups = $this->groupManager->getUserGroupIds($this->userSession->getUser());
100
		if ($this->shareWithGroupOnly || $this->shareeEnumerationInGroupOnly) {
101
			// Search in all the groups this user is part of
102
			foreach ($currentUserGroups as $userGroupId) {
103
				$usersInGroup = $this->groupManager->displayNamesInGroup($userGroupId, $search, $limit, $offset);
104
				foreach ($usersInGroup as $userId => $displayName) {
105
					$userId = (string) $userId;
106
					$user = $this->userManager->get($userId);
107
					if (!$user->isEnabled()) {
108
						// Ignore disabled users
109
						continue;
110
					}
111
					$users[$userId] = $user;
112
				}
113
				if (count($usersInGroup) >= $limit) {
114
					$hasMoreResults = true;
115
				}
116
			}
117
118
			if (!$this->shareWithGroupOnly && $this->shareeEnumerationPhone) {
119
				$usersTmp = $this->userManager->searchKnownUsersByDisplayName($currentUserId, $search, $limit, $offset);
120
				if (!empty($usersTmp)) {
121
					foreach ($usersTmp as $user) {
122
						if ($user->isEnabled()) { // Don't keep deactivated users
123
							$users[$user->getUID()] = $user;
124
						}
125
					}
126
127
					uasort($users, function ($a, $b) {
128
						/**
129
						 * @var \OC\User\User $a
130
						 * @var \OC\User\User $b
131
						 */
132
						return strcasecmp($a->getDisplayName(), $b->getDisplayName());
133
					});
134
				}
135
			}
136
		} else {
137
			// Search in all users
138
			if ($this->shareeEnumerationPhone) {
139
				$usersTmp = $this->userManager->searchKnownUsersByDisplayName($currentUserId, $search, $limit, $offset);
140
			} else {
141
				$usersTmp = $this->userManager->searchDisplayName($search, $limit, $offset);
142
			}
143
			foreach ($usersTmp as $user) {
144
				if ($user->isEnabled()) { // Don't keep deactivated users
145
					$users[$user->getUID()] = $user;
146
				}
147
			}
148
		}
149
150
		$this->takeOutCurrentUser($users);
151
152
		if (!$this->shareeEnumeration || count($users) < $limit) {
153
			$hasMoreResults = true;
154
		}
155
156
		$foundUserById = false;
157
		$lowerSearch = strtolower($search);
158
		$userStatuses = $this->userStatusManager->getUserStatuses(array_keys($users));
159
		foreach ($users as $uid => $user) {
160
			$userDisplayName = $user->getDisplayName();
161
			$userEmail = $user->getEMailAddress();
162
			$uid = (string) $uid;
163
164
			$status = [];
165
			if (array_key_exists($uid, $userStatuses)) {
166
				$userStatus = $userStatuses[$uid];
167
				$status = [
168
					'status' => $userStatus->getStatus(),
169
					'message' => $userStatus->getMessage(),
170
					'icon' => $userStatus->getIcon(),
171
					'clearAt' => $userStatus->getClearAt()
172
						? (int)$userStatus->getClearAt()->format('U')
173
						: null,
174
				];
175
			}
176
177
178
			if (
179
				$this->shareeEnumerationFullMatch &&
180
				$lowerSearch !== '' && (strtolower($uid) === $lowerSearch ||
181
				strtolower($userDisplayName) === $lowerSearch ||
182
				strtolower($userEmail) === $lowerSearch)
183
			) {
184
				if (strtolower($uid) === $lowerSearch) {
185
					$foundUserById = true;
186
				}
187
				$result['exact'][] = [
188
					'label' => $userDisplayName,
189
					'subline' => $status['message'] ?? '',
190
					'icon' => 'icon-user',
191
					'value' => [
192
						'shareType' => IShare::TYPE_USER,
193
						'shareWith' => $uid,
194
					],
195
					'shareWithDisplayNameUnique' => !empty($userEmail) ? $userEmail : $uid,
196
					'status' => $status,
197
				];
198
			} else {
199
				$addToWideResults = false;
200
				if ($this->shareeEnumeration &&
201
					!($this->shareeEnumerationInGroupOnly || $this->shareeEnumerationPhone)) {
202
					$addToWideResults = true;
203
				}
204
205
				if ($this->shareeEnumerationPhone && $this->knownUserService->isKnownToUser($currentUserId, $user->getUID())) {
206
					$addToWideResults = true;
207
				}
208
209
				if (!$addToWideResults && $this->shareeEnumerationInGroupOnly) {
210
					$commonGroups = array_intersect($currentUserGroups, $this->groupManager->getUserGroupIds($user));
211
					if (!empty($commonGroups)) {
212
						$addToWideResults = true;
213
					}
214
				}
215
216
				if ($addToWideResults) {
217
					$result['wide'][] = [
218
						'label' => $userDisplayName,
219
						'subline' => $status['message'] ?? '',
220
						'icon' => 'icon-user',
221
						'value' => [
222
							'shareType' => IShare::TYPE_USER,
223
							'shareWith' => $uid,
224
						],
225
						'shareWithDisplayNameUnique' => !empty($userEmail) ? $userEmail : $uid,
226
						'status' => $status,
227
					];
228
				}
229
			}
230
		}
231
232
		if ($this->shareeEnumerationFullMatch && $offset === 0 && !$foundUserById) {
233
			// On page one we try if the search result has a direct hit on the
234
			// user id and if so, we add that to the exact match list
235
			$user = $this->userManager->get($search);
236
			if ($user instanceof IUser) {
237
				$addUser = true;
238
239
				if ($this->shareWithGroupOnly) {
240
					// Only add, if we have a common group
241
					$commonGroups = array_intersect($currentUserGroups, $this->groupManager->getUserGroupIds($user));
242
					$addUser = !empty($commonGroups);
243
				}
244
245
				if ($addUser) {
246
					$status = [];
247
					$uid = $user->getUID();
248
					$userEmail = $user->getEMailAddress();
249
					if (array_key_exists($user->getUID(), $userStatuses)) {
250
						$userStatus = $userStatuses[$user->getUID()];
251
						$status = [
252
							'status' => $userStatus->getStatus(),
253
							'message' => $userStatus->getMessage(),
254
							'icon' => $userStatus->getIcon(),
255
							'clearAt' => $userStatus->getClearAt()
256
								? (int)$userStatus->getClearAt()->format('U')
257
								: null,
258
						];
259
					}
260
261
					$result['exact'][] = [
262
						'label' => $user->getDisplayName(),
263
						'icon' => 'icon-user',
264
						'subline' => $status['message'] ?? '',
265
						'value' => [
266
							'shareType' => IShare::TYPE_USER,
267
							'shareWith' => $user->getUID(),
268
						],
269
						'shareWithDisplayNameUnique' => $userEmail !== null && $userEmail !== '' ? $userEmail : $uid,
270
						'status' => $status,
271
					];
272
				}
273
			}
274
		}
275
276
277
278
		$type = new SearchResultType('users');
279
		$searchResult->addResultSet($type, $result['wide'], $result['exact']);
280
		if (count($result['exact'])) {
281
			$searchResult->markExactIdMatch($type);
282
		}
283
284
		return $hasMoreResults;
285
	}
286
287
	public function takeOutCurrentUser(array &$users) {
288
		$currentUser = $this->userSession->getUser();
289
		if (!is_null($currentUser)) {
290
			if (isset($users[$currentUser->getUID()])) {
291
				unset($users[$currentUser->getUID()]);
292
			}
293
		}
294
	}
295
}
296