Passed
Push — master ( 80e12c...127af0 )
by Julius
15:15 queued 16s
created

ShareesAPIController   D

Complexity

Total Complexity 58

Size/Duplication

Total Lines 380
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 183
dl 0
loc 380
rs 4.5599
c 1
b 0
f 0
wmc 58

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 15 1
A getPaginationLink() 0 8 2
B getAllSharees() 0 42 8
A isRemoteSharingAllowed() 0 7 2
A sortShareesByFrequency() 0 5 1
A isRemoteGroupSharingAllowed() 0 7 2
A getAllShareesByType() 0 17 3
A isV2() 0 2 1
F search() 0 103 24
C findRecommended() 0 47 14

How to fix   Complexity   

Complex Class

Complex classes like ShareesAPIController often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ShareesAPIController, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * @copyright Copyright (c) 2016, ownCloud, Inc.
7
 *
8
 * @author Arthur Schiwon <[email protected]>
9
 * @author Bjoern Schiessle <[email protected]>
10
 * @author Björn Schießle <[email protected]>
11
 * @author Christoph Wurst <[email protected]>
12
 * @author Daniel Calviño Sánchez <[email protected]>
13
 * @author Daniel Kesselberg <[email protected]>
14
 * @author J0WI <[email protected]>
15
 * @author Joas Schilling <[email protected]>
16
 * @author John Molakvoæ <[email protected]>
17
 * @author Julius Härtl <[email protected]>
18
 * @author Maxence Lange <[email protected]>
19
 * @author Morris Jobke <[email protected]>
20
 * @author Robin Appelman <[email protected]>
21
 * @author Roeland Jago Douma <[email protected]>
22
 * @author Kate Döen <[email protected]>
23
 *
24
 * @license AGPL-3.0
25
 *
26
 * This code is free software: you can redistribute it and/or modify
27
 * it under the terms of the GNU Affero General Public License, version 3,
28
 * as published by the Free Software Foundation.
29
 *
30
 * This program is distributed in the hope that it will be useful,
31
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
32
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
33
 * GNU Affero General Public License for more details.
34
 *
35
 * You should have received a copy of the GNU Affero General Public License, version 3,
36
 * along with this program. If not, see <http://www.gnu.org/licenses/>
37
 *
38
 */
