Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like Share20OCS 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
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 Share20OCS, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 53 | class Share20OCS extends OCSController { |
||
| 54 | |||
| 55 | /** @var IManager */ |
||
| 56 | private $shareManager; |
||
| 57 | /** @var IGroupManager */ |
||
| 58 | private $groupManager; |
||
| 59 | /** @var IUserManager */ |
||
| 60 | private $userManager; |
||
| 61 | /** @var IRequest */ |
||
| 62 | protected $request; |
||
| 63 | /** @var IRootFolder */ |
||
| 64 | private $rootFolder; |
||
| 65 | /** @var IURLGenerator */ |
||
| 66 | private $urlGenerator; |
||
| 67 | /** @var IUser */ |
||
| 68 | private $currentUser; |
||
| 69 | /** @var IL10N */ |
||
| 70 | private $l; |
||
| 71 | /** @var \OCP\Files\Node */ |
||
| 72 | private $lockedNode; |
||
| 73 | |||
| 74 | /** |
||
| 75 | * Share20OCS constructor. |
||
| 76 | * |
||
| 77 | * @param string $appName |
||
| 78 | * @param IRequest $request |
||
| 79 | * @param IManager $shareManager |
||
| 80 | * @param IGroupManager $groupManager |
||
| 81 | * @param IUserManager $userManager |
||
| 82 | * @param IRootFolder $rootFolder |
||
| 83 | * @param IURLGenerator $urlGenerator |
||
| 84 | * @param IUser $currentUser |
||
| 85 | * @param IL10N $l10n |
||
| 86 | */ |
||
| 87 | View Code Duplication | public function __construct( |
|
| 109 | |||
| 110 | /** |
||
| 111 | * Convert an IShare to an array for OCS output |
||
| 112 | * |
||
| 113 | * @param \OCP\Share\IShare $share |
||
| 114 | * @param Node|null $recipientNode |
||
| 115 | * @return array |
||
| 116 | * @throws NotFoundException In case the node can't be resolved. |
||
| 117 | */ |
||
| 118 | protected function formatShare(\OCP\Share\IShare $share, Node $recipientNode = null) { |
||
| 119 | $sharedBy = $this->userManager->get($share->getSharedBy()); |
||
| 120 | $shareOwner = $this->userManager->get($share->getShareOwner()); |
||
| 121 | |||
| 122 | $result = [ |
||
| 123 | 'id' => $share->getId(), |
||
| 124 | 'share_type' => $share->getShareType(), |
||
| 125 | 'uid_owner' => $share->getSharedBy(), |
||
| 126 | 'displayname_owner' => $sharedBy !== null ? $sharedBy->getDisplayName() : $share->getSharedBy(), |
||
| 127 | 'permissions' => $share->getPermissions(), |
||
| 128 | 'stime' => $share->getShareTime()->getTimestamp(), |
||
| 129 | 'parent' => null, |
||
| 130 | 'expiration' => null, |
||
| 131 | 'token' => null, |
||
| 132 | 'uid_file_owner' => $share->getShareOwner(), |
||
| 133 | 'displayname_file_owner' => $shareOwner !== null ? $shareOwner->getDisplayName() : $share->getShareOwner(), |
||
| 134 | ]; |
||
| 135 | |||
| 136 | $userFolder = $this->rootFolder->getUserFolder($this->currentUser->getUID()); |
||
| 137 | if ($recipientNode) { |
||
| 138 | $node = $recipientNode; |
||
| 139 | } else { |
||
| 140 | $nodes = $userFolder->getById($share->getNodeId()); |
||
| 141 | |||
| 142 | if (empty($nodes)) { |
||
| 143 | // fallback to guessing the path |
||
| 144 | $node = $userFolder->get($share->getTarget()); |
||
| 145 | if ($node === null) { |
||
| 146 | throw new NotFoundException(); |
||
| 147 | } |
||
| 148 | } else { |
||
| 149 | $node = $nodes[0]; |
||
| 150 | } |
||
| 151 | } |
||
| 152 | |||
| 153 | $result['path'] = $userFolder->getRelativePath($node->getPath()); |
||
| 154 | if ($node instanceOf \OCP\Files\Folder) { |
||
| 155 | $result['item_type'] = 'folder'; |
||
| 156 | } else { |
||
| 157 | $result['item_type'] = 'file'; |
||
| 158 | } |
||
| 159 | $result['mimetype'] = $node->getMimetype(); |
||
| 160 | $result['storage_id'] = $node->getStorage()->getId(); |
||
| 161 | $result['storage'] = $node->getStorage()->getCache()->getNumericStorageId(); |
||
| 162 | $result['item_source'] = $node->getId(); |
||
| 163 | $result['file_source'] = $node->getId(); |
||
| 164 | $result['file_parent'] = $node->getParent()->getId(); |
||
| 165 | $result['file_target'] = $share->getTarget(); |
||
| 166 | |||
| 167 | if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER) { |
||
| 168 | $sharedWith = $this->userManager->get($share->getSharedWith()); |
||
| 169 | $result['share_with'] = $share->getSharedWith(); |
||
| 170 | $result['share_with_displayname'] = $sharedWith !== null ? $sharedWith->getDisplayName() : $share->getSharedWith(); |
||
| 171 | } else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) { |
||
| 172 | $result['share_with'] = $share->getSharedWith(); |
||
| 173 | $result['share_with_displayname'] = $share->getSharedWith(); |
||
| 174 | } else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) { |
||
| 175 | |||
| 176 | $result['share_with'] = $share->getPassword(); |
||
| 177 | $result['share_with_displayname'] = $share->getPassword(); |
||
| 178 | |||
| 179 | $result['token'] = $share->getToken(); |
||
| 180 | $result['url'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare', ['token' => $share->getToken()]); |
||
| 181 | |||
| 182 | $expiration = $share->getExpirationDate(); |
||
| 183 | if ($expiration !== null) { |
||
| 184 | $result['expiration'] = $expiration->format('Y-m-d 00:00:00'); |
||
| 185 | } |
||
| 186 | |||
| 187 | } else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_REMOTE) { |
||
| 188 | $result['share_with'] = $share->getSharedWith(); |
||
| 189 | $result['share_with_displayname'] = $share->getSharedWith(); |
||
| 190 | $result['token'] = $share->getToken(); |
||
| 191 | } |
||
| 192 | |||
| 193 | $result['mail_send'] = $share->getMailSend() ? 1 : 0; |
||
| 194 | |||
| 195 | return $result; |
||
| 196 | } |
||
| 197 | |||
| 198 | /** |
||
| 199 | * Get a specific share by id |
||
| 200 | * |
||
| 201 | * @NoAdminRequired |
||
| 202 | * |
||
| 203 | * @param string $id |
||
| 204 | * @return DataResponse |
||
| 205 | * @throws OCSNotFoundException |
||
| 206 | */ |
||
| 207 | public function getShare($id) { |
||
| 208 | try { |
||
| 209 | $share = $this->getShareById($id); |
||
| 210 | } catch (ShareNotFound $e) { |
||
| 211 | throw new OCSNotFoundException($this->l->t('Wrong share ID, share doesn\'t exist')); |
||
| 212 | } |
||
| 213 | |||
| 214 | if ($this->canAccessShare($share)) { |
||
| 215 | try { |
||
| 216 | $share = $this->formatShare($share); |
||
| 217 | return new DataResponse([$share]); |
||
| 218 | } catch (NotFoundException $e) { |
||
| 219 | //Fall trough |
||
| 220 | } |
||
| 221 | } |
||
| 222 | |||
| 223 | throw new OCSNotFoundException($this->l->t('Wrong share ID, share doesn\'t exist')); |
||
| 224 | } |
||
| 225 | |||
| 226 | /** |
||
| 227 | * Delete a share |
||
| 228 | * |
||
| 229 | * @NoAdminRequired |
||
| 230 | * |
||
| 231 | * @param string $id |
||
| 232 | * @return DataResponse |
||
| 233 | * @throws OCSNotFoundException |
||
| 234 | */ |
||
| 235 | public function deleteShare($id) { |
||
| 236 | try { |
||
| 237 | $share = $this->getShareById($id); |
||
| 238 | } catch (ShareNotFound $e) { |
||
| 239 | throw new OCSNotFoundException($this->l->t('Wrong share ID, share doesn\'t exist')); |
||
| 240 | } |
||
| 241 | |||
| 242 | try { |
||
| 243 | $this->lock($share->getNode()); |
||
| 244 | } catch (LockedException $e) { |
||
| 245 | throw new OCSNotFoundException($this->l->t('could not delete share')); |
||
| 246 | } |
||
| 247 | |||
| 248 | if (!$this->canAccessShare($share, false)) { |
||
| 249 | throw new OCSNotFoundException($this->l->t('Could not delete share')); |
||
| 250 | } |
||
| 251 | |||
| 252 | $this->shareManager->deleteShare($share); |
||
| 253 | |||
| 254 | return new DataResponse(); |
||
| 255 | } |
||
| 256 | |||
| 257 | /** |
||
| 258 | * @NoAdminRequired |
||
| 259 | * |
||
| 260 | * @param string $path |
||
| 261 | * @param int $permissions |
||
| 262 | * @param int $shareType |
||
| 263 | * @param string $shareWith |
||
| 264 | * @param string $publicUpload |
||
| 265 | * @param string $password |
||
| 266 | * @param string $expireDate |
||
| 267 | * |
||
| 268 | * @return DataResponse |
||
| 269 | * @throws OCSNotFoundException |
||
| 270 | * @throws OCSForbiddenException |
||
| 271 | * @throws OCSBadRequestException |
||
| 272 | * @throws OCSException |
||
| 273 | */ |
||
| 274 | public function createShare( |
||
| 275 | $path = null, |
||
| 276 | $permissions = \OCP\Constants::PERMISSION_ALL, |
||
| 277 | $shareType = -1, |
||
| 278 | $shareWith = null, |
||
| 279 | $publicUpload = 'false', |
||
| 280 | $password = '', |
||
| 281 | $expireDate = '' |
||
| 282 | ) { |
||
| 283 | $share = $this->shareManager->newShare(); |
||
| 284 | |||
| 285 | // Verify path |
||
| 286 | if ($path === null) { |
||
| 287 | throw new OCSNotFoundException($this->l->t('Please specify a file or folder path')); |
||
| 288 | } |
||
| 289 | |||
| 290 | $userFolder = $this->rootFolder->getUserFolder($this->currentUser->getUID()); |
||
| 291 | try { |
||
| 292 | $path = $userFolder->get($path); |
||
| 293 | } catch (NotFoundException $e) { |
||
| 294 | throw new OCSNotFoundException($this->l->t('Wrong path, file/folder doesn\'t exist')); |
||
| 295 | } |
||
| 296 | |||
| 297 | $share->setNode($path); |
||
| 298 | |||
| 299 | try { |
||
| 300 | $this->lock($share->getNode()); |
||
| 301 | } catch (LockedException $e) { |
||
| 302 | throw new OCSNotFoundException($this->l->t('Could not create share')); |
||
| 303 | } |
||
| 304 | |||
| 305 | if ($permissions < 0 || $permissions > \OCP\Constants::PERMISSION_ALL) { |
||
| 306 | throw new OCSNotFoundException($this->l->t('invalid permissions')); |
||
| 307 | } |
||
| 308 | |||
| 309 | // Shares always require read permissions |
||
| 310 | $permissions |= \OCP\Constants::PERMISSION_READ; |
||
| 311 | |||
| 312 | if ($path instanceof \OCP\Files\File) { |
||
| 313 | // Single file shares should never have delete or create permissions |
||
| 314 | $permissions &= ~\OCP\Constants::PERMISSION_DELETE; |
||
| 315 | $permissions &= ~\OCP\Constants::PERMISSION_CREATE; |
||
| 316 | } |
||
| 317 | |||
| 318 | /* |
||
| 319 | * Hack for https://github.com/owncloud/core/issues/22587 |
||
| 320 | * We check the permissions via webdav. But the permissions of the mount point |
||
| 321 | * do not equal the share permissions. Here we fix that for federated mounts. |
||
| 322 | */ |
||
| 323 | if ($path->getStorage()->instanceOfStorage('OCA\Files_Sharing\External\Storage')) { |
||
| 324 | $permissions &= ~($permissions & ~$path->getPermissions()); |
||
| 325 | } |
||
| 326 | |||
| 327 | if ($shareType === \OCP\Share::SHARE_TYPE_USER) { |
||
| 328 | // Valid user is required to share |
||
| 329 | if ($shareWith === null || !$this->userManager->userExists($shareWith)) { |
||
| 330 | throw new OCSNotFoundException($this->l->t('Please specify a valid user')); |
||
| 331 | } |
||
| 332 | $share->setSharedWith($shareWith); |
||
| 333 | $share->setPermissions($permissions); |
||
| 334 | } else if ($shareType === \OCP\Share::SHARE_TYPE_GROUP) { |
||
| 335 | if (!$this->shareManager->allowGroupSharing()) { |
||
| 336 | throw new OCSNotFoundException($this->l->t('Group sharing is disabled by the administrator')); |
||
| 337 | } |
||
| 338 | |||
| 339 | // Valid group is required to share |
||
| 340 | if ($shareWith === null || !$this->groupManager->groupExists($shareWith)) { |
||
| 341 | throw new OCSNotFoundException($this->l->t('Please specify a valid group')); |
||
| 342 | } |
||
| 343 | $share->setSharedWith($shareWith); |
||
| 344 | $share->setPermissions($permissions); |
||
| 345 | } else if ($shareType === \OCP\Share::SHARE_TYPE_LINK) { |
||
| 346 | //Can we even share links? |
||
| 347 | if (!$this->shareManager->shareApiAllowLinks()) { |
||
| 348 | throw new OCSNotFoundException($this->l->t('Public link sharing is disabled by the administrator')); |
||
| 349 | } |
||
| 350 | |||
| 351 | /* |
||
| 352 | * For now we only allow 1 link share. |
||
| 353 | * Return the existing link share if this is a duplicate |
||
| 354 | */ |
||
| 355 | $existingShares = $this->shareManager->getSharesBy($this->currentUser->getUID(), \OCP\Share::SHARE_TYPE_LINK, $path, false, 1, 0); |
||
| 356 | if (!empty($existingShares)) { |
||
| 357 | return new DataResponse($this->formatShare($existingShares[0])); |
||
| 358 | } |
||
| 359 | |||
| 360 | if ($publicUpload === 'true') { |
||
| 361 | // Check if public upload is allowed |
||
| 362 | if (!$this->shareManager->shareApiLinkAllowPublicUpload()) { |
||
| 363 | throw new OCSForbiddenException($this->l->t('Public upload disabled by the administrator')); |
||
| 364 | } |
||
| 365 | |||
| 366 | // Public upload can only be set for folders |
||
| 367 | if ($path instanceof \OCP\Files\File) { |
||
| 368 | throw new OCSNotFoundException($this->l->t('Public upload is only possible for publicly shared folders')); |
||
| 369 | } |
||
| 370 | |||
| 371 | $share->setPermissions( |
||
| 372 | \OCP\Constants::PERMISSION_READ | |
||
| 373 | \OCP\Constants::PERMISSION_CREATE | |
||
| 374 | \OCP\Constants::PERMISSION_UPDATE | |
||
| 375 | \OCP\Constants::PERMISSION_DELETE |
||
| 376 | ); |
||
| 377 | } else { |
||
| 378 | $share->setPermissions(\OCP\Constants::PERMISSION_READ); |
||
| 379 | } |
||
| 380 | |||
| 381 | // Set password |
||
| 382 | if ($password !== '') { |
||
| 383 | $share->setPassword($password); |
||
| 384 | } |
||
| 385 | |||
| 386 | //Expire date |
||
| 387 | if ($expireDate !== '') { |
||
| 388 | try { |
||
| 389 | $expireDate = $this->parseDate($expireDate); |
||
| 390 | $share->setExpirationDate($expireDate); |
||
| 391 | } catch (\Exception $e) { |
||
| 392 | throw new OCSNotFoundException($this->l->t('Invalid date, date format must be YYYY-MM-DD')); |
||
| 393 | } |
||
| 394 | } |
||
| 395 | |||
| 396 | } else if ($shareType === \OCP\Share::SHARE_TYPE_REMOTE) { |
||
| 397 | if (!$this->shareManager->outgoingServer2ServerSharesAllowed()) { |
||
| 398 | throw new OCSForbiddenException($this->l->t('Sharing %s failed because the back end does not allow shares from type %s', [$path->getPath(), $shareType])); |
||
| 399 | } |
||
| 400 | |||
| 401 | $share->setSharedWith($shareWith); |
||
| 402 | $share->setPermissions($permissions); |
||
| 403 | } else { |
||
| 404 | throw new OCSBadRequestException($this->l->t('Unknown share type')); |
||
| 405 | } |
||
| 406 | |||
| 407 | $share->setShareType($shareType); |
||
| 408 | $share->setSharedBy($this->currentUser->getUID()); |
||
| 409 | |||
| 410 | try { |
||
| 411 | $share = $this->shareManager->createShare($share); |
||
| 412 | } catch (GenericShareException $e) { |
||
| 413 | $code = $e->getCode() === 0 ? 403 : $e->getCode(); |
||
| 414 | throw new OCSException($e->getHint(), $code); |
||
| 415 | } catch (\Exception $e) { |
||
| 416 | throw new OCSForbiddenException($e->getMessage()); |
||
| 417 | } |
||
| 418 | |||
| 419 | $output = $this->formatShare($share); |
||
| 420 | |||
| 421 | return new DataResponse($output); |
||
| 422 | } |
||
| 423 | |||
| 424 | /** |
||
| 425 | * @param \OCP\Files\File|\OCP\Files\Folder $node |
||
| 426 | * @return DataResponse |
||
| 427 | */ |
||
| 428 | private function getSharedWithMe($node = null) { |
||
| 429 | $userShares = $this->shareManager->getSharedWith($this->currentUser->getUID(), \OCP\Share::SHARE_TYPE_USER, $node, -1, 0); |
||
| 430 | $groupShares = $this->shareManager->getSharedWith($this->currentUser->getUID(), \OCP\Share::SHARE_TYPE_GROUP, $node, -1, 0); |
||
| 431 | |||
| 432 | $shares = array_merge($userShares, $groupShares); |
||
| 433 | |||
| 434 | $shares = array_filter($shares, function (IShare $share) { |
||
| 435 | return $share->getShareOwner() !== $this->currentUser->getUID(); |
||
| 436 | }); |
||
| 437 | |||
| 438 | $formatted = []; |
||
| 439 | foreach ($shares as $share) { |
||
| 440 | if ($this->canAccessShare($share)) { |
||
| 441 | try { |
||
| 442 | $formatted[] = $this->formatShare($share); |
||
| 443 | } catch (NotFoundException $e) { |
||
| 444 | // Ignore this share |
||
| 445 | } |
||
| 446 | } |
||
| 447 | } |
||
| 448 | |||
| 449 | return new DataResponse($formatted); |
||
| 450 | } |
||
| 451 | |||
| 452 | /** |
||
| 453 | * @param \OCP\Files\Folder $folder |
||
| 454 | * @return DataResponse |
||
| 455 | * @throws OCSBadRequestException |
||
| 456 | */ |
||
| 457 | private function getSharesInDir($folder) { |
||
| 485 | |||
| 486 | /** |
||
| 487 | * The getShares function. |
||
| 488 | * |
||
| 489 | * @NoAdminRequired |
||
| 490 | * |
||
| 491 | * @param string $shared_with_me |
||
| 492 | * @param string $reshares |
||
| 493 | * @param string $subfiles |
||
| 494 | * @param string $path |
||
| 495 | * |
||
| 496 | * - Get shares by the current user |
||
| 497 | * - Get shares by the current user and reshares (?reshares=true) |
||
| 498 | * - Get shares with the current user (?shared_with_me=true) |
||
| 499 | * - Get shares for a specific path (?path=...) |
||
| 500 | * - Get all shares in a folder (?subfiles=true&path=..) |
||
| 501 | * |
||
| 502 | * @return DataResponse |
||
| 503 | * @throws OCSNotFoundException |
||
| 504 | */ |
||
| 505 | public function getShares( |
||
| 562 | |||
| 563 | /** |
||
| 564 | * @NoAdminRequired |
||
| 565 | * |
||
| 566 | * @param int $id |
||
| 567 | * @param int $permissions |
||
| 568 | * @param string $password |
||
| 569 | * @param string $publicUpload |
||
| 570 | * @param string $expireDate |
||
| 571 | * @return DataResponse |
||
| 572 | * @throws OCSNotFoundException |
||
| 573 | * @throws OCSBadRequestException |
||
| 574 | * @throws OCSForbiddenException |
||
| 575 | */ |
||
| 576 | public function updateShare( |
||
| 701 | |||
| 702 | /** |
||
| 703 | * @param \OCP\Share\IShare $share |
||
| 704 | * @return bool |
||
| 705 | */ |
||
| 706 | protected function canAccessShare(\OCP\Share\IShare $share, $checkGroups = true) { |
||
| 707 | // A file with permissions 0 can't be accessed by us. So Don't show it |
||
| 708 | if ($share->getPermissions() === 0) { |
||
| 709 | return false; |
||
| 710 | } |
||
| 711 | |||
| 712 | // Owner of the file and the sharer of the file can always get share |
||
| 713 | if ($share->getShareOwner() === $this->currentUser->getUID() || |
||
| 714 | $share->getSharedBy() === $this->currentUser->getUID() |
||
| 715 | ) { |
||
| 716 | return true; |
||
| 717 | } |
||
| 718 | |||
| 719 | // If the share is shared with you (or a group you are a member of) |
||
| 720 | if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER && |
||
| 721 | $share->getSharedWith() === $this->currentUser->getUID() |
||
| 722 | ) { |
||
| 723 | return true; |
||
| 724 | } |
||
| 725 | |||
| 726 | if ($checkGroups && $share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) { |
||
| 727 | $sharedWith = $this->groupManager->get($share->getSharedWith()); |
||
| 728 | if ($sharedWith->inGroup($this->currentUser)) { |
||
| 729 | return true; |
||
| 730 | } |
||
| 731 | } |
||
| 732 | |||
| 733 | return false; |
||
| 734 | } |
||
| 735 | |||
| 736 | /** |
||
| 737 | * Make sure that the passed date is valid ISO 8601 |
||
| 738 | * So YYYY-MM-DD |
||
| 739 | * If not throw an exception |
||
| 740 | * |
||
| 741 | * @param string $expireDate |
||
| 742 | * |
||
| 743 | * @throws \Exception |
||
| 744 | * @return \DateTime |
||
| 745 | */ |
||
| 746 | private function parseDate($expireDate) { |
||
| 761 | |||
| 762 | /** |
||
| 763 | * Since we have multiple providers but the OCS Share API v1 does |
||
| 764 | * not support this we need to check all backends. |
||
| 765 | * |
||
| 766 | * @param string $id |
||
| 767 | * @return \OCP\Share\IShare |
||
| 768 | * @throws ShareNotFound |
||
| 769 | */ |
||
| 770 | private function getShareById($id) { |
||
| 786 | |||
| 787 | /** |
||
| 788 | * Lock a Node |
||
| 789 | * |
||
| 790 | * @param \OCP\Files\Node $node |
||
| 791 | */ |
||
| 792 | private function lock(\OCP\Files\Node $node) { |
||
| 796 | |||
| 797 | /** |
||
| 798 | * Cleanup the remaining locks |
||
| 799 | */ |
||
| 800 | public function cleanup() { |
||
| 805 | } |
||
| 806 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.