ShareController   F
last analyzed

Complexity

Total Complexity 70

Size/Duplication

Total Lines 533
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 239
c 0
b 0
f 0
dl 0
loc 533
rs 2.8
wmc 70

20 Methods

Rating   Name   Duplication   Size   Complexity  
A emitAccessShareHook() 0 27 4
A publishActivity() 0 12 1
A showAuthFailed() 0 14 2
A getPasswordHash() 0 2 1
B singleFileDownloaded() 0 42 7
A fileListDownloaded() 0 8 4
A __construct() 0 36 1
A verifyPassword() 0 2 1
A showAuthenticate() 0 14 2
A isPasswordProtected() 0 2 1
A authFailed() 0 3 1
A emitShareAccessEvent() 0 7 4
A generatePassword() 0 7 1
A validateShare() 0 14 6
F downloadShare() 0 110 18
A authSucceeded() 0 3 1
B showShare() 0 45 7
A isValidToken() 0 8 2
A showIdentificationResult() 0 14 2
A validateIdentity() 0 11 4

How to fix   Complexity   

Complex Class

Complex classes like ShareController 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 ShareController, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Arthur Schiwon <[email protected]>
6
 * @author Bjoern Schiessle <[email protected]>
7
 * @author Björn Schießle <[email protected]>
8
 * @author Christoph Wurst <[email protected]>
9
 * @author Daniel Calviño Sánchez <[email protected]>
10
 * @author Georg Ehrke <[email protected]>
11
 * @author j3l11234 <[email protected]>
12
 * @author Joas Schilling <[email protected]>
13
 * @author John Molakvoæ <[email protected]>
14
 * @author Jonas Sulzer <[email protected]>
15
 * @author Julius Härtl <[email protected]>
16
 * @author Lukas Reschke <[email protected]>
17
 * @author MartB <[email protected]>
18
 * @author Maxence Lange <[email protected]>
19
 * @author Michael Weimann <[email protected]>
20
 * @author Morris Jobke <[email protected]>
21
 * @author Piotr Filiciak <[email protected]>
22
 * @author Robin Appelman <[email protected]>
23
 * @author Roeland Jago Douma <[email protected]>
24
 * @author Sascha Sambale <[email protected]>
25
 * @author Thomas Müller <[email protected]>
26
 * @author Vincent Petry <[email protected]>
27
 *
28
 * @license AGPL-3.0
29
 *
30
 * This code is free software: you can redistribute it and/or modify
31
 * it under the terms of the GNU Affero General Public License, version 3,
32
 * as published by the Free Software Foundation.
33
 *
34
 * This program is distributed in the hope that it will be useful,
35
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
36
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
37
 * GNU Affero General Public License for more details.
38
 *
39
 * You should have received a copy of the GNU Affero General Public License, version 3,
40
 * along with this program. If not, see <http://www.gnu.org/licenses/>
41
 *
42
 */
43
namespace OCA\Files_Sharing\Controller;
44
45
use OC\Security\CSP\ContentSecurityPolicy;
46
use OC_Files;
47
use OC_Util;
48
use OCA\FederatedFileSharing\FederatedShareProvider;
49
use OCA\Files_Sharing\Activity\Providers\Downloads;
50
use OCA\Files_Sharing\Event\BeforeTemplateRenderedEvent;
51
use OCA\Files_Sharing\Event\ShareLinkAccessedEvent;
52
use OCA\Viewer\Event\LoadViewer;
0 ignored issues
show
Bug introduced by
The type OCA\Viewer\Event\LoadViewer was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
53
use OCP\Accounts\IAccountManager;
54
use OCP\AppFramework\AuthPublicShareController;
55
use OCP\AppFramework\Http\NotFoundResponse;
56
use OCP\AppFramework\Http\Template\ExternalShareMenuAction;
57
use OCP\AppFramework\Http\Template\LinkMenuAction;
58
use OCP\AppFramework\Http\Template\PublicTemplateResponse;
59
use OCP\AppFramework\Http\Template\SimpleMenuAction;
60
use OCP\AppFramework\Http\TemplateResponse;
61
use OCP\Defaults;
62
use OCP\EventDispatcher\IEventDispatcher;
63
use OCP\Files\Folder;
64
use OCP\Files\IRootFolder;
65
use OCP\Files\NotFoundException;
66
use OCP\IConfig;
67
use OCP\IL10N;
68
use OCP\ILogger;
69
use OCP\IPreview;
70
use OCP\IRequest;
71
use OCP\ISession;
72
use OCP\IURLGenerator;
73
use OCP\IUser;
74
use OCP\IUserManager;
75
use OCP\Security\ISecureRandom;
76
use OCP\Share;
77
use OCP\Share\Exceptions\ShareNotFound;
78
use OCP\Share\IManager as ShareManager;
79
use OCP\Share\IShare;
80
use OCP\Share\IPublicShareTemplateFactory;
81
use OCP\Template;
82
83
/**
84
 * Class ShareController
85
 *
86
 * @package OCA\Files_Sharing\Controllers
87
 */