39
namespace OCA\Files_Sharing\Controller;
40
41
use OCP\Constants;
42
use function array_slice;
43
use function array_values;
44
use Generator;
45
use OC\Collaboration\Collaborators\SearchResult;
46
use OCP\AppFramework\Http\DataResponse;
47
use OCP\AppFramework\OCS\OCSBadRequestException;
48
use OCP\AppFramework\OCSController;
49
use OCP\Collaboration\Collaborators\ISearch;
50
use OCP\Collaboration\Collaborators\ISearchResult;
51
use OCP\Collaboration\Collaborators\SearchResultType;
52
use OCP\IConfig;
53
use OCP\IRequest;
54
use OCP\IURLGenerator;
55
use OCP\Share\IShare;
56
use OCP\Share\IManager;
57
use function usort;
58
59
class ShareesAPIController extends OCSController {
60
61
	/** @var string */
62
	protected $userId;
63
64
	/** @var IConfig */
65
	protected $config;
66
67
	/** @var IURLGenerator */
68
	protected $urlGenerator;
69
70
	/** @var IManager */
71
	protected $shareManager;
72
73
	/** @var int */
74
	protected $offset = 0;
75
76
	/** @var int */
77
	protected $limit = 10;
78
79
	/** @var array */
80
	protected $result = [
81
		'exact' => [
82
			'users' => [],
83
			'groups' => [],
84
			'remotes' => [],
85
			'remote_groups' => [],
86
			'emails' => [],
87
			'circles' => [],
88
			'rooms' => [],
89
		],
90
		'users' => [],
91
		'groups' => [],
92
		'remotes' => [],
93
		'remote_groups' => [],
94
		'emails' => [],
95
		'lookup' => [],
96
		'circles' => [],
97
		'rooms' => [],
98
		'lookupEnabled' => false,
99
	];
100
101
	protected $reachedEndFor = [];
102
	/** @var ISearch */
103
	private $collaboratorSearch;
104
105
	/**
106
	 * @param string $UserId
107
	 * @param string $appName
108
	 * @param IRequest $request
109
	 * @param IConfig $config
110
	 * @param IURLGenerator $urlGenerator
111
	 * @param IManager $shareManager
112
	 * @param ISearch $collaboratorSearch
113
	 */
114
	public function __construct(
115
		$UserId,
116
		string $appName,
117
		IRequest $request,
118
		IConfig $config,
119
		IURLGenerator $urlGenerator,
120
		IManager $shareManager,
121
		ISearch $collaboratorSearch
122
	) {
123
		parent::__construct($appName, $request);
124
		$this->userId = $UserId;
125
		$this->config = $config;
126
		$this->urlGenerator = $urlGenerator;
127
		$this->shareManager = $shareManager;
128
		$this->collaboratorSearch = $collaboratorSearch;
129
	}
130
131
	/**
132
	 * @NoAdminRequired
133
	 *
134
	 * @param string $search
135
	 * @param string $itemType
136
	 * @param int $page
137
	 * @param int $perPage
138
	 * @param int|int[] $shareType
139
	 * @param bool $lookup
140
	 * @return DataResponse
141
	 * @throws OCSBadRequestException
142
	 */
143
	public function search(string $search = '', string $itemType = null, int $page = 1, int $perPage = 200, $shareType = null, bool $lookup = false): DataResponse {
144
145
		// only search for string larger than a given threshold
146
		$threshold = $this->config->getSystemValueInt('sharing.minSearchStringLength', 0);
147
		if (strlen($search) < $threshold) {
148
			return new DataResponse($this->result);
149
		}
150
151
		// never return more than the max. number of results configured in the config.php
152
		$maxResults = $this->config->getSystemValueInt('sharing.maxAutocompleteResults', Constants::SHARING_MAX_AUTOCOMPLETE_RESULTS_DEFAULT);
153
		if ($maxResults > 0) {
154
			$perPage = min($perPage, $maxResults);
155
		}
156
		if ($perPage <= 0) {
157
			throw new OCSBadRequestException('Invalid perPage argument');
158
		}
159
		if ($page <= 0) {
160
			throw new OCSBadRequestException('Invalid page');
161
		}
162
163
		$shareTypes = [
164
			IShare::TYPE_USER,
165
		];
166
167
		if ($itemType === null) {
168
			throw new OCSBadRequestException('Missing itemType');
169
		} elseif ($itemType === 'file' || $itemType === 'folder') {
170
			if ($this->shareManager->allowGroupSharing()) {
171
				$shareTypes[] = IShare::TYPE_GROUP;
172
			}
173
174
			if ($this->isRemoteSharingAllowed($itemType)) {
175
				$shareTypes[] = IShare::TYPE_REMOTE;
176
			}
177
178
			if ($this->isRemoteGroupSharingAllowed($itemType)) {
179
				$shareTypes[] = IShare::TYPE_REMOTE_GROUP;
180
			}
181
182
			if ($this->shareManager->shareProviderExists(IShare::TYPE_EMAIL)) {
183
				$shareTypes[] = IShare::TYPE_EMAIL;
184
			}
185
186
			if ($this->shareManager->shareProviderExists(IShare::TYPE_ROOM)) {
187
				$shareTypes[] = IShare::TYPE_ROOM;
188
			}
189
190
			if ($this->shareManager->shareProviderExists(IShare::TYPE_SCIENCEMESH)) {
191
				$shareTypes[] = IShare::TYPE_SCIENCEMESH;
192
			}
193
		} else {
194
			if ($this->shareManager->allowGroupSharing()) {
195
				$shareTypes[] = IShare::TYPE_GROUP;
196
			}
197
			$shareTypes[] = IShare::TYPE_EMAIL;
198
		}
199
200
		// FIXME: DI
201
		if (\OC::$server->getAppManager()->isEnabledForUser('circles') && class_exists('\OCA\Circles\ShareByCircleProvider')) {
202
			$shareTypes[] = IShare::TYPE_CIRCLE;
203
		}
204
205
		if ($this->shareManager->shareProviderExists(IShare::TYPE_SCIENCEMESH)) {
206
			$shareTypes[] = IShare::TYPE_SCIENCEMESH;
207
		}
208
209
		if ($shareType !== null && is_array($shareType)) {
210
			$shareTypes = array_intersect($shareTypes, $shareType);
211
		} elseif (is_numeric($shareType)) {
212
			$shareTypes = array_intersect($shareTypes, [(int) $shareType]);
213
		}
214
		sort($shareTypes);
215
216
		$this->limit = $perPage;
217
		$this->offset = $perPage * ($page - 1);
218
219
		// In global scale mode we always search the loogup server
220
		if ($this->config->getSystemValueBool('gs.enabled', false)) {
221
			$lookup = true;
222
			$this->result['lookupEnabled'] = true;
223
		} else {
224
			$this->result['lookupEnabled'] = $this->config->getAppValue('files_sharing', 'lookupServerEnabled', 'yes') === 'yes';
225
		}
226
227
		[$result, $hasMoreResults] = $this->collaboratorSearch->search($search, $shareTypes, $lookup, $this->limit, $this->offset);
228
229
		// extra treatment for 'exact' subarray, with a single merge expected keys might be lost
230
		if (isset($result['exact'])) {
231
			$result['exact'] = array_merge($this->result['exact'], $result['exact']);
232
		}
233
		$this->result = array_merge($this->result, $result);
234
		$response = new DataResponse($this->result);
235
236
		if ($hasMoreResults) {
237
			$response->addHeader('Link', $this->getPaginationLink($page, [
238
				'search' => $search,
239
				'itemType' => $itemType,
240
				'shareType' => $shareTypes,
241
				'perPage' => $perPage,
242
			]));
243
		}
244
245
		return $response;
246
	}
247
248
	/**
249
	 * @param string $user
250
	 * @param int $shareType
251
	 *
252
	 * @return Generator<array<string>>
253
	 */
254
	private function getAllShareesByType(string $user, int $shareType): Generator {
255
		$offset = 0;
256
		$pageSize = 50;
257
258
		while (count($page = $this->shareManager->getSharesBy(
259
			$user,
260
			$shareType,
261
			null,
262
			false,
263
			$pageSize,
264
			$offset
265
		))) {
266
			foreach ($page as $share) {
267
				yield [$share->getSharedWith(), $share->getSharedWithDisplayName() ?? $share->getSharedWith()];
268
			}
269
270
			$offset += $pageSize;
271
		}
272
	}
273
274
	private function sortShareesByFrequency(array $sharees): array {
275
		usort($sharees, function (array $s1, array $s2): int {
276
			return $s2['count'] - $s1['count'];
277
		});
278
		return $sharees;
279
	}
280
281
	private $searchResultTypeMap = [
282
		IShare::TYPE_USER => 'users',
283
		IShare::TYPE_GROUP => 'groups',
284
		IShare::TYPE_REMOTE => 'remotes',
285
		IShare::TYPE_REMOTE_GROUP => 'remote_groups',
286
		IShare::TYPE_EMAIL => 'emails',
287
	];
288
289
	private function getAllSharees(string $user, array $shareTypes): ISearchResult {
290
		$result = [];
291
		foreach ($shareTypes as $shareType) {
292
			$sharees = $this->getAllShareesByType($user, $shareType);
293
			$shareTypeResults = [];
294
			foreach ($sharees as [$sharee, $displayname]) {
295
				if (!isset($this->searchResultTypeMap[$shareType])) {
296
					continue;
297
				}
298
299
				if (!isset($shareTypeResults[$sharee])) {
300
					$shareTypeResults[$sharee] = [
301
						'count' => 1,
302
						'label' => $displayname,
303
						'value' => [
304
							'shareType' => $shareType,
305
							'shareWith' => $sharee,
306
						],
307
					];
308
				} else {
309
					$shareTypeResults[$sharee]['count']++;
310
				}
311
			}
312
			$result = array_merge($result, array_values($shareTypeResults));
313
		}
314
315
		$top5 = array_slice(
316
			$this->sortShareesByFrequency($result),
317
			0,
318
			5
319
		);
320
321
		$searchResult = new SearchResult();
322
		foreach ($this->searchResultTypeMap as $int => $str) {
323
			$searchResult->addResultSet(new SearchResultType($str), [], []);
324
			foreach ($top5 as $x) {
325
				if ($x['value']['shareType'] === $int) {
326
					$searchResult->addResultSet(new SearchResultType($str), [], [$x]);
327
				}
328
			}
329
		}
330
		return $searchResult;
331
	}
332
333
	/**
334
	 * @NoAdminRequired
335
	 *
336
	 * @param string $itemType
337
	 * @return DataResponse
338
	 * @throws OCSBadRequestException
339
	 */
340
	public function findRecommended(string $itemType = null, $shareType = null): DataResponse {
341
		$shareTypes = [
342
			IShare::TYPE_USER,
343
		];
344
345
		if ($itemType === null) {
346
			throw new OCSBadRequestException('Missing itemType');
347
		} elseif ($itemType === 'file' || $itemType === 'folder') {
348
			if ($this->shareManager->allowGroupSharing()) {
349
				$shareTypes[] = IShare::TYPE_GROUP;
350
			}
351
352
			if ($this->isRemoteSharingAllowed($itemType)) {
353
				$shareTypes[] = IShare::TYPE_REMOTE;
354
			}
355
356
			if ($this->isRemoteGroupSharingAllowed($itemType)) {
357
				$shareTypes[] = IShare::TYPE_REMOTE_GROUP;
358
			}
359
360
			if ($this->shareManager->shareProviderExists(IShare::TYPE_EMAIL)) {
361
				$shareTypes[] = IShare::TYPE_EMAIL;
362
			}
363
364
			if ($this->shareManager->shareProviderExists(IShare::TYPE_ROOM)) {
365
				$shareTypes[] = IShare::TYPE_ROOM;
366
			}
367
		} else {
368
			$shareTypes[] = IShare::TYPE_GROUP;
369
			$shareTypes[] = IShare::TYPE_EMAIL;
370
		}
371
372
		// FIXME: DI
373
		if (\OC::$server->getAppManager()->isEnabledForUser('circles') && class_exists('\OCA\Circles\ShareByCircleProvider')) {
374
			$shareTypes[] = IShare::TYPE_CIRCLE;
375
		}
376
377
		if (isset($_GET['shareType']) && is_array($_GET['shareType'])) {
378
			$shareTypes = array_intersect($shareTypes, $_GET['shareType']);
379
			sort($shareTypes);
380
		} elseif (is_numeric($shareType)) {
381
			$shareTypes = array_intersect($shareTypes, [(int) $shareType]);
382
			sort($shareTypes);
383
		}
384
385
		return new DataResponse(
386
			$this->getAllSharees($this->userId, $shareTypes)->asArray()
387
		);
388
	}
389
390
	/**
391
	 * Method to get out the static call for better testing
392
	 *
393
	 * @param string $itemType
394
	 * @return bool
395
	 */
396
	protected function isRemoteSharingAllowed(string $itemType): bool {
397
		try {
398
			// FIXME: static foo makes unit testing unnecessarily difficult
399
			$backend = \OC\Share\Share::getBackend($itemType);
400
			return $backend->isShareTypeAllowed(IShare::TYPE_REMOTE);
401
		} catch (\Exception $e) {
402
			return false;
403
		}
404
	}
405
406
	protected function isRemoteGroupSharingAllowed(string $itemType): bool {
407
		try {
408
			// FIXME: static foo makes unit testing unnecessarily difficult
409
			$backend = \OC\Share\Share::getBackend($itemType);
410
			return $backend->isShareTypeAllowed(IShare::TYPE_REMOTE_GROUP);
411
		} catch (\Exception $e) {
412
			return false;
413
		}
414
	}
415
416
417
	/**
418
	 * Generates a bunch of pagination links for the current page
419
	 *
420
	 * @param int $page Current page
421
	 * @param array $params Parameters for the URL
422
	 * @return string
423
	 */
424
	protected function getPaginationLink(int $page, array $params): string {
425
		if ($this->isV2()) {
426
			$url = $this->urlGenerator->getAbsoluteURL('/ocs/v2.php/apps/files_sharing/api/v1/sharees') . '?';
427
		} else {
428
			$url = $this->urlGenerator->getAbsoluteURL('/ocs/v1.php/apps/files_sharing/api/v1/sharees') . '?';
429
		}
430
		$params['page'] = $page + 1;
431
		return '<' . $url . http_build_query($params) . '>; rel="next"';
432
	}
433
434
	/**
435
	 * @return bool
436
	 */
437
	protected function isV2(): bool {
438
		return $this->request->getScriptName() === '/ocs/v2.php';
439
	}
440
}
441