Passed
Push — master ( a5443a...38ca46 )
by Julius
14:07 queued 13s
created

ShareesAPIController   C

Complexity

Total Complexity 56

Size/Duplication

Total Lines 372
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 179
c 1
b 0
f 0
dl 0
loc 372
rs 5.5199
wmc 56

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 95 22
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
		} else {
190
			if ($this->shareManager->allowGroupSharing()) {
191
				$shareTypes[] = IShare::TYPE_GROUP;
192
			}
193
			$shareTypes[] = IShare::TYPE_EMAIL;
194
		}
195
196
		// FIXME: DI
197
		if (\OC::$server->getAppManager()->isEnabledForUser('circles') && class_exists('\OCA\Circles\ShareByCircleProvider')) {
198
			$shareTypes[] = IShare::TYPE_CIRCLE;
199
		}
200
201
		if ($shareType !== null && is_array($shareType)) {
202
			$shareTypes = array_intersect($shareTypes, $shareType);
203
		} elseif (is_numeric($shareType)) {
204
			$shareTypes = array_intersect($shareTypes, [(int) $shareType]);
205
		}
206
		sort($shareTypes);
207
208
		$this->limit = $perPage;
209
		$this->offset = $perPage * ($page - 1);
210
211
		// In global scale mode we always search the loogup server
212
		if ($this->config->getSystemValueBool('gs.enabled', false)) {
213
			$lookup = true;
214
			$this->result['lookupEnabled'] = true;
215
		} else {
216
			$this->result['lookupEnabled'] = $this->config->getAppValue('files_sharing', 'lookupServerEnabled', 'yes') === 'yes';
217
		}
218
219
		[$result, $hasMoreResults] = $this->collaboratorSearch->search($search, $shareTypes, $lookup, $this->limit, $this->offset);
220
221
		// extra treatment for 'exact' subarray, with a single merge expected keys might be lost
222
		if (isset($result['exact'])) {
223
			$result['exact'] = array_merge($this->result['exact'], $result['exact']);
224
		}
225
		$this->result = array_merge($this->result, $result);
226
		$response = new DataResponse($this->result);
227
228
		if ($hasMoreResults) {
229
			$response->addHeader('Link', $this->getPaginationLink($page, [
230
				'search' => $search,
231
				'itemType' => $itemType,
232
				'shareType' => $shareTypes,
233
				'perPage' => $perPage,
234
			]));
235
		}
236
237
		return $response;
238
	}
239
240
	/**
241
	 * @param string $user
242
	 * @param int $shareType
243
	 *
244
	 * @return Generator<array<string>>
245
	 */
246
	private function getAllShareesByType(string $user, int $shareType): Generator {
247
		$offset = 0;
248
		$pageSize = 50;
249
250
		while (count($page = $this->shareManager->getSharesBy(
251
			$user,
252
			$shareType,
253
			null,
254
			false,
255
			$pageSize,
256
			$offset
257
		))) {
258
			foreach ($page as $share) {
259
				yield [$share->getSharedWith(), $share->getSharedWithDisplayName() ?? $share->getSharedWith()];
260
			}
261
262
			$offset += $pageSize;
263
		}
264
	}
265
266
	private function sortShareesByFrequency(array $sharees): array {
267
		usort($sharees, function (array $s1, array $s2): int {
268
			return $s2['count'] - $s1['count'];
269
		});
270
		return $sharees;
271
	}
272
273
	private $searchResultTypeMap = [
274
		IShare::TYPE_USER => 'users',
275
		IShare::TYPE_GROUP => 'groups',
276
		IShare::TYPE_REMOTE => 'remotes',
277
		IShare::TYPE_REMOTE_GROUP => 'remote_groups',
278
		IShare::TYPE_EMAIL => 'emails',
279
	];
280
281
	private function getAllSharees(string $user, array $shareTypes): ISearchResult {
282
		$result = [];
283
		foreach ($shareTypes as $shareType) {
284
			$sharees = $this->getAllShareesByType($user, $shareType);
285
			$shareTypeResults = [];
286
			foreach ($sharees as [$sharee, $displayname]) {
287
				if (!isset($this->searchResultTypeMap[$shareType])) {
288
					continue;
289
				}
290
291
				if (!isset($shareTypeResults[$sharee])) {
292
					$shareTypeResults[$sharee] = [
293
						'count' => 1,
294
						'label' => $displayname,
295
						'value' => [
296
							'shareType' => $shareType,
297
							'shareWith' => $sharee,
298
						],
299
					];
300
				} else {
301
					$shareTypeResults[$sharee]['count']++;
302
				}
303
			}
304
			$result = array_merge($result, array_values($shareTypeResults));
305
		}
306
307
		$top5 = array_slice(
308
			$this->sortShareesByFrequency($result),
309
			0,
310
			5
311
		);
312
313
		$searchResult = new SearchResult();
314
		foreach ($this->searchResultTypeMap as $int => $str) {
315
			$searchResult->addResultSet(new SearchResultType($str), [], []);
316
			foreach ($top5 as $x) {
317
				if ($x['value']['shareType'] === $int) {
318
					$searchResult->addResultSet(new SearchResultType($str), [], [$x]);
319
				}
320
			}
321
		}
322
		return $searchResult;
323
	}
