| Total Complexity | 58 |
| Total Lines | 380 |
| Duplicated Lines | 0 % |
| Changes | 1 | ||
| Bugs | 0 | Features | 0 |
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 |
||
| 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( |
||
| 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 { |
||
| 432 | } |
||
| 433 | |||
| 434 | /** |
||
| 435 | * @return bool |
||
| 436 | */ |
||
| 437 | protected function isV2(): bool { |
||
| 439 | } |
||
| 440 | } |
||
| 441 |