88
class ShareController extends AuthPublicShareController {
89
	protected IConfig $config;
90
	protected IUserManager $userManager;
91
	protected ILogger $logger;
92
	protected \OCP\Activity\IManager $activityManager;
93
	protected IPreview $previewManager;
94
	protected IRootFolder $rootFolder;
95
	protected FederatedShareProvider $federatedShareProvider;
96
	protected IAccountManager $accountManager;
97
	protected IEventDispatcher $eventDispatcher;
98
	protected IL10N $l10n;
99
	protected Defaults $defaults;
100
	protected ShareManager $shareManager;
101
	protected ISecureRandom $secureRandom;
102
	protected ?Share\IShare $share = null;
103
	private IPublicShareTemplateFactory $publicShareTemplateFactory;
104
105
	public function __construct(
106
		string $appName,
107
		IRequest $request,
108
		IConfig $config,
109
		IURLGenerator $urlGenerator,
110
		IUserManager $userManager,
111
		ILogger $logger,
112
		\OCP\Activity\IManager $activityManager,
113
		ShareManager $shareManager,
114
		ISession $session,
115
		IPreview $previewManager,
116
		IRootFolder $rootFolder,
117
		FederatedShareProvider $federatedShareProvider,
118
		IAccountManager $accountManager,
119
		IEventDispatcher $eventDispatcher,
120
		IL10N $l10n,
121
		ISecureRandom $secureRandom,
122
		Defaults $defaults,
123
		IPublicShareTemplateFactory $publicShareTemplateFactory
124
	) {
125
		parent::__construct($appName, $request, $session, $urlGenerator);
126
127
		$this->config = $config;
128
		$this->userManager = $userManager;
129
		$this->logger = $logger;
130
		$this->activityManager = $activityManager;
131
		$this->previewManager = $previewManager;
132
		$this->rootFolder = $rootFolder;
133
		$this->federatedShareProvider = $federatedShareProvider;
134
		$this->accountManager = $accountManager;
135
		$this->eventDispatcher = $eventDispatcher;
136
		$this->l10n = $l10n;
137
		$this->secureRandom = $secureRandom;
138
		$this->defaults = $defaults;
139
		$this->shareManager = $shareManager;
140
		$this->publicShareTemplateFactory = $publicShareTemplateFactory;
141
	}
142
143
	public const SHARE_ACCESS = 'access';
144
	public const SHARE_AUTH = 'auth';
145
	public const SHARE_DOWNLOAD = 'download';
146
147
	/**
148
	 * @PublicPage
149
	 * @NoCSRFRequired
150
	 *
151
	 * Show the authentication page
152
	 * The form has to submit to the authenticate method route
153
	 */
154
	public function showAuthenticate(): TemplateResponse {
155
		$templateParameters = ['share' => $this->share];
156
157
		$this->eventDispatcher->dispatchTyped(new BeforeTemplateRenderedEvent($this->share, BeforeTemplateRenderedEvent::SCOPE_PUBLIC_SHARE_AUTH));
158
159
		$response = new TemplateResponse('core', 'publicshareauth', $templateParameters, 'guest');
160
		if ($this->share->getSendPasswordByTalk()) {
0 ignored issues
show
Bug introduced by
The method getSendPasswordByTalk() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

160
		if ($this->share->/** @scrutinizer ignore-call */ getSendPasswordByTalk()) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
161
			$csp = new ContentSecurityPolicy();
162
			$csp->addAllowedConnectDomain('*');
163
			$csp->addAllowedMediaDomain('blob:');
164
			$response->setContentSecurityPolicy($csp);
165
		}
166
167
		return $response;
168
	}
169
170
	/**
171
	 * The template to show when authentication failed
172
	 */
173
	protected function showAuthFailed(): TemplateResponse {
174
		$templateParameters = ['share' => $this->share, 'wrongpw' => true];
175
176
		$this->eventDispatcher->dispatchTyped(new BeforeTemplateRenderedEvent($this->share, BeforeTemplateRenderedEvent::SCOPE_PUBLIC_SHARE_AUTH));
177
178
		$response = new TemplateResponse('core', 'publicshareauth', $templateParameters, 'guest');
179
		if ($this->share->getSendPasswordByTalk()) {
180
			$csp = new ContentSecurityPolicy();
181
			$csp->addAllowedConnectDomain('*');
182
			$csp->addAllowedMediaDomain('blob:');
183
			$response->setContentSecurityPolicy($csp);
184
		}
185
186
		return $response;
187
	}
188
189
	/**
190
	 * The template to show after user identification
191
	 */
192
	protected function showIdentificationResult(bool $success = false): TemplateResponse {
193
		$templateParameters = ['share' => $this->share, 'identityOk' => $success];
194
195
		$this->eventDispatcher->dispatchTyped(new BeforeTemplateRenderedEvent($this->share, BeforeTemplateRenderedEvent::SCOPE_PUBLIC_SHARE_AUTH));
196
197
		$response = new TemplateResponse('core', 'publicshareauth', $templateParameters, 'guest');
198
		if ($this->share->getSendPasswordByTalk()) {
199
			$csp = new ContentSecurityPolicy();
200
			$csp->addAllowedConnectDomain('*');
201
			$csp->addAllowedMediaDomain('blob:');
202
			$response->setContentSecurityPolicy($csp);
203
		}
204
205
		return $response;
206
	}
207
208
	/**
209
	 * Validate the identity token of a public share
210
	 *
211
	 * @param ?string $identityToken
212
	 * @return bool
213
	 */
214
	protected function validateIdentity(?string $identityToken = null): bool {
215
216
		if ($this->share->getShareType() !== IShare::TYPE_EMAIL) {
217
			return false;
218
		}
219
220
		if ($identityToken === null || $this->share->getSharedWith() === null) {
221
			return false;
222
		}
223
224
		return $identityToken === $this->share->getSharedWith();
225
	}
226
227
	/**
228
	 * Generates a password for the share, respecting any password policy defined
229
	 */
230
	protected function generatePassword(): void {
231
		$event = new \OCP\Security\Events\GenerateSecurePasswordEvent();
232
		$this->eventDispatcher->dispatchTyped($event);
233
		$password = $event->getPassword() ?? $this->secureRandom->generate(20);
234
235
		$this->share->setPassword($password);
236
		$this->shareManager->updateShare($this->share);
237
	}
238
239
	protected function verifyPassword(string $password): bool {
240
		return $this->shareManager->checkPassword($this->share, $password);
241
	}
242
243
	protected function getPasswordHash(): string {
244
		return $this->share->getPassword();
245
	}
246
247
	public function isValidToken(): bool {
248
		try {
249
			$this->share = $this->shareManager->getShareByToken($this->getToken());
250
		} catch (ShareNotFound $e) {
251
			return false;
252
		}
253
254
		return true;
255
	}
256
257
	protected function isPasswordProtected(): bool {
258
		return $this->share->getPassword() !== null;
259
	}
260
261
	protected function authSucceeded() {
262
		// For share this was always set so it is still used in other apps
263
		$this->session->set('public_link_authenticated', (string)$this->share->getId());
264
	}
265
266
	protected function authFailed() {
267
		$this->emitAccessShareHook($this->share, 403, 'Wrong password');
268
		$this->emitShareAccessEvent($this->share, self::SHARE_AUTH, 403, 'Wrong password');
269
	}
270
271
	/**
272
	 * throws hooks when a share is attempted to be accessed
273
	 *
274
	 * @param \OCP\Share\IShare|string $share the Share instance if available,
275
	 * otherwise token
276
	 * @param int $errorCode
277
	 * @param string $errorMessage
278
	 *
279
	 * @throws \OCP\HintException
280
	 * @throws \OC\ServerNotAvailableException
281
	 *
282
	 * @deprecated use OCP\Files_Sharing\Event\ShareLinkAccessedEvent
283
	 */
284
	protected function emitAccessShareHook($share, int $errorCode = 200, string $errorMessage = '') {
285
		$itemType = $itemSource = $uidOwner = '';
286
		$token = $share;
287
		$exception = null;
288
		if ($share instanceof \OCP\Share\IShare) {
289
			try {
290
				$token = $share->getToken();
291
				$uidOwner = $share->getSharedBy();
292
				$itemType = $share->getNodeType();
293
				$itemSource = $share->getNodeId();
294
			} catch (\Exception $e) {
295
				// we log what we know and pass on the exception afterwards
296
				$exception = $e;
297
			}
298
		}
299
300
		\OC_Hook::emit(Share::class, 'share_link_access', [
301
			'itemType' => $itemType,
302
			'itemSource' => $itemSource,
303
			'uidOwner' => $uidOwner,
304
			'token' => $token,
305
			'errorCode' => $errorCode,
306
			'errorMessage' => $errorMessage
307
		]);
308
309
		if (!is_null($exception)) {
310
			throw $exception;
311
		}
312
	}
313
314
	/**
315
	 * Emit a ShareLinkAccessedEvent event when a share is accessed, downloaded, auth...
316
	 */
317
	protected function emitShareAccessEvent(IShare $share, string $step = '', int $errorCode = 200, string $errorMessage = ''): void {
318
		if ($step !== self::SHARE_ACCESS &&
319
			$step !== self::SHARE_AUTH &&
320
			$step !== self::SHARE_DOWNLOAD) {
321
			return;
322
		}
323
		$this->eventDispatcher->dispatchTyped(new ShareLinkAccessedEvent($share, $step, $errorCode, $errorMessage));
324
	}
325
326
	/**
327
	 * Validate the permissions of the share
328
	 *
329
	 * @param Share\IShare $share
330
	 * @return bool
331
	 */
332
	private function validateShare(\OCP\Share\IShare $share) {
333
		// If the owner is disabled no access to the link is granted
334
		$owner = $this->userManager->get($share->getShareOwner());
335
		if ($owner === null || !$owner->isEnabled()) {
336
			return false;
337
		}
338
339
		// If the initiator of the share is disabled no access is granted
340
		$initiator = $this->userManager->get($share->getSharedBy());
341
		if ($initiator === null || !$initiator->isEnabled()) {
342
			return false;
343
		}
344
345
		return $share->getNode()->isReadable() && $share->getNode()->isShareable();
346
	}
347
348
	/**
349
	 * @PublicPage
350
	 * @NoCSRFRequired
351
	 *
352
	 *
353
	 * @param string $path
354
	 * @return TemplateResponse
355
	 * @throws NotFoundException
356
	 * @throws \Exception
357
	 */
358
	public function showShare($path = ''): TemplateResponse {
359
		\OC_User::setIncognitoMode(true);
360
361
		// Check whether share exists
362
		try {
363
			$share = $this->shareManager->getShareByToken($this->getToken());
364
		} catch (ShareNotFound $e) {
365
			// The share does not exists, we do not emit an ShareLinkAccessedEvent
366
			$this->emitAccessShareHook($this->getToken(), 404, 'Share not found');
367
			throw new NotFoundException();
368
		}
369
370
		if (!$this->validateShare($share)) {
371
			throw new NotFoundException();
372
		}
373
374
		$shareNode = $share->getNode();
375
376
		try {
377
			$templateProvider = $this->publicShareTemplateFactory->getProvider($share);
378
			$response = $templateProvider->renderPage($share, $this->getToken(), $path);
379
		} catch (NotFoundException $e) {
380
			$this->emitAccessShareHook($share, 404, 'Share not found');
381
			$this->emitShareAccessEvent($share, ShareController::SHARE_ACCESS, 404, 'Share not found');
382
			throw new NotFoundException();
383
		}
384
385
		// We can't get the path of a file share
386
		try {
387
			if ($shareNode instanceof \OCP\Files\File && $path !== '') {
388
				$this->emitAccessShareHook($share, 404, 'Share not found');
389
				$this->emitShareAccessEvent($share, self::SHARE_ACCESS, 404, 'Share not found');
390
				throw new NotFoundException();
391
			}
392
		} catch (\Exception $e) {
393
			$this->emitAccessShareHook($share, 404, 'Share not found');
394
			$this->emitShareAccessEvent($share, self::SHARE_ACCESS, 404, 'Share not found');
395
			throw $e;
396
		}
397
398
399
		$this->emitAccessShareHook($share);
400
		$this->emitShareAccessEvent($share, self::SHARE_ACCESS);
401
402
		return $response;
403
	}
404
405
	/**
406
	 * @PublicPage
407
	 * @NoCSRFRequired
408
	 * @NoSameSiteCookieRequired
409
	 *
410
	 * @param string $token
411
	 * @param string $files
412
	 * @param string $path
413
	 * @param string $downloadStartSecret
414
	 * @return void|\OCP\AppFramework\Http\Response
415
	 * @throws NotFoundException
416
	 */
417
	public function downloadShare($token, $files = null, $path = '', $downloadStartSecret = '') {
418
		\OC_User::setIncognitoMode(true);
419
420
		$share = $this->shareManager->getShareByToken($token);
421
422
		if (!($share->getPermissions() & \OCP\Constants::PERMISSION_READ)) {
423
			return new \OCP\AppFramework\Http\DataResponse('Share has no read permission');
424
		}
425
426
		$files_list = null;
427
		if (!is_null($files)) { // download selected files
428
			$files_list = json_decode($files);
429
			// in case we get only a single file
430
			if ($files_list === null) {
431
				$files_list = [$files];
432
			}
433
			// Just in case $files is a single int like '1234'
434
			if (!is_array($files_list)) {
435
				$files_list = [$files_list];
436
			}
437
		}
438
439
		if (!$this->validateShare($share)) {
440
			throw new NotFoundException();
441
		}
442
443
		$userFolder = $this->rootFolder->getUserFolder($share->getShareOwner());
444
		$originalSharePath = $userFolder->getRelativePath($share->getNode()->getPath());
445
446
447
		// Single file share
448
		if ($share->getNode() instanceof \OCP\Files\File) {
449
			// Single file download
450
			$this->singleFileDownloaded($share, $share->getNode());
451
		}
452
		// Directory share
453
		else {
454
			/** @var \OCP\Files\Folder $node */
455
			$node = $share->getNode();
456
457
			// Try to get the path
458
			if ($path !== '') {
459
				try {
460
					$node = $node->get($path);
461
				} catch (NotFoundException $e) {
462
					$this->emitAccessShareHook($share, 404, 'Share not found');
463
					$this->emitShareAccessEvent($share, self::SHARE_DOWNLOAD, 404, 'Share not found');
464
					return new NotFoundResponse();
465
				}
466
			}
467
468
			$originalSharePath = $userFolder->getRelativePath($node->getPath());
469
470
			if ($node instanceof \OCP\Files\File) {
471
				// Single file download
472
				$this->singleFileDownloaded($share, $share->getNode());
473
			} else {
474
				try {
475
					if (!empty($files_list)) {
476
						$this->fileListDownloaded($share, $files_list, $node);
477
					} else {
478
						// The folder is downloaded
479
						$this->singleFileDownloaded($share, $share->getNode());
480
					}
481
				} catch (NotFoundException $e) {
482
					return new NotFoundResponse();
483
				}
484
			}
485
		}
486
487
		/* FIXME: We should do this all nicely in OCP */
488
		OC_Util::tearDownFS();
489
		OC_Util::setupFS($share->getShareOwner());
490
491
		/**
492
		 * this sets a cookie to be able to recognize the start of the download
493
		 * the content must not be longer than 32 characters and must only contain
494
		 * alphanumeric characters
495
		 */
496
		if (!empty($downloadStartSecret)
497
			&& !isset($downloadStartSecret[32])
498
			&& preg_match('!^[a-zA-Z0-9]+$!', $downloadStartSecret) === 1) {
499
500
			// FIXME: set on the response once we use an actual app framework response
501
			setcookie('ocDownloadStarted', $downloadStartSecret, time() + 20, '/');
502
		}
503
504
		$this->emitAccessShareHook($share);
505
		$this->emitShareAccessEvent($share, self::SHARE_DOWNLOAD);
506
507
		$server_params = [ 'head' => $this->request->getMethod() === 'HEAD' ];
508
509
		/**
510
		 * Http range requests support
511
		 */
512
		if (isset($_SERVER['HTTP_RANGE'])) {
513
			$server_params['range'] = $this->request->getHeader('Range');
514
		}
515
516
		// download selected files
517
		if (!is_null($files) && $files !== '') {
518
			// FIXME: The exit is required here because otherwise the AppFramework is trying to add headers as well
519
			// after dispatching the request which results in a "Cannot modify header information" notice.
520
			OC_Files::get($originalSharePath, $files_list, $server_params);
521
			exit();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
522
		} else {
523
			// FIXME: The exit is required here because otherwise the AppFramework is trying to add headers as well
524
			// after dispatching the request which results in a "Cannot modify header information" notice.
525
			OC_Files::get(dirname($originalSharePath), basename($originalSharePath), $server_params);
526
			exit();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
527
		}
528
	}
529
530
	/**
531
	 * create activity for every downloaded file
532
	 *
533
	 * @param Share\IShare $share
534
	 * @param array $files_list
535
	 * @param \OCP\Files\Folder $node
536
	 * @throws NotFoundException when trying to download a folder or multiple files of a "hide download" share
537
	 */
538
	protected function fileListDownloaded(Share\IShare $share, array $files_list, \OCP\Files\Folder $node) {
539
		if ($share->getHideDownload() && count($files_list) > 1) {
540
			throw new NotFoundException('Downloading more than 1 file');
541
		}
542
543
		foreach ($files_list as $file) {
544
			$subNode = $node->get($file);
545
			$this->singleFileDownloaded($share, $subNode);
546
		}
547
	}
548
549
	/**
550
	 * create activity if a single file was downloaded from a link share
551
	 *
552
	 * @param Share\IShare $share
553
	 * @throws NotFoundException when trying to download a folder of a "hide download" share
554
	 */
555
	protected function singleFileDownloaded(Share\IShare $share, \OCP\Files\Node $node) {
556
		if ($share->getHideDownload() && $node instanceof Folder) {
557
			throw new NotFoundException('Downloading a folder');
558
		}
559
560
		$fileId = $node->getId();
561
562
		$userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
563
		$userNodeList = $userFolder->getById($fileId);
564
		$userNode = $userNodeList[0];
565
		$ownerFolder = $this->rootFolder->getUserFolder($share->getShareOwner());
566
		$userPath = $userFolder->getRelativePath($userNode->getPath());
567
		$ownerPath = $ownerFolder->getRelativePath($node->getPath());
568
		$remoteAddress = $this->request->getRemoteAddress();
569
		$dateTime = new \DateTime();
570
		$dateTime = $dateTime->format('Y-m-d H');
571
		$remoteAddressHash = md5($dateTime . '-' . $remoteAddress);
572
573
		$parameters = [$userPath];
574
575
		if ($share->getShareType() === IShare::TYPE_EMAIL) {
576
			if ($node instanceof \OCP\Files\File) {
577
				$subject = Downloads::SUBJECT_SHARED_FILE_BY_EMAIL_DOWNLOADED;
578
			} else {
579
				$subject = Downloads::SUBJECT_SHARED_FOLDER_BY_EMAIL_DOWNLOADED;
580
			}
581
			$parameters[] = $share->getSharedWith();
582
		} else {
583
			if ($node instanceof \OCP\Files\File) {
584
				$subject = Downloads::SUBJECT_PUBLIC_SHARED_FILE_DOWNLOADED;
585
				$parameters[] = $remoteAddressHash;
586
			} else {
587
				$subject = Downloads::SUBJECT_PUBLIC_SHARED_FOLDER_DOWNLOADED;
588
				$parameters[] = $remoteAddressHash;
589
			}
590
		}
591
592
		$this->publishActivity($subject, $parameters, $share->getSharedBy(), $fileId, $userPath);
593
594
		if ($share->getShareOwner() !== $share->getSharedBy()) {
595
			$parameters[0] = $ownerPath;
596
			$this->publishActivity($subject, $parameters, $share->getShareOwner(), $fileId, $ownerPath);
597
		}
598
	}
599
600
	/**
601
	 * publish activity
602
	 *
603
	 * @param string $subject
604
	 * @param array $parameters
605
	 * @param string $affectedUser
606
	 * @param int $fileId
607
	 * @param string $filePath
608
	 */
609
	protected function publishActivity($subject,
610
										array $parameters,
611
										$affectedUser,
612
										$fileId,
613
										$filePath) {
614
		$event = $this->activityManager->generateEvent();
615
		$event->setApp('files_sharing')
616
			->setType('public_links')
617
			->setSubject($subject, $parameters)
618
			->setAffectedUser($affectedUser)
619
			->setObject('files', $fileId, $filePath);
620
		$this->activityManager->publish($event);
621
	}
622
}
623