324
325
	/**
326
	 * @NoAdminRequired
327
	 *
328
	 * @param string $itemType
329
	 * @return DataResponse
330
	 * @throws OCSBadRequestException
331
	 */
332
	public function findRecommended(string $itemType = null, $shareType = null): DataResponse {
333
		$shareTypes = [
334
			IShare::TYPE_USER,
335
		];
336
337
		if ($itemType === null) {
338
			throw new OCSBadRequestException('Missing itemType');
339
		} elseif ($itemType === 'file' || $itemType === 'folder') {
340
			if ($this->shareManager->allowGroupSharing()) {
341
				$shareTypes[] = IShare::TYPE_GROUP;
342
			}
343
344
			if ($this->isRemoteSharingAllowed($itemType)) {
345
				$shareTypes[] = IShare::TYPE_REMOTE;
346
			}
347
348
			if ($this->isRemoteGroupSharingAllowed($itemType)) {
349
				$shareTypes[] = IShare::TYPE_REMOTE_GROUP;
350
			}
351
352
			if ($this->shareManager->shareProviderExists(IShare::TYPE_EMAIL)) {
353
				$shareTypes[] = IShare::TYPE_EMAIL;
354
			}
355
356
			if ($this->shareManager->shareProviderExists(IShare::TYPE_ROOM)) {
357
				$shareTypes[] = IShare::TYPE_ROOM;
358
			}
359
		} else {
360
			$shareTypes[] = IShare::TYPE_GROUP;
361
			$shareTypes[] = IShare::TYPE_EMAIL;
362
		}
363
364
		// FIXME: DI
365
		if (\OC::$server->getAppManager()->isEnabledForUser('circles') && class_exists('\OCA\Circles\ShareByCircleProvider')) {
366
			$shareTypes[] = IShare::TYPE_CIRCLE;
367
		}
368
369
		if (isset($_GET['shareType']) && is_array($_GET['shareType'])) {
370
			$shareTypes = array_intersect($shareTypes, $_GET['shareType']);
371
			sort($shareTypes);
372
		} elseif (is_numeric($shareType)) {
373
			$shareTypes = array_intersect($shareTypes, [(int) $shareType]);
374
			sort($shareTypes);
375
		}
376
377
		return new DataResponse(
378
			$this->getAllSharees($this->userId, $shareTypes)->asArray()
379
		);
380
	}
381
382
	/**
383
	 * Method to get out the static call for better testing
384
	 *
385
	 * @param string $itemType
386
	 * @return bool
387
	 */
388
	protected function isRemoteSharingAllowed(string $itemType): bool {
389
		try {
390
			// FIXME: static foo makes unit testing unnecessarily difficult
391
			$backend = \OC\Share\Share::getBackend($itemType);
392
			return $backend->isShareTypeAllowed(IShare::TYPE_REMOTE);
393
		} catch (\Exception $e) {
394
			return false;
395
		}
396
	}
397
398
	protected function isRemoteGroupSharingAllowed(string $itemType): bool {
399
		try {
400
			// FIXME: static foo makes unit testing unnecessarily difficult
401
			$backend = \OC\Share\Share::getBackend($itemType);
402
			return $backend->isShareTypeAllowed(IShare::TYPE_REMOTE_GROUP);
403
		} catch (\Exception $e) {
404
			return false;
405
		}
406
	}
407
408
409
	/**
410
	 * Generates a bunch of pagination links for the current page
411
	 *
412
	 * @param int $page Current page
413
	 * @param array $params Parameters for the URL
414
	 * @return string
415
	 */
416
	protected function getPaginationLink(int $page, array $params): string {
417
		if ($this->isV2()) {
418
			$url = $this->urlGenerator->getAbsoluteURL('/ocs/v2.php/apps/files_sharing/api/v1/sharees') . '?';
419
		} else {
420
			$url = $this->urlGenerator->getAbsoluteURL('/ocs/v1.php/apps/files_sharing/api/v1/sharees') . '?';
421
		}
422
		$params['page'] = $page + 1;
423
		return '<' . $url . http_build_query($params) . '>; rel="next"';
424
	}
425
426
	/**
427
	 * @return bool
428
	 */
429
	protected function isV2(): bool {
430
		return $this->request->getScriptName() === '/ocs/v2.php';
431
	}
432
}
433